jbw-api — Vendor Integration API (Spring Boot / Java 17)
Module path:
st-integration/jbw-api/· deployed asprodapi_jb360dealer/devapi_jb360dealer· fronted byJB360IntegrationAPIatapi-jb360.jbwarranties.com· no IaC — manual jar uploads
Purpose
Section titled “Purpose”The vendor-facing integration API: a second, independent read/write interface over the same jbwst MySQL database that jbw-sam and extraction use, aimed at external vendor platforms rather than at the JB360 portal.
It is not the jbwui backend — that is jbw-sam. It is also not the JBW public API (public-api.jbwarranties.com), which is an upstream system jbw-sam calls out to; jbw-api has no outbound HTTP client to JBW at all.
What distinguishes it from jbw-sam is the tblVendor* table family — tblVendor, tblVendorApps, tblVendorInvoice, tblVendorLocationData, tblVendorTransmissionDetails — which has no counterpart in jbw-sam. Every request is attributed to a vendor and archived.
Architecture at a Glance
Section titled “Architecture at a Glance”| Stack | Spring Boot 3.5.4, Java 17, Spring Data JPA / Hibernate (MySQL), MapStruct 1.5.5, Lombok |
| Lambda adapter | aws-serverless-java-container-springboot3 2.0.1 |
| Entry point | StreamLambdaHandler implements RequestStreamHandler — src/main/java/com/maktix/jb/api/StreamLambdaHandler.java:16-26 |
| Trigger | API Gateway proxy only — no SQS, no schedule, no S3 events |
| Database | jbwst MySQL via RDS IAM auth token (no stored password) — config/JpaConfiguration.java:43-73 |
| Docs | springdoc-openapi 2.8.6, Swagger UI vendored into src/main/resources/static/swagger-ui/ |
| Deploy | mvn package → shaded jar (classifier aws) → uploaded by hand |
Endpoints
Section titled “Endpoints”All controllers are mapped @RequestMapping({ "", "/stage" }) and annotated @SecurityRequirement(name = "cognitoAuth").
| Method + path | Source |
|---|---|
GET /dealer/{dealerid}/agreements · /{invoiceid} · /{locationid} · /modified · /created | controller/AgreementController.java:45,72,88,120,136 |
GET /dealer/{dealerid}/jbwagreements/{jbwAgreementId} · GET /synced/agreements/{locationid} · GET /{id} | controller/AgreementController.java:104,152,167 |
POST / PUT / DELETE /dealer/{dealerid}/agreements[/{id}] | controller/AgreementController.java:173,181,191 |
GET /dealer/{dealerId}/claims[/{invoiceId}|{locationId}|/modified|/created] | controller/ClaimController.java:45,61,77,92 |
GET /dealer/{dealerid}/jbwclaims/{jbwClaimId} · GET /{id} | controller/ClaimController.java:123,154 |
POST / PUT / DELETE /dealer/{dealerid}/claims[/{id}] | controller/ClaimController.java:160,168,178 |
GET /dealer/{dealerid} · GET /dealer/platform/{platformName} | controller/DealerController.java:34,43 |
POST / PUT /dealer/{dealerid}/locations[/{id}] · GET variants by customerId / tenantId | controller/LocationController.java:54,69,85,107,135,163 |
POST / PUT / GET /dealer/{dealerid}/invoices[/{invoiceid}|/{id}|/{tenantId}] + bulk | controller/VendorInvoiceController.java:59,75,91,105,116,132,143,163 |
GET /api-docs · /swagger-ui.html — filter-exempt | application.properties:3-4; filter/VendorResolutionFilter.java:39-46 |
An OpenAPI spec for this same API is also served by a jbw-sam Lambda: jbw-sam/src/swaggerjson/swagger.json (28 unique paths, each mirrored with a /stage prefix). Deployed as devapi_swaggerjson — no prod counterpart exists.
Data Flow
Section titled “Data Flow”Every request passes through VendorResolutionFilter (filter/VendorResolutionFilter.java:57-118):
- Resolve the vendor from the API Gateway authorizer’s
principalIdviatblVendorApps(must be ACTIVE, not deleted) →tblVendor(must be active) —:123-135 - Open a
tblVendorTransmissionDetailsrow,REQUIRES_NEW, statusIN_PROGRESS—service/TransmissionLoggingService.java:39-60 - Run the controller
- In
finally: archive the request body (POST/PUT/PATCH only) and response body to S3 attransmissions/{uuid}/request.json/response.json, then mark the rowSUCCESS/FAILED—TransmissionLoggingService.java:62-94
Writes: tblAgreement, tblClaims, tblSTLocationData, tblVendorInvoice / tblVendorInvoiceItem, tblVendorTransmissionDetails. No stored procedures are called — unlike jbw-sam, which drives jbw_st_dataSync.
Configuration
Section titled “Configuration”All via System.getenv, none defaulted:
| Variable | Used by |
|---|---|
AWS_REGION | config/AwsConfig.java:16 |
DATABASE_ENDPOINT · DATABASE_PORT · DATABASE_REGION | config/JpaConfiguration.java:43-45 |
SECRET_NAME | config/JpaConfiguration.java:79 |
COGNITO_SCOPES | config/OpenApiConfig.java:39 |
S3_BUCKET_NAME | utils/S3UploadService.java:48,95 — bucket jb360-api-file-store |
Credentials come from the Lambda execution role (DefaultCredentialsProvider). The DB password is an IAM auth token, never stored — the same bright spot jbw-sam has.
Build & Deploy
Section titled “Build & Deploy”cd jbw-api./mvnw package # → shaded jar, classifier `aws`Defects
Section titled “Defects”Credentials & endpoints
- Commented-out hardcoded DB credentials left in source —
config/JpaConfiguration.java:46-48. - Token URL pinned to staging —
config/OpenApiConfig.java:34hardcodesauth-jb360-stage.jbwarranties.com, with the env-var version commented out at:33. Prod Swagger hands out the stage token URL. Same for the only advertised server (Jb360ApiApplication.java:18). context-path=/stagehardcoded (application.properties:2) — combined with controllers double-mapping{"", "/stage"}, prod requests are served under a/stagepath.- CORS wide open —
allowedOrigins("*")(Jb360ApiApplication.java:35) andAccess-Control-Allow-Origin: *(filter/VendorResolutionFilter.java:146).
Correctness
- The IAM token is generated once at bean creation and
SimpleDriverDataSourceis not pooled (config/JpaConfiguration.java:41,58-73). The token expires ~15 minutes into a warm container’s life — long-lived warm Lambdas will fail to open new connections. @Sizeapplied toIntegerfields —dto/AgreementEquipmentDTO.java(locationId,invoiceItemId,installedEquipmentId).@Sizehas no effect onInteger; Hibernate Validator throwsUnexpectedTypeExceptionif these ever validate.- Contradictory JPA config —
hbm2ddl.auto=none(JpaConfiguration.java:120) butsetGenerateDdl(true)(:105); the configuredjpaVendorAdapter()bean (:112) is immediately overwritten by a bare one (:115-116), discarding the MySQL dialect. - PII in logs and S3 — full request/response/entity objects are logged at INFO throughout
VendorResolutionFilterandTransmissionLoggingService; customer name, address, phone and email reach CloudWatch. MAX_LOG_SIZEis declared but never used (TransmissionLoggingService.java:31) — archived bodies are uncapped despite the constant implying otherwise.- Vendor is never encoded in the S3 key despite being passed in —
utils/S3UploadService.java:53-57,100-104.
Not found here (unlike jbw-sam)
- No SQL injection. Both native queries are fully parameterized via
@Param(repository/DealerRepository.java:17-51); everything else is derived JPA methods or JPQL with named params. A genuine improvement overjbw-sam. - No DLQ concern — synchronous API Gateway invocation only.
Deployed Reality
Section titled “Deployed Reality”Caveats / Known Issues
Section titled “Caveats / Known Issues”- No IaC at all — the single root cause of the prod/dev handler divergence above.
- No tests in the module.
- Unverified: which external vendor platforms hold
tblVendorAppsclient IDs —tblVendor.platformNameis queried (repository/DealerRepository.java:50) but no values live in the repo. Resolve withSELECT vendorName, platformName FROM jbwst.tblVendor. jb360-api-file-store(the PII archive) has versioning off, no lifecycle policy, and no access logging — vendor request/response payloads accumulate indefinitely. Encrypted (AES256 + BucketKey) and fully non-public. See S3 Buckets.
July 2026 Release Delta (f16313c..1ce5b3e)
Section titled “July 2026 Release Delta (f16313c..1ce5b3e)”7 files, +330/−15, all DTOs — a request-validation and OpenAPI-schema hardening pass. No controller, service, repository, entity, or config change.
- Bean Validation (
@Size/@Digits/@NotNull) and@Schema(maxLength=…)added acrossAgreementEquipmentDTO,AgreementRequestDTO,ClaimRequestDTO,InvoiceItemRequest,InvoiceRequest,LocationDTO, aligning DTO constraints to column widths. - New renewal fields —
renewedAgreementId,parentProductCode,customerIdonAgreementRequestDTO;claimSummaryonClaimRequestDTO;invoiceStatus,stCreatedOn,stModifiedOn,isSeaCoast,missingEquipment,isAgreementCreated,possibleJBPlanonInvoiceRequest.