Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
11 KiB
Character Sharing Feature - Implementation Complete
Overview
Users can now share private characters with friends via deep links. Characters remain private to the owner, but anyone with the link can import them to their library for 10 credits.
Key Concept: This is private sharing - no publishing required. Your character stays private, only people with the link can see and import it.
Important: Private Sharing Model
Unlike typical social features, this implementation prioritizes privacy:
- ✅ No Publishing Required - Share any character instantly
- ✅ Stays Private - Your character remains yours, not in public galleries
- ✅ Link-Only Access - Only people with the deep link can view/import
- ✅ Full Control - Delete the character = link stops working
- ❌ No Public Discovery - Characters aren't searchable or browsable
- ❌ No Analytics - You won't see who clicked your link (privacy-first)
Use Case: Share a character privately with a friend via Messages/WhatsApp, not publish to the world.
What Was Implemented
Backend Changes (NestJS)
-
New DTO (
backend/src/character/dto/import-character.dto.ts)- Validation for character import requests
-
Service Methods (
backend/src/core/services/supabase-jsonb-auth.service.ts)getSharedCharacter(characterId)- Public endpoint to fetch any character by ID (private sharing)importCharacter(characterId, userId, token)- Creates a copy of shared character- Includes validation for:
- User can't import their own characters
- User can't import same character twice
-
Controller Endpoints (
backend/src/character/character.controller.ts)GET /character/shared/:characterId- Public endpoint (no auth required) - works with private charactersPOST /character/import/:characterId- Authenticated endpoint with credit check- 10 credits required to import a character
- Tracks import metadata and increments
times_sharedcounter - No publishing required - characters stay private
Mobile App Changes (React Native/Expo)
-
Deep Linking Configuration
- Updated
mobile/app.json- Changed scheme from"myapp"to"maerchenzauber" - Created
mobile/app/+native-intent.tsx- Deep link handler - Handles URLs:
maerchenzauber://share/character/{characterId}
- Updated
-
Character Preview Screen (
mobile/app/character/preview/[characterId].tsx)- Displays shared character details
- Shows all character images with selector
- Import button with credit requirement displayed
- Prevents importing own characters
- Prevents duplicate imports
-
Share Button Component (
mobile/components/character/ShareCharacterButton.tsx)- Simple "Share Character" button
- Native share dialog integration
- No publishing required - direct private sharing
- Generates deep link and opens native share sheet
-
Integration (
mobile/app/character/[id].tsx)- Added ShareCharacterButton to character detail screen
- Positioned below character name/sharing status
Database Schema Requirements
The following fields should exist in the characters table:
-- Existing fields (already in use):
- id (uuid)
- user_id (uuid)
- name (text)
- image_url (text)
- images_data (jsonb)
- user_description (text)
- character_description (text)
- character_description_prompt (text)
- is_animal (boolean)
- animal_type (text)
- blur_hash (text)
-- Publishing fields (optional - not used in private sharing):
- is_published (boolean) -- Can remain, but not required for sharing
- sharing_preference (text) -- Can remain, but not used in this implementation
- share_code (text) -- Can remain, but not used in this implementation
- published_at (timestamp) -- Can remain, but not used in this implementation
-- Existing field (already in database):
- cloned_from (uuid) -- References id of original character (used for imports)
-- New fields added by migration:
- imported_from_user_id (text) -- User ID of original creator
- imported_at (timestamptz) -- When character was imported
- times_shared (integer) -- Counter for how many times character was imported (default: 0)
Migration Applied ✅
The migration has been applied to your database. The following fields were added:
imported_from_user_id(text)imported_at(timestamptz)times_shared(integer, default 0)
And we're using the existing cloned_from field to track the original character.
Testing Guide
Setup
- Restart Backend:
cd backend && npm run dev - Restart Mobile App with Cache Clear:
cd mobile && npx expo start -c- Important: Use
-cflag to clear cache after app.json scheme change
- Important: Use
Test Scenario 1: Private Sharing (Simplified)
- Login as User A
- Navigate to any character you created (doesn't need to be published)
- Tap "Share Character" button
- Native share dialog appears immediately
- No publishing modal required
- Share dialog shows:
- Share link format:
maerchenzauber://share/character/{characterId} - Share via any method (Messages, WhatsApp, Email, Copy Link)
- Send link to yourself or another device for testing
- Share link format:
Test Scenario 2: Receiving and Importing
- Login as User B (different account)
- Open the shared link
- Tap the deep link you shared
- App should open to character preview screen
- Preview screen should show:
- Character images (with image selector if multiple)
- Character name and description
- Animal type badge (if applicable)
- "Import to My Library (10 Credits)" button
- Tap Import button
- Confirm dialog should appear
- Should show "Import for 10 credits?"
- Tap confirm
- After successful import:
- Success toast appears
- Navigate to imported character detail
- Character should be in User B's library
Test Scenario 3: Edge Cases
-
User tries to import their own character
- Should show error: "Cannot import your own character"
-
User tries to import same character twice
- Should show error: "You have already imported this character"
-
User with insufficient credits
- Should show error: "You need 10 credits to import..."
-
User tries to access deleted character
- Deep link should show error: "Character not found"
Test Scenario 4: Deep Link Testing
Test deep link manually:
# iOS
xcrun simctl openurl booted "maerchenzauber://share/character/YOUR_CHARACTER_ID"
# Android
adb shell am start -W -a android.intent.action.VIEW -d "maerchenzauber://share/character/YOUR_CHARACTER_ID"
API Endpoints
Get Shared Character (Public)
GET /character/shared/:characterId
Response:
{
"data": {
"id": "uuid",
"name": "Character Name",
"image_url": "https://...",
"images_data": [...],
"user_description": "...",
"is_animal": true,
"animal_type": "fox",
"user_id": "creator-uuid"
}
}
Import Character
POST /character/import/:characterId
Authorization: Bearer <token>
Response:
{
"data": {
"id": "new-uuid",
"name": "Character Name",
"imageUrl": "https://...",
...
},
"message": "Character imported successfully"
}
Deep Link Format
maerchenzauber://share/character/{characterId}
Example:
maerchenzauber://share/character/a1b2c3d4-e5f6-7890-abcd-ef1234567890
File Changes Summary
New Files
backend/src/character/dto/import-character.dto.tsmobile/app/+native-intent.tsxmobile/app/character/preview/[characterId].tsxmobile/components/character/ShareCharacterButton.tsxCHARACTER_SHARING_IMPLEMENTATION.md(this file)
Modified Files
backend/src/core/services/supabase-jsonb-auth.service.tsbackend/src/character/character.controller.tsmobile/app.jsonmobile/app/character/[id].tsx
Known Limitations
- No web fallback - Deep links only work when app is installed
- No share analytics - Not tracking link opens or share sources
- No creator rewards - Original creator doesn't receive credits when character is imported
- No QR codes - Only URL-based sharing implemented
Future Enhancements
- Universal Links - Add web landing page for users without app
- Share Analytics - Track how many times links are clicked
- Creator Rewards - Give original creator 5 credits when someone imports
- QR Code Generation - Allow generating QR codes for in-person sharing
- Popular Characters Leaderboard - Show most imported characters
- Share Templates - Custom messages for different platforms
Troubleshooting
Deep Link Not Working
- Ensure you restarted Expo with
-cflag after changing app.json - Check that URL scheme is
maerchenzauber(notmyapp) - On iOS, deep links might not work in simulator for some URLs - test on device
- On Android, ensure app is in foreground or background (not force-closed)
Import Failing
- Check user has 10+ credits available
- Verify character is published (is_published = true)
- Check character sharing_preference is not 'private'
- Ensure user is not trying to import their own character
Backend Errors
- Check Supabase RLS policies allow reading published characters
- Verify all new database fields exist in characters table
- Check backend logs for specific error messages
Success Criteria
✅ User can share any private character directly (no publishing required) ✅ Share button opens native share dialog immediately ✅ Deep link opens app to character preview screen ✅ Character preview shows all character details ✅ Import button validates credits (10 required) ✅ Import creates new character copy in user's library ✅ Prevents self-import and duplicate imports ✅ Times_shared counter increments on original character ✅ Share button integrates seamlessly into character detail screen ✅ Character remains private to owner - only people with link can import
Testing Checklist
- Backend endpoints return correct responses
- Deep links open app to preview screen
- Character preview displays all information (even for private characters)
- Import validates credits correctly
- Import creates character copy
- Prevents self-import
- Prevents duplicate import
- Share button works without publishing (private sharing)
- Share dialog shows correct deep link
- Credit deduction works (10 credits)
- Toast notifications appear correctly
- Error handling works for all edge cases
- Original character remains private after sharing
Implementation Date: 2025-10-31 Status: ✅ Complete - Ready for Testing