Remove serverless, add graphql relationships.

This commit is contained in:
Patrick Fic
2026-02-24 12:04:13 -08:00
parent f456523825
commit 605e46b1b0
8 changed files with 74 additions and 470 deletions

View File

@@ -1,3 +1,14 @@
table:
name: joblines
schema: public
object_relationships:
- name: job
using:
manual_configuration:
column_mapping:
jobid: id
version: version
insertion_order: null
remote_table:
name: jobs
schema: public

View File

@@ -1,3 +1,18 @@
table:
name: jobs
schema: public
object_relationships:
- name: shop
using:
foreign_key_constraint_on: shopid
array_relationships:
- name: joblines
using:
manual_configuration:
column_mapping:
id: jobid
version: version
insertion_order: null
remote_table:
name: joblines
schema: public

View File

@@ -1,3 +1,11 @@
table:
name: shops
schema: public
array_relationships:
- name: jobs
using:
foreign_key_constraint_on:
column: shopid
table:
name: jobs
schema: public

View File

@@ -14,6 +14,8 @@
"@aws-sdk/s3-request-presigner": "^3.971.0",
"axios": "^1.13.2",
"form-data": "^4.0.5",
"graphql": "^16.12.0",
"graphql-request": "^7.4.0",
"lodash": "^4.17.21"
},
"devDependencies": {
@@ -1720,6 +1722,15 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@graphql-typed-document-node/core": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
"license": "MIT",
"peerDependencies": {
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -4761,6 +4772,27 @@
"dev": true,
"license": "ISC"
},
"node_modules/graphql": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
"license": "MIT",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/graphql-request": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.4.0.tgz",
"integrity": "sha512-xfr+zFb/QYbs4l4ty0dltqiXIp07U6sl+tOKAb0t50/EnQek6CVVBLjETXi+FghElytvgaAWtIOt3EV7zLzIAQ==",
"license": "MIT",
"dependencies": {
"@graphql-typed-document-node/core": "^3.2.0"
},
"peerDependencies": {
"graphql": "14 - 16"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",

View File

@@ -24,6 +24,8 @@
"@aws-sdk/s3-request-presigner": "^3.971.0",
"axios": "^1.13.2",
"form-data": "^4.0.5",
"graphql": "^16.12.0",
"graphql-request": "^7.4.0",
"lodash": "^4.17.21"
},
"devDependencies": {

View File

@@ -1,397 +0,0 @@
service: esdp-infra
app: esdp-api-app
frameworkVersion: '4'
stages:
shared:
observability: false
params:
# Single shared database name for all app stages (dev/alpha/beta/prod)
db_name: esdp
# Your public IP in CIDR form for direct DB access (lock this down).
admin_cidr: 70.36.57.88/32
provider:
name: aws
runtime: nodejs22.x
region: ca-central-1
resources:
Resources:
EsdpVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-vpc
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-igw
VpcGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: EsdpVpc
InternetGatewayId:
Ref: InternetGateway
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: EsdpVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ''
CidrBlock: 10.0.0.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-public-a
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: EsdpVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ''
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-public-b
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: EsdpVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ''
CidrBlock: 10.0.10.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-private-a
PrivateSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: EsdpVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ''
CidrBlock: 10.0.11.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-private-b
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: EsdpVpc
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-public-rt
PublicRoute:
Type: AWS::EC2::Route
DependsOn: VpcGatewayAttachment
Properties:
RouteTableId:
Ref: PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
PublicSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnetA
RouteTableId:
Ref: PublicRouteTable
PublicSubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnetB
RouteTableId:
Ref: PublicRouteTable
NatEip:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatEip
- AllocationId
SubnetId:
Ref: PublicSubnetA
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-nat
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: EsdpVpc
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-private-rt
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: NatGateway
PrivateSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PrivateSubnetA
RouteTableId:
Ref: PrivateRouteTable
PrivateSubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PrivateSubnetB
RouteTableId:
Ref: PrivateRouteTable
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ${self:service}-${sls:stage} Lambda security group
VpcId:
Ref: EsdpVpc
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-lambda-sg
ProxySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ${self:service}-${sls:stage} RDS proxy security group
VpcId:
Ref: EsdpVpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId:
Ref: LambdaSecurityGroup
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: ${param:admin_cidr}
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-proxy-sg
RdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ${self:service}-${sls:stage} RDS security group
VpcId:
Ref: EsdpVpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId:
Ref: ProxySecurityGroup
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: ${param:admin_cidr}
Tags:
- Key: Name
Value: ${self:service}-${sls:stage}-rds-sg
DbSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: ${self:service}-${sls:stage} DB subnet group
SubnetIds:
- Ref: PublicSubnetA
- Ref: PublicSubnetB
DbSecret:
Type: AWS::SecretsManager::Secret
Properties:
Description: ${self:service}-${sls:stage} RDS master credentials
GenerateSecretString:
SecretStringTemplate: '{"username":"esdp_admin"}'
GenerateStringKey: password
PasswordLength: 32
ExcludeCharacters: '"@/\\'
DbProxyRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- rds.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: ${self:service}-${sls:stage}-db-proxy-secrets
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- Ref: DbSecret
DbProxy:
Type: AWS::RDS::DBProxy
Properties:
DBProxyName: ${self:service}-${sls:stage}-proxy
EngineFamily: POSTGRESQL
IdleClientTimeout: 1800
RequireTLS: true
RoleArn:
Fn::GetAtt:
- DbProxyRole
- Arn
VpcSubnetIds:
- Ref: PrivateSubnetA
- Ref: PrivateSubnetB
VpcSecurityGroupIds:
- Ref: ProxySecurityGroup
Auth:
- AuthScheme: SECRETS
SecretArn:
Ref: DbSecret
IAMAuth: DISABLED
DbProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
Properties:
DBProxyName:
Ref: DbProxy
TargetGroupName: default
DBInstanceIdentifiers:
- Ref: PostgresDb
ConnectionPoolConfigurationInfo:
MaxConnectionsPercent: 75
MaxIdleConnectionsPercent: 50
ConnectionBorrowTimeout: 120
PostgresDb:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
UpdateReplacePolicy: Snapshot
Properties:
DBInstanceIdentifier: ${self:service}-${sls:stage}-postgres
Engine: postgres
# EngineVersion intentionally omitted so AWS uses the current default/latest for RDS Postgres.
AutoMinorVersionUpgrade: true
DBInstanceClass: db.t4g.micro
AllocatedStorage: 20
StorageType: gp3
StorageEncrypted: true
PubliclyAccessible: true
MultiAZ: false
DBName: ${param:db_name}
BackupRetentionPeriod: 7
CopyTagsToSnapshot: true
DeletionProtection: true
VPCSecurityGroups:
- Ref: RdsSecurityGroup
DBSubnetGroupName:
Ref: DbSubnetGroup
MasterUsername:
Fn::Sub: '{{resolve:secretsmanager:${DbSecret}::username}}'
MasterUserPassword:
Fn::Sub: '{{resolve:secretsmanager:${DbSecret}::password}}'
Outputs:
VpcId:
Value:
Ref: EsdpVpc
PrivateSubnetAId:
Value:
Ref: PrivateSubnetA
PrivateSubnetBId:
Value:
Ref: PrivateSubnetB
LambdaSecurityGroupId:
Value:
Ref: LambdaSecurityGroup
ProxySecurityGroupId:
Value:
Ref: ProxySecurityGroup
RdsSecurityGroupId:
Value:
Ref: RdsSecurityGroup
DbName:
Value: ${param:db_name}
DbPort:
Value: 5432
DbEndpointAddress:
Value:
Fn::GetAtt:
- PostgresDb
- Endpoint.Address
DbEndpointPort:
Value:
Fn::GetAtt:
- PostgresDb
- Endpoint.Port
DbSecretArn:
Value:
Ref: DbSecret
DbProxyEndpoint:
Value:
Fn::GetAtt:
- DbProxy
- Endpoint

View File

@@ -1,6 +1,5 @@
service: esdp-api
app: esdp-api-app
frameworkVersion: '4'
stages:
prod:

View File

@@ -1,76 +1,10 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import axios, { AxiosError } from 'axios';
import FormData from 'form-data';
import { ESJobObject, RawJobDataObject } from '../../../shared/types';
import { transformJobForEstimateScrubber } from '../lib/transformEstimate';
import { getVehicleType } from '../lib/vehicleTypes/vehicleType';
const ES_USER = process.env.ES_USER || '';
const ES_PASSWORD = process.env.ES_PASSWORD || '';
const ES_ENDPOINT = process.env.ES_ENDPOINT || '';
interface ScrubRequest {
esApiKey: string;
rawJob: RawJobDataObject;
}
interface ScrubResponse {
report_link?: string;
identified_item?: unknown;
}
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const { esApiKey, rawJob } = JSON.parse(event.body || '{}') as ScrubRequest;
// Transform the raw job object to ES format
const estimate: ESJobObject = await transformJobForEstimateScrubber(rawJob);
// Set vehicle type and sending entity ID
estimate.v_type = getVehicleType(estimate.v_model || '').type;
estimate.sending_entity_id = '87330f61-412b-4251-baaa-d026565b23c5';
const fileName = `${esApiKey}-${rawJob.clm_no}-${Date.now()}`;
const formData = new FormData();
const jsonString = JSON.stringify(estimate);
formData.append('file', Buffer.from(jsonString), {
filename: `${fileName}.json`,
contentType: 'application/json',
});
const result = await axios.post<ScrubResponse>(`${ES_ENDPOINT}/api/sendems`, formData, {
auth: {
username: ES_USER,
password: ES_PASSWORD,
},
headers: {
APIkey: esApiKey,
},
});
const resultPDFUrl = result?.data?.report_link;
const reportIssueUrl = `https://insurtechtoolkit.com/pcontactUs.aspx?apiKey=${esApiKey}&file=${fileName}.json`;
return {
statusCode: 200,
body: JSON.stringify({
resultPDFUrl,
reportIssueUrl,
identified_item: result.data?.identified_item,
}),
};
} catch (error) {
const axiosError = error as AxiosError;
const errorMessage = axiosError.response?.data || axiosError.message || 'Unknown error';
return {
statusCode: 400,
body: JSON.stringify({
message: 'Error scrubbing estimate.',
error: errorMessage,
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({
message: 'Scrub handler is under construction',
}),
};
};