feat: replace moryflow redemption proxy with native implementation#237
Merged
feat: replace moryflow redemption proxy with native implementation#237
Conversation
added 2 commits
March 16, 2026 15:27
Replace the proxy-to-anyhunt redemption module with a native moryflow implementation that directly operates on the moryflow database. Moryflow server: - Add RedemptionCode and RedemptionCodeUsage Prisma models - Implement RedemptionService with full CRUD + atomic redeem logic - Add user-facing RedemptionController (/app/redemption-codes/redeem) - Add AdminRedemptionCodesController with config endpoint - Integrate with CreditService (tx-aware) and ActivityLogService - Delete proxy module (redemption-proxy.controller/service) Anyhunt server: - Add GET /admin/redemption-codes/config endpoint for tier options Admin frontend: - Remove hardcoded MEMBERSHIP_TIER_OPTIONS - Fetch tier options from backend config endpoint - Change membershipTier schema from z.enum() to z.string()
- Replace findUnique+update TOCTOU with single update+P2025 catch in updateCode and deactivateCode - Use != null instead of && for numeric return fields to avoid falsy-zero edge case - Use 'payment' activity category for user redeem (not 'admin')
…d plan - Change creditsAmount/membershipDays/maxRedemptions from && to != null to avoid silently dropping zero values - Delete docs/plans/moryflow-native-redemption-codes.md (fully executed, code is the source of truth)
1. Add Prisma migration 0002_add_redemption_codes with CREATE TYPE, tables, indexes, and foreign key for deployment compatibility 2. Remove .default(30) from membershipDays DTO to prevent CREDITS codes from storing membershipDays: 30; apply default conditionally in service only for MEMBERSHIP type 3. Split RedemptionModule into RedemptionModule (public, /app/) and AdminRedemptionModule (internal, /admin/) so admin endpoints don't appear in the public OpenAPI document
Owner
Author
|
All 3 review comments addressed in commit d50de09:
|
1. Use crypto.randomInt() instead of Math.random() for redemption code generation (CSPRNG) 2. Validate membershipTier regardless of type, and null out type-irrelevant fields (creditsAmount for MEMBERSHIP, membershipTier/Days for CREDITS) before persistence 3. Move anyhunt config endpoint logic to RedemptionService.getConfig() derived from TIER_MONTHLY_QUOTA, matching moryflow pattern
dvlin-dev
pushed a commit
that referenced
this pull request
Mar 16, 2026
Add complete CRUD UI for managing redemption codes in the moryflow admin panel (a.moryflow.com), matching the server API built in PR #237. - Feature module: api, hooks, types, constants, view-state - Components: CreateCodeDialog, EditCodeDialog, CodeDetailDialog - Page with table, type/status filters, search, pagination - Sidebar entry under "支付管理" with Ticket icon
9 tasks
dvlin-dev
pushed a commit
that referenced
this pull request
Mar 16, 2026
Add complete CRUD UI for managing redemption codes in the moryflow admin panel (a.moryflow.com), matching the server API built in PR #237. - Feature module: api, hooks, types, constants, view-state - Components: CreateCodeDialog, EditCodeDialog, CodeDetailDialog - Page with table, type/status filters, search, pagination - Sidebar entry under "支付管理" with Ticket icon
dvlin-dev
added a commit
that referenced
this pull request
Mar 16, 2026
* feat: replace moryflow redemption proxy with native implementation Replace the proxy-to-anyhunt redemption module with a native moryflow implementation that directly operates on the moryflow database. Moryflow server: - Add RedemptionCode and RedemptionCodeUsage Prisma models - Implement RedemptionService with full CRUD + atomic redeem logic - Add user-facing RedemptionController (/app/redemption-codes/redeem) - Add AdminRedemptionCodesController with config endpoint - Integrate with CreditService (tx-aware) and ActivityLogService - Delete proxy module (redemption-proxy.controller/service) Anyhunt server: - Add GET /admin/redemption-codes/config endpoint for tier options Admin frontend: - Remove hardcoded MEMBERSHIP_TIER_OPTIONS - Fetch tier options from backend config endpoint - Change membershipTier schema from z.enum() to z.string() * fix: use != null for number fields in handleCreate and remove executed plan - Change creditsAmount/membershipDays/maxRedemptions from && to != null to avoid silently dropping zero values - Delete docs/plans/moryflow-native-redemption-codes.md (fully executed, code is the source of truth) * feat: add redemption codes page to moryflow admin panel Add complete CRUD UI for managing redemption codes in the moryflow admin panel (a.moryflow.com), matching the server API built in PR #237. - Feature module: api, hooks, types, constants, view-state - Components: CreateCodeDialog, EditCodeDialog, CodeDetailDialog - Page with table, type/status filters, search, pagination - Sidebar entry under "支付管理" with Ticket icon * fix: prevent membership downgrade, extend period, and fix timezone 1. Prevent tier downgrade: reject if user's active subscription is higher than the code's tier (uses getTierLevel comparison) 2. Extend period instead of reset: if user has remaining time on an active subscription, extend from currentPeriodEnd rather than now 3. Convert datetime-local to ISO string before sending expiresAt to server, ensuring correct timezone handling across environments * fix: timezone handling in edit dialog for both admin panels - Convert UTC expiresAt to local time when populating datetime-local input (using sv-SE locale for YYYY-MM-DDTHH:MM format) - Convert local time back to UTC ISO string when submitting updates - Applied to both moryflow and anyhunt admin edit dialogs --------- Co-authored-by: Codex <codex@example.com>
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
GET /admin/redemption-codes/configendpoint to both moryflow and anyhunt servers, making tier options backend-drivenChanges
Moryflow server (
apps/moryflow/server/)RedemptionCodeandRedemptionCodeUsagePrisma models with@@unique([redemptionCodeId, userId])per-account constraintRedemptionServicewith full CRUD + atomic redeem (concurrent-safeupdateManywithlt: maxRedemptions)RedemptionController(/app/redemption-codes/redeem)AdminRedemptionCodesControllerwith config endpoint and CRUDCreditService(tx-aware) andActivityLogServiceredemption-proxy.controller.ts,redemption-proxy.service.ts)Anyhunt server (
apps/anyhunt/server/)GET /admin/redemption-codes/configendpoint returning{ tiers: [BASIC, PRO, TEAM] }Admin frontend (
apps/anyhunt/admin/www/)MEMBERSHIP_TIER_OPTIONSgetRedemptionCodeConfig()API +useRedemptionCodeConfig()hookCreateRedemptionCodeDialogfetches tiers from backend configmembershipTierschema changed fromz.enum()toz.string().min(1)Deployment note
Run
prisma migrate deployon the moryflow database to create the newRedemptionCodeandRedemptionCodeUsagetables.Test plan
tsc --noEmitpasses (verified locally)lintpasses with 0 errors (verified locally)tsc --noEmitpasses (verified locally)buildsucceeds (verified locally)