More careful default types 2 (#37)

This commit is contained in:
Niklas Fondberg
2021-09-09 13:21:07 +02:00
committed by GitHub
parent 3365949920
commit fbce7ec879
11 changed files with 130 additions and 41 deletions
+31 -27
View File
@@ -91,7 +91,7 @@ Resources:
Cluster: Cluster:
Type: AWS::ECS::Cluster Type: AWS::ECS::Cluster
Properties: Properties:
ClusterName: !Join ["", [!Ref "AWS::StackName", Cluster]] ClusterName: !Join ['', [!Ref 'AWS::StackName', Cluster]]
TaskDefinition: TaskDefinition:
Type: AWS::ECS::TaskDefinition Type: AWS::ECS::TaskDefinition
@@ -99,7 +99,7 @@ Resources:
DependsOn: LogGroup DependsOn: LogGroup
Properties: Properties:
# Name of the task definition. Subsequent versions of the task definition are grouped together under this name. # Name of the task definition. Subsequent versions of the task definition are grouped together under this name.
Family: !Join ["", [!Ref "AWS::StackName", TaskDefinition]] Family: !Join ['', [!Ref 'AWS::StackName', TaskDefinition]]
# awsvpc is required for Fargate # awsvpc is required for Fargate
NetworkMode: awsvpc NetworkMode: awsvpc
RequiresCompatibilities: RequiresCompatibilities:
@@ -119,10 +119,14 @@ Resources:
- ContainerPort: !Ref ContainerPort - ContainerPort: !Ref ContainerPort
# Define which parameters should be fetched # Define which parameters should be fetched
Secrets: Secrets:
- Name: "DATABASE_URL" - Name: 'DATABASE_URL'
ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/graphql-api/DATABASE_URL" ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/graphql-api/DATABASE_URL'
- Name: "COGNITO_POOL_ID" - Name: 'COGNITO_POOL_ID'
ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/cognito/ADMIN_POOL_ID" ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/cognito/ADMIN_POOL_ID'
- Name: 'INTERIORS_URL'
ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/INTERIORS_URL'
- Name: 'INTERIORS_TOKEN'
ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/INTERIORS_TOKEN'
# Send logs to CloudWatch Logs # Send logs to CloudWatch Logs
LogConfiguration: LogConfiguration:
LogDriver: awslogs LogDriver: awslogs
@@ -135,47 +139,47 @@ Resources:
ExecutionRole: ExecutionRole:
Type: AWS::IAM::Role Type: AWS::IAM::Role
Properties: Properties:
RoleName: !Join ["", [!Ref "AWS::StackName", ExecutionRole]] RoleName: !Join ['', [!Ref 'AWS::StackName', ExecutionRole]]
AssumeRolePolicyDocument: AssumeRolePolicyDocument:
Statement: Statement:
- Effect: Allow - Effect: Allow
Principal: Principal:
Service: ecs-tasks.amazonaws.com Service: ecs-tasks.amazonaws.com
Action: "sts:AssumeRole" Action: 'sts:AssumeRole'
ManagedPolicyArns: ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
- arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
# A role for the containers # A role for the containers
TaskRole: TaskRole:
Type: AWS::IAM::Role Type: AWS::IAM::Role
Properties: Properties:
RoleName: !Join ["", [!Ref "AWS::StackName", TaskRole]] RoleName: !Join ['', [!Ref 'AWS::StackName', TaskRole]]
AssumeRolePolicyDocument: AssumeRolePolicyDocument:
Statement: Statement:
- Effect: Allow - Effect: Allow
Principal: Principal:
Service: ecs-tasks.amazonaws.com Service: ecs-tasks.amazonaws.com
Action: "sts:AssumeRole" Action: 'sts:AssumeRole'
# A role needed for auto scaling # A role needed for auto scaling
AutoScalingRole: AutoScalingRole:
Type: AWS::IAM::Role Type: AWS::IAM::Role
Properties: Properties:
RoleName: !Join ["", [!Ref "AWS::StackName", AutoScalingRole]] RoleName: !Join ['', [!Ref 'AWS::StackName', AutoScalingRole]]
AssumeRolePolicyDocument: AssumeRolePolicyDocument:
Statement: Statement:
- Effect: Allow - Effect: Allow
Principal: Principal:
Service: ecs-tasks.amazonaws.com Service: ecs-tasks.amazonaws.com
Action: "sts:AssumeRole" Action: 'sts:AssumeRole'
ManagedPolicyArns: ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole" - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
ContainerSecurityGroup: ContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup Type: AWS::EC2::SecurityGroup
Properties: Properties:
GroupDescription: GroupDescription:
!Join ["", [!Ref "AWS::StackName", ContainerSecurityGroup]] !Join ['', [!Ref 'AWS::StackName', ContainerSecurityGroup]]
VpcId: !Ref VPC VpcId: !Ref VPC
SecurityGroupIngress: SecurityGroupIngress:
- IpProtocol: tcp - IpProtocol: tcp
@@ -187,7 +191,7 @@ Resources:
Type: AWS::EC2::SecurityGroup Type: AWS::EC2::SecurityGroup
Properties: Properties:
GroupDescription: GroupDescription:
!Join ["", [!Ref "AWS::StackName", LoadBalancerSecurityGroup]] !Join ['', [!Ref 'AWS::StackName', LoadBalancerSecurityGroup]]
VpcId: !Ref VPC VpcId: !Ref VPC
SecurityGroupIngress: SecurityGroupIngress:
- IpProtocol: tcp - IpProtocol: tcp
@@ -235,7 +239,7 @@ Resources:
HealthCheckTimeoutSeconds: 10 HealthCheckTimeoutSeconds: 10
UnhealthyThresholdCount: 2 UnhealthyThresholdCount: 2
HealthyThresholdCount: 2 HealthyThresholdCount: 2
Name: !Join ["", [!Ref "AWS::StackName", Group]] Name: !Join ['', [!Ref 'AWS::StackName', Group]]
Port: !Ref ContainerPort Port: !Ref ContainerPort
Protocol: HTTP Protocol: HTTP
TargetGroupAttributes: TargetGroupAttributes:
@@ -262,7 +266,7 @@ Resources:
LoadBalancerAttributes: LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds - Key: idle_timeout.timeout_seconds
Value: 1200 Value: 1200
Name: !Join ["", [!Ref "AWS::StackName", LB]] Name: !Join ['', [!Ref 'AWS::StackName', LB]]
# "internal" is also an option # "internal" is also an option
Scheme: internet-facing Scheme: internet-facing
SecurityGroups: SecurityGroups:
@@ -274,8 +278,8 @@ Resources:
DNSRecord: DNSRecord:
Type: AWS::Route53::RecordSet Type: AWS::Route53::RecordSet
Properties: Properties:
HostedZoneName: !Join ["", [!Ref HostedZoneName, .]] HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
Name: !Join ["", [!Ref Subdomain, ., !Ref HostedZoneName, .]] Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]]
Type: A Type: A
AliasTarget: AliasTarget:
DNSName: !GetAtt LoadBalancer.DNSName DNSName: !GetAtt LoadBalancer.DNSName
@@ -284,14 +288,14 @@ Resources:
LogGroup: LogGroup:
Type: AWS::Logs::LogGroup Type: AWS::Logs::LogGroup
Properties: Properties:
LogGroupName: !Join ["", [/ecs/, !Ref "AWS::StackName", TaskDefinition]] LogGroupName: !Join ['', [/ecs/, !Ref 'AWS::StackName', TaskDefinition]]
AutoScalingTarget: AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties: Properties:
MinCapacity: !Ref MinContainers MinCapacity: !Ref MinContainers
MaxCapacity: !Ref MaxContainers MaxCapacity: !Ref MaxContainers
ResourceId: !Join ["/", [service, !Ref Cluster, !GetAtt Service.Name]] ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
ScalableDimension: ecs:service:DesiredCount ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs ServiceNamespace: ecs
# "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target." # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target."
@@ -299,7 +303,7 @@ Resources:
AutoScalingPolicy: AutoScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties: Properties:
PolicyName: !Join ["", [!Ref "AWS::StackName", AutoScalingPolicy]] PolicyName: !Join ['', [!Ref 'AWS::StackName', AutoScalingPolicy]]
PolicyType: TargetTrackingScaling PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref AutoScalingTarget ScalingTargetId: !Ref AutoScalingTarget
TargetTrackingScalingPolicyConfiguration: TargetTrackingScalingPolicyConfiguration:
@@ -327,7 +331,7 @@ Resources:
PolicyDocument: PolicyDocument:
Version: 2012-10-17 Version: 2012-10-17
Statement: Statement:
- Resource: "*" - Resource: '*'
Effect: Allow Effect: Allow
Action: Action:
- logs:CreateLogGroup - logs:CreateLogGroup
@@ -380,7 +384,7 @@ Resources:
- s3:GetObject - s3:GetObject
- s3:GetObjectVersion - s3:GetObjectVersion
- s3:GetBucketVersioning - s3:GetBucketVersioning
- Resource: "*" - Resource: '*'
Effect: Allow Effect: Allow
Action: Action:
- ecs:List* - ecs:List*
@@ -455,7 +459,7 @@ Resources:
Type: AWS::CodePipeline::Pipeline Type: AWS::CodePipeline::Pipeline
Properties: Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Join ["", [!Ref "AWS::StackName", Pipeline]] Name: !Join ['', [!Ref 'AWS::StackName', Pipeline]]
ArtifactStore: ArtifactStore:
Type: S3 Type: S3
Location: !Ref ArtifactBucket Location: !Ref ArtifactBucket
@@ -511,7 +515,7 @@ Resources:
Outputs: Outputs:
Endpoint: Endpoint:
Description: Endpoint Description: Endpoint
Value: !Join ["", ["https://", !Ref DNSRecord]] Value: !Join ['', ['https://', !Ref DNSRecord]]
Service: Service:
Value: Service Value: Service
PipelineUrl: PipelineUrl:
+2
View File
@@ -14,6 +14,8 @@ services:
environment: environment:
- COGNITO_POOL_ID=eu-west-1_3O4VfvPn7 - COGNITO_POOL_ID=eu-west-1_3O4VfvPn7
- DATABASE_URL=${DATABASE_URL} - DATABASE_URL=${DATABASE_URL}
- INTERIOR_TOKEN=${INTERIOR_TOKEN}
- INTERIOR_URL=${INTERIOR_URL}
networks: networks:
default: default:
+1 -1
View File
@@ -47,7 +47,7 @@ export class InteriorAPI extends BaseSQLDataSource {
} }
getInteriorRoomNumber(roomNumberPart: string): number { getInteriorRoomNumber(roomNumberPart: string): number {
return convertToPossibleType(roomNumberPart.slice(4)); return parseInt(roomNumberPart.slice(4), 10);
} }
decorateRow(row: any): Interior { decorateRow(row: any): Interior {
+41
View File
@@ -0,0 +1,41 @@
import { RESTDataSource } from 'apollo-datasource-rest';
import { Product, ProductGroup } from '../types/product-types';
export class InteriorsLambdaAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = process.env.INTERIOR_URL;
}
/**
*
* @param product Product with printProducts and fields
* Example usage in a resolver
* <code>
* const product = await (<ProductAPI>dataSources.productApi).getProduct(productId);
* await (<InteriorsLambdaAPI>dataSources.interiorsLambdaAPI).generateNewInteriors(product);
* </code>
*/
async generateNewInteriors(product: Product): Promise<any> {
const found = product.printProducts.find(
(pr) => pr.groupId === ProductGroup.WALLPAPER,
);
const isRepeating = found !== undefined;
const body = {
token: process.env.INTERIOR_TOKEN,
image: `products/${product.id}.jpg`,
primary_image_area: [
product.fields.focusXpoint2,
product.fields.focusYpoint2,
],
autodetect: true,
};
if (isRepeating) {
body['print_file_height'] = product.fields.printFileHeight;
body['print_file_dpi'] = product.fields.printFileDpi;
}
return this.post('/batch', body);
}
}
+1 -1
View File
@@ -66,7 +66,7 @@ export class OrderAPI extends BaseSQLDataSource {
.count() .count()
.first() .first()
.cache(MINUTE * 60); .cache(MINUTE * 60);
return convertToPossibleType(res['count']); // Optimize later to return Promise return res['count'] as number; // Optimize later to return Promise
} }
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> { async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> {
+32 -5
View File
@@ -17,7 +17,7 @@ import {
ProductSizeTypes, ProductSizeTypes,
} from '../types/product-types'; } from '../types/product-types';
import { Maybe } from '../types/types'; import { Maybe } from '../types/types';
import { convertToPossibleType } from './utils'; import { convertToPossibleType, nullOrNumber } from './utils';
const MINUTE = 60; const MINUTE = 60;
@@ -75,11 +75,40 @@ export class ProductAPI extends BaseSQLDataSource {
if (!row.fields) { if (!row.fields) {
return null; // 53460 for example return null; // 53460 for example
} }
// Convert to camelCase first
const fields = Object.keys(row.fields).reduce((mem, key) => { const fields = Object.keys(row.fields).reduce((mem, key) => {
mem[camelcase(key)] = convertToPossibleType(row.fields[key]); mem[camelcase(key)] = row.fields[key];
return mem; return mem;
}, {}); }, {});
fields['artNo'] = fields['artNo'];
fields['name'] = fields['name'];
fields['copyright'] = fields['copyright'] ?? '';
fields['batch'] = fields['batch'] ?? '';
fields['height'] = nullOrNumber(fields['height'] ?? null);
fields['width'] = nullOrNumber(fields['width'] ?? null);
fields['printFileWidth'] = nullOrNumber(fields['printFileWidth'] ?? null);
fields['printFileHeight'] = nullOrNumber(fields['printFileHeight'] ?? null);
fields['printFileDpi'] = nullOrNumber(fields['printFileDpi'] ?? null);
fields['marginWidthMax'] = nullOrNumber(fields['marginWidthMax'] ?? null);
fields['marginWidthMin'] = nullOrNumber(fields['marginWidthMin'] ?? null);
fields['marginHeightMax'] = nullOrNumber(fields['marginHeightMax'] ?? null);
fields['marginHeightMin'] = nullOrNumber(fields['marginHeightMin'] ?? null);
fields['focusXpoint2'] = nullOrNumber(fields['focusXpoint2'] ?? null);
fields['focusYpoint2'] = nullOrNumber(fields['focusYpoint2'] ?? null);
fields['focusXpoint'] = nullOrNumber(fields['focusXpoint'] ?? null);
fields['focusYpoint'] = nullOrNumber(fields['focusYpoint'] ?? null);
fields['photowallResolution'] = nullOrNumber(
fields['photowallResolution'] ?? null,
);
fields['canvasResolution'] = nullOrNumber(
fields['canvasResolution'] ?? null,
);
fields['wallpaperResolution'] = nullOrNumber(
fields['wallpaperResolution'] ?? null,
);
fields['proportionsWarning?'] = fields['proportionsWarning?'] ?? '';
fields['imageResolution'] = nullOrNumber(fields['imageResolution'] ?? null);
return <ProductFields>fields; return <ProductFields>fields;
} }
@@ -104,8 +133,6 @@ export class ProductAPI extends BaseSQLDataSource {
row.fields = this.getProductFields(row); row.fields = this.getProductFields(row);
row.designerId = row.designerid; row.designerId = row.designerid;
row.orientation = this.getOrientation(row.fields); row.orientation = this.getOrientation(row.fields);
row.copyright = row.copyright ?? '';
row.batch = row.batch ?? '';
return row; return row;
} }
@@ -114,7 +141,7 @@ export class ProductAPI extends BaseSQLDataSource {
const res = await this.cachedRaw(query) const res = await this.cachedRaw(query)
.cache(MINUTE * 60) .cache(MINUTE * 60)
.then((data) => data.rows); .then((data) => data.rows);
return convertToPossibleType(res[0]['count']); // Optimize later to return Promise return res[0]['count'] as number; // Optimize later to return Promise
} }
async getProducts(input: ProductsFilterInput): Promise<Array<Product>> { async getProducts(input: ProductsFilterInput): Promise<Array<Product>> {
+10 -4
View File
@@ -1,8 +1,10 @@
function isNumeric(n: any) { type InputType = string | undefined | null;
return !isNaN(n);
function isNumeric(n: InputType) {
return !isNaN(parseFloat(n));
} }
function isBoolean(n: any) { function isBoolean(n: InputType | boolean) {
if (typeof n === 'boolean') { if (typeof n === 'boolean') {
return true; return true;
} }
@@ -13,9 +15,13 @@ function isBoolean(n: any) {
return false; return false;
} }
export function convertToPossibleType(n: any) { export function convertToPossibleType(n: InputType) {
if ('' === n) return ''; if ('' === n) return '';
if (isBoolean(n)) return Boolean(n); if (isBoolean(n)) return Boolean(n);
if (isNumeric(n)) return Number(n); if (isNumeric(n)) return Number(n);
return n; return n;
} }
export function nullOrNumber(n: InputType) {
return n === null || '' === n ? null : Number(n);
}
+3
View File
@@ -17,6 +17,7 @@ import { InteriorAPI } from './datasources/interior-api';
import { verifyToken } from './cognito/cognito-client'; import { verifyToken } from './cognito/cognito-client';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { InteriorsLambdaAPI } from './datasources/interiors-lambda-api';
const path = require('path'); const path = require('path');
const typeDefs = readFileSync( const typeDefs = readFileSync(
path.join(__dirname, './schema.graphql'), path.join(__dirname, './schema.graphql'),
@@ -29,6 +30,7 @@ const knexConfig = knexStringcase(dbConfig);
const categoryApi = new CategoryAPI(knexConfig); const categoryApi = new CategoryAPI(knexConfig);
const designerApi = new DesignerAPI(knexConfig); const designerApi = new DesignerAPI(knexConfig);
const imageServerApi = new ImageServerApi(); const imageServerApi = new ImageServerApi();
const interiorsLambdaApi = new InteriorsLambdaAPI();
const interiorApi = new InteriorAPI(knexConfig, imageServerApi); const interiorApi = new InteriorAPI(knexConfig, imageServerApi);
const keywordApi = new KeywordAPI(knexConfig); const keywordApi = new KeywordAPI(knexConfig);
const marketLocaleApi = new MarketLocaleAPI(knexConfig); const marketLocaleApi = new MarketLocaleAPI(knexConfig);
@@ -39,6 +41,7 @@ const dataSources = () => ({
categoryApi, categoryApi,
designerApi, designerApi,
interiorApi, interiorApi,
interiorsLambdaApi,
imageServerApi, imageServerApi,
keywordApi, keywordApi,
marketLocaleApi, marketLocaleApi,
-1
View File
@@ -30,7 +30,6 @@ export const keywordsMutationTypeDefs = {
name, name,
type, type,
); );
console.log(id);
return (<KeywordAPI>dataSources.keywordApi).getKeywordById(id); return (<KeywordAPI>dataSources.keywordApi).getKeywordById(id);
}, },
}; };
+8 -1
View File
@@ -10,6 +10,7 @@ import { KeywordAPI } from '../datasources/keyword-api';
import { Keyword } from '../types/keyword-types'; import { Keyword } from '../types/keyword-types';
import { MarketLocaleAPI } from '../datasources/market-locale-api'; import { MarketLocaleAPI } from '../datasources/market-locale-api';
import { InteriorAPI } from '../datasources/interior-api'; import { InteriorAPI } from '../datasources/interior-api';
import { InteriorsLambdaAPI } from '../datasources/interiors-lambda-api';
const ProductBlacklist = { const ProductBlacklist = {
async market(parent, _args, { dataSources }) { async market(parent, _args, { dataSources }) {
@@ -111,7 +112,13 @@ export const productMutationTypeDefs = {
productId, productId,
groupIds, groupIds,
); );
return (<ProductAPI>dataSources.productApi).getProduct(productId); const product = await (<ProductAPI>dataSources.productApi).getProduct(
productId,
);
await (<InteriorsLambdaAPI>(
dataSources.interiorsLambdaApi
)).generateNewInteriors(product);
return product;
}, },
async productKeywords(_, { productId, keywordIds }, { dataSources }) { async productKeywords(_, { productId, keywordIds }, { dataSources }) {
await (<KeywordAPI>dataSources.keywordApi).setProductKeywords( await (<KeywordAPI>dataSources.keywordApi).setProductKeywords(
+1 -1
View File
@@ -267,7 +267,7 @@ type ProductFields {
focusYpoint: Float focusYpoint: Float
focusXpoint2: Float focusXpoint2: Float
focusYpoint2: Float focusYpoint2: Float
batch: String! batch: String
imageResolution: Int imageResolution: Int
printFileWidth: Int printFileWidth: Int
printFileHeight: Int printFileHeight: Int