diff --git a/backend/main.go b/backend/main.go index f7f6856..89d1c62 100644 --- a/backend/main.go +++ b/backend/main.go @@ -70,6 +70,42 @@ func main() { 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 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.Use(ProtectedRoute) @@ -81,7 +117,7 @@ func main() { return } - images, err := userModel.ListWithProperties(r.Context(), userId) + imageProperties, err := userModel.ListWithProperties(r.Context(), userId) if err != nil { log.Println(err) w.WriteHeader(http.StatusNotFound) @@ -89,7 +125,25 @@ func main() { 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 { log.Println(err) w.WriteHeader(http.StatusBadRequest) @@ -130,40 +184,6 @@ func main() { 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) { imageName := r.PathValue("name") userId := r.Context().Value(USER_ID).(uuid.UUID) diff --git a/backend/models/image.go b/backend/models/image.go index e3daa10..ae0697d 100644 --- a/backend/models/image.go +++ b/backend/models/image.go @@ -154,21 +154,14 @@ func (m ImageModel) StartProcessing(ctx context.Context, processingImageId uuid. return err } -func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (ImageData, error) { - getImageStmt := SELECT(UserImages.AllColumns, Image.AllColumns). - FROM( - UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)), - ). - WHERE(UserImages.ID.EQ(UUID(imageId))) +func (m ImageModel) Get(ctx context.Context, imageId uuid.UUID) (model.Image, error) { + getImageStmt := Image.SELECT(Image.AllColumns). + WHERE(Image.ID.EQ(UUID(imageId))) - images := []ImageData{} - err := getImageStmt.QueryContext(ctx, m.dbPool, &images) + image := model.Image{} + err := getImageStmt.QueryContext(ctx, m.dbPool, &image) - if len(images) != 1 { - return ImageData{}, errors.New(fmt.Sprintf("Expected 1, got %d\n", len(images))) - } - - return images[0], err + return image, err } func (m ImageModel) IsUserAuthorized(ctx context.Context, imageId uuid.UUID, userId uuid.UUID) bool { diff --git a/backend/models/user.go b/backend/models/user.go index 81fafb7..97a141f 100644 --- a/backend/models/user.go +++ b/backend/models/user.go @@ -295,6 +295,27 @@ func (m UserModel) Save(ctx context.Context, user model.Users) (model.Users, 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 { return UserModel{dbPool: db} } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 80722b9..c705661 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,7 +3,13 @@ import type { PluginListener } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { readFile } from "@tauri-apps/plugin-fs"; 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 { type ShareEvent, listenForShareEvents, @@ -20,6 +26,10 @@ import { type sendImage, sendImageFile } from "./network"; const currentPlatform = platform(); console.log("Current Platform: ", currentPlatform); +const AppWrapper: Component = ({ children }) => { + return
{children}
; +}; + export const App = () => { createEffect(() => { // TODO: Don't use window.location.href @@ -79,11 +89,13 @@ export const App = () => { onSetProcessingImage={setProcessingImage} /> - + + - - - + + + + diff --git a/frontend/src/Search.tsx b/frontend/src/Search.tsx index 98136c3..0e6be9c 100644 --- a/frontend/src/Search.tsx +++ b/frontend/src/Search.tsx @@ -14,7 +14,7 @@ import { SearchCard } from "./components/search-card/SearchCard"; import { invoke } from "@tauri-apps/api/core"; import { ItemModal } from "./components/item-modal/ItemModal"; import type { Shortcut } from "./components/shortcuts/hooks/useShortcutEditor"; -import type { UserImage } from "./network"; +import { base, type UserImage } from "./network"; import { useSearchImageContext } from "./contexts/SearchImageContext"; export const Search = () => { @@ -24,9 +24,10 @@ export const Search = () => { null, ); - const { images, onRefetchImages } = useSearchImageContext(); + const { images, imagesWithProperties, onRefetchImages } = + useSearchImageContext(); - let fuze = new Fuse(images() ?? [], { + let fuze = new Fuse(images(), { keys: [ { name: "rawData", weight: 1 }, { name: "title", weight: 1 }, @@ -35,11 +36,9 @@ export const Search = () => { }); createEffect(() => { - console.log("DBG: ", images()); - setSearchResults(images() ?? []); - console.log(images()); + setSearchResults(images()); - fuze = new Fuse(images() ?? [], { + fuze = new Fuse(images(), { keys: [ { name: "data.Name", weight: 2 }, { name: "rawData", weight: 1 }, @@ -50,10 +49,9 @@ export const Search = () => { const onInputChange = (event: InputEvent) => { const query = (event.target as HTMLInputElement).value; - console.log(query); if (query.length === 0) { - setSearchResults(images() ?? []); + setSearchResults(images()); } else { setSearchQuery(query); setSearchResults(fuze.search(query).map((s) => s.item)); @@ -134,7 +132,7 @@ export const Search = () => {
-
+
0} fallback={ @@ -169,6 +167,19 @@ export const Search = () => {
+
+ + {(imageId) => ( +
+ One of the users images +
+ )} +
+
+

Use{" "} diff --git a/frontend/src/contexts/SearchImageContext.tsx b/frontend/src/contexts/SearchImageContext.tsx index debdbbb..63dcc8b 100644 --- a/frontend/src/contexts/SearchImageContext.tsx +++ b/frontend/src/contexts/SearchImageContext.tsx @@ -1,19 +1,25 @@ import { - createContext, - type Resource, + type Accessor, type Component, type ParentProps, + createContext, + createMemo, createResource, useContext, } from "solid-js"; import { getUserImages } from "../network"; +import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage"; -type ImageWithRawData = Awaited>[number] & { +export type ImageWithRawData = Awaited< + ReturnType +>["ImageProperties"][number] & { rawData: string[]; }; type SearchImageStore = { - images: Resource; + images: Accessor; + + imagesWithProperties: Accessor>; onRefetchImages: () => void; }; @@ -50,19 +56,36 @@ const getAllValues = (object: object): Array => { const SearchImageContext = createContext(); export const SearchImageContextProvider: Component = (props) => { - const [images, { refetch }] = createResource(() => - getUserImages().then((data) => { - return data.map((d) => ({ - ...d, - rawData: getAllValues(d), - })); - }), - ); + const [data, { refetch }] = createResource(getUserImages); + + const imageData = createMemo(() => { + const d = data(); + if (d == null) { + return []; + } + + return d.ImageProperties.map((d) => ({ + ...d, + rawData: getAllValues(d), + })); + }); + + const imagesWithProperties = createMemo< + ReturnType + >(() => { + const d = data(); + if (d == null) { + return {}; + } + + return groupPropertiesWithImage(d); + }); return ( diff --git a/frontend/src/network/index.ts b/frontend/src/network/index.ts index 7fd7a94..03f3de2 100644 --- a/frontend/src/network/index.ts +++ b/frontend/src/network/index.ts @@ -4,6 +4,7 @@ import { type InferOutput, array, literal, + null_, nullable, parse, pipe, @@ -84,6 +85,7 @@ export const sendImage = async ( const locationValidator = strictObject({ ID: pipe(string(), uuid()), + CreatedAt: pipe(string()), Name: string(), Address: nullable(string()), Description: nullable(string()), @@ -92,6 +94,7 @@ const locationValidator = strictObject({ const contactValidator = strictObject({ ID: pipe(string(), uuid()), + CreatedAt: pipe(string()), Name: string(), Description: nullable(string()), PhoneNumber: nullable(string()), @@ -101,6 +104,7 @@ const contactValidator = strictObject({ const eventValidator = strictObject({ ID: pipe(string(), uuid()), + CreatedAt: pipe(string()), Name: string(), StartDateTime: nullable(pipe(string())), EndDateTime: nullable(pipe(string())), @@ -114,6 +118,7 @@ const eventValidator = strictObject({ const noteValidator = strictObject({ ID: pipe(string(), uuid()), + CreatedAt: pipe(string()), Name: string(), Description: nullable(string()), Content: string(), @@ -146,18 +151,36 @@ const dataTypeValidator = variant("type", [ noteDataType, 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; -export const getUserImages = async (): Promise => { +const imageRequestValidator = strictObject({ + UserImages: array(userImageValidator), + ImageProperties: array(dataTypeValidator), +}); + +export const getUserImages = async (): Promise< + InferOutput +> => { const request = getBaseAuthorizedRequest({ path: "image" }); const res = await fetch(request).then((res) => res.json()); console.log("BACKEND RESPONSE: ", res); - return parse(getUserImagesResponseValidator, res); + return parse(imageRequestValidator, res); }; export const getImage = async (imageId: string): Promise => { diff --git a/frontend/src/utils/groupPropertiesWithImage.ts b/frontend/src/utils/groupPropertiesWithImage.ts new file mode 100644 index 0000000..962abe1 --- /dev/null +++ b/frontend/src/utils/groupPropertiesWithImage.ts @@ -0,0 +1,16 @@ +import type { getUserImages } from "../network"; + +export const groupPropertiesWithImage = ({ + UserImages, + ImageProperties, +}: Awaited>) => { + const imageToProperties: Record = {}; + + for (const image of UserImages) { + imageToProperties[image.ImageID] = ImageProperties.filter((i) => + i.data.Images.includes(image.ImageID), + ); + } + + return imageToProperties; +};