318 lines
9.1 KiB
TypeScript
318 lines
9.1 KiB
TypeScript
import { HttpClient } from '@angular/common/http';
|
|
import { Injectable, inject } from '@angular/core';
|
|
import { Observable } from 'rxjs';
|
|
import { of, tap, throwError } from 'rxjs';
|
|
import {
|
|
AddListItemRequest,
|
|
ApplyListCleanupResponse,
|
|
CreateListRequest,
|
|
CreateListWithItemSuggestionsResponse,
|
|
ListCleanupSuggestionsResponse,
|
|
ListItemSuggestionsResponse,
|
|
UpdateListItemRequest,
|
|
UpdateListRequest,
|
|
UserList,
|
|
} from './lists.models';
|
|
import { ListTemplate } from '../templates/templates.models';
|
|
import { OfflineSyncService } from '../offline/offline-sync.service';
|
|
import { OnlineStatusService } from '../offline/online-status.service';
|
|
|
|
const LIST_CACHE_KEY = 'listify.lists.cache.v1';
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class ListsService {
|
|
private readonly http = inject(HttpClient);
|
|
private readonly offlineSync = inject(OfflineSyncService);
|
|
private readonly onlineStatus = inject(OnlineStatusService);
|
|
private readonly apiUrl = '/api/lists';
|
|
|
|
listLists(): Observable<UserList[]> {
|
|
if (!this.onlineStatus.online()) {
|
|
return of(this.readCachedLists());
|
|
}
|
|
|
|
return this.http
|
|
.get<UserList[]>(this.apiUrl)
|
|
.pipe(tap((lists) => this.writeCachedLists(lists)));
|
|
}
|
|
|
|
getList(listId: string): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
const cachedList = this.readCachedLists().find((list) => list.id === listId);
|
|
|
|
if (cachedList) {
|
|
return of(cachedList);
|
|
}
|
|
|
|
return throwError(() => new Error('Liste ist offline nicht im Cache.'));
|
|
}
|
|
|
|
return this.http
|
|
.get<UserList>(`${this.apiUrl}/${listId}`)
|
|
.pipe(tap((list) => this.upsertCachedList(list)));
|
|
}
|
|
|
|
createList(data: CreateListRequest): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Neue Listen koennen offline noch nicht erstellt werden.'),
|
|
);
|
|
}
|
|
|
|
return this.http.post<UserList>(this.apiUrl, data);
|
|
}
|
|
|
|
createListWithItemSuggestions(
|
|
data: CreateListRequest,
|
|
): Observable<CreateListWithItemSuggestionsResponse> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Listen mit KI koennen nur online erstellt werden.'),
|
|
);
|
|
}
|
|
|
|
return this.http
|
|
.post<CreateListWithItemSuggestionsResponse>(
|
|
`${this.apiUrl}/with-item-suggestions`,
|
|
data,
|
|
)
|
|
.pipe(tap((response) => this.upsertCachedList(response.list)));
|
|
}
|
|
|
|
updateList(listId: string, data: UpdateListRequest): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
const updatedList = this.updateCachedList(listId, (list) => ({
|
|
...list,
|
|
...data,
|
|
updatedAt: new Date().toISOString(),
|
|
}));
|
|
|
|
this.offlineSync.enqueueUpdateList(listId, data);
|
|
return updatedList
|
|
? of(updatedList)
|
|
: throwError(() => new Error('Liste ist offline nicht im Cache.'));
|
|
}
|
|
|
|
return this.http
|
|
.patch<UserList>(`${this.apiUrl}/${listId}`, data)
|
|
.pipe(tap((list) => this.upsertCachedList(list)));
|
|
}
|
|
|
|
deleteList(listId: string): Observable<{ message: string }> {
|
|
if (!this.onlineStatus.online()) {
|
|
this.removeCachedList(listId);
|
|
this.offlineSync.enqueueDeleteList(listId);
|
|
return of({ message: 'List queued for deletion.' });
|
|
}
|
|
|
|
return this.http
|
|
.delete<{ message: string }>(`${this.apiUrl}/${listId}`)
|
|
.pipe(tap(() => this.removeCachedList(listId)));
|
|
}
|
|
|
|
shareList(listId: string, userId: string): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Freigaben koennen offline nicht geaendert werden.'),
|
|
);
|
|
}
|
|
|
|
return this.http.post<UserList>(`${this.apiUrl}/${listId}/shares`, { userId });
|
|
}
|
|
|
|
removeShare(listId: string, userId: string): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Freigaben koennen offline nicht geaendert werden.'),
|
|
);
|
|
}
|
|
|
|
return this.http.delete<UserList>(`${this.apiUrl}/${listId}/shares/${userId}`);
|
|
}
|
|
|
|
addItem(listId: string, data: AddListItemRequest): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
const updatedList = this.updateCachedList(listId, (list) => ({
|
|
...list,
|
|
items: [
|
|
...list.items,
|
|
{
|
|
id: `offline-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
title: data.title,
|
|
notes: data.notes,
|
|
quantity: data.quantity,
|
|
required: data.required ?? true,
|
|
checked: false,
|
|
position: list.items.length,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
},
|
|
],
|
|
updatedAt: new Date().toISOString(),
|
|
}));
|
|
|
|
this.offlineSync.enqueueAddItem(listId, data);
|
|
return updatedList
|
|
? of(updatedList)
|
|
: throwError(() => new Error('Liste ist offline nicht im Cache.'));
|
|
}
|
|
|
|
return this.http
|
|
.post<UserList>(`${this.apiUrl}/${listId}/items`, data)
|
|
.pipe(tap((list) => this.upsertCachedList(list)));
|
|
}
|
|
|
|
suggestItems(listId: string): Observable<ListItemSuggestionsResponse> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Smart Suggestions sind nur online verfuegbar.'),
|
|
);
|
|
}
|
|
|
|
return this.http.post<ListItemSuggestionsResponse>(
|
|
`${this.apiUrl}/${listId}/item-suggestions`,
|
|
{},
|
|
);
|
|
}
|
|
|
|
suggestCleanup(listId: string): Observable<ListCleanupSuggestionsResponse> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('AI Cleanup ist nur online verfuegbar.'),
|
|
);
|
|
}
|
|
|
|
return this.http.post<ListCleanupSuggestionsResponse>(
|
|
`${this.apiUrl}/${listId}/cleanup-suggestions`,
|
|
{},
|
|
);
|
|
}
|
|
|
|
applyCleanup(listId: string): Observable<ApplyListCleanupResponse> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('AI Cleanup ist nur online verfuegbar.'),
|
|
);
|
|
}
|
|
|
|
return this.http
|
|
.post<ApplyListCleanupResponse>(
|
|
`${this.apiUrl}/${listId}/apply-cleanup`,
|
|
{},
|
|
)
|
|
.pipe(tap((response) => this.upsertCachedList(response.list)));
|
|
}
|
|
|
|
createTemplateFromList(listId: string): Observable<ListTemplate> {
|
|
if (!this.onlineStatus.online()) {
|
|
return throwError(
|
|
() => new Error('Templates koennen nur online aus Listen erstellt werden.'),
|
|
);
|
|
}
|
|
|
|
return this.http.post<ListTemplate>(`${this.apiUrl}/${listId}/template`, {});
|
|
}
|
|
|
|
updateItem(
|
|
listId: string,
|
|
itemId: string,
|
|
data: UpdateListItemRequest,
|
|
): Observable<UserList> {
|
|
if (!this.onlineStatus.online()) {
|
|
const updatedList = this.updateCachedList(listId, (list) => ({
|
|
...list,
|
|
items: list.items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
...data,
|
|
checkedAt:
|
|
data.checked === true
|
|
? new Date().toISOString()
|
|
: data.checked === false
|
|
? undefined
|
|
: item.checkedAt,
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
: item,
|
|
),
|
|
updatedAt: new Date().toISOString(),
|
|
}));
|
|
|
|
if (!itemId.startsWith('offline-')) {
|
|
this.offlineSync.enqueueUpdateItem(listId, itemId, data);
|
|
}
|
|
|
|
return updatedList
|
|
? of(updatedList)
|
|
: throwError(() => new Error('Liste ist offline nicht im Cache.'));
|
|
}
|
|
|
|
return this.http
|
|
.patch<UserList>(`${this.apiUrl}/${listId}/items/${itemId}`, data)
|
|
.pipe(tap((list) => this.upsertCachedList(list)));
|
|
}
|
|
|
|
private readCachedLists(): UserList[] {
|
|
if (typeof localStorage === 'undefined') {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const value = localStorage.getItem(LIST_CACHE_KEY);
|
|
return value ? (JSON.parse(value) as UserList[]) : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private writeCachedLists(lists: UserList[]): void {
|
|
if (typeof localStorage === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
localStorage.setItem(LIST_CACHE_KEY, JSON.stringify(lists));
|
|
}
|
|
|
|
private upsertCachedList(list: UserList): void {
|
|
const lists = this.readCachedLists();
|
|
const existingIndex = lists.findIndex((existingList) => existingList.id === list.id);
|
|
|
|
if (existingIndex === -1) {
|
|
this.writeCachedLists([...lists, list]);
|
|
return;
|
|
}
|
|
|
|
this.writeCachedLists(
|
|
lists.map((existingList, index) => (index === existingIndex ? list : existingList)),
|
|
);
|
|
}
|
|
|
|
private updateCachedList(
|
|
listId: string,
|
|
updater: (list: UserList) => UserList,
|
|
): UserList | null {
|
|
const lists = this.readCachedLists();
|
|
let updatedList: UserList | null = null;
|
|
|
|
this.writeCachedLists(
|
|
lists.map((list) => {
|
|
if (list.id !== listId) {
|
|
return list;
|
|
}
|
|
|
|
updatedList = updater(list);
|
|
return updatedList;
|
|
}),
|
|
);
|
|
|
|
return updatedList;
|
|
}
|
|
|
|
private removeCachedList(listId: string): void {
|
|
this.writeCachedLists(
|
|
this.readCachedLists().filter((list) => list.id !== listId),
|
|
);
|
|
}
|
|
}
|