Change serverless to typescript.

This commit is contained in:
Patrick Fic
2026-01-13 14:45:31 -08:00
parent ea4131102c
commit 319e2e37df
16 changed files with 4660 additions and 162 deletions

31
serverless/.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
# Dependencies
node_modules/
# Build output
dist/
# Serverless
.serverless/
.build/
# TypeScript
*.tsbuildinfo
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment
.env
.env.local
.env.*.local

80
serverless/README.md Normal file
View File

@@ -0,0 +1,80 @@
# Serverless TypeScript Project
This folder contains AWS Lambda functions deployed via the Serverless Framework, written in TypeScript.
## Setup
This project is configured with TypeScript, ESLint, and Prettier independently from the main project.
### Prerequisites
- Node.js 22.x or higher
- npm or yarn
### Installation
```bash
npm install
```
## Development
### Available Scripts
- `npm run build` - Compile TypeScript to JavaScript
- `npm run type-check` - Run TypeScript type checking without emitting files
- `npm run lint` - Run ESLint on TypeScript files
- `npm run lint:fix` - Run ESLint with auto-fix
- `npm run format` - Format code with Prettier
- `npm run format:check` - Check if code is formatted correctly
### Project Structure
```
src/
handlers/ # Lambda function handlers
vehicleType.ts # Vehicle type lookup handler
scrub.ts # Estimate scrubbing handler
emsupload.ts # File upload presigned URL handler
lib/ # Shared library code
vehicleTypes/ # Vehicle type utilities
db.ts # Database utilities
```
## Deployment
The project uses `serverless-esbuild` for bundling TypeScript code for deployment.
```bash
# Deploy to dev stage
sls deploy --stage dev
# Deploy to production
sls deploy --stage prod
```
## Local Development
```bash
# Run serverless offline
sls dev
```
## Configuration
- **TypeScript**: Configured in `tsconfig.json`
- **ESLint**: Configured in `eslint.config.mjs` (ESM flat config)
- **Prettier**: Configured in `.prettierrc.json`
- **Serverless**: Configured in `serverless.yml`
## Code Quality
This project enforces:
- TypeScript strict mode
- ESLint rules for TypeScript
- Prettier formatting
- No unused variables or parameters (except those prefixed with `_`)
## Environment Variables
See `serverless.yml` for environment-specific configuration.

View File

@@ -0,0 +1,42 @@
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import prettier from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
export default [
js.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
},
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
prettier: prettier,
},
rules: {
...tsPlugin.configs.recommended.rules,
...prettierConfig.rules,
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
},
},
{
ignores: ['node_modules/**', 'dist/**', '.serverless/**', '**/*.js'],
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,14 @@
"name": "serverless",
"version": "1.0.0",
"description": "",
"main": "index.js",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"lint": "eslint 'src/**/*.ts'",
"lint:fix": "eslint 'src/**/*.ts' --fix",
"format": "prettier --write 'src/**/*.ts'",
"format:check": "prettier --check 'src/**/*.ts'",
"type-check": "tsc --noEmit",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
@@ -15,5 +21,20 @@
"@aws-sdk/s3-request-presigner": "^3.965.0",
"axios": "^1.13.2",
"form-data": "^4.0.1"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/aws-lambda": "^8.10.159",
"@types/node": "^25.0.8",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"esbuild": "^0.27.2",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"prettier": "^3.7.4",
"serverless-esbuild": "^1.57.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}

View File

@@ -60,9 +60,19 @@ provider:
httpApi: # This creates a cheaper, faster "HTTP API" Gateway
cors: true # Automatically configures CORS
build:
esbuild:
bundle: true
minify: false
sourcemap: true
exclude:
- '@aws-sdk/*'
target: node22
platform: node
functions:
vehicleType:
handler: src/handlers/vehicleTypes.handler
handler: src/handlers/vehicleType.handler
events:
- httpApi:
path: /vehicleType

View File

@@ -1,16 +1,33 @@
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3Client = new S3Client({ region: process.env.AWS_REGION || 'ca-central-1' });
const BUCKET_NAME = process.env.UPLOAD_BUCKET_NAME;
const BUCKET_NAME = process.env.UPLOAD_BUCKET_NAME || '';
interface EmsUploadRequest {
esApiKey: string;
ciecaid: string;
clm_no: string;
ownr_ln: string;
}
interface EmsUploadResponse {
success: boolean;
presignedUrl?: string;
key?: string;
bucket?: string;
expiresIn?: number;
error?: string;
message?: string;
}
/**
* Validates the EMS Upload ID
* @param {string} emsUploadId - The EMS Upload ID to validate
* @param {string} esApiKey - The ES API Key to validate
* @returns {boolean} - Always returns true for now (placeholder)
*/
function validateEmsUploadId(esApiKey) {
function validateEmsUploadId(_esApiKey: string): boolean {
// Placeholder validation - always returns true
// TODO: Implement actual validation logic
return true;
@@ -18,12 +35,12 @@ function validateEmsUploadId(esApiKey) {
/**
* Lambda handler for EMS Upload
* Expects a JSON body with: { emsUploadId: string, fileName?: string }
* Expects a JSON body with: { esApiKey: string, ciecaid: string, clm_no: string, ownr_ln: string }
*/
exports.handler = async (event) => {
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
// Parse the request body
const body = JSON.parse(event.body || '{}');
const body = JSON.parse(event.body || '{}') as EmsUploadRequest;
const { esApiKey, ciecaid, clm_no, ownr_ln } = body;
// Validate required fields
@@ -31,8 +48,8 @@ exports.handler = async (event) => {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Missing required field: esApiKey'
}),
error: 'Missing required field: esApiKey',
} as EmsUploadResponse),
};
}
@@ -42,50 +59,57 @@ exports.handler = async (event) => {
return {
statusCode: 401,
body: JSON.stringify({
error: 'Invalid ES API Key'
})
error: 'Invalid ES API Key',
} as EmsUploadResponse),
};
}
// Generate a unique key for the upload
const key = `${esApiKey}/${ciecaid}-${clm_no}-${ownr_ln}-${new Date().toISOString().replace(/T/, '--').replace(/\..+/, '').replace(/:/g, '-')}.zip`;
const timestamp = new Date()
.toISOString()
.replace(/T/, '--')
.replace(/\..+/, '')
.replace(/:/g, '-');
const key = `${esApiKey}/${ciecaid}-${clm_no}-${ownr_ln}-${timestamp}.zip`;
// Create the presigned URL for upload (valid for 15 minutes)
const command = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
StorageClass: "INTELLIGENT_TIERING"
StorageClass: 'INTELLIGENT_TIERING',
});
const presignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 900 // 15 minutes
expiresIn: 900, // 15 minutes
});
console.log('Generated presigned URL for key:', key);
console.log('Presigned URL:', presignedUrl);
// Return success response with presigned URL and key
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
body: JSON.stringify({
success: true,
presignedUrl,
key,
bucket: BUCKET_NAME,
expiresIn: 900
})
expiresIn: 900,
} as EmsUploadResponse),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error('Error generating presigned URL:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Internal server error',
message: error.message
})
message: errorMessage,
} as EmsUploadResponse),
};
}
};
};

View File

@@ -1,63 +0,0 @@
const axios = require('axios');
const FormData = require('form-data');
const ES_USER = process.env.ES_USER;
const ES_PASSWORD = process.env.ES_PASSWORD;
const ES_ENDPOINT = process.env.ES_ENDPOINT;
const getVehicleType = require("../lib/vehicleTypes/vehicleType").getVehicleType;
exports.handler = async (event) => {
// Path parameters are automatically parsed for you
//Take the estimate details, add them to the database, scrub it, and then retrun the result and add the scrubbed results to database.
try {
const { esApiKey, estimate } = event.body ? JSON.parse(event.body) : {};
//TODO: Replace the previous vehicle logic with just getting it here.
estimate.v_type = getVehicleType(estimate.v_model).type;
estimate.sendingEntityId = "87330f61-412b-4251-baaa-d026565b23c5";
const fileName = `${esApiKey}-${estimate.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(
`${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) {
return {
statusCode: 400,
body: JSON.stringify({
message: `Error scrubbing estimate.`,
error: error.response.data || error.message || 'Unknown error',
})
};
}
};

View File

@@ -0,0 +1,78 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import axios, { AxiosError } from 'axios';
import FormData from 'form-data';
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 Estimate {
v_type?: string;
v_model: string;
clm_no: string;
sendingEntityId?: string;
[key: string]: unknown;
}
interface ScrubRequest {
esApiKey: string;
estimate: Estimate;
}
interface ScrubResponse {
report_link?: string;
identified_item?: unknown;
}
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const { esApiKey, estimate } = JSON.parse(event.body || '{}') as ScrubRequest;
// Set vehicle type and sending entity ID
estimate.v_type = getVehicleType(estimate.v_model).type;
estimate.sendingEntityId = '87330f61-412b-4251-baaa-d026565b23c5';
const fileName = `${esApiKey}-${estimate.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,
}),
};
}
};

View File

@@ -1,55 +0,0 @@
// You can require shared code from the lib folder
// const { dbConnect } = require('../lib/db');
const TrucksList = require("../lib/vehicleTypes/trucks.json");
const CargoVanList = require("../lib/vehicleTypes/cargovans.json");
const PassengerVanList = require("../lib/vehicleTypes/passengervans.json");
const SuvList = require("../lib/vehicleTypes/suvs.json");
exports.handler = async (event) => {
try {
const { model } = JSON.parse(event.body || '{}');
if (!model || model.trim() === "") {
return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: "Please provide a model"
})
}
} else {
vehicle
const type = getVehicleType(model.trim())
return {
statusCode: 200,
body: JSON.stringify({
success: true, ...type
})
}
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: error.message,
})
};
};
}
function getVehicleType(model) {
const inTrucks = TrucksList.includes(model.toUpperCase());
const inPV = PassengerVanList.includes(model.toUpperCase());
const inSuv = SuvList.includes(model.toUpperCase());
const inCv = CargoVanList.includes(model.toUpperCase());
if (inTrucks) return { type: "TK", match: true };
else if (inPV) return { type: "PC", match: true };
else if (inSuv) return { type: "SUV", match: true };
else if (inCv) return { type: "VN", match: true };
else return { type: "PC", match: false };
}

View File

@@ -0,0 +1,62 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import TrucksList from '../lib/vehicleTypes/trucks.json';
import CargoVanList from '../lib/vehicleTypes/cargovans.json';
import PassengerVanList from '../lib/vehicleTypes/passengervans.json';
import SuvList from '../lib/vehicleTypes/suvs.json';
interface VehicleTypeRequest {
model?: string;
}
interface VehicleTypeResult {
type: string;
match: boolean;
}
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const { model } = JSON.parse(event.body || '{}') as VehicleTypeRequest;
if (!model || model.trim() === '') {
return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: 'Please provide a model',
}),
};
}
const type = getVehicleType(model.trim());
return {
statusCode: 200,
body: JSON.stringify({
success: true,
...type,
}),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: errorMessage,
}),
};
}
};
function getVehicleType(model: string): VehicleTypeResult {
const upperModel = model.toUpperCase();
const inTrucks = TrucksList.includes(upperModel);
const inPV = PassengerVanList.includes(upperModel);
const inSuv = SuvList.includes(upperModel);
const inCv = CargoVanList.includes(upperModel);
if (inTrucks) return { type: 'TK', match: true };
else if (inPV) return { type: 'PC', match: true };
else if (inSuv) return { type: 'SUV', match: true };
else if (inCv) return { type: 'VN', match: true };
else return { type: 'PC', match: false };
}

2
serverless/src/lib/db.ts Normal file
View File

@@ -0,0 +1,2 @@
// Placeholder for database utilities
// Export database connection and helper functions here

View File

@@ -1,18 +0,0 @@
const TrucksList = require("./trucks.json");
const CargoVanList = require("./cargovans.json");
const PassengerVanList = require("./passengervans.json");
const SuvList = require("./suvs.json");
function getVehicleType(model) {
const inTrucks = TrucksList.includes(model.toUpperCase());
const inPV = PassengerVanList.includes(model.toUpperCase());
const inSuv = SuvList.includes(model.toUpperCase());
const inCv = CargoVanList.includes(model.toUpperCase());
if (inTrucks) return { type: "TK", match: true };
else if (inPV) return { type: "PC", match: true };
else if (inSuv) return { type: "SUV", match: true };
else if (inCv) return { type: "VN", match: true };
else return { type: "PC", match: false };
}
exports.getVehicleType = getVehicleType;

View File

@@ -0,0 +1,23 @@
import TrucksList from './trucks.json';
import CargoVanList from './cargovans.json';
import PassengerVanList from './passengervans.json';
import SuvList from './suvs.json';
interface VehicleTypeResult {
type: string;
match: boolean;
}
export function getVehicleType(model: string): VehicleTypeResult {
const upperModel = model.toUpperCase();
const inTrucks = TrucksList.includes(upperModel);
const inPV = PassengerVanList.includes(upperModel);
const inSuv = SuvList.includes(upperModel);
const inCv = CargoVanList.includes(upperModel);
if (inTrucks) return { type: 'TK', match: true };
else if (inPV) return { type: 'PC', match: true };
else if (inSuv) return { type: 'SUV', match: true };
else if (inCv) return { type: 'VN', match: true };
else return { type: 'PC', match: false };
}

24
serverless/tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", ".serverless"]
}