9 KiB
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/<uuid>(also works) - Users visit
/passport/alice-developer(preferred) or/passport/<uuid>(also works) - Users visit
/ethos/artists/bob-musician(preferred) or/ethos/artists/<uuid>(also works)
Changes Made
1. Core Utility: Identifier Resolver (code/lib/identifier-resolver.ts)
New file that provides helper functions:
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
:identifierparameter - Try username first (preferred)
- Fall back to UUID lookup if username lookup fails
- Return 404 if neither works
// 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
// 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:
- If it matches UUID pattern → Try UUID lookup directly
- If it doesn't match UUID pattern → Try username lookup first, then UUID fallback
- 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
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_creatorstable - Enforced in API validation (returns 409 if duplicate)
- Usernames are indexed for fast lookups
Required Field
usernamecolumn is NOT NULL- All profiles created after enforcement have usernames
- Migration: Existing profiles without usernames should be given auto-generated usernames
Future Improvements
- Profile slugs - Use human-friendly slugs instead of usernames (e.g.,
/profile/john-doe-developer) - Username changes - Allow users to change usernames with proper redirects
- Vanity URLs - Custom profile URLs (e.g.,
/john) - 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
- Create identifier resolver utility
- Update creators API endpoint to support both username and UUID
- Update CreatorProfile component to handle both patterns
- Update ArtistProfile component to handle both patterns
- Enforce usernames as required field
- 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.