init prisma and migrate sns to prisma
This commit is contained in:
parent
a5c90f7a26
commit
a7fdedd310
|
|
@ -1,4 +0,0 @@
|
||||||
version: 3.7
|
|
||||||
services:
|
|
||||||
s3_provider:
|
|
||||||
image: minio
|
|
||||||
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
|
|
@ -10,49 +10,28 @@
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^9.3.10",
|
"@nestjs/common": "^10.4.15",
|
||||||
"@nestjs/config": "^2.3.1",
|
"@nestjs/config": "^3.3.0",
|
||||||
"@nestjs/core": "^9.3.10",
|
"@nestjs/core": "^10.4.15",
|
||||||
"@nestjs/platform-express": "^9.3.10",
|
"@nestjs/platform-express": "^10.4.15",
|
||||||
"@nestjs/typeorm": "^9.0.1",
|
"@prisma/client": "^6.1.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
|
"execa": "^9.5.2",
|
||||||
"joi": "^17.9.0",
|
"joi": "^17.9.0",
|
||||||
"js2xmlparser": "^5.0.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
"morgan": "^1.10.0",
|
|
||||||
"rxjs": "^7.8.0",
|
"rxjs": "^7.8.0",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"typeorm": "^0.3.12",
|
|
||||||
"uuidv4": "^6.2.13"
|
"uuidv4": "^6.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-sns": "^3.321.1",
|
|
||||||
"@nestjs/cli": "^9.3.0",
|
|
||||||
"@nestjs/testing": "^9.4.0",
|
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.1",
|
"@types/joi": "^17.2.2",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/node": "^22.10.2",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
"jest": "^29.5.0",
|
"prisma": "^6.1.0"
|
||||||
"supertest": "^6.3.3",
|
|
||||||
"ts-jest": "^29.1.0"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"engines": {
|
||||||
"globalSetup": "./_jest_/setup.ts",
|
"node": ">=22.11.0",
|
||||||
"globalTeardown": "./_jest_/teardown.ts",
|
"npm": ">=10.9.0"
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"rootDir": "src",
|
|
||||||
"testRegex": ".*\\.*spec\\.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
|
||||||
},
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"**/*.(t|j)s"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "../coverage",
|
|
||||||
"testEnvironment": "node"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = ":memory:"
|
||||||
|
}
|
||||||
|
|
||||||
|
model Attribute {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
arn String
|
||||||
|
name String
|
||||||
|
value String
|
||||||
|
|
||||||
|
@@index([arn])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Audit {
|
||||||
|
id String @id
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
action String?
|
||||||
|
request String?
|
||||||
|
response String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model SnsTopic {
|
||||||
|
name String @id
|
||||||
|
accountId String
|
||||||
|
region String
|
||||||
|
}
|
||||||
|
|
||||||
|
model SnsTopicSubscription {
|
||||||
|
id String @id
|
||||||
|
topicArn String
|
||||||
|
endpoint String?
|
||||||
|
protocol String
|
||||||
|
accountId String
|
||||||
|
region String
|
||||||
|
}
|
||||||
|
|
||||||
|
model Tag {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
arn String
|
||||||
|
name String
|
||||||
|
value String
|
||||||
|
|
||||||
|
@@index([arn])
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppModule } from '../app.module';
|
|
||||||
|
|
||||||
const globalSetup = async (_globalConfig, _projectConfig) => {
|
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [AppModule],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
const app: INestApplication = module.createNestApplication();
|
|
||||||
await app.listen(4566);
|
|
||||||
|
|
||||||
globalThis.__TESTMODULE__ = module;
|
|
||||||
globalThis.__NESTAPP__ = app;
|
|
||||||
globalThis.__ENDPOINT__ = 'http://127.0.0.1:4566';
|
|
||||||
}
|
|
||||||
|
|
||||||
export default globalSetup;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
|
|
||||||
const globalTeardown = async (_globalConfig, _projectConfig) => {
|
|
||||||
|
|
||||||
await (globalThis.__NESTAPP__ as INestApplication).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default globalTeardown;
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { PrismaService } from "./prisma.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
providers: [PrismaService],
|
||||||
|
exports: [PrismaService],
|
||||||
|
})
|
||||||
|
export class PrismaModule {}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { OnModuleInit } from "@nestjs/common";
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.$connect();
|
||||||
|
const tables = await this.$queryRawUnsafe('.tables');
|
||||||
|
console.log({ tables })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,21 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { ActionHandlers } from './app.constants';
|
import { ActionHandlers } from './app.constants';
|
||||||
import { CommonConfig } from './config/common-config.interface';
|
import { AppController } from './app.controller';
|
||||||
|
import { AuditInterceptor } from './audit/audit.interceptor';
|
||||||
|
import { AwsSharedEntitiesModule } from './aws-shared-entities/aws-shared-entities.module';
|
||||||
import localConfig from './config/local.config';
|
import localConfig from './config/local.config';
|
||||||
|
import { IAMHandlers } from './iam/iam.constants';
|
||||||
|
import { IamModule } from './iam/iam.module';
|
||||||
|
import { KMSHandlers } from './kms/kms.constants';
|
||||||
|
import { KmsModule } from './kms/kms.module';
|
||||||
|
import { SecretsManagerHandlers } from './secrets-manager/secrets-manager.constants';
|
||||||
|
import { SecretsManagerModule } from './secrets-manager/secrets-manager.module';
|
||||||
import { SnsHandlers } from './sns/sns.constants';
|
import { SnsHandlers } from './sns/sns.constants';
|
||||||
import { SnsModule } from './sns/sns.module';
|
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';
|
|
||||||
import { SqsModule } from './sqs/sqs.module';
|
|
||||||
import { SqsHandlers } from './sqs/sqs.constants';
|
import { SqsHandlers } from './sqs/sqs.constants';
|
||||||
import { Audit } from './audit/audit.entity';
|
import { SqsModule } from './sqs/sqs.module';
|
||||||
import { AuditInterceptor } from './audit/audit.interceptor';
|
|
||||||
import { KmsModule } from './kms/kms.module';
|
|
||||||
import { KMSHandlers } from './kms/kms.constants';
|
|
||||||
import { configValidator } from './config/config.validator';
|
|
||||||
import { IamModule } from './iam/iam.module';
|
|
||||||
import { IAMHandlers } from './iam/iam.constants';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -26,17 +23,6 @@ import { IAMHandlers } from './iam/iam.constants';
|
||||||
load: [localConfig],
|
load: [localConfig],
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
TypeOrmModule.forRootAsync({
|
|
||||||
inject: [ConfigService],
|
|
||||||
useFactory: (configService: ConfigService<CommonConfig>) => ({
|
|
||||||
type: 'sqlite',
|
|
||||||
database: configService.get('DB_DATABASE') === ':memory:' ? configService.get('DB_DATABASE') : `${__dirname}/../data/${configService.get('DB_DATABASE')}`,
|
|
||||||
logging: configService.get('DB_LOGGING'),
|
|
||||||
synchronize: configService.get('DB_SYNCHRONIZE'),
|
|
||||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
TypeOrmModule.forFeature([Audit]),
|
|
||||||
IamModule,
|
IamModule,
|
||||||
KmsModule,
|
KmsModule,
|
||||||
SecretsManagerModule,
|
SecretsManagerModule,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm';
|
|
||||||
|
|
||||||
@Entity('audit')
|
|
||||||
export class Audit extends BaseEntity {
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
action: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
request: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
response: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +1,68 @@
|
||||||
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
|
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { randomUUID } from 'crypto';
|
||||||
import { Observable, tap } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { Repository } from 'typeorm';
|
import { Request as ExpressRequest } from 'express';
|
||||||
import { Audit } from './audit.entity';
|
import * as Joi from 'joi';
|
||||||
import * as uuid from 'uuid';
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { ActionHandlers } from '../app.constants';
|
import { ActionHandlers } from '../app.constants';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
|
||||||
|
interface Request extends ExpressRequest {
|
||||||
|
headers: {
|
||||||
|
'x-amz-target'?: string;
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
'Action'?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuditInterceptor<T> implements NestInterceptor<T, Response> {
|
export class AuditInterceptor<T> implements NestInterceptor<T, Response> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Audit)
|
|
||||||
private readonly auditRepo: Repository<Audit>,
|
|
||||||
@Inject(ActionHandlers)
|
@Inject(ActionHandlers)
|
||||||
private readonly handlers: ActionHandlers,
|
private readonly handlers: ActionHandlers,
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
|
||||||
|
|
||||||
|
const requestId = randomUUID();
|
||||||
const requestId = uuid.v4();
|
|
||||||
const httpContext = context.switchToHttp();
|
const httpContext = context.switchToHttp();
|
||||||
|
const request = httpContext.getRequest<Request>();
|
||||||
|
|
||||||
const request = httpContext.getRequest();
|
const hasTargetHeader = Object.keys(request.headers).some( k => k.toLocaleLowerCase() === 'x-amz-target');
|
||||||
const targetHeaderKey = Object.keys(request.headers).find( k => k.toLocaleLowerCase() === 'x-amz-target');
|
const action = hasTargetHeader ? request.headers['x-amz-target'] : request.body.Action;
|
||||||
const action = request.headers[targetHeaderKey] ? request.headers[targetHeaderKey] : request.body.Action;
|
const { value: resolvedAction } = Joi.string().required().valid(...Object.values(Action)).validate(action) as { value: Action | undefined };
|
||||||
|
|
||||||
const response = context.switchToHttp().getResponse();
|
const response = context.switchToHttp().getResponse();
|
||||||
|
|
||||||
response.header('x-amzn-RequestId', requestId);
|
response.header('x-amzn-RequestId', requestId);
|
||||||
|
|
||||||
if (!this.handlers[action]?.audit) {
|
if (!resolvedAction || !this.handlers[resolvedAction]?.audit) {
|
||||||
return next.handle();
|
return next.handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
tap({
|
tap({
|
||||||
|
|
||||||
next: async (data) => await this.auditRepo.create({
|
next: async (data) => await this.prismaService.audit.create({
|
||||||
|
data: {
|
||||||
id: requestId,
|
id: requestId,
|
||||||
action,
|
action,
|
||||||
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
||||||
response: JSON.stringify(data),
|
response: JSON.stringify(data),
|
||||||
}).save(),
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
error: async (error) => await this.auditRepo.create({
|
error: async (error) => await this.prismaService.audit.create({
|
||||||
|
data: {
|
||||||
id: requestId,
|
id: requestId,
|
||||||
action,
|
action,
|
||||||
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
||||||
response: JSON.stringify(error),
|
response: JSON.stringify(error),
|
||||||
}).save(),
|
}
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { Attribute, Prisma } from '@prisma/client';
|
||||||
import { In, Repository } from 'typeorm';
|
|
||||||
import { Attribute } from './attributes.entity';
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { CreateAttributeDto } from './create-attribute.dto';
|
import { breakdownAwsQueryParam } from '../util/breakdown-aws-query-param';
|
||||||
|
|
||||||
const ResourcePolicyName = 'ResourcePolicy';
|
const ResourcePolicyName = 'ResourcePolicy';
|
||||||
|
|
||||||
|
|
@ -10,62 +10,75 @@ const ResourcePolicyName = 'ResourcePolicy';
|
||||||
export class AttributesService {
|
export class AttributesService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Attribute)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly repo: Repository<Attribute>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getByArn(arn: string): Promise<Attribute[]> {
|
async getByArn(arn: string): Promise<Attribute[]> {
|
||||||
return await this.repo.find({ where: { arn }});
|
return await this.prismaService.attribute.findMany({ where: { arn }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResourcePolicyByArn(arn: string): Promise<Attribute> {
|
async getResourcePolicyByArn(arn: string): Promise<Attribute | null> {
|
||||||
return await this.repo.findOne({ where: { arn, name: ResourcePolicyName }});
|
return await this.prismaService.attribute.findFirst({ where: { arn, name: ResourcePolicyName }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByArnAndName(arn: string, name: string): Promise<Attribute> {
|
async getByArnAndName(arn: string, name: string): Promise<Attribute | null> {
|
||||||
return await this.repo.findOne({ where: { arn, name }});
|
return await this.prismaService.attribute.findFirst({ where: { arn, name }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByArnAndNames(arn: string, names: string[]): Promise<Attribute[]> {
|
async getByArnAndNames(arn: string, names: string[]): Promise<Attribute[]> {
|
||||||
return await this.repo.find({ where: { arn, name: In(names) }});
|
return await this.prismaService.attribute.findMany({ where: {
|
||||||
|
arn,
|
||||||
|
name: {
|
||||||
|
in: names
|
||||||
|
}
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createResourcePolicy(arn: string, value: string): Promise<Attribute> {
|
async createResourcePolicy(arn: string, value: string): Promise<Attribute> {
|
||||||
return await this.create({arn, value, name: ResourcePolicyName });
|
return await this.create({arn, value, name: ResourcePolicyName });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(dto: CreateAttributeDto): Promise<Attribute> {
|
async create(data: Prisma.AttributeCreateArgs['data']): Promise<Attribute> {
|
||||||
return await this.repo.save(dto);
|
return await this.prismaService.attribute.create({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByArn(arn: string) {
|
async deleteByArn(arn: string): Promise<void> {
|
||||||
await this.repo.delete({ arn });
|
await this.prismaService.attribute.deleteMany({ where: { arn } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByArnAndName(arn: string, name: string) {
|
async deleteByArnAndName(arn: string, name: string): Promise<void> {
|
||||||
await this.repo.delete({ arn, name });
|
await this.prismaService.attribute.deleteMany({ where: { arn, name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
||||||
for (const record of records) {
|
await this.prismaService.attribute.createMany({
|
||||||
await this.create({ arn, name: record.key, value: record.value });
|
data: records.map(r => ({
|
||||||
}
|
name: r.key,
|
||||||
|
value: r.value,
|
||||||
|
arn,
|
||||||
|
}))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static attributePairs(queryParams: Record<string, string>): { key: string, value: string }[] {
|
static attributePairs(queryParams: Record<string, string>): { key: string, value: string }[] {
|
||||||
const pairs = [null];
|
const pairs: { key: string, value: string }[] = [];
|
||||||
for (const param of Object.keys(queryParams)) {
|
for (const param of Object.keys(queryParams)) {
|
||||||
const [type, _, idx, slot] = param.split('.');
|
const components = breakdownAwsQueryParam(param);
|
||||||
|
|
||||||
|
if (!components) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [type, _, idx, slot] = components;
|
||||||
|
|
||||||
if (type === 'Attributes') {
|
if (type === 'Attributes') {
|
||||||
if (!pairs[+idx]) {
|
if (!pairs[idx]) {
|
||||||
pairs[+idx] = { key: '', value: ''};
|
pairs[idx] = { key: '', value: ''};
|
||||||
}
|
}
|
||||||
pairs[+idx][slot] = queryParams[param];
|
pairs[idx][slot] = queryParams[param];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs.shift();
|
|
||||||
return pairs;
|
return pairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export interface CreateAttributeDto {
|
|
||||||
arn: string;
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +1,61 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { Prisma, Tag } from '@prisma/client';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { Tag } from './tags.entity';
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { CreateTagDto } from './create-tag.dto';
|
import { breakdownAwsQueryParam } from '../util/breakdown-aws-query-param';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TagsService {
|
export class TagsService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Tag)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly repo: Repository<Tag>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getByArn(arn: string): Promise<Tag[]> {
|
async getByArn(arn: string): Promise<Tag[]> {
|
||||||
return await this.repo.find({ where: { arn }});
|
return await this.prismaService.tag.findMany({ where: { arn }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(dto: CreateTagDto): Promise<Tag> {
|
async create(data: Prisma.TagCreateArgs['data']): Promise<Tag> {
|
||||||
return await this.repo.save(dto);
|
return await this.prismaService.tag.create({ data })
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMany(arn: string, records: { Key: string, Value: string }[]): Promise<void> {
|
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
||||||
for (const record of records) {
|
await this.prismaService.attribute.createMany({
|
||||||
await this.create({ arn, name: record.Key, value: record.Value });
|
data: records.map(r => ({
|
||||||
}
|
name: r.key,
|
||||||
|
value: r.value,
|
||||||
|
arn,
|
||||||
|
}))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByArn(arn: string) {
|
async deleteByArn(arn: string): Promise<void> {
|
||||||
await this.repo.delete({ arn });
|
await this.prismaService.tag.deleteMany({ where: { arn } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByArnAndName(arn: string, name: string) {
|
async deleteByArnAndName(arn: string, name: string): Promise<void> {
|
||||||
await this.repo.delete({ arn, name });
|
await this.prismaService.tag.deleteMany({ where: { arn, name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
static tagPairs(queryParams: Record<string, string>): { Key: string, Value: string }[] {
|
static tagPairs(queryParams: Record<string, string>): { key: string, value: string }[] {
|
||||||
const pairs = [null];
|
const pairs: { key: string, value: string }[] = [];
|
||||||
for (const param of Object.keys(queryParams)) {
|
for (const param of Object.keys(queryParams)) {
|
||||||
const [type, _, idx, slot] = param.split('.');
|
const components = breakdownAwsQueryParam(param);
|
||||||
|
|
||||||
|
if (!components) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [type, _, idx, slot] = components;
|
||||||
|
|
||||||
if (type === 'Tags') {
|
if (type === 'Tags') {
|
||||||
if (!pairs[+idx]) {
|
if (!pairs[+idx]) {
|
||||||
pairs[+idx] = { Key: '', Value: ''};
|
pairs[+idx] = { key: '', value: ''};
|
||||||
}
|
}
|
||||||
pairs[+idx][slot] = queryParams[param];
|
pairs[+idx][slot] = queryParams[param];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs.shift();
|
|
||||||
return pairs;
|
return pairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
src/main.ts
12
src/main.ts
|
|
@ -1,19 +1,19 @@
|
||||||
import { ClassSerializerInterceptor } from '@nestjs/common';
|
import { ClassSerializerInterceptor } from '@nestjs/common';
|
||||||
import { NestFactory, Reflector } from '@nestjs/core';
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
import * as morgan from 'morgan';
|
|
||||||
import { CommonConfig } from './config/common-config.interface';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import { CommonConfig } from './config/common-config.interface';
|
||||||
|
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.use(morgan('dev'));
|
|
||||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
||||||
app.use(bodyParser.json({ type: 'application/x-amz-json-1.1'}));
|
app.use(bodyParser.json({ type: 'application/x-amz-json-1.1'}));
|
||||||
|
|
||||||
const configService: ConfigService<CommonConfig> = app.get(ConfigService)
|
const configService: ConfigService<CommonConfig, true> = app.get(ConfigService);
|
||||||
|
|
||||||
await app.listen(configService.get('PORT'), () => console.log(`Listening on port ${configService.get('PORT')}`));
|
await app.listen(configService.get('PORT'), () => console.log(`Listening on port ${configService.get('PORT')}`));
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
import { CreateTopicCommand, CreateTopicCommandOutput, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, ListTagsForResourceCommand, ListTopicsCommand, PublishCommand, SNSClient, SubscribeCommand, SubscribeCommandOutput } from '@aws-sdk/client-sns';
|
|
||||||
import { TestingModule } from '@nestjs/testing';
|
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { Tag } from '../../aws-shared-entities/tags.entity';
|
|
||||||
import { SnsTopicSubscription } from '../sns-topic-subscription.entity';
|
|
||||||
import { SnsTopic } from '../sns-topic.entity';
|
|
||||||
|
|
||||||
describe('SNS Module', () => {
|
|
||||||
|
|
||||||
let snsClient: SNSClient;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
snsClient = new SNSClient({
|
|
||||||
endpoint: globalThis.__ENDPOINT__,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const describeCleanup = async () => {
|
|
||||||
const testModule: TestingModule = globalThis.__TESTMODULE__;
|
|
||||||
|
|
||||||
const snsTopicRepo = testModule.get<Repository<SnsTopic>>(getRepositoryToken(SnsTopic));
|
|
||||||
await snsTopicRepo.delete({});
|
|
||||||
|
|
||||||
const subscriptionRepo = testModule.get<Repository<SnsTopicSubscription>>(getRepositoryToken(SnsTopicSubscription));
|
|
||||||
await subscriptionRepo.delete({});
|
|
||||||
|
|
||||||
const tagsRepo = testModule.get<Repository<Tag>>(getRepositoryToken(Tag));
|
|
||||||
await tagsRepo.delete({});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('creation', () => {
|
|
||||||
|
|
||||||
afterAll(describeCleanup);
|
|
||||||
|
|
||||||
it('can create a topic', async () => {
|
|
||||||
const response = await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-1',
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.TopicArn).toBe('arn:aws:sns:us-east-1:000000000000:test-topic-1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can subscribe', async () => {
|
|
||||||
|
|
||||||
const topicResponse = await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-2',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const response = await snsClient.send(new SubscribeCommand({
|
|
||||||
TopicArn: topicResponse.TopicArn,
|
|
||||||
Protocol: 'https',
|
|
||||||
Endpoint: 'https://google.com',
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.SubscriptionArn).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can publish', async () => {
|
|
||||||
|
|
||||||
const topicResponse = await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-3',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const response = await snsClient.send(new PublishCommand({
|
|
||||||
Message: "hello world",
|
|
||||||
TopicArn: topicResponse.TopicArn,
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.MessageId).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reading', () => {
|
|
||||||
|
|
||||||
afterAll(describeCleanup);
|
|
||||||
|
|
||||||
let subscribedTopic: CreateTopicCommandOutput;
|
|
||||||
let subscription: SubscribeCommandOutput;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
subscribedTopic = await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-4',
|
|
||||||
Tags: [{ Key: 'V_a', Value: 'a' }, { Key: 'V_b', Value: 'b', }]
|
|
||||||
}));
|
|
||||||
|
|
||||||
await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-5',
|
|
||||||
}));
|
|
||||||
|
|
||||||
await snsClient.send(new CreateTopicCommand({
|
|
||||||
Name: 'test-topic-6',
|
|
||||||
}));
|
|
||||||
|
|
||||||
subscription = await snsClient.send(new SubscribeCommand({
|
|
||||||
TopicArn: subscribedTopic.TopicArn,
|
|
||||||
Protocol: 'https',
|
|
||||||
Endpoint: 'https://google.com',
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get subscription attributes', async () => {
|
|
||||||
const response = await snsClient.send(new GetSubscriptionAttributesCommand({
|
|
||||||
SubscriptionArn: subscription.SubscriptionArn,
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.Attributes).toEqual(expect.objectContaining({
|
|
||||||
"ConfirmationWasAuthenticated": "true",
|
|
||||||
"PendingConfirmation": "false",
|
|
||||||
"Owner": "000000000000",
|
|
||||||
"SubscriptionArn": subscription.SubscriptionArn,
|
|
||||||
"TopicArn": subscribedTopic.TopicArn,
|
|
||||||
"TracingConfig": "PassThrough"
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get topic attributes', async () => {
|
|
||||||
|
|
||||||
const response = await snsClient.send(new GetTopicAttributesCommand({
|
|
||||||
TopicArn: subscribedTopic.TopicArn,
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.Attributes).toEqual(expect.objectContaining({
|
|
||||||
"DisplayName": 'test-topic-4',
|
|
||||||
"Owner": "000000000000",
|
|
||||||
"SubscriptionsConfirmed": "1",
|
|
||||||
"SubscriptionsDeleted": "0",
|
|
||||||
"SubscriptionsPending": "0",
|
|
||||||
"TopicArn": subscribedTopic.TopicArn,
|
|
||||||
"TracingConfig": "PassThrough"
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can list tags for resource', async () => {
|
|
||||||
|
|
||||||
const response = await snsClient.send(new ListTagsForResourceCommand({
|
|
||||||
ResourceArn: subscribedTopic.TopicArn,
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.Tags).toHaveLength(2);
|
|
||||||
|
|
||||||
const map = new Map(response.Tags.map(({ Key, Value }) => [ Key, Value ]));
|
|
||||||
expect(map.get('V_a')).toBe('a');
|
|
||||||
expect(map.get('V_b')).toBe('b');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can list all topics', async () => {
|
|
||||||
const response = await snsClient.send(new ListTopicsCommand({}));
|
|
||||||
expect(response.Topics).toHaveLength(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// describe('updating', () => {
|
|
||||||
|
|
||||||
// afterAll(describeCleanup);
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('deleting', () => {
|
|
||||||
|
|
||||||
// afterAll(describeCleanup);
|
|
||||||
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import { SnsTopic } from './sns-topic.entity';
|
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
Name: string;
|
Name: string;
|
||||||
|
|
@ -15,8 +15,7 @@ type QueryParams = {
|
||||||
export class CreateTopicHandler extends AbstractActionHandler<QueryParams> {
|
export class CreateTopicHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopic)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
|
||||||
private readonly tagsService: TagsService,
|
private readonly tagsService: TagsService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -30,15 +29,18 @@ export class CreateTopicHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
const { Name: name } = params;
|
const { Name: name } = params;
|
||||||
|
|
||||||
const topic = await this.snsTopicRepo.create({
|
const topic = await this.prismaService.snsTopic.create({
|
||||||
|
data: {
|
||||||
name,
|
name,
|
||||||
accountId: awsProperties.accountId,
|
accountId: awsProperties.accountId,
|
||||||
region: awsProperties.region,
|
region: awsProperties.region,
|
||||||
}).save();
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const tags = TagsService.tagPairs(params);
|
const tags = TagsService.tagPairs(params);
|
||||||
await this.tagsService.createMany(topic.topicArn, tags);
|
const arn = ArnUtil.fromTopic(topic);
|
||||||
|
await this.tagsService.createMany(arn, tags);
|
||||||
|
|
||||||
return { TopicArn: topic.topicArn };
|
return { TopicArn: arn };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
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';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
SubscriptionArn: string;
|
SubscriptionArn: string;
|
||||||
|
|
@ -15,8 +15,7 @@ type QueryParams = {
|
||||||
export class GetSubscriptionAttributesHandler extends AbstractActionHandler {
|
export class GetSubscriptionAttributesHandler extends AbstractActionHandler {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopicSubscription)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicSubscriptionRepo: Repository<SnsTopicSubscription>,
|
|
||||||
private readonly attributeService: AttributesService,
|
private readonly attributeService: AttributesService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -29,7 +28,7 @@ export class GetSubscriptionAttributesHandler extends AbstractActionHandler {
|
||||||
protected async handle({ SubscriptionArn }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ SubscriptionArn }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const id = SubscriptionArn.split(':').pop();
|
const id = SubscriptionArn.split(':').pop();
|
||||||
const subscription = await this.snsTopicSubscriptionRepo.findOne({ where: { id }});
|
const subscription = await this.prismaService.snsTopicSubscription.findFirst({ where: { id }});
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -39,13 +38,13 @@ export class GetSubscriptionAttributesHandler extends AbstractActionHandler {
|
||||||
const attributeMap = attributes.reduce((m, a) => {
|
const attributeMap = attributes.reduce((m, a) => {
|
||||||
m[a.name] = a.value;
|
m[a.name] = a.value;
|
||||||
return m;
|
return m;
|
||||||
}, {});
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
ConfirmationWasAuthenticated: 'true',
|
ConfirmationWasAuthenticated: 'true',
|
||||||
PendingConfirmation: 'false',
|
PendingConfirmation: 'false',
|
||||||
Owner: subscription.accountId,
|
Owner: subscription.accountId,
|
||||||
SubscriptionArn: subscription.arn,
|
SubscriptionArn: ArnUtil.fromTopicSub(subscription),
|
||||||
TopicArn: subscription.topicArn,
|
TopicArn: subscription.topicArn,
|
||||||
...attributeMap,
|
...attributeMap,
|
||||||
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import { SnsTopic } from './sns-topic.entity';
|
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
TopicArn: string;
|
TopicArn: string;
|
||||||
|
|
@ -16,10 +15,7 @@ type QueryParams = {
|
||||||
export class GetTopicAttributesHandler extends AbstractActionHandler {
|
export class GetTopicAttributesHandler extends AbstractActionHandler {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopic)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
|
||||||
@InjectRepository(SnsTopicSubscription)
|
|
||||||
private readonly snsTopicSubscriptionRepo: Repository<SnsTopicSubscription>,
|
|
||||||
private readonly attributeService: AttributesService,
|
private readonly attributeService: AttributesService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -32,7 +28,7 @@ export class GetTopicAttributesHandler extends AbstractActionHandler {
|
||||||
protected async handle({ TopicArn }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ TopicArn }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const name = TopicArn.split(':').pop();
|
const name = TopicArn.split(':').pop();
|
||||||
const topic = await this.snsTopicRepo.findOne({ where: { name }});
|
const topic = await this.prismaService.snsTopic.findFirst({ where: { name }});
|
||||||
|
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
|
|
@ -42,9 +38,9 @@ export class GetTopicAttributesHandler extends AbstractActionHandler {
|
||||||
const attributeMap = attributes.reduce((m, a) => {
|
const attributeMap = attributes.reduce((m, a) => {
|
||||||
m[a.name] = a.value;
|
m[a.name] = a.value;
|
||||||
return m;
|
return m;
|
||||||
}, {});
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
const subscriptionCount = await this.snsTopicSubscriptionRepo.count({ where: { topicArn: TopicArn } });
|
const subscriptionCount = await this.prismaService.snsTopicSubscription.count({ where: { topicArn: TopicArn } });
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
DisplayName: topic.name,
|
DisplayName: topic.name,
|
||||||
|
|
@ -52,7 +48,7 @@ export class GetTopicAttributesHandler extends AbstractActionHandler {
|
||||||
SubscriptionsConfirmed: `${subscriptionCount}`,
|
SubscriptionsConfirmed: `${subscriptionCount}`,
|
||||||
SubscriptionsDeleted: '0',
|
SubscriptionsDeleted: '0',
|
||||||
SubscriptionsPending: '0',
|
SubscriptionsPending: '0',
|
||||||
TopicArn: topic.topicArn,
|
TopicArn: ArnUtil.fromTopic(topic),
|
||||||
...attributeMap,
|
...attributeMap,
|
||||||
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import { SnsTopic } from './sns-topic.entity';
|
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import { SnsTopic } from './sns-topic.entity';
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
import * as Joi from 'joi';
|
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
NextToken: number;
|
NextToken: number;
|
||||||
|
|
@ -14,8 +14,7 @@ type QueryParams = {
|
||||||
export class ListTopicsHandler extends AbstractActionHandler {
|
export class ListTopicsHandler extends AbstractActionHandler {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopic)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +25,11 @@ export class ListTopicsHandler extends AbstractActionHandler {
|
||||||
|
|
||||||
protected async handle({ NextToken: skip }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ NextToken: skip }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const [ topics, total ] = await this.snsTopicRepo.findAndCount({ order: { name: 'DESC' }, take: 100, skip });
|
const [ topics, total ] = await Promise.all([
|
||||||
const response = { Topics: { member: topics.map(t => ({ TopicArn: t.topicArn } ))} };
|
this.prismaService.snsTopic.findMany({ orderBy: { name: 'desc' }, take: 100, skip }),
|
||||||
|
this.prismaService.snsTopic.count(),
|
||||||
|
]);
|
||||||
|
const response = { Topics: { member: topics.map(t => ({ TopicArn: ArnUtil.fromTopic(t) } ))} };
|
||||||
|
|
||||||
if (total >= 100) {
|
if (total >= 100) {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import * as Joi from 'joi';
|
||||||
import { Repository } from 'typeorm';
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { SqsQueueEntryService } from '../sqs/sqs-queue-entry.service';
|
|
||||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
|
import { SqsQueueEntryService } from '../sqs/sqs-queue-entry.service';
|
||||||
import { SqsQueue } from '../sqs/sqs-queue.entity';
|
import { SqsQueue } from '../sqs/sqs-queue.entity';
|
||||||
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
TopicArn: string;
|
TopicArn: string;
|
||||||
|
|
@ -21,8 +21,7 @@ type QueryParams = {
|
||||||
export class PublishHandler extends AbstractActionHandler<QueryParams> {
|
export class PublishHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopicSubscription)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicSubscriptionRepo: Repository<SnsTopicSubscription>,
|
|
||||||
private readonly sqsQueueEntryService: SqsQueueEntryService,
|
private readonly sqsQueueEntryService: SqsQueueEntryService,
|
||||||
private readonly attributeService: AttributesService,
|
private readonly attributeService: AttributesService,
|
||||||
) {
|
) {
|
||||||
|
|
@ -46,13 +45,14 @@ export class PublishHandler extends AbstractActionHandler<QueryParams> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageId = uuid.v4();
|
const MessageId = uuid.v4();
|
||||||
const subscriptions = await this.snsTopicSubscriptionRepo.find({ where: { topicArn: arn } });
|
const subscriptions = await this.prismaService.snsTopicSubscription.findMany({ where: { topicArn: arn } });
|
||||||
const topicAttributes = await this.attributeService.getByArn(arn);
|
const topicAttributes = await this.attributeService.getByArn(arn);
|
||||||
|
|
||||||
for (const sub of subscriptions) {
|
for (const sub of subscriptions) {
|
||||||
const attributes = await this.attributeService.getByArn(sub.arn);
|
const subArn = ArnUtil.fromTopicSub(sub);
|
||||||
if (sub.protocol === 'sqs') {
|
const attributes = await this.attributeService.getByArn(subArn);
|
||||||
const { value: isRaw } = attributes.find(a => a.name === 'RawMessageDelivery');
|
if (sub.protocol === 'sqs' && sub.endpoint) {
|
||||||
|
const { value: isRaw } = attributes.find(a => a.name === 'RawMessageDelivery') ?? {};
|
||||||
const [queueAccountId, queueName] = SqsQueue.tryGetAccountIdAndNameFromPathOrArn(sub.endpoint);
|
const [queueAccountId, queueName] = SqsQueue.tryGetAccountIdAndNameFromPathOrArn(sub.endpoint);
|
||||||
|
|
||||||
const message = isRaw === 'true' ? Message : JSON.stringify({
|
const message = isRaw === 'true' ? Message : JSON.stringify({
|
||||||
|
|
@ -65,7 +65,7 @@ export class PublishHandler extends AbstractActionHandler<QueryParams> {
|
||||||
SignatureVersion: topicAttributes.find(a => a.name === 'SignatureVersion')?.value ?? '1',
|
SignatureVersion: topicAttributes.find(a => a.name === 'SignatureVersion')?.value ?? '1',
|
||||||
Signature: '',
|
Signature: '',
|
||||||
SigningCertURL: '',
|
SigningCertURL: '',
|
||||||
UnsubscribeURL: `${awsProperties.host}/?Action=Unsubscribe&SubscriptionArn=${sub.arn}`,
|
UnsubscribeURL: `${awsProperties.host}/?Action=Unsubscribe&SubscriptionArn=${subArn}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.sqsQueueEntryService.publish(queueAccountId, queueName, message);
|
await this.sqsQueueEntryService.publish(queueAccountId, queueName, message);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { Format } from '../abstract-action.handler';
|
||||||
import { AbstractActionHandler, Format } from '../abstract-action.handler';
|
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
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 { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider';
|
||||||
import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider';
|
import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider';
|
||||||
import { SqsModule } from '../sqs/sqs.module';
|
import { SqsModule } from '../sqs/sqs.module';
|
||||||
|
|
@ -15,11 +13,10 @@ import { ListTopicsHandler } from './list-topics.handler';
|
||||||
import { PublishHandler } from './publish.handler';
|
import { PublishHandler } from './publish.handler';
|
||||||
import { SetSubscriptionAttributesHandler } from './set-subscription-attributes.handler';
|
import { SetSubscriptionAttributesHandler } from './set-subscription-attributes.handler';
|
||||||
import { SetTopicAttributesHandler } from './set-topic-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 { SnsHandlers } from './sns.constants';
|
||||||
import { SubscribeHandler } from './subscribe.handler';
|
import { SubscribeHandler } from './subscribe.handler';
|
||||||
import { UnsubscribeHandler } from './unsubscribe.handler';
|
import { UnsubscribeHandler } from './unsubscribe.handler';
|
||||||
|
import { PrismaModule } from '../_prisma/prisma.module';
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
CreateTopicHandler,
|
CreateTopicHandler,
|
||||||
|
|
@ -81,8 +78,8 @@ const actions = [
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([SnsTopic, SnsTopicSubscription]),
|
|
||||||
AwsSharedEntitiesModule,
|
AwsSharedEntitiesModule,
|
||||||
|
PrismaModule,
|
||||||
SqsModule,
|
SqsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { randomUUID } from 'crypto';
|
||||||
import { Repository } from 'typeorm';
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
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 { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
import * as uuid from 'uuid';
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
import { SqsQueueEntryService } from '../sqs/sqs-queue-entry.service';
|
|
||||||
import { SqsQueue } from '../sqs/sqs-queue.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
TopicArn: string;
|
TopicArn: string;
|
||||||
|
|
@ -21,11 +19,9 @@ type QueryParams = {
|
||||||
export class SubscribeHandler extends AbstractActionHandler<QueryParams> {
|
export class SubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopicSubscription)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicSubscription: Repository<SnsTopicSubscription>,
|
|
||||||
private readonly tagsService: TagsService,
|
private readonly tagsService: TagsService,
|
||||||
private readonly attributeService: AttributesService,
|
private readonly attributeService: AttributesService,
|
||||||
private readonly sqsQueueEntryService: SqsQueueEntryService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
@ -40,21 +36,25 @@ export class SubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const subscription = await this.snsTopicSubscription.create({
|
const subscription = await this.prismaService.snsTopicSubscription.create({
|
||||||
id: uuid.v4(),
|
data: {
|
||||||
|
id: randomUUID(),
|
||||||
topicArn: params.TopicArn,
|
topicArn: params.TopicArn,
|
||||||
protocol: params.Protocol,
|
protocol: params.Protocol,
|
||||||
endpoint: params.Endpoint,
|
endpoint: params.Endpoint,
|
||||||
accountId: awsProperties.accountId,
|
accountId: awsProperties.accountId,
|
||||||
region: awsProperties.region,
|
region: awsProperties.region,
|
||||||
}).save();
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const arn = ArnUtil.fromTopicSub(subscription);
|
||||||
|
|
||||||
const tags = TagsService.tagPairs(params);
|
const tags = TagsService.tagPairs(params);
|
||||||
await this.tagsService.createMany(subscription.arn, tags);
|
await this.tagsService.createMany(arn, tags);
|
||||||
|
|
||||||
const attributes = AttributesService.attributePairs(params);
|
const attributes = AttributesService.attributePairs(params);
|
||||||
await this.attributeService.createMany(subscription.arn, attributes);
|
await this.attributeService.createMany(arn, attributes);
|
||||||
|
|
||||||
return { SubscriptionArn: subscription.arn };
|
return { SubscriptionArn: arn };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import * as uuid from 'uuid';
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
SubscriptionArn: string;
|
SubscriptionArn: string;
|
||||||
|
|
@ -17,8 +15,7 @@ type QueryParams = {
|
||||||
export class UnsubscribeHandler extends AbstractActionHandler<QueryParams> {
|
export class UnsubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SnsTopicSubscription)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly snsTopicSubscription: Repository<SnsTopicSubscription>,
|
|
||||||
private readonly tagsService: TagsService,
|
private readonly tagsService: TagsService,
|
||||||
private readonly attributeService: AttributesService,
|
private readonly attributeService: AttributesService,
|
||||||
) {
|
) {
|
||||||
|
|
@ -34,10 +31,16 @@ export class UnsubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||||
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const id = params.SubscriptionArn.split(':').pop();
|
const id = params.SubscriptionArn.split(':').pop();
|
||||||
const subscription = await this.snsTopicSubscription.findOne({ where: { id } });
|
const subscription = await this.prismaService.snsTopicSubscription.findFirst({ where: { id } });
|
||||||
|
|
||||||
await this.tagsService.deleteByArn(subscription.arn);
|
if (!subscription) {
|
||||||
await this.attributeService.deleteByArn(subscription.arn);
|
return;
|
||||||
await this.snsTopicSubscription.delete({ id });
|
}
|
||||||
|
|
||||||
|
const arn = ArnUtil.fromTopicSub(subscription);
|
||||||
|
|
||||||
|
await this.tagsService.deleteByArn(arn);
|
||||||
|
await this.attributeService.deleteByArn(arn);
|
||||||
|
await this.prismaService.snsTopicSubscription.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { SnsTopic, SnsTopicSubscription } from "@prisma/client";
|
||||||
|
|
||||||
|
export class ArnUtil {
|
||||||
|
|
||||||
|
static fromTopic(topic: SnsTopic): string {
|
||||||
|
return `arn:aws:sns:${topic.region}:${topic.accountId}:${topic.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromTopicSub(topicSub: SnsTopicSubscription): string {
|
||||||
|
return `${topicSub.topicArn}:${topicSub.id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const breakdownAwsQueryParam = (paramKey: string): [string, unknown, number, 'key' | 'value'] | null => {
|
||||||
|
|
||||||
|
const parts = paramKey.split('.');
|
||||||
|
|
||||||
|
if (parts.length !== 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [type, _, idx, slot] = parts;
|
||||||
|
return [type, _, +idx, slot as 'key' | 'value'];
|
||||||
|
}
|
||||||
|
|
@ -6,12 +6,13 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "es2017",
|
"target": "es2023",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
"include": ["src/**/*.ts"]
|
"include": ["src/**/*.ts"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue