Skip to content

feat: replace moryflow redemption proxy with native implementation#237

Merged
dvlin-dev merged 5 commits intomainfrom
feat/moryflow-native-redemption
Mar 16, 2026
Merged

feat: replace moryflow redemption proxy with native implementation#237
dvlin-dev merged 5 commits intomainfrom
feat/moryflow-native-redemption

Conversation

@dvlin-dev
Copy link
Copy Markdown
Owner

@dvlin-dev dvlin-dev commented Mar 16, 2026

Summary

  • Replace the proxy-to-anyhunt redemption module with a native moryflow implementation that directly operates on the moryflow database
  • Add GET /admin/redemption-codes/config endpoint to both moryflow and anyhunt servers, making tier options backend-driven
  • Admin frontend now fetches tier options from config endpoint instead of hardcoding them, enabling a single codebase to serve both deployments

Changes

Moryflow server (apps/moryflow/server/)

  • Add RedemptionCode and RedemptionCodeUsage Prisma models with @@unique([redemptionCodeId, userId]) per-account constraint
  • Implement RedemptionService with full CRUD + atomic redeem (concurrent-safe updateMany with lt: maxRedemptions)
  • Add user-facing RedemptionController (/app/redemption-codes/redeem)
  • Add AdminRedemptionCodesController with config endpoint and CRUD
  • Integrate with CreditService (tx-aware) and ActivityLogService
  • Delete proxy module (redemption-proxy.controller.ts, redemption-proxy.service.ts)

Anyhunt server (apps/anyhunt/server/)

  • Add GET /admin/redemption-codes/config endpoint returning { tiers: [BASIC, PRO, TEAM] }

Admin frontend (apps/anyhunt/admin/www/)

  • Remove hardcoded MEMBERSHIP_TIER_OPTIONS
  • Add getRedemptionCodeConfig() API + useRedemptionCodeConfig() hook
  • CreateRedemptionCodeDialog fetches tiers from backend config
  • membershipTier schema changed from z.enum() to z.string().min(1)

Deployment note

Run prisma migrate deploy on the moryflow database to create the new RedemptionCode and RedemptionCodeUsage tables.

Test plan

  • Moryflow server: tsc --noEmit passes (verified locally)
  • Moryflow server: lint passes with 0 errors (verified locally)
  • Anyhunt server: tsc --noEmit passes (verified locally)
  • Admin frontend: build succeeds (verified locally)
  • Run Prisma migration on staging database
  • Verify admin create/list/edit/delete redemption codes
  • Verify user redeem flow from PC app
  • Verify per-account single-use constraint
  • Verify concurrent redemption safety

Open with Devin

Codex 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')
chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…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)
devin-ai-integration[bot]

This comment was marked as resolved.

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
@dvlin-dev
Copy link
Copy Markdown
Owner Author

All 3 review comments addressed in commit d50de09:

  1. Migration file — Added prisma/migrations/0002_add_redemption_codes/migration.sql with CREATE TYPE, tables, indexes, and foreign key. prisma migrate deploy will create the tables automatically.

  2. membershipDays default bleed — Removed .default(30) from the DTO. Default now applied conditionally in service: dto.type === 'MEMBERSHIP' ? (dto.membershipDays ?? 30) : null. CREDITS codes correctly store membershipDays: null.

  3. OpenAPI exposure — Split into RedemptionModule (public /app/) and AdminRedemptionModule (internal /admin/). PUBLIC_API_MODULES only contains RedemptionModule, INTERNAL_API_MODULES only contains AdminRedemptionModule. Admin endpoints no longer appear in the public OpenAPI doc.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

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 dvlin-dev merged commit dfa4a2e into main Mar 16, 2026
2 checks passed
@dvlin-dev dvlin-dev deleted the feat/moryflow-native-redemption branch March 16, 2026 08:49
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 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant