From 8e558d234252767bf125173afedd165971b90a3e Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Thu, 8 Jul 2021 21:17:07 +1000 Subject: [PATCH] massive refactor toDrop cascadeStudio and add CadQuery + OpenSCAD resolves #400 --- .gitmodules | 3 - .vscode/settings.json | 2 + README.md | 10 +- app/.eslintignore | 2 +- .../db/dataMigrations/.keep} | 0 .../migration.sql | 9 + .../migration.sql | 31 ++ .../migration.sql | 59 +++ .../migration.sql | 5 + app/api/db/migrations/migration_lock.toml | 1 + app/api/db/schema.prisma | 38 +- app/api/db/seed.js | 38 +- app/api/package.json | 1 + app/api/src/graphql/ProjectReactions.sdl.js | 39 ++ app/api/src/graphql/comments.sdl.js | 8 +- app/api/src/graphql/partReactions.sdl.js | 39 -- app/api/src/graphql/parts.sdl.js | 45 -- app/api/src/graphql/projects.sdl.ts | 52 +++ app/api/src/graphql/users.sdl.js | 6 +- app/api/src/lib/{owner.js => owner.ts} | 14 +- .../src/services/comments/comments.test.js | 9 - .../comments/{comments.js => comments.ts} | 4 +- .../src/services/{helpers.js => helpers.ts} | 22 + .../partReactions/partReactions.test.js | 9 - app/api/src/services/parts/parts.js | 117 ----- app/api/src/services/parts/parts.test.js | 9 - .../projectReactions.js} | 40 +- app/api/src/services/projects/projects.ts | 141 ++++++ .../subjectAccessRequests.test.js | 9 - app/api/src/services/users/users.test.js | 9 - app/api/src/services/users/users.ts | 16 +- app/redwood.toml | 2 +- app/web/config/webpack.config.js | 83 ---- app/web/package.json | 17 +- app/web/src/{App.js => App.tsx} | 0 app/web/src/Routes.js | 14 +- app/web/src/cascade | 1 - .../AdminProjects.tsx} | 62 +-- .../AdminProjectsCell.tsx} | 14 +- .../src/components/Breadcrumb/Breadcrumb.js | 42 -- .../Breadcrumb/Breadcrumb.stories.js | 7 - app/web/src/components/Button/Button.js | 4 +- .../src/components/CadPackage/CadPackage.tsx | 30 ++ .../CaptureButton/CaptureButton.tsx | 199 +++++++++ .../EditableProjecTitle.tsx | 105 +++++ .../src/components/EditorMenu/EditorMenu.tsx | 23 +- app/web/src/components/EditorMenu/helpers.ts | 4 +- .../EmojiReaction/EmojiReaction.stories.js | 7 - .../{EmojiReaction.js => EmojiReaction.tsx} | 41 +- app/web/src/components/EncodedUrl/helpers.ts | 7 +- app/web/src/components/Gravatar/Gravatar.tsx | 27 ++ .../IdeCascadeStudio/IdeCascadeStudio.js | 126 ------ .../IdeCascadeStudio.stories.js | 7 - .../{IdeContainer.js => IdeContainer.tsx} | 53 +-- app/web/src/components/IdeEditor/IdeEditor.js | 8 +- .../src/components/IdeHeader/IdeHeader.tsx | 141 +++++- .../src/components/IdePartCell/IdePartCell.js | 90 ---- .../IdeProjectCell.mock.ts} | 2 +- .../IdeProjectCell/IdeProjectCell.stories.tsx | 16 + .../IdeProjectCell.test.tsx} | 13 +- .../IdeProjectCell/IdeProjectCell.tsx | 120 +++++ .../src/components/IdeToolbar/IdeToolbar.js | 422 ------------------ .../IdeToolbar/IdeToolbar.stories.js | 20 - .../IdeViewer/{IdeViewer.js => IdeViewer.tsx} | 48 +- .../src/components/IdeWrapper/IdeWrapper.tsx | 23 +- .../src/components/IdeWrapper/useSaveCode.ts | 27 ++ .../LandingSection/LandingSection.js | 24 +- .../NavPlusButton/NavPlusButton.tsx | 16 +- .../components/PanelToolbar/PanelToolbar.tsx | 39 +- .../src/components/PartProfile/PartProfile.js | 306 ------------- .../PartProfile/PartProfile.stories.js | 7 - .../PartsOfUserCell.stories.js | 20 - .../ProfileSlashLogin.stories.tsx | 7 + .../ProfileSlashLogin.test.tsx | 11 + .../ProfileSlashLogin/ProfileSlashLogin.tsx | 114 +++++ .../ProfileViewer/ProfileViewer.tsx | 22 + .../ProjectCell.mock.ts} | 2 +- .../ProjectCell.stories.tsx} | 6 +- .../ProjectCell.test.tsx} | 8 +- .../ProjectCell.tsx} | 104 +++-- .../ProjectForm.tsx} | 16 +- .../ProjectProfile/ProjectProfile.tsx | 293 ++++++++++++ .../ProjectReactions.tsx} | 18 +- .../ProjectReactionsCell.mock.ts} | 2 +- .../ProjectReactionsCell.stories.tsx} | 6 +- .../ProjectReactionsCell.test.tsx} | 8 +- .../ProjectReactionsCell.tsx} | 12 +- .../{Parts/Parts.js => Projects/Projects.tsx} | 31 +- .../ProjectsCell.tsx} | 20 +- .../ProjectsOfUserCell.mock.ts} | 2 +- .../ProjectsOfUserCell.stories.jsx} | 6 +- .../ProjectsOfUserCell.test.tsx} | 13 +- .../ProjectsOfUserCell.tsx} | 18 +- app/web/src/components/Svg/Svg.tsx | 44 +- .../src/components/UserProfile/UserProfile.js | 15 +- .../helpers/cadPackages/cadQueryController.ts | 4 +- app/web/src/helpers/cadPackages/index.ts | 8 +- .../helpers/cadPackages/openScadController.ts | 9 +- app/web/src/helpers/cascadeController.js | 55 --- app/web/src/helpers/cloudinary.js | 11 - .../src/helpers/hooks/use3dViewerResize.ts | 51 +++ app/web/src/helpers/hooks/useIdeContext.ts | 21 +- app/web/src/helpers/hooks/useIdeState.ts | 21 +- app/web/src/helpers/hooks/useUpdateProject.ts | 21 + .../helpers/hooks/{useUser.js => useUser.ts} | 0 app/web/src/index.css | 3 + app/web/src/index.html | 21 - .../pages/AdminPartsPage/AdminPartsPage.js | 12 - .../AdminProjectsPage/AdminProjectsPage.tsx | 12 + app/web/src/pages/DevIdePage/DevIdePage.tsx | 19 +- .../src/pages/DraftPartPage/DraftPartPage.js | 18 - .../DraftPartPage/DraftPartPage.stories.js | 7 - .../pages/DraftPartPage/DraftPartPage.test.js | 11 - .../DraftProjectPage.stories.tsx | 7 + .../DraftProjectPage.test.tsx} | 6 +- .../DraftProjectPage/DraftProjectPage.tsx | 36 ++ .../EditPartPage/EditPartPage.stories.js | 7 - .../pages/EditPartPage/EditPartPage.test.js | 11 - .../EditProjectPage.stories.tsx | 7 + .../EditProjectPage/EditProjectPage.test.tsx} | 6 +- .../EditProjectPage.tsx} | 12 +- .../src/pages/EditUserPage/EditUserPage.js | 2 +- app/web/src/pages/HomePage/HomePage.js | 14 +- app/web/src/pages/IdePartPage/IdePartPage.js | 15 - .../src/pages/IdePartPage/IdePartPage.test.js | 11 - .../IdeProjectPage/IdeProjectPage.test.tsx} | 6 +- .../pages/IdeProjectPage/IdeProjectPage.tsx | 13 + .../pages/NewPartPage/NewPartPage.stories.js | 7 - .../src/pages/NewPartPage/NewPartPage.test.js | 11 - .../NewProjectPage/NewProjectPage.stories.tsx | 7 + .../NewProjectPage/NewProjectPage.test.tsx} | 6 +- .../NewProjectPage.tsx} | 14 +- app/web/src/pages/PartPage/PartPage.js | 22 - .../src/pages/PartPage/PartPage.stories.js | 7 - app/web/src/pages/PartPage/PartPage.test.js | 11 - .../pages/ProjectPage/ProjectPage.stories.tsx | 7 + .../ProjectPage/ProjectPage.test.tsx} | 6 +- app/web/src/pages/ProjectPage/ProjectPage.tsx | 25 ++ .../SubjectAccessRequestsPage.js | 6 +- app/web/tailwind.config.js | 12 +- app/yarn.lock | 143 ++---- docs/blog/2020-09-06-openscad-review.md | 2 +- docs/blog/2020-10-31-curated-code-cad.md | 4 +- .../adding-clearances.mdx | 4 +- .../definitive-beginners/adding-fillets.mdx | 6 +- .../extruding-2d-shapes.mdx | 4 +- docs/docs/definitive-beginners/loops.mdx | 14 +- docs/docs/definitive-beginners/modifiers.mdx | 6 +- .../definitive-beginners/module-arguments.mdx | 6 +- docs/docs/definitive-beginners/modules.mdx | 6 +- docs/docs/definitive-beginners/the-basics.mdx | 10 +- docs/docs/definitive-beginners/wrap-up.mdx | 2 +- .../your-openscad-journey.mdx | 4 +- .../general-cadhub/external-resource-url.mdx | 2 +- docs/docs/getting-started/getting-started.mdx | 2 +- docs/docs/round-anything/api-reference.mdx | 22 +- docs/docs/round-anything/overview.mdx | 14 +- docs/src/pages/index.js | 4 +- 158 files changed, 2335 insertions(+), 2300 deletions(-) rename app/{web/src/components/PartReactions/PartReactions.test.js => api/db/dataMigrations/.keep} (100%) create mode 100644 app/api/db/migrations/20210704054715_create_data_migrations/migration.sql create mode 100644 app/api/db/migrations/20210708100818_drop_cascade_tables/migration.sql create mode 100644 app/api/db/migrations/20210709103029_add_tables_back_in/migration.sql create mode 100644 app/api/db/migrations/20210716102203_add_cad_package_to_projects/migration.sql create mode 100644 app/api/src/graphql/ProjectReactions.sdl.js delete mode 100644 app/api/src/graphql/partReactions.sdl.js delete mode 100644 app/api/src/graphql/parts.sdl.js create mode 100644 app/api/src/graphql/projects.sdl.ts rename app/api/src/lib/{owner.js => owner.ts} (80%) delete mode 100644 app/api/src/services/comments/comments.test.js rename app/api/src/services/comments/{comments.js => comments.ts} (88%) rename app/api/src/services/{helpers.js => helpers.ts} (70%) delete mode 100644 app/api/src/services/partReactions/partReactions.test.js delete mode 100644 app/api/src/services/parts/parts.js delete mode 100644 app/api/src/services/parts/parts.test.js rename app/api/src/services/{partReactions/partReactions.js => projectReactions/projectReactions.js} (53%) create mode 100644 app/api/src/services/projects/projects.ts delete mode 100644 app/api/src/services/subjectAccessRequests/subjectAccessRequests.test.js delete mode 100644 app/api/src/services/users/users.test.js rename app/web/src/{App.js => App.tsx} (100%) delete mode 160000 app/web/src/cascade rename app/web/src/components/{AdminParts/AdminParts.js => AdminProjects/AdminProjects.tsx} (58%) rename app/web/src/components/{AdminPartsCell/AdminPartsCell.js => AdminProjectsCell/AdminProjectsCell.tsx} (60%) delete mode 100644 app/web/src/components/Breadcrumb/Breadcrumb.js delete mode 100644 app/web/src/components/Breadcrumb/Breadcrumb.stories.js create mode 100644 app/web/src/components/CadPackage/CadPackage.tsx create mode 100644 app/web/src/components/CaptureButton/CaptureButton.tsx create mode 100644 app/web/src/components/EditableProjecTitle/EditableProjecTitle.tsx delete mode 100644 app/web/src/components/EmojiReaction/EmojiReaction.stories.js rename app/web/src/components/EmojiReaction/{EmojiReaction.js => EmojiReaction.tsx} (67%) create mode 100644 app/web/src/components/Gravatar/Gravatar.tsx delete mode 100644 app/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js delete mode 100644 app/web/src/components/IdeCascadeStudio/IdeCascadeStudio.stories.js rename app/web/src/components/IdeContainer/{IdeContainer.js => IdeContainer.tsx} (64%) delete mode 100644 app/web/src/components/IdePartCell/IdePartCell.js rename app/web/src/components/{IdePartCell/IdePartCell.mock.js => IdeProjectCell/IdeProjectCell.mock.ts} (87%) create mode 100644 app/web/src/components/IdeProjectCell/IdeProjectCell.stories.tsx rename app/web/src/components/{PartsOfUserCell/PartsOfUserCell.test.js => IdeProjectCell/IdeProjectCell.test.tsx} (55%) create mode 100644 app/web/src/components/IdeProjectCell/IdeProjectCell.tsx delete mode 100644 app/web/src/components/IdeToolbar/IdeToolbar.js delete mode 100644 app/web/src/components/IdeToolbar/IdeToolbar.stories.js rename app/web/src/components/IdeViewer/{IdeViewer.js => IdeViewer.tsx} (87%) create mode 100644 app/web/src/components/IdeWrapper/useSaveCode.ts delete mode 100644 app/web/src/components/PartProfile/PartProfile.js delete mode 100644 app/web/src/components/PartProfile/PartProfile.stories.js delete mode 100644 app/web/src/components/PartsOfUserCell/PartsOfUserCell.stories.js create mode 100644 app/web/src/components/ProfileSlashLogin/ProfileSlashLogin.stories.tsx create mode 100644 app/web/src/components/ProfileSlashLogin/ProfileSlashLogin.test.tsx create mode 100644 app/web/src/components/ProfileSlashLogin/ProfileSlashLogin.tsx create mode 100644 app/web/src/components/ProfileViewer/ProfileViewer.tsx rename app/web/src/components/{PartCell/PartCell.mock.js => ProjectCell/ProjectCell.mock.ts} (89%) rename app/web/src/components/{IdePartCell/IdePartCell.stories.js => ProjectCell/ProjectCell.stories.tsx} (67%) rename app/web/src/components/{IdePartCell/IdePartCell.test.js => ProjectCell/ProjectCell.test.tsx} (77%) rename app/web/src/components/{PartCell/PartCell.js => ProjectCell/ProjectCell.tsx} (53%) rename app/web/src/components/{PartForm/PartForm.js => ProjectForm/ProjectForm.tsx} (88%) create mode 100644 app/web/src/components/ProjectProfile/ProjectProfile.tsx rename app/web/src/components/{PartReactions/PartReactions.js => ProjectReactions/ProjectReactions.tsx} (80%) rename app/web/src/components/{PartsOfUserCell/PartsOfUserCell.mock.js => ProjectReactionsCell/ProjectReactionsCell.mock.ts} (83%) rename app/web/src/components/{PartReactionsCell/PartReactionsCell.stories.js => ProjectReactionsCell/ProjectReactionsCell.stories.tsx} (63%) rename app/web/src/components/{PartCell/PartCell.test.js => ProjectReactionsCell/ProjectReactionsCell.test.tsx} (73%) rename app/web/src/components/{PartReactionsCell/PartReactionsCell.js => ProjectReactionsCell/ProjectReactionsCell.tsx} (50%) rename app/web/src/components/{Parts/Parts.js => Projects/Projects.tsx} (77%) rename app/web/src/components/{PartsCell/PartsCell.js => ProjectsCell/ProjectsCell.tsx} (58%) rename app/web/src/components/{PartReactionsCell/PartReactionsCell.mock.js => ProjectsOfUserCell/ProjectsOfUserCell.mock.ts} (84%) rename app/web/src/components/{PartCell/PartCell.stories.js => ProjectsOfUserCell/ProjectsOfUserCell.stories.jsx} (64%) rename app/web/src/components/{PartReactionsCell/PartReactionsCell.test.js => ProjectsOfUserCell/ProjectsOfUserCell.test.tsx} (55%) rename app/web/src/components/{PartsOfUserCell/PartsOfUserCell.js => ProjectsOfUserCell/ProjectsOfUserCell.tsx} (50%) delete mode 100644 app/web/src/helpers/cascadeController.js create mode 100644 app/web/src/helpers/hooks/use3dViewerResize.ts create mode 100644 app/web/src/helpers/hooks/useUpdateProject.ts rename app/web/src/helpers/hooks/{useUser.js => useUser.ts} (100%) delete mode 100644 app/web/src/pages/AdminPartsPage/AdminPartsPage.js create mode 100644 app/web/src/pages/AdminProjectsPage/AdminProjectsPage.tsx delete mode 100644 app/web/src/pages/DraftPartPage/DraftPartPage.js delete mode 100644 app/web/src/pages/DraftPartPage/DraftPartPage.stories.js delete mode 100644 app/web/src/pages/DraftPartPage/DraftPartPage.test.js create mode 100644 app/web/src/pages/DraftProjectPage/DraftProjectPage.stories.tsx rename app/web/src/{components/IdeCascadeStudio/IdeCascadeStudio.test.js => pages/DraftProjectPage/DraftProjectPage.test.tsx} (51%) create mode 100644 app/web/src/pages/DraftProjectPage/DraftProjectPage.tsx delete mode 100644 app/web/src/pages/EditPartPage/EditPartPage.stories.js delete mode 100644 app/web/src/pages/EditPartPage/EditPartPage.test.js create mode 100644 app/web/src/pages/EditProjectPage/EditProjectPage.stories.tsx rename app/web/src/{components/IdeToolbar/IdeToolbar.test.js => pages/EditProjectPage/EditProjectPage.test.tsx} (52%) rename app/web/src/pages/{EditPartPage/EditPartPage.js => EditProjectPage/EditProjectPage.tsx} (53%) delete mode 100644 app/web/src/pages/IdePartPage/IdePartPage.js delete mode 100644 app/web/src/pages/IdePartPage/IdePartPage.test.js rename app/web/src/{components/EmojiReaction/EmojiReaction.test.js => pages/IdeProjectPage/IdeProjectPage.test.tsx} (53%) create mode 100644 app/web/src/pages/IdeProjectPage/IdeProjectPage.tsx delete mode 100644 app/web/src/pages/NewPartPage/NewPartPage.stories.js delete mode 100644 app/web/src/pages/NewPartPage/NewPartPage.test.js create mode 100644 app/web/src/pages/NewProjectPage/NewProjectPage.stories.tsx rename app/web/src/{components/PartProfile/PartProfile.test.js => pages/NewProjectPage/NewProjectPage.test.tsx} (53%) rename app/web/src/pages/{NewPartPage/NewPartPage.js => NewProjectPage/NewProjectPage.tsx} (66%) delete mode 100644 app/web/src/pages/PartPage/PartPage.js delete mode 100644 app/web/src/pages/PartPage/PartPage.stories.js delete mode 100644 app/web/src/pages/PartPage/PartPage.test.js create mode 100644 app/web/src/pages/ProjectPage/ProjectPage.stories.tsx rename app/web/src/{components/Breadcrumb/Breadcrumb.test.js => pages/ProjectPage/ProjectPage.test.tsx} (56%) create mode 100644 app/web/src/pages/ProjectPage/ProjectPage.tsx diff --git a/.gitmodules b/.gitmodules index 51e0448..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "web/src/cascade"] - path = app/web/src/cascade - url = https://github.com/Irev-Dev/CascadeStudio.git diff --git a/.vscode/settings.json b/.vscode/settings.json index 34de47f..d7cb8aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "cSpell.words": [ "Hutten", + "cadquery", + "openscad", "sendmail" ] } diff --git a/README.md b/README.md index 3976431..c006956 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ # [C a d H u b](https://cadhub.xyz) + +Let's help Code-CAD reach its [full potential!](https://cadhub.xyz) We're making a ~~cad~~hub for the Code-CAD community, think of it as model-repository crossed with a live editor. We have integrations in progress for [OpenSCAD](https://cadhub.xyz/dev-ide/openscad) and [CadQuery](https://cadhub.xyz/dev-ide/cadquery) with [more coming soon](https://github.com/Irev-Dev/curated-code-cad). + +If you want to be involved in anyway, checkout the [Road Map](https://github.com/Irev-Dev/cadhub/discussions/212) and get in touch via, [twitter](https://twitter.com/IrevDev), [discord](https://discord.gg/SD7zFRNjGH) or [discussions](https://github.com/Irev-Dev/cadhub/discussions). + + ## Getting your dev environment setup @@ -10,9 +16,9 @@ Because we're integrating cascadeStudio, this is done some what crudely for the time being, so you'll need to clone the repo with submodules. ```terminal -git clone --recurse-submodules -j8 git@github.com:Irev-Dev/cadhub.git +git clone git@github.com:Irev-Dev/cadhub.git # or -git clone --recurse-submodules -j8 https://github.com/Irev-Dev/cadhub.git +git clone https://github.com/Irev-Dev/cadhub.git ``` Install dependencies diff --git a/app/.eslintignore b/app/.eslintignore index 8a1a375..8b13789 100644 --- a/app/.eslintignore +++ b/app/.eslintignore @@ -1 +1 @@ -/web/src/cascade/* + diff --git a/app/web/src/components/PartReactions/PartReactions.test.js b/app/api/db/dataMigrations/.keep similarity index 100% rename from app/web/src/components/PartReactions/PartReactions.test.js rename to app/api/db/dataMigrations/.keep diff --git a/app/api/db/migrations/20210704054715_create_data_migrations/migration.sql b/app/api/db/migrations/20210704054715_create_data_migrations/migration.sql new file mode 100644 index 0000000..efdc0ae --- /dev/null +++ b/app/api/db/migrations/20210704054715_create_data_migrations/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "RW_DataMigration" ( + "version" TEXT NOT NULL, + "name" TEXT NOT NULL, + "startedAt" TIMESTAMP(3) NOT NULL, + "finishedAt" TIMESTAMP(3) NOT NULL, + + PRIMARY KEY ("version") +); diff --git a/app/api/db/migrations/20210708100818_drop_cascade_tables/migration.sql b/app/api/db/migrations/20210708100818_drop_cascade_tables/migration.sql new file mode 100644 index 0000000..98b2b2f --- /dev/null +++ b/app/api/db/migrations/20210708100818_drop_cascade_tables/migration.sql @@ -0,0 +1,31 @@ +/* + Warnings: + + - You are about to drop the `Comment` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Part` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `PartReaction` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Comment" DROP CONSTRAINT "Comment_partId_fkey"; + +-- DropForeignKey +ALTER TABLE "Comment" DROP CONSTRAINT "Comment_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "Part" DROP CONSTRAINT "Part_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_partId_fkey"; + +-- DropForeignKey +ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_userId_fkey"; + +-- DropTable +DROP TABLE "Comment"; + +-- DropTable +DROP TABLE "Part"; + +-- DropTable +DROP TABLE "PartReaction"; diff --git a/app/api/db/migrations/20210709103029_add_tables_back_in/migration.sql b/app/api/db/migrations/20210709103029_add_tables_back_in/migration.sql new file mode 100644 index 0000000..f65d24a --- /dev/null +++ b/app/api/db/migrations/20210709103029_add_tables_back_in/migration.sql @@ -0,0 +1,59 @@ +-- CreateTable +CREATE TABLE "Project" ( + "id" TEXT NOT NULL, + "title" VARCHAR(25) NOT NULL, + "description" TEXT, + "code" TEXT, + "mainImage" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "userId" TEXT NOT NULL, + "deleted" BOOLEAN NOT NULL DEFAULT false, + + PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ProjectReaction" ( + "id" TEXT NOT NULL, + "emote" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Comment" ( + "id" TEXT NOT NULL, + "text" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Project.title_userId_unique" ON "Project"("title", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ProjectReaction.emote_userId_projectId_unique" ON "ProjectReaction"("emote", "userId", "projectId"); + +-- AddForeignKey +ALTER TABLE "Project" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectReaction" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectReaction" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/app/api/db/migrations/20210716102203_add_cad_package_to_projects/migration.sql b/app/api/db/migrations/20210716102203_add_cad_package_to_projects/migration.sql new file mode 100644 index 0000000..db3560f --- /dev/null +++ b/app/api/db/migrations/20210716102203_add_cad_package_to_projects/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "CadPackage" AS ENUM ('openscad', 'cadquery'); + +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "cadPackage" "CadPackage" NOT NULL DEFAULT E'openscad'; diff --git a/app/api/db/migrations/migration_lock.toml b/app/api/db/migrations/migration_lock.toml index 2cdb8f0..fbffa92 100644 --- a/app/api/db/migrations/migration_lock.toml +++ b/app/api/db/migrations/migration_lock.toml @@ -1,2 +1,3 @@ # Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) provider = "postgresql" \ No newline at end of file diff --git a/app/api/db/schema.prisma b/app/api/db/schema.prisma index dfe3654..95d924e 100644 --- a/app/api/db/schema.prisma +++ b/app/api/db/schema.prisma @@ -14,8 +14,7 @@ generator client { // ADMIN // } -// enum PartType { -// CASCADESTUDIO +// enum ProjectType { // JSCAD // } @@ -33,15 +32,20 @@ model User { image String? // url maybe id or file storage service? cloudinary? bio String? //mark down - Part Part[] - Reaction PartReaction[] + Project Project[] + Reaction ProjectReaction[] Comment Comment[] SubjectAccessRequest SubjectAccessRequest[] } -model Part { +enum CadPackage { + openscad + cadquery +} + +model Project { id String @id @default(uuid()) - title String + title String @db.VarChar(25) description String? // markdown string code String? mainImage String? // link to cloudinary @@ -50,23 +54,24 @@ model Part { user User @relation(fields: [userId], references: [id]) userId String deleted Boolean @default(false) + cadPackage CadPackage @default(openscad) Comment Comment[] - Reaction PartReaction[] + Reaction ProjectReaction[] @@unique([title, userId]) } -model PartReaction { +model ProjectReaction { id String @id @default(uuid()) emote String // an emoji user User @relation(fields: [userId], references: [id]) userId String - part Part @relation(fields: [partId], references: [id]) - partId String + project Project @relation(fields: [projectId], references: [id]) + projectId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - @@unique([emote, userId, partId]) + @@unique([emote, userId, projectId]) } model Comment { @@ -74,8 +79,8 @@ model Comment { text String // the comment, should I allow mark down? user User @relation(fields: [userId], references: [id]) userId String - part Part @relation(fields: [partId], references: [id]) - partId String + project Project @relation(fields: [projectId], references: [id]) + projectId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -91,3 +96,10 @@ model SubjectAccessRequest { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model RW_DataMigration { + version String @id + name String + startedAt DateTime + finishedAt DateTime +} diff --git a/app/api/db/seed.js b/app/api/db/seed.js index 9382735..7b26e70 100644 --- a/app/api/db/seed.js +++ b/app/api/db/seed.js @@ -50,9 +50,9 @@ async function main() { }) } - const parts = [ + const projects = [ { - title: 'demo-part1', + title: 'demo-project1', description: '# can be markdown', mainImage: 'CadHub/kjdlgjnu0xmwksia7xox', user: { @@ -62,7 +62,7 @@ async function main() { }, }, { - title: 'demo-part2', + title: 'demo-project2', description: '## [hey](www.google.com)', user: { connect: { @@ -72,39 +72,43 @@ async function main() { }, ] - existing = await db.part.findMany({where: { title: parts[0].title}}) + existing = await db.project.findMany({where: { title: projects[0].title}}) if(!existing.length) { - await db.part.create({ - data: parts[0], + await db.project.create({ + data: projects[0], }) } - existing = await db.part.findMany({where: { title: parts[1].title}}) + existing = await db.project.findMany({where: { title: projects[1].title}}) if(!existing.length) { - await db.part.create({ - data: parts[1], + await db.project.create({ + data: projects[1], }) } - const aPart = await db.part.findUnique({where: { + const aProject = await db.project.findUnique({where: { title_userId: { - title: parts[0].title, + title: projects[0].title, userId: users[0].id, } }}) await db.comment.create({ data: { - text: "nice part, I like it", - user: {connect: { id: users[0].id}}, - part: {connect: { id: aPart.id}}, + text: "nice project, I like it", + userId: users[0].id, + projectId: aProject.id, + // user: {connect: { id: users[0].id}}, + // project: {connect: { id: aProject.id}}, } }) - await db.partReaction.create({ + await db.projectReaction.create({ data: { emote: "❤️", - user: {connect: { id: users[0].id}}, - part: {connect: { id: aPart.id}}, + userId: users[0].id, + projectId: aProject.id, + // user: {connect: { id: users[0].id}}, + // project: {connect: { id: aProject.id}}, } }) diff --git a/app/api/package.json b/app/api/package.json index 83c4250..b56a4c1 100644 --- a/app/api/package.json +++ b/app/api/package.json @@ -6,6 +6,7 @@ "@redwoodjs/api": "^0.34.1", "@sentry/node": "^6.5.1", "cloudinary": "^1.23.0", + "human-id": "^2.0.1", "nodemailer": "^6.6.2" }, "devDependencies": { diff --git a/app/api/src/graphql/ProjectReactions.sdl.js b/app/api/src/graphql/ProjectReactions.sdl.js new file mode 100644 index 0000000..8b34626 --- /dev/null +++ b/app/api/src/graphql/ProjectReactions.sdl.js @@ -0,0 +1,39 @@ +export const schema = gql` + type ProjectReaction { + id: String! + emote: String! + user: User! + userId: String! + project: Project! + projectId: String! + createdAt: DateTime! + updatedAt: DateTime! + } + + type Query { + projectReactions: [ProjectReaction!]! + projectReaction(id: String!): ProjectReaction + projectReactionsByProjectId(projectId: String!): [ProjectReaction!]! + } + + input ToggleProjectReactionInput { + emote: String! + userId: String! + projectId: String! + } + + input UpdateProjectReactionInput { + emote: String + userId: String + projectId: String + } + + type Mutation { + toggleProjectReaction(input: ToggleProjectReactionInput!): ProjectReaction! + updateProjectReaction( + id: String! + input: UpdateProjectReactionInput! + ): ProjectReaction! + deleteProjectReaction(id: String!): ProjectReaction! + } +` diff --git a/app/api/src/graphql/comments.sdl.js b/app/api/src/graphql/comments.sdl.js index b36296b..fd7dc1c 100644 --- a/app/api/src/graphql/comments.sdl.js +++ b/app/api/src/graphql/comments.sdl.js @@ -4,8 +4,8 @@ export const schema = gql` text: String! user: User! userId: String! - part: Part! - partId: String! + project: Project! + projectId: String! createdAt: DateTime! updatedAt: DateTime! } @@ -18,13 +18,13 @@ export const schema = gql` input CreateCommentInput { text: String! userId: String! - partId: String! + projectId: String! } input UpdateCommentInput { text: String userId: String - partId: String + projectId: String } type Mutation { diff --git a/app/api/src/graphql/partReactions.sdl.js b/app/api/src/graphql/partReactions.sdl.js deleted file mode 100644 index b27334e..0000000 --- a/app/api/src/graphql/partReactions.sdl.js +++ /dev/null @@ -1,39 +0,0 @@ -export const schema = gql` - type PartReaction { - id: String! - emote: String! - user: User! - userId: String! - part: Part! - partId: String! - createdAt: DateTime! - updatedAt: DateTime! - } - - type Query { - partReactions: [PartReaction!]! - partReaction(id: String!): PartReaction - partReactionsByPartId(partId: String!): [PartReaction!]! - } - - input TogglePartReactionInput { - emote: String! - userId: String! - partId: String! - } - - input UpdatePartReactionInput { - emote: String - userId: String - partId: String - } - - type Mutation { - togglePartReaction(input: TogglePartReactionInput!): PartReaction! - updatePartReaction( - id: String! - input: UpdatePartReactionInput! - ): PartReaction! - deletePartReaction(id: String!): PartReaction! - } -` diff --git a/app/api/src/graphql/parts.sdl.js b/app/api/src/graphql/parts.sdl.js deleted file mode 100644 index ec9c3c3..0000000 --- a/app/api/src/graphql/parts.sdl.js +++ /dev/null @@ -1,45 +0,0 @@ -export const schema = gql` - type Part { - id: String! - title: String! - description: String - code: String - mainImage: String - createdAt: DateTime! - updatedAt: DateTime! - deleted: Boolean! - user: User! - userId: String! - Comment: [Comment]! - Reaction(userId: String): [PartReaction]! - } - - type Query { - parts(userName: String): [Part!]! - part(id: String!): Part - partByUserAndTitle(userName: String!, partTitle: String!): Part - } - - input CreatePartInput { - title: String! - description: String - code: String - mainImage: String - userId: String! - } - - input UpdatePartInput { - title: String - description: String - code: String - mainImage: String - userId: String - } - - type Mutation { - createPart(input: CreatePartInput!): Part! - forkPart(input: CreatePartInput!): Part! - updatePart(id: String!, input: UpdatePartInput!): Part! - deletePart(id: String!): Part! - } -` diff --git a/app/api/src/graphql/projects.sdl.ts b/app/api/src/graphql/projects.sdl.ts new file mode 100644 index 0000000..6ec8af2 --- /dev/null +++ b/app/api/src/graphql/projects.sdl.ts @@ -0,0 +1,52 @@ +export const schema = gql` + type Project { + id: String! + title: String! + description: String + code: String + mainImage: String + createdAt: DateTime! + updatedAt: DateTime! + user: User! + userId: String! + deleted: Boolean! + cadPackage: CadPackage! + Comment: [Comment]! + Reaction(userId: String): [ProjectReaction]! + } + + enum CadPackage { + openscad + cadquery + } + + type Query { + projects(userName: String): [Project!]! + project(id: String!): Project + projectByUserAndTitle(userName: String!, projectTitle: String!): Project + } + + input CreateProjectInput { + title: String + description: String + code: String + mainImage: String + userId: String! + cadPackage: CadPackage! + } + + input UpdateProjectInput { + title: String + description: String + code: String + mainImage: String + userId: String + } + + type Mutation { + createProject(input: CreateProjectInput!): Project! + forkProject(input: CreateProjectInput!): Project! + updateProject(id: String!, input: UpdateProjectInput!): Project! + deleteProject(id: String!): Project! + } +` diff --git a/app/api/src/graphql/users.sdl.js b/app/api/src/graphql/users.sdl.js index 5365ba2..d9b6f0b 100644 --- a/app/api/src/graphql/users.sdl.js +++ b/app/api/src/graphql/users.sdl.js @@ -8,9 +8,9 @@ export const schema = gql` updatedAt: DateTime! image: String bio: String - Parts: [Part]! - Part(partTitle: String): Part - Reaction: [PartReaction]! + Projects: [Project]! + Project(projectTitle: String): Project + Reaction: [ProjectReaction]! Comment: [Comment]! SubjectAccessRequest: [SubjectAccessRequest]! } diff --git a/app/api/src/lib/owner.js b/app/api/src/lib/owner.ts similarity index 80% rename from app/api/src/lib/owner.js rename to app/api/src/lib/owner.ts index 7a03bbc..3210355 100644 --- a/app/api/src/lib/owner.js +++ b/app/api/src/lib/owner.ts @@ -1,13 +1,17 @@ import { AuthenticationError, ForbiddenError } from '@redwoodjs/api' import { db } from 'src/lib/db' -export const requireOwnership = async ({ userId, userName, partId } = {}) => { +export const requireOwnership = async ({ + userId, + userName, + projectId, +}: { userId?: string; userName?: string; projectId?: string } = {}) => { // IMPORTANT, don't forget to await this function, as it will only block // unwanted db actions if it has time to look up resources in the db. if (!context.currentUser) { throw new AuthenticationError("You don't have permission to do that.") } - if (!userId && !userName && !partId) { + if (!userId && !userName && !projectId) { throw new ForbiddenError("You don't have access to do that.") } @@ -33,10 +37,10 @@ export const requireOwnership = async ({ userId, userName, partId } = {}) => { } } - if (partId) { - const user = await db.part + if (projectId) { + const user = await db.project .findUnique({ - where: { id: partId }, + where: { id: projectId }, }) .user() diff --git a/app/api/src/services/comments/comments.test.js b/app/api/src/services/comments/comments.test.js deleted file mode 100644 index 108fd0f..0000000 --- a/app/api/src/services/comments/comments.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { comments } from './comments' -*/ - -describe('comments', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/app/api/src/services/comments/comments.js b/app/api/src/services/comments/comments.ts similarity index 88% rename from app/api/src/services/comments/comments.js rename to app/api/src/services/comments/comments.ts index 1b4269f..aea1d9d 100644 --- a/app/api/src/services/comments/comments.js +++ b/app/api/src/services/comments/comments.ts @@ -33,6 +33,6 @@ export const deleteComment = ({ id }) => { export const Comment = { user: (_obj, { root }) => db.comment.findUnique({ where: { id: root.id } }).user(), - part: (_obj, { root }) => - db.comment.findUnique({ where: { id: root.id } }).part(), + project: (_obj, { root }) => + db.comment.findUnique({ where: { id: root.id } }).project(), } diff --git a/app/api/src/services/helpers.js b/app/api/src/services/helpers.ts similarity index 70% rename from app/api/src/services/helpers.js rename to app/api/src/services/helpers.ts index f0cc19c..d4e919b 100644 --- a/app/api/src/services/helpers.js +++ b/app/api/src/services/helpers.ts @@ -1,4 +1,6 @@ import { v2 as cloudinary } from 'cloudinary' +import humanId from 'human-id' + cloudinary.config({ cloud_name: 'irevdev', api_key: process.env.CLOUDINARY_API_KEY, @@ -36,6 +38,26 @@ export const generateUniqueString = async ( return generateUniqueString(newSeed, isUniqueCallback, count) } +export const generateUniqueStringWithoutSeed = async ( + isUniqueCallback: (seed: string) => Promise, + count = 0 +) => { + const seed = humanId({ + separator: '-', + capitalize: false, + }) + const isUnique = !(await isUniqueCallback(seed)) + if (isUnique) { + return seed + } + count += 1 + if (count > 100) { + console.log('trouble finding unique') + return `very-unique-${seed}`.slice(0, 10) + } + return generateUniqueStringWithoutSeed(isUniqueCallback, count) +} + export const destroyImage = ({ publicId }) => new Promise((resolve, reject) => { cloudinary.uploader.destroy(publicId, (error, result) => { diff --git a/app/api/src/services/partReactions/partReactions.test.js b/app/api/src/services/partReactions/partReactions.test.js deleted file mode 100644 index 47d4f20..0000000 --- a/app/api/src/services/partReactions/partReactions.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { partReactions } from './partReactions' -*/ - -describe('partReactions', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/app/api/src/services/parts/parts.js b/app/api/src/services/parts/parts.js deleted file mode 100644 index e636055..0000000 --- a/app/api/src/services/parts/parts.js +++ /dev/null @@ -1,117 +0,0 @@ -import { db } from 'src/lib/db' -import { - foreignKeyReplacement, - enforceAlphaNumeric, - generateUniqueString, - destroyImage, -} from 'src/services/helpers' -import { requireAuth } from 'src/lib/auth' -import { requireOwnership } from 'src/lib/owner' - -export const parts = ({ userName }) => { - if (!userName) { - return db.part.findMany({ where: { deleted: false } }) - } - return db.part.findMany({ - where: { - deleted: false, - user: { - userName, - }, - }, - }) -} - -export const part = ({ id }) => { - return db.part.findUnique({ - where: { id }, - }) -} -export const partByUserAndTitle = async ({ userName, partTitle }) => { - const user = await db.user.findUnique({ - where: { - userName, - }, - }) - return db.part.findUnique({ - where: { - title_userId: { - title: partTitle, - userId: user.id, - }, - }, - }) -} - -export const createPart = async ({ input }) => { - requireAuth() - return db.part.create({ - data: foreignKeyReplacement(input), - }) -} - -export const forkPart = async ({ input }) => { - // Only difference between create and fork part is that fork part will generate a unique title - // (for the user) if there is a conflict - const isUniqueCallback = async (seed) => - db.part.findUnique({ - where: { - title_userId: { - title: seed, - userId: input.userId, - }, - }, - }) - const title = await generateUniqueString(input.title, isUniqueCallback) - // TODO change the description to `forked from userName/partName ${rest of description}` - return db.part.create({ - data: foreignKeyReplacement({ ...input, title }), - }) -} - -export const updatePart = async ({ id, input }) => { - requireAuth() - await requireOwnership({ partId: id }) - if (input.title) { - input.title = enforceAlphaNumeric(input.title) - } - const originalPart = await db.part.findUnique({ where: { id } }) - const imageToDestroy = - originalPart.mainImage !== input.mainImage && - input.mainImage && - originalPart.mainImage - const update = await db.part.update({ - data: foreignKeyReplacement(input), - where: { id }, - }) - if (imageToDestroy) { - console.log( - `image destroyed, publicId: ${imageToDestroy}, partId: ${id}, replacing image is ${input.mainImage}` - ) - // destroy after the db has been updated - destroyImage({ publicId: imageToDestroy }) - } - return update -} - -export const deletePart = async ({ id }) => { - requireAuth() - await requireOwnership({ partId: id }) - return db.part.update({ - data: { - deleted: true, - }, - where: { id }, - }) -} - -export const Part = { - user: (_obj, { root }) => - db.part.findUnique({ where: { id: root.id } }).user(), - Comment: (_obj, { root }) => - db.part.findUnique({ where: { id: root.id } }).Comment(), - Reaction: (_obj, { root }) => - db.part - .findUnique({ where: { id: root.id } }) - .Reaction({ where: { userId: _obj.userId } }), -} diff --git a/app/api/src/services/parts/parts.test.js b/app/api/src/services/parts/parts.test.js deleted file mode 100644 index e8b80ce..0000000 --- a/app/api/src/services/parts/parts.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { parts } from './parts' -*/ - -describe('parts', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/app/api/src/services/partReactions/partReactions.js b/app/api/src/services/projectReactions/projectReactions.js similarity index 53% rename from app/api/src/services/partReactions/partReactions.js rename to app/api/src/services/projectReactions/projectReactions.js index a7388fe..e9abdaf 100644 --- a/app/api/src/services/partReactions/partReactions.js +++ b/app/api/src/services/projectReactions/projectReactions.js @@ -5,24 +5,24 @@ import { requireOwnership } from 'src/lib/owner' import { db } from 'src/lib/db' import { foreignKeyReplacement } from 'src/services/helpers' -export const partReactions = () => { - return db.partReaction.findMany() +export const projectReactions = () => { + return db.projectReaction.findMany() } -export const partReaction = ({ id }) => { - return db.partReaction.findUnique({ +export const projectReaction = ({ id }) => { + return db.projectReaction.findUnique({ where: { id }, }) } -export const partReactionsByPartId = ({ partId }) => { - return db.partReaction.findMany({ - where: { partId: partId }, +export const projectReactionsByProjectId = ({ projectId }) => { + return db.projectReaction.findMany({ + where: { projectId }, }) } -export const togglePartReaction = async ({ input }) => { - // if write fails emote_userId_partId @@unique constraint, then delete it instead +export const toggleProjectReaction = async ({ input }) => { + // if write fails emote_userId_projectId @@unique constraint, then delete it instead requireAuth() await requireOwnership({ userId: input?.userId }) const legalReactions = ['❤️', '👍', '😄', '🙌'] // TODO figure out a way of sharing code between FE and BE, so this is consistent with web/src/components/EmojiReaction/EmojiReaction.js @@ -36,33 +36,33 @@ export const togglePartReaction = async ({ input }) => { let dbPromise const inputClone = { ...input } // TODO foreignKeyReplacement mutates input, which I should fix but am lazy right now try { - dbPromise = await db.partReaction.create({ + dbPromise = await db.projectReaction.create({ data: foreignKeyReplacement(input), }) } catch (e) { - dbPromise = db.partReaction.delete({ - where: { emote_userId_partId: inputClone }, + dbPromise = db.projectReaction.delete({ + where: { emote_userId_projectId: inputClone }, }) } return dbPromise } -export const updatePartReaction = ({ id, input }) => { - return db.partReaction.update({ +export const updateProjectReaction = ({ id, input }) => { + return db.projectReaction.update({ data: foreignKeyReplacement(input), where: { id }, }) } -export const deletePartReaction = ({ id }) => { - return db.partReaction.delete({ +export const deleteProjectReaction = ({ id }) => { + return db.projectReaction.delete({ where: { id }, }) } -export const PartReaction = { +export const ProjectReaction = { user: (_obj, { root }) => - db.partReaction.findUnique({ where: { id: root.id } }).user(), - part: (_obj, { root }) => - db.partReaction.findUnique({ where: { id: root.id } }).part(), + db.projectReaction.findUnique({ where: { id: root.id } }).user(), + project: (_obj, { root }) => + db.projectReaction.findUnique({ where: { id: root.id } }).project(), } diff --git a/app/api/src/services/projects/projects.ts b/app/api/src/services/projects/projects.ts new file mode 100644 index 0000000..ba6c5d8 --- /dev/null +++ b/app/api/src/services/projects/projects.ts @@ -0,0 +1,141 @@ +import type { Prisma } from '@prisma/client' +import type { ResolverArgs } from '@redwoodjs/api' + +import { db } from 'src/lib/db' +import { + foreignKeyReplacement, + enforceAlphaNumeric, + generateUniqueString, + generateUniqueStringWithoutSeed, + destroyImage, +} from 'src/services/helpers' +import { requireAuth } from 'src/lib/auth' +import { requireOwnership } from 'src/lib/owner' + +export const projects = ({ userName }) => { + if (!userName) { + return db.project.findMany({ where: { deleted: false } }) + } + return db.project.findMany({ + where: { + deleted: false, + user: { + userName, + }, + }, + }) +} + +export const project = ({ id }: Prisma.ProjectWhereUniqueInput) => { + return db.project.findUnique({ + where: { id }, + }) +} +export const projectByUserAndTitle = async ({ userName, projectTitle }) => { + const user = await db.user.findUnique({ + where: { + userName, + }, + }) + return db.project.findUnique({ + where: { + title_userId: { + title: projectTitle, + userId: user.id, + }, + }, + }) +} +const isUniqueProjectTitle = (userId: string) => async (seed: string) => + db.project.findUnique({ + where: { + title_userId: { + title: seed, + userId, + }, + }, + }) + +interface CreateProjectArgs { + input: Prisma.ProjectCreateArgs['data'] +} + +export const createProject = async ({ input }: CreateProjectArgs) => { + requireAuth() + console.log(input.userId) + const isUniqueCallback = isUniqueProjectTitle(input.userId) + let title = input.title + if (!title) { + title = await generateUniqueStringWithoutSeed(isUniqueCallback) + } + return db.project.create({ + data: foreignKeyReplacement({ + ...input, + title, + }), + }) +} + +export const forkProject = async ({ input }) => { + // Only difference between create and fork project is that fork project will generate a unique title + // (for the user) if there is a conflict + const isUniqueCallback = isUniqueProjectTitle(input.userId) + const title = await generateUniqueString(input.title, isUniqueCallback) + // TODO change the description to `forked from userName/projectName ${rest of description}` + return db.project.create({ + data: foreignKeyReplacement({ ...input, title }), + }) +} + +interface UpdateProjectArgs extends Prisma.ProjectWhereUniqueInput { + input: Prisma.ProjectUpdateInput +} + +export const updateProject = async ({ id, input }: UpdateProjectArgs) => { + requireAuth() + await requireOwnership({ projectId: id }) + if (input.title) { + input.title = enforceAlphaNumeric(input.title) + } + const originalProject = await db.project.findUnique({ where: { id } }) + const imageToDestroy = + originalProject.mainImage !== input.mainImage && + input.mainImage && + originalProject.mainImage + const update = await db.project.update({ + data: foreignKeyReplacement(input), + where: { id }, + }) + if (imageToDestroy) { + console.log( + `image destroyed, publicId: ${imageToDestroy}, projectId: ${id}, replacing image is ${input.mainImage}` + ) + // destroy after the db has been updated + destroyImage({ publicId: imageToDestroy }) + } + return update +} + +export const deleteProject = async ({ id }: Prisma.ProjectWhereUniqueInput) => { + requireAuth() + await requireOwnership({ projectId: id }) + return db.project.update({ + data: { + deleted: true, + }, + where: { id }, + }) +} + +export const Project = { + user: (_obj, { root }: ResolverArgs>) => + db.project.findUnique({ where: { id: root.id } }).user(), + Comment: (_obj, { root }: ResolverArgs>) => + db.project + .findUnique({ where: { id: root.id } }) + .Comment({ orderBy: { createdAt: 'desc' } }), + Reaction: (_obj, { root }: ResolverArgs>) => + db.project + .findUnique({ where: { id: root.id } }) + .Reaction({ where: { userId: _obj.userId } }), +} diff --git a/app/api/src/services/subjectAccessRequests/subjectAccessRequests.test.js b/app/api/src/services/subjectAccessRequests/subjectAccessRequests.test.js deleted file mode 100644 index 88bcd4c..0000000 --- a/app/api/src/services/subjectAccessRequests/subjectAccessRequests.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { subjectAccessRequests } from './subjectAccessRequests' -*/ - -describe('subjectAccessRequests', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/app/api/src/services/users/users.test.js b/app/api/src/services/users/users.test.js deleted file mode 100644 index bac7cb4..0000000 --- a/app/api/src/services/users/users.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { users } from './users' -*/ - -describe('users', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/app/api/src/services/users/users.ts b/app/api/src/services/users/users.ts index 2e2e874..235a00e 100644 --- a/app/api/src/services/users/users.ts +++ b/app/api/src/services/users/users.ts @@ -51,9 +51,9 @@ export const updateUserByUserName = async ({ userName, input }) => { `You've tried to used a protected word as you userName, try something other than ` ) } - const originalPart = await db.user.findUnique({ where: { userName } }) + const originalProject = await db.user.findUnique({ where: { userName } }) const imageToDestroy = - originalPart.image !== input.image && originalPart.image + originalProject.image !== input.image && originalProject.image const update = await db.user.update({ data: input, where: { userName }, @@ -73,14 +73,14 @@ export const deleteUser = ({ id }) => { } export const User = { - Parts: (_obj, { root }) => - db.user.findUnique({ where: { id: root.id } }).Part(), - Part: (_obj, { root }) => - _obj.partTitle && - db.part.findUnique({ + Projects: (_obj, { root }) => + db.user.findUnique({ where: { id: root.id } }).Project(), + Project: (_obj, { root }) => + _obj.projectTitle && + db.project.findUnique({ where: { title_userId: { - title: _obj.partTitle, + title: _obj.projectTitle, userId: root.id, }, }, diff --git a/app/redwood.toml b/app/redwood.toml index 762f83b..2f16ebf 100644 --- a/app/redwood.toml +++ b/app/redwood.toml @@ -27,5 +27,5 @@ open = true [experimental] - esbuild = false + esbuild = true diff --git a/app/web/config/webpack.config.js b/app/web/config/webpack.config.js index 543bc02..35685cc 100644 --- a/app/web/config/webpack.config.js +++ b/app/web/config/webpack.config.js @@ -1,92 +1,9 @@ -const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') module.exports = (config, { env }) => { config.plugins.forEach((plugin) => { if (plugin.constructor.name === 'HtmlWebpackPlugin') { plugin.options.favicon = './src/favicon.svg' - } else if (plugin.constructor.name === 'CopyPlugin') { - plugin.patterns.push({ - from: './src/cascade/js/StandardLibraryIntellisense.ts', - to: 'js/StandardLibraryIntellisense.ts', - }) - plugin.patterns.push({ - from: './src/cascade/static_node_modules/opencascade.js/dist/oc.d.ts', - to: 'opencascade.d.ts', - }) - plugin.patterns.push({ - from: '../node_modules/three/src/Three.d.ts', - to: 'Three.d.ts', - }) - plugin.patterns.push({ - from: './src/cascade/fonts', - to: 'fonts', - }) - plugin.patterns.push({ - from: './src/cascade/textures', - to: 'textures', - }) } }) - config.plugins.push( - new MonacoWebpackPlugin({ - languages: ['typescript'], - features: [ - 'accessibilityHelp', - 'anchorSelect', - 'bracketMatching', - 'caretOperations', - 'clipboard', - 'codeAction', - 'codelens', - 'comment', - 'contextmenu', - 'coreCommands', - 'cursorUndo', - 'documentSymbols', - 'find', - 'folding', - 'fontZoom', - 'format', - 'gotoError', - 'gotoLine', - 'gotoSymbol', - 'hover', - 'inPlaceReplace', - 'indentation', - 'inlineHints', - 'inspectTokens', - 'linesOperations', - 'linkedEditing', - 'links', - 'multicursor', - 'parameterHints', - 'quickCommand', - 'quickHelp', - 'quickOutline', - 'referenceSearch', - 'rename', - 'smartSelect', - 'snippets', - 'suggest', - 'toggleHighContrast', - 'toggleTabFocusMode', - 'transpose', - 'unusualLineTerminators', - 'viewportSemanticTokens', - 'wordHighlighter', - 'wordOperations', - 'wordPartOperations', - ], - }) - ) - config.module.rules[0].oneOf.push({ - test: /opencascade\.wasm\.wasm$/, - type: 'javascript/auto', - loader: 'file-loader', - }) - config.node = { - fs: 'empty', - } - return config } diff --git a/app/web/package.json b/app/web/package.json index a805434..7e2c2a5 100644 --- a/app/web/package.json +++ b/app/web/package.json @@ -16,23 +16,19 @@ "@headlessui/react": "^1.0.0", "@material-ui/core": "^4.11.0", "@monaco-editor/react": "^4.0.11", + "@react-three/fiber": "^7.0.5", "@redwoodjs/auth": "^0.34.1", "@redwoodjs/forms": "^0.34.1", "@redwoodjs/router": "^0.34.1", "@redwoodjs/web": "^0.34.1", "@sentry/browser": "^6.5.1", + "@tailwindcss/aspect-ratio": "^0.2.1", "browser-fs-access": "^0.17.2", "cloudinary-react": "^1.6.7", - "controlkit": "^0.1.9", "get-active-classes": "^0.0.11", - "golden-layout": "^1.5.9", "gotrue-js": "^0.9.27", - "jquery": "^3.5.1", "lodash": "^4.17.21", - "monaco-editor": "^0.20.0", - "monaco-editor-webpack-plugin": "^1.9.1", "netlify-identity-widget": "^1.9.1", - "opencascade.js": "^0.1.15", "pako": "^2.0.3", "prop-types": "^15.7.2", "react": "^17.0.2", @@ -43,19 +39,16 @@ "react-image-crop": "^8.6.6", "react-mosaic-component": "^4.1.1", "react-tabs": "^3.2.2", - "react-three-fiber": "^5.3.19", "rich-markdown-editor": "^11.0.2", "styled-components": "^5.2.0", - "three": "^0.118.3" + "three": "^0.130.1" }, "devDependencies": { "@types/lodash": "^4.14.170", "autoprefixer": "^10.2.5", "html-webpack-plugin": "^4.5.0", - "opentype.js": "^1.3.3", "postcss": "^8.2.13", "postcss-loader": "4.0.2", - "tailwindcss": "^2.1.2", - "worker-loader": "^3.0.7" + "tailwindcss": "^2.1.2" } -} \ No newline at end of file +} diff --git a/app/web/src/App.js b/app/web/src/App.tsx similarity index 100% rename from app/web/src/App.js rename to app/web/src/App.tsx diff --git a/app/web/src/Routes.js b/app/web/src/Routes.js index 634a328..665ad9d 100644 --- a/app/web/src/Routes.js +++ b/app/web/src/Routes.js @@ -44,26 +44,26 @@ const Routes = () => { {/* Ownership enforced routes */} - + - + {/* End ownership enforced routes */} - + - - + + - + - {/* Retired for now but might want to bring it back, delete if older that I danno late 2021 */} + {/* Retired for now but might want to bring it back, delete if older that I dunno late 2021 */} {/* */} diff --git a/app/web/src/cascade b/app/web/src/cascade deleted file mode 160000 index cd23a8e..0000000 --- a/app/web/src/cascade +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd23a8e67358278e96ec37505cf0fcc616e93225 diff --git a/app/web/src/components/AdminParts/AdminParts.js b/app/web/src/components/AdminProjects/AdminProjects.tsx similarity index 58% rename from app/web/src/components/AdminParts/AdminParts.js rename to app/web/src/components/AdminProjects/AdminProjects.tsx index 89a281f..e8a9ab8 100644 --- a/app/web/src/components/AdminParts/AdminParts.js +++ b/app/web/src/components/AdminProjects/AdminProjects.tsx @@ -2,11 +2,11 @@ import { useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' import { Link, routes } from '@redwoodjs/router' -import { QUERY } from 'src/components/AdminPartsCell' +import { QUERY } from 'src/components/AdminProjectsCell/AdminProjectsCell' -const DELETE_PART_MUTATION = gql` - mutation DeletePartMutationAdmin($id: String!) { - deletePart(id: $id) { +const DELETE_PROJECT_MUTATION_ADMIN = gql` + mutation DeleteProjectMutationAdmin($id: String!) { + deleteProject(id: $id) { id } } @@ -34,10 +34,10 @@ const checkboxInputTag = (checked) => { return } -const AdminParts = ({ parts }) => { - const [deletePart] = useMutation(DELETE_PART_MUTATION, { +const AdminProjects = ({ projects }) => { + const [deleteProject] = useMutation(DELETE_PROJECT_MUTATION_ADMIN, { onCompleted: () => { - toast.success('Part deleted.') + toast.success('Project deleted.') }, // This refetches the query on the list page. Read more about other ways to // update the cache over here: @@ -47,8 +47,8 @@ const AdminParts = ({ parts }) => { }) const onDeleteClick = (id) => { - if (confirm('Are you sure you want to delete part ' + id + '?')) { - deletePart({ variables: { id } }) + if (confirm('Are you sure you want to delete project ' + id + '?')) { + deleteProject({ variables: { id } }) } } @@ -70,44 +70,44 @@ const AdminParts = ({ parts }) => { - {parts.map((part) => ( - - {truncate(part.id)} - {truncate(part.title)} - {truncate(part.description)} - {truncate(part.code)} - {truncate(part.mainImage)} - {timeTag(part.createdAt)} - {timeTag(part.updatedAt)} - {truncate(part.userId)} - {checkboxInputTag(part.deleted)} + {projects.map((project) => ( + + {truncate(project.id)} + {truncate(project.title)} + {truncate(project.description)} + {truncate(project.code)} + {truncate(project.mainImage)} + {timeTag(project.createdAt)} + {timeTag(project.updatedAt)} + {truncate(project.userId)} + {checkboxInputTag(project.deleted)}