AI credit purchases (money -> credits via PayU) never generated an
invoice, even though the exact same infrastructure already works for
TraceCoin purchases in main.rs's generate_purchase_invoice. Spending
credits (try_reserve_credits/capture) correctly does NOT get an
invoice - only real-money purchases do, matching existing TraceCoin
behavior.
- invoices.payment_id had a hard FK to payments(id) only, which blocks
using it for ai_credit_orders(id) rows. Postgres has no polymorphic
FK; dropped the constraint (invoice_type already says which table
payment_id points into) via a new migration rather than editing the
original invoices migration.
- Added generate_ai_credit_invoice in ai_credits.rs, called from
verify_order after a successful PayU payment - mirrors main.rs's
pattern exactly (same non-blocking failure handling, same seller
details via the now pub(crate) seller_details(), invoice_type
'AI_CREDIT_PURCHASE').
- Caught a real bug while writing this: order.amount_inr is the
POST-discount final price, but NewInvoice.discount_amount is
subtracted again inside compute_totals (subtotal - discount) - using
amount_inr directly as unit_price_paise would have double-subtracted
the discount. Reconstructed the pre-discount price
(amount_inr + discount_applied) for the line item instead.
Applied the FK-drop migration to nxtgauge_test and prod; verified the
constraint is gone and user_id's FK is untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds applicable_roles TEXT[] to ai_credit_packages (empty = visible to
every role, matching today's behavior for the 4 existing packages -
nothing changes for them until an admin opts them into specific
roles). Mirrors pricing_packages' existing role_key convention, as an
array since one AI package can reasonably apply to several roles at
once.
- GET /api/ai-credits (list_packages) now accepts optional auth (via a
local MaybeAuthUser wrapper, since AuthUser doesn't implement axum's
optional-extraction trait) and filters out packages not applicable
to the viewer's roles. Anonymous viewers only see role-unrestricted
packages.
- POST /api/ai-credits/order (create_order) re-validates role
eligibility server-side too, not just in the listing - closes off
a logged-in user buying a package never shown to them.
- Admin CRUD (GET/POST /api/admin/ai-credits/packages,
PATCH .../{id}) now reads/writes applicable_roles.
Applied to nxtgauge_test and prod; verified column + index created,
existing 4 packages default to empty (all roles).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Asked to review Tracecoin and AI implementation safety. Found and fixed
two exploitable TOCTOU races, plus a data-integrity bug:
1. apps/payments/src/main.rs::verify_payment — the PayU success callback
is called directly by the client (not a server-to-server webhook), so
a user fully controls how many times they replay a valid success
payload. The payment "is it still PENDING" check and the "mark
SUCCESS + credit wallet" write were separate, non-transactional
queries — concurrent replays could both pass the check before either
commits, double- (or N-times-) crediting the wallet for one real
payment. Now wrapped in a single transaction with
`SELECT ... FOR UPDATE` on the payments row, so a second concurrent
call blocks until the first commits, then correctly sees the row is
no longer PENDING (Postgres re-evaluates the WHERE clause via
EvalPlanQual after the lock is granted).
2. crates/db/src/models/ai/repository.rs — UserAiSubscriptionRepository
had the exact same shape of bug: apps/users/src/ai/credits.rs::
charge_feature read the subscription, checked daily-limit and credit
balance, THEN issued two separate unconditional `UPDATE ... SET x =
x + $1` statements with no WHERE guard on the balance. N concurrent
requests from one user all pass the check before any deduction
lands, running up unlimited LLM API spend (this endpoint is called
before/around real LiteLLM calls, so the cost is real). Added
UserAiSubscriptionRepository::try_charge — a single conditional
UPDATE that checks the daily limit and credit balance and deducts
atomically, returning None (mapped to the existing error types) if
either check fails.
3. apps/cron/src/tasks/auto_apply.rs — daily_actions_used was being
incremented twice per auto-applied job (once in the credit-deduct
UPDATE, once more in a second, redundant UPDATE right after) —
silently halving job seekers' effective daily auto-apply limit.
Removed the redundant second UPDATE.
Also added non-negative CHECK constraints directly to the live
database (tracecoin_wallets.balance/reserved,
user_ai_subscriptions.daily_actions_used/monthly_credits_used/
purchased_credits_used) as defense in depth — belt-and-suspenders in
case a future code path reintroduces a similar bug.
Seeds one TRACECOIN_BUNDLE package per role (100 Tracecoins / ₹250,
placeholder pricing per your instruction) so the purchase flow
(CreditsPage.tsx -> GET /api/packages, filtered client-side by
role_key) has something to show instead of "No packages available for
your role," and so create_order/verify_payment/invoice generation can
actually be exercised end-to-end. Idempotent (WHERE NOT EXISTS guard)
so it's safe against the custom db-migrate runner re-executing every
file on every deploy. Adjust real pricing later via the existing admin
CRUD (POST/PATCH /api/packages) — no new endpoint needed, it already
works.
Also fixes a unit bug introduced in the invoice wiring last commit:
payments.amount_inr is already paise (copied straight from
pricing_packages.price_inr, which is paise despite the name — PayU
order creation divides it by 100 via paise_to_rupee_string), but the
invoice line item multiplied it by 100 again, making every generated
invoice show a 100x inflated amount.
crates/invoice (InvoiceService, GST computation, HTML rendering) was
fully built but never wired to anything and never had its tables —
invoices/invoice_line_items/billing_profiles/invoice_number_seq never
existed in any active migration (same root cause as everything else
this session: only in scripts/init-db.sql, which the real db-migrate
job never runs). Created them, matching InvoiceService's actual
columns exactly rather than init-db.sql's older, simpler invoices
shape.
Fixed a real bug in InvoiceService::create while at it: four money
fields (total, and three line-item amounts) were bound as i64 against
columns/read-models that are i32 everywhere else — would have failed
every insert with a Postgres type mismatch the first time this code
ever actually ran against a real table.
Wired invoice generation into apps/payments' PayU verify_payment
handler (the actual success callback) — right after the wallet is
credited, a one-line-item GST invoice is generated from the purchased
package and PayU's billing fields (firstname/email/phone), using a
new INVOICE_SELLER_* env-configurable seller identity. Generation
failures are logged, not surfaced to the buyer, since the payment and
wallet credit have already succeeded by that point.
Also added the missing user-facing endpoints to fetch what got
generated: GET /api/payments/invoices (list) and
GET /api/payments/invoices/{id} (detail + line items) — previously
only admin-side invoice viewing existed.
NOTE: the frontend (nxtgauge-frontend-solid, a separate repo) has an
existing invoice-viewing page at src/routes/dashboard/wallet/invoices/
but it calls /wallet/me/invoices (no /api/ prefix) via a different,
apparently-dead API helper (api.get, not apiFetch) that every other
live page avoids — same dead-code pattern as the earlier apps/leads
discovery. The live purchase flow (CreditsPage.tsx) has no invoice UI
at all yet. Not fixed here since it's out of this repo's scope this
session — flagging for a frontend pass.
Adds a DB-trigger-generated reference_number (NXT-{TYPE}-{YY}-{000001}) to
verifications, support_tickets, payments, job_applications, lead_requests,
and users, replacing raw UUIDs shown to customers/admins. Also fixes
verification-status endpoint to return uploaded documents (previously
omitted, so documents never appeared after submission), and adds a
reference-number lookup endpoint for the AI support assistant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- companies/admin: add proper POST /jobs/{id}/approve and /jobs/{id}/reject
endpoints (sets status=LIVE, not direct SQL bypass)
- customers: fix list_requests to query by customer_user_id (not professional),
add optional lead_id filter; fix debit to use professional_user_id
- payments: switch razorpay_order_id column to payu_txnid (PayU migration)
- users/auth: fix role resolution to not inject phantom roles for professionals
- contracts/profession_shared: fix my_requests SQL to join leads+users instead
of nonexistent requirements table
- db/job_seeker: fix INSERT VALUES placeholder count (add missing $10)
- storage: add MOCK_STORAGE=true mode for local dev without real B2 creds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both landed ImagePullBackOff on bad digests from the previous batch;
old replicas kept serving throughout, so no outage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add #![allow(dead_code)] pragma to all main.rs files
- Remove unused imports from users handlers (ai_cache, AiCreditPackageRepository, AiCreditTransactionRepository)
- Make LiteLLMChatMessage and LiteLLMChoice public with public fields
- Fix remaining unused variables with cargo fix
- Add missing pub visibility modifiers to litellm structs
All packages now compile with ZERO warnings and errors!
- Remove duplicate 'name' field from PricingPackageRow struct
- Add tracecoins_credited variable from package.tracecoins_amount
- Change rust_decimal to use i32 instead of Decimal for SQL compatibility
- Simplify discount calculation to work with paise (integer) values
- Fix payu config field names (merchant_key, merchant_salt)
payments package now compiles successfully.
- Restore deleted module declarations (tutor, ugc_content_creator, user,
user_role_profile, verification, video_editor) in db/models/mod.rs
- Add mod ai; to users/src/main.rs (fixes crate::ai import)
- Add pub mod ai_auto; to handlers/mod.rs
- Add rust_decimal dependency to payments/Cargo.toml
- Fix missing PgPool import in payments/src/ai_credits.rs
- Make LiteLLMUsage fields public in users/src/litellm.rs
- Add get_llm_base_url() and get_llm_model() helper functions
- Remove leading underscore from variables that are used
Partial fix for high-performance branch build issues.
The frontend (lib/payu.ts, payu-return.tsx) and admin config UI had
already switched to PayU, but the payments service was still calling
Razorpay's order API and verifying Razorpay HMAC signatures -
payments were broken end to end. Rewrites apps/payments to PayU's
hash-based hosted-checkout flow (SHA-512 request/response hash,
key+salt from admin config or PAYU_MERCHANT_KEY/PAYU_SALT), for both
the tracecoin wallet purchase flow and the AI credits flow. The AI
credits frontend checkout was also fabricating a fake payment_id and
signature client-side instead of ever opening a real PayU checkout -
fixed to use the same real flow as tracecoin purchases.
Also fixes the ai_create_ticket endpoint on the users service, which
never validated the X-AI-Service-Key header despite the client
sending one - anyone could create tickets under an arbitrary user_id.
- Add AI plans, credits, model routing, LiteLLM client, and orchestrator services
- Add AI management endpoints, auto-apply/auto-request handlers, and log endpoints
- Add cron jobs for daily action reset and monthly credit reset
- Add AI credit purchase flow in payments service
- Add ai_credit_packages migration with seed data
- Update Dockerfile build tooling across services
- payments/src/main.rs: fail-fast on BEECEPTOR_URL and DATABASE_URL
- gateway/src/main.rs: fail-fast on all service URLs and CORS URLs
- users/src/handlers/ai.rs: fail-fast on LEADS_SERVICE_URL
- leads/src/main.rs: fail-fast on OLLAMA_BASE_URL and OLLAMA_CHAT_MODEL
- storage/Cargo.toml: replace rustls-aws-lc with rustls for aws-config/aws-sdk-s3
Added openssl-libs-static and OPENSSL_STATIC=1 environment variable
to fix reqwest/native-tls compilation errors with musl target.
Changes:
- Install openssl-libs-static in builder
- Set OPENSSL_STATIC=1 and OPENSSL_DIR=/usr
- Ensures OpenSSL is statically linked for all services
Switched from Debian to Alpine Linux for significant improvements:
- Image size: ~5MB vs ~100MB (95% smaller)
- Security: Minimal attack surface, no glibc vulnerabilities
- Static linking: No glibc version issues ever again
- Uses rust:alpine builder with x86_64-unknown-linux-musl target
- Static binaries with RUSTFLAGS='-C target-feature=+crt-static'
Fixes the GLIBC_2.38 error permanently by avoiding glibc entirely.
Fixed glibc version mismatch between rust:latest builder (glibc 2.38+)
and debian:bookworm-slim runtime (glibc 2.36). This was causing:
- ./companies: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.38 not found
- ./payments: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.38 not found
- Similar errors for users service
Updated all 19 service Dockerfiles + Dockerfile.template to use
debian:trixie-slim which includes glibc 2.38+.
- Create scripts/init-db.sql for DB schema initialization
- Enhance start-services.sh to auto-initialize DB if needed
- Fix users admin handler: change root route from '/users' to '/' to avoid double prefix
- Remove deprecated handlers (departments/designations/employees) from users service
- Add missing admin route mappings for users and approval/case endpoints in gateway
- Update gateway to correctly handle /api/admin/users, /api/admin/approvals, etc.
- Update .env.example and docs
These changes enable running the stack without Docker and fix admin panel routing.
- Add activity_logs handler with paginated admin API
- Register /api/admin/activity-logs route in users service
- Add gateway routing for activity-logs to users service
- Trigger notification on successful tracecoin purchase
- Update handlers mod to include activity_logs module
- Add payments service proxying to Beeceptor mock gateway (create-order, verify, status)
- Add companies admin approve/reject/suspend + get detail endpoints
- Apply require_admin auth guards to all employee/department/designation handlers
- Add submit-for-verification endpoint to all 12 roles (10 professions + job seekers + customers + companies)
- Fix port conflict (employees moved from 8085 to 8096)
- Add submit_for_verification methods to all profile repositories