More careful default types 2 (#37)
This commit is contained in:
@@ -91,7 +91,7 @@ Resources:
|
||||
Cluster:
|
||||
Type: AWS::ECS::Cluster
|
||||
Properties:
|
||||
ClusterName: !Join ["", [!Ref "AWS::StackName", Cluster]]
|
||||
ClusterName: !Join ['', [!Ref 'AWS::StackName', Cluster]]
|
||||
|
||||
TaskDefinition:
|
||||
Type: AWS::ECS::TaskDefinition
|
||||
@@ -99,7 +99,7 @@ Resources:
|
||||
DependsOn: LogGroup
|
||||
Properties:
|
||||
# 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
|
||||
NetworkMode: awsvpc
|
||||
RequiresCompatibilities:
|
||||
@@ -119,10 +119,14 @@ Resources:
|
||||
- ContainerPort: !Ref ContainerPort
|
||||
# Define which parameters should be fetched
|
||||
Secrets:
|
||||
- Name: "DATABASE_URL"
|
||||
ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/graphql-api/DATABASE_URL"
|
||||
- Name: "COGNITO_POOL_ID"
|
||||
ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/cognito/ADMIN_POOL_ID"
|
||||
- Name: 'DATABASE_URL'
|
||||
ValueFrom: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/graphql-api/DATABASE_URL'
|
||||
- Name: 'COGNITO_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
|
||||
LogConfiguration:
|
||||
LogDriver: awslogs
|
||||
@@ -135,47 +139,47 @@ Resources:
|
||||
ExecutionRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: !Join ["", [!Ref "AWS::StackName", ExecutionRole]]
|
||||
RoleName: !Join ['', [!Ref 'AWS::StackName', ExecutionRole]]
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ecs-tasks.amazonaws.com
|
||||
Action: "sts:AssumeRole"
|
||||
Action: 'sts:AssumeRole'
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
||||
- 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
|
||||
- arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
|
||||
# A role for the containers
|
||||
TaskRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: !Join ["", [!Ref "AWS::StackName", TaskRole]]
|
||||
RoleName: !Join ['', [!Ref 'AWS::StackName', TaskRole]]
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ecs-tasks.amazonaws.com
|
||||
Action: "sts:AssumeRole"
|
||||
Action: 'sts:AssumeRole'
|
||||
|
||||
# A role needed for auto scaling
|
||||
AutoScalingRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: !Join ["", [!Ref "AWS::StackName", AutoScalingRole]]
|
||||
RoleName: !Join ['', [!Ref 'AWS::StackName', AutoScalingRole]]
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ecs-tasks.amazonaws.com
|
||||
Action: "sts:AssumeRole"
|
||||
Action: 'sts:AssumeRole'
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole"
|
||||
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
|
||||
|
||||
ContainerSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription:
|
||||
!Join ["", [!Ref "AWS::StackName", ContainerSecurityGroup]]
|
||||
!Join ['', [!Ref 'AWS::StackName', ContainerSecurityGroup]]
|
||||
VpcId: !Ref VPC
|
||||
SecurityGroupIngress:
|
||||
- IpProtocol: tcp
|
||||
@@ -187,7 +191,7 @@ Resources:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription:
|
||||
!Join ["", [!Ref "AWS::StackName", LoadBalancerSecurityGroup]]
|
||||
!Join ['', [!Ref 'AWS::StackName', LoadBalancerSecurityGroup]]
|
||||
VpcId: !Ref VPC
|
||||
SecurityGroupIngress:
|
||||
- IpProtocol: tcp
|
||||
@@ -235,7 +239,7 @@ Resources:
|
||||
HealthCheckTimeoutSeconds: 10
|
||||
UnhealthyThresholdCount: 2
|
||||
HealthyThresholdCount: 2
|
||||
Name: !Join ["", [!Ref "AWS::StackName", Group]]
|
||||
Name: !Join ['', [!Ref 'AWS::StackName', Group]]
|
||||
Port: !Ref ContainerPort
|
||||
Protocol: HTTP
|
||||
TargetGroupAttributes:
|
||||
@@ -262,7 +266,7 @@ Resources:
|
||||
LoadBalancerAttributes:
|
||||
- Key: idle_timeout.timeout_seconds
|
||||
Value: 1200
|
||||
Name: !Join ["", [!Ref "AWS::StackName", LB]]
|
||||
Name: !Join ['', [!Ref 'AWS::StackName', LB]]
|
||||
# "internal" is also an option
|
||||
Scheme: internet-facing
|
||||
SecurityGroups:
|
||||
@@ -274,8 +278,8 @@ Resources:
|
||||
DNSRecord:
|
||||
Type: AWS::Route53::RecordSet
|
||||
Properties:
|
||||
HostedZoneName: !Join ["", [!Ref HostedZoneName, .]]
|
||||
Name: !Join ["", [!Ref Subdomain, ., !Ref HostedZoneName, .]]
|
||||
HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
|
||||
Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]]
|
||||
Type: A
|
||||
AliasTarget:
|
||||
DNSName: !GetAtt LoadBalancer.DNSName
|
||||
@@ -284,14 +288,14 @@ Resources:
|
||||
LogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: !Join ["", [/ecs/, !Ref "AWS::StackName", TaskDefinition]]
|
||||
LogGroupName: !Join ['', [/ecs/, !Ref 'AWS::StackName', TaskDefinition]]
|
||||
|
||||
AutoScalingTarget:
|
||||
Type: AWS::ApplicationAutoScaling::ScalableTarget
|
||||
Properties:
|
||||
MinCapacity: !Ref MinContainers
|
||||
MaxCapacity: !Ref MaxContainers
|
||||
ResourceId: !Join ["/", [service, !Ref Cluster, !GetAtt Service.Name]]
|
||||
ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
|
||||
ScalableDimension: ecs:service:DesiredCount
|
||||
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."
|
||||
@@ -299,7 +303,7 @@ Resources:
|
||||
AutoScalingPolicy:
|
||||
Type: AWS::ApplicationAutoScaling::ScalingPolicy
|
||||
Properties:
|
||||
PolicyName: !Join ["", [!Ref "AWS::StackName", AutoScalingPolicy]]
|
||||
PolicyName: !Join ['', [!Ref 'AWS::StackName', AutoScalingPolicy]]
|
||||
PolicyType: TargetTrackingScaling
|
||||
ScalingTargetId: !Ref AutoScalingTarget
|
||||
TargetTrackingScalingPolicyConfiguration:
|
||||
@@ -327,7 +331,7 @@ Resources:
|
||||
PolicyDocument:
|
||||
Version: 2012-10-17
|
||||
Statement:
|
||||
- Resource: "*"
|
||||
- Resource: '*'
|
||||
Effect: Allow
|
||||
Action:
|
||||
- logs:CreateLogGroup
|
||||
@@ -380,7 +384,7 @@ Resources:
|
||||
- s3:GetObject
|
||||
- s3:GetObjectVersion
|
||||
- s3:GetBucketVersioning
|
||||
- Resource: "*"
|
||||
- Resource: '*'
|
||||
Effect: Allow
|
||||
Action:
|
||||
- ecs:List*
|
||||
@@ -455,7 +459,7 @@ Resources:
|
||||
Type: AWS::CodePipeline::Pipeline
|
||||
Properties:
|
||||
RoleArn: !GetAtt CodePipelineServiceRole.Arn
|
||||
Name: !Join ["", [!Ref "AWS::StackName", Pipeline]]
|
||||
Name: !Join ['', [!Ref 'AWS::StackName', Pipeline]]
|
||||
ArtifactStore:
|
||||
Type: S3
|
||||
Location: !Ref ArtifactBucket
|
||||
@@ -511,7 +515,7 @@ Resources:
|
||||
Outputs:
|
||||
Endpoint:
|
||||
Description: Endpoint
|
||||
Value: !Join ["", ["https://", !Ref DNSRecord]]
|
||||
Value: !Join ['', ['https://', !Ref DNSRecord]]
|
||||
Service:
|
||||
Value: Service
|
||||
PipelineUrl:
|
||||
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
environment:
|
||||
- COGNITO_POOL_ID=eu-west-1_3O4VfvPn7
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- INTERIOR_TOKEN=${INTERIOR_TOKEN}
|
||||
- INTERIOR_URL=${INTERIOR_URL}
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
||||
@@ -47,7 +47,7 @@ export class InteriorAPI extends BaseSQLDataSource {
|
||||
}
|
||||
|
||||
getInteriorRoomNumber(roomNumberPart: string): number {
|
||||
return convertToPossibleType(roomNumberPart.slice(4));
|
||||
return parseInt(roomNumberPart.slice(4), 10);
|
||||
}
|
||||
|
||||
decorateRow(row: any): Interior {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export class OrderAPI extends BaseSQLDataSource {
|
||||
.count()
|
||||
.first()
|
||||
.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>> {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
ProductSizeTypes,
|
||||
} from '../types/product-types';
|
||||
import { Maybe } from '../types/types';
|
||||
import { convertToPossibleType } from './utils';
|
||||
import { convertToPossibleType, nullOrNumber } from './utils';
|
||||
|
||||
const MINUTE = 60;
|
||||
|
||||
@@ -75,11 +75,40 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
if (!row.fields) {
|
||||
return null; // 53460 for example
|
||||
}
|
||||
// Convert to camelCase first
|
||||
const fields = Object.keys(row.fields).reduce((mem, key) => {
|
||||
mem[camelcase(key)] = convertToPossibleType(row.fields[key]);
|
||||
mem[camelcase(key)] = row.fields[key];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -104,8 +133,6 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
row.fields = this.getProductFields(row);
|
||||
row.designerId = row.designerid;
|
||||
row.orientation = this.getOrientation(row.fields);
|
||||
row.copyright = row.copyright ?? '';
|
||||
row.batch = row.batch ?? '';
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -114,7 +141,7 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
const res = await this.cachedRaw(query)
|
||||
.cache(MINUTE * 60)
|
||||
.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>> {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
function isNumeric(n: any) {
|
||||
return !isNaN(n);
|
||||
type InputType = string | undefined | null;
|
||||
|
||||
function isNumeric(n: InputType) {
|
||||
return !isNaN(parseFloat(n));
|
||||
}
|
||||
|
||||
function isBoolean(n: any) {
|
||||
function isBoolean(n: InputType | boolean) {
|
||||
if (typeof n === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
@@ -13,9 +15,13 @@ function isBoolean(n: any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function convertToPossibleType(n: any) {
|
||||
export function convertToPossibleType(n: InputType) {
|
||||
if ('' === n) return '';
|
||||
if (isBoolean(n)) return Boolean(n);
|
||||
if (isNumeric(n)) return Number(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
export function nullOrNumber(n: InputType) {
|
||||
return n === null || '' === n ? null : Number(n);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { InteriorAPI } from './datasources/interior-api';
|
||||
|
||||
import { verifyToken } from './cognito/cognito-client';
|
||||
import { readFileSync } from 'fs';
|
||||
import { InteriorsLambdaAPI } from './datasources/interiors-lambda-api';
|
||||
const path = require('path');
|
||||
const typeDefs = readFileSync(
|
||||
path.join(__dirname, './schema.graphql'),
|
||||
@@ -29,6 +30,7 @@ const knexConfig = knexStringcase(dbConfig);
|
||||
const categoryApi = new CategoryAPI(knexConfig);
|
||||
const designerApi = new DesignerAPI(knexConfig);
|
||||
const imageServerApi = new ImageServerApi();
|
||||
const interiorsLambdaApi = new InteriorsLambdaAPI();
|
||||
const interiorApi = new InteriorAPI(knexConfig, imageServerApi);
|
||||
const keywordApi = new KeywordAPI(knexConfig);
|
||||
const marketLocaleApi = new MarketLocaleAPI(knexConfig);
|
||||
@@ -39,6 +41,7 @@ const dataSources = () => ({
|
||||
categoryApi,
|
||||
designerApi,
|
||||
interiorApi,
|
||||
interiorsLambdaApi,
|
||||
imageServerApi,
|
||||
keywordApi,
|
||||
marketLocaleApi,
|
||||
|
||||
@@ -30,7 +30,6 @@ export const keywordsMutationTypeDefs = {
|
||||
name,
|
||||
type,
|
||||
);
|
||||
console.log(id);
|
||||
return (<KeywordAPI>dataSources.keywordApi).getKeywordById(id);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { KeywordAPI } from '../datasources/keyword-api';
|
||||
import { Keyword } from '../types/keyword-types';
|
||||
import { MarketLocaleAPI } from '../datasources/market-locale-api';
|
||||
import { InteriorAPI } from '../datasources/interior-api';
|
||||
import { InteriorsLambdaAPI } from '../datasources/interiors-lambda-api';
|
||||
|
||||
const ProductBlacklist = {
|
||||
async market(parent, _args, { dataSources }) {
|
||||
@@ -111,7 +112,13 @@ export const productMutationTypeDefs = {
|
||||
productId,
|
||||
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 }) {
|
||||
await (<KeywordAPI>dataSources.keywordApi).setProductKeywords(
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ type ProductFields {
|
||||
focusYpoint: Float
|
||||
focusXpoint2: Float
|
||||
focusYpoint2: Float
|
||||
batch: String!
|
||||
batch: String
|
||||
imageResolution: Int
|
||||
printFileWidth: Int
|
||||
printFileHeight: Int
|
||||
|
||||
Reference in New Issue
Block a user