Prettier format pending files
This commit is contained in:
parent
0180c560cc
commit
f9465d75c2
7 changed files with 114 additions and 68 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -188,8 +196,8 @@ 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue