Skip to content

Instantly share code, notes, and snippets.

@rychkog
Last active November 22, 2021 16:48
Show Gist options
  • Select an option

  • Save rychkog/fb9a98d7587e7b719f5b7eb09d64a2ba to your computer and use it in GitHub Desktop.

Select an option

Save rychkog/fb9a98d7587e7b719f5b7eb09d64a2ba to your computer and use it in GitHub Desktop.
Work activities

Code Snippets

Delete sqs messages in batches

  async deleteBatch(
    messages: { receiptHandle: string; eventSourceARN: string }[],
  ): Promise<DeleteMessageBatchResult> {
    if (!messages.length) {
      return {
        Failed: [],
        Successful: []
      };
    }

    const batch = messages.map<DeleteMessageBatchRequestEntry>(
      (message, index) => {
        return {
          Id: String(index),
          ReceiptHandle: message.receiptHandle,
        };
      },
    );

    const queueUrl = convertQueueArnToUrl(messages[0].eventSourceARN);
    const result = await this.sqs
      .deleteMessageBatch({
        QueueUrl: queueUrl,
        Entries: batch,
      })
      .promise();

    return result;
  }

INVOKE SQS FROM API GATEWAY (CDK)

import * as cdk from '@aws-cdk/core';
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as lambdaNode from '@aws-cdk/aws-lambda-nodejs';
import * as lambda from '@aws-cdk/aws-lambda';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as sqs from '@aws-cdk/aws-sqs';
import * as route53 from '@aws-cdk/aws-route53';
import * as route53Targets from '@aws-cdk/aws-route53-targets';
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as dynamodb from '@aws-cdk/aws-dynamodb';
import * as path from 'path';
import { Config } from '../config';
import { RemovalPolicy } from '@aws-cdk/core';

export interface ServiceStackProps extends cdk.StackProps {
  config: Config;
}

interface LambdaDef {
  name: string;
  path: string;
  routes: {
    path: string | string[];
    httpMethod: 'POST' | 'GET' | 'PUT' | 'DELETE' | 'ANY';
  }[];
}

export class ServiceStack extends cdk.Stack {
  private config: Config;

  private privateLambdaDefs: LambdaDef[] = [
    {
      name: 'metaFn',
      routes: [
        {
          path: 'meta',
          httpMethod: 'GET',
        },
      ],
      path: 'api/meta/index.ts',
    },
    {
      name: 'echoFn',
      routes: [
        {
          path: 'echo',
          httpMethod: 'POST',
        },
      ],
      path: 'api/echo/index.ts',
    },
    {
      name: 'accountsFn',
      routes: [
        {
          path: ['accounts', '{orgId}'],
          httpMethod: 'GET',
        },
        {
          path: 'accounts',
          httpMethod: 'POST',
        },
      ],
      path: 'api/accounts/index.ts',
    },
  ];

  private publicLambdaDefs: LambdaDef[] = [
    {
      name: 'publishRailsbankWebhookEventFn',
      routes: [
        {
          path: 'railsbank-webhook',
          httpMethod: 'POST',
        },
      ],
      path: 'api/railsbank-webhook/index.ts',
    },
  ];

  constructor(scope: cdk.Construct, id: string, props: ServiceStackProps) {
    super(scope, id, props);
    this.config = props.config;

    const vpc = ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true });


    const bankingTable = this.buildDynamoDbBankingTable();
    const { railsbankWebhookQueue } = this.buildRailsbankWebhookQueue();

    const envVars = this.buildEnvVariables({
      RAILSBANK_WEBHOOK_QUEUE_ARN: railsbankWebhookQueue.queueArn,
    });

    this.buildPrivateApi(vpc, envVars, bankingTable, railsbankWebhookQueue);
    this.buildPublicApi(envVars, bankingTable);
  }

  buildPublicApi(
    envVars: Record<string, string>,
    bankingTable: dynamodb.Table,
  ) {
    if (!this.privateLambdaDefs.length) {
      return;
    }

    const publicApiGateway = this.buildPublicApiGateway();
    this.buildLambdas(
      publicApiGateway.root.addResource('api'),
      this.publicLambdaDefs,
      envVars,
      bankingTable,
    );
  }

  buildPublicApi2(
    envVars: Record<string, string>,
    bankingTable: dynamodb.Table,
    railsbankWebhookQueue: sqs.Queue,
  ) {
    const publicApiGateway = this.buildPublicApiGateway();
    this.buildRailsbankWebhookIntegration(
      publicApiGateway,
      railsbankWebhookQueue,
    );
    this.setupDomainFor(publicApiGateway);
  }

  buildPrivateApi(
    vpc: ec2.IVpc,
    envVars: Record<string, string>,
    bankingTable: dynamodb.Table,
    railsbankWebhookQueue: sqs.Queue,
  ) {
    if (!this.privateLambdaDefs.length) {
      return;
    }

    const privateApiGateway = this.buildPrivateApiGateway(vpc);
    const lambdas = this.buildLambdas(
      privateApiGateway.root.addResource('api'),
      this.privateLambdaDefs,
      envVars,
      bankingTable,
    );

    if (!lambdas.publishRailsbankWebhookEventFn) {
      throw new Error('There is no Railsbank webhook events publisher lambda');
    }

    railsbankWebhookQueue.grantSendMessages(
      lambdas.publishRailsbankWebhookEventFn,
    );
  }

  buildLambdas(
    rootResource: apigateway.IResource,
    lambdaDefs: LambdaDef[],
    envVars: Record<string, string>,
    bankingTable: dynamodb.Table,
  ): { [name: string]: lambdaNode.NodejsFunction } {
    const builtLambdas = {} as { [name: string]: lambdaNode.NodejsFunction };
    lambdaDefs.forEach(lambdaDef => {
      const lambda = this.buildLambda(lambdaDef.name, lambdaDef.path, envVars);
      bankingTable.grantReadWriteData(lambda);

      lambdaDef.routes.forEach(route => {
        const gatewayIntegration = new apigateway.LambdaIntegration(lambda);

        const pathParts = ([] as string[]).concat(route.path);
        let currentLambdaPath = rootResource;
        pathParts.forEach(pathPart => {
          const existingResource = currentLambdaPath.getResource(pathPart);
          currentLambdaPath =
            existingResource ?? currentLambdaPath.addResource(pathPart);
        });

        currentLambdaPath.addMethod(route.httpMethod, gatewayIntegration);
      });

      builtLambdas[lambdaDef.name] = lambda;
    });

    return builtLambdas;
  }

  private buildEnvVariables(extraEnvVars: Record<string, string> = {}) {
    return {
      AWS_ACCOUNT: this.account,
      RAILSBANK_API_URL: this.config.railsbank.apiUrl,
      RAILSBANK_KEY: this.config.railsbank.apiKey,
      RAILSBANK_PARTNER_PRODUCT: this.config.railsbank.partnerProduct,
      RAILSBANK_WEBHOOK_SECRET: this.config.railsbank.webhookSecret,
      REMAGINE_SERVICE_VERSION: this.config.version,
      REMAGINE_DEPLOY_ENV: this.config.deployEnv,
      REMAGINE_VPN_IP: this.config.vpnIp,
      ... extraEnvVars
    };
  }

  private buildPublicApiGateway(): apigateway.RestApi {
    const gatewayId = `${this.config.serviceNameSlug}-api`;
    const api = new apigateway.RestApi(this, gatewayId, {
      restApiName: `${this.stackName} public API`,
      description: `This a public API for ${this.stackName}`,
    });

    this.addTagsTo(api);

    return api;
  }

  private buildPrivateApiGateway(vpc: ec2.IVpc): apigateway.RestApi {
    const endpoint = new ec2.InterfaceVpcEndpoint(
      this,
      'kaminoApiVpcEndpoint',
      {
        vpc,
        service: {
          name: `com.amazonaws.${this.config.aws.region}.execute-api`,
          port: 443,
        },
        lookupSupportedAzs: true,
        privateDnsEnabled: false,
      },
    );

    const gatewayId = `${this.config.serviceNameSlug}-private-api`;
    const api = new apigateway.RestApi(this, gatewayId, {
      restApiName: `${this.stackName} private API`,
      description: `This a private API for ${this.stackName}`,
      endpointConfiguration: {
        types: [apigateway.EndpointType.PRIVATE],
        vpcEndpoints: [endpoint],
      },
      policy: new iam.PolicyDocument({
        statements: [
          new iam.PolicyStatement({
            principals: [new iam.AnyPrincipal()],
            actions: ['execute-api:Invoke'],
            resources: ['execute-api:/*'],
            effect: iam.Effect.DENY,
            conditions: this.denyAccessingServiceWhenAllConditionsSatisfied(
              endpoint,
            ),
          }),
          new iam.PolicyStatement({
            principals: [new iam.AnyPrincipal()],
            actions: ['execute-api:Invoke'],
            resources: ['execute-api:/*'],
            effect: iam.Effect.ALLOW,
          }),
        ],
      }),
    });

    this.addTagsTo(api);
    this.addTagsTo(endpoint);

    new cdk.CfnOutput(this, 'kaminoInterfaceVpcEndpointDns', {
      value: cdk.Fn.select(0, endpoint.vpcEndpointDnsEntries),
    });

    return api;
  }

  // The method allows accessing Kamino only in following scenarios:
  // 1. A request was sent from specified VPC Endpoint ID
  // 2. A request was sent from specified IP if the one was given
  denyAccessingServiceWhenAllConditionsSatisfied(
    endpoint: ec2.InterfaceVpcEndpoint,
  ) {
    const conditions: iam.PolicyStatementProps['conditions'] = {
      StringNotEquals: {
        'aws:SourceVpce': endpoint.vpcEndpointId,
      },
    };

    if (this.config.vpnIp) {
      conditions.NotIpAddress = {
        'aws:SourceIp': this.config.vpnIp,
      };
    }

    return conditions;
  }

  private buildLambda(
    name: string,
    pathToIndex: string,
    env: { [key: string]: string } = {},
    handler: string = 'handler',
  ): lambdaNode.NodejsFunction {
    const fn = new lambdaNode.NodejsFunction(this, name, {
      entry: path.resolve(__dirname, '..', `./functions/${pathToIndex}`),
      handler,
      runtime: lambda.Runtime.NODEJS_14_X,
      timeout: cdk.Duration.minutes(3),
      environment: env,
    });

    this.addTagsTo(fn);

    return fn;
  }

  setupDomainFor(api: apigateway.RestApi): void {
    const { domainName, appDomain } = this.config;
    if (!domainName || !appDomain) {
      return;
    }

    const hostedZone = this.lookupHostedZone(domainName);

    const remagineCertificate = new acm.Certificate(this, 'KaminoCertificate', {
      domainName: appDomain,
      validation: acm.CertificateValidation.fromDns(hostedZone),
    });

    cdk.Tags.of(remagineCertificate).add('domainName', appDomain);
    this.addTagsTo(remagineCertificate);

    api.addDomainName('KaminoDomainName', {
      domainName: appDomain,
      certificate: remagineCertificate,
      securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
    });

    new route53.ARecord(this, 'AliasRecord', {
      recordName: appDomain,
      zone: hostedZone,
      target: route53.RecordTarget.fromAlias(
        new route53Targets.ApiGateway(api),
      ),
    });
  }

  buildDynamoDbBankingTable() {
    return new dynamodb.Table(this, 'Banking', {
      partitionKey: {
        name: '_pk',
        type: dynamodb.AttributeType.STRING,
      },
      sortKey: {
        name: '_sk',
        type: dynamodb.AttributeType.STRING,
      },
      tableName: 'Banking',
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecovery: true,
      removalPolicy: RemovalPolicy.RETAIN,
    });
  }

  buildRailsbankWebhookQueue() {
    const railsbankWebhookDeadLetterQueue = new sqs.Queue(
      this,
      'railsbankWebhookDlq',
    );

    const railsbankWebhookQueue = new sqs.Queue(this, 'railsbankWebhook', {
      queueName: `${this.config.servicePrefix}-railsbankWebhook-${this.account}`,
      visibilityTimeout: cdk.Duration.seconds(90),
      retentionPeriod: cdk.Duration.hours(72),
      deadLetterQueue: {
        queue: railsbankWebhookDeadLetterQueue,
        maxReceiveCount: 5,
      },
    });

    this.addTagsTo(railsbankWebhookDeadLetterQueue);
    this.addTagsTo(railsbankWebhookQueue);

    return {
      railsbankWebhookQueue,
    };
  }

  buildRailsbankWebhookIntegration(
    apiGw: apigateway.IRestApi,
    railsbankWebhookQueue: sqs.Queue,
  ) {
    const railsbankWebhookPublisherRole = new iam.Role(
      this,
      'railsbankWebhookPublisherRole',
      {
        assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
      },
    );

    railsbankWebhookPublisherRole.addToPolicy(
      new iam.PolicyStatement({
        resources: [railsbankWebhookQueue.queueArn],
        actions: ['sqs:SendMessage'],
      }),
    );

    const railsbankWebhookApiGatewayIntegration = new apigateway.AwsIntegration(
      {
        service: 'sqs',
        integrationHttpMethod: 'POST',
        options: {
          passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
          credentialsRole: railsbankWebhookPublisherRole,
          requestParameters: {
            'integration.request.header.Content-Type':
              "'application/x-www-form-urlencoded'",
          },
          requestTemplates: {
            'application/json':
              'Action=SendMessage&MessageBody=$util.urlEncode("$input.body")',
          },
          integrationResponses: [
            {
              statusCode: '200',
              responseTemplates: {
                'application/json': '{"success":true}',
              },
            },
            {
              statusCode: '500',
              responseTemplates: {
                'application/json': '{"success":false}',
              },
              selectionPattern: '500',
            },
          ],
        },
        path: `${cdk.Aws.ACCOUNT_ID}/${railsbankWebhookQueue.queueName}`,
      },
    );

    const apiKeyAuthorizer = new RequestAuthorizer(this, 'apiKeyAuthorizer', {
      handler: this.buildLambda(
        'apiKeyAuthorizerFn',
        'api/railsbank-webhook/api-key-authorizer/index.ts',
      ),
      identitySources: ['method.request.body.secret'],
    });

    const railsbankWebhookEndpoint = apiGw.root.addResource(
      'railsbank-webhook',
    );
    railsbankWebhookEndpoint.addMethod(
      'POST',
      railsbankWebhookApiGatewayIntegration,
      {
        methodResponses: [
          {
            statusCode: '200',
            responseParameters: {
              'method.response.header.Content-Type': true,
            },
          },
          {
            statusCode: '500',
            responseParameters: {
              'method.response.header.Content-Type': true,
            },
          },
        ],

        authorizer: apiKeyAuthorizer,
      },
    );

    return {
      railsbankWebhookQueue,
    };
  }

  lookupHostedZone(domainName: string) {
    return route53.HostedZone.fromLookup(this, 'hostedZone', {
      domainName,
    });
  }

  addTagsTo(construct: cdk.Construct) {
    cdk.Tags.of(construct).add('environment', this.config.deployEnv);
    cdk.Tags.of(construct).add('tier', 'kamino');
    cdk.Tags.of(construct).add('createdBy', 'cdk');
  }
}
  private resourceForPath(
    path: string,
    root: apigw.IResource
  ): apigw.IResource {
    let resource = root.getResource(path);
    if (resource) {
      return resource;
    }

    resource = root.addResource(path) as apigw.Resource;
    return resource.addProxy();
  }
import * as cdk from '@aws-cdk/core';
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as route53 from '@aws-cdk/aws-route53';
import * as route53Targets from '@aws-cdk/aws-route53-targets';
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns';
import * as servicediscovery from '@aws-cdk/aws-servicediscovery';
import * as path from 'path';
import { Config } from '../config';

export interface SharedCdkStackProps extends cdk.StackProps {
  config: Config;
}

export class SharedCdkStack extends cdk.Stack {
  private config: Config;

  constructor(scope: cdk.Construct, id: string, props: SharedCdkStackProps) {
    super(scope, id, props);
    this.config = props.config;

    const vpc = ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true });

    // const azs = vpc.availabilityZones.slice().sort();
    // const publicSubnetIds = vpc.publicSubnets
    //   .slice()
    //   .map(s => s.subnetId)
    //   .sort();

    // const privateSubnet1 = new ec2.Subnet(this, 'privateSubnet1', {
    //   vpcId: vpc.vpcId,
    //   cidrBlock: '172.31.48.0/20',
    //   availabilityZone: azs[0],
    //   mapPublicIpOnLaunch: false,
    // });

    // const privateSubnet2 = new ec2.Subnet(this, 'privateSubnet2', {
    //   vpcId: vpc.vpcId,
    //   cidrBlock: '172.31.64.0/20',
    //   availabilityZone: azs[1],
    //   mapPublicIpOnLaunch: false,
    // });

    // const natGateway = new ec2.CfnNatGateway(this, 'sharedNat', {
    //   subnetId: publicSubnetIds[0],
    //   allocationId: new ec2.CfnEIP(this, 'sharedNatElasticIp', {
    //     domain: 'vpc',
    //   }).attrAllocationId,
    // });

    // new ec2.CfnRoute(this, 'privateSubnet1NatRoute', {
    //   routeTableId: privateSubnet1.routeTable.routeTableId,
    //   natGatewayId: natGateway.ref,
    //   destinationCidrBlock: '0.0.0.0/0',
    // });

    // new ec2.CfnRoute(this, 'privateSubnet2NatRoute', {
    //   routeTableId: privateSubnet2.routeTable.routeTableId,
    //   natGatewayId: natGateway.ref,
    //   destinationCidrBlock: '0.0.0.0/0',
    // });

    const publicApiGateway = this.buildPublicApiGateway();
    this.setupPublicApiDomainFor(publicApiGateway);

    // Cloud Map Namespace
    const namespace = new servicediscovery.PrivateDnsNamespace(
      this,
      'MyNamespace',
      {
        name: 'private.dev-remagine.net',
        vpc,
      },
    );

    new cdk.CfnOutput(this, 'sharedCloudMapNamespaceArn', {
      value: namespace.namespaceArn,
    });

    new cdk.CfnOutput(this, 'sharedCloudMapNamespaceName', {
      value: namespace.namespaceName,
    });

    new cdk.CfnOutput(this, 'sharedCloudMapNamespaceId', {
      value: namespace.namespaceId,
    });

    const ecs = this.buildEcsInstance(vpc);
    ecs.service.enableCloudMap({
      cloudMapNamespace: namespace,
      dnsTtl: cdk.Duration.seconds(30),
      failureThreshold: 1,
      name: 'foobar',
    });

    ecs.targetGroup.configureHealthCheck({
      path: '/',
      port: String(3000)
    });
  }

  private buildEcsInstance(vpc: ec2.IVpc) {
    const cluster = new ecs.Cluster(this, 'sharedStackEcsSampleCluster', {
      vpc,
    });
    return new ecsPatterns.ApplicationLoadBalancedFargateService(
      this,
      'sharedStackEcsSample',
      {
        publicLoadBalancer: false,
        assignPublicIp: true,
        cluster,
        taskImageOptions: {
          image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
          containerPort: 3000,
          environment: {},
        },
      },
    );
  }

  private buildPrivateApiGateway(): apigateway.RestApi {
    const gatewayId = `${this.config.stackName}-public-api`;

    const publicApiGateway = new apigateway.RestApi(this, gatewayId, {
      restApiName: `Remagine public API (${this.stackName})`,
      description: `This is Remagine public API, which is accessible from outside world`,
    });

    publicApiGateway.root.addResource('api').addMethod('ANY');

    return publicApiGateway;
  }

  private buildPublicApiGateway(): apigateway.RestApi {
    const gatewayId = `${this.config.stackName}-public-api`;

    const publicApiGateway = new apigateway.RestApi(this, gatewayId, {
      restApiName: `Remagine public API (${this.stackName})`,
      description: `This is Remagine public API, which is accessible from outside world`,
    });

    publicApiGateway.root.addResource('api').addMethod('ANY');

    return publicApiGateway;
  }

  private setupPublicApiDomainFor(api: apigateway.RestApi): void {
    const { publicApiDomain, domainName } = this.config;
    if (!domainName || !publicApiDomain) {
      return;
    }

    const remagineHostedZone = this.lookupHostedZone(domainName);

    const remagineCertificate = new acm.Certificate(
      this,
      `RemagineCertificate`,
      {
        domainName: publicApiDomain,
        validation: acm.CertificateValidation.fromDns(remagineHostedZone),
      },
    );

    this.addTagsTo(remagineCertificate);

    api.addDomainName('PublicApiDomainName', {
      domainName: publicApiDomain,
      certificate: remagineCertificate,
      securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
    });

    new route53.ARecord(this, 'PublicApiAliasRecord', {
      recordName: publicApiDomain,
      zone: remagineHostedZone,
      target: route53.RecordTarget.fromAlias(
        new route53Targets.ApiGateway(api),
      ),
    });
  }

  lookupHostedZone(domainName: string) {
    return route53.HostedZone.fromLookup(this, 'hostedZone', {
      domainName,
    });
  }

  addTagsTo(construct: cdk.Construct) {
    cdk.Tags.of(construct).add('environment', this.config.deployEnv);
    cdk.Tags.of(construct).add('tier', 'shared-cdk');
    cdk.Tags.of(construct).add('createdBy', 'cdk');
  }
}

ECS Scheduled

private buildEcsWorkersTask(
    cluster: ecs.Cluster,
    dataConnectionEncryptionKey: kms.IKey,
    environment: Record<string, string>
  ) {
    const workersLogGroup = new logs.LogGroup(
      this,
      `${this.config.servicePrefix}TatooineWorkersLogGroup`,
      {
        logGroupName: `${this.config.servicePrefix}TatooineWorkers`,
        removalPolicy: cdk.RemovalPolicy.DESTROY
      }
    );

    const workersTask = new ecsPatterns.ScheduledFargateTask(
      this,
      `${this.config.servicePrefix}TatooineWorkers`,
      {
        cluster,
        desiredTaskCount: 1,
        subnetSelection: cluster.vpc.selectSubnets({
          subnetType: SubnetType.PUBLIC
        }),
        scheduledFargateTaskImageOptions: {
          command: ['npm', 'run', 'start:workers:prod'],
          image: ecs.ContainerImage.fromAsset(
            path.resolve(__dirname, '..', '..')
          ),
          environment,
          memoryLimitMiB: 512,
          logDriver: new ecs.AwsLogDriver({
            logGroup: workersLogGroup,
            streamPrefix: `${this.config.servicePrefix}TatooineWorkers`
          })
        },
        schedule: events.Schedule.expression('rate(2 hours)')
      }
    );

    this.addTagsTo(workersTask.taskDefinition);

    dataConnectionEncryptionKey.grantEncryptDecrypt(
      workersTask.taskDefinition.taskRole
    );
  }

Validate PIN

  private validatePin(pin: string) {
    const digits = pin.split('').map(Number);

    const grouped = digits.reduce((result, digit) => {
      result[digit] = result[digit] ? result[digit] + 1 : 1;
      return result;
    }, {} as Record<string, number>);

    if (Object.values(grouped).some(digit => digit > 2)) {
      throw new BadRequestException('Three or more digits in PIN are not allowed to repeat');
    }

    if (this.isSequential(digits)) {
      throw new BadRequestException(
        'PIN should not contain digits in a sequentially increasing order'
      );
    }

    if (this.isSequential(digits, false)) {
      throw new BadRequestException(
        'PIN should not contain digits in a sequentially decreasing order'
      );
    }
  }

  private isSequential(digits: number[], increasing = true) {
    for (let i = 0; i < digits.length - 1; i++) {
      if (digits[i] - digits[i + 1] != (increasing ? -1 : 1)) {
        return false;
      }
    }

    return true;
  }

CloudMap and Scaling

    const cmService = new servicediscovery.Service(this, '', {
      namespace: dnsNamespace,
      loadBalancer: true
    });

    service.associateCloudMapService({
      service: cmService
    })

    cmService.registerLoadBalancer('', albFargateWrapper.loadBalancer);

    const scaling = service.autoScaleTaskCount({ minCapacity: 1, maxCapacity: 2 });
    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 60
    });

    scaling.scaleOnMemoryUtilization('MemoryScaling', {
      targetUtilizationPercent: 70
    });

Work Journal

5.03.2021

DONE:

  • Fixed Kamino deployment
  • Fixed issue with pending conneciotns removal
  • Fixed issue related to not supported finApi banks
  • Architecture guild
  • Deployed stuff (not supported banks) to live

TODO:

  • Sync "not supported banks" UI between Naboo and Bespin

  • Deploy changes regarding pending data connections removal (Bespin + Tatooine). Make sure everything is cherry-picked

  • Make sure Kamino is behind private API-GW

  • What if some of the operations will fail during account creation, what's there, how to roll them back?

  • Issue with configs in microservices:

    • One config and custom env per lambda
    • One config per project and the same env for all lambdas
  • Handler errors thrown by axios in Kamino. They might contain sensitive info:

  • Мигрировать в шаблон:

    import { ILogger } from './logger';
    import { toLambdaErrorResponse } from './to-lambda-response';
    import { AxiosError } from 'axios';
    
    export function makeErrorsHandler({ logger }: { logger: ILogger }) {
      return async (error: Error) => {
        const normalizedError = normalizeAxiosError(error);
        const errorResponse = await toLambdaErrorResponse(normalizedError);
    
        logger.error(Object.assign(normalizedError, { code: errorResponse.statusCode }));
    
        return errorResponse;
      };
    }
    
    function normalizeAxiosError(error: Error) {
      if (isAxiosError(error)) {
        const data = error.response?.data;
        Reflect.deleteProperty(error, 'request');
        Reflect.deleteProperty(error, 'response');
        Reflect.deleteProperty(error, 'config');
    
        error.response = { data } as any;
        return error;
      }
    
      return error;
    }
    
    function isAxiosError(error: Error): error is AxiosError {
      return !!(error && (error as any).isAxiosError);
    }

9.03.2021

DONE:

  • Fixed issue with JWKS in Naboo
  • Investigated another login issue connected to out migration to Ando and cognito_id column (won't fix - as designed)
  • Fixed an issue when unverified users could log in to Naboo, now only verified and those who passed data connections step can login
  • Fixed an issue with Axios errors that used to contain lots of unneeded or sensitive info
  • Fixed an issue with unauthorized redirect urls for facebook and google-ads
  • Added extra methods to Railsbank service and enriched Kamino endpoints for getting and creating an account

TODO:

  • We need to deploy "removing pending data connections" to live
  • We need to move Kamino to private API GW
  • We need to propagate axios error handling to other services

QUESTIONS:

  • Idempotency in Kamino for account creation
  • We need to start using DB (Dynamo?) in Kamino. Instead of storing rb_enduser_id in Kamino I would propose to create an account table with rb_enduser_id, rb_ledgers_ids, etc
  • For now I suppose we can avoid storing transactions there, right?

10.03.2021

DONE:

  • Fixed an error message connected to Google scopes enforcement
  • Fixed an issue with Facebook connections from Naboo - provided memberId was not used in Tatooine
  • Added private API GW to Kamino
  • Investigated DynamoDB, ran it locally, investigated and implemented locking table, created a table for banking accounts

15.03.2021

DONE:

  • Talked to Kay regarding Facebook long-lived tokens and the way of handling their expiration on Tython side
  • Railsbank webhooks for Kamino

IN PROGRESS:

TODO:

QUESTIONS:

  • I guess we need to handle webhook messages in order, right? Are there any consequences of our of order message processing?

16.03.2021

DONE:

  • Kamino has Railsbank webhook exposed and appropriate webhooks is registered on Railsbank side. All the credentials are in 1Password.
  • We had a planning

IN PROGRESS:

TODO:

QUESTIONS:

  • I guess we need to handle webhook messages in order, right? Are there any consequences of our of order message processing?

17.03.2021

DONE:

  • Addressed PR comments for my webhooks PR
  • Implemented endpoints for serving transactions
  • Was reading AWS doc shared in Slack about API Gateway usage
  • Investigated a bit how to start using single API Gateway for all the services

IN PROGRESS:

TODO:

  • Add Joi validation to Kamino

QUESTIONS:

18.03.2021

DONE:

  • Added more unit test to Kamino
  • Finished transactions fetching in Kamino
  • Added mandatory unit tests check for every PR in Kamino
  • Enforced facebook scopes in data connections, also reworked a google scopes check, cause came up with a better solution while working on facebook scopes enforcement
  • Helped Praveen to investigate Naboo reCaptcha issue (wrong action was sent to Tatooine)

IN PROGRESS:

TODO:

  • Add Joi validation to Kamino
  • Add stuff to newsletter

QUESTIONS:

  • We need to change the way we store data about accounts in DynamoDb
from https://aws-blog.de/2021/03/dynamodb-in-15-minutes.html:

It’s more common to have a composite primary key on a table, which you can see below. This allows for different and more flexible query patterns.

>>>> Items that share the same partition key value are called an item collection.

 The items in a collection can still be different entities.

So I guess we actually need to store all that relates to a particular account under the same PK, so all accounts transactions will have this PK. This means that all of them will live on the same shard

Here is another article that explains how to model various data access patterns: https://aws-blog.de/2021/03/modelling-a-product-catalog-in-dynamodb.html

19.03.2021

DONE:

  • Was reading about DynamoDB, SQS and Lambdas,
  • Shared infra repository

IN PROGRESS:

TODO:

  • Add Joi validation to Kamino
  • Add stuff to newsletter

QUESTIONS:

===

22.03.2021

DONE:

IN PROGRESS:

TODO:

QUESTIONS:

===

2.04.2021

DONE:

  • Added amount update on a ledger whenever "ledger-changed" gets triggered
  • Populate sender/receiver transaction info based on transaction type (we do populate "transaction-type-send" transactions)
  • Migrates facebook, google connectors
  • Migrates country-launch set of functions

IN PROGRESS:

TODO:

QUESTIONS:

5.04.2021

DONE:

  • Tatooine ECS migration
    • Onboarding
    • Me
    • DataConnectors

IN PROGRESS:

  • DockerCompose for Tatooine
  • Change URLs in Hasura-Tatooine communication

TODO:

  • Push all the code before the vacation!!!!

QUESTIONS:

6.04.2021

DONE:

  • Add docker-compose for local development
  • Removes JWKS usage
  • Fix docker-compose
  • Remove functions dir
  • Fixes unit tests github action
  • Fixes tests
  • Moves codegen to server
  • Investigated an issue with customer who couldn't login (he though the login is a signup)

IN PROGRESS:

TODO:

  • Перебить урлы

  • Мигировать папку serverless-stack (api-gw)

  • Починить гитхаб экшены

  • Проверить деплой и задеплоить на дев

  • Убить ярн

  • Добавить в контекст логгера инфу из session_variables

  • Сделать конфигурацию код-стайла и линта в корне приложения

  • Привести в единую структуру папку data-stack и переименовать её в hasura

  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope

QUESTIONS:

7.04.2021

DONE:

IN PROGRESS:

TODO:

  • Починить гитхаб экшены
  • Проверить деплой и задеплоить на дев
  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope

QUESTIONS:

8.04.2021

DONE:

  • Backport changes for new Finapi communication protocol to Tatooine ecs
  • Fixed deployment
  • Tested local Tatooine dev workflow
  • Participated in a Product team workshop

IN PROGRESS:

TODO:

  • Починить гитхаб экшены
  • Проверить деплой и задеплоить на дев
  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope
  • avoid inferring hosted zone in Tatooine Hasura - infer it instead
  • Remove those and use host and build the urls instead:
    • REMAGINE_FRONTEND_EMAIL_CONFIRMATION_URL
    • REMAGINE_FRONTEND_PASSWORD_RECOVERY_URL
    • REMAGINE_FRONTEND_LOGIN_URL

QUESTIONS:

9.04.2021

DONE:

  • Read the Kamino doc
  • Reviewd PRs
  • Updated Tatooine's docs

IN PROGRESS:

TODO:

  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope
  • avoid inferring hosted zone in Tatooine Hasura - infer it instead
  • Remove those and use host and build the urls instead:
    • REMAGINE_FRONTEND_EMAIL_CONFIRMATION_URL
    • REMAGINE_FRONTEND_PASSWORD_RECOVERY_URL
    • REMAGINE_FRONTEND_LOGIN_URL

QUESTIONS:

12.04.2021

DONE:

  • Merged Tatooine's docs changes with Svyat changes.
  • Meeting regarding Naboo wireframes
  • Extracted shared cdk constructs into shared-cdk repo.

IN PROGRESS:

TODO:

  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope
  • avoid inferring hosted zone in Tatooine Hasura - infer it instead
  • Remove those and use host and build the urls instead:
    • REMAGINE_FRONTEND_EMAIL_CONFIRMATION_URL
    • REMAGINE_FRONTEND_PASSWORD_RECOVERY_URL
    • REMAGINE_FRONTEND_LOGIN_URL

QUESTIONS:

QUESTIONS:

05.05.2021

DONE:

  • Adjusted and deployed Corellia
  • Participated in Tatooine DB design discussion

IN PROGRESS:

TODO:

  • Добавить вменяемый логгер, чтоб можно было инжектить - у меня есть идея как это сделать без REQUEST scope
  • Remove those and use host and build the urls instead:
    • REMAGINE_FRONTEND_EMAIL_CONFIRMATION_URL
    • REMAGINE_FRONTEND_PASSWORD_RECOVERY_URL
    • REMAGINE_FRONTEND_LOGIN_URL

QUESTIONS:

QUESTIONS:

06.05.2021

DONE:

  • Adjusted Kamino-ecs

IN PROGRESS:

TODO:

  • Remove those and use host and build the urls instead:
    • REMAGINE_FRONTEND_EMAIL_CONFIRMATION_URL
    • REMAGINE_FRONTEND_PASSWORD_RECOVERY_URL
    • REMAGINE_FRONTEND_LOGIN_URL

QUESTIONS:

07.05.2021

DONE:

IN PROGRESS:

TODO:

QUESTIONS:

11.05.2021

DONE:

IN PROGRESS:

TODO:

QUESTIONS:

12.08.2021

DONE:

IN PROGRESS:

TODO:

  • Overwrite user info after successfull identification
  • Add new API for Karol
  • Send emails for LRs identification
  • Check that business get's created and is active in BUSINESS_IDENTIFICATION

QUESTIONS:

  • What to do when identification fails?

13.08.2021

DONE:

IN PROGRESS:

  • Help Karol with Postman and Solaris API

  • Checked the flow with LR invitation email and invitation email token verification as well as the fact that we do have attachments

TODO:

  • Overwrite user info after successfull identification
  • Add new API for Karol
  • Send emails for LRs identification
  • Check that business get's created and is active in BUSINESS_IDENTIFICATION

QUESTIONS:

  • What to do when identification fails?

  • Create LR's when the BUSINESS_IDENTIFICATION is successful with email for password reset and random password ando users

30.08.2021

DONE:

IN PROGRESS:

  • Accounts management using our internal Account entity (including new account creation)
  • Helping Diego
  • Helping Padma
  • Was transfering money back and forth :)

TODO:

  • Add handler for account BLOCKING
  • Add handler for account CLOSING
  • Use propser SECTOR, INDUSTRY_KEY and INDUSTRY_ID instead of placeholders
  • Start adding facts

QUESTIONS:

01.09.2021

DONE:

  • Fixed account post deployment issues
  • Migrated existing Solaris accounts and cards to our internal accounts and cards
  • Helped folks from Cobalt
  • Finished Smart Agent (at least at the degree I see it)

IN PROGRESS:

TODO:

  • Tell UI folks that we are required to show this on whitelisted fraud case:

    If the customer chooses to whitelist, you are required to show the customer the whitelisted_until period, during which they can try the transaction again.

QUESTIONS:

02.09.2021

DONE:

  • Finished Smart Agent, this time for real!
  • Added endpoints for creating and settling card test transactions
  • Added endpoints for creating card test fraud cases

IN PROGRESS:

TODO:

QUESTIONS:

03.09.2021

DONE:

  • Was reviewing Diego's PR
  • Updated attachment for LR invitation
  • Helped investigating card "limits are not respected"
  • Added Helmet on Hoth
  • Fixed recurring thing with the address.line2 thing for both: org and LRs

IN PROGRESS:

  • Add facts to the app

TODO:

  • !!!!!!!! TELL FOLKS THAT NOW WE NEED TO PROVIDE ACCOUNT ID FOR TRANSACTION CONFIRMATION!

  • Create a doc on where to store file attachments, so that they can be used in Naraka

  • Create a doc on how to use test card transactions and fraud cases

  • Add helmet to all BFFs

QUESTIONS:

06.09.2021

DONE:

  • ch2280: industry sector
  • ch2386: Finished working on facts
  • ch2496: Address line 2 should be nullable
  • ch2280: Pass industry sector to Solaris
  • Helped Diego to setup local Onderon, Naboo, Hoth

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Narak afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS

QUESTIONS:

07.09.2021

DONE:

  • helping Diego to clarify some things regarding data-connections
  • helping Cobalt team with onboarding flow
  • was fixing issue with Swagger documentation
  • started working on reservations

IN PROGRESS:

  • reservations

TODO:

  • Create a doc on where to store file attachments (to use in Narak afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS

QUESTIONS:

08.09.2021

DONE:

  • ch2543: reservations
  • fixed links for fraud case emails
  • looked who uses which AWS accounts for AWS deployment
  • reviewed Diego's PR about data-connections in Naboo
  • redacted sensitive values from fact's payload

IN PROGRESS:

  • ch2563: adding seizure support to Hoth
  • check whether a business can be created without balance_sheet_total and number_employees
  • Check that Omar enabled the policy for seizures and I can fetch org seizures

TODO:

  • Create a doc on where to store file attachments (to use in Narak afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS

QUESTIONS:

09.09.2021

DONE:

  • check whether a business can be created without balance_sheet_total and number_employees
  • Was investigating issue with failed data-connections deployment (OutOfMemory). Increased amount of RAM for deployment
  • ch2563: adding seizure support to Hoth (only for serving them)
  • Check that Omar enabled the policy for seizures and I can fetch org seizures
  • Was investigating why Seli (cobalt) couldn't login (because onboardign is completed and there is not tentative subscription which Onderon asks for, and because of null in not nullable subscription field we were failing)
  • ch2497: implemented new card name generation logic

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Narak afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

10.09.2021

DONE:

  • ch2450: Emails enumeration on forgot password is possible
  • ch2476: recaptcha for forgot password

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Narak afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

13.09.2021

DONE:

  • sc-2631: tax identification
  • sc-2606: stop using total assets and employees count
  • fixing build changes that popped up when I was doing reCaptcha
  • investigating failing deployment ECS (related to NAT)

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

14.09.2021

DONE:

IN PROGRESS:

  • sc-2609: Add EDD support to Hoth (Update the status docs with new PENDING_EDD onboarding status for subscription)

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

15.09.2021

DONE:

  • sc-2609: Add EDD support to Hoth (Update the status docs with new PENDING_EDD onboarding status for subscription)

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

16.09.2021

DONE:

  • sc-2497]: adjust cardholder name generation
  • fixed issues with Hoth health checks by using ApplicationLoadBalancedFargateService

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

QUESTIONS:

17.09.2021

DONE:

  • fixed issues with Hoth when SendMessageForProcessing lambda was not able to reach out Hoth. The issue with in fact that lambda was placed in a subnet that was not allowed by Hoth ECS security group
  • fixed issue regarding email - they should all be case-insensitive
  • fixed issue with aborted identifications

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

20.09.2021

DONE:

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

21.09.2021

DONE:

  • fixed edd case fetching on Dagobah
  • investigating issues that Padma had with EDD (including email notifications)
  • investigated an error Omar was getting
  • prepare Hoth, Naraka, shared-cdk to go live (investigated issues related to NAT, removed DB from cdk management, reconfigured the db in live and dev)

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

22.09.2021

DONE:

  • was helping Diego with data connections
  • was whitelisting Solaris IPs for webhooks via WAF ACL
  • Created proper log groups for all the projects
  • Configured secrets for Hoth (get them from Secrets Manager rather then GitHub secrets)

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

23.09.2021

DONE:

  • deployment activities
  • setting up webhooks
  • improved solaris errors logging

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

24.09.2021

DONE:

IN PROGRESS:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase
  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

QUESTIONS:

27.09.2021

DONE:

  • investigating ecs restart options

IN PROGRESS:

  • account product packages

NEXT:

  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

  • sick leave for a planned medical procedure for tomorrow

Hi Claudio! Don't you mind if I get a sick leave for a planned medical procedure for today?

My status:

  • I was investigating issue regarding the email not being sent. Seem like after changes that Kay did - it started working again. But it is not good when something gets changed somewhere producing unexpected bugs. I guess if someone is going to change global things in VPC it is better to announce it in the channel with @channel mentioning It should be really well thought through before modifying something like this.

  • Was working on a products/packages feature. https://github.com/conscious-growth/hoth/pull/118 https://github.com/conscious-growth/ando/pull/29

29.09.2021

DONE:

  • adjusted products/packages feature after discussion and PR comments

IN PROGRESS:

  • idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

NEXT:

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

30.09.2021

DONE:

IN PROGRESS:

NEXT:

  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

01.10.2021

DONE:

IN PROGRESS:

NEXT:

  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

04.10.2021

DONE:

  • add indexes for fact table
  • fix issue with edd case in business identification handler, when PENDING_EDD should also be eligible for successful identification
    • found inconsistenty w.r.t legal identification status in Solaris docs (peding status from mark_as_ready is not documented)
  • stop bastion EC2 for live db

IN PROGRESS:

NEXT:

  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow
  • !!! All input params should be validated!!! Especially for account

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

06.10.2021

DONE:

  • fix analytics and admin endpoint by introducing shared TokenGuard
  • fix SCA endpoints by fixing the sca challenges fetching condition (wrong: expires_at < Date.now())
  • fix SCA endpoints by avoid passing challengRequestId to Solaris
  • fix SCA endpoints by marking SCA challenges as APPROVED, DECLINED, FAILED

IN PROGRESS:

NEXT:

  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow
  • !!! All input params should be validated!!! Especially for account

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

07.10.2021

DONE:

  • (3DS refactoring) fixing issues in 3ds connected to the fact that change requests are reused

IN PROGRESS:

NEXT:

  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow
  • !!! All input params should be validated!!! Especially for account

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

08.10.2021

DONE:

IN PROGRESS:

  • Create a doc for async workflows
  • Test async workflows in dev using lambda and reals sqs queue

NEXT:

  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow
  • !!! All input params should be validated!!! Especially for account
  • SHOULD WE MARK SCA AS FAILED WHEN CHANGE REQUEST AUTHORIZATION FAILS??????????????????????????????

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

11.10.2021

DONE:

IN PROGRESS:

  • Create a doc for async workflows
  • Test async workflows in dev using lambda and reals sqs queue

NEXT:

  • Make toCamelKeys and toSnakeKeys global in axios through the request config transoformers
  • Find a place where we're trying to create a fact without an org (I guess there we're passing org as part of the payload)
  • Create a RFC in Notion for state transitions use Business Identification as example
  • Idempotancy in message handlers: docs, make them smaller and atomic, create statuses table for a particular flow
  • !!! All input params should be validated!!! Especially for account
  • SHOULD WE MARK SCA AS FAILED WHEN CHANGE REQUEST AUTHORIZATION FAILS??????????????????????????????

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! START CREATING FACTS ON CARDS STATUS CHANGE IN CardLifecycleEventHandler
  • !!! Handle failures in identification process and all unknown events in general
  • !!! Send login success/failure events to hoth via SQS
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

12.10.2021

DONE:

  • Created a doc for async workflows
  • Investigated an issue with the customer not being able to recover its pass (because of absent cognito-idp:AdminUpdateUserAttributes permission in setPassword lambda)

IN PROGRESS:

NEXT:

  • Make toCamelKeys and toSnakeKeys global in axios through the request config transoformers
  • !!! All input params should be validated!!! Especially for account

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! Handle failures in identification process and all unknown events in general
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

13.10.2021

DONE:

  • Extended a README.md for async workflows and deployed the feature
  • Fixed an issue with the customer not being able to recover its pass (because of absent cognito-idp:AdminUpdateUserAttributes permission in setPassword lambda)
  • Realized that we have issues with multitenancy and documented it adding endpoints for member tax identification

IN PROGRESS:

NEXT:

  • Make toCamelKeys and toSnakeKeys global in axios through the request config transoformers
  • !!! All input params should be validated!!! Especially for account
  • Fix issues with multitenancy

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! Handle failures in identification process and all unknown events in general
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

01.11.2021

DONE:

IN PROGRESS:

NEXT:

  • Make toCamelKeys and toSnakeKeys global in axios through the request config transoformers
  • !!! All input params should be validated!!! Especially for account
  • Fix issues with multitenancy

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! Handle failures in identification process and all unknown events in general
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

THOUGHTS:

  • The duplication can be of two types:
    • Accidental
    • Parent Child relationship

With Parent-Child relationship we can extend one class to inherit props for another one. But conceptually the child is the same as parent, and can be used in the same context.

With accidental, even though the fields are the same for both classes or one class has just a subset of them, doesn't mean we can use inheritance, because conceptually those two classes cannot be used in the same contexts and child is not parent!

WE NEED TO SOLVE ISSUES with complicatedly nested GQL errors!!!!!!!!!

09.11.2021

DONE:

  • helped Wissem with Solaris local env
  • participated in the epic discussion
  • implemented FATCA update

IN PROGRESS:

NEXT:

  • Make toCamelKeys and toSnakeKeys global in axios through the request config transoformers
  • !!! All input params should be validated!!! Especially for account
  • Fix issues with multitenancy

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! Handle failures in identification process and all unknown events in general
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

THOUGHTS:

  • The duplication can be of two types:
    • Accidental
    • Parent Child relationship

With Parent-Child relationship we can extend one class to inherit props for another one. But conceptually the child is the same as parent, and can be used in the same context.

With accidental, even though the fields are the same for both classes or one class has just a subset of them, doesn't mean we can use inheritance, because conceptually those two classes cannot be used in the same contexts and child is not parent!

23.11.2021

DONE:

IN PROGRESS:

NEXT:

  • Check whether we receive a BUSINESS_CHANGED webhook for screening_progress changes, and if so rewrite business identification procedure.
  • Fix issues with multitenancy

TODO:

  • Create a doc on where to store file attachments (to use in Naraka afterwards)
  • Create a doc on how to use test card transactions and fraud cases
  • !!! Handle failures in identification process and all unknown events in general
  • Create tickets from TODOs available in Hoth codebase

QUESTIONS:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment