feat/fix: AI credits, image validation, per-tenant moderation, atomic persistence - #1
Closed
popsman01 wants to merge 4 commits into
Closed
feat/fix: AI credits, image validation, per-tenant moderation, atomic persistence#1popsman01 wants to merge 4 commits into
popsman01 wants to merge 4 commits into
Conversation
…#627) - Add BillingService.deductCreditsForTokens(userId, tokens) — 1 credit per token - Update AIService.generateContent to return {text, totalTokens} and deduct credits post-call using res.usageMetadata.totalTokenCount - Remove requireCredits('ai:generate') pre-call middleware from /ai/analyze-image - Update workers to destructure {text} from generateContent result - Add 6 tests: short/long token deduction, insufficient credits, no-userId skip
- Add ValidationError class (extends GeminiServiceError, code: VALIDATION_ERROR) - Reject images > 20 MB before the API call with a clear error message - Add gemini-validation Jest project to bypass the global geminiService mock - Add 3 tests: below limit, at limit, above limit
…#629) - Add ConfigKey.MODERATION_SENSITIVITY to DynamicConfigService - Update getSensitivity(tenantId?) to check tenant:ID:MODERATION_SENSITIVITY in DynamicConfigService before falling back to env var - Thread tenantId through ModerationService.moderate(text, tenantId?) - Add 4 tests: high/low tenant thresholds, env fallback, cross-tenant diff
- Import withTransaction and TxClient in workers/index.ts
- Update persistAIResult to accept optional TxClient and use upsert
keyed on jobId (update:{} no-op prevents duplicates on retry)
- Wrap persistence in withTransaction in all 5 AI processors
- Add 3 tests: success path, crash propagation, no-duplicate on retry
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR resolves four issues across the AI and billing subsystems.
hman38705#627 — Deduct credits proportional to actual AI token usage
Files:
backend/src/services/AIService.ts,backend/src/services/BillingService.ts,backend/src/routes/ai.ts,backend/src/workers/index.tsBillingService.deductCreditsForTokens(userId, tokens)— 1 credit per token consumed.AIService.generateContentto return{ text, totalTokens }and readres.usageMetadata.totalTokenCountfrom the Gemini response.requireCredits('ai:generate')pre-call middleware on/ai/analyze-image.generateCaption,generateReplies,analyzeContent, workers) to destructure{ text }from the new return type.Closes hman38705#627
hman38705#628 — Validate image size before sending to Gemini API
Files:
backend/src/services/geminiService.ts,backend/jest.config.jsValidationErrorclass (extendsGeminiServiceError, codeVALIDATION_ERROR).analyzeImagenow computes the decoded byte size from the base64 string (accounting for padding) and throwsValidationErrorfor images exceeding the 20 MB limit before any API call is made.gemini-validationJest project that bypasses the globalgeminiServicestub so tests run against the real implementation.Closes hman38705#628
hman38705#629 — Support per-tenant moderation sensitivity thresholds
Files:
backend/src/services/ModerationService.ts,backend/src/services/DynamicConfigService.tsConfigKey.MODERATION_SENSITIVITYto theDynamicConfigServiceenum.getSensitivity(tenantId?)now checksdynamicConfigService.get(tenant:${tenantId}:MODERATION_SENSITIVITY)first, then falls back toprocess.env.MODERATION_SENSITIVITY.tenantIdthroughModerationService.moderate(text, tenantId?)— fully backward-compatible (parameter is optional).Closes hman38705#629
hman38705#630 — Persist AI generation result inside the job transaction
Files:
backend/src/workers/index.tswithTransactionandTxClientfrombackend/src/lib/transaction.ts.persistAIResultnow accepts an optionalTxClientand uses upsert keyed onjobId(update: {}is a no-op) — prevents duplicate records when a retried job re-runs after a crash between generation and persistence.generate-caption,generate-hashtags,generate-content,analyze-sentiment,translate-content) now callwithTransaction(async (tx) => persistAIResult(job, output, tx)).Closes hman38705#630
Testing
All new tests pass:
aiProportionalCredits.test.ts— 6 passinggeminiImageValidation.test.ts— 3 passingmoderationPerTenant.test.ts— 4 passingaiAtomicPersist.test.ts— 3 passing