diff --git a/api/db/migrations/20220426045928_added_comments/migration.sql b/api/db/migrations/20220426045928_added_comments/migration.sql new file mode 100644 index 0000000..93f7f07 --- /dev/null +++ b/api/db/migrations/20220426045928_added_comments/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "Comment" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "body" TEXT NOT NULL, + "postId" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); diff --git a/api/db/schema.prisma b/api/db/schema.prisma index 24e6494..3735bff 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -9,10 +9,11 @@ generator client { } model Post { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) title String body String - createdAt DateTime @default(now()) + comments Comment[] + createdAt DateTime @default(now()) } model Contact { @@ -24,11 +25,20 @@ model Contact { } model User { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) name String? - email String @unique + email String @unique hashedPassword String salt String resetToken String? resetTokenExpiresAt DateTime? } + +model Comment { + id Int @id @default(autoincrement()) + name String + body String + post Post @relation(fields: [postId], references: [id]) + postId Int + createdAt DateTime @default(now()) +} diff --git a/api/src/graphql/comments.sdl.js b/api/src/graphql/comments.sdl.js new file mode 100644 index 0000000..da485f8 --- /dev/null +++ b/api/src/graphql/comments.sdl.js @@ -0,0 +1,31 @@ +export const schema = gql` + type Comment { + id: Int! + name: String! + body: String! + post: Post! + postId: Int! + createdAt: DateTime! + } + + type Query { + comments: [Comment!]! @skipAuth + } + + input CreateCommentInput { + name: String! + body: String! + postId: Int! + } + + input UpdateCommentInput { + name: String + body: String + postId: Int + } + + type Mutation { + createComment(input: CreateCommentInput!): Comment! @skipAuth + deleteComment(id: Int!): Comment! @requireAuth + } +` diff --git a/api/src/services/comments/comments.js b/api/src/services/comments/comments.js new file mode 100644 index 0000000..c55034e --- /dev/null +++ b/api/src/services/comments/comments.js @@ -0,0 +1,28 @@ +import { db } from 'src/lib/db' + +export const comments = () => { + return db.comment.findMany() +} + +export const comment = ({ id }) => { + return db.comment.findUnique({ + where: { id }, + }) +} + +export const Comment = { + post: (_obj, { root }) => + db.comment.findUnique({ where: { id: root.id } }).post(), +} + +export const createComment = ({ input }) => { + return db.comment.create({ + data: input, + }) +} + +export const deleteComment = ({ id }) => { + return db.comment.delete({ + where: { id }, + }) +} diff --git a/api/src/services/comments/comments.scenarios.js b/api/src/services/comments/comments.scenarios.js new file mode 100644 index 0000000..2f15b38 --- /dev/null +++ b/api/src/services/comments/comments.scenarios.js @@ -0,0 +1,42 @@ +import { defineScenario } from '@redwoodjs/testing/dist/api' + +export const standard = defineScenario({ + comment: { + one: { + data: { + name: 'Jane Doe', + body: 'I like trees.', + post: { + create: { + title: 'Redwood Leaves', + body: 'The quick brown fox jumped over the lazy dog.', + }, + }, + }, + }, + + two: { + data: { + name: 'John Doe', + body: 'Hug a tree today', + post: { + create: { + title: 'Root Systems', + body: 'The five boxing wizards jump quickly', + }, + }, + }, + }, + }, +}) + +export const postOnly = defineScenario({ + post: { + bark: { + data: { + title: 'Bark', + body: "A tree's bark is worse than its bite", + }, + }, + }, +}) diff --git a/api/src/services/comments/comments.test.js b/api/src/services/comments/comments.test.js new file mode 100644 index 0000000..8bd5f0a --- /dev/null +++ b/api/src/services/comments/comments.test.js @@ -0,0 +1,30 @@ +import { comments, createComment } from './comments' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-services +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('comments', () => { + scenario('returns all comments', async (scenario) => { + const result = await comments() + + expect(result.length).toEqual(Object.keys(scenario.comment).length) + }) + + scenario('postOnly', 'creates a new comment', async (scenario) => { + const comment = await createComment({ + input: { + name: 'Billy Bob', + body: 'What is your favorite tree bark?', + postId: scenario.post.bark.id, + }, + }) + + expect(comment.name).toEqual('Billy Bob') + expect(comment.body).toEqual('What is your favorite tree bark?') + expect(comment.postId).toEqual(scenario.post.bark.id) + expect(comment.createdAt).not.toEqual(null) + }) +}) diff --git a/web/src/components/Article/Article.js b/web/src/components/Article/Article.js index 092f957..43e2c23 100644 --- a/web/src/components/Article/Article.js +++ b/web/src/components/Article/Article.js @@ -1,4 +1,5 @@ import { Link, routes } from '@redwoodjs/router' +import CommentsCell from 'src/components/CommentsCell' const truncate = (text, length) => { return text.substring(0, length) + '...' @@ -15,6 +16,11 @@ const Article = ({ article, summary = false }) => {
{comment.body}
diff --git a/web/src/components/Comment/Comment.test.js b/web/src/components/Comment/Comment.test.js index a1bc6f8..f6069f4 100644 --- a/web/src/components/Comment/Comment.test.js +++ b/web/src/components/Comment/Comment.test.js @@ -1,4 +1,4 @@ -import { render } from '@redwoodjs/testing/web' +import { render, screen } from '@redwoodjs/testing/web' import Comment from './Comment' @@ -7,8 +7,17 @@ import Comment from './Comment' describe('Comment', () => { it('renders successfully', () => { - expect(() => { - render(