mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
- Remove duplicate route prefix in GiftsController (was /api/v1/api/v1/gifts) - Fix JwtAuthGuard to use JWT_ISSUER as fallback when BASE_URL is not set - Add comprehensive GIFT_CODES.md documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Gift Code System
User-generated gift codes for sharing credits across the Mana ecosystem.
Overview
Users can create gift codes to share credits with others. The system supports various modes:
| Type | Description |
|---|---|
simple |
Single-use, one recipient |
personalized |
Restricted to specific email/Matrix ID |
split |
Divided into portions (e.g., 100 credits / 5 = 20 each) |
first_come |
First N users get full amount |
riddle |
Requires correct answer to redeem |
API Endpoints
Base URL: /api/v1/gifts
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /:code |
- | Get gift code info (public preview) |
| POST | / |
JWT | Create new gift code |
| POST | /:code/redeem |
JWT | Redeem a gift code |
| GET | /me/created |
JWT | List codes you created |
| GET | /me/received |
JWT | List gifts you received |
| DELETE | /:id |
JWT | Cancel code & refund unclaimed |
Usage Examples
Create Gift Code
curl -X POST "https://auth.mana.how/api/v1/gifts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credits": 100,
"message": "Happy Birthday!",
"type": "simple"
}'
Response:
{
"id": "uuid",
"code": "ABC123",
"url": "https://mana.how/g/ABC123",
"totalCredits": 100,
"creditsPerPortion": 100,
"totalPortions": 1,
"type": "simple",
"expiresAt": "2026-05-14T00:00:00Z"
}
Create Split Gift (5 portions)
curl -X POST "https://auth.mana.how/api/v1/gifts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credits": 100,
"type": "split",
"portions": 5,
"message": "Share this with friends!"
}'
Each recipient gets 20 credits (100 / 5).
Create Riddle Gift
curl -X POST "https://auth.mana.how/api/v1/gifts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credits": 50,
"type": "riddle",
"riddleQuestion": "What is the capital of Germany?",
"riddleAnswer": "Berlin"
}'
Get Gift Info (Public)
curl "https://auth.mana.how/api/v1/gifts/ABC123"
Response:
{
"code": "ABC123",
"type": "simple",
"status": "active",
"creditsPerPortion": 100,
"totalPortions": 1,
"remainingPortions": 1,
"message": "Happy Birthday!",
"hasRiddle": false,
"isPersonalized": false,
"expiresAt": "2026-05-14T00:00:00Z",
"creatorName": "John Doe"
}
Redeem Gift Code
curl -X POST "https://auth.mana.how/api/v1/gifts/ABC123/redeem" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
For riddle gifts, include the answer:
curl -X POST "https://auth.mana.how/api/v1/gifts/ABC123/redeem" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"answer": "Berlin"}'
Response:
{
"success": true,
"creditsReceived": 100,
"newBalance": 250,
"message": "Happy Birthday!"
}
Matrix Bot Commands
German
!geschenk 50 # Simple gift
!geschenk 100 /5 # Split into 5 portions
!geschenk 50 ?="Berlin" # With riddle
!geschenk 50 "Viel Spass!" # With message
!einloesen ABC123 # Redeem code
!einloesen ABC123 Berlin # Redeem with riddle answer
!meine-geschenke # List your gifts
English
!gift 50
!gift 100 /5
!gift 50 ?="Berlin"
!redeem ABC123
!my-gifts
Database Schema
Tables
gifts.gift_codes
id- UUID primary keycode- Unique 6-char code (e.g., "ABC123")short_url- Full URL (e.g., "mana.how/g/ABC123")creator_id- FK to auth.userstotal_credits- Reserved amountcredits_per_portion- Credits per redemptiontotal_portions- Number of portionsclaimed_portions- Portions already redeemedtype- simple|personalized|split|first_come|riddlestatus- active|depleted|expired|cancelled|refundedtarget_email- For personalized giftstarget_matrix_id- For personalized giftsriddle_question- Question textriddle_answer_hash- bcrypt hash of answermessage- Optional messageexpires_at- Expiration timestampreservation_transaction_id- FK to credits.transactions
gifts.gift_redemptions
id- UUID primary keygift_code_id- FK to gift_codesredeemer_user_id- FK to auth.usersstatus- success|failed_wrong_answer|failed_wrong_user|...credits_received- Amount creditedportion_number- Which portion was claimedcredit_transaction_id- FK to credits.transactionssource_app_id- 'matrix-bot', 'web', etc.
Transaction Types
Credits schema includes gift-related transaction types:
gift_reserve- Credits reserved when creating giftgift_release- Credits returned when cancelling giftgift_receive- Credits received when redeeming gift
Integration Points
Web Apps (SvelteKit)
// Fetch gift info
const response = await fetch(`/api/v1/gifts/${code}`);
const giftInfo = await response.json();
// Redeem
const result = await fetch(`/api/v1/gifts/${code}/redeem`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ answer: riddleAnswer })
});
Matrix Bots
import { GiftService } from '@manacore/bot-services';
import { handleGiftCommand } from '@manacore/matrix-bot-common';
// In bot command handler
if (isGiftCommand(command)) {
return handleGiftCommand(this, roomId, userId, command, args);
}
Mobile Apps (Expo)
Same REST API, use fetch or axios with JWT token.
Security
- Gift codes use 6-char alphanumeric codes (no ambiguous chars)
- Riddle answers are bcrypt hashed
- Row-level locking prevents race conditions
- Credits are reserved atomically when creating gifts
- Personalized gifts verify email or Matrix ID
Configuration
Environment variables:
# Base URL for short links
APP_BASE_URL=https://mana.how
Gift code rules (hardcoded):
const GIFT_CODE_RULES = {
minCredits: 1,
maxCredits: 10000,
maxPortions: 100,
maxMessageLength: 500,
maxRiddleQuestionLength: 200,
defaultExpirationDays: 90,
};