From fb9cb74f9cf5484a5a379c870246332442e2a94d Mon Sep 17 00:00:00 2001 From: Ludy87 Date: Tue, 1 Sep 2026 11:23:50 +0200 Subject: [PATCH] saas: wrap long strings + Jackson import reorder Reflow and wrap long string literals across multiple SaaS modules (logging messages, SQL/JPQL queries, email/content headers, and agreement text) and relocate tools.jackson imports for consistent grouping. These are formatting-only changes to improve line lengths and readability; no functional logic was altered. --- .../ai/controller/AiCreateController.java | 6 ++--- .../AiCreateInternalController.java | 6 ++--- .../service/StripeUsageReportingService.java | 10 ++++--- .../saas/config/SaasDataSourceConfig.java | 12 +++++---- .../saas/config/SaasProjectNotice.java | 5 ++-- .../saas/legal/LegalDocumentRegistry.java | 9 +++---- .../payg/entitlement/EntitlementGuard.java | 4 +-- .../payg/filter/PaygChargeInterceptor.java | 6 +++-- .../saas/payg/filter/PaygOutputExtractor.java | 3 ++- .../payg/filter/PaygResponseBodyWrapper.java | 6 +++-- .../instance/InstanceUsageIngestService.java | 3 ++- .../repository/WalletLedgerRepository.java | 14 +++++----- .../saas/payg/stripe/StripeInvoiceDao.java | 10 +++---- .../api/ProcurementController.java | 8 +++--- .../procurement/legal/AgreementAssembler.java | 27 ++++++++++--------- .../legal/AgreementPdfRenderer.java | 4 +-- .../KeygenEnterpriseLicenseService.java | 11 ++++---- .../license/MockEnterpriseLicenseService.java | 3 ++- .../service/ProcurementService.java | 6 ++--- .../repository/SupabaseUserRepository.java | 3 ++- .../repository/TeamInvitationRepository.java | 16 ++++++----- .../SupabaseAuthenticationFilter.java | 3 ++- .../saas/security/SupabaseSecurityConfig.java | 5 ++-- .../saas/service/SaasTeamService.java | 24 ++++++++++------- .../saas/config/SaasSchemaOwnershipTest.java | 3 ++- .../entitlement/EntitlementGuardTest.java | 6 ++--- .../saas/service/SaasTeamServiceTest.java | 4 +-- .../service/SaasUserAccountServiceTest.java | 6 +++-- 28 files changed, 126 insertions(+), 97 deletions(-) diff --git a/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateController.java b/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateController.java index 77a87a27bb..d56849ff7b 100644 --- a/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateController.java +++ b/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateController.java @@ -26,9 +26,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; - import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; @@ -53,6 +50,9 @@ import stirling.software.saas.payg.model.JobSource; import stirling.software.saas.payg.model.ProcessType; import stirling.software.saas.util.AuthenticationUtils; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + @RestController @Profile("saas") @RequestMapping("/api/v1/ai/create") diff --git a/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateInternalController.java b/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateInternalController.java index 04b8302e7e..ea9ee64145 100644 --- a/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateInternalController.java +++ b/app/saas/src/main/java/stirling/software/saas/ai/controller/AiCreateInternalController.java @@ -14,9 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; - import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; @@ -29,6 +26,9 @@ import stirling.software.saas.ai.service.AiCreateSessionService; import stirling.software.saas.payg.cap.RequiresFeature; import stirling.software.saas.payg.model.FeatureGate; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + @RestController @Profile("saas") @RequestMapping("/api/v1/ai/create/internal") diff --git a/app/saas/src/main/java/stirling/software/saas/billing/service/StripeUsageReportingService.java b/app/saas/src/main/java/stirling/software/saas/billing/service/StripeUsageReportingService.java index ae5d3d943a..f00a3c50bf 100644 --- a/app/saas/src/main/java/stirling/software/saas/billing/service/StripeUsageReportingService.java +++ b/app/saas/src/main/java/stirling/software/saas/billing/service/StripeUsageReportingService.java @@ -12,12 +12,12 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import tools.jackson.databind.ObjectMapper; - import lombok.extern.slf4j.Slf4j; import stirling.software.saas.config.SupabaseConfigurationProperties; +import tools.jackson.databind.ObjectMapper; + /** * Reports per-tenant overage to Stripe Billing Meters via the Supabase {@code meter-usage} Edge * Function. Only credits consumed above the free tier flow through {@link #reportUsageToStripe}. @@ -58,13 +58,15 @@ public class StripeUsageReportingService { if (supabaseUrl == null || supabaseUrl.isEmpty()) { log.error( - "[USAGE-BILLING] supabase.url not configured; cannot report usage. Set SUPABASE_URL."); + "[USAGE-BILLING] supabase.url not configured; cannot report usage. Set" + + " SUPABASE_URL."); return false; } if (!supabaseConfig.isEdgeFunctionConfigured()) { log.error( - "[USAGE-BILLING] Supabase edge function not configured (URL + secret required); cannot report usage."); + "[USAGE-BILLING] Supabase edge function not configured (URL + secret required);" + + " cannot report usage."); return false; } diff --git a/app/saas/src/main/java/stirling/software/saas/config/SaasDataSourceConfig.java b/app/saas/src/main/java/stirling/software/saas/config/SaasDataSourceConfig.java index f16511c77d..033c47333c 100644 --- a/app/saas/src/main/java/stirling/software/saas/config/SaasDataSourceConfig.java +++ b/app/saas/src/main/java/stirling/software/saas/config/SaasDataSourceConfig.java @@ -50,7 +50,8 @@ public class SaasDataSourceConfig { // search_path so native SQL hits stirling_pdf, not the postgres-default public. @Value( - "${spring.datasource.hikari.connection-init-sql:SET search_path TO stirling_pdf, auth, public}") + "${spring.datasource.hikari.connection-init-sql:SET search_path TO stirling_pdf, auth," + + " public}") private String connectionInitSql; @Bean @@ -59,9 +60,9 @@ public class SaasDataSourceConfig { public DataSource saasDataSource() { if (url == null || url.isBlank()) { throw new IllegalStateException( - "spring.datasource.url is required when the saas profile is active. " - + "Set it via application-{profile}.properties (e.g. application-dev.properties) " - + "or via the SPRING_DATASOURCE_URL env var."); + "spring.datasource.url is required when the saas profile is active. Set it via" + + " application-{profile}.properties (e.g. application-dev.properties) or" + + " via the SPRING_DATASOURCE_URL env var."); } HikariConfig config = new HikariConfig(); @@ -79,7 +80,8 @@ public class SaasDataSourceConfig { } log.info( - "Saas DataSource configured (ApplicationName: '{}', max pool: {}, min idle: {}, search_path init: '{}')", + "Saas DataSource configured (ApplicationName: '{}', max pool: {}, min idle: {}," + + " search_path init: '{}')", applicationName, maximumPoolSize, minimumIdle, diff --git a/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java b/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java index 39305fcbcd..8ea5f8edcd 100644 --- a/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java +++ b/app/saas/src/main/java/stirling/software/saas/config/SaasProjectNotice.java @@ -45,8 +45,9 @@ public class SaasProjectNotice { return; } log.info( - "SaaS dev profile: Supabase preview branch {}, ddl-auto={}. Disposable, so Hibernate" - + " is allowed to add the inherited tables the migrations do not create.", + "SaaS dev profile: Supabase preview branch {}, ddl-auto={}. Disposable, so" + + " Hibernate is allowed to add the inherited tables the migrations do not" + + " create.", projectRef, ddlAuto); } diff --git a/app/saas/src/main/java/stirling/software/saas/legal/LegalDocumentRegistry.java b/app/saas/src/main/java/stirling/software/saas/legal/LegalDocumentRegistry.java index 48dd6a1c3b..2111a52112 100644 --- a/app/saas/src/main/java/stirling/software/saas/legal/LegalDocumentRegistry.java +++ b/app/saas/src/main/java/stirling/software/saas/legal/LegalDocumentRegistry.java @@ -13,13 +13,13 @@ import java.util.regex.Pattern; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; - import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + /** * Loads the versioned legal-document registry from {@code legal/manifest.json} on startup and * serves document metadata + rendered markdown from the classpath. @@ -63,8 +63,7 @@ public class LegalDocumentRegistry { d.path("parts"), objectMapper .getTypeFactory() - .constructCollectionType( - List.class, String.class)); + .constructCollectionType(List.class, String.class)); documents.put( id, new LegalDocumentMeta( diff --git a/app/saas/src/main/java/stirling/software/saas/payg/entitlement/EntitlementGuard.java b/app/saas/src/main/java/stirling/software/saas/payg/entitlement/EntitlementGuard.java index 441ebf1220..b9856a17b5 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/entitlement/EntitlementGuard.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/entitlement/EntitlementGuard.java @@ -20,8 +20,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; -import tools.jackson.databind.ObjectMapper; - import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; @@ -40,6 +38,8 @@ import stirling.software.saas.payg.cap.RequiresFeature; import stirling.software.saas.payg.model.FeatureGate; import stirling.software.saas.util.AuthenticationUtils; +import tools.jackson.databind.ObjectMapper; + /** * Hot-path entitlement check. Runs after {@code PaygChargeInterceptor} in the MVC chain and short- * circuits the request before any handler work happens when the team's snapshot is missing one of diff --git a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygChargeInterceptor.java b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygChargeInterceptor.java index b758c89787..2376f1d679 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygChargeInterceptor.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygChargeInterceptor.java @@ -155,7 +155,8 @@ public class PaygChargeInterceptor implements AsyncHandlerInterceptor { this.callsBypassed = Counter.builder("payg.filter.bypassed") .description( - "Manual UI tool calls that skipped openProcess (BillingCategory.BYPASSED)") + "Manual UI tool calls that skipped openProcess" + + " (BillingCategory.BYPASSED)") .register(meterRegistry); this.refundsCounter = Counter.builder("payg.filter.refunds") @@ -429,7 +430,8 @@ public class PaygChargeInterceptor implements AsyncHandlerInterceptor { Long maxBytes = properties.getResponse().getMaxBytes(); if (maxBytes != null && wrapper.bytesWritten() > maxBytes) { log.debug( - "Response size {} exceeds payg.filter.response.max-bytes={}; skipping OUTPUT recording", + "Response size {} exceeds payg.filter.response.max-bytes={}; skipping OUTPUT" + + " recording", wrapper.bytesWritten(), maxBytes); return; diff --git a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygOutputExtractor.java b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygOutputExtractor.java index e413d6c73d..ed913feace 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygOutputExtractor.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygOutputExtractor.java @@ -181,7 +181,8 @@ public class PaygOutputExtractor { // ZipException extends IOException; IllegalArgumentException covers ZipEntry // "MALFORMED" surfaces from zlib. Fail-open: caller still serves the response. log.debug( - "ZIP unpack failed for response body {} ({}); skipping per-PDF OUTPUT recording", + "ZIP unpack failed for response body {} ({}); skipping per-PDF OUTPUT" + + " recording", bodyPath, e.getClass().getSimpleName()); // Close anything we already opened. diff --git a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygResponseBodyWrapper.java b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygResponseBodyWrapper.java index 25450c54f1..735e29aa78 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygResponseBodyWrapper.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/filter/PaygResponseBodyWrapper.java @@ -91,7 +91,8 @@ public class PaygResponseBodyWrapper extends HttpServletResponseWrapper implemen if (writer != null) { // Servlet spec: getOutputStream() and getWriter() are mutually exclusive per request. throw new IllegalStateException( - "getWriter() was already called on this response; cannot switch to getOutputStream()"); + "getWriter() was already called on this response; cannot switch to" + + " getOutputStream()"); } if (teeOut == null) { teeOut = new TeeingServletOutputStream(super.getOutputStream()); @@ -103,7 +104,8 @@ public class PaygResponseBodyWrapper extends HttpServletResponseWrapper implemen public PrintWriter getWriter() throws IOException { if (teeOut != null) { throw new IllegalStateException( - "getOutputStream() was already called on this response; cannot switch to getWriter()"); + "getOutputStream() was already called on this response; cannot switch to" + + " getWriter()"); } if (writer == null) { String encoding = getCharacterEncoding() != null ? getCharacterEncoding() : "UTF-8"; diff --git a/app/saas/src/main/java/stirling/software/saas/payg/instance/InstanceUsageIngestService.java b/app/saas/src/main/java/stirling/software/saas/payg/instance/InstanceUsageIngestService.java index 67e9c581d7..ccc8d21eb9 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/instance/InstanceUsageIngestService.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/instance/InstanceUsageIngestService.java @@ -101,7 +101,8 @@ public class InstanceUsageIngestService { // The cumulative counter went backwards — a reset or tampering. Refuse to credit; don't // advance, so the discrepancy stays visible and a corrected resend can reconcile. log.warn( - "Instance usage regression team={} category={} reported {} < last {}; ignoring.", + "Instance usage regression team={} category={} reported {} < last {};" + + " ignoring.", teamId, category, cumulative, diff --git a/app/saas/src/main/java/stirling/software/saas/payg/repository/WalletLedgerRepository.java b/app/saas/src/main/java/stirling/software/saas/payg/repository/WalletLedgerRepository.java index f307d54241..69dea3a1ca 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/repository/WalletLedgerRepository.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/repository/WalletLedgerRepository.java @@ -69,7 +69,8 @@ public interface WalletLedgerRepository extends JpaRepository= :periodStart" - + " AND e.occurredAt < :periodEnd") + "SELECT COALESCE(SUM(e.amountUnits), 0) FROM WalletLedgerEntry e WHERE e.teamId =" + + " :teamId AND e.entryType IN" + + " (stirling.software.saas.payg.model.LedgerEntryType.DEBIT," + + " stirling.software.saas.payg.model.LedgerEntryType.REFUND) AND e.occurredAt >=" + + " :periodStart AND e.occurredAt < :periodEnd") long sumPeriodNetBillable( @Param("teamId") Long teamId, @Param("periodStart") LocalDateTime periodStart, diff --git a/app/saas/src/main/java/stirling/software/saas/payg/stripe/StripeInvoiceDao.java b/app/saas/src/main/java/stirling/software/saas/payg/stripe/StripeInvoiceDao.java index 8e6b59f9ce..e6696b874e 100644 --- a/app/saas/src/main/java/stirling/software/saas/payg/stripe/StripeInvoiceDao.java +++ b/app/saas/src/main/java/stirling/software/saas/payg/stripe/StripeInvoiceDao.java @@ -175,12 +175,10 @@ public class StripeInvoiceDao { } String placeholders = invoiceIds.stream().map(id -> "?").collect(Collectors.joining(",")); String sql = - "SELECT i.id AS invoice_id," - + " (SELECT SUM((l->>'quantity')::int)" - + " FROM jsonb_array_elements(COALESCE(i.lines->'data', '[]'::jsonb)) AS l" - + " WHERE l->'price'->'recurring'->>'usage_type' = 'metered') AS qty" - + " FROM stripe.invoices i" - + " WHERE i.id IN (" + "SELECT i.id AS invoice_id, (SELECT SUM((l->>'quantity')::int) FROM" + + " jsonb_array_elements(COALESCE(i.lines->'data', '[]'::jsonb)) AS l WHERE" + + " l->'price'->'recurring'->>'usage_type' = 'metered') AS qty FROM" + + " stripe.invoices i WHERE i.id IN (" + placeholders + ")"; try { diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/api/ProcurementController.java b/app/saas/src/main/java/stirling/software/saas/procurement/api/ProcurementController.java index c08778bdbc..c633f8686a 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/api/ProcurementController.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/api/ProcurementController.java @@ -18,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import tools.jackson.databind.ObjectMapper; - import io.swagger.v3.oas.annotations.Hidden; import jakarta.servlet.http.HttpServletRequest; @@ -43,6 +41,8 @@ import stirling.software.saas.procurement.pricing.QuoteLineItem; import stirling.software.saas.procurement.service.ProcurementService; import stirling.software.saas.util.AuthenticationUtils; +import tools.jackson.databind.ObjectMapper; + /** * The enterprise procurement journey for a linked team: read the deal snapshot, start/extend a * (mock-licensed) trial, build a server-priced quote, and accept it. Stripe checkout itself is a @@ -449,7 +449,7 @@ public class ProcurementController { .header( HttpHeaders.CONTENT_DISPOSITION, "attachment;" - + " filename=\"stirling-enterprise-agreement.pdf\"") + + " filename=\"stirling-enterprise-agreement.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(pdf)) .orElseGet(() -> ResponseEntity.notFound().build()); @@ -472,7 +472,7 @@ public class ProcurementController { .header( HttpHeaders.CONTENT_DISPOSITION, "attachment;" - + " filename=\"stirling-enterprise-agreement.pdf\"") + + " filename=\"stirling-enterprise-agreement.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(pdf)) .orElseGet(() -> ResponseEntity.notFound().build()); diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementAssembler.java b/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementAssembler.java index e75f3cbb32..f19fc2a32c 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementAssembler.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementAssembler.java @@ -10,8 +10,6 @@ import java.util.Map; import org.springframework.stereotype.Service; -import tools.jackson.databind.ObjectMapper; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,6 +20,8 @@ import stirling.software.saas.procurement.pricing.ProcurementPricingService; import stirling.software.saas.procurement.pricing.QuoteConfig; import stirling.software.saas.procurement.pricing.QuoteLineItem; +import tools.jackson.databind.ObjectMapper; + /** * Builds the full Stirling Enterprise Agreement for a specific quote: the static MSA (Part A) and * DPA (Part C) from the {@link LegalDocumentRegistry}, with the dynamic Order Form (Part B) @@ -159,7 +159,8 @@ public class AgreementAssembler { row( sb, "Term", - "{{term_years}} year(s) · term discount {{term_discount_pct}} on committed processing"); + "{{term_years}} year(s) · term discount {{term_discount_pct}} on committed" + + " processing"); row(sb, "Itemized services", itemizedServices(quote)); row(sb, "Annual Fee (year 1)", "{{annual_fee_y1}}"); row(sb, "Total (paid in advance)", "{{contract_total}}"); @@ -174,12 +175,13 @@ public class AgreementAssembler { sb, "Data schedule", "First 25 MB per file included; each additional 25 MB or part thereof (decimal MB," - + " rounded up per file, measured once at ingestion) draws down 1 PDF Process." - + " Frozen for the Term (MSA §3.5)."); + + " rounded up per file, measured once at ingestion) draws down 1 PDF Process." + + " Frozen for the Term (MSA §3.5)."); row( sb, "Drawdown schedule", - "{{posture}}: {{processes_per_pdf}} PDF Processes per PDF (MSA §3.3, frozen for the Term)"); + "{{posture}}: {{processes_per_pdf}} PDF Processes per PDF (MSA §3.3, frozen for the" + + " Term)"); row( sb, "Enhanced IP Protection", @@ -187,12 +189,13 @@ public class AgreementAssembler { row(sb, "Standard terms", "SSO, SCIM, RBAC, and audit logs included."); sb.append( - "\n**Itemized services menu (include as elected):** Self-hosted deployment $12,000/yr" - + " · Air-gapped deployment $36,000/yr · Dedicated SE/CSM $30,000/yr · Enhanced IP" - + " Protection (patent coverage, Section 7.3) 5% of committed processing fees ·" - + " Onboarding & training $7,500 one-time · Quarterly business reviews $8,000/yr." - + " Baseline IP indemnification (copyright, trademark, trade secret) is included at" - + " no charge.\n\n"); + "\n" + + "**Itemized services menu (include as elected):** Self-hosted deployment" + + " $12,000/yr · Air-gapped deployment $36,000/yr · Dedicated SE/CSM $30,000/yr" + + " · Enhanced IP Protection (patent coverage, Section 7.3) 5% of committed" + + " processing fees · Onboarding & training $7,500 one-time · Quarterly" + + " business reviews $8,000/yr. Baseline IP indemnification (copyright," + + " trademark, trade secret) is included at no charge.\n\n"); sb.append( "**Signatures.** By signing, each signatory represents they have authority to bind" + " their Party. Signatures delivered electronically or in counterparts are" diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementPdfRenderer.java b/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementPdfRenderer.java index 8204df6514..14a4611e0f 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementPdfRenderer.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/legal/AgreementPdfRenderer.java @@ -45,8 +45,8 @@ public class AgreementPdfRenderer { } catch (Exception e) { org.slf4j.LoggerFactory.getLogger(AgreementPdfRenderer.class) .warn( - "[legal] agreement PDF render unavailable; recording signature without a" - + " stored PDF: {}", + "[legal] agreement PDF render unavailable; recording signature without" + + " a stored PDF: {}", e.getMessage()); return null; } diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/license/KeygenEnterpriseLicenseService.java b/app/saas/src/main/java/stirling/software/saas/procurement/license/KeygenEnterpriseLicenseService.java index a1c09a9cb9..372d2a20c0 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/license/KeygenEnterpriseLicenseService.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/license/KeygenEnterpriseLicenseService.java @@ -16,13 +16,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; - import lombok.extern.slf4j.Slf4j; import stirling.software.saas.procurement.config.KeygenConfigurationProperties; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + /** * Real {@link EnterpriseLicenseService}: manages the team's enterprise licence directly against the * Keygen API (the "call Keygen from Java" direction), rather than via the Supabase edge functions @@ -53,8 +53,9 @@ public class KeygenEnterpriseLicenseService implements EnterpriseLicenseService // caught at startup, not at the first trial/provision. (Flag off → Mock bean, never here.) if (!config.isConfigured()) { throw new IllegalStateException( - "stirling.keygen.enabled=true but Keygen is not fully configured — set " - + "STIRLING_KEYGEN_ACCOUNT_ID / _API_TOKEN / _POLICY_ID, or turn the flag off"); + "stirling.keygen.enabled=true but Keygen is not fully configured — set" + + " STIRLING_KEYGEN_ACCOUNT_ID / _API_TOKEN / _POLICY_ID, or turn the flag" + + " off"); } } diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/license/MockEnterpriseLicenseService.java b/app/saas/src/main/java/stirling/software/saas/procurement/license/MockEnterpriseLicenseService.java index 511d67cd08..ab715d1355 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/license/MockEnterpriseLicenseService.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/license/MockEnterpriseLicenseService.java @@ -55,7 +55,8 @@ public class MockEnterpriseLicenseService implements EnterpriseLicenseService { String ref = existingRef != null ? existingRef : "mock-annual-" + UUID.randomUUID(); // Owner email is deliberately not logged — it's PII and adds nothing to the mock trace. log.info( - "[procurement][mock-license] issue annual team={} seats={} volume={} deployment={} expires={} ref={} upgrade={}", + "[procurement][mock-license] issue annual team={} seats={} volume={} deployment={}" + + " expires={} ref={} upgrade={}", teamId, ent.seats(), ent.volume(), diff --git a/app/saas/src/main/java/stirling/software/saas/procurement/service/ProcurementService.java b/app/saas/src/main/java/stirling/software/saas/procurement/service/ProcurementService.java index 26bfb733dd..388b95eef3 100644 --- a/app/saas/src/main/java/stirling/software/saas/procurement/service/ProcurementService.java +++ b/app/saas/src/main/java/stirling/software/saas/procurement/service/ProcurementService.java @@ -10,9 +10,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; - import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.enumeration.TeamRole; @@ -38,6 +35,9 @@ import stirling.software.saas.procurement.repository.ProcurementQuoteRepository; import stirling.software.saas.service.SaasTeamService; import stirling.software.saas.util.LogRedactionUtils; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + /** * Orchestrates a linked team's procurement journey: start a (mock-licensed) trial, build a * server-priced quote, and accept it. Stripe checkout itself lives in a Supabase edge function the diff --git a/app/saas/src/main/java/stirling/software/saas/repository/SupabaseUserRepository.java b/app/saas/src/main/java/stirling/software/saas/repository/SupabaseUserRepository.java index 4e61de4145..dc38efd325 100644 --- a/app/saas/src/main/java/stirling/software/saas/repository/SupabaseUserRepository.java +++ b/app/saas/src/main/java/stirling/software/saas/repository/SupabaseUserRepository.java @@ -21,7 +21,8 @@ public interface SupabaseUserRepository extends JpaRepository findByCreatedAtBeforeAndIsAnonymousTrue( @Param("cutoffDate") LocalDateTime cutoffDate); diff --git a/app/saas/src/main/java/stirling/software/saas/repository/TeamInvitationRepository.java b/app/saas/src/main/java/stirling/software/saas/repository/TeamInvitationRepository.java index 38877c56b6..ce1b6e551a 100644 --- a/app/saas/src/main/java/stirling/software/saas/repository/TeamInvitationRepository.java +++ b/app/saas/src/main/java/stirling/software/saas/repository/TeamInvitationRepository.java @@ -23,7 +23,8 @@ public interface TeamInvitationRepository extends JpaRepository findByInvitationToken(@Param("token") String token); /** @@ -33,7 +34,8 @@ public interface TeamInvitationRepository extends JpaRepository findByInviteeEmail(@Param("email") String email); /** @@ -43,8 +45,8 @@ public interface TeamInvitationRepository extends JpaRepository :now") + "SELECT ti FROM TeamInvitation ti JOIN FETCH ti.team JOIN FETCH ti.inviter WHERE" + + " ti.inviteeEmail = :email AND ti.status = 'PENDING' AND ti.expiresAt > :now") List findPendingInvitationsByEmail( @Param("email") String email, @Param("now") LocalDateTime now); @@ -64,7 +66,8 @@ public interface TeamInvitationRepository extends JpaRepository findByInviterUserId(@Param("inviterUserId") Long inviterUserId); /** @@ -75,7 +78,8 @@ public interface TeamInvitationRepository extends JpaRepository new AuthenticationFailureException( - "User creation conflict, but unable to find existing user", + "User creation conflict, but unable to find existing" + + " user", dup)); } } diff --git a/app/saas/src/main/java/stirling/software/saas/security/SupabaseSecurityConfig.java b/app/saas/src/main/java/stirling/software/saas/security/SupabaseSecurityConfig.java index ea1c9bca9b..afb7a01b7a 100644 --- a/app/saas/src/main/java/stirling/software/saas/security/SupabaseSecurityConfig.java +++ b/app/saas/src/main/java/stirling/software/saas/security/SupabaseSecurityConfig.java @@ -174,8 +174,9 @@ public class SupabaseSecurityConfig { String issuerError = validateIssuer(issuer); if (issuerError != null) { log.warn( - "{} saas profile is active but JWTs cannot be validated. Set SAAS_DB_PROJECT_REF" - + " (or app.supabase.issuer) in application-saas.properties or via env.", + "{} saas profile is active but JWTs cannot be validated. Set" + + " SAAS_DB_PROJECT_REF (or app.supabase.issuer) in" + + " application-saas.properties or via env.", issuerError); // Build a decoder that will reject every token; failing closed is safer than failing // open when configuration is incomplete. diff --git a/app/saas/src/main/java/stirling/software/saas/service/SaasTeamService.java b/app/saas/src/main/java/stirling/software/saas/service/SaasTeamService.java index d81e0d9ee7..e1565b8c6d 100644 --- a/app/saas/src/main/java/stirling/software/saas/service/SaasTeamService.java +++ b/app/saas/src/main/java/stirling/software/saas/service/SaasTeamService.java @@ -274,7 +274,8 @@ public class SaasTeamService { existingUser -> { if (hasPaidSubscription(existingUser)) { throw new IllegalArgumentException( - "Cannot invite paid users to teams. Only team leaders manage billing."); + "Cannot invite paid users to teams. Only team leaders" + + " manage billing."); } // Check if already a member @@ -324,7 +325,8 @@ public class SaasTeamService { Team userTeam = user.getTeam(); if (userTeam == null || !hasActivePaidSubscription(userTeam)) { log.warn( - "User {} joined team {} but team has no active subscription - not granting PRO role", + "User {} joined team {} but team has no active subscription - not granting PRO" + + " role", user.getUsername(), userTeam != null ? userTeam.getName() : "null"); return; @@ -386,7 +388,8 @@ public class SaasTeamService { // Validate: user doesn't have paid subscription if (hasPaidSubscription(acceptingUser)) { throw new IllegalArgumentException( - "Cannot join team with active paid subscription. Cancel your subscription first."); + "Cannot join team with active paid subscription. Cancel your subscription" + + " first."); } // Validate: team has available seats @@ -683,9 +686,9 @@ public class SaasTeamService { // Check if Supabase is configured if (!supabaseConfig.isEdgeFunctionConfigured()) { log.warn( - "Supabase integration not configured, skipping email send. " - + "Please configure supabase.edgeFunctionUrl and supabase.edgeFunctionSecret " - + "in application properties."); + "Supabase integration not configured, skipping email send. Please configure" + + " supabase.edgeFunctionUrl and supabase.edgeFunctionSecret in" + + " application properties."); return; } @@ -748,7 +751,8 @@ public class SaasTeamService { if (maxSeats < currentSeatsUsed) { int excessMembers = currentSeatsUsed - maxSeats; log.warn( - "Team {} reducing seats from {} to {} with {} current members. Removing {} excess members.", + "Team {} reducing seats from {} to {} with {} current members. Removing {}" + + " excess members.", teamId, currentMaxSeats, maxSeats, @@ -811,7 +815,8 @@ public class SaasTeamService { downgradeUserToFree(userToRemove); log.info( - "User {} removed from team {} and migrated to personal team due to seat reduction", + "User {} removed from team {} and migrated to personal team due to seat" + + " reduction", userToRemove.getId(), teamId); } @@ -886,7 +891,8 @@ public class SaasTeamService { if (hasActiveSubscription) { log.info( - "User {} has active subscription (trial or paid), maintaining PRO access after leaving team", + "User {} has active subscription (trial or paid), maintaining PRO" + + " access after leaving team", refetchedUser.getUsername()); return; // Keep PRO access } diff --git a/app/saas/src/test/java/stirling/software/saas/config/SaasSchemaOwnershipTest.java b/app/saas/src/test/java/stirling/software/saas/config/SaasSchemaOwnershipTest.java index e07b1042e7..439b245182 100644 --- a/app/saas/src/test/java/stirling/software/saas/config/SaasSchemaOwnershipTest.java +++ b/app/saas/src/test/java/stirling/software/saas/config/SaasSchemaOwnershipTest.java @@ -121,7 +121,8 @@ class SaasSchemaOwnershipTest { will not exist on a fresh preview branch. - HIBERNATE_MANAGED: only correct for a table inherited from the \ self-hosted app that no Supabase migration creates. - Offending tables -> entities: %s""" + Offending tables -> entities: %s\ + """ .formatted( undeclared.stream() .map(t -> t + " (" + mapped.get(t) + ")") diff --git a/app/saas/src/test/java/stirling/software/saas/payg/entitlement/EntitlementGuardTest.java b/app/saas/src/test/java/stirling/software/saas/payg/entitlement/EntitlementGuardTest.java index a9ca7642a6..dad953d61e 100644 --- a/app/saas/src/test/java/stirling/software/saas/payg/entitlement/EntitlementGuardTest.java +++ b/app/saas/src/test/java/stirling/software/saas/payg/entitlement/EntitlementGuardTest.java @@ -28,9 +28,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.method.HandlerMethod; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; - import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -45,6 +42,9 @@ import stirling.software.saas.payg.model.FeatureGate; import stirling.software.saas.payg.model.FeatureSet; import stirling.software.saas.security.EnhancedJwtAuthenticationToken; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + /** * Pure-Mockito tests for {@link EntitlementGuard}. Covers the four decision-matrix cells: anonymous * billable → 401, anonymous manual → pass, authenticated FULL → pass, authenticated DEGRADED for a diff --git a/app/saas/src/test/java/stirling/software/saas/service/SaasTeamServiceTest.java b/app/saas/src/test/java/stirling/software/saas/service/SaasTeamServiceTest.java index fc1fbcb0ab..de28d2be0a 100644 --- a/app/saas/src/test/java/stirling/software/saas/service/SaasTeamServiceTest.java +++ b/app/saas/src/test/java/stirling/software/saas/service/SaasTeamServiceTest.java @@ -1433,8 +1433,8 @@ class SaasTeamServiceTest { assertThatThrownBy(() -> service.acceptInvitation(TOKEN, joiner)) .isInstanceOf(IllegalStateException.class) .hasMessage( - "Revoke linked self-hosted instances on this team before joining another" - + " team."); + "Revoke linked self-hosted instances on this team before joining" + + " another team."); // Guard fires before any team mutation. verify(membershipRepository, never()).delete(any()); diff --git a/app/saas/src/test/java/stirling/software/saas/service/SaasUserAccountServiceTest.java b/app/saas/src/test/java/stirling/software/saas/service/SaasUserAccountServiceTest.java index 5bf0a33e4c..3b1a7a88a0 100644 --- a/app/saas/src/test/java/stirling/software/saas/service/SaasUserAccountServiceTest.java +++ b/app/saas/src/test/java/stirling/software/saas/service/SaasUserAccountServiceTest.java @@ -178,7 +178,8 @@ class SaasUserAccountServiceTest { @Test @DisplayName( - "downgrades a PRO user whose team is personal (personal team is not a shared PRO source)") + "downgrades a PRO user whose team is personal (personal team is not a shared PRO" + + " source)") void proWithPersonalTeam_isDowngraded() { User u = userWithRole(Role.PRO_USER.getRoleId()); Team t = team(7L, "alice-personal"); @@ -414,7 +415,8 @@ class SaasUserAccountServiceTest { @Test @DisplayName( - "does not overwrite username/email when the email is blank, but still promotes the type") + "does not overwrite username/email when the email is blank, but still promotes the" + + " type") void blankEmail_keepsUsername() { SupabaseUser su = supabaseUser(false); User u = anonymousLocalUser();