Prettier format pending files

This commit is contained in:
Builder.io 2025-11-17 03:02:34 +00:00
parent 0180c560cc
commit f9465d75c2
7 changed files with 114 additions and 68 deletions

View file

@ -132,9 +132,15 @@ export async function createCreatorProfile(req: Request, userId: string) {
} = body; } = body;
// Validate required fields // Validate required fields
if (!username || typeof username !== "string" || username.trim().length === 0) { if (
!username ||
typeof username !== "string" ||
username.trim().length === 0
) {
return new Response( return new Response(
JSON.stringify({ error: "Username is required and must be a non-empty string" }), JSON.stringify({
error: "Username is required and must be a non-empty string",
}),
{ status: 400, headers: { "Content-Type": "application/json" } }, { status: 400, headers: { "Content-Type": "application/json" } },
); );
} }
@ -234,10 +240,7 @@ export async function updateCreatorProfile(req: Request, userId: string) {
const normalizedUsername = username.trim().toLowerCase(); const normalizedUsername = username.trim().toLowerCase();
if ( if (currentCreator && currentCreator.username !== normalizedUsername) {
currentCreator &&
currentCreator.username !== normalizedUsername
) {
// Username is being changed, check if new username exists // Username is being changed, check if new username exists
const { data: existingCreator } = await supabase const { data: existingCreator } = await supabase
.from("aethex_creators") .from("aethex_creators")
@ -265,12 +268,17 @@ export async function updateCreatorProfile(req: Request, userId: string) {
if (bio !== undefined) updateData.bio = bio; if (bio !== undefined) updateData.bio = bio;
if (skills !== undefined) updateData.skills = skills; if (skills !== undefined) updateData.skills = skills;
if (avatar_url !== undefined) updateData.avatar_url = avatar_url; if (avatar_url !== undefined) updateData.avatar_url = avatar_url;
if (experience_level !== undefined) updateData.experience_level = experience_level; if (experience_level !== undefined)
updateData.experience_level = experience_level;
if (primary_arm !== undefined) updateData.primary_arm = primary_arm; if (primary_arm !== undefined) updateData.primary_arm = primary_arm;
if (arm_affiliations !== undefined) updateData.arm_affiliations = arm_affiliations; if (arm_affiliations !== undefined)
if (is_discoverable !== undefined) updateData.is_discoverable = is_discoverable; updateData.arm_affiliations = arm_affiliations;
if (allow_recommendations !== undefined) updateData.allow_recommendations = allow_recommendations; if (is_discoverable !== undefined)
if (spotify_profile_url !== undefined) updateData.spotify_profile_url = spotify_profile_url; updateData.is_discoverable = is_discoverable;
if (allow_recommendations !== undefined)
updateData.allow_recommendations = allow_recommendations;
if (spotify_profile_url !== undefined)
updateData.spotify_profile_url = spotify_profile_url;
const { data, error } = await supabase const { data, error } = await supabase
.from("aethex_creators") .from("aethex_creators")

View file

@ -81,7 +81,10 @@ export default function CreatorProfile({
}; };
const canProceed = useMemo(() => { const canProceed = useMemo(() => {
const hasUsername = data?.username && typeof data.username === 'string' && data.username.trim().length > 0; const hasUsername =
data?.username &&
typeof data.username === "string" &&
data.username.trim().length > 0;
const hasPrimaryArm = creatorData.primaryArm; const hasPrimaryArm = creatorData.primaryArm;
const hasSkills = creatorData.skills.length > 0; const hasSkills = creatorData.skills.length > 0;
return hasUsername && hasPrimaryArm && hasSkills; return hasUsername && hasPrimaryArm && hasSkills;
@ -153,7 +156,8 @@ export default function CreatorProfile({
/> />
</div> </div>
<p className="text-xs text-gray-400 mt-2"> <p className="text-xs text-gray-400 mt-2">
{(data?.username || "").length}/32 characters. Lowercase letters, numbers, hyphens, and underscores only. {(data?.username || "").length}/32 characters. Lowercase letters,
numbers, hyphens, and underscores only.
</p> </p>
{(data?.username?.length || 0) === 0 && ( {(data?.username?.length || 0) === 0 && (
<p className="text-xs text-red-400 mt-1">Username is required</p> <p className="text-xs text-red-400 mt-1">Username is required</p>

View file

@ -11,7 +11,8 @@ const API_BASE = typeof window !== "undefined" ? window.location.origin : "";
* Check if a string is a valid UUID * Check if a string is a valid UUID
*/ */
export function isUUID(str: string): boolean { export function isUUID(str: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(str); return uuidRegex.test(str);
} }
@ -26,14 +27,18 @@ export async function resolveIdentifierToCreator(
try { try {
// Try username first (most common case) // Try username first (most common case)
const usernameResponse = await fetch(`${API_BASE}/api/creators/${identifier}`); const usernameResponse = await fetch(
`${API_BASE}/api/creators/${identifier}`,
);
if (usernameResponse.ok) { if (usernameResponse.ok) {
return await usernameResponse.json(); return await usernameResponse.json();
} }
// If username lookup failed and identifier is a UUID, try UUID lookup // If username lookup failed and identifier is a UUID, try UUID lookup
if (isUUID(identifier)) { if (isUUID(identifier)) {
const uuidResponse = await fetch(`${API_BASE}/api/creators/user/${identifier}`); const uuidResponse = await fetch(
`${API_BASE}/api/creators/user/${identifier}`,
);
if (uuidResponse.ok) { if (uuidResponse.ok) {
return await uuidResponse.json(); return await uuidResponse.json();
} }
@ -49,7 +54,9 @@ export async function resolveIdentifierToCreator(
/** /**
* Resolve an identifier to a user by ID * Resolve an identifier to a user by ID
*/ */
export async function resolveIdentifierToUserId(identifier: string): Promise<string | null> { export async function resolveIdentifierToUserId(
identifier: string,
): Promise<string | null> {
if (!identifier) return null; if (!identifier) return null;
if (isUUID(identifier)) { if (isUUID(identifier)) {
@ -69,7 +76,9 @@ export async function resolveIdentifierToUserId(identifier: string): Promise<str
/** /**
* Resolve an identifier to a username * Resolve an identifier to a username
*/ */
export async function resolveIdentifierToUsername(identifier: string): Promise<string | null> { export async function resolveIdentifierToUsername(
identifier: string,
): Promise<string | null> {
if (!identifier) return null; if (!identifier) return null;
if (!isUUID(identifier)) { if (!isUUID(identifier)) {

View file

@ -321,7 +321,9 @@ export default function Onboarding() {
// Use username from form if provided, otherwise generate from first name // Use username from form if provided, otherwise generate from first name
const usernameFromForm = data.username?.trim().toLowerCase(); const usernameFromForm = data.username?.trim().toLowerCase();
const generatedUsername = normalizedFirst.replace(/\s+/g, "_").toLowerCase(); const generatedUsername = normalizedFirst
.replace(/\s+/g, "_")
.toLowerCase();
const finalUsername = usernameFromForm || generatedUsername; const finalUsername = usernameFromForm || generatedUsername;
if (!finalUsername) { if (!finalUsername) {
@ -377,23 +379,24 @@ export default function Onboarding() {
); );
// Create creator profile if they provided primary arm // Create creator profile if they provided primary arm
const creatorProfilePromise = data.creatorProfile.primaryArm && finalUsername const creatorProfilePromise =
? fetch(`${API_BASE}/api/creators`, { data.creatorProfile.primaryArm && finalUsername
method: "POST", ? fetch(`${API_BASE}/api/creators`, {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify({ headers: { "Content-Type": "application/json" },
user_id: user.id, body: JSON.stringify({
username: finalUsername, user_id: user.id,
bio: data.creatorProfile.bio || null, username: finalUsername,
avatar_url: null, // Can be added later in profile settings bio: data.creatorProfile.bio || null,
experience_level: data.experience.level || "junior", avatar_url: null, // Can be added later in profile settings
primary_arm: data.creatorProfile.primaryArm, experience_level: data.experience.level || "junior",
arm_affiliations: [data.creatorProfile.primaryArm], primary_arm: data.creatorProfile.primaryArm,
skills: data.creatorProfile.skills || [], arm_affiliations: [data.creatorProfile.primaryArm],
is_discoverable: true, skills: data.creatorProfile.skills || [],
}), is_discoverable: true,
}) }),
: Promise.resolve(); })
: Promise.resolve();
// Save followed arms // Save followed arms
const followedArmsPromises = (data.followedArms || []).map((armId) => const followedArmsPromises = (data.followedArms || []).map((armId) =>
@ -405,7 +408,7 @@ export default function Onboarding() {
arm_id: armId, arm_id: armId,
action: "follow", action: "follow",
}), }),
}) }),
); );
Promise.allSettled([ Promise.allSettled([
@ -509,7 +512,8 @@ export default function Onboarding() {
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 text-sm"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 text-sm">
<div className="px-4 py-2 rounded-full bg-aethex-500/10 border border-aethex-400/40"> <div className="px-4 py-2 rounded-full bg-aethex-500/10 border border-aethex-400/40">
<p className="text-aethex-300 font-semibold"> <p className="text-aethex-300 font-semibold">
Step <span className="text-lg">{currentStep + 1}</span> of <span className="text-lg">{steps.length}</span> Step <span className="text-lg">{currentStep + 1}</span> of{" "}
<span className="text-lg">{steps.length}</span>
</p> </p>
</div> </div>
<Link <Link
@ -559,12 +563,14 @@ export default function Onboarding() {
</h2> </h2>
</div> </div>
<p className="text-muted-foreground text-sm pl-4"> <p className="text-muted-foreground text-sm pl-4">
{currentStep === 0 && "Choose your path and let's begin your journey"} {currentStep === 0 &&
"Choose your path and let's begin your journey"}
{currentStep === 1 && "Help us know you better"} {currentStep === 1 && "Help us know you better"}
{currentStep === 2 && "Tell us about your experience"} {currentStep === 2 && "Tell us about your experience"}
{currentStep === 3 && "What are your interests?"} {currentStep === 3 && "What are your interests?"}
{currentStep === 4 && "Select your primary focus area"} {currentStep === 4 && "Select your primary focus area"}
{currentStep === 5 && "Follow the arms you want to see in your feed"} {currentStep === 5 &&
"Follow the arms you want to see in your feed"}
{currentStep === 6 && "Set up your creator profile"} {currentStep === 6 && "Set up your creator profile"}
{currentStep === 7 && "You're ready to go!"} {currentStep === 7 && "You're ready to go!"}
</p> </p>

View file

@ -72,7 +72,9 @@ export default function ArtistProfile() {
} }
} }
const res = await fetch(`${API_BASE}/api/ethos/artists?id=${resolvedUserId}`); const res = await fetch(
`${API_BASE}/api/ethos/artists?id=${resolvedUserId}`,
);
const data = await res.json(); const data = await res.json();
setArtist(data); setArtist(data);
} catch (error) { } catch (error) {

View file

@ -5,6 +5,7 @@
The entire system now uses **usernames as the primary identifier** with **UUID fallback** for all user/creator lookups across routes and APIs. The entire system now uses **usernames as the primary identifier** with **UUID fallback** for all user/creator lookups across routes and APIs.
This means: This means:
- Users visit `/creators/john-doe` (preferred) or `/creators/<uuid>` (also works) - Users visit `/creators/john-doe` (preferred) or `/creators/<uuid>` (also works)
- Users visit `/passport/alice-developer` (preferred) or `/passport/<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) - Users visit `/ethos/artists/bob-musician` (preferred) or `/ethos/artists/<uuid>` (also works)
@ -18,15 +19,16 @@ This means:
**New file** that provides helper functions: **New file** that provides helper functions:
```typescript ```typescript
isUUID(str) // Check if string is UUID format isUUID(str); // Check if string is UUID format
resolveIdentifierToCreator(id) // Resolve username/UUID → creator object resolveIdentifierToCreator(id); // Resolve username/UUID → creator object
resolveIdentifierToUserId(id) // Resolve username/UUID → UUID resolveIdentifierToUserId(id); // Resolve username/UUID → UUID
resolveIdentifierToUsername(id) // Resolve UUID → username resolveIdentifierToUsername(id); // Resolve UUID → username
``` ```
### 2. Creators API Endpoint (`code/server/index.ts`) ### 2. Creators API Endpoint (`code/server/index.ts`)
**Updated** `GET /api/creators/:identifier` to: **Updated** `GET /api/creators/:identifier` to:
- Accept username OR UUID in the `:identifier` parameter - Accept username OR UUID in the `:identifier` parameter
- Try **username first** (preferred) - Try **username first** (preferred)
- Fall back to **UUID lookup** if username lookup fails - Fall back to **UUID lookup** if username lookup fails
@ -41,6 +43,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 3. Creators Profile Component (`code/client/pages/creators/CreatorProfile.tsx`) ### 3. Creators Profile Component (`code/client/pages/creators/CreatorProfile.tsx`)
**Updated** to: **Updated** to:
- Import UUID/identifier resolver helpers - Import UUID/identifier resolver helpers
- Accept both username and UUID as route parameters - Accept both username and UUID as route parameters
- Resolve UUID to username for canonical URL redirect (optional) - Resolve UUID to username for canonical URL redirect (optional)
@ -49,6 +52,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 4. Ethos Artist Profile (`code/client/pages/ethos/ArtistProfile.tsx`) ### 4. Ethos Artist Profile (`code/client/pages/ethos/ArtistProfile.tsx`)
**Updated** to: **Updated** to:
- Import identifier resolver helpers - Import identifier resolver helpers
- Accept both username and userId as route parameters - Accept both username and userId as route parameters
- Resolve username → userId before API call - Resolve username → userId before API call
@ -57,6 +61,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 5. Passport Profile (`code/client/pages/ProfilePassport.tsx`) ### 5. Passport Profile (`code/client/pages/ProfilePassport.tsx`)
**Already supported** username-first with UUID fallback: **Already supported** username-first with UUID fallback:
- Has built-in `isUuid()` function - Has built-in `isUuid()` function
- Tries `getProfileByUsername()` first - Tries `getProfileByUsername()` first
- Falls back to `getProfileById()` if username lookup fails - Falls back to `getProfileById()` if username lookup fails
@ -65,6 +70,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 6. Creator Profile Validation (`code/api/creators.ts`) ### 6. Creator Profile Validation (`code/api/creators.ts`)
**Enforced usernames as required**: **Enforced usernames as required**:
- Username must be provided when creating creator profile - Username must be provided when creating creator profile
- Username must be unique (409 Conflict if duplicate) - Username must be unique (409 Conflict if duplicate)
- Username is normalized to lowercase - Username is normalized to lowercase
@ -74,12 +80,12 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
## Routes That Support Username-First with UUID Fallback ## Routes That Support Username-First with UUID Fallback
| Route | Type | Status | | Route | Type | Status |
|-------|------|--------| | ---------------------------- | -------- | ------------------ |
| `/creators/:identifier` | Frontend | ✅ Updated | | `/creators/:identifier` | Frontend | ✅ Updated |
| `/passport/:identifier` | Frontend | ✅ Already working | | `/passport/:identifier` | Frontend | ✅ Already working |
| `/ethos/artists/:identifier` | Frontend | ✅ Updated | | `/ethos/artists/:identifier` | Frontend | ✅ Updated |
| `/api/creators/:identifier` | Backend | ✅ Updated | | `/api/creators/:identifier` | Backend | ✅ Updated |
--- ---
@ -156,7 +162,8 @@ Return artist data ✅
```typescript ```typescript
// UUID regex pattern (standard RFC 4122) // 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; 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 { function isUUID(str: string): boolean {
return uuidPattern.test(str); return uuidPattern.test(str);
@ -166,6 +173,7 @@ function isUUID(str: string): boolean {
### Lookup Priority ### Lookup Priority
For any identifier: For any identifier:
1. **If it matches UUID pattern** → Try UUID lookup directly 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 2. **If it doesn't match UUID pattern** → Try username lookup first, then UUID fallback
3. **If both fail** → Return 404 Not Found 3. **If both fail** → Return 404 Not Found
@ -184,12 +192,12 @@ For any identifier:
### Using the Resolver Utilities ### Using the Resolver Utilities
```typescript ```typescript
import { import {
isUUID, isUUID,
resolveIdentifierToCreator, resolveIdentifierToCreator,
resolveIdentifierToUserId, resolveIdentifierToUserId,
resolveIdentifierToUsername resolveIdentifierToUsername,
} from '@/lib/identifier-resolver'; } from "@/lib/identifier-resolver";
// Check if string is UUID // Check if string is UUID
if (isUUID(userInput)) { if (isUUID(userInput)) {
@ -297,4 +305,3 @@ code/
✅ **Username-First with UUID Fallback is now implemented across the entire system.** ✅ **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. 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.

View file

@ -435,7 +435,9 @@ export function createServer() {
if (error || !project) { if (error || !project) {
console.log("[Passport Data API] Project not found:", projectSlug); console.log("[Passport Data API] Project not found:", projectSlug);
return res.status(404).json({ error: "Project not found", projectSlug }); return res
.status(404)
.json({ error: "Project not found", projectSlug });
} }
return res.json({ return res.json({
@ -4446,7 +4448,10 @@ export function createServer() {
} }
// Check if identifier is a UUID (username-first, UUID fallback) // Check if identifier is a UUID (username-first, UUID fallback)
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier); const isUUID =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
identifier,
);
let query = adminSupabase let query = adminSupabase
.from("aethex_creators") .from("aethex_creators")
@ -4477,11 +4482,16 @@ export function createServer() {
// If username lookup failed and it's a valid UUID format, try UUID // If username lookup failed and it's a valid UUID format, try UUID
if (error && !isUUID) { if (error && !isUUID) {
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier)) { if (
const { data: creatorByUUID, error: uuidError } = await adminSupabase /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
.from("aethex_creators") identifier,
.select( )
` ) {
const { data: creatorByUUID, error: uuidError } =
await adminSupabase
.from("aethex_creators")
.select(
`
id, id,
username, username,
bio, bio,
@ -4493,10 +4503,10 @@ export function createServer() {
created_at, created_at,
updated_at updated_at
`, `,
) )
.eq("is_discoverable", true) .eq("is_discoverable", true)
.eq("id", identifier) .eq("id", identifier)
.single(); .single();
if (!uuidError && creatorByUUID) { if (!uuidError && creatorByUUID) {
// Found by UUID, optionally redirect to username canonical URL // Found by UUID, optionally redirect to username canonical URL