feat(web): add profile management
Some checks failed
CI / validate (push) Successful in 19m25s
CI / validate (pull_request) Failing after 9m54s

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-08-20 19:24:50 +05:00
parent fc205c232f
commit 41f4633dca
12 changed files with 687 additions and 15 deletions

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { profileAvatarUrl, visibleProfile } from './profile'
const profile = {
id: '00000000-0000-0000-0000-000000000001',
display_name: 'Aster Vale',
description: 'Keeper of impossible maps.',
avatar_path: '00000000-0000-0000-0000-000000000001/avatar',
created_at: '2026-08-20T10:00:00.000Z',
updated_at: '2026-08-20T11:00:00.000Z',
}
describe('profile presentation', () => {
it('builds a cache-busted public avatar URL', () => {
expect(profileAvatarUrl(profile, 'https://example.supabase.co/')).toBe(
'https://example.supabase.co/storage/v1/object/public/profile-avatars/00000000-0000-0000-0000-000000000001/avatar?v=2026-08-20T11%3A00%3A00.000Z',
)
})
it('does not expose the storage path in a visible profile', () => {
const visible = visibleProfile(profile, 'https://example.supabase.co')
expect(visible).not.toHaveProperty('avatar_path')
expect(visible.avatar_url).toContain('/profile-avatars/')
})
it('returns no URL when the profile has no avatar', () => {
expect(profileAvatarUrl({ ...profile, avatar_path: null }, 'https://example.supabase.co')).toBeNull()
})
})

View File

@@ -0,0 +1,26 @@
export interface ProfileRecord {
id: string
display_name: string
description: string
avatar_path: string | null
created_at: string
updated_at: string
}
export function profileAvatarUrl(profile: Pick<ProfileRecord, 'avatar_path' | 'updated_at'>, supabaseUrl: string): string | null {
if (!profile.avatar_path) return null
const encodedPath = profile.avatar_path.split('/').map(encodeURIComponent).join('/')
const version = encodeURIComponent(profile.updated_at)
return `${supabaseUrl.replace(/\/$/, '')}/storage/v1/object/public/profile-avatars/${encodedPath}?v=${version}`
}
export function visibleProfile(profile: ProfileRecord, supabaseUrl: string) {
return {
id: profile.id,
display_name: profile.display_name,
description: profile.description,
avatar_url: profileAvatarUrl(profile, supabaseUrl),
created_at: profile.created_at,
updated_at: profile.updated_at,
}
}