Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d2ccd34
refactor: split models/index into separate queries and mutations
AdamPospisil Jun 7, 2024
154f39d
fix: models imports in api
AdamPospisil Jun 7, 2024
0e3b16f
feat: models frontend WIP
AdamPospisil Jun 13, 2024
38f8b55
feat: frontend model upload & delete
AdamPospisil Jun 13, 2024
50d511f
redirect on model upload
AdamPospisil Jun 13, 2024
f9c2250
Merge branch 'dev' into feature/storage_frontend
AdamPospisil Jun 13, 2024
efd6f80
feat: model file download
AdamPospisil Jun 14, 2024
35863b0
feat: refetch models on model deletion
AdamPospisil Jun 14, 2024
4757838
fix: imports in model tests
AdamPospisil Jun 14, 2024
f65d28d
feat: model pages require enabled user WIP
AdamPospisil Jun 14, 2024
e445f44
fix: add correct typing to HOC withUserEnabled
vojtatom Jun 14, 2024
93a9dd3
feat: model upload form validation
AdamPospisil Jun 14, 2024
b74bae0
refactor: added some comments to model upload page
AdamPospisil Jun 14, 2024
dab3475
feat: made mode upload into a dialog instead its own page
AdamPospisil Jun 14, 2024
51f0bdc
model detail dialog
AdamPospisil Jun 14, 2024
04894cc
Merge branch 'dev' into feature/storage_frontend
AdamPospisil Jun 17, 2024
7780234
refactor: added toPlain to models mutations and queries
AdamPospisil Jun 17, 2024
f76cf0c
feat: model upload form validation uses toast to display messages
AdamPospisil Jun 18, 2024
eb1847a
feat: moved model detail page to modal dialog
AdamPospisil Jun 18, 2024
c7744b4
refactor: delete model is now client hook instead of api endpoint
AdamPospisil Jun 18, 2024
bf999ee
feat: model delete has to be confirmed in dialog window
AdamPospisil Jun 18, 2024
064f31e
fix: open model detail dialog only once the model instance is fetched
AdamPospisil Jun 20, 2024
0b397e3
feat: download all model files as an archive
AdamPospisil Jun 20, 2024
94a2068
Merge branch 'dev' into feature/storage_frontend
AdamPospisil Jun 20, 2024
669d6bf
Merge branch 'dev' into feature/storage_frontend
AdamPospisil Jun 20, 2024
0c4a418
chore: remove Own from naming
vojtatom Jun 20, 2024
6f20b1b
feat: rename useOwnModels to useModels
vojtatom Jun 20, 2024
f04fc9d
Merge remote-tracking branch 'origin/dev' into feature/storage_frontend
vojtatom Jun 20, 2024
680b2b9
fix: renamed Own methods
vojtatom Jun 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions studio/app/api/models/[model]/data/[file]/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use server";

import { createOwnModel } from "@features/models/mutations/createOwnModel";
import { deleteOwnModel } from "@features/models/mutations/deleteOwnModel";
import { createModel } from "@features/models/mutations/createModel";
import { deleteModel } from "@features/models/mutations/deleteModel";
import { expect, test } from "vitest";
import { GET } from "./route";

Expand All @@ -17,7 +17,7 @@ const file = {
} as File;

test("GET /models/[model]/data/[file]", async () => {
const model = await createOwnModel(
const model = await createModel(
{
name: "TEST",
coordinateSystem: "WGS84",
Expand All @@ -41,5 +41,5 @@ test("GET /models/[model]/data/[file]", async () => {
expect(response.status).toBe(200);
expect(body).toBe("test");

await deleteOwnModel(model.id);
await deleteModel(model.id);
});
4 changes: 2 additions & 2 deletions studio/app/api/models/[model]/data/[file]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { downloadOwnModelFile } from "@features/models/queries/downloadOwnModelFile";
import { downloadModelFile } from "@features/models/queries/downloadModelFile";
import mime from "mime";
import { Readable } from "stream";

Expand All @@ -7,7 +7,7 @@ export async function GET(
{ params }: { params: { model: string; file: string } }
) {
const fileStream: ReadableStream = Readable.toWeb(
await downloadOwnModelFile(parseInt(params.model), params.file)
await downloadModelFile(parseInt(params.model), params.file)
) as ReadableStream;

return new Response(fileStream, {
Expand Down
15 changes: 15 additions & 0 deletions studio/app/api/models/[model]/data/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { downloadModelArchive } from "@features/models/queries/downloadModelArchive";
import mime from "mime";

export async function GET(
req: Request,
{ params }: { params: { model: string} }
) {
const fileStream: ReadableStream = await downloadModelArchive(parseInt(params.model));

return new Response(fileStream, {
headers: {
"Content-Type": mime.getType(params.model) ?? "application/octet-stream",
},
});
}
4 changes: 2 additions & 2 deletions studio/app/api/models/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use server";

import { deleteOwnModel } from "@features/models/mutations/deleteOwnModel";
import { deleteModel } from "@features/models/mutations/deleteModel";
import { expect, test } from "vitest";
import { POST } from "./route";

Expand Down Expand Up @@ -32,5 +32,5 @@ test("POST /models", async () => {
},
});

await deleteOwnModel(body.id);
await deleteModel(body.id);
});
9 changes: 4 additions & 5 deletions studio/app/api/models/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { createOwnModel } from "@features/models/mutations/createOwnModel";
import { createModel } from "@features/models/mutations/createModel";
import { z } from "zod";
import { zfd } from "zod-form-data";

const postSchema = zfd.formData({
file: zfd.repeatableOfType(zfd.file()),
files: zfd.repeatableOfType(zfd.file()),
name: zfd.text(),
coordinateSystem: zfd.text().optional(),
});

export async function POST(req: Request) {
try {
const data = postSchema.parse(await req.formData());

const model = await createOwnModel(
const model = await createModel(
{
name: data.name,
coordinateSystem: data.coordinateSystem,
},
data.file
data.files
);

return Response.json(model, { status: 201 });
Expand Down
133 changes: 133 additions & 0 deletions studio/app/models/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"use client";

import {
ActionButton,
ActionMenu,
DialogTrigger,
Flex,
Item,
ListView,
Text,
} from "@adobe/react-spectrum";
import { withPageAuthRequired } from "@auth0/nextjs-auth0/client";
import { ContentContainer } from "@core/components/ContentContainer";
import { NoData } from "@core/components/Empty";
import { withUserEnabled } from "@core/utils/withUserEnabled";
import ModelDeleteDialog from "@features/models/components/ModelDeleteDialog";
import ModelDetailDialog from "@features/models/components/ModelDetailDialog";
import ModelUploadDialog from "@features/models/components/ModelUploadDialog";
import { useModels } from "@features/models/hooks/useModels";
import Header from "@features/projects/components/Header";
import { ToastContainer } from "@react-spectrum/toast";
import File from "@spectrum-icons/illustrations/File";
import { useState } from "react";

function ModelListPage() {
const { data: models, isLoading, refetch } = useModels();
const [detailDialogOpen, setDetailDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [modelId, setModelId] = useState<number | null>(null);

async function downloadModelArchive(modelId: number, modelName: string) {
const response = await fetch(`/api/models/${modelId}/data`, {
method: "GET",
});
const data = await response.blob();
const link = document.createElement("a");
link.href = URL.createObjectURL(data);
link.download = `${modelName}.zip`;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

return (
<Flex
direction="column"
width="100%"
height="100%"
gap="size-10"
justifyContent="start"
alignItems="start"
>
<Header
nav={[
{
key: "models",
children: "Models",
},
]}
/>
<ContentContainer>
<DialogTrigger>
<ActionButton>Upload New Model</ActionButton>
{(close) => (
<ModelUploadDialog
close={() => {
close();
refetch();
}}
/>
)}
</DialogTrigger>
<ListView
width="size-6000"
minHeight="size-3000"
aria-label="ListView"
renderEmptyState={() => <NoData />}
>
{models.map((model) => (
<Item key={model.id} textValue={model.name}>
<File />
<Text>{model.name}</Text>
<ActionMenu
onAction={(key) => {
if (key === "delete") {
setDeleteDialogOpen(true);
setModelId(model.id);
} else if (key === "detail") {
setDetailDialogOpen(true);
setModelId(model.id);
} else if (key === "download") {
downloadModelArchive(model.id, model.name);
}
}}
>
<Item key="detail" textValue="View">
<Text>View</Text>
</Item>
<Item key="download" textValue="Download">
<Text>Download</Text>
</Item>
<Item key="delete" textValue="Delete">
<Text>Delete</Text>
</Item>
</ActionMenu>
</Item>
))}
</ListView>
</ContentContainer>
<ModelDetailDialog
open={detailDialogOpen}
close={() => {
setDetailDialogOpen(false);
setModelId(null);
}}
modelId={modelId}
/>
<ModelDeleteDialog
open={deleteDialogOpen}
close={() => {
setDeleteDialogOpen(false);
setModelId(null);
refetch();
}}
modelId={modelId}
/>
<ToastContainer />
</Flex>
);
}

export default withPageAuthRequired(withUserEnabled(ModelListPage));
20 changes: 20 additions & 0 deletions studio/app/projects/[projectId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import { withPageAuthRequired } from "@auth0/nextjs-auth0/client";
import { withUserEnabled } from "@core/utils/withUserEnabled";

type ProjectPageProps = {
projectId: string;
};

function ProjectPage({ projectId }: ProjectPageProps) {
console.log(projectId);

return (
<div>
<h1>Project Page</h1>
</div>
);
}

export default withPageAuthRequired(withUserEnabled(ProjectPage));
4 changes: 2 additions & 2 deletions studio/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import DuplicateDialog from "@features/projects/components/DuplicateDialog";
import EditDialog from "@features/projects/components/EditDialog";
import Header from "@features/projects/components/Header";
import { useHandleProjectAction } from "@features/projects/hooks/useHandleProjectAction";
import { useOwnProjects } from "@features/projects/hooks/useOwnProjects";
import { useProjects } from "@features/projects/hooks/useProjects";
import { ToastContainer } from "@react-spectrum/toast";
import File from "@spectrum-icons/illustrations/File";
import { Key, useCallback } from "react";

function ProjectListPage() {
const { data: projects, isLoading, refetch } = useOwnProjects();
const { data: projects, isLoading, refetch } = useProjects();
const [dispatchAction, closeDialog, dialogState] = useHandleProjectAction();

const handleCloseActionDialog = useCallback(() => {
Expand Down
10 changes: 5 additions & 5 deletions studio/features/auth/acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export async function canCreateProject() {
return !!user;
}

export async function canReadOwnProjects() {
export async function canReadProjects() {
const user = await getUserToken();
return !!user;
}

export async function canEditOwnProject() {
export async function canEditProject() {
const user = await getUserToken();
return !!user;
}
Expand All @@ -21,17 +21,17 @@ export async function canCreateModel() {
return !!user;
}

export async function canReadOwnModels() {
export async function canReadModels() {
const user = await getUserToken();
return !!user;
}

export async function canEditOwnModel() {
export async function canEditModel() {
const user = await getUserToken();
return !!user;
}

export async function canEditOwnModelMetadata() {
export async function canEditModelMetadata() {
const user = await getUserToken();
return !!user;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use server";

import { canEditOwnModelMetadata } from "@features/auth/acl";
import { canEditModelMetadata } from "@features/auth/acl";
import { getUserToken } from "@features/auth/user";
import { ModelMetadata } from "@features/db/entities/modelMetadata";
import { injectRepository } from "@features/db/helpers";

export async function deleteModelMetadata(
where: Pick<ModelMetadata, "project_id" | "model_id" | "key">
) {
if (!(await canEditOwnModelMetadata())) throw new Error("Unauthorized");
if (!(await canEditModelMetadata())) throw new Error("Unauthorized");

const user = await getUserToken();

Expand Down
8 changes: 4 additions & 4 deletions studio/features/modelMetadata/mutations/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Model } from "@features/db/entities/model";
import { Project } from "@features/db/entities/project";

import { ModelMetadata } from "@features/db/entities/modelMetadata";
import { createOwnModel } from "@features/models/mutations/createOwnModel";
import { deleteOwnModel } from "@features/models/mutations/deleteOwnModel";
import { createModel } from "@features/models/mutations/createModel";
import { deleteModel } from "@features/models/mutations/deleteModel";
import { deleteProject } from "@features/projects/mutations/deleteProject";
import { createProject } from "../../projects/mutations/createProject";
import { getAllProjectMetadata } from "../queries/getAllProjectMetadata";
Expand All @@ -21,7 +21,7 @@ test("project CRUD", async () => {
description: "This is a test project",
});

model = await createOwnModel(
model = await createModel(
{
name: "Test Model",
},
Expand Down Expand Up @@ -65,5 +65,5 @@ test("project CRUD", async () => {

// CLEANUP
await deleteProject(project.id);
await deleteOwnModel(model!.id);
await deleteModel(model!.id);
});
4 changes: 2 additions & 2 deletions studio/features/modelMetadata/mutations/saveModelMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { canEditOwnModelMetadata } from "@features/auth/acl";
import { canEditModelMetadata } from "@features/auth/acl";
import { getUserToken } from "@features/auth/user";
import { ModelMetadata } from "@features/db/entities/modelMetadata";
import { injectRepository } from "@features/db/helpers";
Expand All @@ -10,7 +10,7 @@ export async function saveModelMetadata(
"project_id" | "model_id" | "object_id" | "key" | "value" | "type"
>
) {
if (!(await canEditOwnModelMetadata())) throw new Error("Unauthorized");
if (!(await canEditModelMetadata())) throw new Error("Unauthorized");

const userToken = await getUserToken();

Expand Down
Loading