diff --git a/.vscode/settings.json b/.vscode/settings.json index a95220c..76d6767 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,9 @@ "./web/src/pages", "./web/src/index.js", "./web/src/Routes.js", + ], + "cSpell.words": [ + "Uploader", + "redwoodjs" ] } diff --git a/api/prisma/migrations/20201009213512-create-posts/README.md b/api/prisma/migrations/20201009213512-create-posts/README.md deleted file mode 100644 index f52912a..0000000 --- a/api/prisma/migrations/20201009213512-create-posts/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Migration `20201009213512-create-posts` - -This migration has been generated by Kurt Hutten at 10/10/2020, 8:35:12 AM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -CREATE TABLE "Post" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "body" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -) -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration ..20201009213512-create-posts ---- datamodel.dml -+++ datamodel.dml -@@ -1,0 +1,18 @@ -+datasource DS { -+ // optionally set multiple providers -+ // example: provider = ["sqlite", "postgresql"] -+ provider = "sqlite" -+ url = "***" -+} -+ -+generator client { -+ provider = "prisma-client-js" -+ binaryTargets = "native" -+} -+ -+model Post { -+ id Int @id @default(autoincrement()) -+ title String -+ body String -+ createdAt DateTime @default(now()) -+} -``` - - diff --git a/api/prisma/migrations/20201009213512-create-posts/schema.prisma b/api/prisma/migrations/20201009213512-create-posts/schema.prisma deleted file mode 100644 index 624cbdb..0000000 --- a/api/prisma/migrations/20201009213512-create-posts/schema.prisma +++ /dev/null @@ -1,18 +0,0 @@ -datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} diff --git a/api/prisma/migrations/20201009213512-create-posts/steps.json b/api/prisma/migrations/20201009213512-create-posts/steps.json deleted file mode 100644 index f2edc88..0000000 --- a/api/prisma/migrations/20201009213512-create-posts/steps.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "CreateSource", - "source": "DS" - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Source", - "source": "DS" - }, - "argument": "provider", - "value": "\"sqlite\"" - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Source", - "source": "DS" - }, - "argument": "url", - "value": "\"***\"" - }, - { - "tag": "CreateModel", - "model": "Post" - }, - { - "tag": "CreateField", - "model": "Post", - "field": "id", - "type": "Int", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Post", - "field": "id" - }, - "directive": "id" - } - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Post", - "field": "id" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Post", - "field": "id" - }, - "directive": "default" - }, - "argument": "", - "value": "autoincrement()" - }, - { - "tag": "CreateField", - "model": "Post", - "field": "title", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Post", - "field": "body", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Post", - "field": "createdAt", - "type": "DateTime", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Post", - "field": "createdAt" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Post", - "field": "createdAt" - }, - "directive": "default" - }, - "argument": "", - "value": "now()" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201011043647-create-parts/README.md b/api/prisma/migrations/20201011043647-create-parts/README.md deleted file mode 100644 index 1def1c5..0000000 --- a/api/prisma/migrations/20201011043647-create-parts/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Migration `20201011043647-create-parts` - -This migration has been generated by Kurt Hutten at 10/11/2020, 3:36:47 PM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -CREATE TABLE "Part" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "mainImage" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -) -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201009213512-create-posts..20201011043647-create-parts ---- datamodel.dml -+++ datamodel.dml -@@ -1,9 +1,9 @@ - datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" -- url = "***" -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -15,4 +15,14 @@ - title String - body String - createdAt DateTime @default(now()) - } -+ -+model Part { -+ id Int @id @default(autoincrement()) -+ title String -+ description String // markdown string -+ mainImage String // link to cloudinary -+ createdAt DateTime @default(now()) -+ // userId -+ //likes, comments, reactions -+} -``` - - diff --git a/api/prisma/migrations/20201011043647-create-parts/schema.prisma b/api/prisma/migrations/20201011043647-create-parts/schema.prisma deleted file mode 100644 index 6d2b834..0000000 --- a/api/prisma/migrations/20201011043647-create-parts/schema.prisma +++ /dev/null @@ -1,28 +0,0 @@ -datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} diff --git a/api/prisma/migrations/20201011043647-create-parts/steps.json b/api/prisma/migrations/20201011043647-create-parts/steps.json deleted file mode 100644 index 3328ce6..0000000 --- a/api/prisma/migrations/20201011043647-create-parts/steps.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "CreateModel", - "model": "Part" - }, - { - "tag": "CreateField", - "model": "Part", - "field": "id", - "type": "Int", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Part", - "field": "id" - }, - "directive": "id" - } - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Part", - "field": "id" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Part", - "field": "id" - }, - "directive": "default" - }, - "argument": "", - "value": "autoincrement()" - }, - { - "tag": "CreateField", - "model": "Part", - "field": "title", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Part", - "field": "description", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Part", - "field": "mainImage", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Part", - "field": "createdAt", - "type": "DateTime", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Part", - "field": "createdAt" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Part", - "field": "createdAt" - }, - "directive": "default" - }, - "argument": "", - "value": "now()" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201011052155-add-code-to-part/README.md b/api/prisma/migrations/20201011052155-add-code-to-part/README.md deleted file mode 100644 index ffbd7bc..0000000 --- a/api/prisma/migrations/20201011052155-add-code-to-part/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Migration `20201011052155-add-code-to-part` - -This migration has been generated by Kurt Hutten at 10/11/2020, 4:21:55 PM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Part" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "code" TEXT NOT NULL DEFAULT '// Welcome to Cascade Studio! Here are some useful functions: -// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection() -// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon() -// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(), -// FilletEdges(), ChamferEdges(), -// Slider(), Button(), Checkbox() -let holeRadius = Slider("Radius", 30 , 20 , 40); -let sphere = Sphere(50); -let cylinderZ = Cylinder(holeRadius, 200, true);/nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true)); -let cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));/nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ])); - -Translate([-25, 0, 40], Text3D("Hi!"));/n// Don''t forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!', - "mainImage" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); -INSERT INTO "new_Part" ("id", "title", "description", "mainImage", "createdAt") SELECT "id", "title", "description", "mainImage", "createdAt" FROM "Part"; -DROP TABLE "Part"; -ALTER TABLE "new_Part" RENAME TO "Part"; -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201011043647-create-parts..20201011052155-add-code-to-part ---- datamodel.dml -+++ datamodel.dml -@@ -1,9 +1,9 @@ - datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" -- url = "***" -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -20,8 +20,9 @@ - model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string -+ code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);/nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));/nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));/n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -``` - - diff --git a/api/prisma/migrations/20201011052155-add-code-to-part/schema.prisma b/api/prisma/migrations/20201011052155-add-code-to-part/schema.prisma deleted file mode 100644 index 41ca83b..0000000 --- a/api/prisma/migrations/20201011052155-add-code-to-part/schema.prisma +++ /dev/null @@ -1,29 +0,0 @@ -datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);/nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));/nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));/n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} diff --git a/api/prisma/migrations/20201011052155-add-code-to-part/steps.json b/api/prisma/migrations/20201011052155-add-code-to-part/steps.json deleted file mode 100644 index 0c8bd6f..0000000 --- a/api/prisma/migrations/20201011052155-add-code-to-part/steps.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "CreateField", - "model": "Part", - "field": "code", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Part", - "field": "code" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Part", - "field": "code" - }, - "directive": "default" - }, - "argument": "", - "value": "\"// Welcome to Cascade Studio! Here are some useful functions:\\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\\n// FilletEdges(), ChamferEdges(),\\n// Slider(), Button(), Checkbox()\\nlet holeRadius = Slider(\\\"Radius\\\", 30 , 20 , 40);\\nlet sphere = Sphere(50);\\nlet cylinderZ = Cylinder(holeRadius, 200, true);/nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));/nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\\n\\nTranslate([-25, 0, 40], Text3D(\\\"Hi!\\\"));/n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!\"" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/README.md b/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/README.md deleted file mode 100644 index 1e30c97..0000000 --- a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Migration `20201011082558-add-code-not-needed-upon-create` - -This migration has been generated by Kurt Hutten at 10/11/2020, 7:25:58 PM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Part" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "code" TEXT NOT NULL DEFAULT '// Welcome to Cascade Studio! Here are some useful functions: -// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection() -// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon() -// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(), -// FilletEdges(), ChamferEdges(), -// Slider(), Button(), Checkbox() -let holeRadius = Slider("Radius", 30 , 20 , 40); -let sphere = Sphere(50); -let cylinderZ = Cylinder(holeRadius, 200, true); -let cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true)); -let cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true)); -Translate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ])); - -Translate([-25, 0, 40], Text3D("Hi!")); -// Don''t forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!', - "mainImage" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); -INSERT INTO "new_Part" ("id", "title", "description", "mainImage", "createdAt", "code") SELECT "id", "title", "description", "mainImage", "createdAt", "code" FROM "Part"; -DROP TABLE "Part"; -ALTER TABLE "new_Part" RENAME TO "Part"; -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201011052155-add-code-to-part..20201011082558-add-code-not-needed-upon-create ---- datamodel.dml -+++ datamodel.dml -@@ -1,9 +1,9 @@ - datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" -- url = "***" -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -20,9 +20,9 @@ - model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string -- code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);/nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));/nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));/n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") -+ code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -``` - - diff --git a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/schema.prisma b/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/schema.prisma deleted file mode 100644 index f22b4b7..0000000 --- a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/schema.prisma +++ /dev/null @@ -1,29 +0,0 @@ -datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} diff --git a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/steps.json b/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/steps.json deleted file mode 100644 index 8c127a5..0000000 --- a/api/prisma/migrations/20201011082558-add-code-not-needed-upon-create/steps.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "UpdateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Part", - "field": "code" - }, - "directive": "default" - }, - "argument": "", - "newValue": "\"// Welcome to Cascade Studio! Here are some useful functions:\\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\\n// FilletEdges(), ChamferEdges(),\\n// Slider(), Button(), Checkbox()\\nlet holeRadius = Slider(\\\"Radius\\\", 30 , 20 , 40);\\nlet sphere = Sphere(50);\\nlet cylinderZ = Cylinder(holeRadius, 200, true);\\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\\n\\nTranslate([-25, 0, 40], Text3D(\\\"Hi!\\\"));\\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!\"" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201011095227-create-contact/README.md b/api/prisma/migrations/20201011095227-create-contact/README.md deleted file mode 100644 index 5748d5e..0000000 --- a/api/prisma/migrations/20201011095227-create-contact/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Migration `20201011095227-create-contact` - -This migration has been generated by Kurt Hutten at 10/11/2020, 8:52:27 PM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -CREATE TABLE "Contact" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "message" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -) -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201011082558-add-code-not-needed-upon-create..20201011095227-create-contact ---- datamodel.dml -+++ datamodel.dml -@@ -1,9 +1,9 @@ - datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" -- url = "***" -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -26,4 +26,12 @@ - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions - } -+ -+model Contact { -+ id Int @id @default(autoincrement()) -+ name String -+ email String -+ message String -+ createdAt DateTime @default(now()) -+} -``` - - diff --git a/api/prisma/migrations/20201011095227-create-contact/schema.prisma b/api/prisma/migrations/20201011095227-create-contact/schema.prisma deleted file mode 100644 index 03189f2..0000000 --- a/api/prisma/migrations/20201011095227-create-contact/schema.prisma +++ /dev/null @@ -1,37 +0,0 @@ -datasource DS { - // optionally set multiple providers - // example: provider = ["sqlite", "postgresql"] - provider = "sqlite" - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} - -model Contact { - id Int @id @default(autoincrement()) - name String - email String - message String - createdAt DateTime @default(now()) -} diff --git a/api/prisma/migrations/20201011095227-create-contact/steps.json b/api/prisma/migrations/20201011095227-create-contact/steps.json deleted file mode 100644 index 0ca4b72..0000000 --- a/api/prisma/migrations/20201011095227-create-contact/steps.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "CreateModel", - "model": "Contact" - }, - { - "tag": "CreateField", - "model": "Contact", - "field": "id", - "type": "Int", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Contact", - "field": "id" - }, - "directive": "id" - } - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Contact", - "field": "id" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Contact", - "field": "id" - }, - "directive": "default" - }, - "argument": "", - "value": "autoincrement()" - }, - { - "tag": "CreateField", - "model": "Contact", - "field": "name", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Contact", - "field": "email", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Contact", - "field": "message", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateField", - "model": "Contact", - "field": "createdAt", - "type": "DateTime", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "Contact", - "field": "createdAt" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "Contact", - "field": "createdAt" - }, - "directive": "default" - }, - "argument": "", - "value": "now()" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201018233330-add-simple-user-model/README.md b/api/prisma/migrations/20201018233330-add-simple-user-model/README.md deleted file mode 100644 index c8ec755..0000000 --- a/api/prisma/migrations/20201018233330-add-simple-user-model/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Migration `20201018233330-add-simple-user-model` - -This migration has been generated by Kurt Hutten at 10/19/2020, 10:33:30 AM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -CREATE TABLE "User" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userName" TEXT NOT NULL, - "email" TEXT NOT NULL, - "issuer" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "image" TEXT, - "bio" TEXT -) - -CREATE UNIQUE INDEX "User.userName_unique" ON "User"("userName") - -CREATE UNIQUE INDEX "User.email_unique" ON "User"("email") - -CREATE UNIQUE INDEX "User.issuer_unique" ON "User"("issuer") -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201011095227-create-contact..20201018233330-add-simple-user-model ---- datamodel.dml -+++ datamodel.dml -@@ -1,9 +1,7 @@ - datasource DS { -- // optionally set multiple providers -- // example: provider = ["sqlite", "postgresql"] -- provider = "sqlite" -- url = "***" -+ provider = ["sqlite", "postgresql"] -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -34,4 +32,17 @@ - email String - message String - createdAt DateTime @default(now()) - } -+ -+model User { -+ id Int @id @default(autoincrement()) -+ userName String @unique -+ email String @unique -+ issuer String @unique -+ -+ createdAt DateTime @default(now()) -+ updatedAt DateTime @updatedAt -+ -+ image String? // url maybe id or file storage service? cloudinary? -+ bio String? //mark down -+} -``` - - diff --git a/api/prisma/migrations/20201018233330-add-simple-user-model/schema.prisma b/api/prisma/migrations/20201018233330-add-simple-user-model/schema.prisma deleted file mode 100644 index 15e3468..0000000 --- a/api/prisma/migrations/20201018233330-add-simple-user-model/schema.prisma +++ /dev/null @@ -1,48 +0,0 @@ -datasource DS { - provider = ["sqlite", "postgresql"] - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} - -model Contact { - id Int @id @default(autoincrement()) - name String - email String - message String - createdAt DateTime @default(now()) -} - -model User { - id Int @id @default(autoincrement()) - userName String @unique - email String @unique - issuer String @unique - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - image String? // url maybe id or file storage service? cloudinary? - bio String? //mark down -} diff --git a/api/prisma/migrations/20201018233330-add-simple-user-model/steps.json b/api/prisma/migrations/20201018233330-add-simple-user-model/steps.json deleted file mode 100644 index 8ea741d..0000000 --- a/api/prisma/migrations/20201018233330-add-simple-user-model/steps.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "UpdateArgument", - "location": { - "tag": "Source", - "source": "DS" - }, - "argument": "provider", - "newValue": "[\"sqlite\", \"postgresql\"]" - }, - { - "tag": "CreateModel", - "model": "User" - }, - { - "tag": "CreateField", - "model": "User", - "field": "id", - "type": "Int", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "id" - }, - "directive": "id" - } - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "id" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "User", - "field": "id" - }, - "directive": "default" - }, - "argument": "", - "value": "autoincrement()" - }, - { - "tag": "CreateField", - "model": "User", - "field": "userName", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "userName" - }, - "directive": "unique" - } - }, - { - "tag": "CreateField", - "model": "User", - "field": "email", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "email" - }, - "directive": "unique" - } - }, - { - "tag": "CreateField", - "model": "User", - "field": "issuer", - "type": "String", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "issuer" - }, - "directive": "unique" - } - }, - { - "tag": "CreateField", - "model": "User", - "field": "createdAt", - "type": "DateTime", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "createdAt" - }, - "directive": "default" - } - }, - { - "tag": "CreateArgument", - "location": { - "tag": "Directive", - "path": { - "tag": "Field", - "model": "User", - "field": "createdAt" - }, - "directive": "default" - }, - "argument": "", - "value": "now()" - }, - { - "tag": "CreateField", - "model": "User", - "field": "updatedAt", - "type": "DateTime", - "arity": "Required" - }, - { - "tag": "CreateDirective", - "location": { - "path": { - "tag": "Field", - "model": "User", - "field": "updatedAt" - }, - "directive": "updatedAt" - } - }, - { - "tag": "CreateField", - "model": "User", - "field": "image", - "type": "String", - "arity": "Optional" - }, - { - "tag": "CreateField", - "model": "User", - "field": "bio", - "type": "String", - "arity": "Optional" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201019072122-add-simplify-user-model/README.md b/api/prisma/migrations/20201019072122-add-simplify-user-model/README.md deleted file mode 100644 index 29d4e11..0000000 --- a/api/prisma/migrations/20201019072122-add-simplify-user-model/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Migration `20201019072122-add-simplify-user-model` - -This migration has been generated by Kurt Hutten at 10/19/2020, 6:21:22 PM. -You can check out the [state of the schema](./schema.prisma) after the migration. - -## Database Steps - -```sql -DROP INDEX "User.issuer_unique" - -DROP INDEX "User.userName_unique" - -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_User" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "email" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "image" TEXT, - "bio" TEXT -); -INSERT INTO "new_User" ("id", "email", "createdAt", "updatedAt", "image", "bio") SELECT "id", "email", "createdAt", "updatedAt", "image", "bio" FROM "User"; -DROP TABLE "User"; -ALTER TABLE "new_User" RENAME TO "User"; -CREATE UNIQUE INDEX "User.email_unique" ON "User"("email"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON -``` - -## Changes - -```diff -diff --git schema.prisma schema.prisma -migration 20201018233330-add-simple-user-model..20201019072122-add-simplify-user-model ---- datamodel.dml -+++ datamodel.dml -@@ -1,7 +1,7 @@ - datasource DS { - provider = ["sqlite", "postgresql"] -- url = "***" -+ url = "***" - } - generator client { - provider = "prisma-client-js" -@@ -34,12 +34,12 @@ - createdAt DateTime @default(now()) - } - model User { -- id Int @id @default(autoincrement()) -- userName String @unique -- email String @unique -- issuer String @unique -+ id Int @id @default(autoincrement()) -+ email String @unique -+ // userName String @unique -+ // issuer String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -``` - - diff --git a/api/prisma/migrations/20201019072122-add-simplify-user-model/schema.prisma b/api/prisma/migrations/20201019072122-add-simplify-user-model/schema.prisma deleted file mode 100644 index 24140d8..0000000 --- a/api/prisma/migrations/20201019072122-add-simplify-user-model/schema.prisma +++ /dev/null @@ -1,48 +0,0 @@ -datasource DS { - provider = ["sqlite", "postgresql"] - url = "***" -} - -generator client { - provider = "prisma-client-js" - binaryTargets = "native" -} - -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} - -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} - -model Contact { - id Int @id @default(autoincrement()) - name String - email String - message String - createdAt DateTime @default(now()) -} - -model User { - id Int @id @default(autoincrement()) - email String @unique - // userName String @unique - // issuer String @unique - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - image String? // url maybe id or file storage service? cloudinary? - bio String? //mark down -} diff --git a/api/prisma/migrations/20201019072122-add-simplify-user-model/steps.json b/api/prisma/migrations/20201019072122-add-simplify-user-model/steps.json deleted file mode 100644 index 0285263..0000000 --- a/api/prisma/migrations/20201019072122-add-simplify-user-model/steps.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "0.3.14-fixed", - "steps": [ - { - "tag": "DeleteField", - "model": "User", - "field": "userName" - }, - { - "tag": "DeleteField", - "model": "User", - "field": "issuer" - } - ] -} \ No newline at end of file diff --git a/api/prisma/migrations/20201101183848-db-init/README.md b/api/prisma/migrations/20201101183848-db-init/README.md new file mode 100644 index 0000000..29cd885 --- /dev/null +++ b/api/prisma/migrations/20201101183848-db-init/README.md @@ -0,0 +1,158 @@ +# Migration `20201101183848-db-init` + +This migration has been generated by Kurt Hutten at 11/2/2020, 5:38:48 AM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "userName" TEXT NOT NULL, + "email" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "image" TEXT, + "bio" TEXT, +PRIMARY KEY ("id") +) + +CREATE TABLE "Part" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "code" TEXT, + "mainImage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT NOT NULL, + + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE, +PRIMARY KEY ("id") +) + +CREATE TABLE "PartReaction" ( + "id" TEXT NOT NULL, + "emote" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "partId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("partId") REFERENCES "Part"("id") ON DELETE CASCADE ON UPDATE CASCADE, +PRIMARY KEY ("id") +) + +CREATE TABLE "Comment" ( + "id" TEXT NOT NULL, + "text" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "partId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("partId") REFERENCES "Part"("id") ON DELETE CASCADE ON UPDATE CASCADE, +PRIMARY KEY ("id") +) + +CREATE UNIQUE INDEX "User.userName_unique" ON "User"("userName") + +CREATE UNIQUE INDEX "User.email_unique" ON "User"("email") + +CREATE UNIQUE INDEX "Part.title_userId_unique" ON "Part"("title", "userId") + +CREATE UNIQUE INDEX "PartReaction.emote_userId_partId_unique" ON "PartReaction"("emote", "userId", "partId") +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration ..20201101183848-db-init +--- datamodel.dml ++++ datamodel.dml +@@ -1,0 +1,79 @@ ++datasource DS { ++ provider = ["sqlite", "postgresql"] ++ url = "***" ++} ++ ++generator client { ++ provider = "prisma-client-js" ++ binaryTargets = "native" ++} ++ ++// sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode ++// enum Role { ++// USER ++// ADMIN ++// } ++ ++// enum PartType { ++// CASCADESTUDIO ++// JSCAD ++// } ++ ++model User { ++ id String @id @default(uuid()) ++ userName String @unique // reffered to as userId in @relations ++ email String @unique ++ // role should probably be a list [] and also use enums, neither are supported by sqllight, so we need to set up postgresql in dev ++ // maybe let netlify handle roles for now. ++ // role String @default("user") ++ ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @updatedAt ++ ++ image String? // url maybe id or file storage service? cloudinary? ++ bio String? //mark down ++ Part Part[] ++ Reaction PartReaction[] ++ Comment Comment[] ++} ++ ++model Part { ++ id String @id @default(uuid()) ++ title String ++ description String? // markdown string ++ code String? ++ mainImage String? // link to cloudinary ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @updatedAt ++ user User @relation(fields: [userId], references: [id]) ++ userId String ++ ++ Comment Comment[] ++ Reaction PartReaction[] ++ @@unique([title, userId]) ++} ++ ++model PartReaction { ++ 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 ++ ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @updatedAt ++ @@unique([emote, userId, partId]) ++} ++ ++model Comment { ++ id String @id @default(uuid()) ++ 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 ++ ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @updatedAt ++} +``` + + diff --git a/api/prisma/migrations/20201101183848-db-init/schema.prisma b/api/prisma/migrations/20201101183848-db-init/schema.prisma new file mode 100644 index 0000000..f5d13ba --- /dev/null +++ b/api/prisma/migrations/20201101183848-db-init/schema.prisma @@ -0,0 +1,79 @@ +datasource DS { + provider = ["sqlite", "postgresql"] + url = "***" +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +// sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode +// enum Role { +// USER +// ADMIN +// } + +// enum PartType { +// CASCADESTUDIO +// JSCAD +// } + +model User { + id String @id @default(uuid()) + userName String @unique // reffered to as userId in @relations + email String @unique + // role should probably be a list [] and also use enums, neither are supported by sqllight, so we need to set up postgresql in dev + // maybe let netlify handle roles for now. + // role String @default("user") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + image String? // url maybe id or file storage service? cloudinary? + bio String? //mark down + Part Part[] + Reaction PartReaction[] + Comment Comment[] +} + +model Part { + id String @id @default(uuid()) + title String + description String? // markdown string + code String? + mainImage String? // link to cloudinary + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + userId String + + Comment Comment[] + Reaction PartReaction[] + @@unique([title, userId]) +} + +model PartReaction { + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@unique([emote, userId, partId]) +} + +model Comment { + id String @id @default(uuid()) + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/api/prisma/migrations/20201101183848-db-init/steps.json b/api/prisma/migrations/20201101183848-db-init/steps.json new file mode 100644 index 0000000..ce0e48a --- /dev/null +++ b/api/prisma/migrations/20201101183848-db-init/steps.json @@ -0,0 +1,839 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateSource", + "source": "DS" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "DS" + }, + "argument": "provider", + "value": "[\"sqlite\", \"postgresql\"]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "DS" + }, + "argument": "url", + "value": "\"***\"" + }, + { + "tag": "CreateModel", + "model": "User" + }, + { + "tag": "CreateField", + "model": "User", + "field": "id", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "uuid()" + }, + { + "tag": "CreateField", + "model": "User", + "field": "userName", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "userName" + }, + "directive": "unique" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "email", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "email" + }, + "directive": "unique" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "User", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "updatedAt" + }, + "directive": "updatedAt" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "image", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "bio", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "Part", + "type": "Part", + "arity": "List" + }, + { + "tag": "CreateField", + "model": "User", + "field": "Reaction", + "type": "PartReaction", + "arity": "List" + }, + { + "tag": "CreateField", + "model": "User", + "field": "Comment", + "type": "Comment", + "arity": "List" + }, + { + "tag": "CreateModel", + "model": "Part" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "id", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Part", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "uuid()" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "title", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "description", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "code", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "mainImage", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Part", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "updatedAt" + }, + "directive": "updatedAt" + } + }, + { + "tag": "CreateField", + "model": "Part", + "field": "user", + "type": "User", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "user" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Part", + "field": "user" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[userId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Part", + "field": "user" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "userId", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "Comment", + "type": "Comment", + "arity": "List" + }, + { + "tag": "CreateField", + "model": "Part", + "field": "Reaction", + "type": "PartReaction", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Model", + "model": "Part", + "arguments": [ + { + "name": "", + "value": "[title, userId]" + } + ] + }, + "directive": "unique" + } + }, + { + "tag": "CreateModel", + "model": "PartReaction" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "id", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "uuid()" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "emote", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "user", + "type": "User", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "user" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "user" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[userId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "user" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "userId", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "part", + "type": "Part", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "part" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "part" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[partId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "part" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "partId", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "PartReaction", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "PartReaction", + "field": "updatedAt" + }, + "directive": "updatedAt" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Model", + "model": "PartReaction", + "arguments": [ + { + "name": "", + "value": "[emote, userId, partId]" + } + ] + }, + "directive": "unique" + } + }, + { + "tag": "CreateModel", + "model": "Comment" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "id", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "uuid()" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "text", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "user", + "type": "User", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "user" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "user" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[userId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "user" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "userId", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "part", + "type": "Part", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "part" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "part" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[partId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "part" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "partId", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "updatedAt" + }, + "directive": "updatedAt" + } + } + ] +} \ No newline at end of file diff --git a/api/prisma/migrations/20201105184423-add-name-to-user/README.md b/api/prisma/migrations/20201105184423-add-name-to-user/README.md new file mode 100644 index 0000000..e89ab99 --- /dev/null +++ b/api/prisma/migrations/20201105184423-add-name-to-user/README.md @@ -0,0 +1,43 @@ +# Migration `20201105184423-add-name-to-user` + +This migration has been generated by Kurt Hutten at 11/6/2020, 5:44:24 AM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "User" ADD COLUMN "name" TEXT +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20201101183848-db-init..20201105184423-add-name-to-user +--- datamodel.dml ++++ datamodel.dml +@@ -1,7 +1,7 @@ + datasource DS { + provider = ["sqlite", "postgresql"] +- url = "***" ++ url = "***" + } + generator client { + provider = "prisma-client-js" +@@ -19,11 +19,12 @@ + // JSCAD + // } + model User { +- id String @id @default(uuid()) +- userName String @unique // reffered to as userId in @relations +- email String @unique ++ id String @id @default(uuid()) ++ userName String @unique // reffered to as userId in @relations ++ email String @unique ++ name String? + // role should probably be a list [] and also use enums, neither are supported by sqllight, so we need to set up postgresql in dev + // maybe let netlify handle roles for now. + // role String @default("user") +``` + + diff --git a/api/prisma/migrations/20201105184423-add-name-to-user/schema.prisma b/api/prisma/migrations/20201105184423-add-name-to-user/schema.prisma new file mode 100644 index 0000000..e0f3879 --- /dev/null +++ b/api/prisma/migrations/20201105184423-add-name-to-user/schema.prisma @@ -0,0 +1,80 @@ +datasource DS { + provider = ["sqlite", "postgresql"] + url = "***" +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +// sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode +// enum Role { +// USER +// ADMIN +// } + +// enum PartType { +// CASCADESTUDIO +// JSCAD +// } + +model User { + id String @id @default(uuid()) + userName String @unique // reffered to as userId in @relations + email String @unique + name String? + // role should probably be a list [] and also use enums, neither are supported by sqllight, so we need to set up postgresql in dev + // maybe let netlify handle roles for now. + // role String @default("user") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + image String? // url maybe id or file storage service? cloudinary? + bio String? //mark down + Part Part[] + Reaction PartReaction[] + Comment Comment[] +} + +model Part { + id String @id @default(uuid()) + title String + description String? // markdown string + code String? + mainImage String? // link to cloudinary + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + userId String + + Comment Comment[] + Reaction PartReaction[] + @@unique([title, userId]) +} + +model PartReaction { + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@unique([emote, userId, partId]) +} + +model Comment { + id String @id @default(uuid()) + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/api/prisma/migrations/20201105184423-add-name-to-user/steps.json b/api/prisma/migrations/20201105184423-add-name-to-user/steps.json new file mode 100644 index 0000000..21ac63d --- /dev/null +++ b/api/prisma/migrations/20201105184423-add-name-to-user/steps.json @@ -0,0 +1,12 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "User", + "field": "name", + "type": "String", + "arity": "Optional" + } + ] +} \ No newline at end of file diff --git a/api/prisma/migrations/migrate.lock b/api/prisma/migrations/migrate.lock index dddc03e..0f07957 100644 --- a/api/prisma/migrations/migrate.lock +++ b/api/prisma/migrations/migrate.lock @@ -1,9 +1,4 @@ # Prisma Migrate lockfile v1 -20201009213512-create-posts -20201011043647-create-parts -20201011052155-add-code-to-part -20201011082558-add-code-not-needed-upon-create -20201011095227-create-contact -20201018233330-add-simple-user-model -20201019072122-add-simplify-user-model \ No newline at end of file +20201101183848-db-init +20201105184423-add-name-to-user \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index baecb2a..c55ce7f 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -8,41 +8,73 @@ generator client { binaryTargets = "native" } -model Post { - id Int @id @default(autoincrement()) - title String - body String - createdAt DateTime @default(now()) -} +// sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode +// enum Role { +// USER +// ADMIN +// } -model Part { - id Int @id @default(autoincrement()) - title String - description String // markdown string - code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") - mainImage String // link to cloudinary - createdAt DateTime @default(now()) - // userId - //likes, comments, reactions -} - -model Contact { - id Int @id @default(autoincrement()) - name String - email String - message String - createdAt DateTime @default(now()) -} +// enum PartType { +// CASCADESTUDIO +// JSCAD +// } model User { - id Int @id @default(autoincrement()) - email String @unique - // userName String @unique - // issuer String @unique + id String @id @default(uuid()) + userName String @unique // reffered to as userId in @relations + email String @unique + name String? + // role should probably be a list [] and also use enums, neither are supported by sqllight, so we need to set up postgresql in dev + // maybe let netlify handle roles for now. + // role String @default("user") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - image String? // url maybe id or file storage service? cloudinary? - bio String? //mark down + image String? // url maybe id or file storage service? cloudinary? + bio String? //mark down + Part Part[] + Reaction PartReaction[] + Comment Comment[] +} + +model Part { + id String @id @default(uuid()) + title String + description String? // markdown string + code String? + mainImage String? // link to cloudinary + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + userId String + + Comment Comment[] + Reaction PartReaction[] + @@unique([title, userId]) +} + +model PartReaction { + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@unique([emote, userId, partId]) +} + +model Comment { + id String @id @default(uuid()) + 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 + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } diff --git a/api/src/functions/identity-signup.js b/api/src/functions/identity-signup.js index 6b52938..1d0a003 100644 --- a/api/src/functions/identity-signup.js +++ b/api/src/functions/identity-signup.js @@ -1,4 +1,5 @@ import { createUserInsecure } from 'src/services/users/users.js' +import { db } from 'src/lib/db' export const handler = async (req, _context) => { const body = JSON.parse(req.body) @@ -61,10 +62,25 @@ export const handler = async (req, _context) => { // image: '', // bio: '' // } + + const generateUniqueUserName = async (seed, count = 0) => { + const isUnique = !(await db.user.findOne({ + where: { userName: seed }, + })) + if(isUnique) { + return seed + } + count += 1 + const newSeed = count === 1 ? `${seed}_${count}` : seed.slice(0,-1) + count + return generateUniqueUserName(newSeed, count) + } + const userNameSeed = email.split('@')[0] + const userName = await generateUniqueUserName(userNameSeed) // TODO maybe come up with a better default userName? const input = { email, - bio: 'default bio' - // full_name: user.user_metadata.full_name + userName, + name: user.user_metadata && user.user_metadata.full_name, + id: user.id, } await createUserInsecure({input}) diff --git a/api/src/graphql/comments.sdl.js b/api/src/graphql/comments.sdl.js new file mode 100644 index 0000000..b36296b --- /dev/null +++ b/api/src/graphql/comments.sdl.js @@ -0,0 +1,35 @@ +export const schema = gql` + type Comment { + id: String! + text: String! + user: User! + userId: String! + part: Part! + partId: String! + createdAt: DateTime! + updatedAt: DateTime! + } + + type Query { + comments: [Comment!]! + comment(id: String!): Comment + } + + input CreateCommentInput { + text: String! + userId: String! + partId: String! + } + + input UpdateCommentInput { + text: String + userId: String + partId: String + } + + type Mutation { + createComment(input: CreateCommentInput!): Comment! + updateComment(id: String!, input: UpdateCommentInput!): Comment! + deleteComment(id: String!): Comment! + } +` diff --git a/api/src/graphql/contacts.sdl.js b/api/src/graphql/contacts.sdl.js deleted file mode 100644 index c4153b6..0000000 --- a/api/src/graphql/contacts.sdl.js +++ /dev/null @@ -1,29 +0,0 @@ -export const schema = gql` - type Contact { - id: Int! - name: String! - email: String! - message: String! - createdAt: DateTime! - } - - type Query { - contacts: [Contact!]! - } - - input CreateContactInput { - name: String! - email: String! - message: String! - } - - input UpdateContactInput { - name: String - email: String - message: String - } - - type Mutation { - createContact(input: CreateContactInput!): Contact - } -` diff --git a/api/src/graphql/partReactions.sdl.js b/api/src/graphql/partReactions.sdl.js new file mode 100644 index 0000000..4eaaa26 --- /dev/null +++ b/api/src/graphql/partReactions.sdl.js @@ -0,0 +1,38 @@ +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 + } + + 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/api/src/graphql/parts.sdl.js b/api/src/graphql/parts.sdl.js index bec69fa..a7f42e4 100644 --- a/api/src/graphql/parts.sdl.js +++ b/api/src/graphql/parts.sdl.js @@ -1,23 +1,30 @@ export const schema = gql` type Part { - id: Int! + id: String! title: String! - description: String! - code: String! - mainImage: String! + description: String + code: String + mainImage: String createdAt: DateTime! + updatedAt: DateTime! + user: User! + userId: String! + Comment: [Comment]! + Reaction(userId: String): [PartReaction]! } type Query { parts: [Part!]! - part(id: Int!): Part + part(id: String!): Part + partByUserAndTitle(userName: String! partTitle: String!): Part } input CreatePartInput { title: String! - description: String! + description: String code: String mainImage: String + userId: String! } input UpdatePartInput { @@ -25,11 +32,12 @@ export const schema = gql` description: String code: String mainImage: String + userId: String } type Mutation { createPart(input: CreatePartInput!): Part! - updatePart(id: Int!, input: UpdatePartInput!): Part! - deletePart(id: Int!): Part! + updatePart(id: String!, input: UpdatePartInput!): Part! + deletePart(id: String!): Part! } ` diff --git a/api/src/graphql/posts.sdl.js b/api/src/graphql/posts.sdl.js deleted file mode 100644 index ad384ff..0000000 --- a/api/src/graphql/posts.sdl.js +++ /dev/null @@ -1,29 +0,0 @@ -export const schema = gql` - type Post { - id: Int! - title: String! - body: String! - createdAt: DateTime! - } - - type Query { - posts: [Post!]! - post(id: Int!): Post - } - - input CreatePostInput { - title: String! - body: String! - } - - input UpdatePostInput { - title: String - body: String - } - - type Mutation { - createPost(input: CreatePostInput!): Post! - updatePost(id: Int!, input: UpdatePostInput!): Post! - deletePost(id: Int!): Post! - } -` diff --git a/api/src/graphql/users.sdl.js b/api/src/graphql/users.sdl.js index 366075d..1830193 100644 --- a/api/src/graphql/users.sdl.js +++ b/api/src/graphql/users.sdl.js @@ -1,35 +1,45 @@ export const schema = gql` type User { - id: Int! + id: String! + userName: String! email: String! + name: String createdAt: DateTime! updatedAt: DateTime! image: String bio: String + Parts: [Part]! + Part(partTitle: String): Part + Reaction: [PartReaction]! + Comment: [Comment]! } type Query { users: [User!]! - user(id: Int!): User + user(id: String!): User + userName(userName: String!): User } input CreateUserInput { + userName: String! email: String! - # issuer: String! + name: String image: String bio: String } input UpdateUserInput { + userName: String email: String - # issuer: String + name: String image: String bio: String } type Mutation { createUser(input: CreateUserInput!): User! - updateUser(id: Int!, input: UpdateUserInput!): User! - deleteUser(id: Int!): User! + updateUser(id: String!, input: UpdateUserInput!): User! + updateUserByUserName(userName: String!, input: UpdateUserInput!): User! + deleteUser(id: String!): User! } ` diff --git a/api/src/lib/owner.js b/api/src/lib/owner.js new file mode 100644 index 0000000..086deeb --- /dev/null +++ b/api/src/lib/owner.js @@ -0,0 +1,43 @@ +import { AuthenticationError, ForbiddenError } from '@redwoodjs/api' +import { db } from 'src/lib/db' + +export const requireOwnership = async ({ userId, userName, partId } = {}) => { + // 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) { + throw new ForbiddenError("You don't have access to do that.") + } + + if(context.currentUser.roles?.includes('admin')) { + return + } + + const netlifyUserId = context.currentUser?.sub + if(userId && userId !== netlifyUserId) { + throw new ForbiddenError("You don't own this resource.") + } + + if(userName) { + const user = await db.user.findOne({ + where: { userName }, + }) + + if(!user || user.id !== netlifyUserId) { + throw new ForbiddenError("You don't own this resource.") + } + } + + if(partId) { + const user = await db.part.findOne({ + where: { id: partId }, + }).user() + + if(!user || user.id !== netlifyUserId) { + throw new ForbiddenError("You don't own this resource.") + } + } + +} diff --git a/api/src/services/comments/comments.js b/api/src/services/comments/comments.js new file mode 100644 index 0000000..5c46fdf --- /dev/null +++ b/api/src/services/comments/comments.js @@ -0,0 +1,38 @@ +import { db } from 'src/lib/db' +import { foreignKeyReplacement } from 'src/services/helpers' + +export const comments = () => { + return db.comment.findMany() +} + +export const comment = ({ id }) => { + return db.comment.findOne({ + where: { id }, + }) +} + +export const createComment = ({ input }) => { + return db.comment.create({ + data: foreignKeyReplacement(input), + }) +} + +export const updateComment = ({ id, input }) => { + return db.comment.update({ + data: foreignKeyReplacement(input), + where: { id }, + }) +} + +export const deleteComment = ({ id }) => { + return db.comment.delete({ + where: { id }, + }) +} + +export const Comment = { + user: (_obj, { root }) => + db.comment.findOne({ where: { id: root.id } }).user(), + part: (_obj, { root }) => + db.comment.findOne({ where: { id: root.id } }).part(), +} diff --git a/api/src/services/contacts/contacts.test.js b/api/src/services/comments/comments.test.js similarity index 51% rename from api/src/services/contacts/contacts.test.js rename to api/src/services/comments/comments.test.js index fbbbc1e..108fd0f 100644 --- a/api/src/services/contacts/contacts.test.js +++ b/api/src/services/comments/comments.test.js @@ -1,8 +1,8 @@ /* -import { contacts } from './contacts' +import { comments } from './comments' */ -describe('contacts', () => { +describe('comments', () => { it('returns true', () => { expect(true).toBe(true) }) diff --git a/api/src/services/contacts/contacts.js b/api/src/services/contacts/contacts.js deleted file mode 100644 index 3481975..0000000 --- a/api/src/services/contacts/contacts.js +++ /dev/null @@ -1,24 +0,0 @@ -import { UserInputError } from '@redwoodjs/api' - -import { db } from 'src/lib/db' - - -const validate = (input) => { - if (input.email && !input.email.match(/[^@]+@[^.]+\..+/)) { - throw new UserInputError("Can't create new contact", { - messages: { - email: ['is not formatted like an email address'], - }, - }) - } -} - -export const contacts = () => { - return db.contact.findMany() -} - -export const createContact = ({ input }) => { - - validate(input) - return db.contact.create({ data: input }) -} diff --git a/api/src/services/helpers.js b/api/src/services/helpers.js new file mode 100644 index 0000000..ed474f3 --- /dev/null +++ b/api/src/services/helpers.js @@ -0,0 +1,13 @@ +export const foreignKeyReplacement = (input) => { + let output = input + const foreignKeys = Object.keys(input).filter((k) => k.match(/Id$/)) + foreignKeys.forEach((key) => { + const modelName = key.replace(/Id$/, '') + const value = input[key] + delete output[key] + output = Object.assign(output, { + [modelName]: { connect: { id: value } }, + }) + }) + return output +} diff --git a/api/src/services/partReactions/partReactions.js b/api/src/services/partReactions/partReactions.js new file mode 100644 index 0000000..4efd545 --- /dev/null +++ b/api/src/services/partReactions/partReactions.js @@ -0,0 +1,58 @@ +import { UserInputError } from '@redwoodjs/api' + +import { requireAuth } from 'src/lib/auth' +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 partReaction = ({ id }) => { + return db.partReaction.findOne({ + where: { id }, + }) +} + +export const togglePartReaction = async ({ input }) => { + // if write fails emote_userId_partId @@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 + if(!legalReactions.includes(input.emote)) { + throw new UserInputError(`You can't react with '${input.emote}', only the following are allowed: ${legalReactions.join(', ')}`) + } + let dbPromise + const inputClone = {...input} // TODO foreignKeyReplacement mutates input, which I should fix but am lazy right now + try{ + dbPromise = await db.partReaction.create({ + data: foreignKeyReplacement(input), + }) + } catch(e) { + dbPromise = db.partReaction.delete({ + where: { emote_userId_partId: inputClone}, + }) + } + return dbPromise +} + +export const updatePartReaction = ({ id, input }) => { + return db.partReaction.update({ + data: foreignKeyReplacement(input), + where: { id }, + }) +} + +export const deletePartReaction = ({ id }) => { + return db.partReaction.delete({ + where: { id }, + }) +} + +export const PartReaction = { + user: (_obj, { root }) => + db.partReaction.findOne({ where: { id: root.id } }).user(), + part: (_obj, { root }) => + db.partReaction.findOne({ where: { id: root.id } }).part(), +} diff --git a/api/src/services/partReactions/partReactions.test.js b/api/src/services/partReactions/partReactions.test.js new file mode 100644 index 0000000..47d4f20 --- /dev/null +++ b/api/src/services/partReactions/partReactions.test.js @@ -0,0 +1,9 @@ +/* +import { partReactions } from './partReactions' +*/ + +describe('partReactions', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/api/src/services/parts/parts.js b/api/src/services/parts/parts.js index 98c6243..1b96b42 100644 --- a/api/src/services/parts/parts.js +++ b/api/src/services/parts/parts.js @@ -1,4 +1,8 @@ import { db } from 'src/lib/db' +import { foreignKeyReplacement } from 'src/services/helpers' +import { requireAuth } from 'src/lib/auth' +import { requireOwnership } from 'src/lib/owner' +import { user } from 'src/services/users/users' export const parts = () => { return db.part.findMany() @@ -9,22 +13,52 @@ export const part = ({ id }) => { where: { id }, }) } - -export const createPart = ({ input }) => { - return db.part.create({ - data: input, +export const partByUserAndTitle = async ({ userName, partTitle }) => { + const user = await db.user.findOne({ + where: { + userName + } + }) + return db.part.findOne({ + where: { + title_userId: { + title: partTitle, + userId: user.id, + } + }, }) } -export const updatePart = ({ id, input }) => { +export const createPart = async ({ input }) => { + requireAuth() + return db.part.create({ + data: foreignKeyReplacement(input), + }) +} + +export const updatePart = async ({ id, input }) => { + requireAuth() + await requireOwnership({partId: id}) + if(input.title) { + input.title = input.title.replace(/([^a-zA-Z\d_:])/g, '-') + } return db.part.update({ - data: input, + data: foreignKeyReplacement(input), where: { id }, }) } export const deletePart = ({ id }) => { + requireAuth() return db.part.delete({ where: { id }, }) } + +export const Part = { + user: (_obj, { root }) => db.part.findOne({ where: { id: root.id } }).user(), + Comment: (_obj, { root }) => + db.part.findOne({ where: { id: root.id } }).Comment(), + Reaction: (_obj, { root }) => + db.part.findOne({ where: { id: root.id } }).Reaction({where: {userId: _obj.userId}}), +} diff --git a/api/src/services/posts/posts.js b/api/src/services/posts/posts.js deleted file mode 100644 index 8912a71..0000000 --- a/api/src/services/posts/posts.js +++ /dev/null @@ -1,34 +0,0 @@ -import { db } from 'src/lib/db' -import { requireAuth } from 'src/lib/auth' - -export const posts = () => { - return db.post.findMany() -} - -export const post = ({ id }) => { - return db.post.findOne({ - where: { id }, - }) -} - -export const createPost = ({ input }) => { - requireAuth() - return db.post.create({ - data: input, - }) -} - -export const updatePost = ({ id, input }) => { - requireAuth() - return db.post.update({ - data: input, - where: { id }, - }) -} - -export const deletePost = ({ id }) => { - requireAuth() - return db.post.delete({ - where: { id }, - }) -} diff --git a/api/src/services/posts/posts.test.js b/api/src/services/posts/posts.test.js deleted file mode 100644 index f23483a..0000000 --- a/api/src/services/posts/posts.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -import { posts } from './posts' -*/ - -describe('posts', () => { - it('returns true', () => { - expect(true).toBe(true) - }) -}) diff --git a/api/src/services/users/users.js b/api/src/services/users/users.js index 3d3675c..cae1557 100644 --- a/api/src/services/users/users.js +++ b/api/src/services/users/users.js @@ -1,5 +1,7 @@ import { db } from 'src/lib/db' import { requireAuth } from 'src/lib/auth' +import { requireOwnership } from 'src/lib/owner' +import { UserInputError } from '@redwoodjs/api' export const users = () => { requireAuth({ role: 'admin' }) @@ -7,17 +9,21 @@ export const users = () => { } export const user = ({ id }) => { - requireAuth() return db.user.findOne({ where: { id }, }) } -export const createUser = ({ input }) => { - requireAuth({ role: 'admin' }) - return createUserInsecure({input}) +export const userName = ({ userName }) => { + return db.user.findOne({ + where: { userName }, + }) } +export const createUser = ({ input }) => { + requireAuth({ role: 'admin' }) + createUserInsecure({input}) +} export const createUserInsecure = ({ input }) => { return db.user.create({ data: input, @@ -32,9 +38,36 @@ export const updateUser = ({ id, input }) => { }) } +export const updateUserByUserName = async ({ userName, input }) => { + requireAuth() + await requireOwnership({userName}) + if(input.userName) { + input.userName = input.userName.replace(/([^a-zA-Z\d_:])/g, '-') + } + if(input.userName && ['new', 'edit', 'update'].includes(input.userName)) { //TODO complete this and use a regexp so that it's not case sensitive, don't want someone with the userName eDiT + throw new UserInputError(`You've tried to used a protected word as you userName, try something other than `) + } + return db.user.update({ + data: input, + where: { userName }, + }) +} + export const deleteUser = ({ id }) => { requireAuth({ role: 'admin' }) return db.user.delete({ where: { id }, }) } + +export const User = { + Parts: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).Part(), + Part: (_obj, { root, ...rest }) => _obj.partTitle && db.part.findOne({where: { title_userId: { + title: _obj.partTitle, + userId: root.id, + }}}), + Reaction: (_obj, { root }) => + db.user.findOne({ where: { id: root.id } }).Reaction(), + Comment: (_obj, { root }) => + db.user.findOne({ where: { id: root.id } }).Comment(), +} diff --git a/web/package.json b/web/package.json index a9942bc..e9ac1d7 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,7 @@ "@redwoodjs/web": "^0.19.2", "cloudinary-react": "^1.6.7", "controlkit": "^0.1.9", + "get-active-classes": "^0.0.11", "golden-layout": "^1.5.9", "jquery": "^3.5.1", "monaco-editor": "^0.20.0", diff --git a/web/src/Routes.js b/web/src/Routes.js index 8255681..547071b 100644 --- a/web/src/Routes.js +++ b/web/src/Routes.js @@ -12,31 +12,39 @@ import { Router, Route, Private } from '@redwoodjs/router' const Routes = () => { return ( - {/* TODO add add min role to users and users/new */} - - - - - - - - - - - - - - - - - - - - - - + {/* */} + + {/* Ownership enforced routes */} + + + + {/* End ownership enforced routes */} + + + + {/* */} + + {/* GENERATED ROUTES BELOW, probably going to clean these up and delete most of them, but the CRUD functionality is useful for now */} + {/* All private by default for safety and because the routes that are left after clean up will probably be admin pages */} + + + + + + + + + + + + + + + + + ) } diff --git a/web/src/components/BlogPost/BlogPost.js b/web/src/components/BlogPost/BlogPost.js deleted file mode 100644 index 524ee4b..0000000 --- a/web/src/components/BlogPost/BlogPost.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Link, routes } from '@redwoodjs/router' - -const BlogPost = ({ post }) => { - return ( -
-
-

- {post.title} -

-
-
{post.body}
-
- ) -} - -export default BlogPost \ No newline at end of file diff --git a/web/src/components/BlogPost/BlogPost.stories.js b/web/src/components/BlogPost/BlogPost.stories.js deleted file mode 100644 index 03e9f5f..0000000 --- a/web/src/components/BlogPost/BlogPost.stories.js +++ /dev/null @@ -1,7 +0,0 @@ -import BlogPost from './BlogPost' - -export const generated = () => { - return -} - -export default { title: 'Components/BlogPost' } diff --git a/web/src/components/BlogPostCell/BlogPostCell.js b/web/src/components/BlogPostCell/BlogPostCell.js deleted file mode 100644 index fd7987f..0000000 --- a/web/src/components/BlogPostCell/BlogPostCell.js +++ /dev/null @@ -1,22 +0,0 @@ -import BlogPost from 'src/components/BlogPost' - -export const QUERY = gql` - query BlogPostQuery($id: Int!) { - post(id: $id) { - id - title - body - createdAt - } - } -` - -export const Loading = () =>
Loading...
- -export const Empty = () =>
Empty
- -export const Failure = ({ error }) =>
Error: {error.message}
- -export const Success = ({ post }) => { - return -} diff --git a/web/src/components/BlogPostsCell/BlogPostsCell.js b/web/src/components/BlogPostsCell/BlogPostsCell.js deleted file mode 100644 index 1d4cb61..0000000 --- a/web/src/components/BlogPostsCell/BlogPostsCell.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Link, routes } from '@redwoodjs/router' -import BlogPost from 'src/components/BlogPost' - -export const QUERY = gql` - query BlogPostsQuery { - posts { - id - title - body - createdAt - } - } -` - -export const Loading = () =>
Loading...
- -export const Empty = () =>
Empty
- -export const Failure = ({ error }) =>
Error: {error.message}
- -export const Success = ({ posts }) => { - return posts.map((post) => ) -} diff --git a/web/src/components/Breadcrumb/Breadcrumb.js b/web/src/components/Breadcrumb/Breadcrumb.js new file mode 100644 index 0000000..fe921fb --- /dev/null +++ b/web/src/components/Breadcrumb/Breadcrumb.js @@ -0,0 +1,23 @@ +import { getActiveClasses } from "get-active-classes" + +import InputText from 'src/components/InputText' + +const Breadcrumb = ({ userName, partTitle, onPartTitleChange, className }) => { + return ( +

+
.
+ + {userName} + +
.
+ +

+ ) +} + +export default Breadcrumb diff --git a/web/src/components/Breadcrumb/Breadcrumb.stories.js b/web/src/components/Breadcrumb/Breadcrumb.stories.js new file mode 100644 index 0000000..98b7ef1 --- /dev/null +++ b/web/src/components/Breadcrumb/Breadcrumb.stories.js @@ -0,0 +1,7 @@ +import Breadcrumb from './Breadcrumb' + +export const generated = () => { + return +} + +export default { title: 'Components/Breadcrumb' } diff --git a/web/src/layouts/BlogLayout/BlogLayout.test.js b/web/src/components/Breadcrumb/Breadcrumb.test.js similarity index 57% rename from web/src/layouts/BlogLayout/BlogLayout.test.js rename to web/src/components/Breadcrumb/Breadcrumb.test.js index b87e730..adc1e45 100644 --- a/web/src/layouts/BlogLayout/BlogLayout.test.js +++ b/web/src/components/Breadcrumb/Breadcrumb.test.js @@ -1,11 +1,11 @@ import { render } from '@redwoodjs/testing' -import BlogLayout from './BlogLayout' +import Breadcrumb from './Breadcrumb' -describe('BlogLayout', () => { +describe('Breadcrumb', () => { it('renders successfully', () => { expect(() => { - render() + render() }).not.toThrow() }) }) diff --git a/web/src/components/Button/Button.js b/web/src/components/Button/Button.js new file mode 100644 index 0000000..5d76f0f --- /dev/null +++ b/web/src/components/Button/Button.js @@ -0,0 +1,22 @@ +import { getActiveClasses } from 'get-active-classes' +import Svg from 'src/components/Svg' + +const Button = ({onClick, iconName, children, className, shouldAnimateHover, disabled}) => { + return ( + + ) +} + +export default Button diff --git a/web/src/components/Button/Button.stories.js b/web/src/components/Button/Button.stories.js new file mode 100644 index 0000000..7d95d2d --- /dev/null +++ b/web/src/components/Button/Button.stories.js @@ -0,0 +1,10 @@ +import Button from './Button' + +export const generated = () => { + return <> + button with icon + + +} + +export default { title: 'Components/Button' } diff --git a/web/src/components/BlogPost/BlogPost.test.js b/web/src/components/Button/Button.test.js similarity index 59% rename from web/src/components/BlogPost/BlogPost.test.js rename to web/src/components/Button/Button.test.js index 87f976f..b0f1d6e 100644 --- a/web/src/components/BlogPost/BlogPost.test.js +++ b/web/src/components/Button/Button.test.js @@ -1,11 +1,11 @@ import { render } from '@redwoodjs/testing' -import BlogPost from './BlogPost' +import Button from './Button' -describe('BlogPost', () => { +describe('Button', () => { it('renders successfully', () => { expect(() => { - render() + render( + - -
- {emotes.map((emote, i) => ( - handleEmojiClick(emote.emoji)}> - {emote.emoji} {emote.count} - - ))} +
+ {(emotes.length ? emotes : noEmotes).map((emote, i) => ( + handleEmojiClick(emote.emoji)} + > + {emote.emoji}{emote.count} + + ))} +
- , - - {emojiMenu.map((emoji, i) => ( - handleEmojiClick(emoji)}>{emoji} - ))} - , - ] + +
+ {emojiMenu.map((emoji, i) => ( + + ))} +
+
+ + ) } export default EmojiReaction diff --git a/web/src/components/IdePartCell/IdePartCell.js b/web/src/components/IdePartCell/IdePartCell.js index 3b95c0b..122ed88 100644 --- a/web/src/components/IdePartCell/IdePartCell.js +++ b/web/src/components/IdePartCell/IdePartCell.js @@ -1,6 +1,6 @@ import { useMutation, useFlash } from '@redwoodjs/web' import { navigate, routes } from '@redwoodjs/router' -import Part from 'src/components/Part' +// import Part from 'src/components/Part' export const QUERY = gql` query FIND_PART_BY_ID($id: Int!) { @@ -42,5 +42,6 @@ export const Success = ({ part }) => { console.log(id, input, 'wowow') updatePart({ variables: { id, input } }) } - return + return
TODO part
+ // return } diff --git a/web/src/components/PartForm/ImageUploader.js b/web/src/components/ImageUploader/ImageUploader.js similarity index 76% rename from web/src/components/PartForm/ImageUploader.js rename to web/src/components/ImageUploader/ImageUploader.js index 0314a13..ca93f5e 100644 --- a/web/src/components/PartForm/ImageUploader.js +++ b/web/src/components/ImageUploader/ImageUploader.js @@ -11,19 +11,25 @@ import Svg from 'src/components/Svg/Svg.js' const CLOUDINARY_UPLOAD_PRESET = "CadHub_project_images"; const CLOUDINARY_UPLOAD_URL = "https://api.cloudinary.com/v1_1/irevdev/upload"; -export default function ImageUploader({ onImageUpload, imageUrl }) { +export default function ImageUploader({ + onImageUpload, + imageUrl, + aspectRatio, + className, + isEditable, + width=600 + }) { const [isModalOpen, setIsModalOpen] = useState(false) const [file, setFile] = useState() const [cloudinaryId, setCloudinaryId] = useState(imageUrl) const [imageObj, setImageObj] = useState() const [crop, setCrop] = useState({ - aspect: 16 / 9, + aspect: aspectRatio, unit: '%', width: 100, }); async function handleImageUpload() { const croppedFile = await getCroppedImg(imageObj, crop, 'avatar') - console.log(croppedFile) const imageData = new FormData(); imageData.append('upload_preset', CLOUDINARY_UPLOAD_PRESET); imageData.append('file', croppedFile); @@ -51,27 +57,29 @@ export default function ImageUploader({ onImageUpload, imageUrl }) { const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }); return ( -
-
- {cloudinaryId && } - - {cloudinaryId &&
+ {isEditable && } + {(cloudinaryId || !isEditable) &&
} {!cloudinaryId && } - {!cloudinaryId &&
+ {!cloudinaryId && isEditable &&
+
Drop files here ... - or + or upload +
}
{ + return ( + <> +

AspectRatio:1, no initial image, editable

+ < + ImageUploader + onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)} + aspectRatio={1} + isEditable={true} + className={"bg-red-400 rounded-half rounded-br-xl"} + /> +

AspectRatio 16:9, no initial image, editable

+ < + ImageUploader + onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)} + aspectRatio={16/9} + isEditable={true} + className={"bg-red-400 rounded-xl"} + imageUrl="CadHub/inakek2urbreynblzhgt" + /> +

AspectRatio:1, no initial image, NOT editable

+ < + ImageUploader + onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)} + aspectRatio={1} + className={"rounded-half rounded-br-xl"} + /> +

AspectRatio ,16:9 no initial image, NOT editable

+ < + ImageUploader + onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)} + aspectRatio={16/9} + className={"rounded-xl"} + imageUrl="CadHub/inakek2urbreynblzhgt" + /> + + ) +} + +export default { title: 'Components/ImageUploader' } diff --git a/web/src/components/ImageUploader/ImageUploader.test.js b/web/src/components/ImageUploader/ImageUploader.test.js new file mode 100644 index 0000000..1a05ab3 --- /dev/null +++ b/web/src/components/ImageUploader/ImageUploader.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import ImageUploader from './ImageUploader' + +describe('ImageUploader', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/InputText/InputText.js b/web/src/components/InputText/InputText.js new file mode 100644 index 0000000..93826dd --- /dev/null +++ b/web/src/components/InputText/InputText.js @@ -0,0 +1,21 @@ +import { getActiveClasses } from 'get-active-classes' + +const InputText = ({value, isEditable, onChange ,className}) => { + return ( + <> +
+
+ +
+ {value} + + ) +} + +export default InputText diff --git a/web/src/components/InputText/InputText.stories.js b/web/src/components/InputText/InputText.stories.js new file mode 100644 index 0000000..612cc72 --- /dev/null +++ b/web/src/components/InputText/InputText.stories.js @@ -0,0 +1,7 @@ +import InputText from './InputText' + +export const generated = () => { + return +} + +export default { title: 'Components/InputText' } diff --git a/web/src/pages/AboutPage/AboutPage.test.js b/web/src/components/InputText/InputText.test.js similarity index 58% rename from web/src/pages/AboutPage/AboutPage.test.js rename to web/src/components/InputText/InputText.test.js index 6878394..d16c361 100644 --- a/web/src/pages/AboutPage/AboutPage.test.js +++ b/web/src/components/InputText/InputText.test.js @@ -1,11 +1,11 @@ import { render } from '@redwoodjs/testing' -import AboutPage from './AboutPage' +import InputText from './InputText' -describe('AboutPage', () => { +describe('InputText', () => { it('renders successfully', () => { expect(() => { - render() + render() }).not.toThrow() }) }) diff --git a/web/src/components/NewComment/NewComment.js b/web/src/components/NewComment/NewComment.js new file mode 100644 index 0000000..750bba4 --- /dev/null +++ b/web/src/components/NewComment/NewComment.js @@ -0,0 +1,41 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import CommentForm from 'src/components/CommentForm' + +const CREATE_COMMENT_MUTATION = gql` + mutation CreateCommentMutation($input: CreateCommentInput!) { + createComment(input: $input) { + id + } + } +` + +const NewComment = () => { + const { addMessage } = useFlash() + const [createComment, { loading, error }] = useMutation( + CREATE_COMMENT_MUTATION, + { + onCompleted: () => { + navigate(routes.comments()) + addMessage('Comment created.', { classes: 'rw-flash-success' }) + }, + } + ) + + const onSave = (input) => { + createComment({ variables: { input } }) + } + + return ( +
+
+

New Comment

+
+
+ +
+
+ ) +} + +export default NewComment diff --git a/web/src/components/NewPartReaction/NewPartReaction.js b/web/src/components/NewPartReaction/NewPartReaction.js new file mode 100644 index 0000000..07457d2 --- /dev/null +++ b/web/src/components/NewPartReaction/NewPartReaction.js @@ -0,0 +1,41 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import PartReactionForm from 'src/components/PartReactionForm' + +const CREATE_PART_REACTION_MUTATION = gql` + mutation TogglePartReactionMutation($input: TogglePartReactionInput!) { + togglePartReaction(input: $input) { + id + } + } +` + +const NewPartReaction = () => { + const { addMessage } = useFlash() + const [togglePartReaction, { loading, error }] = useMutation( + CREATE_PART_REACTION_MUTATION, + { + onCompleted: () => { + navigate(routes.partReactions()) + addMessage('PartReaction created.', { classes: 'rw-flash-success' }) + }, + } + ) + + const onSave = (input) => { + togglePartReaction({ variables: { input } }) + } + + return ( +
+
+

New PartReaction

+
+
+ +
+
+ ) +} + +export default NewPartReaction diff --git a/web/src/components/NewPost/NewPost.js b/web/src/components/NewPost/NewPost.js deleted file mode 100644 index ff4eec2..0000000 --- a/web/src/components/NewPost/NewPost.js +++ /dev/null @@ -1,38 +0,0 @@ -import { useMutation, useFlash } from '@redwoodjs/web' -import { navigate, routes } from '@redwoodjs/router' -import PostForm from 'src/components/PostForm' - -const CREATE_POST_MUTATION = gql` - mutation CreatePostMutation($input: CreatePostInput!) { - createPost(input: $input) { - id - } - } -` - -const NewPost = () => { - const { addMessage } = useFlash() - const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, { - onCompleted: () => { - navigate(routes.posts()) - addMessage('Post created.', { classes: 'rw-flash-success' }) - }, - }) - - const onSave = (input) => { - createPost({ variables: { input } }) - } - - return ( -
-
-

New Post

-
-
- -
-
- ) -} - -export default NewPost diff --git a/web/src/components/Part/Part.js b/web/src/components/Part/Part.js index b132dbc..4fe2e15 100644 --- a/web/src/components/Part/Part.js +++ b/web/src/components/Part/Part.js @@ -1,23 +1,35 @@ import { useMutation, useFlash } from '@redwoodjs/web' import { Link, routes, navigate } from '@redwoodjs/router' -import { initialize } from 'src/cascade/js/MainPage/CascadeMain' -import { useEffect, useState } from 'react' const DELETE_PART_MUTATION = gql` - mutation DeletePartMutation($id: Int!) { + mutation DeletePartMutation($id: String!) { deletePart(id: $id) { id } } ` -const Part = ({ part, saveCode, loading, error}) => { - const [code, setCode] = useState(part.code) - useEffect(() => { - const sickCallback = (code) => setCode(code) - initialize(sickCallback, part.code) - }, []) - const hasChanges = code !== part.code +const jsonDisplay = (obj) => { + return ( +
+      {JSON.stringify(obj, null, 2)}
+    
+ ) +} + +const timeTag = (datetime) => { + return ( + + ) +} + +const checkboxInputTag = (checked) => { + return +} + +const Part = ({ part }) => { const { addMessage } = useFlash() const [deletePart] = useMutation(DELETE_PART_MUTATION, { onCompleted: () => { @@ -42,6 +54,10 @@ const Part = ({ part, saveCode, loading, error}) => { + + + + @@ -50,59 +66,44 @@ const Part = ({ part, saveCode, loading, error}) => { - {/* + - */} - {/* + + - */} - {/* + + - */} + + + + + + + + +
Id{part.id}
Title {part.title}Description {part.description}
Code {part.code}
Main image {part.mainImage}
Created at {timeTag(part.createdAt)}
Updated at{timeTag(part.updatedAt)}
User id{part.userId}
-
- ) } diff --git a/web/src/components/Part2Cell/Part2Cell.js b/web/src/components/Part2Cell/Part2Cell.js new file mode 100644 index 0000000..a9016c4 --- /dev/null +++ b/web/src/components/Part2Cell/Part2Cell.js @@ -0,0 +1,140 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import { useAuth } from '@redwoodjs/auth' + +import PartProfile from 'src/components/PartProfile' + +export const QUERY = gql` + query FIND_PART_BY_USERNAME_TITLE($userName: String!, $partTitle: String, $currentUserId: String) { + userPart: userName(userName: $userName) { + id + name + userName + bio + image + Part(partTitle: $partTitle) { + id + title + description + code + mainImage + createdAt + updatedAt + userId + Reaction { + emote + } + userReactions: Reaction(userId: $currentUserId) { + emote + } + Comment { + id + text + user { + userName + image + } + } + } + } + } +` + +const UPDATE_PART_MUTATION = gql` + mutation UpdatePartMutation($id: String!, $input: UpdatePartInput!) { + updatePart:updatePart(id: $id, input: $input) { + id + title + user { + id + userName + } + } + } +` +const CREATE_PART_MUTATION = gql` + mutation CreatePartMutation($input: CreatePartInput!) { + createPart(input: $input) { + id + title + user { + id + userName + } + } + } +` +const TOGGLE_REACTION_MUTATION = gql` + mutation ToggleReactionMutation($input: TogglePartReactionInput!) { + togglePartReaction(input: $input){ + id + emote + } + } +` +const CREATE_COMMENT_MUTATION = gql` + mutation CreateCommentMutation($input: CreateCommentInput!) { + createComment(input: $input) { + id + text + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) =>
Error: {error.message}
+ +export const Success = ({ userPart, variables: {isEditable}, refetch}) => { + const { currentUser } = useAuth() + const { addMessage } = useFlash() + const [updateUser, { loading, error }] = useMutation(UPDATE_PART_MUTATION, { + onCompleted: ({updatePart}) => { + navigate(routes.part2({userName: updatePart.user.userName, partTitle: updatePart.title})) + addMessage('Part updated.', { classes: 'rw-flash-success' }) + }, + }) + const [createUser] = useMutation(CREATE_PART_MUTATION, { + onCompleted: ({createPart}) => { + navigate(routes.part2({userName: createPart?.user?.userName, partTitle: createPart?.title})) + addMessage('Part Created.', { classes: 'rw-flash-success' }) + }, + }) + const onSave = (id, input) => { + if(!id) { + createUser({ variables: { input } }) + return + } + updateUser({ variables: { id, input } }) + } + + const [toggleReaction] = useMutation(TOGGLE_REACTION_MUTATION, { + onCompleted: () => refetch() + }) + const onReaction = (emote) => toggleReaction({variables: {input: { + emote, + userId: currentUser.sub, + partId: userPart?.Part?.id, + }}}) + + const [createComment] = useMutation(CREATE_COMMENT_MUTATION, { + onCompleted: () => refetch() + }) + const onComment = (text) => createComment({variables: {input: { + text, + userId: currentUser.sub, + partId: userPart?.Part?.id, + }}}) + + return +} diff --git a/web/src/components/BlogPostCell/BlogPostCell.mock.js b/web/src/components/Part2Cell/Part2Cell.mock.js similarity index 88% rename from web/src/components/BlogPostCell/BlogPostCell.mock.js rename to web/src/components/Part2Cell/Part2Cell.mock.js index c689622..3dde5f5 100644 --- a/web/src/components/BlogPostCell/BlogPostCell.mock.js +++ b/web/src/components/Part2Cell/Part2Cell.mock.js @@ -1,6 +1,6 @@ // Define your own mock data here: export const standard = (/* vars, { ctx, req } */) => ({ - blogPost: { + part2: { id: 42, }, }) diff --git a/web/src/components/BlogPostCell/BlogPostCell.stories.js b/web/src/components/Part2Cell/Part2Cell.stories.js similarity index 67% rename from web/src/components/BlogPostCell/BlogPostCell.stories.js rename to web/src/components/Part2Cell/Part2Cell.stories.js index 597ce5f..8ddfafb 100644 --- a/web/src/components/BlogPostCell/BlogPostCell.stories.js +++ b/web/src/components/Part2Cell/Part2Cell.stories.js @@ -1,5 +1,5 @@ -import { Loading, Empty, Failure, Success } from './BlogPostCell' -import { standard } from './BlogPostCell.mock' +import { Loading, Empty, Failure, Success } from './Part2Cell' +import { standard } from './Part2Cell.mock' export const loading = () => { return Loading ? : null @@ -17,4 +17,4 @@ export const success = () => { return Success ? : null } -export default { title: 'Cells/BlogPostCell' } +export default { title: 'Cells/Part2Cell' } diff --git a/web/src/components/BlogPostCell/BlogPostCell.test.js b/web/src/components/Part2Cell/Part2Cell.test.js similarity index 76% rename from web/src/components/BlogPostCell/BlogPostCell.test.js rename to web/src/components/Part2Cell/Part2Cell.test.js index 61adcd6..231c8c6 100644 --- a/web/src/components/BlogPostCell/BlogPostCell.test.js +++ b/web/src/components/Part2Cell/Part2Cell.test.js @@ -1,8 +1,8 @@ import { render, screen } from '@redwoodjs/testing' -import { Loading, Empty, Failure, Success } from './BlogPostCell' -import { standard } from './BlogPostCell.mock' +import { Loading, Empty, Failure, Success } from './Part2Cell' +import { standard } from './Part2Cell.mock' -describe('BlogPostCell', () => { +describe('Part2Cell', () => { test('Loading renders successfully', () => { render() // Use screen.debug() to see output @@ -20,7 +20,7 @@ describe('BlogPostCell', () => { }) test('Success renders successfully', async () => { - render() + render() expect(screen.getByText(/42/i)).toBeInTheDocument() }) }) diff --git a/web/src/components/PartCell/PartCell.js b/web/src/components/PartCell/PartCell.js index 22044f0..3c69b24 100644 --- a/web/src/components/PartCell/PartCell.js +++ b/web/src/components/PartCell/PartCell.js @@ -1,32 +1,24 @@ +import Part from 'src/components/Part' -import {QUERY as reExportQuery} from 'src/components/EditPartCell' -import Editor from "rich-markdown-editor"; - -export const QUERY = reExportQuery +export const QUERY = gql` + query FIND_PART_BY_ID($id: String!) { + part: part(id: $id) { + id + title + description + code + mainImage + createdAt + updatedAt + userId + } + } +` export const Loading = () =>
Loading...
-export const Empty = () =>
Empty
- -export const Failure = ({ error }) =>
Error: {error.message}
+export const Empty = () =>
Part not found
export const Success = ({ part }) => { - console.log(part) - return ( - <> -
-
- -
-
-

{part.title}

- -
-
- - ) + return } diff --git a/web/src/components/PartForm/PartForm.js b/web/src/components/PartForm/PartForm.js index daecc16..3fc1009 100644 --- a/web/src/components/PartForm/PartForm.js +++ b/web/src/components/PartForm/PartForm.js @@ -6,36 +6,14 @@ import { TextField, Submit, } from '@redwoodjs/forms' -import { useState } from 'react'; -import { navigate, routes } from '@redwoodjs/router' -import { useFlash } from '@redwoodjs/web' -import ImageUploader from './ImageUploader.js' - - -import Editor from "rich-markdown-editor"; const PartForm = (props) => { - const { addMessage } = useFlash() - const [description, setDescription] = useState(props?.part?.description) - const [imageUrl, setImageUrl] = useState(props?.part?.mainImage) - const onSubmit = async (data, e) => { - - await props.onSave({ - ...data, - description, - mainImage: imageUrl - }, props?.part?.id) - const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde - if(shouldOpenIde) { - navigate(routes.partIde({id: props?.part?.id})) - } else { - navigate(routes.part({id: props?.part?.id})) - } - addMessage('Part updated.', { classes: 'rw-flash-success' }) + const onSubmit = (data) => { + props.onSave(data, props?.part?.id) } return ( -
+
{
diff --git a/web/src/components/PartProfile/PartProfile.js b/web/src/components/PartProfile/PartProfile.js new file mode 100644 index 0000000..9a7facc --- /dev/null +++ b/web/src/components/PartProfile/PartProfile.js @@ -0,0 +1,173 @@ +import {useState, useEffect} from 'react' +import { useAuth } from '@redwoodjs/auth' +import { Link, navigate, routes } from '@redwoodjs/router' +import Editor from "rich-markdown-editor"; + +import ImageUploader from 'src/components/ImageUploader' +import Breadcrumb from 'src/components/Breadcrumb' +import EmojiReaction from 'src/components/EmojiReaction' +import Button from 'src/components/Button' +import { countEmotes } from 'src/helpers/emote' +import { getActiveClasses } from 'get-active-classes'; + +const PartProfile = ({ + userPart, + isEditable, + onSave, + loading, + error, + onReaction, + onComment, + }) => { + const [comment, setComment] = useState('') + const { currentUser } = useAuth() + const canEdit = currentUser?.sub === userPart.id + const part = userPart?.Part + const emotes = countEmotes(part?.Reaction) + const userEmotes = part?.userReactions.map(({emote}) => emote) + useEffect(() => {isEditable && + !canEdit && + navigate(routes.part2({userName: userPart.userName, partTitle: part.title}))}, + [currentUser]) + const [input, setInput] = useState({ + title: part?.title, + mainImage: part?.mainImage, + description: part?.description, + userId: userPart?.id, + }) + const setProperty = (property, value) => setInput({ + ...input, + [property]: value, + }) + const onTitleChange = ({target}) => setProperty('title', target.value.replace(/([^a-zA-Z\d_:])/g, '-')) + const onDescriptionChange = (description) => setProperty('description', description()) + const onImageUpload = ({cloudinaryPublicId}) => setProperty('mainImage', cloudinaryPublicId) + const onEditSaveClick = () => { + if (isEditable) { + input.title && onSave(part?.id, input) + return + } + navigate(routes.editPart2({userName: userPart.userName, partTitle: part.title})) + } + return ( + <> +
+ + {/* Side column */} + + + {/* main project center column */} +
+ + { !!(input?.mainImage || isEditable) && } +
+ +
+ + + + {/* comments */} + { !isEditable && <> +
+

Comments

+ +
    + {part?.Comment.map(({text, user, id}) => ( +
  • +
    + {}} + aspectRatio={1} + imageUrl={user?.image} + width={50} + /> +
    +
    +
    + {user.userName} +
    +
    + {text} +
    +
    +
  • + ))} +
+ {currentUser && <> +
+