meals-made-easy-app/src/app/shared/services/RecipeService.ts

105 lines
4.3 KiB
TypeScript

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom, lastValueFrom, map, Observable } from 'rxjs';
import { FullRecipeView, FullRecipeViewWrapper, RecipeInfoView } from '../models/Recipe.model';
import { AuthService } from './AuthService';
import { QueryClient } from '@tanstack/angular-query-experimental';
import { RecipeComment } from '../models/RecipeComment.model';
import { QueryParams } from '../models/Query.model';
import { EndpointService } from './EndpointService';
import { SliceView } from '../models/SliceView.model';
import { WithStringDates } from '../util';
import { ImageService } from './ImageService';
@Injectable({
providedIn: 'root',
})
export class RecipeService {
private readonly http = inject(HttpClient);
private readonly authService = inject(AuthService);
private readonly queryClient = inject(QueryClient);
private readonly endpointService = inject(EndpointService);
private readonly imageService = inject(ImageService);
private hydrateRecipeInfoView(withStringDates: WithStringDates<RecipeInfoView>): RecipeInfoView {
return {
...withStringDates,
created: new Date(withStringDates.created),
modified: withStringDates.modified ? new Date(withStringDates.modified) : undefined,
mainImage: withStringDates.mainImage
? this.imageService.hydrateImageView(withStringDates.mainImage)
: undefined,
};
}
public getRecipes(): Observable<SliceView<RecipeInfoView>> {
return this.http.get<SliceView<WithStringDates<RecipeInfoView>>>(this.endpointService.getUrl('recipes')).pipe(
map((sliceView) => ({
...sliceView,
content: sliceView.content.map((withStringDates) => this.hydrateRecipeInfoView(withStringDates)),
})),
);
}
public getRecipeView(username: string, slug: string): Promise<FullRecipeViewWrapper> {
return firstValueFrom(
this.http.get<FullRecipeViewWrapper>(this.endpointService.getUrl('recipes', [username, slug])),
);
}
private getRecipeBaseUrl(recipeView: FullRecipeViewWrapper): string {
return this.endpointService.getUrl('recipes', [recipeView.recipe.owner.username, recipeView.recipe.slug]);
}
public async toggleStar(recipeView: FullRecipeViewWrapper): Promise<void> {
if (this.authService.accessToken()) {
if (recipeView.isStarred) {
await lastValueFrom(this.http.delete(this.getRecipeBaseUrl(recipeView) + '/star'));
} else {
await lastValueFrom(this.http.post(this.getRecipeBaseUrl(recipeView) + '/star', null));
}
await this.queryClient.invalidateQueries({
queryKey: ['recipe', recipeView.recipe.owner.username, recipeView.recipe.slug],
});
} else {
throw new Error('Cannot star a recipe when not logged in.');
}
}
public getComments(username: string, slug: string, queryParams?: QueryParams): Promise<SliceView<RecipeComment>> {
return firstValueFrom(
this.http.get<SliceView<RecipeComment>>(
this.endpointService.getUrl('recipes', [username, slug, 'comments'], queryParams),
),
);
}
public async addComment(username: string, slug: string, commentText: string): Promise<RecipeComment> {
const comment = await firstValueFrom(
this.http.post<RecipeComment>(this.endpointService.getUrl('recipes', [username, slug, 'comments']), {
text: commentText,
}),
);
await this.queryClient.invalidateQueries({
queryKey: ['recipeComments', username, slug],
});
return comment;
}
public async aiSearch(prompt: string): Promise<FullRecipeView[]> {
const recipeInfoViews = await firstValueFrom(
this.http.post<{ results: FullRecipeView[] }>(this.endpointService.getUrl('recipes'), {
type: 'AI_PROMPT',
data: {
prompt,
},
}),
);
return recipeInfoViews.results;
}
public deleteRecipe(username: string, slug: string): Observable<void> {
return this.http.delete<void>(this.endpointService.getUrl('recipes', [username, slug]));
}
}