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;
// Validate required fields
if (!username || typeof username !== "string" || username.trim().length === 0) {
if (
!username ||
typeof username !== "string" ||
username.trim().length === 0
) {
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" } },
);
}
@ -234,10 +240,7 @@ export async function updateCreatorProfile(req: Request, userId: string) {
const normalizedUsername = username.trim().toLowerCase();
if (
currentCreator &&
currentCreator.username !== normalizedUsername
) {
if (currentCreator && currentCreator.username !== normalizedUsername) {
// Username is being changed, check if new username exists
const { data: existingCreator } = await supabase
.from("aethex_creators")
@ -265,12 +268,17 @@ export async function updateCreatorProfile(req: Request, userId: string) {
if (bio !== undefined) updateData.bio = bio;
if (skills !== undefined) updateData.skills = skills;
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 (arm_affiliations !== undefined) updateData.arm_affiliations = arm_affiliations;
if (is_discoverable !== undefined) 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;
if (arm_affiliations !== undefined)
updateData.arm_affiliations = arm_affiliations;
if (is_discoverable !== undefined)
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
.from("aethex_creators")

View file

@ -81,7 +81,10 @@ export default function CreatorProfile({
};
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 hasSkills = creatorData.skills.length > 0;
return hasUsername && hasPrimaryArm && hasSkills;
@ -153,7 +156,8 @@ export default function CreatorProfile({
/>
</div>
<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>
{(data?.username?.length || 0) === 0 && (
<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
*/
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);
}
@ -26,14 +27,18 @@ export async function resolveIdentifierToCreator(
try {
// 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) {
return await usernameResponse.json();
}
// If username lookup failed and identifier is a UUID, try UUID lookup
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) {
return await uuidResponse.json();
}
@ -49,7 +54,9 @@ export async function resolveIdentifierToCreator(
/**
* 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 (isUUID(identifier)) {
@ -69,7 +76,9 @@ export async function resolveIdentifierToUserId(identifier: string): Promise<str
/**
* 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 (!isUUID(identifier)) {

View file

@ -321,7 +321,9 @@ export default function Onboarding() {
// Use username from form if provided, otherwise generate from first name
const usernameFromForm = data.username?.trim().toLowerCase();
const generatedUsername = normalizedFirst.replace(/\s+/g, "_").toLowerCase();
const generatedUsername = normalizedFirst
.replace(/\s+/g, "_")
.toLowerCase();
const finalUsername = usernameFromForm || generatedUsername;
if (!finalUsername) {
@ -377,23 +379,24 @@ export default function Onboarding() {
);
// Create creator profile if they provided primary arm
const creatorProfilePromise = data.creatorProfile.primaryArm && finalUsername
? fetch(`${API_BASE}/api/creators`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: user.id,
username: finalUsername,
bio: data.creatorProfile.bio || null,
avatar_url: null, // Can be added later in profile settings
experience_level: data.experience.level || "junior",
primary_arm: data.creatorProfile.primaryArm,
arm_affiliations: [data.creatorProfile.primaryArm],
skills: data.creatorProfile.skills || [],
is_discoverable: true,
}),
})
: Promise.resolve();
const creatorProfilePromise =
data.creatorProfile.primaryArm && finalUsername
? fetch(`${API_BASE}/api/creators`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: user.id,
username: finalUsername,
bio: data.creatorProfile.bio || null,
avatar_url: null, // Can be added later in profile settings
experience_level: data.experience.level || "junior",
primary_arm: data.creatorProfile.primaryArm,
arm_affiliations: [data.creatorProfile.primaryArm],
skills: data.creatorProfile.skills || [],
is_discoverable: true,
}),
})
: Promise.resolve();
// Save followed arms
const followedArmsPromises = (data.followedArms || []).map((armId) =>
@ -405,7 +408,7 @@ export default function Onboarding() {
arm_id: armId,
action: "follow",
}),
})
}),
);
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="px-4 py-2 rounded-full bg-aethex-500/10 border border-aethex-400/40">
<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>
</div>
<Link
@ -559,12 +563,14 @@ export default function Onboarding() {
</h2>
</div>
<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 === 2 && "Tell us about your experience"}
{currentStep === 3 && "What are your interests?"}
{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 === 7 && "You're ready to go!"}
</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();
setArtist(data);
} 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.
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)
@ -18,15 +19,16 @@ This means:
**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
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
@ -41,6 +43,7 @@ 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)
@ -49,6 +52,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 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
@ -57,6 +61,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 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
@ -65,6 +70,7 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
### 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
@ -74,12 +80,12 @@ GET /api/creators/550e8400-... // ✅ UUID lookup fallback
## 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 |
| Route | Type | Status |
| ---------------------------- | -------- | ------------------ |
| `/creators/:identifier` | Frontend | ✅ Updated |
| `/passport/:identifier` | Frontend | ✅ Already working |
| `/ethos/artists/:identifier` | Frontend | ✅ Updated |
| `/api/creators/:identifier` | Backend | ✅ Updated |
---
@ -156,7 +162,8 @@ Return artist data ✅
```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;
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);
@ -166,6 +173,7 @@ function isUUID(str: string): boolean {
### 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
@ -184,12 +192,12 @@ For any identifier:
### Using the Resolver Utilities
```typescript
import {
isUUID,
import {
isUUID,
resolveIdentifierToCreator,
resolveIdentifierToUserId,
resolveIdentifierToUsername
} from '@/lib/identifier-resolver';
resolveIdentifierToUsername,
} from "@/lib/identifier-resolver";
// Check if string is UUID
if (isUUID(userInput)) {
@ -297,4 +305,3 @@ code/
✅ **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.

View file

@ -435,7 +435,9 @@ export function createServer() {
if (error || !project) {
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({
@ -4446,7 +4448,10 @@ export function createServer() {
}
// 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
.from("aethex_creators")
@ -4477,11 +4482,16 @@ export function createServer() {
// If username lookup failed and it's a valid UUID format, try UUID
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)) {
const { data: creatorByUUID, error: uuidError } = await adminSupabase
.from("aethex_creators")
.select(
`
if (
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
identifier,
)
) {
const { data: creatorByUUID, error: uuidError } =
await adminSupabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
@ -4493,10 +4503,10 @@ export function createServer() {
created_at,
updated_at
`,
)
.eq("is_discoverable", true)
.eq("id", identifier)
.single();
)
.eq("is_discoverable", true)
.eq("id", identifier)
.single();
if (!uuidError && creatorByUUID) {
// Found by UUID, optionally redirect to username canonical URL