feat: allowing user to get a list of their images
feat: UI to show and organise user images
This commit is contained in:
@ -70,6 +70,42 @@ func main() {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Temporarily not in protect route because we aren't using cookies.
|
||||||
|
// Therefore they don't get automatically attached to the request.
|
||||||
|
// So <img src=""> cannot send the tokensend the token
|
||||||
|
r.Get("/image/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
stringImageId := r.PathValue("id")
|
||||||
|
// userId := r.Context().Value(USER_ID).(uuid.UUID)
|
||||||
|
|
||||||
|
imageId, err := uuid.Parse(stringImageId)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
fmt.Fprintf(w, "You cannot read this")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if authorized := imageModel.IsUserAuthorized(r.Context(), imageId, userId); !authorized {
|
||||||
|
// w.WriteHeader(http.StatusForbidden)
|
||||||
|
// fmt.Fprintf(w, "You cannot read this")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
image, err := imageModel.Get(r.Context(), imageId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, "Could not get image")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this could be part of the db table
|
||||||
|
extension := filepath.Ext(image.ImageName)
|
||||||
|
extension = extension[1:]
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "image/"+extension)
|
||||||
|
w.Write(image.Image)
|
||||||
|
})
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(ProtectedRoute)
|
r.Use(ProtectedRoute)
|
||||||
|
|
||||||
@ -81,7 +117,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
images, err := userModel.ListWithProperties(r.Context(), userId)
|
imageProperties, err := userModel.ListWithProperties(r.Context(), userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
@ -89,7 +125,25 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonImages, err := json.Marshal(models.GetTypedImageProperties(images))
|
images, err := userModel.GetUserImages(r.Context(), userId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, "Something went wrong")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImagesReturn struct {
|
||||||
|
UserImages []models.UserImageWithImage
|
||||||
|
ImageProperties []models.TypedProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesReturn := ImagesReturn{
|
||||||
|
UserImages: images,
|
||||||
|
ImageProperties: models.GetTypedImageProperties(imageProperties),
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonImages, err := json.Marshal(imagesReturn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
@ -130,40 +184,6 @@ func main() {
|
|||||||
w.Write(jsonImages)
|
w.Write(jsonImages)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Get("/image/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
stringImageId := r.PathValue("id")
|
|
||||||
userId := r.Context().Value(USER_ID).(uuid.UUID)
|
|
||||||
|
|
||||||
imageId, err := uuid.Parse(stringImageId)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
fmt.Fprintf(w, "You cannot read this")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if authorized := imageModel.IsUserAuthorized(r.Context(), imageId, userId); !authorized {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
fmt.Fprintf(w, "You cannot read this")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: really need authorization here!
|
|
||||||
image, err := imageModel.Get(r.Context(), imageId)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
fmt.Fprintf(w, "Could not get image")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this could be part of the db table
|
|
||||||
extension := filepath.Ext(image.Image.ImageName)
|
|
||||||
extension = extension[1:]
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "image/"+extension)
|
|
||||||
w.Write(image.Image.Image)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Post("/image/{name}", func(w http.ResponseWriter, r *http.Request) {
|
r.Post("/image/{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
imageName := r.PathValue("name")
|
imageName := r.PathValue("name")
|
||||||
userId := r.Context().Value(USER_ID).(uuid.UUID)
|
userId := r.Context().Value(USER_ID).(uuid.UUID)
|
||||||
|
@ -154,21 +154,14 @@ func (m ImageModel) StartProcessing(ctx context.Context, processingImageId uuid.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (ImageData, error) {
|
func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (model.Image, error) {
|
||||||
getImageStmt := SELECT(UserImages.AllColumns, Image.AllColumns).
|
getImageStmt := Image.SELECT(Image.AllColumns).
|
||||||
FROM(
|
WHERE(Image.ID.EQ(UUID(imageId)))
|
||||||
UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)),
|
|
||||||
).
|
|
||||||
WHERE(UserImages.ID.EQ(UUID(imageId)))
|
|
||||||
|
|
||||||
images := []ImageData{}
|
image := model.Image{}
|
||||||
err := getImageStmt.QueryContext(ctx, m.dbPool, &images)
|
err := getImageStmt.QueryContext(ctx, m.dbPool, &image)
|
||||||
|
|
||||||
if len(images) != 1 {
|
return image, err
|
||||||
return ImageData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return images[0], err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m ImageModel) IsUserAuthorized(ctx context.Context, imageId uuid.UUID, userId uuid.UUID) bool {
|
func (m ImageModel) IsUserAuthorized(ctx context.Context, imageId uuid.UUID, userId uuid.UUID) bool {
|
||||||
|
@ -295,6 +295,27 @@ func (m UserModel) Save(ctx context.Context, user model.Users) (model.Users, err
|
|||||||
return insertedUser, err
|
return insertedUser, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserImageWithImage struct {
|
||||||
|
model.UserImages
|
||||||
|
|
||||||
|
Image model.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UserModel) GetUserImages(ctx context.Context, userId uuid.UUID) ([]UserImageWithImage, error) {
|
||||||
|
getUserImagesStmt := SELECT(
|
||||||
|
UserImages.AllColumns,
|
||||||
|
Image.ID,
|
||||||
|
Image.ImageName,
|
||||||
|
).
|
||||||
|
FROM(UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID))).
|
||||||
|
WHERE(UserImages.UserID.EQ(UUID(userId)))
|
||||||
|
|
||||||
|
userImages := []UserImageWithImage{}
|
||||||
|
err := getUserImagesStmt.QueryContext(ctx, m.dbPool, &userImages)
|
||||||
|
|
||||||
|
return userImages, err
|
||||||
|
}
|
||||||
|
|
||||||
func NewUserModel(db *sql.DB) UserModel {
|
func NewUserModel(db *sql.DB) UserModel {
|
||||||
return UserModel{dbPool: db}
|
return UserModel{dbPool: db}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,13 @@ import type { PluginListener } from "@tauri-apps/api/core";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { readFile } from "@tauri-apps/plugin-fs";
|
import { readFile } from "@tauri-apps/plugin-fs";
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
import { createEffect, createSignal, onCleanup } from "solid-js";
|
import {
|
||||||
|
type Component,
|
||||||
|
type ParentProps,
|
||||||
|
createEffect,
|
||||||
|
createSignal,
|
||||||
|
onCleanup,
|
||||||
|
} from "solid-js";
|
||||||
import {
|
import {
|
||||||
type ShareEvent,
|
type ShareEvent,
|
||||||
listenForShareEvents,
|
listenForShareEvents,
|
||||||
@ -20,6 +26,10 @@ import { type sendImage, sendImageFile } from "./network";
|
|||||||
const currentPlatform = platform();
|
const currentPlatform = platform();
|
||||||
console.log("Current Platform: ", currentPlatform);
|
console.log("Current Platform: ", currentPlatform);
|
||||||
|
|
||||||
|
const AppWrapper: Component<ParentProps> = ({ children }) => {
|
||||||
|
return <div class="flex w-full justify-center h-screen">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
// TODO: Don't use window.location.href
|
// TODO: Don't use window.location.href
|
||||||
@ -79,11 +89,13 @@ export const App = () => {
|
|||||||
onSetProcessingImage={setProcessingImage}
|
onSetProcessingImage={setProcessingImage}
|
||||||
/>
|
/>
|
||||||
<Router>
|
<Router>
|
||||||
<Route path="/login" component={Login} />
|
<Route path="/" component={AppWrapper}>
|
||||||
|
<Route path="/login" component={Login} />
|
||||||
|
|
||||||
<Route path="/" component={ProtectedRoute}>
|
<Route path="/" component={ProtectedRoute}>
|
||||||
<Route path="/" component={Search} />
|
<Route path="/" component={Search} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Route path="/settings" component={Settings} />
|
||||||
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
</SearchImageContextProvider>
|
</SearchImageContextProvider>
|
||||||
|
@ -14,7 +14,7 @@ import { SearchCard } from "./components/search-card/SearchCard";
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { ItemModal } from "./components/item-modal/ItemModal";
|
import { ItemModal } from "./components/item-modal/ItemModal";
|
||||||
import type { Shortcut } from "./components/shortcuts/hooks/useShortcutEditor";
|
import type { Shortcut } from "./components/shortcuts/hooks/useShortcutEditor";
|
||||||
import type { UserImage } from "./network";
|
import { base, type UserImage } from "./network";
|
||||||
import { useSearchImageContext } from "./contexts/SearchImageContext";
|
import { useSearchImageContext } from "./contexts/SearchImageContext";
|
||||||
|
|
||||||
export const Search = () => {
|
export const Search = () => {
|
||||||
@ -24,9 +24,10 @@ export const Search = () => {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { images, onRefetchImages } = useSearchImageContext();
|
const { images, imagesWithProperties, onRefetchImages } =
|
||||||
|
useSearchImageContext();
|
||||||
|
|
||||||
let fuze = new Fuse<UserImage>(images() ?? [], {
|
let fuze = new Fuse<UserImage>(images(), {
|
||||||
keys: [
|
keys: [
|
||||||
{ name: "rawData", weight: 1 },
|
{ name: "rawData", weight: 1 },
|
||||||
{ name: "title", weight: 1 },
|
{ name: "title", weight: 1 },
|
||||||
@ -35,11 +36,9 @@ export const Search = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
console.log("DBG: ", images());
|
setSearchResults(images());
|
||||||
setSearchResults(images() ?? []);
|
|
||||||
console.log(images());
|
|
||||||
|
|
||||||
fuze = new Fuse<UserImage>(images() ?? [], {
|
fuze = new Fuse<UserImage>(images(), {
|
||||||
keys: [
|
keys: [
|
||||||
{ name: "data.Name", weight: 2 },
|
{ name: "data.Name", weight: 2 },
|
||||||
{ name: "rawData", weight: 1 },
|
{ name: "rawData", weight: 1 },
|
||||||
@ -50,10 +49,9 @@ export const Search = () => {
|
|||||||
|
|
||||||
const onInputChange = (event: InputEvent) => {
|
const onInputChange = (event: InputEvent) => {
|
||||||
const query = (event.target as HTMLInputElement).value;
|
const query = (event.target as HTMLInputElement).value;
|
||||||
console.log(query);
|
|
||||||
|
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
setSearchResults(images() ?? []);
|
setSearchResults(images());
|
||||||
} else {
|
} else {
|
||||||
setSearchQuery(query);
|
setSearchQuery(query);
|
||||||
setSearchResults(fuze.search(query).map((s) => s.item));
|
setSearchResults(fuze.search(query).map((s) => s.item));
|
||||||
@ -134,7 +132,7 @@ export const Search = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 mt-4 bg-white rounded-t-2xl">
|
<div class="px-4 mt-4 bg-white rounded-t-2xl">
|
||||||
<div class="h-[254px] mt-4 overflow-scroll scrollbar-hide">
|
<div class="mt-4 overflow-scroll scrollbar-hide">
|
||||||
<Show
|
<Show
|
||||||
when={searchResults().length > 0}
|
when={searchResults().length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
@ -169,6 +167,19 @@ export const Search = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full grid grid-cols-9 gap-2 grid-flow-row-dense py-4">
|
||||||
|
<For each={Object.keys(imagesWithProperties())}>
|
||||||
|
{(imageId) => (
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
alt="One of the users images"
|
||||||
|
src={`${base}/image/${imageId}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w-full border-t h-10 bg-white px-4 flex items-center border-neutral-100">
|
<div class="w-full border-t h-10 bg-white px-4 flex items-center border-neutral-100">
|
||||||
<p class="text-sm text-neutral-700">
|
<p class="text-sm text-neutral-700">
|
||||||
Use{" "}
|
Use{" "}
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
createContext,
|
type Accessor,
|
||||||
type Resource,
|
|
||||||
type Component,
|
type Component,
|
||||||
type ParentProps,
|
type ParentProps,
|
||||||
|
createContext,
|
||||||
|
createMemo,
|
||||||
createResource,
|
createResource,
|
||||||
useContext,
|
useContext,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { getUserImages } from "../network";
|
import { getUserImages } from "../network";
|
||||||
|
import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage";
|
||||||
|
|
||||||
type ImageWithRawData = Awaited<ReturnType<typeof getUserImages>>[number] & {
|
export type ImageWithRawData = Awaited<
|
||||||
|
ReturnType<typeof getUserImages>
|
||||||
|
>["ImageProperties"][number] & {
|
||||||
rawData: string[];
|
rawData: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type SearchImageStore = {
|
type SearchImageStore = {
|
||||||
images: Resource<ImageWithRawData[]>;
|
images: Accessor<ImageWithRawData[]>;
|
||||||
|
|
||||||
|
imagesWithProperties: Accessor<ReturnType<typeof groupPropertiesWithImage>>;
|
||||||
onRefetchImages: () => void;
|
onRefetchImages: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,19 +56,36 @@ const getAllValues = (object: object): Array<string> => {
|
|||||||
|
|
||||||
const SearchImageContext = createContext<SearchImageStore>();
|
const SearchImageContext = createContext<SearchImageStore>();
|
||||||
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||||
const [images, { refetch }] = createResource(() =>
|
const [data, { refetch }] = createResource(getUserImages);
|
||||||
getUserImages().then((data) => {
|
|
||||||
return data.map((d) => ({
|
const imageData = createMemo<ImageWithRawData[]>(() => {
|
||||||
...d,
|
const d = data();
|
||||||
rawData: getAllValues(d),
|
if (d == null) {
|
||||||
}));
|
return [];
|
||||||
}),
|
}
|
||||||
);
|
|
||||||
|
return d.ImageProperties.map((d) => ({
|
||||||
|
...d,
|
||||||
|
rawData: getAllValues(d),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagesWithProperties = createMemo<
|
||||||
|
ReturnType<typeof groupPropertiesWithImage>
|
||||||
|
>(() => {
|
||||||
|
const d = data();
|
||||||
|
if (d == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupPropertiesWithImage(d);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchImageContext.Provider
|
<SearchImageContext.Provider
|
||||||
value={{
|
value={{
|
||||||
images,
|
images: imageData,
|
||||||
|
imagesWithProperties: imagesWithProperties,
|
||||||
onRefetchImages: refetch,
|
onRefetchImages: refetch,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
type InferOutput,
|
type InferOutput,
|
||||||
array,
|
array,
|
||||||
literal,
|
literal,
|
||||||
|
null_,
|
||||||
nullable,
|
nullable,
|
||||||
parse,
|
parse,
|
||||||
pipe,
|
pipe,
|
||||||
@ -84,6 +85,7 @@ export const sendImage = async (
|
|||||||
|
|
||||||
const locationValidator = strictObject({
|
const locationValidator = strictObject({
|
||||||
ID: pipe(string(), uuid()),
|
ID: pipe(string(), uuid()),
|
||||||
|
CreatedAt: pipe(string()),
|
||||||
Name: string(),
|
Name: string(),
|
||||||
Address: nullable(string()),
|
Address: nullable(string()),
|
||||||
Description: nullable(string()),
|
Description: nullable(string()),
|
||||||
@ -92,6 +94,7 @@ const locationValidator = strictObject({
|
|||||||
|
|
||||||
const contactValidator = strictObject({
|
const contactValidator = strictObject({
|
||||||
ID: pipe(string(), uuid()),
|
ID: pipe(string(), uuid()),
|
||||||
|
CreatedAt: pipe(string()),
|
||||||
Name: string(),
|
Name: string(),
|
||||||
Description: nullable(string()),
|
Description: nullable(string()),
|
||||||
PhoneNumber: nullable(string()),
|
PhoneNumber: nullable(string()),
|
||||||
@ -101,6 +104,7 @@ const contactValidator = strictObject({
|
|||||||
|
|
||||||
const eventValidator = strictObject({
|
const eventValidator = strictObject({
|
||||||
ID: pipe(string(), uuid()),
|
ID: pipe(string(), uuid()),
|
||||||
|
CreatedAt: pipe(string()),
|
||||||
Name: string(),
|
Name: string(),
|
||||||
StartDateTime: nullable(pipe(string())),
|
StartDateTime: nullable(pipe(string())),
|
||||||
EndDateTime: nullable(pipe(string())),
|
EndDateTime: nullable(pipe(string())),
|
||||||
@ -114,6 +118,7 @@ const eventValidator = strictObject({
|
|||||||
|
|
||||||
const noteValidator = strictObject({
|
const noteValidator = strictObject({
|
||||||
ID: pipe(string(), uuid()),
|
ID: pipe(string(), uuid()),
|
||||||
|
CreatedAt: pipe(string()),
|
||||||
Name: string(),
|
Name: string(),
|
||||||
Description: nullable(string()),
|
Description: nullable(string()),
|
||||||
Content: string(),
|
Content: string(),
|
||||||
@ -146,18 +151,36 @@ const dataTypeValidator = variant("type", [
|
|||||||
noteDataType,
|
noteDataType,
|
||||||
contactDataType,
|
contactDataType,
|
||||||
]);
|
]);
|
||||||
const getUserImagesResponseValidator = array(dataTypeValidator);
|
|
||||||
|
const userImageValidator = strictObject({
|
||||||
|
ID: pipe(string(), uuid()),
|
||||||
|
CreatedAt: pipe(string()),
|
||||||
|
ImageID: pipe(string(), uuid()),
|
||||||
|
UserID: pipe(string(), uuid()),
|
||||||
|
Image: strictObject({
|
||||||
|
ID: pipe(string(), uuid()),
|
||||||
|
ImageName: string(),
|
||||||
|
Image: null_(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export type UserImage = InferOutput<typeof dataTypeValidator>;
|
export type UserImage = InferOutput<typeof dataTypeValidator>;
|
||||||
|
|
||||||
export const getUserImages = async (): Promise<UserImage[]> => {
|
const imageRequestValidator = strictObject({
|
||||||
|
UserImages: array(userImageValidator),
|
||||||
|
ImageProperties: array(dataTypeValidator),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getUserImages = async (): Promise<
|
||||||
|
InferOutput<typeof imageRequestValidator>
|
||||||
|
> => {
|
||||||
const request = getBaseAuthorizedRequest({ path: "image" });
|
const request = getBaseAuthorizedRequest({ path: "image" });
|
||||||
|
|
||||||
const res = await fetch(request).then((res) => res.json());
|
const res = await fetch(request).then((res) => res.json());
|
||||||
|
|
||||||
console.log("BACKEND RESPONSE: ", res);
|
console.log("BACKEND RESPONSE: ", res);
|
||||||
|
|
||||||
return parse(getUserImagesResponseValidator, res);
|
return parse(imageRequestValidator, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getImage = async (imageId: string): Promise<UserImage> => {
|
export const getImage = async (imageId: string): Promise<UserImage> => {
|
||||||
|
16
frontend/src/utils/groupPropertiesWithImage.ts
Normal file
16
frontend/src/utils/groupPropertiesWithImage.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { getUserImages } from "../network";
|
||||||
|
|
||||||
|
export const groupPropertiesWithImage = ({
|
||||||
|
UserImages,
|
||||||
|
ImageProperties,
|
||||||
|
}: Awaited<ReturnType<typeof getUserImages>>) => {
|
||||||
|
const imageToProperties: Record<string, typeof ImageProperties> = {};
|
||||||
|
|
||||||
|
for (const image of UserImages) {
|
||||||
|
imageToProperties[image.ImageID] = ImageProperties.filter((i) =>
|
||||||
|
i.data.Images.includes(image.ImageID),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageToProperties;
|
||||||
|
};
|
Reference in New Issue
Block a user