Files
library/apps/frontend/src/app/services/likes.service.ts
T

172 lines
5.0 KiB
TypeScript

/**
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Injectable, inject, signal, computed } from '@angular/core';
import { Observable, map, tap, catchError, of } from 'rxjs';
import { ApiService } from './api.service';
import { CreateLikeDto, LikeResponse, LikedItemDto, Like } from '@library/shared-types';
interface LikeState {
entityType: Like['entityType'];
entityId: string;
liked: boolean;
count: number;
}
@Injectable({
providedIn: 'root'
})
export class LikesService {
private api = inject(ApiService);
// Store like states for caching
private likeStates = signal<Map<string, LikeState>>(new Map());
/**
* Toggle like on an item.
*/
toggleLike(entityType: Like['entityType'], entityId: string): Observable<LikeResponse> {
const dto: CreateLikeDto = { entityType, entityId };
return this.api.post<LikeResponse>('/likes/toggle', dto).pipe(
tap(response => {
const key = this.getCacheKey(entityType, entityId);
this.likeStates.update(states => {
const newStates = new Map(states);
newStates.set(key, {
entityType,
entityId,
liked: response.liked,
count: response.count
});
return newStates;
});
}),
catchError(() => {
// On error, return current state or default
const key = this.getCacheKey(entityType, entityId);
const currentState = this.likeStates().get(key);
return of({
liked: currentState?.liked || false,
count: currentState?.count || 0
});
})
);
}
/**
* Get like count for an item.
*/
getLikeCount(entityType: Like['entityType'], entityId: string): Observable<number> {
return this.api.get<{ count: number }>(`/likes/count?entityType=${entityType}&entityId=${entityId}`).pipe(
map(response => response.count),
tap(count => {
const key = this.getCacheKey(entityType, entityId);
this.likeStates.update(states => {
const newStates = new Map(states);
const currentState = newStates.get(key);
newStates.set(key, {
entityType,
entityId,
liked: currentState?.liked || false,
count
});
return newStates;
});
})
);
}
/**
* Check if current user has liked an item.
*/
getUserLikeStatus(entityType: Like['entityType'], entityId: string): Observable<boolean> {
return this.api.get<{ liked: boolean }>(`/likes/status?entityType=${entityType}&entityId=${entityId}`).pipe(
map(response => response.liked),
tap(liked => {
const key = this.getCacheKey(entityType, entityId);
this.likeStates.update(states => {
const newStates = new Map(states);
const currentState = newStates.get(key);
newStates.set(key, {
entityType,
entityId,
liked,
count: currentState?.count || 0
});
return newStates;
});
})
);
}
/**
* Get all items liked by the current user.
*/
getUserLikedItems(entityType?: Like['entityType']): Observable<LikedItemDto[]> {
const query = entityType ? `?entityType=${entityType}` : '';
return this.api.get<LikedItemDto[]>(`/likes/user${query}`);
}
/**
* Get bulk like statuses for multiple items.
*/
getBulkLikeStatuses(items: Array<{ entityType: string; entityId: string }>): Observable<Array<{
entityType: string;
entityId: string;
liked: boolean;
count: number;
}>> {
return this.api.post<Array<{
entityType: string;
entityId: string;
liked: boolean;
count: number;
}>>('/likes/bulk-status', { items }).pipe(
tap(results => {
this.likeStates.update(states => {
const newStates = new Map(states);
results.forEach(result => {
const key = this.getCacheKey(result.entityType as Like['entityType'], result.entityId);
newStates.set(key, {
entityType: result.entityType as Like['entityType'],
entityId: result.entityId,
liked: result.liked,
count: result.count
});
});
return newStates;
});
})
);
}
/**
* Get like state from cache.
*/
getCachedLikeState(entityType: Like['entityType'], entityId: string): LikeState | undefined {
const key = this.getCacheKey(entityType, entityId);
return this.likeStates().get(key);
}
/**
* Create computed signal for specific item's like state.
*/
createLikeStateSignal(entityType: Like['entityType'], entityId: string) {
const key = this.getCacheKey(entityType, entityId);
return computed(() => {
const state = this.likeStates().get(key);
return {
liked: state?.liked || false,
count: state?.count || 0
};
});
}
private getCacheKey(entityType: Like['entityType'], entityId: string): string {
return `${entityType}:${entityId}`;
}
}