meals-made-easy-app/src/app/shared/services/ImageService.ts
2026-02-07 21:22:35 -06:00

176 lines
6.7 KiB
TypeScript

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom, from, map, mergeMap, Observable, tap, toArray } from 'rxjs';
import { EndpointService } from './EndpointService';
import { ImageView } from '../models/ImageView.model';
import { SliceView } from '../models/SliceView.model';
import { QueryParams } from '../models/Query.model';
import { QueryClient, QueryOptions, queryOptions } from '@tanstack/angular-query-experimental';
import { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl';
import { WithStringDates } from '../util';
import { ImageUpdateBody } from '../bodies/ImageUpdateBody';
@Injectable({
providedIn: 'root',
})
export class ImageService {
public static ImageProps = [
'id',
'created',
'modified',
'userFilename',
'mimeType',
'alt',
'caption',
'objectName',
'height',
'width',
'owner',
'viewers',
] as const;
private readonly httpClient = inject(HttpClient);
private readonly endpointService = inject(EndpointService);
private readonly queryClient = inject(QueryClient);
public hydrateImageView(rawImageView: WithStringDates<ImageView>): ImageView {
return {
...rawImageView,
created: new Date(rawImageView.created),
modified: rawImageView.modified ? new Date(rawImageView.modified) : undefined,
};
}
public getOwnedImageViewsWithBlobUrls(
queryParams?: QueryParams<typeof ImageService.ImageProps>,
): Observable<SliceView<ImageViewWithBlobUrl>> {
return this.httpClient
.get<SliceView<WithStringDates<ImageView>>>(this.endpointService.getUrl('images', [], queryParams))
.pipe(
map((sliceView) => ({
...sliceView,
content: sliceView.content.map((withStringDates) => this.hydrateImageView(withStringDates)),
})),
mergeMap((sliceView) => {
return from(sliceView.content).pipe(
mergeMap((imageView) => {
return this.httpClient
.get(
this.endpointService.getUrl('images', [
imageView.owner.username,
imageView.filename,
]),
{
responseType: 'blob',
},
)
.pipe(
map((blob) => URL.createObjectURL(blob)),
map((blobUrl) => ({
...imageView,
blobUrl,
})),
);
}),
toArray(),
map((content) => {
return {
...sliceView,
content,
} satisfies SliceView<ImageViewWithBlobUrl>;
}),
);
}),
);
}
public getOwnedImagesCount(): Observable<number> {
return this.httpClient
.get<{ count: number }>(this.endpointService.getUrl('images', [], { custom: { count: true } }))
.pipe(map((res) => res.count));
}
public getImageViewWithBlobUrl(imageView: ImageView): Promise<ImageViewWithBlobUrl> {
return firstValueFrom(
this.httpClient
.get(this.endpointService.getUrl('images', [imageView.owner.username, imageView.filename]), {
responseType: 'blob',
})
.pipe(
map((blob) => URL.createObjectURL(blob)),
map(
(blobUrl) =>
({
...imageView,
blobUrl,
}) satisfies ImageViewWithBlobUrl,
),
),
);
}
public getImage(
imageView: ImageView,
): QueryOptions<ImageViewWithBlobUrl, Error, ImageViewWithBlobUrl, [string, string, string]> {
return queryOptions({
queryKey: ['image-views-with-blob-urls', imageView.owner.username, imageView.filename],
queryFn: () => this.getImageViewWithBlobUrl(imageView),
});
}
public getImageView2(username: string, filename: string): Observable<ImageView> {
return this.httpClient
.get<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename, 'view']))
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
}
public uploadImage(
image: File,
filename?: string,
alt?: string,
caption?: string,
isPublic?: boolean,
): Promise<ImageView> {
const formData = new FormData();
formData.append('image', image);
formData.append('filename', filename ?? image.name);
if (alt) {
formData.append('alt', alt);
}
if (caption) {
formData.append('caption', caption);
}
if (isPublic !== undefined) {
formData.append('isPublic', isPublic.toString());
}
return firstValueFrom(
this.httpClient.post<ImageView>(this.endpointService.getUrl('images'), formData).pipe(
tap(async () => {
await this.queryClient.invalidateQueries({
queryKey: ['image-views'],
});
}),
),
);
}
public imageExists(username: string, filename: string): Promise<boolean> {
return firstValueFrom(
this.httpClient
.get<{ exists: boolean }>(this.endpointService.getUrl('images', [username, filename, 'exists']))
.pipe(map((view) => view.exists)),
);
}
public deleteImage(username: string, filename: string): Observable<void> {
return this.httpClient.delete<void>(this.endpointService.getUrl('images', [username, filename]));
}
public updateImage(username: string, filename: string, data: ImageUpdateBody): Observable<ImageView> {
return this.httpClient
.put<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename]), data)
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
}
}