diff --git a/cloudformation/ecs-service.yaml b/cloudformation/ecs-service.yaml
index 3099f95..47536fb 100644
--- a/cloudformation/ecs-service.yaml
+++ b/cloudformation/ecs-service.yaml
@@ -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:
diff --git a/docker-compose.yml b/docker-compose.yml
index 57012ed..c82ffeb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -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:
diff --git a/src/datasources/interior-api.ts b/src/datasources/interior-api.ts
index 51f8b8d..b6b313d 100644
--- a/src/datasources/interior-api.ts
+++ b/src/datasources/interior-api.ts
@@ -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 {
diff --git a/src/datasources/interiors-lambda-api.ts b/src/datasources/interiors-lambda-api.ts
new file mode 100644
index 0000000..810d477
--- /dev/null
+++ b/src/datasources/interiors-lambda-api.ts
@@ -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
+ *
+ * const product = await (dataSources.productApi).getProduct(productId);
+ * await (dataSources.interiorsLambdaAPI).generateNewInteriors(product);
+ *
+ */
+ async generateNewInteriors(product: Product): Promise {
+ 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);
+ }
+}
diff --git a/src/datasources/order-api.ts b/src/datasources/order-api.ts
index eb61602..a701053 100644
--- a/src/datasources/order-api.ts
+++ b/src/datasources/order-api.ts
@@ -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): Promise> {
diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts
index 5392a40..84506c2 100644
--- a/src/datasources/product-api.ts
+++ b/src/datasources/product-api.ts
@@ -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 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> {
diff --git a/src/datasources/utils.ts b/src/datasources/utils.ts
index b6844ee..373aa4c 100644
--- a/src/datasources/utils.ts
+++ b/src/datasources/utils.ts
@@ -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);
+}
diff --git a/src/index.ts b/src/index.ts
index 3c6ef47..357187f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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,
diff --git a/src/resolvers/keywords-resolver.ts b/src/resolvers/keywords-resolver.ts
index 0c36b2d..82f2e69 100644
--- a/src/resolvers/keywords-resolver.ts
+++ b/src/resolvers/keywords-resolver.ts
@@ -30,7 +30,6 @@ export const keywordsMutationTypeDefs = {
name,
type,
);
- console.log(id);
return (dataSources.keywordApi).getKeywordById(id);
},
};
diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts
index 34b5562..320563b 100644
--- a/src/resolvers/products-resolver.ts
+++ b/src/resolvers/products-resolver.ts
@@ -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 (dataSources.productApi).getProduct(productId);
+ const product = await (dataSources.productApi).getProduct(
+ productId,
+ );
+ await ((
+ dataSources.interiorsLambdaApi
+ )).generateNewInteriors(product);
+ return product;
},
async productKeywords(_, { productId, keywordIds }, { dataSources }) {
await (dataSources.keywordApi).setProductKeywords(
diff --git a/src/schema.graphql b/src/schema.graphql
index 13a84ad..1944340 100644
--- a/src/schema.graphql
+++ b/src/schema.graphql
@@ -267,7 +267,7 @@ type ProductFields {
focusYpoint: Float
focusXpoint2: Float
focusYpoint2: Float
- batch: String!
+ batch: String
imageResolution: Int
printFileWidth: Int
printFileHeight: Int