fix: frontend with new backend schema
This commit is contained in:
@ -34,8 +34,8 @@ type ImageHandler struct {
|
||||
}
|
||||
|
||||
type ImagesReturn struct {
|
||||
UserImages []models.UserImageWithImage `json:"userImages"`
|
||||
Lists []models.ListsWithImages `json:"lists"`
|
||||
UserImages []models.UserImageWithImage
|
||||
Stacks []models.ListsWithImages
|
||||
}
|
||||
|
||||
func (h *ImageHandler) serveImage(w http.ResponseWriter, r *http.Request) {
|
||||
@ -89,7 +89,7 @@ func (h *ImageHandler) listImages(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
listsWithImages, err := h.userModel.ListWithImages(r.Context(), userId)
|
||||
stacksWithImages, err := h.userModel.ListWithImages(r.Context(), userId)
|
||||
if err != nil {
|
||||
middleware.WriteErrorInternal(h.logger, "could not get lists with images", w)
|
||||
return
|
||||
@ -97,7 +97,7 @@ func (h *ImageHandler) listImages(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
imagesReturn := ImagesReturn{
|
||||
UserImages: images,
|
||||
Lists: listsWithImages,
|
||||
Stacks: stacksWithImages,
|
||||
}
|
||||
|
||||
middleware.WriteJsonOrError(h.logger, imagesReturn, w)
|
||||
|
@ -29,7 +29,7 @@ func (m ImageModel) Save(ctx context.Context, name string, image []byte, userID
|
||||
}
|
||||
|
||||
func (m ImageModel) Get(ctx context.Context, imageID uuid.UUID) (model.Image, bool, error) {
|
||||
getImageStmt := Image.SELECT(Image.AllColumns.Except(Image.Image)).WHERE(Image.ID.EQ(UUID(imageID)))
|
||||
getImageStmt := Image.SELECT(Image.AllColumns).WHERE(Image.ID.EQ(UUID(imageID)))
|
||||
|
||||
image := model.Image{}
|
||||
err := getImageStmt.QueryContext(ctx, m.dbPool, &image)
|
||||
|
@ -33,14 +33,14 @@ func (h *StackHandler) getAllStacks(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
lists, err := h.stackModel.List(ctx, userID)
|
||||
stacks, err := h.stackModel.List(ctx, userID)
|
||||
if err != nil {
|
||||
h.logger.Warn("could not get stacks", "err", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.WriteJsonOrError(h.logger, lists, w)
|
||||
middleware.WriteJsonOrError(h.logger, stacks, w)
|
||||
}
|
||||
|
||||
func (h *StackHandler) getStackItems(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -17,7 +17,7 @@ const colors = [
|
||||
"bg-pink-50",
|
||||
];
|
||||
|
||||
export const ListCard: Component<{ list: List }> = (props) => {
|
||||
export const StackCard: Component<{ list: List }> = (props) => {
|
||||
return (
|
||||
<A
|
||||
href={`/list/${props.list.ID}`}
|
||||
|
@ -34,7 +34,7 @@ export const Notifications = (onCompleteImage: () => void) => {
|
||||
ProcessingLists: {},
|
||||
});
|
||||
|
||||
const { processingImages } = useSearchImageContext();
|
||||
const { userImages } = useSearchImageContext();
|
||||
|
||||
const [accessToken] = createResource(getAccessToken);
|
||||
|
||||
@ -91,19 +91,19 @@ export const Notifications = (onCompleteImage: () => void) => {
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const images = processingImages();
|
||||
const images = userImages();
|
||||
if (images == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
upsertImageProcessing(
|
||||
Object.fromEntries(
|
||||
images.map((i) => [
|
||||
i.ImageID,
|
||||
images.filter(i => i.Status !== 'complete').map((i) => [
|
||||
i.ID,
|
||||
{
|
||||
Type: "image",
|
||||
ImageID: i.ImageID,
|
||||
ImageName: i.Image.ImageName,
|
||||
ImageID: i.ID,
|
||||
ImageName: i.ImageName,
|
||||
Status: i.Status,
|
||||
},
|
||||
]),
|
||||
|
@ -14,14 +14,10 @@ export type SearchImageStore = {
|
||||
Array<{ date: Date; images: JustTheImageWhatAreTheseNames }>
|
||||
>;
|
||||
|
||||
lists: Accessor<Awaited<ReturnType<typeof getUserImages>>["lists"]>;
|
||||
stacks: Accessor<Awaited<ReturnType<typeof getUserImages>>["Stacks"]>;
|
||||
|
||||
userImages: Accessor<JustTheImageWhatAreTheseNames>;
|
||||
|
||||
processingImages: Accessor<
|
||||
Awaited<ReturnType<typeof getUserImages>>["processingImages"] | undefined
|
||||
>;
|
||||
|
||||
onRefetchImages: () => void;
|
||||
onDeleteImage: (imageID: string) => void;
|
||||
onDeleteImageFromStack: (stackID: string, imageID: string) => void;
|
||||
@ -41,7 +37,7 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
// Sorted by day. But we could potentially add more in the future.
|
||||
const buckets: Record<string, JustTheImageWhatAreTheseNames> = {};
|
||||
|
||||
for (const image of d.userImages) {
|
||||
for (const image of d.UserImages) {
|
||||
if (image.CreatedAt == null) {
|
||||
continue;
|
||||
}
|
||||
@ -60,15 +56,12 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
},
|
||||
);
|
||||
|
||||
const processingImages = () => data()?.processingImages ?? [];
|
||||
|
||||
return (
|
||||
<SearchImageContext.Provider
|
||||
value={{
|
||||
imagesByDate: sortedImages,
|
||||
lists: () => data()?.lists ?? [],
|
||||
userImages: () => data()?.userImages ?? [],
|
||||
processingImages,
|
||||
stacks: () => data()?.Stacks ?? [],
|
||||
userImages: () => data()?.UserImages ?? [],
|
||||
onRefetchImages: refetch,
|
||||
onDeleteImage: (imageID: string) => {
|
||||
deleteImage(imageID).then(refetch);
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { getTokenProperties } from "@components/protected-route";
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
|
||||
import {
|
||||
type InferOutput,
|
||||
array,
|
||||
literal,
|
||||
null_,
|
||||
literal,
|
||||
nullable,
|
||||
parse,
|
||||
pipe,
|
||||
@ -172,91 +171,70 @@ export const sendImage = async (
|
||||
return parsedRes.output;
|
||||
};
|
||||
|
||||
const imageMetaValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageName: string(),
|
||||
Description: string(),
|
||||
Image: null_(),
|
||||
});
|
||||
|
||||
const userImageValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
CreatedAt: pipe(string()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
CreatedAt: string(),
|
||||
UserID: pipe(string(), uuid()),
|
||||
Image: strictObject({
|
||||
...imageMetaValidator.entries,
|
||||
ImageLists: pipe(nullable(array(
|
||||
Description: string(),
|
||||
|
||||
Image: null_(),
|
||||
ImageName: string(),
|
||||
|
||||
Status: union([literal('not-started'), literal('in-progress'), literal('complete')]),
|
||||
|
||||
ImageStacks: pipe(nullable(array(
|
||||
strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
ListID: pipe(string(), uuid()),
|
||||
}),
|
||||
)), transform(l => l ?? [])),
|
||||
}),
|
||||
});
|
||||
|
||||
const userProcessingImageValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
UserID: pipe(string(), uuid()),
|
||||
Image: imageMetaValidator,
|
||||
Status: union([
|
||||
literal("not-started"),
|
||||
literal("in-progress"),
|
||||
literal("complete"),
|
||||
]),
|
||||
});
|
||||
|
||||
const listValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
UserID: pipe(string(), uuid()),
|
||||
CreatedAt: pipe(string()),
|
||||
Name: string(),
|
||||
Description: nullable(string()),
|
||||
|
||||
Images: pipe(
|
||||
nullable(
|
||||
array(
|
||||
strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
ListID: pipe(string(), uuid()),
|
||||
Items: array(
|
||||
strictObject({
|
||||
const stackItem = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
SchemaItemID: pipe(string(), uuid()),
|
||||
Value: string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
transform((n) => n ?? []),
|
||||
),
|
||||
|
||||
Schema: strictObject({
|
||||
Value: string(),
|
||||
})
|
||||
|
||||
const stackImage = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ListID: pipe(string(), uuid()),
|
||||
SchemaItems: array(
|
||||
strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
SchemaID: pipe(string(), uuid()),
|
||||
Item: string(),
|
||||
Value: nullable(string()),
|
||||
Description: string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
StackID: pipe(string(), uuid()),
|
||||
|
||||
Items: pipe(nullable(array(stackItem)), transform(l => l ?? [])),
|
||||
});
|
||||
|
||||
export type List = InferOutput<typeof listValidator>;
|
||||
const stackSchemaItem = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
StackID: pipe(string(), uuid()),
|
||||
|
||||
Description: string(),
|
||||
Item: string(),
|
||||
Value: string(),
|
||||
})
|
||||
|
||||
const stackValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
CreatedAt: string(),
|
||||
UserID: pipe(string(), uuid()),
|
||||
Description: string(),
|
||||
|
||||
Status: union([literal('not-started'), literal('in-progress'), literal('complete')]),
|
||||
|
||||
Name: string(),
|
||||
|
||||
Images: array(stackImage),
|
||||
SchemaItems: array(stackSchemaItem),
|
||||
});
|
||||
|
||||
export type List = InferOutput<typeof stackValidator>;
|
||||
|
||||
const imageRequestValidator = strictObject({
|
||||
userImages: array(userImageValidator),
|
||||
processingImages: array(userProcessingImageValidator),
|
||||
lists: array(listValidator),
|
||||
UserImages: array(userImageValidator),
|
||||
Stacks: array(stackValidator),
|
||||
});
|
||||
|
||||
export type JustTheImageWhatAreTheseNames = InferOutput<
|
||||
@ -274,7 +252,7 @@ export const getUserImages = async (): Promise<
|
||||
|
||||
const parsedRes = safeParse(imageRequestValidator, res);
|
||||
if (!parsedRes.success) {
|
||||
console.log(parsedRes.issues)
|
||||
console.log("Schema error: ", parsedRes.issues)
|
||||
throw new Error(JSON.stringify(parsedRes.issues));
|
||||
}
|
||||
|
||||
@ -310,7 +288,7 @@ export const postCode = async (
|
||||
|
||||
const parsedRes = safeParse(codeValidator, res);
|
||||
if (!parsedRes.success) {
|
||||
console.log(parsedRes.issues)
|
||||
console.log("Schema error: ", parsedRes.issues)
|
||||
throw new Error(JSON.stringify(parsedRes.issues));
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { Component, For } from "solid-js";
|
||||
import { createVirtualizer } from "@tanstack/solid-virtual";
|
||||
import { ImageComponent } from "@components/image";
|
||||
import { chunkRows } from "./chunk";
|
||||
import { deleteImage } from "@network/index";
|
||||
|
||||
type ImageOrDate =
|
||||
| { type: "image"; ID: string[] }
|
||||
@ -21,7 +20,7 @@ export const AllImages: Component = () => {
|
||||
items.push({ type: "date", date });
|
||||
const chunkedRows = chunkRows(3, images);
|
||||
for (const chunk of chunkedRows) {
|
||||
items.push({ type: "image", ID: chunk.map((c) => c.ImageID) });
|
||||
items.push({ type: "image", ID: chunk.map((c) => c.ID) });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Component, For, createSignal } from "solid-js";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { ListCard } from "@components/list-card";
|
||||
import { StackCard } from "@components/list-card";
|
||||
import { Button } from "@kobalte/core/button";
|
||||
import { Dialog } from "@kobalte/core/dialog";
|
||||
import { createList, ReachedListLimit } from "../../network";
|
||||
import { createToast } from "../../utils/show-toast";
|
||||
|
||||
export const Categories: Component = () => {
|
||||
const { lists, onRefetchImages } = useSearchImageContext();
|
||||
const { stacks, onRefetchImages } = useSearchImageContext();
|
||||
|
||||
const [title, setTitle] = createSignal("");
|
||||
const [description, setDescription] = createSignal("");
|
||||
@ -25,11 +25,11 @@ export const Categories: Component = () => {
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
setShowForm(false);
|
||||
onRefetchImages(); // Refresh the lists
|
||||
onRefetchImages(); // Refresh the stacks
|
||||
} catch (error) {
|
||||
console.error("Failed to create list:", error);
|
||||
if (error instanceof ReachedListLimit) {
|
||||
createToast("Reached limit!", "You've reached your limit for new lists");
|
||||
createToast("Reached limit!", "You've reached your limit for new stacks");
|
||||
}
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
@ -38,9 +38,9 @@ export const Categories: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="rounded-xl bg-white p-4 flex flex-col gap-2">
|
||||
<h2 class="text-xl font-bold">Generated Lists</h2>
|
||||
<h2 class="text-xl font-bold">Generated stacks</h2>
|
||||
<div class="w-full grid grid-cols-3 auto-rows-[minmax(100px,1fr)] gap-4">
|
||||
<For each={lists()}>{(list) => <ListCard list={list} />}</For>
|
||||
<For each={stacks()}>{(list) => <StackCard list={list} />}</For>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Component, For } from "solid-js";
|
||||
import { ImageComponent } from "@components/image";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { deleteImage } from "@network/index";
|
||||
|
||||
const NUMBER_OF_MAX_RECENT_IMAGES = 10;
|
||||
|
||||
@ -21,7 +20,7 @@ export const Recent: Component = () => {
|
||||
<h2 class="text-xl font-bold">Recent Screenshots</h2>
|
||||
<div class="grid grid-cols-3 gap-4 place-items-center">
|
||||
<For each={latestImages()}>
|
||||
{(image) => <ImageComponent ID={image.ImageID} onDelete={onDeleteImage} />}
|
||||
{(image) => <ImageComponent ID={image.ID} onDelete={onDeleteImage} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,15 +3,15 @@ import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { useNavigate, useParams } from "@solidjs/router";
|
||||
import { For, type Component } from "solid-js";
|
||||
import SolidjsMarkdown from "solidjs-markdown";
|
||||
import { ListCard } from "@components/list-card";
|
||||
import { StackCard } from "@components/list-card";
|
||||
|
||||
export const ImagePage: Component = () => {
|
||||
const { imageId } = useParams<{ imageId: string }>();
|
||||
const nav = useNavigate();
|
||||
|
||||
const { userImages, lists, onDeleteImage } = useSearchImageContext();
|
||||
const { userImages, stacks, onDeleteImage } = useSearchImageContext();
|
||||
|
||||
const image = () => userImages().find((i) => i.ImageID === imageId);
|
||||
const image = () => userImages().find((i) => i.ID === imageId);
|
||||
|
||||
return (
|
||||
<main class="flex flex-col items-center gap-4">
|
||||
@ -24,10 +24,10 @@ export const ImagePage: Component = () => {
|
||||
<div class="w-full bg-white rounded-xl p-4 flex flex-col gap-4">
|
||||
<h2 class="font-bold text-2xl">Description</h2>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<For each={image()?.Image.ImageLists}>
|
||||
<For each={image()?.ImageStacks}>
|
||||
{(imageList) => (
|
||||
<ListCard
|
||||
list={lists().find((l) => l.ID === imageList.ListID)!}
|
||||
<StackCard
|
||||
list={stacks().find((l) => l.ID === imageList.ListID)!}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@ -35,7 +35,7 @@ export const ImagePage: Component = () => {
|
||||
</div>
|
||||
<div class="w-full bg-white rounded-xl p-4">
|
||||
<h2 class="font-bold text-2xl">Description</h2>
|
||||
<SolidjsMarkdown>{image()?.Image.Description}</SolidjsMarkdown>
|
||||
<SolidjsMarkdown>{image()?.Description}</SolidjsMarkdown>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
@ -107,11 +107,11 @@ export const List: Component = () => {
|
||||
const { listId } = useParams();
|
||||
const nav = useNavigate();
|
||||
|
||||
const { lists, onDeleteImageFromStack } = useSearchImageContext();
|
||||
const { stacks, onDeleteImageFromStack } = useSearchImageContext();
|
||||
|
||||
const [accessToken] = createResource(getAccessToken);
|
||||
|
||||
const list = () => lists().find((l) => l.ID === listId);
|
||||
const list = () => stacks().find((l) => l.ID === listId);
|
||||
|
||||
const handleDeleteList = async () => {
|
||||
await deleteList(listId)
|
||||
@ -148,11 +148,11 @@ export const List: Component = () => {
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold text-neutral-900 border-r border-neutral-200 min-w-40">
|
||||
Image
|
||||
</th>
|
||||
<For each={l().Schema.SchemaItems}>
|
||||
<For each={l().SchemaItems}>
|
||||
{(item, index) => (
|
||||
<th
|
||||
class={`px-6 py-4 text-left text-sm font-semibold text-neutral-900 min-w-32 ${index() <
|
||||
l().Schema.SchemaItems
|
||||
l().SchemaItems
|
||||
.length -
|
||||
1
|
||||
? "border-r border-neutral-200"
|
||||
|
@ -2,7 +2,7 @@ import { Component, createSignal, For } from "solid-js";
|
||||
import { Search } from "@kobalte/core/search";
|
||||
import { IconSearch } from "@tabler/icons-solidjs";
|
||||
import { useSearch } from "./search";
|
||||
import { deleteImage, JustTheImageWhatAreTheseNames } from "@network/index";
|
||||
import { JustTheImageWhatAreTheseNames } from "@network/index";
|
||||
import { ImageComponent } from "@components/image";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
|
||||
@ -41,7 +41,7 @@ export const SearchPage: Component = () => {
|
||||
<Search.Content class="container relative w-full rounded-xl bg-white p-4 grid grid-cols-3 gap-4">
|
||||
<Search.Arrow />
|
||||
<For each={searchItems()}>
|
||||
{(item) => <ImageComponent ID={item.ImageID} onDelete={onDeleteImage} />}
|
||||
{(item) => <ImageComponent ID={item.ID} onDelete={onDeleteImage} />}
|
||||
</For>
|
||||
<Search.NoResult>No result found</Search.NoResult>
|
||||
</Search.Content>
|
||||
|
Reference in New Issue
Block a user