feat: restore profile settings API integration
Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
90
apps/web-client/src/services/api/storage.api.ts
Normal file
90
apps/web-client/src/services/api/storage.api.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { ApiResponse } from '@goodgo/types';
|
||||
|
||||
import { apiClient } from './client';
|
||||
|
||||
/**
|
||||
* EN: Raw upload response from storage service.
|
||||
* VI: Response upload thô từ storage service.
|
||||
*/
|
||||
interface UploadFileResult {
|
||||
success: boolean;
|
||||
fileId?: string;
|
||||
objectKey?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: CDN URL response payload.
|
||||
* VI: Payload response URL CDN.
|
||||
*/
|
||||
interface CdnUrlResult {
|
||||
url: string;
|
||||
isCDN: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Normalized avatar upload response.
|
||||
* VI: Response upload avatar đã chuẩn hóa.
|
||||
*/
|
||||
export interface UploadAvatarResult {
|
||||
fileId: string;
|
||||
objectKey?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Storage API for avatar upload flow.
|
||||
* VI: Storage API cho luồng upload avatar.
|
||||
*/
|
||||
export const storageApi = {
|
||||
/**
|
||||
* EN: Upload avatar as a public file and resolve CDN/fallback URL.
|
||||
* VI: Upload avatar ở chế độ public và lấy URL CDN/fallback.
|
||||
*/
|
||||
uploadAvatar: async (file: File): Promise<ApiResponse<UploadAvatarResult>> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const uploadResponse = await apiClient.post<UploadFileResult>(
|
||||
'/files/upload?accessLevel=public',
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const uploadedFileId = uploadResponse.data?.fileId;
|
||||
if (!uploadResponse.success || !uploadResponse.data?.success || !uploadedFileId) {
|
||||
throw new Error(
|
||||
uploadResponse.data?.error ||
|
||||
uploadResponse.error?.message ||
|
||||
'Failed to upload avatar'
|
||||
);
|
||||
}
|
||||
|
||||
let resolvedUrl = '';
|
||||
try {
|
||||
const cdnUrlResponse = await apiClient.get<CdnUrlResult>(
|
||||
`/files/${uploadedFileId}/cdn-url`
|
||||
);
|
||||
resolvedUrl = cdnUrlResponse.data?.url ?? '';
|
||||
} catch {
|
||||
// EN: Fallback keeps profile update possible even if CDN URL fetch fails.
|
||||
// VI: Fallback vẫn cho phép cập nhật profile nếu lấy CDN URL thất bại.
|
||||
resolvedUrl = '';
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
fileId: uploadedFileId,
|
||||
objectKey: uploadResponse.data.objectKey,
|
||||
url: resolvedUrl,
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -9,16 +9,21 @@ import { apiClient } from './client';
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
userId: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phone?: string;
|
||||
phoneVerified?: boolean;
|
||||
bio?: string;
|
||||
avatarUrl?: string;
|
||||
customFields?: Record<string, unknown>;
|
||||
preferences?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
timezone?: string;
|
||||
locale?: string;
|
||||
phoneNumber?: {
|
||||
countryCode: string;
|
||||
nationalNumber: string;
|
||||
};
|
||||
attributes: Array<{
|
||||
key: string;
|
||||
value: string;
|
||||
valueType: string;
|
||||
}>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,13 +31,19 @@ export interface UserProfile {
|
||||
* VI: DTO cập nhật profile người dùng
|
||||
*/
|
||||
export interface UpdateUserProfileDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phone?: string;
|
||||
avatarUrl?: string;
|
||||
customFields?: Record<string, unknown>;
|
||||
preferences?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
bio?: string;
|
||||
timezone?: string;
|
||||
locale?: string;
|
||||
avatarUrl?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Set profile attribute DTO.
|
||||
* VI: DTO đặt thuộc tính profile.
|
||||
*/
|
||||
export interface SetProfileAttributeDto {
|
||||
value: string;
|
||||
valueType?: 'String' | 'Number' | 'Boolean' | 'Date' | 'Json';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +58,7 @@ export const userApi = {
|
||||
* @param userId - User ID / ID người dùng
|
||||
*/
|
||||
getProfile: async (userId: string): Promise<ApiResponse<UserProfile>> => {
|
||||
return apiClient.get(`/identity/users/${userId}/profile`);
|
||||
return apiClient.get(`/users/${userId}/profile`);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -61,7 +72,25 @@ export const userApi = {
|
||||
userId: string,
|
||||
data: UpdateUserProfileDto
|
||||
): Promise<ApiResponse<UserProfile>> => {
|
||||
return apiClient.put(`/identity/users/${userId}/profile`, data);
|
||||
return apiClient.put(`/users/${userId}/profile`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Set custom profile attribute.
|
||||
* VI: Đặt thuộc tính tùy chỉnh cho profile.
|
||||
*/
|
||||
setProfileAttribute: async (
|
||||
userId: string,
|
||||
key: string,
|
||||
payload: SetProfileAttributeDto
|
||||
): Promise<ApiResponse<{ key: string; value: string; valueType: string }>> => {
|
||||
return apiClient.put(
|
||||
`/users/${userId}/profile/attributes/${encodeURIComponent(key)}`,
|
||||
{
|
||||
value: payload.value,
|
||||
valueType: payload.valueType ?? 'String',
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -75,9 +104,7 @@ export const userApi = {
|
||||
userId: string,
|
||||
avatarUrl: string
|
||||
): Promise<ApiResponse<UserProfile>> => {
|
||||
return apiClient.post(`/identity/users/${userId}/profile/avatar`, {
|
||||
avatarUrl,
|
||||
});
|
||||
return apiClient.put(`/users/${userId}/profile`, { avatarUrl });
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -87,6 +114,6 @@ export const userApi = {
|
||||
* @param userId - User ID / ID người dùng
|
||||
*/
|
||||
deleteAvatar: async (userId: string): Promise<ApiResponse> => {
|
||||
return apiClient.delete(`/identity/users/${userId}/profile/avatar`);
|
||||
return apiClient.put(`/users/${userId}/profile`, { avatarUrl: null });
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user