Added product admin features
This commit is contained in:
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
"extends": ["prettier"],
|
"extends": ["prettier"],
|
||||||
"plugins": ["@typescript-eslint", "prettier"],
|
"plugins": ["@typescript-eslint", "prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": ["error"]
|
"prettier/prettier": ["error"],
|
||||||
|
"no-return-await": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Uses the node base image with the latest LTS version
|
# Uses the node base image with the latest LTS version
|
||||||
FROM node:14.16.0
|
FROM node:14.17.0
|
||||||
# Informs Docker that the container listens on the
|
# Informs Docker that the container listens on the
|
||||||
# specified network ports at runtime
|
# specified network ports at runtime
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Uses the node base image with the latest LTS version
|
||||||
|
FROM node:14.17.0
|
||||||
|
# Informs Docker that the container listens on the
|
||||||
|
# specified network ports at runtime
|
||||||
|
EXPOSE 4000
|
||||||
|
COPY . app/
|
||||||
|
# Changes working directory to the new directory just created
|
||||||
|
WORKDIR /app
|
||||||
|
# Installs npm dependencies on container
|
||||||
|
RUN npm ci
|
||||||
|
# Command container will actually run when called
|
||||||
|
CMD ["npx", "nodemon", "-w", "src", "--ext", "ts", "--exec", "ts-node", "src/index.ts"]
|
||||||
@@ -17,6 +17,7 @@ Then head over to [The playground GUI](https://localhost:4000) in the browser
|
|||||||
## Run tests
|
## Run tests
|
||||||
|
|
||||||
Scripts will create the insert sql files for a randomized fake data postgres database. This database is based of the actual db in photowall project.
|
Scripts will create the insert sql files for a randomized fake data postgres database. This database is based of the actual db in photowall project.
|
||||||
|
|
||||||
```
|
```
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
@@ -27,6 +28,6 @@ The tests are held in testcontainers [testcontainers-node](https://github.com/te
|
|||||||
|
|
||||||
Each test is build around inserting fake data in the empty db, fetching that data through the GraphQL and with raw Sql queries and then comparing the results.
|
Each test is build around inserting fake data in the empty db, fetching that data through the GraphQL and with raw Sql queries and then comparing the results.
|
||||||
|
|
||||||
To write new inserts into the DB generate sql files with js in the src/__test__/db_generator tool. Then load the new sql file in the containerSetup.ts file.
|
To write new inserts into the DB generate sql files with js in the src/**test**/db_generator tool. Then load the new sql file in the containerSetup.ts file.
|
||||||
|
|
||||||
When you have the data you need in the db, write tests in all.test.ts file following the pattern already set.
|
When you have the data you need in the db, write tests in all.test.ts file following the pattern already set.
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
##
|
||||||
|
# Example:
|
||||||
|
# make create-stack STACK_ENVIRONMENT_NAME=staging
|
||||||
|
# make create-stack STACK_ENVIRONMENT_NAME=production
|
||||||
|
#
|
||||||
|
# # make update-stack STACK_ENVIRONMENT_NAME=staging
|
||||||
|
##
|
||||||
|
STACK_NAME=graphql
|
||||||
|
|
||||||
|
GITHUB_REPO := graphql-api-js
|
||||||
|
GITHUB_USER := Photowall
|
||||||
|
$(eval GITHUB_TOKEN=$(shell aws ssm get-parameter --with-decryption --name /github/CODE_PIPELINE_TOKEN --output text --query Parameter.Value))
|
||||||
|
|
||||||
|
AWS_REGION=$(shell aws configure get region)
|
||||||
|
AWS_ACCOUNT_ID=$(shell aws sts get-caller-identity --query Account --output text)
|
||||||
|
|
||||||
|
# HOSTED_ZONE=photowall.com default in the template
|
||||||
|
# Cert ARN for the hosted zone
|
||||||
|
CERTIFICATE_ARN='arn:aws:acm:eu-west-1:954747537408:certificate/1278df32-006d-48a4-abd6-2bfccabb11fd'
|
||||||
|
DB_SECURITY_GROUP_ID_PRODUCTION=sg-0b5bafc1e0a97e848
|
||||||
|
DB_SECURITY_GROUP_ID_STAGING=sg-001b3667edf19e1ce
|
||||||
|
|
||||||
|
|
||||||
|
checkdef = $(if $(value $(1)),,$(error $(1) not set))
|
||||||
|
|
||||||
|
DOCKER_IMAGE=$(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
|
||||||
|
ifeq ($(STACK_ENVIRONMENT_NAME),production)
|
||||||
|
SUBDOMAIN=$(STACK_NAME)
|
||||||
|
GITHUB_BRANCH=master
|
||||||
|
DB_SECURITY_GROUP_ID=$(DB_SECURITY_GROUP_ID_PRODUCTION)
|
||||||
|
CPU_SIZE=1024
|
||||||
|
MEMORY_SIZE=2GB
|
||||||
|
else
|
||||||
|
SUBDOMAIN=$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
GITHUB_BRANCH=$(STACK_ENVIRONMENT_NAME)
|
||||||
|
DB_SECURITY_GROUP_ID=$(DB_SECURITY_GROUP_ID_STAGING)
|
||||||
|
CPU_SIZE=512
|
||||||
|
MEMORY_SIZE=1GB
|
||||||
|
endif
|
||||||
|
|
||||||
|
check-create-initial-ecr-image:
|
||||||
|
@echo Checking for inital image $(DOCKER_IMAGE)
|
||||||
|
@aws ecr list-images --repository-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) 2>/dev/null | grep imageTag | grep -q latest; \
|
||||||
|
status=$$?; \
|
||||||
|
if [ $$status != 0 ]; \
|
||||||
|
then \
|
||||||
|
echo ""; \
|
||||||
|
echo "ECR Repo for initial image does not exist, creating"; \
|
||||||
|
echo ""; \
|
||||||
|
aws ecr create-repository --repository $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME); \
|
||||||
|
aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com; \
|
||||||
|
docker pull nginx; \
|
||||||
|
docker tag nginx $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME):latest; \
|
||||||
|
docker push $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME):latest; \
|
||||||
|
echo ""; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
set-repository-lifecycle-policy:
|
||||||
|
@echo Setting lifecycle policy for repo $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
@aws ecr put-lifecycle-policy \
|
||||||
|
--repository-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) \
|
||||||
|
--lifecycle-policy-text "file://ecr-lifecycle-policy.json"
|
||||||
|
|
||||||
|
check-create-variables-set:
|
||||||
|
$(call checkdef,STACK_NAME)
|
||||||
|
$(call checkdef,STACK_ENVIRONMENT_NAME)
|
||||||
|
$(call checkdef,GITHUB_TOKEN)
|
||||||
|
$(call checkdef,GITHUB_USER)
|
||||||
|
$(call checkdef,GITHUB_REPO)
|
||||||
|
|
||||||
|
check-update-variables-set:
|
||||||
|
$(call checkdef,STACK_ENVIRONMENT_NAME)
|
||||||
|
|
||||||
|
delete-stack:
|
||||||
|
aws cloudformation delete-stack --stack-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
aws ecr delete-repository --force --repository-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
@echo ""
|
||||||
|
@echo "################################################################"
|
||||||
|
@echo "# Do not forget to delete the articast bucket and loggroup:"
|
||||||
|
@aws s3 ls | grep $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)
|
||||||
|
@echo "# aws s3 rb --force s3://<bucket name>"
|
||||||
|
@echo ""
|
||||||
|
@echo "aws logs delete-log-group --log-group-name /aws/codebuild/$(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)"
|
||||||
|
@echo "aws logs delete-log-group --log-group-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME)"
|
||||||
|
@echo "################################################################"
|
||||||
|
|
||||||
|
create-stack: check-create-variables-set check-create-initial-ecr-image
|
||||||
|
aws cloudformation create-stack --stack-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \
|
||||||
|
--capabilities CAPABILITY_NAMED_IAM \
|
||||||
|
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) \
|
||||||
|
--parameters ParameterKey="EnvironmentName",ParameterValue="$(STACK_ENVIRONMENT_NAME)" \
|
||||||
|
ParameterKey="Image",ParameterValue="$(DOCKER_IMAGE)" \
|
||||||
|
ParameterKey="Subdomain",ParameterValue="$(SUBDOMAIN)" \
|
||||||
|
ParameterKey="Certificate",ParameterValue="$(CERTIFICATE_ARN)" \
|
||||||
|
ParameterKey="DatabaseSecurityGroup",ParameterValue="$(DB_SECURITY_GROUP_ID)" \
|
||||||
|
ParameterKey="GitHubRepo",ParameterValue="$(GITHUB_REPO)" \
|
||||||
|
ParameterKey="GitHubUser",ParameterValue="$(GITHUB_USER)" \
|
||||||
|
ParameterKey="GitHubBranch",ParameterValue="$(GITHUB_BRANCH)" \
|
||||||
|
ParameterKey="GitHubToken",ParameterValue="$(GITHUB_TOKEN)" \
|
||||||
|
ParameterKey="CpuSize",ParameterValue="$(CPU_SIZE)" \
|
||||||
|
ParameterKey="MemorySize",ParameterValue="$(MEMORY_SIZE)"
|
||||||
|
|
||||||
|
update-stack: set-repository-lifecycle-policy
|
||||||
|
$(call checkdef,STACK_NAME)
|
||||||
|
$(call checkdef,ENVIRONMENT_NAME)
|
||||||
|
aws cloudformation update-stack --stack-name $(STACK_NAME)-$(STACK_ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \
|
||||||
|
--capabilities CAPABILITY_NAMED_IAM \
|
||||||
|
--tags Key=EnvironmentName,Value=$(STACK_ENVIRONMENT_NAME) \
|
||||||
|
--parameters ParameterKey="EnvironmentName",UsePreviousValue=true \
|
||||||
|
ParameterKey="Image",UsePreviousValue=true \
|
||||||
|
ParameterKey="Subdomain",UsePreviousValue=true \
|
||||||
|
ParameterKey="Certificate",UsePreviousValue=true \
|
||||||
|
ParameterKey="DatabaseSecurityGroup",,UsePreviousValue=true \
|
||||||
|
ParameterKey="GitHubRepo",UsePreviousValue=true \
|
||||||
|
ParameterKey="GitHubUser",UsePreviousValue=true \
|
||||||
|
ParameterKey="GitHubBranch",UsePreviousValue=true \
|
||||||
|
ParameterKey="GitHubToken",UsePreviousValue=true \
|
||||||
|
ParameterKey="CpuSize",ParameterValue="$(CPU_SIZE)" \
|
||||||
|
ParameterKey="MemorySize",ParameterValue="$(MEMORY_SIZE)"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"rulePriority": 1,
|
||||||
|
"description": "Only keep 8 images",
|
||||||
|
"selection": {
|
||||||
|
"tagStatus": "any",
|
||||||
|
"countType": "imageCountMoreThan",
|
||||||
|
"countNumber": 8
|
||||||
|
},
|
||||||
|
"action": { "type": "expire" }
|
||||||
|
}]
|
||||||
|
}
|
||||||
@@ -0,0 +1,527 @@
|
|||||||
|
AWSTemplateFormatVersion: 2010-09-09
|
||||||
|
Description: CloudFormation template for a full E2E ECS service with CI/CD on Fargate.
|
||||||
|
Parameters:
|
||||||
|
VPC:
|
||||||
|
Type: AWS::EC2::VPC::Id
|
||||||
|
Default: vpc-6f596904
|
||||||
|
Description: The id of the VPC
|
||||||
|
SubnetA:
|
||||||
|
Type: AWS::EC2::Subnet::Id
|
||||||
|
Default: subnet-6e596905
|
||||||
|
Description: The id of the first subnet
|
||||||
|
SubnetB:
|
||||||
|
Type: AWS::EC2::Subnet::Id
|
||||||
|
Default: subnet-6d596906
|
||||||
|
Description: The id of the second subnet
|
||||||
|
|
||||||
|
DatabaseSecurityGroup:
|
||||||
|
Type: String
|
||||||
|
Description: Security group id to access the database
|
||||||
|
|
||||||
|
Image:
|
||||||
|
Type: String
|
||||||
|
# Docker image for this service. It must have a latest tag
|
||||||
|
# Example 260851616142.dkr.ecr.eu-west-1.amazonaws.com/nginx
|
||||||
|
EnvironmentName:
|
||||||
|
Type: String
|
||||||
|
Description: Name of env such as staging or production
|
||||||
|
|
||||||
|
# CPU
|
||||||
|
# 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
|
||||||
|
# 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
|
||||||
|
# 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
|
||||||
|
# 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
|
||||||
|
# 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
|
||||||
|
CpuSize:
|
||||||
|
Type: Number
|
||||||
|
|
||||||
|
# Memory
|
||||||
|
# 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
|
||||||
|
# 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
|
||||||
|
# 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
|
||||||
|
# Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
|
||||||
|
# Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
|
||||||
|
MemorySize:
|
||||||
|
Type: String
|
||||||
|
|
||||||
|
ContainerPort:
|
||||||
|
Type: Number
|
||||||
|
Default: 4000
|
||||||
|
LoadBalancerPort:
|
||||||
|
Type: Number
|
||||||
|
Default: 443
|
||||||
|
HealthCheckPath:
|
||||||
|
Type: String
|
||||||
|
Default: /.well-known/apollo/server-health
|
||||||
|
|
||||||
|
Certificate:
|
||||||
|
Type: String
|
||||||
|
# Update with the certificate ARN from Certificate Manager, which must exist in the same region.
|
||||||
|
HostedZoneName:
|
||||||
|
Type: String
|
||||||
|
Default: photowall.com
|
||||||
|
Subdomain:
|
||||||
|
Type: String
|
||||||
|
|
||||||
|
# for autoscaling
|
||||||
|
MinContainers:
|
||||||
|
Type: Number
|
||||||
|
Default: 1
|
||||||
|
# for autoscaling
|
||||||
|
MaxContainers:
|
||||||
|
Type: Number
|
||||||
|
Default: 4
|
||||||
|
# target CPU utilization (%)
|
||||||
|
AutoScalingTargetValue:
|
||||||
|
Type: Number
|
||||||
|
Default: 50
|
||||||
|
|
||||||
|
# Github info for code pipeline
|
||||||
|
GitHubRepo:
|
||||||
|
Type: String
|
||||||
|
GitHubBranch:
|
||||||
|
Type: String
|
||||||
|
GitHubToken:
|
||||||
|
Type: String
|
||||||
|
NoEcho: true
|
||||||
|
GitHubUser:
|
||||||
|
Type: String
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
Cluster:
|
||||||
|
Type: AWS::ECS::Cluster
|
||||||
|
Properties:
|
||||||
|
ClusterName: !Join ["", [!Ref "AWS::StackName", Cluster]]
|
||||||
|
|
||||||
|
TaskDefinition:
|
||||||
|
Type: AWS::ECS::TaskDefinition
|
||||||
|
# Makes sure the log group is created before it is used.
|
||||||
|
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]]
|
||||||
|
# awsvpc is required for Fargate
|
||||||
|
NetworkMode: awsvpc
|
||||||
|
RequiresCompatibilities:
|
||||||
|
- FARGATE
|
||||||
|
Cpu: !Ref CpuSize
|
||||||
|
Memory: !Ref MemorySize
|
||||||
|
# A role needed by ECS.
|
||||||
|
# "The ARN of the task execution role that containers in this task can assume. All containers in this task are granted the permissions that are specified in this role."
|
||||||
|
# "There is an optional task execution IAM role that you can specify with Fargate to allow your Fargate tasks to make API calls to Amazon ECR."
|
||||||
|
ExecutionRoleArn: !Ref ExecutionRole
|
||||||
|
# "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that grants containers in the task permission to call AWS APIs on your behalf."
|
||||||
|
TaskRoleArn: !Ref TaskRole
|
||||||
|
ContainerDefinitions:
|
||||||
|
- Name: !Ref AWS::StackName
|
||||||
|
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${AWS::StackName}:latest
|
||||||
|
PortMappings:
|
||||||
|
- 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"
|
||||||
|
# Send logs to CloudWatch Logs
|
||||||
|
LogConfiguration:
|
||||||
|
LogDriver: awslogs
|
||||||
|
Options:
|
||||||
|
awslogs-region: !Ref AWS::Region
|
||||||
|
awslogs-group: !Ref LogGroup
|
||||||
|
awslogs-stream-prefix: ecs
|
||||||
|
|
||||||
|
# A role needed by ECS
|
||||||
|
ExecutionRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
RoleName: !Join ["", [!Ref "AWS::StackName", ExecutionRole]]
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: ecs-tasks.amazonaws.com
|
||||||
|
Action: "sts:AssumeRole"
|
||||||
|
ManagedPolicyArns:
|
||||||
|
- "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]]
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: ecs-tasks.amazonaws.com
|
||||||
|
Action: "sts:AssumeRole"
|
||||||
|
|
||||||
|
# A role needed for auto scaling
|
||||||
|
AutoScalingRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
RoleName: !Join ["", [!Ref "AWS::StackName", AutoScalingRole]]
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: ecs-tasks.amazonaws.com
|
||||||
|
Action: "sts:AssumeRole"
|
||||||
|
ManagedPolicyArns:
|
||||||
|
- "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole"
|
||||||
|
|
||||||
|
ContainerSecurityGroup:
|
||||||
|
Type: AWS::EC2::SecurityGroup
|
||||||
|
Properties:
|
||||||
|
GroupDescription:
|
||||||
|
!Join ["", [!Ref "AWS::StackName", ContainerSecurityGroup]]
|
||||||
|
VpcId: !Ref VPC
|
||||||
|
SecurityGroupIngress:
|
||||||
|
- IpProtocol: tcp
|
||||||
|
FromPort: !Ref ContainerPort
|
||||||
|
ToPort: !Ref ContainerPort
|
||||||
|
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
|
||||||
|
|
||||||
|
LoadBalancerSecurityGroup:
|
||||||
|
Type: AWS::EC2::SecurityGroup
|
||||||
|
Properties:
|
||||||
|
GroupDescription:
|
||||||
|
!Join ["", [!Ref "AWS::StackName", LoadBalancerSecurityGroup]]
|
||||||
|
VpcId: !Ref VPC
|
||||||
|
SecurityGroupIngress:
|
||||||
|
- IpProtocol: tcp
|
||||||
|
FromPort: !Ref LoadBalancerPort
|
||||||
|
ToPort: !Ref LoadBalancerPort
|
||||||
|
CidrIp: 0.0.0.0/0
|
||||||
|
|
||||||
|
Service:
|
||||||
|
Type: AWS::ECS::Service
|
||||||
|
# This dependency is needed so that the load balancer is setup correctly in time
|
||||||
|
DependsOn:
|
||||||
|
- ListenerHTTPS
|
||||||
|
Properties:
|
||||||
|
ServiceName: !Ref AWS::StackName
|
||||||
|
Cluster: !Ref Cluster
|
||||||
|
TaskDefinition: !Ref TaskDefinition
|
||||||
|
DeploymentConfiguration:
|
||||||
|
MinimumHealthyPercent: 100
|
||||||
|
MaximumPercent: 200
|
||||||
|
DesiredCount: 1
|
||||||
|
# This may need to be adjusted if the container takes a while to start up
|
||||||
|
HealthCheckGracePeriodSeconds: 30
|
||||||
|
LaunchType: FARGATE
|
||||||
|
NetworkConfiguration:
|
||||||
|
AwsvpcConfiguration:
|
||||||
|
# change to DISABLED if you're using private subnets that have access to a NAT gateway
|
||||||
|
AssignPublicIp: ENABLED
|
||||||
|
Subnets:
|
||||||
|
- !Ref SubnetA
|
||||||
|
- !Ref SubnetB
|
||||||
|
SecurityGroups:
|
||||||
|
- !Ref ContainerSecurityGroup
|
||||||
|
- !Ref DatabaseSecurityGroup
|
||||||
|
LoadBalancers:
|
||||||
|
- ContainerName: !Ref AWS::StackName
|
||||||
|
ContainerPort: !Ref ContainerPort
|
||||||
|
TargetGroupArn: !Ref TargetGroup
|
||||||
|
|
||||||
|
TargetGroup:
|
||||||
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
||||||
|
Properties:
|
||||||
|
HealthCheckIntervalSeconds: 15
|
||||||
|
# will look for a 200 status code by default unless specified otherwise
|
||||||
|
HealthCheckPath: !Ref HealthCheckPath
|
||||||
|
HealthCheckTimeoutSeconds: 10
|
||||||
|
UnhealthyThresholdCount: 2
|
||||||
|
HealthyThresholdCount: 2
|
||||||
|
Name: !Join ["", [!Ref "AWS::StackName", Group]]
|
||||||
|
Port: !Ref ContainerPort
|
||||||
|
Protocol: HTTP
|
||||||
|
TargetGroupAttributes:
|
||||||
|
- Key: deregistration_delay.timeout_seconds
|
||||||
|
Value: 60 # default is 300
|
||||||
|
TargetType: ip
|
||||||
|
VpcId: !Ref VPC
|
||||||
|
|
||||||
|
ListenerHTTPS:
|
||||||
|
Type: AWS::ElasticLoadBalancingV2::Listener
|
||||||
|
Properties:
|
||||||
|
DefaultActions:
|
||||||
|
- TargetGroupArn: !Ref TargetGroup
|
||||||
|
Type: forward
|
||||||
|
LoadBalancerArn: !Ref LoadBalancer
|
||||||
|
Port: !Ref LoadBalancerPort
|
||||||
|
Protocol: HTTPS
|
||||||
|
Certificates:
|
||||||
|
- CertificateArn: !Ref Certificate
|
||||||
|
|
||||||
|
LoadBalancer:
|
||||||
|
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
||||||
|
Properties:
|
||||||
|
LoadBalancerAttributes:
|
||||||
|
- Key: idle_timeout.timeout_seconds
|
||||||
|
Value: 1200
|
||||||
|
Name: !Join ["", [!Ref "AWS::StackName", LB]]
|
||||||
|
# "internal" is also an option
|
||||||
|
Scheme: internet-facing
|
||||||
|
SecurityGroups:
|
||||||
|
- !Ref LoadBalancerSecurityGroup
|
||||||
|
Subnets:
|
||||||
|
- !Ref SubnetA
|
||||||
|
- !Ref SubnetB
|
||||||
|
|
||||||
|
DNSRecord:
|
||||||
|
Type: AWS::Route53::RecordSet
|
||||||
|
Properties:
|
||||||
|
HostedZoneName: !Join ["", [!Ref HostedZoneName, .]]
|
||||||
|
Name: !Join ["", [!Ref Subdomain, ., !Ref HostedZoneName, .]]
|
||||||
|
Type: A
|
||||||
|
AliasTarget:
|
||||||
|
DNSName: !GetAtt LoadBalancer.DNSName
|
||||||
|
HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
|
||||||
|
|
||||||
|
LogGroup:
|
||||||
|
Type: AWS::Logs::LogGroup
|
||||||
|
Properties:
|
||||||
|
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]]
|
||||||
|
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."
|
||||||
|
RoleARN: !GetAtt AutoScalingRole.Arn
|
||||||
|
AutoScalingPolicy:
|
||||||
|
Type: AWS::ApplicationAutoScaling::ScalingPolicy
|
||||||
|
Properties:
|
||||||
|
PolicyName: !Join ["", [!Ref "AWS::StackName", AutoScalingPolicy]]
|
||||||
|
PolicyType: TargetTrackingScaling
|
||||||
|
ScalingTargetId: !Ref AutoScalingTarget
|
||||||
|
TargetTrackingScalingPolicyConfiguration:
|
||||||
|
PredefinedMetricSpecification:
|
||||||
|
PredefinedMetricType: ECSServiceAverageCPUUtilization
|
||||||
|
ScaleInCooldown: 10
|
||||||
|
ScaleOutCooldown: 10
|
||||||
|
# Keep things at or lower than 50% CPU utilization, for example
|
||||||
|
TargetValue: !Ref AutoScalingTargetValue
|
||||||
|
|
||||||
|
## Deployment pipeline below
|
||||||
|
CodeBuildServiceRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
Path: /
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: codebuild.amazonaws.com
|
||||||
|
Action: sts:AssumeRole
|
||||||
|
Policies:
|
||||||
|
- PolicyName: root
|
||||||
|
PolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Resource: "*"
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- logs:CreateLogGroup
|
||||||
|
- logs:CreateLogStream
|
||||||
|
- logs:PutLogEvents
|
||||||
|
- ecr:GetAuthorizationToken
|
||||||
|
- Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- s3:GetObject
|
||||||
|
- s3:PutObject
|
||||||
|
- s3:GetObjectVersion
|
||||||
|
- Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${AWS::StackName}
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- ecr:GetDownloadUrlForLayer
|
||||||
|
- ecr:BatchGetImage
|
||||||
|
- ecr:BatchCheckLayerAvailability
|
||||||
|
- ecr:PutImage
|
||||||
|
- ecr:InitiateLayerUpload
|
||||||
|
- ecr:UploadLayerPart
|
||||||
|
- ecr:CompleteLayerUpload
|
||||||
|
- Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/dockerhub/*
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- ssm:GetParameter
|
||||||
|
- ssm:GetParameters
|
||||||
|
|
||||||
|
CodePipelineServiceRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
Path: /
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: codepipeline.amazonaws.com
|
||||||
|
Action: sts:AssumeRole
|
||||||
|
Policies:
|
||||||
|
- PolicyName: root
|
||||||
|
PolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Resource:
|
||||||
|
- !Sub arn:aws:s3:::${ArtifactBucket}/*
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- s3:PutObject
|
||||||
|
- s3:GetObject
|
||||||
|
- s3:GetObjectVersion
|
||||||
|
- s3:GetBucketVersioning
|
||||||
|
- Resource: "*"
|
||||||
|
Effect: Allow
|
||||||
|
Action:
|
||||||
|
- ecs:List*
|
||||||
|
- ecs:Describe*
|
||||||
|
- ecs:RegisterTaskDefinition
|
||||||
|
- ecs:UpdateService
|
||||||
|
- codebuild:StartBuild
|
||||||
|
- codebuild:BatchGetBuilds
|
||||||
|
- iam:PassRole
|
||||||
|
|
||||||
|
ArtifactBucket:
|
||||||
|
Type: AWS::S3::Bucket
|
||||||
|
DeletionPolicy: Retain
|
||||||
|
|
||||||
|
CodeBuildProject:
|
||||||
|
Type: AWS::CodeBuild::Project
|
||||||
|
Properties:
|
||||||
|
Artifacts:
|
||||||
|
Type: CODEPIPELINE
|
||||||
|
Source:
|
||||||
|
Type: CODEPIPELINE
|
||||||
|
BuildSpec: |
|
||||||
|
version: 0.2
|
||||||
|
phases:
|
||||||
|
install:
|
||||||
|
runtime-versions:
|
||||||
|
docker: 19
|
||||||
|
pre_build:
|
||||||
|
commands:
|
||||||
|
- echo Logging in to Docker Hub...
|
||||||
|
- echo $DOCKERHUB_PASSWORD | docker login --username $DOCKERHUB_USERNAME --password-stdin
|
||||||
|
- TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)"
|
||||||
|
- IMAGE_URI="${REPOSITORY_URI}:${TAG}"
|
||||||
|
- IMAGE_LATEST_URI="${REPOSITORY_URI}:latest"
|
||||||
|
build:
|
||||||
|
commands:
|
||||||
|
- docker build --tag "$IMAGE_URI" .
|
||||||
|
- docker tag "$IMAGE_URI" "$IMAGE_LATEST_URI"
|
||||||
|
post_build:
|
||||||
|
commands:
|
||||||
|
- echo Logging in to ECR to push image
|
||||||
|
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||||
|
- docker push "$IMAGE_URI"
|
||||||
|
- docker push "$IMAGE_LATEST_URI"
|
||||||
|
- printf '[{"name":"%s","imageUri":"%s"}]' "$IMAGE_NAME" "$IMAGE_LATEST_URI" > images.json
|
||||||
|
artifacts:
|
||||||
|
files: images.json
|
||||||
|
Environment:
|
||||||
|
ComputeType: BUILD_GENERAL1_SMALL
|
||||||
|
PrivilegedMode: true
|
||||||
|
Image: aws/codebuild/standard:4.0
|
||||||
|
Type: LINUX_CONTAINER
|
||||||
|
EnvironmentVariables:
|
||||||
|
- Name: AWS_DEFAULT_REGION
|
||||||
|
Value: !Ref AWS::Region
|
||||||
|
- Name: AWS_ACCOUNT_ID
|
||||||
|
Value: !Ref AWS::AccountId
|
||||||
|
- Name: REPOSITORY_URI
|
||||||
|
Value: !Ref Image
|
||||||
|
- Name: IMAGE_NAME
|
||||||
|
Value: !Ref AWS::StackName
|
||||||
|
- Name: DOCKERHUB_USERNAME
|
||||||
|
Value: /dockerhub/username
|
||||||
|
Type: PARAMETER_STORE
|
||||||
|
- Name: DOCKERHUB_PASSWORD
|
||||||
|
Value: /dockerhub/password
|
||||||
|
Type: PARAMETER_STORE
|
||||||
|
Name: !Ref AWS::StackName
|
||||||
|
ServiceRole: !Ref CodeBuildServiceRole
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
Type: AWS::CodePipeline::Pipeline
|
||||||
|
Properties:
|
||||||
|
RoleArn: !GetAtt CodePipelineServiceRole.Arn
|
||||||
|
Name: !Join ["", [!Ref "AWS::StackName", Pipeline]]
|
||||||
|
ArtifactStore:
|
||||||
|
Type: S3
|
||||||
|
Location: !Ref ArtifactBucket
|
||||||
|
Stages:
|
||||||
|
- Name: Source
|
||||||
|
Actions:
|
||||||
|
- Name: App
|
||||||
|
ActionTypeId:
|
||||||
|
Category: Source
|
||||||
|
Owner: ThirdParty
|
||||||
|
Version: 1
|
||||||
|
Provider: GitHub
|
||||||
|
Configuration:
|
||||||
|
Owner: !Ref GitHubUser
|
||||||
|
Repo: !Ref GitHubRepo
|
||||||
|
Branch: !Ref GitHubBranch
|
||||||
|
OAuthToken: !Ref GitHubToken
|
||||||
|
OutputArtifacts:
|
||||||
|
- Name: App
|
||||||
|
RunOrder: 1
|
||||||
|
- Name: Build
|
||||||
|
Actions:
|
||||||
|
- Name: Build
|
||||||
|
ActionTypeId:
|
||||||
|
Category: Build
|
||||||
|
Owner: AWS
|
||||||
|
Version: 1
|
||||||
|
Provider: CodeBuild
|
||||||
|
Configuration:
|
||||||
|
ProjectName: !Ref CodeBuildProject
|
||||||
|
InputArtifacts:
|
||||||
|
- Name: App
|
||||||
|
OutputArtifacts:
|
||||||
|
- Name: BuildOutput
|
||||||
|
RunOrder: 1
|
||||||
|
- Name: Deploy
|
||||||
|
Actions:
|
||||||
|
- Name: Deploy
|
||||||
|
ActionTypeId:
|
||||||
|
Category: Deploy
|
||||||
|
Owner: AWS
|
||||||
|
Version: 1
|
||||||
|
Provider: ECS
|
||||||
|
Configuration:
|
||||||
|
ClusterName: !Ref Cluster
|
||||||
|
ServiceName: !Ref Service
|
||||||
|
FileName: images.json
|
||||||
|
InputArtifacts:
|
||||||
|
- Name: BuildOutput
|
||||||
|
RunOrder: 1
|
||||||
|
|
||||||
|
# What outputs to expose
|
||||||
|
Outputs:
|
||||||
|
Endpoint:
|
||||||
|
Description: Endpoint
|
||||||
|
Value: !Join ["", ["https://", !Ref DNSRecord]]
|
||||||
|
Service:
|
||||||
|
Value: Service
|
||||||
|
PipelineUrl:
|
||||||
|
Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}
|
||||||
|
ContainerSecurityGroup:
|
||||||
|
Value: !Ref ContainerSecurityGroup
|
||||||
|
TaskDefinition:
|
||||||
|
Value: !Ref TaskDefinition
|
||||||
|
TargetGroup:
|
||||||
|
Value: !Ref TargetGroup
|
||||||
|
LoadBalancerUrl:
|
||||||
|
Description: The URL of the ALB
|
||||||
|
Value: !GetAtt LoadBalancer.DNSName
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./Dockerfile-dev
|
||||||
|
volumes:
|
||||||
|
- ./:/app
|
||||||
|
container_name: photowall.graphql
|
||||||
|
hostname: graphql.local
|
||||||
|
ports:
|
||||||
|
- '4000:4000'
|
||||||
|
environment:
|
||||||
|
- COGNITO_POOL_ID=eu-west-1_3O4VfvPn7
|
||||||
|
- DATABASE_URL=postgresql://fondberg:@docker.for.mac.localhost/photowall
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
external:
|
||||||
|
name: photowall-docker-network
|
||||||
+4
-3
@@ -1,18 +1,19 @@
|
|||||||
version: '3.7'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./Dockerfile
|
dockerfile: ./Dockerfile
|
||||||
volumes:
|
# volumes:
|
||||||
- ./:/app
|
# - ./:/app
|
||||||
container_name: photowall.graphql
|
container_name: photowall.graphql
|
||||||
hostname: graphql.local
|
hostname: graphql.local
|
||||||
ports:
|
ports:
|
||||||
- '4000:4000'
|
- '4000:4000'
|
||||||
environment:
|
environment:
|
||||||
- COGNITO_POOL_ID=eu-west-1_3O4VfvPn7
|
- COGNITO_POOL_ID=eu-west-1_3O4VfvPn7
|
||||||
|
- DATABASE_URL=postgresql://fondberg:@docker.for.mac.localhost/photowall
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
Generated
+117
-84
@@ -1251,9 +1251,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@nodelib/fs.walk": {
|
"@nodelib/fs.walk": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||||
"integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==",
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nodelib/fs.scandir": "2.1.5",
|
"@nodelib/fs.scandir": "2.1.5",
|
||||||
@@ -1380,9 +1380,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/archiver": {
|
"@types/archiver": {
|
||||||
"version": "5.3.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.1.tgz",
|
||||||
"integrity": "sha512-qJ79qsmq7O/k9FYwsF6O1xVA1PeLV+9Bh3TYkVCu3VzMR6vN9JQkgEOh/rrQ0R+F4Ta+R3thHGewxQtFglwVfg==",
|
"integrity": "sha512-heuaCk0YH5m274NOLSi66H1zX6GtZoMsdE6TYFcpFFjBjg0FoU4i4/M/a/kNlgNg26Xk3g364mNOYe1JaiEPOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/glob": "*"
|
"@types/glob": "*"
|
||||||
@@ -1468,9 +1468,9 @@
|
|||||||
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
|
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
|
||||||
},
|
},
|
||||||
"@types/dockerode": {
|
"@types/dockerode": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.2.4.tgz",
|
||||||
"integrity": "sha512-nZRhpSxm3PYianRBcRExcHxDvEzYHUPfGCnRL5Fe4/fSEZbtxrRNJ7okzCans3lXxj2t298EynFHGTnTC2f1Iw==",
|
"integrity": "sha512-CSpvSohBQib0KCyg99h6RSmLZ4V9wF9hx6vT2FatplBNQpRaUzJh/XGjesRPzvqOlpp/Ol7aLFEgiE02nj/m2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -1506,9 +1506,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/glob": {
|
"@types/glob": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz",
|
||||||
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
|
"integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/minimatch": "*",
|
"@types/minimatch": "*",
|
||||||
@@ -1559,9 +1559,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/jest": {
|
"@types/jest": {
|
||||||
"version": "26.0.23",
|
"version": "26.0.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz",
|
||||||
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
|
"integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"jest-diff": "^26.0.0",
|
"jest-diff": "^26.0.0",
|
||||||
@@ -1569,9 +1569,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.7",
|
"version": "7.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
|
||||||
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
|
"integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/keygrip": {
|
"@types/keygrip": {
|
||||||
@@ -1613,9 +1613,9 @@
|
|||||||
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
|
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
|
||||||
},
|
},
|
||||||
"@types/minimatch": {
|
"@types/minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
|
||||||
"integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==",
|
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
@@ -1649,9 +1649,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/ssh2": {
|
"@types/ssh2": {
|
||||||
"version": "0.5.46",
|
"version": "0.5.47",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.46.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.47.tgz",
|
||||||
"integrity": "sha512-1pC8FHrMPYdkLoUOwTYYifnSEPzAFZRsp3JFC/vokQ+dRrVI+hDBwz0SNmQ3pL6h39OSZlPs0uCG7wKJkftnaA==",
|
"integrity": "sha512-ZhqJg8BRV7OsCi0KVqPr27lUMMmLEeHYw1VXUNGGDlQEDq9HTsKx+wYvi8E6oNC6gRZ7PV99ZMZmMr5vztcYYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -1659,9 +1659,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/ssh2-streams": {
|
"@types/ssh2-streams": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.9.tgz",
|
||||||
"integrity": "sha512-I7gixRPUvVIyJuCEvnmhr3KvA2dC0639kKswqD4H5b4/FOcnPtNU+qWLiXdKIqqX9twUvi5j0U1mwKE5CUsrfA==",
|
"integrity": "sha512-I2J9jKqfmvXLR5GomDiCoHrEJ58hAOmFrekfFqmCFd+A6gaEStvWnPykoWUwld1PNg4G5ag1LwdA+Lz1doRJqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -1682,9 +1682,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "15.0.13",
|
"version": "15.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz",
|
||||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
"integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
@@ -1697,13 +1697,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.3.tgz",
|
||||||
"integrity": "sha512-PGqpLLzHSxq956rzNGasO3GsAPf2lY9lDUBXhS++SKonglUmJypaUtcKzRtUte8CV7nruwnDxtLUKpVxs0wQBw==",
|
"integrity": "sha512-jW8sEFu1ZeaV8xzwsfi6Vgtty2jf7/lJmQmDkDruBjYAbx5DA8JtbcMnP0rNPUG+oH5GoQBTSp+9613BzuIpYg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "4.28.2",
|
"@typescript-eslint/experimental-utils": "4.28.3",
|
||||||
"@typescript-eslint/scope-manager": "4.28.2",
|
"@typescript-eslint/scope-manager": "4.28.3",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"regexpp": "^3.1.0",
|
"regexpp": "^3.1.0",
|
||||||
@@ -1738,28 +1738,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/experimental-utils": {
|
"@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.3.tgz",
|
||||||
"integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==",
|
"integrity": "sha512-zZYl9TnrxwEPi3FbyeX0ZnE8Hp7j3OCR+ELoUfbwGHGxWnHg9+OqSmkw2MoCVpZksPCZYpQzC559Ee9pJNHTQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json-schema": "^7.0.7",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.28.2",
|
"@typescript-eslint/scope-manager": "4.28.3",
|
||||||
"@typescript-eslint/types": "4.28.2",
|
"@typescript-eslint/types": "4.28.3",
|
||||||
"@typescript-eslint/typescript-estree": "4.28.2",
|
"@typescript-eslint/typescript-estree": "4.28.3",
|
||||||
"eslint-scope": "^5.1.1",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^3.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.3.tgz",
|
||||||
"integrity": "sha512-Q0gSCN51eikAgFGY+gnd5p9bhhCUAl0ERMiDKrTzpSoMYRubdB8MJrTTR/BBii8z+iFwz8oihxd0RAdP4l8w8w==",
|
"integrity": "sha512-ZyWEn34bJexn/JNYvLQab0Mo5e+qqQNhknxmc8azgNd4XqspVYR5oHq9O11fLwdZMRcj4by15ghSlIEq+H5ltQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "4.28.2",
|
"@typescript-eslint/scope-manager": "4.28.3",
|
||||||
"@typescript-eslint/types": "4.28.2",
|
"@typescript-eslint/types": "4.28.3",
|
||||||
"@typescript-eslint/typescript-estree": "4.28.2",
|
"@typescript-eslint/typescript-estree": "4.28.3",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1781,29 +1781,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.3.tgz",
|
||||||
"integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==",
|
"integrity": "sha512-/8lMisZ5NGIzGtJB+QizQ5eX4Xd8uxedFfMBXOKuJGP0oaBBVEMbJVddQKDXyyB0bPlmt8i6bHV89KbwOelJiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.28.2",
|
"@typescript-eslint/types": "4.28.3",
|
||||||
"@typescript-eslint/visitor-keys": "4.28.2"
|
"@typescript-eslint/visitor-keys": "4.28.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.3.tgz",
|
||||||
"integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==",
|
"integrity": "sha512-kQFaEsQBQVtA9VGVyciyTbIg7S3WoKHNuOp/UF5RG40900KtGqfoiETWD/v0lzRXc+euVE9NXmfer9dLkUJrkA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.3.tgz",
|
||||||
"integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==",
|
"integrity": "sha512-YAb1JED41kJsqCQt1NcnX5ZdTA93vKFCMP4lQYG6CFxd0VzDJcKttRlMrlG+1qiWAw8+zowmHU1H0OzjWJzR2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.28.2",
|
"@typescript-eslint/types": "4.28.3",
|
||||||
"@typescript-eslint/visitor-keys": "4.28.2",
|
"@typescript-eslint/visitor-keys": "4.28.3",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.3",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
@@ -1838,12 +1838,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.28.2",
|
"version": "4.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.3.tgz",
|
||||||
"integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==",
|
"integrity": "sha512-ri1OzcLnk1HH4gORmr1dllxDzzrN6goUIz/P4MHFV0YZJDCADPR3RvYNp0PW2SetKTThar6wlbFTL00hV2Q+fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.28.2",
|
"@typescript-eslint/types": "4.28.3",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2051,15 +2051,48 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apollo-datasource-rest": {
|
"apollo-datasource-rest": {
|
||||||
"version": "0.14.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/apollo-datasource-rest/-/apollo-datasource-rest-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/apollo-datasource-rest/-/apollo-datasource-rest-3.0.0.tgz",
|
||||||
"integrity": "sha512-OXh+kbpZPx0F5fNJLYmxIa8Nt+UIC7oK1pnoWtLMDws9/TiICaG6J7SHHAKnz03ZVnZ4XE9gKtm9RTCjCYJtSw==",
|
"integrity": "sha512-pjx2uOvQSGHCE6fEeItTUniqYshL3OvD02ybwhbISg+7iQXKsyvgYKDgzY77tDjIiNSdUZsFOkNw2lmfnORBxA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"apollo-datasource": "^0.9.0",
|
"apollo-datasource": "^3.0.0",
|
||||||
"apollo-server-caching": "^0.7.0",
|
"apollo-server-caching": "^3.0.0",
|
||||||
"apollo-server-env": "^3.1.0",
|
"apollo-server-env": "^4.0.0",
|
||||||
"apollo-server-errors": "^2.5.0",
|
"apollo-server-errors": "^3.0.0",
|
||||||
"http-cache-semantics": "^4.0.0"
|
"http-cache-semantics": "^4.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"apollo-datasource": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-mX+o0AQj/C4Z5M6r4g0BLRS6Ey7zxp4EvAhhLuMFB3xUnpcLxPxaq+FBLmxxo8OfE7Ay3AUe6mJgnAYIC2eqlg==",
|
||||||
|
"requires": {
|
||||||
|
"apollo-server-caching": "^3.0.0",
|
||||||
|
"apollo-server-env": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-server-caching": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-5ziYq8qv4PBAteY4dxVgiUq4h9OyQPY9BS/xadsyoa3VNChfXu1ByVrSx6v9hUcYkZAf5eioH0eT74EG/rjSgw==",
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-server-env": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-NpdtkJRZq07XrZGjjrL0joU8N1OivUNYun00gafCxzZqNg+D0YkXIU0any6brXFh+u0qIXuvYS+eKFcjiVWCJg==",
|
||||||
|
"requires": {
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"util.promisify": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-server-errors": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-fyvyn/3EN4RO4A9GL26EWOEGbqmbeOy5vgh0MK9YErfKctWWNFvH4cuCWAy30hiqYnwR1apNKDedOKEhe+/CIg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apollo-graphql": {
|
"apollo-graphql": {
|
||||||
@@ -4247,9 +4280,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fast-glob": {
|
"fast-glob": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
|
||||||
"integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==",
|
"integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
@@ -7786,9 +7819,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"nodemon": {
|
"nodemon": {
|
||||||
"version": "2.0.9",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz",
|
||||||
"integrity": "sha512-6O4k7C8f2HQArGpaPBOqGGddjzDLQAqCYmq3tKMeNIbz7Is/hOphMHy2dcY10sSq5wl3cqyn9Iz+Ep2j51JOLg==",
|
"integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": "^3.2.2",
|
"chokidar": "^3.2.2",
|
||||||
"debug": "^3.2.6",
|
"debug": "^3.2.6",
|
||||||
@@ -9318,9 +9351,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"testcontainers": {
|
"testcontainers": {
|
||||||
"version": "7.11.1",
|
"version": "7.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-7.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-7.12.2.tgz",
|
||||||
"integrity": "sha512-lfZeys5bLkADjOaoXfQy0V0+G8sGKr8ESANz7MhSVBwC+OTTxkP3+FVwP48bW4mwRcQ4Hojwbfw10OYT80QZmQ==",
|
"integrity": "sha512-rp35IGJPPB6ldOjho/LP2OTSmxI5RIpmd0G1HMgm7oYgdAmpRvSLazIHkUMzG3g7tg5fJtTl7qO18KUv+bU3YQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/archiver": "^5.1.0",
|
"@types/archiver": "^5.1.0",
|
||||||
@@ -9504,9 +9537,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"version": "10.0.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
|
||||||
"integrity": "sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==",
|
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
"@tsconfig/node12": "^1.0.7",
|
"@tsconfig/node12": "^1.0.7",
|
||||||
|
|||||||
+7
-7
@@ -12,7 +12,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apollo-datasource": "^0.9.0",
|
"apollo-datasource": "^0.9.0",
|
||||||
"apollo-datasource-rest": "^0.14.0",
|
"apollo-datasource-rest": "^3.0.0",
|
||||||
"apollo-server": "^2.25.2",
|
"apollo-server": "^2.25.2",
|
||||||
"async-redis": "^2.0.0",
|
"async-redis": "^2.0.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
@@ -23,26 +23,26 @@
|
|||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"jwk-to-pem": "^2.0.5",
|
"jwk-to-pem": "^2.0.5",
|
||||||
"knex-stringcase": "^1.4.5",
|
"knex-stringcase": "^1.4.5",
|
||||||
"nodemon": "^2.0.9",
|
"nodemon": "^2.0.12",
|
||||||
"pg": "^8.6.0",
|
"pg": "^8.6.0",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"stringcase": "^4.3.1",
|
"stringcase": "^4.3.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.1.0",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"util": "^0.12.4",
|
"util": "^0.12.4",
|
||||||
"validator": "^13.6.0"
|
"validator": "^13.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.24",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.3",
|
||||||
"eslint": "^7.30.0",
|
"eslint": "^7.30.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"testcontainers": "^7.11.1",
|
"testcontainers": "^7.12.2",
|
||||||
"ts-jest": "^27.0.3"
|
"ts-jest": "^27.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+151
-48
@@ -1,32 +1,29 @@
|
|||||||
{
|
{
|
||||||
products(limit: 10000, browsable: true, visible: true) {
|
products(limit: 10000, browsable: true, visible: true) {
|
||||||
id
|
id
|
||||||
|
keywords {
|
||||||
|
id
|
||||||
|
value
|
||||||
|
type
|
||||||
|
}
|
||||||
|
type
|
||||||
|
fields_json
|
||||||
inserted
|
inserted
|
||||||
|
path
|
||||||
updated
|
updated
|
||||||
visible
|
visible
|
||||||
browsable
|
browsable
|
||||||
printProducts {
|
categories {
|
||||||
group
|
|
||||||
id
|
id
|
||||||
updated
|
name
|
||||||
|
path
|
||||||
|
pathNumeric
|
||||||
|
isLeaf
|
||||||
|
depth
|
||||||
|
childCount
|
||||||
|
lft
|
||||||
|
rgt
|
||||||
}
|
}
|
||||||
blacklisting {
|
|
||||||
group
|
|
||||||
id
|
|
||||||
marketId
|
|
||||||
updated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
product(id: 42280) {
|
|
||||||
id
|
|
||||||
inserted
|
|
||||||
updated
|
|
||||||
visible
|
|
||||||
browsable
|
|
||||||
designerId
|
|
||||||
designer {
|
designer {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@@ -46,37 +43,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Orders
|
||||||
{
|
{
|
||||||
orders(
|
orders(
|
||||||
filter: { limit: 1, dates: { from: "2021-03-01", to: "2021-04-30" } }
|
filter: {
|
||||||
) {
|
limit: 100
|
||||||
id
|
offset: 0
|
||||||
market {
|
dates: { from: "2021-03-01", to: "2021-04-30" }
|
||||||
id
|
|
||||||
name
|
|
||||||
vat
|
|
||||||
currency
|
|
||||||
priceAdjustment
|
|
||||||
}
|
}
|
||||||
rows {
|
) {
|
||||||
|
total
|
||||||
|
offset
|
||||||
|
limit
|
||||||
|
items {
|
||||||
id
|
id
|
||||||
order {
|
paid
|
||||||
|
delivered
|
||||||
|
confirmed
|
||||||
|
billingInformation {
|
||||||
|
email
|
||||||
|
phone
|
||||||
|
address {
|
||||||
|
firstname
|
||||||
|
lastname
|
||||||
|
recipientName
|
||||||
|
companyname
|
||||||
|
address1
|
||||||
|
address2
|
||||||
|
zipcode
|
||||||
|
stateCountyOrRegion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deliveryInformation {
|
||||||
|
email
|
||||||
|
phone
|
||||||
|
address {
|
||||||
|
firstname
|
||||||
|
lastname
|
||||||
|
recipientName
|
||||||
|
companyname
|
||||||
|
address1
|
||||||
|
address2
|
||||||
|
zipcode
|
||||||
|
stateCountyOrRegion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
market {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
|
vat
|
||||||
|
currency
|
||||||
|
priceAdjustment
|
||||||
|
}
|
||||||
|
rows {
|
||||||
|
id
|
||||||
|
order {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
inserted
|
||||||
|
orderId
|
||||||
|
productId
|
||||||
|
productGroup
|
||||||
|
artNo
|
||||||
|
path
|
||||||
|
name
|
||||||
|
price
|
||||||
|
designerId
|
||||||
|
commissionAmount
|
||||||
|
status
|
||||||
|
pwintyImageId
|
||||||
|
frameColor
|
||||||
|
pwintySku
|
||||||
}
|
}
|
||||||
inserted
|
|
||||||
orderId
|
|
||||||
productId
|
|
||||||
productGroup
|
|
||||||
artNo
|
|
||||||
path
|
|
||||||
name
|
|
||||||
price
|
|
||||||
designerId
|
|
||||||
commissionAmount
|
|
||||||
status
|
|
||||||
pwintyImageId
|
|
||||||
frameColor
|
|
||||||
pwintySku
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,14 +192,51 @@
|
|||||||
|
|
||||||
# JULY 2021
|
# JULY 2021
|
||||||
{
|
{
|
||||||
product(id: 73803) {
|
product(id: 42166) {
|
||||||
id
|
id
|
||||||
|
stockid
|
||||||
|
fields_json
|
||||||
|
orientation
|
||||||
|
related {
|
||||||
|
id
|
||||||
|
fields {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref1
|
||||||
|
ref2
|
||||||
|
ref3
|
||||||
|
wallpaperTypes
|
||||||
keywords {
|
keywords {
|
||||||
id
|
id
|
||||||
value
|
value
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
fields_json
|
type
|
||||||
|
fields {
|
||||||
|
artNo
|
||||||
|
name
|
||||||
|
height
|
||||||
|
width
|
||||||
|
photowallResolution
|
||||||
|
canvasResolution
|
||||||
|
wallpaperResolution
|
||||||
|
copyright
|
||||||
|
proportionsWarning
|
||||||
|
marginWidthMax
|
||||||
|
marginWidthMin
|
||||||
|
marginHeightMax
|
||||||
|
marginHeightMin
|
||||||
|
focusXpoint
|
||||||
|
focusYpoint
|
||||||
|
focusXpoint2
|
||||||
|
focusYpoint2
|
||||||
|
batch
|
||||||
|
imageResolution
|
||||||
|
printFileWidth
|
||||||
|
printFileHeight
|
||||||
|
printFileDpi
|
||||||
|
}
|
||||||
inserted
|
inserted
|
||||||
path
|
path
|
||||||
updated
|
updated
|
||||||
@@ -171,6 +246,7 @@
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
path
|
path
|
||||||
|
pathNumeric
|
||||||
isLeaf
|
isLeaf
|
||||||
depth
|
depth
|
||||||
childCount
|
childCount
|
||||||
@@ -186,6 +262,14 @@
|
|||||||
group
|
group
|
||||||
id
|
id
|
||||||
updated
|
updated
|
||||||
|
inserted
|
||||||
|
interiors {
|
||||||
|
id
|
||||||
|
roomName
|
||||||
|
uri
|
||||||
|
position
|
||||||
|
roomType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
blacklisting {
|
blacklisting {
|
||||||
group
|
group
|
||||||
@@ -195,7 +279,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Categories
|
# Categories
|
||||||
{
|
{
|
||||||
category(id: 1653) {
|
category(id: 1653) {
|
||||||
@@ -236,3 +319,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
markets {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
vat
|
||||||
|
currency
|
||||||
|
priceAdjustment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
market(name: "GB") {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
vat
|
||||||
|
currency
|
||||||
|
priceAdjustment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ describe('GraphQL Apollo tests', () => {
|
|||||||
it('should be possible to fetch a designers with limit', async () => {
|
it('should be possible to fetch a designers with limit', async () => {
|
||||||
// CONTROL
|
// CONTROL
|
||||||
const db_response = await allContainers.db.raw(
|
const db_response = await allContainers.db.raw(
|
||||||
`SELECT * FROM designers LIMIT 3;`,
|
`SELECT * FROM designers ORDER BY name LIMIT 3;`,
|
||||||
);
|
);
|
||||||
const controlData = db_response.rows;
|
const controlData = db_response.rows;
|
||||||
// -------------------
|
// -------------------
|
||||||
@@ -186,10 +186,6 @@ describe('GraphQL Apollo tests', () => {
|
|||||||
|
|
||||||
expect(designers.length).toBe(3);
|
expect(designers.length).toBe(3);
|
||||||
|
|
||||||
expect(designers[0].id).toBe('1');
|
|
||||||
expect(designers[1].id).toBe('2');
|
|
||||||
expect(designers[2].id).toBe('3');
|
|
||||||
|
|
||||||
expect(designers[0].name).toBe(controlData[0].name);
|
expect(designers[0].name).toBe(controlData[0].name);
|
||||||
expect(designers[1].name).toBe(controlData[1].name);
|
expect(designers[1].name).toBe(controlData[1].name);
|
||||||
expect(designers[2].name).toBe(controlData[2].name);
|
expect(designers[2].name).toBe(controlData[2].name);
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { RESTDataSource } from 'apollo-datasource-rest';
|
|
||||||
|
|
||||||
export class Api1DataSource extends RESTDataSource {
|
|
||||||
authHeader: string;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.baseURL = 'http://docker.for.mac.localhost:8082';
|
|
||||||
this.authHeader = 'Basic ZGV2OmRldg==';
|
|
||||||
}
|
|
||||||
|
|
||||||
willSendRequest(request) {
|
|
||||||
request.headers.set('Authorization', this.authHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProduct(id) {
|
|
||||||
return await this.get(`/products/${id}?` + new URLSearchParams({}), null, {
|
|
||||||
cacheOptions: { ttl: 5 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { RESTDataSource } from 'apollo-datasource-rest';
|
|
||||||
|
|
||||||
/*
|
|
||||||
https://wE4D45790.api.esales.apptus.cloud/api/v2/panels/category
|
|
||||||
?esales.market=SE&
|
|
||||||
esales.customerKey=471c09ac-a775-4dd2-8bfc-041bfbb6d7b7&
|
|
||||||
esales.sessionKey=b106299c-eac0-4f6f-96d2-93e57c255784&
|
|
||||||
window_first=1&
|
|
||||||
window_last=10&
|
|
||||||
selected_category=categories_SE:'root/animals'&
|
|
||||||
root_category=categories_SE:'root'
|
|
||||||
&max_facets=50
|
|
||||||
&filter=blacklisted:'0' AND visible_in_listing:'1' AND visible_in_shop:'1' AND (group:'poster' OR group:'framed-print')
|
|
||||||
blacklisted:'0'%20AND%20visible_in_listing:'1'%20AND%20visible_in_shop:'1'%20AND%20(group:'poster'%20OR%20group:'framed-print')
|
|
||||||
|
|
||||||
*/
|
|
||||||
export class Api2DataSource extends RESTDataSource {
|
|
||||||
authHeader: string;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.baseURL = 'https://api-staging.photowall.com/';
|
|
||||||
this.authHeader =
|
|
||||||
'Basic ZGExNzU5ZWEtOTBiNC00OTNjLWJjYWItNjAwNDg1NjA5YjkyOg==';
|
|
||||||
}
|
|
||||||
|
|
||||||
willSendRequest(request) {
|
|
||||||
request.headers.set('Authorization', this.authHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPrice(market, width, height, sku) {
|
|
||||||
return await this.get(
|
|
||||||
'prices/wallpaper?' +
|
|
||||||
new URLSearchParams({
|
|
||||||
width: '200',
|
|
||||||
height: '200',
|
|
||||||
market: '1',
|
|
||||||
product: '58941',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ WHERE product_category.product_id = ?
|
|||||||
data.rows.map((row) => {
|
data.rows.map((row) => {
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
path: row.path.replace(/^root/, ''),
|
pathNumeric: row.path_numeric,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -29,33 +29,16 @@ WHERE product_category.product_id = ?
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCategories(): Promise<Array<Category>> {
|
async getCategories(): Promise<Array<Category>> {
|
||||||
return await this.knex
|
return this.knex.select('*').from('v_categorytree').cache(MINUTE);
|
||||||
.select('*')
|
|
||||||
.from('v_categorytree')
|
|
||||||
.cache(MINUTE)
|
|
||||||
.then((rows) => {
|
|
||||||
return rows.map((row) => {
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
path: row.path.replace(/^root/, ''),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCategoryById(id: number): Promise<Category> {
|
async getCategoryById(id: number): Promise<Category> {
|
||||||
return await this.knex
|
return this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
.from('v_categorytree')
|
.from('v_categorytree')
|
||||||
.where('id', id)
|
.where('id', id)
|
||||||
.first()
|
.first()
|
||||||
.cache(MINUTE)
|
.cache(MINUTE);
|
||||||
.then((row) => {
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
path: row.path.replace(/^root/, ''),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,10 +48,18 @@ WHERE product_category.product_id = ?
|
|||||||
SELECT category_keyword.category_id, keywords.value
|
SELECT category_keyword.category_id, keywords.value
|
||||||
FROM category_keyword
|
FROM category_keyword
|
||||||
JOIN keywords ON category_keyword.keyword_id = keywords.id;
|
JOIN keywords ON category_keyword.keyword_id = keywords.id;
|
||||||
|
|
||||||
|
|
||||||
FROM categorydata cd
|
FROM categorydata cd
|
||||||
JOIN categorydatakeys cdk ON cd.datakey_id = cdk.id
|
JOIN categorydatakeys cdk ON cd.datakey_id = cdk.id
|
||||||
*
|
*
|
||||||
|
* category texts
|
||||||
|
SELECT name,
|
||||||
|
text
|
||||||
|
FROM categorydata cd
|
||||||
|
JOIN categorydatakeys cdk ON cd.datakey_id = cdk.id
|
||||||
|
WHERE category_id = ?
|
||||||
|
AND locale_id = ?;
|
||||||
|
|
||||||
|
|
||||||
|
* category teaser images???
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class DesignerAPI extends BaseSQLDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getDesignerById(id: number): Promise<Designer> {
|
async getDesignerById(id: number): Promise<Designer> {
|
||||||
return await this.knex
|
return this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
.from('designers')
|
.from('designers')
|
||||||
.where('designerid', id)
|
.where('designerid', id)
|
||||||
@@ -24,11 +24,12 @@ export class DesignerAPI extends BaseSQLDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getDesigners(limit: Maybe<number>): Promise<Array<Designer>> {
|
async getDesigners(limit: Maybe<number>): Promise<Array<Designer>> {
|
||||||
limit = limit ?? 100;
|
limit = limit ?? 5000;
|
||||||
return await this.knex
|
return this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
.from('designers')
|
.from('designers')
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
.orderBy('name')
|
||||||
.cache(MINUTE)
|
.cache(MINUTE)
|
||||||
.then((rows) => {
|
.then((rows) => {
|
||||||
return rows.map((row) => {
|
return rows.map((row) => {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { RESTDataSource } from 'apollo-datasource-rest';
|
||||||
|
import { InteriorType } from '../types/interior-types';
|
||||||
|
import { Orientation } from '../types/product-types';
|
||||||
|
|
||||||
|
export class ImageServerApi extends RESTDataSource {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.baseURL =
|
||||||
|
process.env.ENVIRONMENT_NAME === 'production'
|
||||||
|
? 'http://images.photowall.com'
|
||||||
|
: 'http://images-dev.photowall.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
willSendRequest(request) {
|
||||||
|
request.headers.set('X-Photowall', 'pele');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInteriorsForProduct(
|
||||||
|
productId: number,
|
||||||
|
type: InteriorType,
|
||||||
|
orientation: Orientation,
|
||||||
|
): Promise<Array<{ uri: string }>> {
|
||||||
|
const it = type.toString().toLowerCase();
|
||||||
|
const or = orientation.toString().toLowerCase();
|
||||||
|
return this.get(`/rooms/${productId}/${or}/${it}/`, null, {
|
||||||
|
cacheOptions: { ttl: 5 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import {
|
||||||
|
Interior,
|
||||||
|
InteriorsFilter,
|
||||||
|
InteriorType,
|
||||||
|
} from '../types/interior-types';
|
||||||
|
import { Orientation } from '../types/product-types';
|
||||||
|
import { Maybe } from '../types/types';
|
||||||
|
import { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||||
|
import { ImageServerApi } from './imageserver-api';
|
||||||
|
import { convertToPossibleType } from './utils';
|
||||||
|
|
||||||
|
const MINUTE = 60;
|
||||||
|
|
||||||
|
export class InteriorAPI extends BaseSQLDataSource {
|
||||||
|
imageServerApi: ImageServerApi;
|
||||||
|
constructor(config, imageServerApi: ImageServerApi) {
|
||||||
|
super(config);
|
||||||
|
this.imageServerApi = imageServerApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInteriorType(roomTypePart: string): string {
|
||||||
|
switch (roomTypePart) {
|
||||||
|
case 'wallpaper':
|
||||||
|
return InteriorType[InteriorType.WALLPAPER];
|
||||||
|
case 'painting':
|
||||||
|
return InteriorType[InteriorType.PAINTING];
|
||||||
|
case 'poster':
|
||||||
|
return InteriorType[InteriorType.POSTER];
|
||||||
|
case 'framed-print':
|
||||||
|
return InteriorType[InteriorType.FRAMED_PRINT];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown room type: ${roomTypePart}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInteriorOrientation(roomOrientationPart: string): string {
|
||||||
|
switch (roomOrientationPart) {
|
||||||
|
case 'landscape':
|
||||||
|
return Orientation[Orientation.LANDSCAPE];
|
||||||
|
case 'standing':
|
||||||
|
return Orientation[Orientation.PORTRAIT];
|
||||||
|
case 'square':
|
||||||
|
return Orientation[Orientation.SQUARE];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown room orientation: ${roomOrientationPart}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInteriorRoomNumber(roomNumberPart: string): number {
|
||||||
|
return convertToPossibleType(roomNumberPart.slice(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
decorateRow(row: any): Interior {
|
||||||
|
const parts = row.roomName.split('_');
|
||||||
|
row.roomNumber = this.getInteriorRoomNumber(parts[0]);
|
||||||
|
row.type = this.getInteriorType(parts[1]);
|
||||||
|
row.orientation = this.getInteriorOrientation(parts[2]);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInteriors(filter: Maybe<InteriorsFilter>): Promise<Array<Interior>> {
|
||||||
|
const sql = /* sql */ `
|
||||||
|
SELECT rooms.*, room_types.name roomType FROM rooms
|
||||||
|
LEFT JOIN room_types ON rooms.room_type_id = room_types.id
|
||||||
|
`;
|
||||||
|
|
||||||
|
const allInteriors = await this.cachedRaw(sql)
|
||||||
|
.cache(MINUTE * 60)
|
||||||
|
.then((data) =>
|
||||||
|
data.rows.map((row) => {
|
||||||
|
const res = {
|
||||||
|
...row,
|
||||||
|
roomName: row.name,
|
||||||
|
roomType: row.roomtype,
|
||||||
|
};
|
||||||
|
this.decorateRow(res);
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Based on filter get valid interiors and filter out any else from all interiors
|
||||||
|
if (filter) {
|
||||||
|
const validUris = await this.imageServerApi.getInteriorsForProduct(
|
||||||
|
filter.productId,
|
||||||
|
filter.type,
|
||||||
|
filter.orientation,
|
||||||
|
);
|
||||||
|
return allInteriors.filter((i: Interior) => {
|
||||||
|
const it = i.type.toString().toLowerCase();
|
||||||
|
const or = i.orientation.toString().toLocaleLowerCase();
|
||||||
|
const uriMatch = `${or}/${it}/room${i.roomNumber}.`;
|
||||||
|
const foundUri = validUris.find(({ uri }) => uri.includes(uriMatch));
|
||||||
|
if (foundUri) {
|
||||||
|
i.uri = foundUri.uri;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// No filter so return all
|
||||||
|
return allInteriors;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrintProductInteriors(printId: number): Promise<Array<Interior>> {
|
||||||
|
return this.knex
|
||||||
|
.select('*')
|
||||||
|
.from('v_interior_image_urls')
|
||||||
|
.orderBy('position')
|
||||||
|
.where('print_id', printId)
|
||||||
|
.cache(MINUTE)
|
||||||
|
.then((rows) => {
|
||||||
|
return rows.map((row) => {
|
||||||
|
const res = {
|
||||||
|
...row,
|
||||||
|
id: row.roomId,
|
||||||
|
};
|
||||||
|
this.decorateRow(res);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,10 +28,9 @@ WHERE product_keyword.product_id = ?
|
|||||||
ORDER BY keywords.value
|
ORDER BY keywords.value
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const res = await this.knex
|
return this.knex
|
||||||
.raw(query, productId)
|
.raw(query, productId)
|
||||||
.then((data) => data.rows.map((row) => this.getType(row)));
|
.then((data) => data.rows.map((row) => this.getType(row)));
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKeywords(): Promise<Array<Keyword>> {
|
async getKeywords(): Promise<Array<Keyword>> {
|
||||||
@@ -40,14 +39,13 @@ SELECT keywords.*, keyword_type.type_id FROM keywords
|
|||||||
LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id
|
LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id
|
||||||
ORDER BY keywords.value
|
ORDER BY keywords.value
|
||||||
`;
|
`;
|
||||||
const res = await this.knex
|
return this.knex
|
||||||
.raw(query)
|
.raw(query)
|
||||||
.then((data) => data.rows.map((row) => this.getType(row)));
|
.then((data) => data.rows.map((row) => this.getType(row)));
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKeywordById(id: number): Promise<Keyword> {
|
async getKeywordById(id: number): Promise<Keyword> {
|
||||||
const res = await this.knex
|
return this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
.from('keywords')
|
.from('keywords')
|
||||||
.leftJoin('keyword_type', 'keyword_type.keyword_id', 'keywords.id')
|
.leftJoin('keyword_type', 'keyword_type.keyword_id', 'keywords.id')
|
||||||
@@ -55,7 +53,6 @@ ORDER BY keywords.value
|
|||||||
.first()
|
.first()
|
||||||
.cache(MINUTE)
|
.cache(MINUTE)
|
||||||
.then((row) => this.getType(row));
|
.then((row) => this.getType(row));
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { SQLDataSource } from 'datasource-sql';
|
||||||
|
import { Market } from '../types/market-types';
|
||||||
|
|
||||||
|
const MINUTE = 60;
|
||||||
|
|
||||||
|
export class MarketAPI extends SQLDataSource {
|
||||||
|
constructor(config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMarketById(id: number): Promise<Market> {
|
||||||
|
return this.knex
|
||||||
|
.select('*')
|
||||||
|
.from('markets')
|
||||||
|
.where('id', id)
|
||||||
|
.first()
|
||||||
|
.cache(MINUTE * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMarketByName(name: string): Promise<Market> {
|
||||||
|
return this.knex
|
||||||
|
.select('*')
|
||||||
|
.from('markets')
|
||||||
|
.where('name', name)
|
||||||
|
.first()
|
||||||
|
.cache(MINUTE * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMarkets(): Promise<Array<Market>> {
|
||||||
|
return this.knex
|
||||||
|
.select('*')
|
||||||
|
.from('markets')
|
||||||
|
.cache(MINUTE * 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import { camelcase } from 'stringcase';
|
|||||||
import {
|
import {
|
||||||
Address,
|
Address,
|
||||||
ContactInformation,
|
ContactInformation,
|
||||||
Market,
|
|
||||||
Order,
|
Order,
|
||||||
OrderRow,
|
OrderRow,
|
||||||
} from '../types/order-types';
|
} from '../types/order-types';
|
||||||
@@ -17,24 +16,6 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMarketById(id: number): Promise<Market> {
|
|
||||||
return await this.knex
|
|
||||||
.select('*')
|
|
||||||
.from('markets')
|
|
||||||
.where('id', id)
|
|
||||||
.first()
|
|
||||||
.cache(MINUTE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMarketByName(name: string): Promise<Market> {
|
|
||||||
return await this.knex
|
|
||||||
.select('*')
|
|
||||||
.from('markets')
|
|
||||||
.where('name', name)
|
|
||||||
.first()
|
|
||||||
.cache(MINUTE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAddress(addressId: number): Promise<Maybe<Address>> {
|
async getAddress(addressId: number): Promise<Maybe<Address>> {
|
||||||
const row = await this.knex
|
const row = await this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -80,20 +61,13 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrdersTotal(input: Maybe<GeneralInput>): Promise<Number> {
|
async getOrdersTotal(input: Maybe<GeneralInput>): Promise<number> {
|
||||||
let query = this.knex.table('orders');
|
const res = await this.getOrdersQuery(input)
|
||||||
|
.clone()
|
||||||
if (input?.dates?.from) {
|
.count()
|
||||||
query = query.where('inserted', '>=', input.dates.from);
|
.first()
|
||||||
}
|
.cache(MINUTE * 60);
|
||||||
|
return convertToPossibleType(res['count']); // Optimize later to return Promise
|
||||||
if (input?.dates?.to) {
|
|
||||||
query = query.where('inserted', '<=', input.dates.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await query.clone().count();
|
|
||||||
console.log(res);
|
|
||||||
return 57777;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> {
|
async getOrders(input: Maybe<GeneralInput>): Promise<Array<Order>> {
|
||||||
@@ -103,7 +77,7 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
query = query.limit(input?.limit);
|
query = query.limit(input?.limit);
|
||||||
}
|
}
|
||||||
query = query.orderBy('inserted', 'ASC');
|
query = query.orderBy('inserted', 'ASC');
|
||||||
return await query
|
return query
|
||||||
.cache(MINUTE)
|
.cache(MINUTE)
|
||||||
.then((rows) => rows.map((row) => this.createOrderFromRow(row)));
|
.then((rows) => rows.map((row) => this.createOrderFromRow(row)));
|
||||||
}
|
}
|
||||||
@@ -122,7 +96,7 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getOrderById(id: number): Promise<Order> {
|
async getOrderById(id: number): Promise<Order> {
|
||||||
return await this.knex
|
return this.knex
|
||||||
.select('*')
|
.select('*')
|
||||||
.from('orders')
|
.from('orders')
|
||||||
.where('id', id)
|
.where('id', id)
|
||||||
@@ -200,7 +174,7 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
.raw('SELECT * FROM "order-rows" WHERE orderid = ?', orderId)
|
.raw('SELECT * FROM "order-rows" WHERE orderid = ?', orderId)
|
||||||
.then((data) => data.rows);
|
.then((data) => data.rows);
|
||||||
|
|
||||||
return await this.getOrderRows(rows);
|
return this.getOrderRows(rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrderRowsByDesignerId(
|
async getOrderRowsByDesignerId(
|
||||||
@@ -228,6 +202,6 @@ export class OrderAPI extends BaseSQLDataSource {
|
|||||||
const rows = await this.knex
|
const rows = await this.knex
|
||||||
.raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)])
|
.raw(query, [designerId, this.getSQLDate(from), this.getSQLDate(to)])
|
||||||
.then((data) => data.rows);
|
.then((data) => data.rows);
|
||||||
return await this.getOrderRows(rows);
|
return this.getOrderRows(rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SQLDataSource } from 'datasource-sql';
|
import { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||||
|
import { camelcase } from 'stringcase';
|
||||||
import * as sql from './sql';
|
import * as sql from './sql';
|
||||||
import {
|
import {
|
||||||
Product,
|
Product,
|
||||||
@@ -6,12 +7,16 @@ import {
|
|||||||
PrintProduct,
|
PrintProduct,
|
||||||
ProductType,
|
ProductType,
|
||||||
ProductBlacklist,
|
ProductBlacklist,
|
||||||
|
ProductWallpaperType,
|
||||||
|
ProductFields,
|
||||||
|
Orientation,
|
||||||
} from '../types/product-types';
|
} from '../types/product-types';
|
||||||
import { Maybe } from '../types/types';
|
import { Maybe } from '../types/types';
|
||||||
|
import { convertToPossibleType } from './utils';
|
||||||
|
|
||||||
const MINUTE = 60;
|
const MINUTE = 60;
|
||||||
|
|
||||||
export class ProductAPI extends SQLDataSource {
|
export class ProductAPI extends BaseSQLDataSource {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
@@ -21,6 +26,25 @@ export class ProductAPI extends SQLDataSource {
|
|||||||
// SELECT materialid, material, (price / 100::float) as price FROM "product-materials"
|
// SELECT materialid, material, (price / 100::float) as price FROM "product-materials"
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
getWallpaperTypes(row: any): Array<ProductWallpaperType> {
|
||||||
|
return row.wallpapertypes?.map((t: number) => ProductWallpaperType[t]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProductTypes(row: any): Array<ProductType> {
|
||||||
|
if (!row.types) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return row.types.map((type: any) => {
|
||||||
|
if (type === 'typeillustration') {
|
||||||
|
return ProductType[ProductType.ILLUSTRATION];
|
||||||
|
}
|
||||||
|
if (type === 'typephotography') {
|
||||||
|
return ProductType[ProductType.PHOTO];
|
||||||
|
}
|
||||||
|
return ProductType[ProductType.UNKNOWN];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getBlacklisting(row: any): Array<ProductBlacklist> {
|
getBlacklisting(row: any): Array<ProductBlacklist> {
|
||||||
if (!row.blacklisting) {
|
if (!row.blacklisting) {
|
||||||
return [];
|
return [];
|
||||||
@@ -42,26 +66,36 @@ export class ProductAPI extends SQLDataSource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getProductTypes(row: any): Array<ProductType> {
|
getProductFields(row: any): ProductFields {
|
||||||
if (!row.types) {
|
const fields = Object.keys(row.fields).reduce((mem, key) => {
|
||||||
return [];
|
mem[camelcase(key)] = convertToPossibleType(row.fields[key]);
|
||||||
|
return mem;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return <ProductFields>fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrientation(fields: ProductFields): string {
|
||||||
|
if (!fields.width || !fields.height) {
|
||||||
|
return Orientation[Orientation.UNKNOWN];
|
||||||
}
|
}
|
||||||
return row.types.map((type: any) => {
|
if (fields.width > fields.height) {
|
||||||
if (type === 'typeillustration') {
|
return Orientation[Orientation.LANDSCAPE];
|
||||||
return ProductType[ProductType.ILLUSTRATION];
|
}
|
||||||
}
|
if (fields.width < fields.height) {
|
||||||
if (type === 'typephotography') {
|
return Orientation[Orientation.PORTRAIT];
|
||||||
return ProductType[ProductType.PHOTO];
|
}
|
||||||
}
|
return Orientation[Orientation.SQUARE];
|
||||||
return ProductType[ProductType.UNKNOWN];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createProductFromRow(row: any) {
|
createProductFromRow(row: any) {
|
||||||
row.blacklisting = this.getBlacklisting(row);
|
row.blacklisting = this.getBlacklisting(row);
|
||||||
row.printProducts = this.getPrintProducts(row);
|
row.printProducts = this.getPrintProducts(row);
|
||||||
row.type = this.getProductTypes(row);
|
row.type = this.getProductTypes(row);
|
||||||
|
row.wallpaperTypes = this.getWallpaperTypes(row);
|
||||||
|
row.fields = this.getProductFields(row);
|
||||||
row.designerId = row.designerid;
|
row.designerId = row.designerid;
|
||||||
|
row.orientation = this.getOrientation(row.fields);
|
||||||
row.fields_json = row.fields;
|
row.fields_json = row.fields;
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
@@ -74,7 +108,7 @@ export class ProductAPI extends SQLDataSource {
|
|||||||
limit = limit ?? 100;
|
limit = limit ?? 100;
|
||||||
const query = sql.products(limit, visible, browsable);
|
const query = sql.products(limit, visible, browsable);
|
||||||
|
|
||||||
return await this.knex
|
return this.knex
|
||||||
.raw(query)
|
.raw(query)
|
||||||
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
||||||
}
|
}
|
||||||
@@ -94,7 +128,7 @@ export class ProductAPI extends SQLDataSource {
|
|||||||
async getCategoryProducts(categoryId: number): Promise<Array<Product>> {
|
async getCategoryProducts(categoryId: number): Promise<Array<Product>> {
|
||||||
const query = sql.categoryProducts(categoryId);
|
const query = sql.categoryProducts(categoryId);
|
||||||
|
|
||||||
return await this.knex
|
return this.knex
|
||||||
.raw(query)
|
.raw(query)
|
||||||
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
||||||
}
|
}
|
||||||
@@ -102,8 +136,18 @@ export class ProductAPI extends SQLDataSource {
|
|||||||
async getKeywordProducts(keywordId: number): Promise<Array<Product>> {
|
async getKeywordProducts(keywordId: number): Promise<Array<Product>> {
|
||||||
const query = sql.keywordProducts(keywordId);
|
const query = sql.keywordProducts(keywordId);
|
||||||
|
|
||||||
return await this.knex
|
return this.knex
|
||||||
.raw(query)
|
.raw(query)
|
||||||
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
.then((data) => data.rows.map((row) => this.createProductFromRow(row)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRelatedProducts(id: number): Promise<Array<Product>> {
|
||||||
|
const ids = await this.cachedRaw(
|
||||||
|
`SELECT productid2 FROM "product-products_products" WHERE productid1 = ${id}`,
|
||||||
|
)
|
||||||
|
.cache(MINUTE * 60)
|
||||||
|
.then((data) => data.rows.map((r) => r.productid2));
|
||||||
|
|
||||||
|
return ids.map(async (id) => this.getProduct(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,57 +4,75 @@ import { Maybe } from '../../types/types';
|
|||||||
|
|
||||||
const baseQuery = /* sql */ `
|
const baseQuery = /* sql */ `
|
||||||
|
|
||||||
SELECT products.productid as id,
|
SELECT products.productid AS id,
|
||||||
|
stock.stockid,
|
||||||
products.path,
|
products.path,
|
||||||
products.visible,
|
products.visible,
|
||||||
products.browsable,
|
products.browsable,
|
||||||
products.designerid,
|
products.designerid,
|
||||||
products.inserted,
|
products.inserted,
|
||||||
products.updated,
|
products.updated,
|
||||||
|
products.ref1,
|
||||||
|
products.ref2,
|
||||||
|
products.ref3,
|
||||||
(
|
(
|
||||||
SELECT json_agg(
|
SELECT json_agg(
|
||||||
json_build_object(
|
json_build_object(
|
||||||
'printId',
|
'printId',
|
||||||
"product-printproducts".printid,
|
"product-printproducts".printid,
|
||||||
'productId',
|
'productId',
|
||||||
"product-printproducts".productid,
|
"product-printproducts".productid,
|
||||||
'groupId',
|
'groupId',
|
||||||
"product-printproducts".groupid,
|
"product-printproducts".groupid,
|
||||||
'inserted',
|
'inserted',
|
||||||
"product-printproducts".inserted,
|
"product-printproducts".inserted,
|
||||||
'updated',
|
'updated',
|
||||||
"product-printproducts".updated
|
"product-printproducts".updated
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) FROM "product-printproducts" WHERE "product-printproducts".productid = products.productid
|
FROM "product-printproducts"
|
||||||
|
WHERE "product-printproducts".productid = products.productid
|
||||||
) AS printproducts,
|
) AS printproducts,
|
||||||
( SELECT json_agg(
|
(
|
||||||
json_build_object(
|
SELECT json_agg(
|
||||||
'id',
|
json_build_object(
|
||||||
product_blacklist.id,
|
'id',
|
||||||
'productId',
|
product_blacklist.id,
|
||||||
product_blacklist.product_id,
|
'productId',
|
||||||
'marketId',
|
product_blacklist.product_id,
|
||||||
product_blacklist.market_id,
|
'marketId',
|
||||||
'groupId',
|
product_blacklist.market_id,
|
||||||
product_blacklist.group_id,
|
'groupId',
|
||||||
'inserted',
|
product_blacklist.group_id,
|
||||||
product_blacklist.created_at,
|
'inserted',
|
||||||
'updated',
|
product_blacklist.created_at,
|
||||||
product_blacklist.updated_at
|
'updated',
|
||||||
|
product_blacklist.updated_at
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) FROM product_blacklist WHERE product_blacklist.product_id = products.productid
|
FROM product_blacklist
|
||||||
|
WHERE product_blacklist.product_id = products.productid
|
||||||
) AS blacklisting,
|
) AS blacklisting,
|
||||||
( SELECT json_object_agg(fields.field, pf.value)
|
(
|
||||||
|
SELECT json_object_agg(fields.field, pf.value)
|
||||||
FROM "product-products_fields" pf
|
FROM "product-products_fields" pf
|
||||||
JOIN "product-fields" fields ON fields.fieldid = pf.fieldid
|
JOIN "product-fields" fields ON fields.fieldid = pf.fieldid
|
||||||
WHERE pf.productid = products.productid
|
WHERE pf.productid = products.productid
|
||||||
) AS fields,
|
) AS fields,
|
||||||
( SELECT json_agg(keywords.value)
|
(
|
||||||
|
SELECT json_agg(keywords.value)
|
||||||
FROM product_keyword pk
|
FROM product_keyword pk
|
||||||
JOIN keywords ON keywords.id = pk.keyword_id
|
JOIN keywords ON keywords.id = pk.keyword_id
|
||||||
WHERE keywords.id IN (1103,1104)AND pk.product_id = products.productid
|
WHERE keywords.id IN (1103, 1104)
|
||||||
) AS types
|
AND pk.product_id = products.productid
|
||||||
FROM "product-products" products
|
) AS types,
|
||||||
|
(
|
||||||
|
SELECT json_agg(wt.wallpapertype_id)
|
||||||
|
FROM product_wallpapertypes wt
|
||||||
|
WHERE wt.product_id = products.productid
|
||||||
|
) AS wallpapertypes
|
||||||
|
FROM "product-products" products
|
||||||
|
LEFT JOIN "product-stockproducts" stock ON stock.productid = products.productid
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function products(
|
export function products(
|
||||||
|
|||||||
+14
-12
@@ -9,33 +9,34 @@ import resolvers from './resolvers';
|
|||||||
import { OrderAPI } from './datasources/order-api';
|
import { OrderAPI } from './datasources/order-api';
|
||||||
import { ProductAPI } from './datasources/product-api';
|
import { ProductAPI } from './datasources/product-api';
|
||||||
import { DesignerAPI } from './datasources/designer-api';
|
import { DesignerAPI } from './datasources/designer-api';
|
||||||
import { Api2DataSource } from './datasources/api2-datasource';
|
|
||||||
import { Api1DataSource } from './datasources/api1-datasource';
|
|
||||||
|
|
||||||
import { verifyToken } from './cognito/cognito-client';
|
|
||||||
import { CategoryAPI } from './datasources/category-api';
|
import { CategoryAPI } from './datasources/category-api';
|
||||||
import { KeywordAPI } from './datasources/keyword-api';
|
import { KeywordAPI } from './datasources/keyword-api';
|
||||||
|
import { MarketAPI } from './datasources/market-api';
|
||||||
|
import { ImageServerApi } from './datasources/imageserver-api';
|
||||||
|
import { InteriorAPI } from './datasources/interior-api';
|
||||||
|
|
||||||
console.log(`dbConfig`, dbConfig);
|
import { verifyToken } from './cognito/cognito-client';
|
||||||
|
|
||||||
// Should we convert columns?
|
// Should we convert columns?
|
||||||
const knexConfig = knexStringcase(dbConfig);
|
const knexConfig = knexStringcase(dbConfig);
|
||||||
|
|
||||||
// set up any dataSources our resolvers need
|
// set up any dataSources our resolvers need
|
||||||
const api1 = new Api1DataSource();
|
const categoryApi = new CategoryAPI(knexConfig);
|
||||||
const api2 = new Api2DataSource();
|
const designerApi = new DesignerAPI(knexConfig);
|
||||||
|
const imageServerApi = new ImageServerApi();
|
||||||
|
const interiorApi = new InteriorAPI(knexConfig, imageServerApi);
|
||||||
|
const keywordApi = new KeywordAPI(knexConfig);
|
||||||
|
const marketApi = new MarketAPI(knexConfig);
|
||||||
const orderApi = new OrderAPI(knexConfig);
|
const orderApi = new OrderAPI(knexConfig);
|
||||||
const productApi = new ProductAPI(knexConfig);
|
const productApi = new ProductAPI(knexConfig);
|
||||||
const categoryApi = new CategoryAPI(knexConfig);
|
|
||||||
const keywordApi = new KeywordAPI(knexConfig);
|
|
||||||
const designerApi = new DesignerAPI(knexConfig);
|
|
||||||
|
|
||||||
const dataSources = () => ({
|
const dataSources = () => ({
|
||||||
api1,
|
|
||||||
api2,
|
|
||||||
categoryApi,
|
categoryApi,
|
||||||
designerApi,
|
designerApi,
|
||||||
|
interiorApi,
|
||||||
|
imageServerApi,
|
||||||
keywordApi,
|
keywordApi,
|
||||||
|
marketApi,
|
||||||
orderApi,
|
orderApi,
|
||||||
productApi,
|
productApi,
|
||||||
});
|
});
|
||||||
@@ -51,6 +52,7 @@ const context = async ({ req }) => {
|
|||||||
const res = await verifyToken({ token });
|
const res = await verifyToken({ token });
|
||||||
return { auth: res };
|
return { auth: res };
|
||||||
} else if (authHeader == 'Basic ZGV2OmRldg==') {
|
} else if (authHeader == 'Basic ZGV2OmRldg==') {
|
||||||
|
// dev:dev remove later
|
||||||
return { auth: 'during development' };
|
return { auth: 'during development' };
|
||||||
}
|
}
|
||||||
throw new AuthenticationError('Not authorized');
|
throw new AuthenticationError('Not authorized');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ async function getDesigners(_, { limit }, { dataSources }) {
|
|||||||
return (<DesignerAPI>dataSources.designerApi).getDesigners(limit);
|
return (<DesignerAPI>dataSources.designerApi).getDesigners(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDesigner(_, { id }, { dataSources }) {
|
async function getDesigner(parent, { id }, { dataSources }) {
|
||||||
return (<DesignerAPI>dataSources.designerApi).getDesignerById(id);
|
return (<DesignerAPI>dataSources.designerApi).getDesignerById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-2
@@ -1,9 +1,16 @@
|
|||||||
import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver';
|
import { orderQueryTypeDefs, orderTypeDefs } from './orders-resolver';
|
||||||
import { productQueryTypeDefs, productTypeDefs } from './products-resolver';
|
import {
|
||||||
|
productQueryTypeDefs,
|
||||||
|
productTypeDefs,
|
||||||
|
productMutationTypeDefs,
|
||||||
|
} from './products-resolver';
|
||||||
import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver';
|
import { designerQueryTypeDefs, designerTypeDefs } from './designers-resolver';
|
||||||
import { categoryQueryTypeDefs, categoryTypeDefs } from './categories-resolver';
|
import { categoryQueryTypeDefs, categoryTypeDefs } from './categories-resolver';
|
||||||
import { DateTimeResolver, DateResolver, JSONResolver } from 'graphql-scalars';
|
|
||||||
import { keywordsQueryTypeDefs, keywordTypeDefs } from './keywords-resolver';
|
import { keywordsQueryTypeDefs, keywordTypeDefs } from './keywords-resolver';
|
||||||
|
import { marketsQueryTypeDefs, marketTypeDefs } from './markets-resolver';
|
||||||
|
|
||||||
|
import { DateTimeResolver, DateResolver, JSONResolver } from 'graphql-scalars';
|
||||||
|
import { interiorsQueryTypeDefs, interiorTypeDefs } from './interiors-resolver';
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
...orderQueryTypeDefs,
|
...orderQueryTypeDefs,
|
||||||
@@ -11,12 +18,19 @@ const resolvers = {
|
|||||||
...designerQueryTypeDefs,
|
...designerQueryTypeDefs,
|
||||||
...categoryQueryTypeDefs,
|
...categoryQueryTypeDefs,
|
||||||
...keywordsQueryTypeDefs,
|
...keywordsQueryTypeDefs,
|
||||||
|
...marketsQueryTypeDefs,
|
||||||
|
...interiorsQueryTypeDefs,
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
...productMutationTypeDefs,
|
||||||
},
|
},
|
||||||
...orderTypeDefs,
|
...orderTypeDefs,
|
||||||
...productTypeDefs,
|
...productTypeDefs,
|
||||||
...designerTypeDefs,
|
...designerTypeDefs,
|
||||||
...categoryTypeDefs,
|
...categoryTypeDefs,
|
||||||
...keywordTypeDefs,
|
...keywordTypeDefs,
|
||||||
|
...marketTypeDefs,
|
||||||
|
...interiorTypeDefs,
|
||||||
DateTime: DateTimeResolver,
|
DateTime: DateTimeResolver,
|
||||||
Date: DateResolver,
|
Date: DateResolver,
|
||||||
JSON: JSONResolver,
|
JSON: JSONResolver,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { IResolverObject } from 'graphql-tools';
|
||||||
|
import { InteriorAPI } from '../datasources/interior-api';
|
||||||
|
import { InteriorsFilterInput } from '../types/interior-types';
|
||||||
|
|
||||||
|
const Interior: IResolverObject = {};
|
||||||
|
|
||||||
|
async function getInteriors(_, args: InteriorsFilterInput, { dataSources }) {
|
||||||
|
return (<InteriorAPI>dataSources.interiorApi).getInteriors(args.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const interiorTypeDefs = { Interior };
|
||||||
|
|
||||||
|
export const interiorsQueryTypeDefs = {
|
||||||
|
interiors: getInteriors,
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { IResolverObject } from 'graphql-tools';
|
||||||
|
import { MarketAPI } from '../datasources/market-api';
|
||||||
|
|
||||||
|
const Market: IResolverObject = {};
|
||||||
|
|
||||||
|
async function getMarkets(_, _args, { dataSources }) {
|
||||||
|
return (<MarketAPI>dataSources.marketApi).getMarkets();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMarket(_, { name }, { dataSources }) {
|
||||||
|
return (<MarketAPI>dataSources.marketApi).getMarketByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const marketTypeDefs = { Market };
|
||||||
|
|
||||||
|
export const marketsQueryTypeDefs = {
|
||||||
|
markets: getMarkets,
|
||||||
|
market: getMarket,
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IResolverObject } from 'graphql-tools';
|
import { IResolverObject } from 'graphql-tools';
|
||||||
|
import { MarketAPI } from '../datasources/market-api';
|
||||||
import { OrderAPI } from '../datasources/order-api';
|
import { OrderAPI } from '../datasources/order-api';
|
||||||
import { FilterInput } from '../types/types';
|
import { FilterInput } from '../types/types';
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ const Order: IResolverObject = {
|
|||||||
return (<OrderAPI>dataSources.orderApi).getOrderRowsByOrderId(id);
|
return (<OrderAPI>dataSources.orderApi).getOrderRowsByOrderId(id);
|
||||||
},
|
},
|
||||||
async market({ market }, _args, { dataSources }) {
|
async market({ market }, _args, { dataSources }) {
|
||||||
return (<OrderAPI>dataSources.orderApi).getMarketByName(market);
|
return (<MarketAPI>dataSources.marketApi).getMarketByName(market);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { IResolverObject } from 'graphql-tools';
|
import { IResolverObject } from 'graphql-tools';
|
||||||
import { Api2DataSource } from '../datasources/api2-datasource';
|
|
||||||
import { OrderAPI } from '../datasources/order-api';
|
|
||||||
import { ProductAPI } from '../datasources/product-api';
|
import { ProductAPI } from '../datasources/product-api';
|
||||||
import { Product } from '../types/product-types';
|
import { Product } from '../types/product-types';
|
||||||
import { CategoryAPI } from '../datasources/category-api';
|
import { CategoryAPI } from '../datasources/category-api';
|
||||||
import { Category } from '../types/category-types';
|
import { Category } from '../types/category-types';
|
||||||
import { KeywordAPI } from '../datasources/keyword-api';
|
import { KeywordAPI } from '../datasources/keyword-api';
|
||||||
import { Keyword } from '../types/keyword-types';
|
import { Keyword } from '../types/keyword-types';
|
||||||
|
import { MarketAPI } from '../datasources/market-api';
|
||||||
|
import { InteriorAPI } from '../datasources/interior-api';
|
||||||
|
|
||||||
const ProductBlacklist: IResolverObject = {
|
const ProductBlacklist: IResolverObject = {
|
||||||
async market(parent, _args, { dataSources }) {
|
async market(parent, _args, { dataSources }) {
|
||||||
return (<OrderAPI>dataSources.orderApi).getMarketById(parent.marketId);
|
return (<MarketAPI>dataSources.marketApi).getMarketById(parent.marketId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,24 +20,22 @@ const Product: IResolverObject = {
|
|||||||
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
||||||
},
|
},
|
||||||
async designer({ designerId }, args, { dataSources }) {
|
async designer({ designerId }, args, { dataSources }) {
|
||||||
|
if (!designerId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return dataSources.designerApi.getDesignerById(designerId);
|
return dataSources.designerApi.getDesignerById(designerId);
|
||||||
},
|
},
|
||||||
async keywords({ id }, _args, { dataSources }): Promise<Array<Keyword>> {
|
async keywords({ id }, _args, { dataSources }): Promise<Array<Keyword>> {
|
||||||
return (<KeywordAPI>dataSources.keywordApi).getProductKeywords(id);
|
return (<KeywordAPI>dataSources.keywordApi).getProductKeywords(id);
|
||||||
},
|
},
|
||||||
async api1json({ id }, _args, { dataSources }): Promise<JSON> {
|
async related({ id }, _args, { dataSources }) {
|
||||||
return dataSources.api1.getProduct(id);
|
return (<ProductAPI>dataSources.productApi).getRelatedProducts(id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PrintProduct: IResolverObject = {
|
const PrintProduct: IResolverObject = {
|
||||||
async price({ id }, { market, width, height, sku }, { dataSources }) {
|
async interiors({ id }, _args, { dataSources }) {
|
||||||
return (<Api2DataSource>dataSources.api2).getPrice(
|
return (<InteriorAPI>dataSources.interiorApi).getPrintProductInteriors(id);
|
||||||
market,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
sku,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,3 +65,11 @@ export const productQueryTypeDefs = {
|
|||||||
products: getProducts,
|
products: getProducts,
|
||||||
product: getProduct,
|
product: getProduct,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mutations below
|
||||||
|
|
||||||
|
export const productMutationTypeDefs = {
|
||||||
|
addKeyword(_, { productId, keywordId }, { dataSources }) {
|
||||||
|
return { result: 'OK' };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
+94
-8
@@ -19,6 +19,26 @@ const typeDefs = gql`
|
|||||||
keyword(id: Int!): Keyword
|
keyword(id: Int!): Keyword
|
||||||
designers(limit: Int): [Designer]
|
designers(limit: Int): [Designer]
|
||||||
designer(id: Int!): Designer
|
designer(id: Int!): Designer
|
||||||
|
markets: [Market]
|
||||||
|
market(name: String!): Market
|
||||||
|
interiors(filter: InteriorsFilterInput): [Interior]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
addKeyword(productId: Int!, keywordId: Int!): MutationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutationResult {
|
||||||
|
result: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Only return valid interiors for product, type and orientation
|
||||||
|
"""
|
||||||
|
input InteriorsFilterInput {
|
||||||
|
productId: Int!
|
||||||
|
type: InteriorType!
|
||||||
|
orientation: Orientation!
|
||||||
}
|
}
|
||||||
|
|
||||||
input DateInput {
|
input DateInput {
|
||||||
@@ -134,8 +154,8 @@ const typeDefs = gql`
|
|||||||
designerPath: String
|
designerPath: String
|
||||||
discountType: String
|
discountType: String
|
||||||
discountValue: Float
|
discountValue: Float
|
||||||
displayHeight: Int
|
displayHeight: Float
|
||||||
displayWidth: Int
|
displayWidth: Float
|
||||||
edge: String
|
edge: String
|
||||||
frameColor: String
|
frameColor: String
|
||||||
framed: Int
|
framed: Int
|
||||||
@@ -175,6 +195,7 @@ const typeDefs = gql`
|
|||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
path: String
|
path: String
|
||||||
|
pathNumeric: String
|
||||||
isLeaf: Int
|
isLeaf: Int
|
||||||
depth: Int
|
depth: Int
|
||||||
childCount: Int
|
childCount: Int
|
||||||
@@ -185,6 +206,7 @@ const typeDefs = gql`
|
|||||||
|
|
||||||
type Product {
|
type Product {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
stockid: Int
|
||||||
path: String!
|
path: String!
|
||||||
visible: Boolean!
|
visible: Boolean!
|
||||||
browsable: Boolean!
|
browsable: Boolean!
|
||||||
@@ -196,15 +218,53 @@ const typeDefs = gql`
|
|||||||
designerId: Int
|
designerId: Int
|
||||||
designer: Designer
|
designer: Designer
|
||||||
keywords: [Keyword]
|
keywords: [Keyword]
|
||||||
|
ref1: String
|
||||||
|
ref2: String
|
||||||
|
ref3: String
|
||||||
|
related: [Product]
|
||||||
|
wallpaperTypes: [ProductWallpaperType]
|
||||||
|
fields: ProductFields
|
||||||
|
orientation: Orientation
|
||||||
"""
|
"""
|
||||||
Will be single value return in later release
|
Will be single values return in later release
|
||||||
"""
|
"""
|
||||||
type: [ProductType]
|
type: [ProductType]
|
||||||
# Below are for debug
|
# Below are for debug and will be removed soon
|
||||||
api1json: JSON
|
|
||||||
fields_json: JSON
|
fields_json: JSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProductFields {
|
||||||
|
artNo: String!
|
||||||
|
name: String!
|
||||||
|
height: Int
|
||||||
|
width: Int
|
||||||
|
photowallResolution: Int
|
||||||
|
canvasResolution: Int
|
||||||
|
wallpaperResolution: Int
|
||||||
|
copyright: String
|
||||||
|
proportionsWarning: String
|
||||||
|
marginWidthMax: Int
|
||||||
|
marginWidthMin: Int
|
||||||
|
marginHeightMax: Int
|
||||||
|
marginHeightMin: Int
|
||||||
|
focusXpoint: Float
|
||||||
|
focusYpoint: Float
|
||||||
|
focusXpoint2: Float
|
||||||
|
focusYpoint2: Float
|
||||||
|
batch: String
|
||||||
|
imageResolution: Int
|
||||||
|
printFileWidth: Int
|
||||||
|
printFileHeight: Int
|
||||||
|
printFileDpi: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Orientation {
|
||||||
|
UNKNOWN
|
||||||
|
PORTRAIT
|
||||||
|
LANDSCAPE
|
||||||
|
SQUARE
|
||||||
|
}
|
||||||
|
|
||||||
enum ProductGroup {
|
enum ProductGroup {
|
||||||
PHOTO_WALLPAPER
|
PHOTO_WALLPAPER
|
||||||
CANVAS
|
CANVAS
|
||||||
@@ -223,6 +283,11 @@ const typeDefs = gql`
|
|||||||
ILLUSTRATION
|
ILLUSTRATION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ProductWallpaperType {
|
||||||
|
WALLMURAL
|
||||||
|
DESIGN
|
||||||
|
}
|
||||||
|
|
||||||
type ProductBlacklist {
|
type ProductBlacklist {
|
||||||
id: ID!
|
id: ID!
|
||||||
groupId: Int!
|
groupId: Int!
|
||||||
@@ -239,7 +304,28 @@ const typeDefs = gql`
|
|||||||
group: ProductGroup!
|
group: ProductGroup!
|
||||||
inserted: DateTime!
|
inserted: DateTime!
|
||||||
updated: DateTime
|
updated: DateTime
|
||||||
price(market: Int, width: Int, height: Int, sku: String): JSON
|
interiors: [Interior]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InteriorType {
|
||||||
|
WALLPAPER
|
||||||
|
PAINTING
|
||||||
|
POSTER
|
||||||
|
FRAMED_PRINT
|
||||||
|
}
|
||||||
|
|
||||||
|
type Interior {
|
||||||
|
"""
|
||||||
|
room id
|
||||||
|
"""
|
||||||
|
id: ID!
|
||||||
|
roomName: String
|
||||||
|
position: Int
|
||||||
|
roomType: String
|
||||||
|
uri: String
|
||||||
|
roomNumber: Int
|
||||||
|
type: InteriorType
|
||||||
|
orientation: Orientation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Designer {
|
type Designer {
|
||||||
@@ -255,7 +341,7 @@ const typeDefs = gql`
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Keyword {
|
type Keyword {
|
||||||
id: Int!
|
id: ID!
|
||||||
value: String!
|
value: String!
|
||||||
type: KeywordType
|
type: KeywordType
|
||||||
products: [Product]
|
products: [Product]
|
||||||
@@ -321,7 +407,7 @@ orderrows example
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
types:
|
||||||
"doityourselfframe"
|
"doityourselfframe"
|
||||||
"fixed"
|
"fixed"
|
||||||
"tiling"
|
"tiling"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface Category {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
pathNumeric: string;
|
||||||
isLeaf: number;
|
isLeaf: number;
|
||||||
depth: number;
|
depth: number;
|
||||||
childCount: number;
|
childCount: number;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Orientation } from './product-types';
|
||||||
|
|
||||||
|
export interface Interior {
|
||||||
|
id: number;
|
||||||
|
roomName: string;
|
||||||
|
position: number;
|
||||||
|
roomType: string;
|
||||||
|
uri: string;
|
||||||
|
roomNumber: number;
|
||||||
|
type: InteriorType;
|
||||||
|
orientation: Orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum InteriorType {
|
||||||
|
WALLPAPER,
|
||||||
|
PAINTING,
|
||||||
|
POSTER,
|
||||||
|
FRAMED_PRINT,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InteriorsFilter {
|
||||||
|
productId: number;
|
||||||
|
type: InteriorType;
|
||||||
|
orientation: Orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InteriorsFilterInput {
|
||||||
|
filter?: InteriorsFilter;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Market {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
vat: number;
|
||||||
|
currency: string;
|
||||||
|
priceAdjustment: number;
|
||||||
|
}
|
||||||
@@ -1,13 +1,3 @@
|
|||||||
import { DateInput } from './types';
|
|
||||||
|
|
||||||
export interface Market {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
vat: number;
|
|
||||||
currency: string;
|
|
||||||
priceAdjustment: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
id: number;
|
id: number;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ export enum ProductType {
|
|||||||
PHOTO = 1,
|
PHOTO = 1,
|
||||||
ILLUSTRATION = 2,
|
ILLUSTRATION = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProductWallpaperType {
|
||||||
|
WALLMURAL = 1,
|
||||||
|
DESIGN = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Orientation {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
PORTRAIT = 1,
|
||||||
|
LANDSCAPE = 2,
|
||||||
|
SQUARE = 3,
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductBlacklist {
|
export interface ProductBlacklist {
|
||||||
id: number;
|
id: number;
|
||||||
groupId: number;
|
groupId: number;
|
||||||
@@ -34,16 +47,44 @@ export interface PrintProduct {
|
|||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id: number;
|
id: number;
|
||||||
|
stockid: number;
|
||||||
path: string;
|
path: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
browsable: boolean;
|
browsable: boolean;
|
||||||
inserted: Date;
|
inserted: Date;
|
||||||
updated?: Date;
|
updated?: Date;
|
||||||
|
orientation?: Orientation;
|
||||||
printProducts: [PrintProduct];
|
printProducts: [PrintProduct];
|
||||||
blacklisting: [ProductBlacklist];
|
blacklisting: [ProductBlacklist];
|
||||||
type: [ProductType];
|
type: [ProductType];
|
||||||
api1json: JSON;
|
fields: ProductFields;
|
||||||
fields_json: JSON;
|
fields_json: JSON; // remove later
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test stock products to see what props can be mandatory
|
||||||
|
export interface ProductFields {
|
||||||
|
artNo: string;
|
||||||
|
name: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
photowallResolution?: number;
|
||||||
|
canvasResolution?: number;
|
||||||
|
wallpaperResolution?: number;
|
||||||
|
copyright?: string;
|
||||||
|
proportionsWarning?: string;
|
||||||
|
marginWidthMax?: number;
|
||||||
|
marginWidthMin?: number;
|
||||||
|
marginHeightMax?: number;
|
||||||
|
marginHeightMin?: number;
|
||||||
|
focusXpoint?: number;
|
||||||
|
focusYpoint?: number;
|
||||||
|
focusXpoint2?: number;
|
||||||
|
focusYpoint2?: number;
|
||||||
|
batch?: string;
|
||||||
|
imageResolution?: number;
|
||||||
|
printFileWidth?: number;
|
||||||
|
printFileHeight?: number;
|
||||||
|
printFileDpi?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProductGroupStringFromString(group: string): string {
|
export function getProductGroupStringFromString(group: string): string {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
When added as order rows :
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
kit 47851:
|
||||||
|
'stockId'
|
||||||
|
'productId' 47851
|
||||||
|
'type' // always 'stock'
|
||||||
|
'artNo'
|
||||||
|
'path'
|
||||||
|
'name'
|
||||||
|
'baseName'
|
||||||
|
'price'
|
||||||
|
'weight'
|
||||||
|
|
||||||
|
|
||||||
|
dyi frame 45479:
|
||||||
|
'stockId'
|
||||||
|
'productId' 45479
|
||||||
|
'product_description'
|
||||||
|
'type' // always 'stock'
|
||||||
|
'artNo'
|
||||||
|
'path'
|
||||||
|
'name'
|
||||||
|
'price'
|
||||||
|
'width'
|
||||||
|
'height'
|
||||||
|
'weight'
|
||||||
|
'measure_unit'
|
||||||
|
'displayWidth'
|
||||||
|
'displayHeight'
|
||||||
|
|
||||||
|
|
||||||
|
sample:
|
||||||
|
'productId'
|
||||||
|
'name'
|
||||||
|
'artNo'
|
||||||
|
'material'
|
||||||
|
'weight'
|
||||||
|
'price'
|
||||||
|
'type' // always 'stock'
|
||||||
|
'path'
|
||||||
Reference in New Issue
Block a user