Skip to content

jbw-api — Vendor Integration API (Spring Boot / Java 17)

Module path: st-integration/jbw-api/ · deployed as prodapi_jb360dealer / devapi_jb360dealer · fronted by JB360IntegrationAPI at api-jb360.jbwarranties.com · no IaC — manual jar uploads

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.

StackSpring Boot 3.5.4, Java 17, Spring Data JPA / Hibernate (MySQL), MapStruct 1.5.5, Lombok
Lambda adapteraws-serverless-java-container-springboot3 2.0.1
Entry pointStreamLambdaHandler implements RequestStreamHandlersrc/main/java/com/maktix/jb/api/StreamLambdaHandler.java:16-26
TriggerAPI Gateway proxy only — no SQS, no schedule, no S3 events
Databasejbwst MySQL via RDS IAM auth token (no stored password) — config/JpaConfiguration.java:43-73
Docsspringdoc-openapi 2.8.6, Swagger UI vendored into src/main/resources/static/swagger-ui/
Deploymvn package → shaded jar (classifier aws) → uploaded by hand

All controllers are mapped @RequestMapping({ "", "/stage" }) and annotated @SecurityRequirement(name = "cognitoAuth").

Method + pathSource
GET /dealer/{dealerid}/agreements · /{invoiceid} · /{locationid} · /modified · /createdcontroller/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 / tenantIdcontroller/LocationController.java:54,69,85,107,135,163
POST / PUT / GET /dealer/{dealerid}/invoices[/{invoiceid}|/{id}|/{tenantId}] + bulkcontroller/VendorInvoiceController.java:59,75,91,105,116,132,143,163
GET /api-docs · /swagger-ui.htmlfilter-exemptapplication.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_swaggerjsonno prod counterpart exists.

Every request passes through VendorResolutionFilter (filter/VendorResolutionFilter.java:57-118):

  1. Resolve the vendor from the API Gateway authorizer’s principalId via tblVendorApps (must be ACTIVE, not deleted) → tblVendor (must be active) — :123-135
  2. Open a tblVendorTransmissionDetails row, REQUIRES_NEW, status IN_PROGRESSservice/TransmissionLoggingService.java:39-60
  3. Run the controller
  4. In finally: archive the request body (POST/PUT/PATCH only) and response body to S3 at transmissions/{uuid}/request.json / response.json, then mark the row SUCCESS / FAILEDTransmissionLoggingService.java:62-94

Writes: tblAgreement, tblClaims, tblSTLocationData, tblVendorInvoice / tblVendorInvoiceItem, tblVendorTransmissionDetails. No stored procedures are called — unlike jbw-sam, which drives jbw_st_dataSync.

All via System.getenv, none defaulted:

VariableUsed by
AWS_REGIONconfig/AwsConfig.java:16
DATABASE_ENDPOINT · DATABASE_PORT · DATABASE_REGIONconfig/JpaConfiguration.java:43-45
SECRET_NAMEconfig/JpaConfiguration.java:79
COGNITO_SCOPESconfig/OpenApiConfig.java:39
S3_BUCKET_NAMEutils/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.

cd jbw-api
./mvnw package # → shaded jar, classifier `aws`

Credentials & endpoints

  • Commented-out hardcoded DB credentials left in source — config/JpaConfiguration.java:46-48.
  • Token URL pinned to stagingconfig/OpenApiConfig.java:34 hardcodes auth-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=/stage hardcoded (application.properties:2) — combined with controllers double-mapping {"", "/stage"}, prod requests are served under a /stage path.
  • CORS wide openallowedOrigins("*") (Jb360ApiApplication.java:35) and Access-Control-Allow-Origin: * (filter/VendorResolutionFilter.java:146).

Correctness

  • The IAM token is generated once at bean creation and SimpleDriverDataSource is 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.
  • @Size applied to Integer fieldsdto/AgreementEquipmentDTO.java (locationId, invoiceItemId, installedEquipmentId). @Size has no effect on Integer; Hibernate Validator throws UnexpectedTypeException if these ever validate.
  • Contradictory JPA confighbm2ddl.auto=none (JpaConfiguration.java:120) but setGenerateDdl(true) (:105); the configured jpaVendorAdapter() 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 VendorResolutionFilter and TransmissionLoggingService; customer name, address, phone and email reach CloudWatch.
  • MAX_LOG_SIZE is 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 over jbw-sam.
  • No DLQ concern — synchronous API Gateway invocation only.
  • 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 tblVendorApps client IDs — tblVendor.platformName is queried (repository/DealerRepository.java:50) but no values live in the repo. Resolve with SELECT 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 across AgreementEquipmentDTO, AgreementRequestDTO, ClaimRequestDTO, InvoiceItemRequest, InvoiceRequest, LocationDTO, aligning DTO constraints to column widths.
  • New renewal fieldsrenewedAgreementId, parentProductCode, customerId on AgreementRequestDTO; claimSummary on ClaimRequestDTO; invoiceStatus, stCreatedOn, stModifiedOn, isSeaCoast, missingEquipment, isAgreementCreated, possibleJBPlan on InvoiceRequest.