From 2b763f23d8e1101cf2fbb7e5a2869ac336a73fdd Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Sun, 13 Dec 2020 12:25:54 +1100 Subject: [PATCH] issue-159 Add delete button to part profile (for part owner) schema had to be update to add a deleted boolean to part. Easier than setting up cascading deletes for comments and reactions. resolves #159 --- .../README.md | 63 +++++++++++++++ .../schema.prisma | 81 +++++++++++++++++++ .../steps.json | 37 +++++++++ api/prisma/migrations/migrate.lock | 3 +- api/prisma/schema.prisma | 1 + api/src/services/parts/parts.js | 10 ++- web/src/components/Button/Button.js | 11 ++- .../components/ConfirmDialog/ConfirmDialog.js | 38 +++++++++ .../ConfirmDialog/ConfirmDialog.stories.js | 7 ++ .../ConfirmDialog/ConfirmDialog.test.js | 11 +++ web/src/components/PartCell/PartCell.js | 23 ++++++ web/src/components/PartProfile/PartProfile.js | 40 ++++++--- web/src/components/Svg/Svg.js | 15 ++++ web/src/layouts/MainLayout/MainLayout.js | 7 +- 14 files changed, 326 insertions(+), 21 deletions(-) create mode 100644 api/prisma/migrations/20201213004819-add-delete-on-part/README.md create mode 100644 api/prisma/migrations/20201213004819-add-delete-on-part/schema.prisma create mode 100644 api/prisma/migrations/20201213004819-add-delete-on-part/steps.json create mode 100644 web/src/components/ConfirmDialog/ConfirmDialog.js create mode 100644 web/src/components/ConfirmDialog/ConfirmDialog.stories.js create mode 100644 web/src/components/ConfirmDialog/ConfirmDialog.test.js diff --git a/api/prisma/migrations/20201213004819-add-delete-on-part/README.md b/api/prisma/migrations/20201213004819-add-delete-on-part/README.md new file mode 100644 index 0000000..f3bf834 --- /dev/null +++ b/api/prisma/migrations/20201213004819-add-delete-on-part/README.md @@ -0,0 +1,63 @@ +# Migration `20201213004819-add-delete-on-part` + +This migration has been generated by Kurt Hutten at 12/13/2020, 11:48:20 AM. +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" 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, + "deleted" BOOLEAN NOT NULL DEFAULT false, + + FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE, +PRIMARY KEY ("id") +); +INSERT INTO "new_Part" ("id", "title", "description", "code", "mainImage", "createdAt", "updatedAt", "userId") SELECT "id", "title", "description", "code", "mainImage", "createdAt", "updatedAt", "userId" FROM "Part"; +DROP TABLE "Part"; +ALTER TABLE "new_Part" RENAME TO "Part"; +CREATE UNIQUE INDEX "Part.title_userId_unique" ON "Part"("title", "userId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20201105184423-add-name-to-user..20201213004819-add-delete-on-part +--- datamodel.dml ++++ datamodel.dml +@@ -1,12 +1,12 @@ + datasource DS { + provider = ["sqlite", "postgresql"] +- url = "***" ++ url = "***" + } + generator client { + provider = "prisma-client-js" +- binaryTargets = "native" ++ binaryTargets = ["native", "rhel-openssl-1.0.x"] + } + // sqlLight does not suport enums so we can't use enums until we set up postgresql in dev mode + // enum Role { +@@ -47,8 +47,9 @@ + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + userId String ++ deleted Boolean @default(false) + Comment Comment[] + Reaction PartReaction[] + @@unique([title, userId]) +``` + + diff --git a/api/prisma/migrations/20201213004819-add-delete-on-part/schema.prisma b/api/prisma/migrations/20201213004819-add-delete-on-part/schema.prisma new file mode 100644 index 0000000..f5701d4 --- /dev/null +++ b/api/prisma/migrations/20201213004819-add-delete-on-part/schema.prisma @@ -0,0 +1,81 @@ +datasource DS { + provider = ["sqlite", "postgresql"] + url = "***" +} + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "rhel-openssl-1.0.x"] +} + +// 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 + deleted Boolean @default(false) + + 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/20201213004819-add-delete-on-part/steps.json b/api/prisma/migrations/20201213004819-add-delete-on-part/steps.json new file mode 100644 index 0000000..1dcd082 --- /dev/null +++ b/api/prisma/migrations/20201213004819-add-delete-on-part/steps.json @@ -0,0 +1,37 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "Part", + "field": "deleted", + "type": "Boolean", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Part", + "field": "deleted" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Part", + "field": "deleted" + }, + "directive": "default" + }, + "argument": "", + "value": "false" + } + ] +} \ No newline at end of file diff --git a/api/prisma/migrations/migrate.lock b/api/prisma/migrations/migrate.lock index 0f07957..d4f2e8e 100644 --- a/api/prisma/migrations/migrate.lock +++ b/api/prisma/migrations/migrate.lock @@ -1,4 +1,5 @@ # Prisma Migrate lockfile v1 20201101183848-db-init -20201105184423-add-name-to-user \ No newline at end of file +20201105184423-add-name-to-user +20201213004819-add-delete-on-part \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 2645a77..13f644a 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -48,6 +48,7 @@ model Part { updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) userId String + deleted Boolean @default(false) Comment Comment[] Reaction PartReaction[] diff --git a/api/src/services/parts/parts.js b/api/src/services/parts/parts.js index 230de93..e682f3c 100644 --- a/api/src/services/parts/parts.js +++ b/api/src/services/parts/parts.js @@ -8,7 +8,7 @@ import { requireAuth } from 'src/lib/auth' import { requireOwnership } from 'src/lib/owner' export const parts = () => { - return db.part.findMany() + return db.part.findMany({ where: { deleted: false } }) } export const part = ({ id }) => { @@ -70,9 +70,13 @@ export const updatePart = async ({ id, input }) => { }) } -export const deletePart = ({ id }) => { +export const deletePart = async ({ id }) => { requireAuth() - return db.part.delete({ + await requireOwnership({ partId: id }) + return db.part.update({ + data: { + deleted: true, + }, where: { id }, }) } diff --git a/web/src/components/Button/Button.js b/web/src/components/Button/Button.js index 28b1056..e8d1939 100644 --- a/web/src/components/Button/Button.js +++ b/web/src/components/Button/Button.js @@ -8,18 +8,23 @@ const Button = ({ className, shouldAnimateHover, disabled, + type, }) => { return ( + + + + + + ) +} + +export default ConfirmDialog diff --git a/web/src/components/ConfirmDialog/ConfirmDialog.stories.js b/web/src/components/ConfirmDialog/ConfirmDialog.stories.js new file mode 100644 index 0000000..16a3919 --- /dev/null +++ b/web/src/components/ConfirmDialog/ConfirmDialog.stories.js @@ -0,0 +1,7 @@ +import ConfirmDialog from './ConfirmDialog' + +export const generated = () => { + return +} + +export default { title: 'Components/ConfirmDialog' } diff --git a/web/src/components/ConfirmDialog/ConfirmDialog.test.js b/web/src/components/ConfirmDialog/ConfirmDialog.test.js new file mode 100644 index 0000000..1357ccd --- /dev/null +++ b/web/src/components/ConfirmDialog/ConfirmDialog.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import ConfirmDialog from './ConfirmDialog' + +describe('ConfirmDialog', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/PartCell/PartCell.js b/web/src/components/PartCell/PartCell.js index 6789090..a35ccd7 100644 --- a/web/src/components/PartCell/PartCell.js +++ b/web/src/components/PartCell/PartCell.js @@ -84,6 +84,18 @@ const CREATE_COMMENT_MUTATION = gql` } } ` +const DELETE_PART_MUTATION = gql` + mutation DeletePartMutation($id: String!) { + deletePart(id: $id) { + id + title + user { + id + userName + } + } + } +` export const Loading = () =>
Loading...
@@ -123,6 +135,16 @@ export const Success = ({ userPart, variables: { isEditable }, refetch }) => { } updateUser({ variables: { id, input } }) } + const [deletePart] = useMutation(DELETE_PART_MUTATION, { + onCompleted: ({ deletePart }) => { + navigate(routes.home()) + addMessage('Part deleted.', { classes: 'rw-flash-success' }) + }, + }) + + const onDelete = () => { + userPart?.Part?.id && deletePart({ variables: { id: userPart?.Part?.id } }) + } const [toggleReaction] = useMutation(TOGGLE_REACTION_MUTATION, { onCompleted: () => refetch(), @@ -156,6 +178,7 @@ export const Success = ({ userPart, variables: { isEditable }, refetch }) => { { const [comment, setComment] = useState('') + const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false) const { currentUser } = useAuth() const canEdit = currentUser?.sub === userPart.id const part = userPart?.Part - console.log(part) const emotes = countEmotes(part?.Reaction) const userEmotes = part?.userReactions.map(({ emote }) => emote) useEffect(() => { @@ -100,7 +102,7 @@ const PartProfile = ({ })} > {canEdit && ( - + <> + + + )} + {/* gray overlay */} {isEditable && (
)} @@ -216,6 +230,12 @@ const PartProfile = ({ )}
+ setIsConfirmDialogOpen(false)} + onConfirm={onDelete} + message="Are you sure you want to delete? This action cannot be undone." + /> ) } diff --git a/web/src/components/Svg/Svg.js b/web/src/components/Svg/Svg.js index a655d3a..0629ae7 100644 --- a/web/src/components/Svg/Svg.js +++ b/web/src/components/Svg/Svg.js @@ -307,6 +307,21 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { /> ), + trash: ( + + + + ), } return
{svgs[name]}
diff --git a/web/src/layouts/MainLayout/MainLayout.js b/web/src/layouts/MainLayout/MainLayout.js index e9e3cbe..5444ea6 100644 --- a/web/src/layouts/MainLayout/MainLayout.js +++ b/web/src/layouts/MainLayout/MainLayout.js @@ -91,7 +91,6 @@ const MainLayout = ({ children }) => { useEffect(() => { const [key, token] = hash.slice(1).split('=') if (key === 'confirmation_token') { - console.log('confirming with', token) client .confirm(token, true) .then(() => { @@ -209,7 +208,7 @@ const MainLayout = ({ children }) => { horizontal: 'right', }} > -
+

Hello {data?.user?.name} @@ -217,8 +216,8 @@ const MainLayout = ({ children }) => {

- -
Edit Profile
+ +
Your Profile
Logout