Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit 163ebe8

Browse files
authored
feat(story📗): add first draft (#2)
chore(prisma): setup postgresql https://redwoodjs.com/docs/local-postgres-setup#local-test-db https://redwoodjs.com/docs/tutorial/chapter4/deployment#change-database-provider hand edited migrations # Conflicts: # README.md # api/db/migrations/20220311022654_create_post/migration.sql # api/db/migrations/20220319174614_create_contact/migration.sql # api/db/migrations/20220323132434_create_user/migration.sql # api/db/migrations/migration_lock.toml Revert "test(include): storybook static" Manually deleted web/public/storybook/main.d4d655ec.iframe.bundle.js This reverts commit b198e8213ff366f264f4758274ad46d45ccdf77f. # Conflicts: # web/public/storybook/iframe.html # web/public/storybook/main.d4d655ec.iframe.bundle.js.LICENSE.txt # web/public/storybook/static/css/main.b53ed70f.css chore: gitignore storybook build build(netlify): add sb build command build(netlify): add `--no-data-migrate` https://redwoodjs.com/docs/cli-commands#deploy-netlify build(netlify): add ` --no-prisma` https://redwoodjs.com/docs/cli-commands#deploy-netlify
1 parent 511f892 commit 163ebe8

File tree

89 files changed

+3645
-109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+3645
-109
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ api/types/graphql.d.ts
1717
!.yarn/plugins
1818
!.yarn/releases
1919
!.yarn/sdks
20-
!.yarn/versions
20+
!.yarn/versions
21+
web/public/storybook

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,23 @@ yarn redwood dev
7575
```
7676

7777
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`.
78+
79+
```terminal
80+
yarn rw prisma studio
81+
```
82+
83+
A new browser should open to http://localhost:5555 and now you can view and manipulate data in the database directly!
84+
85+
86+
87+
---
88+
89+
https://github.com/prisma/prisma/issues/9553
90+
https://github.com/redwoodjs/redwood/issues/4605
91+
92+
CORRECT
93+
node_modules/.prisma
94+
WRONG
95+
api/node_modules/.prisma
96+
97+
---
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- CreateTable
2+
CREATE TABLE "Post" (
3+
"id" SERIAL NOT NULL,
4+
"title" TEXT NOT NULL,
5+
"body" TEXT NOT NULL,
6+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
8+
CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
9+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- CreateTable
2+
CREATE TABLE "Contact" (
3+
"id" SERIAL NOT NULL,
4+
"name" TEXT NOT NULL,
5+
"email" TEXT NOT NULL,
6+
"message" TEXT NOT NULL,
7+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
9+
CONSTRAINT "Contact_pkey" PRIMARY KEY ("id")
10+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- CreateTable
2+
CREATE TABLE "User" (
3+
"id" SERIAL NOT NULL,
4+
"name" TEXT,
5+
"email" TEXT NOT NULL,
6+
"hashedPassword" TEXT NOT NULL,
7+
"salt" TEXT NOT NULL,
8+
"resetToken" TEXT,
9+
"resetTokenExpiresAt" TIMESTAMP(3),
10+
11+
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
12+
);
13+
14+
-- CreateIndex
15+
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

api/db/migrations/migration_lock.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Please do not edit this file manually
2+
# It should be added in your version-control system (i.e. Git)
3+
provider = "postgresql"

api/db/schema.prisma

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
datasource db {
2-
provider = "sqlite"
2+
provider = "postgresql"
33
url = env("DATABASE_URL")
44
}
55

@@ -8,11 +8,27 @@ generator client {
88
binaryTargets = "native"
99
}
1010

11-
// Define your own datamodels here and run `yarn redwood prisma migrate dev`
12-
// to create migrations for them and apply to your dev DB.
13-
// TODO: Please remove the following example:
14-
model UserExample {
15-
id Int @id @default(autoincrement())
16-
email String @unique
17-
name String?
11+
model Post {
12+
id Int @id @default(autoincrement())
13+
title String
14+
body String
15+
createdAt DateTime @default(now())
16+
}
17+
18+
model Contact {
19+
id Int @id @default(autoincrement())
20+
name String
21+
email String
22+
message String
23+
createdAt DateTime @default(now())
24+
}
25+
26+
model User {
27+
id Int @id @default(autoincrement())
28+
name String?
29+
email String @unique
30+
hashedPassword String
31+
salt String
32+
resetToken String?
33+
resetTokenExpiresAt DateTime?
1834
}

api/src/directives/requireAuth/requireAuth.test.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ describe('requireAuth directive', () => {
88
expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth')
99
})
1010

11-
it('requireAuth has stub implementation. Should not throw when current user', () => {
12-
// If you want to set values in context, pass it through e.g.
13-
// mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }})
11+
it('requireAuth should throw when current user is unauthenticated', () => {
1412
const mockExecution = mockRedwoodDirective(requireAuth, { context: {} })
1513

14+
expect(mockExecution).toThrowErrorMatchingInlineSnapshot(
15+
`"You don't have permission to do that."`
16+
)
17+
})
18+
19+
it('requireAuth should not throw when current user is authenticated', () => {
20+
// If you want to set values in context, pass it through e.g.
21+
const mockExecution = mockRedwoodDirective(requireAuth, {
22+
context: { currentUser: { id: 1, name: 'Lebron McGretzky' } },
23+
})
24+
1625
expect(mockExecution).not.toThrowError()
1726
})
1827
})

api/src/functions/auth.js

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { db } from 'src/lib/db'
2+
import { DbAuthHandler } from '@redwoodjs/api'
3+
4+
export const handler = async (event, context) => {
5+
const forgotPasswordOptions = {
6+
// handler() is invoked after verifying that a user was found with the given
7+
// username. This is where you can send the user an email with a link to
8+
// reset their password. With the default dbAuth routes and field names, the
9+
// URL to reset the password will be:
10+
//
11+
// https://example.com/reset-password?resetToken=${user.resetToken}
12+
//
13+
// Whatever is returned from this function will be returned from
14+
// the `forgotPassword()` function that is destructured from `useAuth()`
15+
// You could use this return value to, for example, show the email
16+
// address in a toast message so the user will know it worked and where
17+
// to look for the email.
18+
handler: (user) => {
19+
return user
20+
},
21+
22+
// How long the resetToken is valid for, in seconds (default is 24 hours)
23+
expires: 60 * 60 * 24,
24+
25+
errors: {
26+
// for security reasons you may want to be vague here rather than expose
27+
// the fact that the email address wasn't found (prevents fishing for
28+
// valid email addresses)
29+
usernameNotFound: 'Username not found',
30+
// if the user somehow gets around client validation
31+
usernameRequired: 'Username is required',
32+
},
33+
}
34+
35+
const loginOptions = {
36+
// handler() is called after finding the user that matches the
37+
// username/password provided at login, but before actually considering them
38+
// logged in. The `user` argument will be the user in the database that
39+
// matched the username/password.
40+
//
41+
// If you want to allow this user to log in simply return the user.
42+
//
43+
// If you want to prevent someone logging in for another reason (maybe they
44+
// didn't validate their email yet), throw an error and it will be returned
45+
// by the `logIn()` function from `useAuth()` in the form of:
46+
// `{ message: 'Error message' }`
47+
handler: (user) => {
48+
return user
49+
},
50+
51+
errors: {
52+
usernameOrPasswordMissing: 'Both username and password are required',
53+
usernameNotFound: 'Username ${username} not found',
54+
// For security reasons you may want to make this the same as the
55+
// usernameNotFound error so that a malicious user can't use the error
56+
// to narrow down if it's the username or password that's incorrect
57+
incorrectPassword: 'Incorrect password for ${username}',
58+
},
59+
60+
// How long a user will remain logged in, in seconds
61+
expires: 60 * 60 * 24 * 365 * 10,
62+
}
63+
64+
const resetPasswordOptions = {
65+
// handler() is invoked after the password has been successfully updated in
66+
// the database. Returning anything truthy will automatically logs the user
67+
// in. Return `false` otherwise, and in the Reset Password page redirect the
68+
// user to the login page.
69+
handler: (user) => {
70+
return user
71+
},
72+
73+
// If `false` then the new password MUST be different than the current one
74+
allowReusedPassword: true,
75+
76+
errors: {
77+
// the resetToken is valid, but expired
78+
resetTokenExpired: 'resetToken is expired',
79+
// no user was found with the given resetToken
80+
resetTokenInvalid: 'resetToken is invalid',
81+
// the resetToken was not present in the URL
82+
resetTokenRequired: 'resetToken is required',
83+
// new password is the same as the old password (apparently they did not forget it)
84+
reusedPassword: 'Must choose a new password',
85+
},
86+
}
87+
88+
const signupOptions = {
89+
// Whatever you want to happen to your data on new user signup. Redwood will
90+
// check for duplicate usernames before calling this handler. At a minimum
91+
// you need to save the `username`, `hashedPassword` and `salt` to your
92+
// user table. `userAttributes` contains any additional object members that
93+
// were included in the object given to the `signUp()` function you got
94+
// from `useAuth()`.
95+
//
96+
// If you want the user to be immediately logged in, return the user that
97+
// was created.
98+
//
99+
// If this handler throws an error, it will be returned by the `signUp()`
100+
// function in the form of: `{ error: 'Error message' }`.
101+
//
102+
// If this returns anything else, it will be returned by the
103+
// `signUp()` function in the form of: `{ message: 'String here' }`.
104+
// https://redwoodjs.com/docs/tutorial/chapter4/deployment#the-signup-problem
105+
handler: () => false,
106+
// handler: ({ username, hashedPassword, salt, _userAttributes }) => {
107+
// return db.user.create({
108+
// data: {
109+
// email: username,
110+
// hashedPassword: hashedPassword,
111+
// salt: salt,
112+
// // name: userAttributes.name
113+
// },
114+
// })
115+
// },
116+
117+
errors: {
118+
// `field` will be either "username" or "password"
119+
fieldMissing: '${field} is required',
120+
usernameTaken: 'Username `${username}` already in use',
121+
},
122+
}
123+
124+
const authHandler = new DbAuthHandler(event, context, {
125+
// Provide prisma db client
126+
db: db,
127+
128+
// The name of the property you'd call on `db` to access your user table.
129+
// ie. if your Prisma model is named `User` this value would be `user`, as in `db.user`
130+
authModelAccessor: 'user',
131+
132+
// A map of what dbAuth calls a field to what your database calls it.
133+
// `id` is whatever column you use to uniquely identify a user (probably
134+
// something like `id` or `userId` or even `email`)
135+
authFields: {
136+
id: 'id',
137+
username: 'email',
138+
hashedPassword: 'hashedPassword',
139+
salt: 'salt',
140+
resetToken: 'resetToken',
141+
resetTokenExpiresAt: 'resetTokenExpiresAt',
142+
},
143+
144+
// Specifies attributes on the cookie that dbAuth sets in order to remember
145+
// who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies
146+
cookie: {
147+
HttpOnly: true,
148+
Path: '/',
149+
SameSite: 'Strict',
150+
Secure: true,
151+
152+
// If you need to allow other domains (besides the api side) access to
153+
// the dbAuth session cookie:
154+
// Domain: 'example.com',
155+
},
156+
157+
forgotPassword: forgotPasswordOptions,
158+
login: loginOptions,
159+
resetPassword: resetPasswordOptions,
160+
signup: signupOptions,
161+
})
162+
163+
return await authHandler.invoke()
164+
}

api/src/functions/graphql.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import directives from 'src/directives/**/*.{js,ts}'
44
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
55
import services from 'src/services/**/*.{js,ts}'
66

7+
import { getCurrentUser } from 'src/lib/auth'
8+
79
import { db } from 'src/lib/db'
810
import { logger } from 'src/lib/logger'
911

1012
export const handler = createGraphQLHandler({
13+
getCurrentUser,
1114
loggerConfig: { logger, options: {} },
1215
directives,
1316
sdls,

api/src/graphql/contacts.sdl.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const schema = gql`
2+
type Contact {
3+
id: Int!
4+
name: String!
5+
email: String!
6+
message: String!
7+
createdAt: DateTime!
8+
}
9+
10+
type Query {
11+
contacts: [Contact!]! @requireAuth
12+
contact(id: Int!): Contact @requireAuth
13+
}
14+
15+
input CreateContactInput {
16+
name: String!
17+
email: String!
18+
message: String!
19+
}
20+
21+
input UpdateContactInput {
22+
name: String
23+
email: String
24+
message: String
25+
}
26+
27+
type Mutation {
28+
createContact(input: CreateContactInput!): Contact! @skipAuth
29+
updateContact(id: Int!, input: UpdateContactInput!): Contact! @requireAuth
30+
deleteContact(id: Int!): Contact! @requireAuth
31+
}
32+
`

api/src/graphql/posts.sdl.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const schema = gql`
2+
type Post {
3+
id: Int!
4+
title: String!
5+
body: String!
6+
createdAt: DateTime!
7+
}
8+
9+
type Query {
10+
posts: [Post!]! @skipAuth
11+
post(id: Int!): Post @skipAuth
12+
}
13+
14+
input CreatePostInput {
15+
title: String!
16+
body: String!
17+
}
18+
19+
input UpdatePostInput {
20+
title: String
21+
body: String
22+
}
23+
24+
type Mutation {
25+
createPost(input: CreatePostInput!): Post! @requireAuth
26+
updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth
27+
deletePost(id: Int!): Post! @requireAuth
28+
}
29+
`

0 commit comments

Comments
 (0)