266 lines
No EOL
7 KiB
TypeScript
266 lines
No EOL
7 KiB
TypeScript
import { BaseAdapter } from './base.adapter'
|
|
import type { Party } from '$lib/types/api/party'
|
|
import type { RequestOptions } from './types'
|
|
import { DEFAULT_ADAPTER_CONFIG } from './config'
|
|
|
|
/**
|
|
* API response for user data (already camelCased by BaseAdapter.transformResponse)
|
|
* Note: BaseAdapter automatically transforms snake_case to camelCase,
|
|
* so we receive granblueId, showGamertag, etc.
|
|
*/
|
|
interface ApiUserResponse {
|
|
id: string
|
|
username: string
|
|
language: string
|
|
private: boolean
|
|
gender: number
|
|
theme: string
|
|
role: number
|
|
granblueId?: number | string | null // API returns number, transformed to camelCase
|
|
showGamertag?: boolean // transformed from show_gamertag
|
|
showGranblueId?: boolean // transformed from show_granblue_id
|
|
collectionPrivacy?: number // transformed from collection_privacy (0=everyone, 1=crew_only, 2=private)
|
|
gamertag?: string
|
|
email?: string // Only included in settings view
|
|
avatar: {
|
|
picture: string
|
|
element: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transformed user info (camelCase for frontend)
|
|
* Uses our preferred naming convention (showCrewGamertag, crewGamertag)
|
|
*/
|
|
export interface UserInfo {
|
|
id: string
|
|
username: string
|
|
language: string
|
|
private: boolean
|
|
gender: number
|
|
theme: string
|
|
role: number
|
|
granblueId?: string
|
|
showCrewGamertag?: boolean
|
|
showGranblueId?: boolean
|
|
collectionPrivacy?: number
|
|
crewGamertag?: string
|
|
avatar: {
|
|
picture: string
|
|
element: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* User settings info - includes email (only returned by /users/me endpoint)
|
|
*/
|
|
export interface UserSettings extends UserInfo {
|
|
email: string
|
|
}
|
|
|
|
export interface UserProfile extends UserInfo {
|
|
parties?: Party[]
|
|
}
|
|
|
|
export interface UserProfileResponse {
|
|
user: UserProfile
|
|
items: Party[]
|
|
page: number
|
|
total?: number
|
|
totalPages?: number
|
|
perPage?: number
|
|
}
|
|
|
|
/**
|
|
* Transform API user response to frontend UserInfo format
|
|
* Renames API fields to our preferred naming convention
|
|
*/
|
|
function transformUserResponse(apiUser: ApiUserResponse): UserInfo {
|
|
return {
|
|
id: apiUser.id,
|
|
username: apiUser.username,
|
|
language: apiUser.language,
|
|
private: apiUser.private,
|
|
gender: apiUser.gender,
|
|
theme: apiUser.theme,
|
|
role: apiUser.role,
|
|
// granblueId comes as number from API, convert to string
|
|
granblueId: apiUser.granblueId != null ? String(apiUser.granblueId) : undefined,
|
|
// Rename showGamertag to showCrewGamertag
|
|
showCrewGamertag: apiUser.showGamertag,
|
|
// Privacy settings
|
|
showGranblueId: apiUser.showGranblueId,
|
|
collectionPrivacy: apiUser.collectionPrivacy,
|
|
// Rename gamertag to crewGamertag
|
|
crewGamertag: apiUser.gamertag,
|
|
avatar: apiUser.avatar
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform API user response to frontend UserSettings format (includes email)
|
|
*/
|
|
function transformSettingsResponse(apiUser: ApiUserResponse): UserSettings {
|
|
return {
|
|
...transformUserResponse(apiUser),
|
|
email: apiUser.email ?? ''
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adapter for user-related API operations
|
|
*/
|
|
export class UserAdapter extends BaseAdapter {
|
|
/**
|
|
* Get user information
|
|
*/
|
|
async getInfo(username: string, options?: RequestOptions): Promise<UserInfo> {
|
|
const result = await this.request<ApiUserResponse>(
|
|
`/users/info/${encodeURIComponent(username)}`,
|
|
options
|
|
)
|
|
return transformUserResponse(result)
|
|
}
|
|
|
|
/**
|
|
* Get user profile with their parties
|
|
*/
|
|
async getProfile(username: string, page = 1): Promise<UserProfileResponse> {
|
|
const params = page > 1 ? { page } : undefined
|
|
const response = await this.request<{
|
|
profile: ApiUserResponse & { parties?: Party[] }
|
|
meta?: {
|
|
count?: number
|
|
total_pages?: number
|
|
totalPages?: number
|
|
per_page?: number
|
|
perPage?: number
|
|
}
|
|
}>(`/users/${encodeURIComponent(username)}`, { params })
|
|
|
|
const items = Array.isArray(response.profile?.parties) ? response.profile.parties : []
|
|
|
|
// Transform API response to frontend format
|
|
const user: UserProfile = {
|
|
...transformUserResponse(response.profile),
|
|
parties: items
|
|
}
|
|
|
|
const result: UserProfileResponse = {
|
|
user,
|
|
items,
|
|
page
|
|
}
|
|
|
|
if (response.meta?.count !== undefined) {
|
|
result.total = response.meta.count
|
|
}
|
|
const totalPages = response.meta?.total_pages ?? response.meta?.totalPages
|
|
if (totalPages !== undefined) {
|
|
result.totalPages = totalPages
|
|
}
|
|
const perPage = response.meta?.per_page ?? response.meta?.perPage
|
|
if (perPage !== undefined) {
|
|
result.perPage = perPage
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Get user profile parties (for infinite scroll)
|
|
* Returns in standard paginated format
|
|
*/
|
|
async getProfileParties(username: string, page = 1): Promise<{
|
|
results: Party[]
|
|
page: number
|
|
total: number
|
|
totalPages: number
|
|
perPage: number
|
|
}> {
|
|
const response = await this.getProfile(username, page)
|
|
return {
|
|
results: response.items,
|
|
page: response.page,
|
|
total: response.total || 0,
|
|
totalPages: response.totalPages || 1,
|
|
perPage: response.perPage || 20
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user's favorite parties
|
|
*/
|
|
async getFavorites(options: { page?: number } = {}): Promise<{
|
|
items: Party[]
|
|
page: number
|
|
total: number
|
|
totalPages: number
|
|
perPage: number
|
|
}> {
|
|
const { page = 1 } = options
|
|
const params = page > 1 ? { page } : undefined
|
|
|
|
const response = await this.request<{
|
|
results: Party[]
|
|
total: number
|
|
total_pages?: number
|
|
totalPages?: number
|
|
per?: number
|
|
}>('/parties/favorites', { params })
|
|
|
|
return {
|
|
items: response.results,
|
|
page,
|
|
total: response.total,
|
|
totalPages: response.total_pages || response.totalPages || 1,
|
|
perPage: response.per || 20
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check username availability
|
|
*/
|
|
async checkUsernameAvailability(username: string): Promise<{ available: boolean }> {
|
|
return this.request<{ available: boolean }>(`/check/username`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username })
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Check email availability
|
|
*/
|
|
async checkEmailAvailability(email: string): Promise<{ available: boolean }> {
|
|
return this.request<{ available: boolean }>(`/check/email`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email })
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Update user profile
|
|
*/
|
|
async updateProfile(updates: Partial<UserInfo>): Promise<UserInfo> {
|
|
// Wrap updates in 'user' key as required by Rails backend
|
|
const result = await this.request<ApiUserResponse>('/users/me', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ user: updates })
|
|
})
|
|
|
|
// Clear cache for current user after update
|
|
this.clearCache('/users/me')
|
|
|
|
return transformUserResponse(result)
|
|
}
|
|
|
|
/**
|
|
* Get current user settings (includes email - only for settings modal)
|
|
*/
|
|
async getCurrentUser(): Promise<UserSettings> {
|
|
const result = await this.request<ApiUserResponse>('/users/me')
|
|
return transformSettingsResponse(result)
|
|
}
|
|
}
|
|
|
|
export const userAdapter = new UserAdapter(DEFAULT_ADAPTER_CONFIG) |