307 lines
9 KiB
Markdown
307 lines
9 KiB
Markdown
# 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:
|
|
|
|
```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.
|