Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion apps/backend/src/envelopPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import {
} from '@envelop/generic-auth'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { User } from '@prisma/client'
import { EnumValueNode } from 'graphql'
import { TokenExpiredError } from 'jsonwebtoken'

import { Role } from './api/graphql/generated/graphql'
import resolvers from './api/graphql/resolvers/resolvers'
import { schema } from './api/graphql/typeDefs'
import { createContext, GraphqlServerContext, prisma } from './context'
Expand Down Expand Up @@ -48,11 +50,31 @@ const resolveUserFn: ResolveUserFn<User, GraphqlServerContext> = async (
}
}

const validateUserFn: ValidateUserFn<User> = ({ user }) => {
const validateUserFn: ValidateUserFn<User> = ({
user,
fieldAuthDirectiveNode,
}) => {
if (!user) {
throw new EnvelopError('request not authenticated', {
code: 'NOT_AUTHENTICATED',
})
} else {
if (!fieldAuthDirectiveNode?.arguments) {
return
}

const valueNode = fieldAuthDirectiveNode.arguments.find(
(arg) => arg.name.value === 'role'
)?.value as EnumValueNode | undefined

if (valueNode) {
const role = valueNode.value as Role
if (role !== user.role) {
throw new EnvelopError('request not authorized', {
code: 'NOT_AUTHORIZED',
})
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import '@testing-library/jest-dom/extend-expect'
import { resetFactoryIds } from '~/mocks/factories/factory'
import { server } from '~/mocks/server'

globalThis.IS_REACT_ACT_ENVIRONMENT = true

process.env = {
...process.env,
NEXT_PUBLIC_GRAPHQL_END_POINT: 'http://localhost:4000/dev/graphql',
Expand Down
9 changes: 5 additions & 4 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
"@radix-ui/react-portal": "0.1.4",
"graphql": "15.8.0",
"graphql-anywhere": "4.2.7",
"next": "12.0.10",
"react": "17.0.2",
"react-dom": "17.0.2",
"next": "12.1.4",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-error-boundary": "^3.1.4",
"react-hook-form": "7.22.5",
"spinners-react": "1.0.6",
"styled-components": "5.3.3",
Expand All @@ -53,7 +54,7 @@
"@graphql-typed-document-node/core": "3.1.1",
"@next/bundle-analyzer": "12.0.10",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3",
"@testing-library/react": "^13.0.0",
"@types/jest": "27.4.1",
"@types/react": "17.0.39",
"@types/react-dom": "17.0.13",
Expand Down
9 changes: 8 additions & 1 deletion apps/frontend/src/components/util/UrqlClientProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
fetchExchange,
Provider as UrqlProvider,
} from 'urql'
import { pipe, tap } from 'wonka'
import { delay, pipe, tap } from 'wonka'

const fakeSlowNetworkExchange: Exchange =
({ forward }) =>
(ops$) =>
pipe(ops$, delay(1000), forward)

const authCheckExchange: Exchange =
({ forward }) =>
Expand Down Expand Up @@ -57,6 +62,7 @@ const exchanges = [
cacheExchange,
...(process.env.NODE_ENV !== 'test' ? [debugExchange] : []),
authCheckExchange,
fakeSlowNetworkExchange,
fetchExchange,
]

Expand Down Expand Up @@ -89,6 +95,7 @@ export function UrqlClientProvider({
authorization: token ? `Bearer ${token}` : '',
},
}),
suspense: true,
})
)
}
Expand Down
7 changes: 4 additions & 3 deletions apps/frontend/src/contexts/currentUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { gql } from '~/generated'

type CurrentUserModel = Omit<
ReturnType<typeof useCurrentUserInternal>,
'auth0Loading'
'auth0Loading' | 'getProfileFetching'
>

const CurrentUserContext = createContext<CurrentUserModel>(undefined as any)
Expand Down Expand Up @@ -53,6 +53,7 @@ function useCurrentUserInternal() {

return {
currentUser: res.data?.getProfile,
getProfileFetching: res.fetching,
auth0Loading: isLoading,
isOnboarded,
isAuthenticated,
Expand All @@ -68,9 +69,9 @@ export function CurrentUserProvider({
const value = useCurrentUserInternal()
const router = useRouter()

const { auth0Loading, ...rest } = value
const { auth0Loading, getProfileFetching, ...rest } = value

if (auth0Loading) {
if (auth0Loading || getProfileFetching) {
return <Spinner global />
}

Expand Down
41 changes: 41 additions & 0 deletions apps/frontend/src/pages/all-todos/AllTodoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useQuery } from 'urql'

import { gql } from '~/generated'

const GetAllTodos = gql(/* GraphQL */ `
query GetAllTodos {
allTodos {
id
createdAt
updatedAt
title
content
completed
author {
name
}
}
}
`)

export function AllTodoList() {
const [res] = useQuery({ query: GetAllTodos })

if (res.error) {
throw new Error(
res.error.graphQLErrors.map((error) => error.message).toString()
)
}

return (
<div>
{res.data && res.data.allTodos.length < 1 && <p>No Items</p>}
{res.data?.allTodos.map((todo) => (
<div key={todo.id}>
<div>{todo.title}</div>
<p>{todo.author?.name}</p>
</div>
))}
</div>
)
}
40 changes: 10 additions & 30 deletions apps/frontend/src/pages/all-todos/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
import { useQuery } from 'urql'
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'

import { Spinner } from '~/components'
import { DevNote } from '~/components/general/DevNote'
import { gql } from '~/generated'

const GetAllTodos = gql(/* GraphQL */ `
query GetAllTodos {
allTodos {
id
createdAt
updatedAt
title
content
completed
author {
name
}
}
}
`)
import { AllTodoList } from './AllTodoList'

function AllTodos() {
const [res] = useQuery({ query: GetAllTodos })

return (
<div>
<h1 tw="text-black font-bold text-3xl">Todos</h1>
Expand All @@ -35,17 +19,13 @@ function AllTodos() {
admin role user to see
</DevNote.P>
</DevNote.Root>

<div>
{res.fetching && <Spinner global />}
{res.data && res.data.allTodos.length < 1 && <p>No Items</p>}
{res.data?.allTodos.map((todo) => (
<div key={todo.id}>
<div>{todo.title}</div>
<p>{todo.author?.name}</p>
</div>
))}
</div>
<ErrorBoundary
fallbackRender={(props) => <div>{JSON.stringify(props)}</div>}
>
<Suspense fallback={<Spinner />}>
<AllTodoList />
</Suspense>
</ErrorBoundary>
</div>
)
}
Expand Down
4 changes: 0 additions & 4 deletions apps/frontend/src/pages/signin/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useAuth0 } from '@auth0/auth0-react'
import Link from 'next/link'

import { Button } from '~/components'
import { Role } from '~/generated/graphql'
Expand All @@ -20,9 +19,6 @@ function SignIn() {
</Button>
<div>
if you don't have an account? then
{/* <Link href="/signup">
<span tw="text-blue-500 cursor-pointer">sign up</span>
</Link> */}
<Button secondary tw="ml-1" onClick={loginAsUser}>
Sign up
</Button>
Expand Down
20 changes: 12 additions & 8 deletions apps/frontend/src/pages/todos/CreateTodoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ export function CreateTodoModal() {
formState: { errors },
} = useForm<TodoInput>()
const onSubmit: SubmitHandler<TodoInput> = async (data) => {
executeMutation({
todo: removeEmptyFields(data),
})
try {
await executeMutation({
todo: removeEmptyFields(data),
})
window.alert('success!')
} catch (error) {
console.log(error)
window.alert('Failed to create todo')
}
}

return (
Expand All @@ -50,11 +56,9 @@ export function CreateTodoModal() {
{errors.title && <span tw="text-red-500">title is required</span>}
<label htmlFor={register('content').name}>content</label>
<TextArea {...register('content')} />
<DialogClose asChild>
<Button primary type="submit">
Submit
</Button>
</DialogClose>
<Button primary type="submit">
Submit
</Button>
</form>
</DialogContent>
)
Expand Down
37 changes: 37 additions & 0 deletions apps/frontend/src/pages/todos/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { filter } from 'graphql-anywhere'
import { useQuery } from 'urql'

import { gql } from '~/generated'

import { TodoItem } from './TodoItem'

const GetTodos = gql(/* GraphQL */ `
query GetTodos {
todosByCurrentUser {
...TodoItem_Todo
id
completed
}
}
`)

export function TodoList() {
const [res] = useQuery({
query: GetTodos,
requestPolicy: 'network-only', // for the sake of Suspense demo
})

return (
<div>
{res.data && res.data.todosByCurrentUser.length < 1 && <p>No Items</p>}
{res.data?.todosByCurrentUser
.filter(({ completed }) => !completed)
.map((todo) => (
<TodoItem
key={todo.id}
todo={filter(TodoItem.fragments.todo, todo)}
/>
))}
</div>
)
}
30 changes: 5 additions & 25 deletions apps/frontend/src/pages/todos/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import { filter } from 'graphql-anywhere'
import { useQuery } from 'urql'
import { Suspense } from 'react'

import { Button, Dialog, DialogTrigger, Spinner } from '~/components'
import { DevNote } from '~/components/general/DevNote'
import { gql } from '~/generated'

import { CreateTodoModal } from './CreateTodoModal'
import { TodoItem } from './TodoItem'

const GetTodos = gql(/* GraphQL */ `
query GetTodos {
todosByCurrentUser {
...TodoItem_Todo
id
completed
}
}
`)
import { TodoList } from './TodoList'

function Todos() {
const [res] = useQuery({ query: GetTodos })
return (
<div>
<h1 tw="text-black font-bold text-3xl">Todos</h1>
Expand All @@ -38,16 +25,9 @@ function Todos() {
<CreateTodoModal />
</Dialog>
<div tw="mt-4">
{res.fetching && <Spinner global />}
{res.data && res.data.todosByCurrentUser.length < 1 && <p>No Items</p>}
{res.data?.todosByCurrentUser
.filter(({ completed }) => !completed)
.map((todo) => (
<TodoItem
key={todo.id}
todo={filter(TodoItem.fragments.todo, todo)}
/>
))}
<Suspense fallback={<Spinner />}>
<TodoList />
</Suspense>
</div>
</div>
)
Expand Down
Loading