@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
386 lines (353 loc) • 12.2 kB
text/typescript
/**
* User Management Methods Mixin
*/
import type { User, Notification, SearchProfilesResponse, PaginationInfo } from '../../models/interfaces';
import type { OxyServicesBase } from '../OxyServices.base';
import { buildSearchParams, buildPaginationParams, type PaginationParams } from '../../utils/apiUtils';
export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T) {
return class extends Base {
constructor(...args: any[]) {
super(...(args as [any]));
}
/**
* Get profile by username
*/
async getProfileByUsername(username: string): Promise<User> {
try {
return await this.makeRequest<User>('GET', `/api/profiles/username/${username}`, undefined, {
cache: true,
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
});
} catch (error) {
throw this.handleError(error);
}
}
/**
* Search user profiles
*/
async searchProfiles(query: string, pagination?: PaginationParams): Promise<SearchProfilesResponse> {
try {
const params = { query, ...pagination };
const searchParams = buildSearchParams(params);
const paramsObj = Object.fromEntries(searchParams.entries());
const response = await this.makeRequest<SearchProfilesResponse | User[]>(
'GET',
'/api/profiles/search',
paramsObj,
{
cache: true,
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
}
);
// New API shape: { data: User[], pagination: {...} }
const isSearchProfilesResponse = (payload: unknown): payload is SearchProfilesResponse =>
typeof payload === 'object' &&
payload !== null &&
Array.isArray((payload as SearchProfilesResponse).data);
if (isSearchProfilesResponse(response)) {
const typedResponse = response;
const paginationInfo: PaginationInfo = typedResponse.pagination ?? {
total: typedResponse.data.length,
limit: pagination?.limit ?? typedResponse.data.length,
offset: pagination?.offset ?? 0,
hasMore: typedResponse.data.length === (pagination?.limit ?? typedResponse.data.length) &&
(pagination?.limit ?? typedResponse.data.length) > 0,
};
return {
data: typedResponse.data,
pagination: paginationInfo,
};
}
// Legacy API shape: returns raw User[]
if (Array.isArray(response)) {
const fallbackLimit = pagination?.limit ?? response.length;
const fallbackPagination: PaginationInfo = {
total: response.length,
limit: fallbackLimit,
offset: pagination?.offset ?? 0,
hasMore: fallbackLimit > 0 && response.length === fallbackLimit,
};
return { data: response, pagination: fallbackPagination };
}
// If response is unexpected, throw an error
throw new Error('Unexpected search response format');
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get profile recommendations
*/
async getProfileRecommendations(): Promise<Array<{
id: string;
username: string;
name?: { first?: string; last?: string; full?: string };
description?: string;
_count?: { followers: number; following: number };
[key: string]: any;
}>> {
return this.withAuthRetry(async () => {
return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, { cache: true });
}, 'getProfileRecommendations');
}
/**
* Get user by ID
*/
async getUserById(userId: string): Promise<User> {
try {
return await this.makeRequest<User>('GET', `/api/users/${userId}`, undefined, {
cache: true,
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
});
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get current user
*/
async getCurrentUser(): Promise<User> {
return this.withAuthRetry(async () => {
return await this.makeRequest<User>('GET', '/api/users/me', undefined, {
cache: true,
cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
});
}, 'getCurrentUser');
}
/**
* Update user profile
*/
async updateProfile(updates: Record<string, any>): Promise<User> {
try {
return await this.makeRequest<User>('PUT', '/api/users/me', updates, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get privacy settings for a user
* @param userId - The user ID (defaults to current user)
*/
async getPrivacySettings(userId?: string): Promise<any> {
try {
const id = userId || (await this.getCurrentUser()).id;
return await this.makeRequest<any>('GET', `/api/privacy/${id}/privacy`, undefined, {
cache: true,
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
});
} catch (error) {
throw this.handleError(error);
}
}
/**
* Update privacy settings
* @param settings - Partial privacy settings object
* @param userId - The user ID (defaults to current user)
*/
async updatePrivacySettings(settings: Record<string, any>, userId?: string): Promise<any> {
try {
const id = userId || (await this.getCurrentUser()).id;
return await this.makeRequest<any>('PATCH', `/api/privacy/${id}/privacy`, settings, {
cache: false,
});
} catch (error) {
throw this.handleError(error);
}
}
/**
* Request account verification
*/
async requestAccountVerification(reason: string, evidence?: string): Promise<{ message: string; requestId: string }> {
try {
return await this.makeRequest<{ message: string; requestId: string }>('POST', '/api/users/verify/request', {
reason,
evidence,
}, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Download account data export
*/
async downloadAccountData(format: 'json' | 'csv' = 'json'): Promise<Blob> {
try {
// Use httpService for blob responses (it handles blob responses automatically)
const result = await this.getClient().request<Blob>({
method: 'GET',
url: `/api/users/me/data`,
params: { format },
cache: false,
});
return result;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Delete account permanently
* @param password - User password for confirmation
* @param confirmText - Confirmation text (usually username)
*/
async deleteAccount(password: string, confirmText: string): Promise<{ message: string }> {
try {
return await this.makeRequest<{ message: string }>('DELETE', '/api/users/me', {
password,
confirmText,
}, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Update user by ID (admin function)
*/
async updateUser(userId: string, updates: Record<string, any>): Promise<User> {
try {
return await this.makeRequest<User>('PUT', `/api/users/${userId}`, updates, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Follow a user
*/
async followUser(userId: string): Promise<{ success: boolean; message: string }> {
try {
return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Unfollow a user
*/
async unfollowUser(userId: string): Promise<{ success: boolean; message: string }> {
try {
return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get follow status
*/
async getFollowStatus(userId: string): Promise<{ isFollowing: boolean }> {
try {
return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
cache: true,
cacheTTL: 1 * 60 * 1000, // 1 minute cache
});
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get user followers
*/
async getUserFollowers(
userId: string,
pagination?: PaginationParams
): Promise<{ followers: User[]; total: number; hasMore: boolean }> {
try {
const params = buildPaginationParams(pagination || {});
const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/followers`, params, {
cache: true,
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
});
return {
followers: response.data || [],
total: response.pagination.total,
hasMore: response.pagination.hasMore,
};
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get user following
*/
async getUserFollowing(
userId: string,
pagination?: PaginationParams
): Promise<{ following: User[]; total: number; hasMore: boolean }> {
try {
const params = buildPaginationParams(pagination || {});
const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/following`, params, {
cache: true,
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
});
return {
following: response.data || [],
total: response.pagination.total,
hasMore: response.pagination.hasMore,
};
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get notifications
*/
async getNotifications(): Promise<Notification[]> {
return this.withAuthRetry(async () => {
return await this.makeRequest<Notification[]>('GET', '/api/notifications', undefined, {
cache: false, // Don't cache notifications - always get fresh data
});
}, 'getNotifications');
}
/**
* Get unread notification count
*/
async getUnreadCount(): Promise<number> {
try {
const res = await this.makeRequest<{ count: number }>('GET', '/api/notifications/unread-count', undefined, {
cache: false, // Don't cache unread count - always get fresh data
});
return res.count;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Create notification
*/
async createNotification(data: Partial<Notification>): Promise<Notification> {
try {
return await this.makeRequest<Notification>('POST', '/api/notifications', data, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Mark notification as read
*/
async markNotificationAsRead(notificationId: string): Promise<void> {
try {
await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Mark all notifications as read
*/
async markAllNotificationsAsRead(): Promise<void> {
try {
await this.makeRequest('PUT', '/api/notifications/read-all', undefined, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
/**
* Delete notification
*/
async deleteNotification(notificationId: string): Promise<void> {
try {
await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, { cache: false });
} catch (error) {
throw this.handleError(error);
}
}
};
}