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 { 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> { return this.http.get>>(this.endpointService.getUrl('recipes')).pipe( map((sliceView) => ({ ...sliceView, content: sliceView.content.map((withStringDates) => this.hydrateRecipeInfoView(withStringDates)), })), ); } public getRecipeView(username: string, slug: string): Promise { return firstValueFrom( this.http.get(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 { 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> { return firstValueFrom( this.http.get>( this.endpointService.getUrl('recipes', [username, slug, 'comments'], queryParams), ), ); } public async addComment(username: string, slug: string, commentText: string): Promise { const comment = await firstValueFrom( this.http.post(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 { 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 { return this.http.delete(this.endpointService.getUrl('recipes', [username, slug])); } }