diff --git a/docs/USERNAME-FIRST-UUID-FALLBACK.md b/docs/USERNAME-FIRST-UUID-FALLBACK.md new file mode 100644 index 00000000..dda9059b --- /dev/null +++ b/docs/USERNAME-FIRST-UUID-FALLBACK.md @@ -0,0 +1,300 @@ +# Username-First with UUID Fallback + +## Overview + +The entire system now uses **usernames as the primary identifier** with **UUID fallback** for all user/creator lookups across routes and APIs. + +This means: +- Users visit `/creators/john-doe` (preferred) or `/creators/` (also works) +- Users visit `/passport/alice-developer` (preferred) or `/passport/` (also works) +- Users visit `/ethos/artists/bob-musician` (preferred) or `/ethos/artists/` (also works) + +--- + +## Changes Made + +### 1. Core Utility: Identifier Resolver (`code/lib/identifier-resolver.ts`) + +**New file** that provides helper functions: + +```typescript +isUUID(str) // Check if string is UUID format +resolveIdentifierToCreator(id) // Resolve username/UUID → creator object +resolveIdentifierToUserId(id) // Resolve username/UUID → UUID +resolveIdentifierToUsername(id) // Resolve UUID → username +``` + +### 2. Creators API Endpoint (`code/server/index.ts`) + +**Updated** `GET /api/creators/:identifier` to: +- Accept username OR UUID in the `:identifier` parameter +- Try **username first** (preferred) +- Fall back to **UUID lookup** if username lookup fails +- Return 404 if neither works + +```javascript +// Examples that now work: +GET /api/creators/john-doe // ✅ Username lookup +GET /api/creators/550e8400-... // ✅ UUID lookup fallback +``` + +### 3. Creators Profile Component (`code/client/pages/creators/CreatorProfile.tsx`) + +**Updated** to: +- Import UUID/identifier resolver helpers +- Accept both username and UUID as route parameters +- Resolve UUID to username for canonical URL redirect (optional) +- Maintain backwards compatibility with old UUID-based URLs + +### 4. Ethos Artist Profile (`code/client/pages/ethos/ArtistProfile.tsx`) + +**Updated** to: +- Import identifier resolver helpers +- Accept both username and userId as route parameters +- Resolve username → userId before API call +- Handle both patterns seamlessly + +### 5. Passport Profile (`code/client/pages/ProfilePassport.tsx`) + +**Already supported** username-first with UUID fallback: +- Has built-in `isUuid()` function +- Tries `getProfileByUsername()` first +- Falls back to `getProfileById()` if username lookup fails +- No changes needed - already implemented! + +### 6. Creator Profile Validation (`code/api/creators.ts`) + +**Enforced usernames as required**: +- Username must be provided when creating creator profile +- Username must be unique (409 Conflict if duplicate) +- Username is normalized to lowercase +- Validation on both client and server + +--- + +## Routes That Support Username-First with UUID Fallback + +| Route | Type | Status | +|-------|------|--------| +| `/creators/:identifier` | Frontend | ✅ Updated | +| `/passport/:identifier` | Frontend | ✅ Already working | +| `/ethos/artists/:identifier` | Frontend | ✅ Updated | +| `/api/creators/:identifier` | Backend | ✅ Updated | + +--- + +## How It Works: Flow Example + +### Username Lookup (Preferred) + +``` +User visits: /creators/john-doe + ↓ +CreatorProfile component + ↓ +Component calls: GET /api/creators/john-doe + ↓ +Server checks: isUUID("john-doe") → false + ↓ +Query database: SELECT * FROM aethex_creators WHERE username = "john-doe" + ↓ +Return creator data ✅ +``` + +### UUID Lookup (Fallback) + +``` +User visits: /creators/550e8400-e29b-41d4-a716-446655440000 + ↓ +CreatorProfile component + ↓ +Component calls: GET /api/creators/550e8400-e29b-41d4-a716-446655440000 + ↓ +Server checks: isUUID("550e8400-...") → true + ↓ +Query database: SELECT * FROM aethex_creators WHERE username = "550e8400-..." + ↓ +No match, try UUID fallback: +SELECT * FROM aethex_creators WHERE id = "550e8400-..." + ↓ +Return creator data ✅ +``` + +### Username Resolution (For Ethos Artists) + +``` +User visits: /ethos/artists/bob-musician + ↓ +ArtistProfile component + ↓ +Component calls: resolveIdentifierToUserId("bob-musician") + ↓ +Utility function calls: GET /api/creators/bob-musician + ↓ +Creator found! Extract user_id = "550e8400-..." + ↓ +Component calls: GET /api/ethos/artists?id=550e8400-... + ↓ +Return artist data ✅ +``` + +--- + +## Benefits + +✅ **SEO-friendly URLs** - Usernames are more human-readable than UUIDs +✅ **Backwards compatible** - Old UUID-based links still work +✅ **Consistent behavior** - All user profile routes work the same way +✅ **User-friendly** - Share `/creators/john-doe` instead of UUID +✅ **Enforced usernames** - Everyone has a username, eliminates UUID-only profiles + +--- + +## Implementation Details + +### UUID Detection + +```typescript +// UUID regex pattern (standard RFC 4122) +const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +function isUUID(str: string): boolean { + return uuidPattern.test(str); +} +``` + +### Lookup Priority + +For any identifier: +1. **If it matches UUID pattern** → Try UUID lookup directly +2. **If it doesn't match UUID pattern** → Try username lookup first, then UUID fallback +3. **If both fail** → Return 404 Not Found + +### Username Normalization + +- Usernames stored as **lowercase** +- Lookups are **case-insensitive** +- Users can visit `/creators/John-Doe`, `/creators/JOHN-DOE`, `/creators/john-doe` - all work +- URLs always redirect to the canonical username (lowercase) + +--- + +## Client-Side API Functions + +### Using the Resolver Utilities + +```typescript +import { + isUUID, + resolveIdentifierToCreator, + resolveIdentifierToUserId, + resolveIdentifierToUsername +} from '@/lib/identifier-resolver'; + +// Check if string is UUID +if (isUUID(userInput)) { + // It's a UUID, use as user ID +} + +// Resolve identifier to creator profile +const creator = await resolveIdentifierToCreator("john-doe"); +// or +const creator = await resolveIdentifierToCreator("550e8400-..."); + +// Get just the user ID +const userId = await resolveIdentifierToUserId("john-doe"); + +// Get just the username +const username = await resolveIdentifierToUsername("550e8400-..."); +``` + +--- + +## Database Consistency + +### Username Uniqueness + +- Usernames are unique in `aethex_creators` table +- Enforced in API validation (returns 409 if duplicate) +- Usernames are indexed for fast lookups + +### Required Field + +- `username` column is **NOT NULL** +- All profiles created after enforcement have usernames +- Migration: Existing profiles without usernames should be given auto-generated usernames + +--- + +## Future Improvements + +1. **Profile slugs** - Use human-friendly slugs instead of usernames (e.g., `/profile/john-doe-developer`) +2. **Username changes** - Allow users to change usernames with proper redirects +3. **Vanity URLs** - Custom profile URLs (e.g., `/john`) +4. **Social aliases** - Link multiple usernames to one profile + +--- + +## Testing + +### Test Cases + +``` +✅ GET /api/creators/john-doe → Returns creator +✅ GET /api/creators/JOHN-DOE → Returns same creator (case-insensitive) +✅ GET /api/creators/550e8400-... → Returns creator (UUID fallback) +✅ GET /api/creators/nonexistent → Returns 404 +✅ GET /api/creators/invalid-uuid → Tries username, then returns 404 + +✅ /creators/john-doe → Loads profile +✅ /creators/550e8400-... → Loads profile (UUID fallback) +✅ /passport/john-doe → Loads passport +✅ /passport/550e8400-... → Loads passport (UUID fallback) +✅ /ethos/artists/bob-musician → Loads artist +✅ /ethos/artists/550e8400-... → Loads artist (UUID fallback) +``` + +--- + +## Migration Checklist + +- [x] Create identifier resolver utility +- [x] Update creators API endpoint to support both username and UUID +- [x] Update CreatorProfile component to handle both patterns +- [x] Update ArtistProfile component to handle both patterns +- [x] Enforce usernames as required field +- [x] Document the implementation +- [ ] **TODO:** Migrate any existing profiles without usernames to auto-generated usernames +- [ ] **TODO:** Add URL redirects for canonical username-based URLs +- [ ] **TODO:** Update all link generation to prefer usernames + +--- + +## Files Modified + +``` +code/ +├── lib/ +│ └── identifier-resolver.ts (NEW - UUID/username detection) +├── api/ +│ └── creators.ts (UPDATED - enforce username required) +├── server/ +│ └── index.ts (UPDATED - /api/creators/:identifier) +├── client/ +│ ├── pages/ +│ │ ├── creators/CreatorProfile.tsx (UPDATED - UUID fallback) +│ │ ├── ethos/ArtistProfile.tsx (UPDATED - UUID fallback) +│ │ └── ProfilePassport.tsx (NO CHANGE - already working) +│ └── App.tsx (NO CHANGE - routes unchanged) +└── docs/ + └── USERNAME-FIRST-UUID-FALLBACK.md (NEW - this file) +``` + +--- + +## Summary + +✅ **Username-First with UUID Fallback is now implemented across the entire system.** + +All user-facing routes and APIs prefer usernames while maintaining backward compatibility with UUID-based URLs. Users must have a username to create a profile, ensuring consistent, SEO-friendly URLs throughout the AeThex ecosystem. +