added book details, update, delete. fixes
This commit is contained in:
parent
d1da980501
commit
b5738c610d
12 changed files with 290 additions and 16 deletions
|
|
@ -3,10 +3,12 @@ import { Link } from '@tanstack/react-router';
|
||||||
|
|
||||||
export function BookOverviewTabs({
|
export function BookOverviewTabs({
|
||||||
bookId,
|
bookId,
|
||||||
currentTab
|
currentTab,
|
||||||
|
isAuthor
|
||||||
}: {
|
}: {
|
||||||
readonly bookId: string;
|
readonly bookId: string;
|
||||||
readonly currentTab: 'contents' | 'reviews';
|
readonly currentTab: 'contents' | 'reviews' | 'details';
|
||||||
|
readonly isAuthor: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Tabs.Root defaultValue="members" value={currentTab}>
|
<Tabs.Root defaultValue="members" value={currentTab}>
|
||||||
|
|
@ -17,6 +19,11 @@ export function BookOverviewTabs({
|
||||||
<Link to={`/work/book/${bookId}/overview/reviews`}>
|
<Link to={`/work/book/${bookId}/overview/reviews`}>
|
||||||
<Tabs.Trigger value="reviews">Reviews</Tabs.Trigger>
|
<Tabs.Trigger value="reviews">Reviews</Tabs.Trigger>
|
||||||
</Link>
|
</Link>
|
||||||
|
{isAuthor && (
|
||||||
|
<Link to={`/work/book/${bookId}/overview/details`}>
|
||||||
|
<Tabs.Trigger value="details">Details</Tabs.Trigger>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache, from } from '@apollo/client';
|
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache, from } from '@apollo/client';
|
||||||
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
|
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
|
||||||
import { onError } from '@apollo/client/link/error';
|
import { onError } from '@apollo/client/link/error';
|
||||||
import { Box, Center, Spinner } from '@chakra-ui/react';
|
import { Box, Center, Spinner, Stack } from '@chakra-ui/react';
|
||||||
import '@client/assets/styles/index.css';
|
import '@client/assets/styles/index.css';
|
||||||
import '@client/assets/styles/nerd-fonts-icons.css';
|
import '@client/assets/styles/nerd-fonts-icons.css';
|
||||||
import { Provider as ChakraProvider } from '@client/components/ui/provider';
|
import { Provider as ChakraProvider } from '@client/components/ui/provider';
|
||||||
|
|
@ -118,7 +118,9 @@ function App() {
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<ChakraProvider>
|
<ChakraProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
<Stack maxWidth="100vw">
|
||||||
<RouterProviderWrapper authHeaders={authHeaders} />
|
<RouterProviderWrapper authHeaders={authHeaders} />
|
||||||
|
</Stack>
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import {Stack} from '@chakra-ui/react';
|
||||||
|
import {createFileRoute} from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/profile/$username')({
|
export const Route = createFileRoute('/profile/$username')({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
|
|
@ -6,5 +7,5 @@ export const Route = createFileRoute('/profile/$username')({
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { username } = Route.useParams();
|
const { username } = Route.useParams();
|
||||||
return <div>{`Hello, "${username}"!`}</div>;
|
return <Stack>{`Hello, "${username}"!`}</Stack>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,14 @@ function RouteComponent() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BookOverviewTabs bookId={id} currentTab="contents" />
|
|
||||||
|
|
||||||
{data != null ? (
|
{data != null ? (
|
||||||
<>
|
<>
|
||||||
|
<BookOverviewTabs
|
||||||
|
isAuthor={data?.bookOne.author.id === identity?.data.id}
|
||||||
|
bookId={id}
|
||||||
|
currentTab="contents"
|
||||||
|
/>
|
||||||
|
|
||||||
<BookChapterList
|
<BookChapterList
|
||||||
id={data.bookOne.id}
|
id={data.bookOne.id}
|
||||||
isAuthor={identity?.data.id === data.bookOne.author.id}
|
isAuthor={identity?.data.id === data.bookOne.author.id}
|
||||||
|
|
|
||||||
151
app/client/pages/work/book/$bookId/overview/details.tsx
Normal file
151
app/client/pages/work/book/$bookId/overview/details.tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
|
||||||
|
import { bookListQuery } from '@app/client/components/BookSearchList';
|
||||||
|
import { bookFragment } from '@app/client/fragments';
|
||||||
|
import { useIdentity } from '@app/client/hooks/identity';
|
||||||
|
import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
||||||
|
import { Button, Input, NativeSelect, Skeleton, Stack, Textarea } from '@chakra-ui/react';
|
||||||
|
import { BookDetailsQuery, WorkingProgressStatus } from '@generated/types';
|
||||||
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/work/book/$bookId/overview/details')({
|
||||||
|
component: RouteComponent
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { bookId: id } = Route.useParams();
|
||||||
|
|
||||||
|
const identity = useIdentity();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data } = useQuery<BookDetailsQuery>(bookQuery, {
|
||||||
|
variables: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
const { update: bookUpdate } = useToastMutation({
|
||||||
|
mutation: bookUpdateMutation,
|
||||||
|
successMsg: 'Book updated successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { update: bookDelete } = useToastMutation({
|
||||||
|
mutation: bookDeleteMutation,
|
||||||
|
successMsg: 'Book deleted successfully',
|
||||||
|
refetchQueries: [bookListQuery]
|
||||||
|
});
|
||||||
|
|
||||||
|
const [title, setTitle] = useState<string | undefined>(data?.bookOne.title);
|
||||||
|
const [description, setDescription] = useState<string | undefined>(data?.bookOne.description);
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState<WorkingProgressStatus | undefined>(
|
||||||
|
data?.bookOne.progressStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data == null) return <Skeleton />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BookOverviewTabs
|
||||||
|
isAuthor={data.bookOne.author.id === identity?.data.id}
|
||||||
|
bookId={id}
|
||||||
|
currentTab="details"
|
||||||
|
/>
|
||||||
|
<Stack justifyContent="center" alignItems="center">
|
||||||
|
<Stack width="70%">
|
||||||
|
<Input
|
||||||
|
value={title}
|
||||||
|
onChange={event => setTitle(event.currentTarget.value)}
|
||||||
|
placeholder="title"
|
||||||
|
maxLength={60}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NativeSelect.Root>
|
||||||
|
<NativeSelect.Field
|
||||||
|
value={progress}
|
||||||
|
onChange={event => {
|
||||||
|
const updatedStatus = event.currentTarget.value;
|
||||||
|
ensureProgressStatus(updatedStatus);
|
||||||
|
setProgress(updatedStatus);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={WorkingProgressStatus.InProgress}>In progress</option>
|
||||||
|
<option value={WorkingProgressStatus.Frozen}>Frozen</option>
|
||||||
|
<option value={WorkingProgressStatus.Complete}>Complete</option>
|
||||||
|
</NativeSelect.Field>
|
||||||
|
<NativeSelect.Indicator />
|
||||||
|
</NativeSelect.Root>
|
||||||
|
<Textarea
|
||||||
|
value={description}
|
||||||
|
onChange={event => setDescription(event.currentTarget.value)}
|
||||||
|
placeholder="description"
|
||||||
|
maxLength={500}
|
||||||
|
autoresize
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
width="100%"
|
||||||
|
onClick={async () => {
|
||||||
|
await bookUpdate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
progressStatus: progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
width="100%"
|
||||||
|
backgroundColor="red.500"
|
||||||
|
onClick={async () => {
|
||||||
|
await bookDelete({ variables: { id } });
|
||||||
|
navigate({ to: '/' });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureProgressStatus(value: string): asserts value is WorkingProgressStatus {
|
||||||
|
if (value === WorkingProgressStatus.Complete) return;
|
||||||
|
if (value === WorkingProgressStatus.InProgress) return;
|
||||||
|
if (value === WorkingProgressStatus.Frozen) return;
|
||||||
|
|
||||||
|
throw new Error('Invalid WorkingPrgoressStatus');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookQuery = gql`
|
||||||
|
query BookDetails($id: ID!) {
|
||||||
|
bookOne(id: $id) {
|
||||||
|
...Book
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${bookFragment}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const bookUpdateMutation = gql`
|
||||||
|
mutation BookDetailsUpdate($input: BookUpdateInput!) {
|
||||||
|
bookUpdate(input: $input) {
|
||||||
|
...Book
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${bookFragment}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const bookDeleteMutation = gql`
|
||||||
|
mutation BookDetailsDelete($id: ID!) {
|
||||||
|
bookDelete(id: $id)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { gql } from '@apollo/client';
|
import { gql, useQuery } from '@apollo/client';
|
||||||
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
|
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
|
||||||
import { Pagination } from '@app/client/components/Pagination';
|
import { Pagination } from '@app/client/components/Pagination';
|
||||||
import { reviewFragment } from '@app/client/fragments';
|
import { reviewFragment } from '@app/client/fragments';
|
||||||
|
|
@ -6,7 +6,7 @@ import { useIdentity } from '@app/client/hooks/identity';
|
||||||
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
||||||
import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
||||||
import { Skeleton, Stack, Text } from '@chakra-ui/react';
|
import { Skeleton, Stack, Text } from '@chakra-ui/react';
|
||||||
import { BookReviewCreateMutation, BookReviewsQuery } from '@generated/types';
|
import { BookReviewCreateMutation, BookReviewsDataQuery, BookReviewsQuery } from '@generated/types';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { bookQuery } from '../overview';
|
import { bookQuery } from '../overview';
|
||||||
|
|
@ -36,17 +36,25 @@ function RouteComponent() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: authorData } = useQuery<BookReviewsDataQuery>(bookAuthorQuery, {
|
||||||
|
variables: { id }
|
||||||
|
});
|
||||||
|
|
||||||
const { update: reviewCreate } = useToastMutation<BookReviewCreateMutation>({
|
const { update: reviewCreate } = useToastMutation<BookReviewCreateMutation>({
|
||||||
mutation: bookReviewCreateMutation,
|
mutation: bookReviewCreateMutation,
|
||||||
successMsg: 'Review posted!',
|
successMsg: 'Review posted!',
|
||||||
refetchQueries: [bookReviewsQuery, bookQuery]
|
refetchQueries: [bookReviewsQuery, bookQuery]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data == null) return <Skeleton />;
|
if (data == null || authorData == null) return <Skeleton />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BookOverviewTabs bookId={id} currentTab="reviews" />
|
<BookOverviewTabs
|
||||||
|
isAuthor={authorData.bookOne.author.id === identity?.data.id}
|
||||||
|
bookId={id}
|
||||||
|
currentTab="reviews"
|
||||||
|
/>
|
||||||
|
|
||||||
{identity != null && reviewsPage === 1 && (
|
{identity != null && reviewsPage === 1 && (
|
||||||
<ReviewInput
|
<ReviewInput
|
||||||
|
|
@ -97,6 +105,17 @@ function RouteComponent() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bookAuthorQuery = gql`
|
||||||
|
query BookReviewsData($id: ID!) {
|
||||||
|
bookOne(id: $id) {
|
||||||
|
id
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const bookReviewsQuery = gql`
|
const bookReviewsQuery = gql`
|
||||||
query BookReviews($input: ReviewManyInput!) {
|
query BookReviews($input: ReviewManyInput!) {
|
||||||
reviewMany(input: $input) {
|
reviewMany(input: $input) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { Route as ProfileUsernameImport } from './pages/profile/$username'
|
||||||
import { Route as NewBookImport } from './pages/new/book'
|
import { Route as NewBookImport } from './pages/new/book'
|
||||||
import { Route as WorkBookBookIdOverviewImport } from './pages/work/book/$bookId/overview'
|
import { Route as WorkBookBookIdOverviewImport } from './pages/work/book/$bookId/overview'
|
||||||
import { Route as WorkBookBookIdOverviewReviewsImport } from './pages/work/book/$bookId/overview/reviews'
|
import { Route as WorkBookBookIdOverviewReviewsImport } from './pages/work/book/$bookId/overview/reviews'
|
||||||
|
import { Route as WorkBookBookIdOverviewDetailsImport } from './pages/work/book/$bookId/overview/details'
|
||||||
import { Route as WorkBookBookIdOverviewContentsImport } from './pages/work/book/$bookId/overview/contents'
|
import { Route as WorkBookBookIdOverviewContentsImport } from './pages/work/book/$bookId/overview/contents'
|
||||||
import { Route as WorkBookBookIdChapterNewImport } from './pages/work/book/$bookId/chapter/new'
|
import { Route as WorkBookBookIdChapterNewImport } from './pages/work/book/$bookId/chapter/new'
|
||||||
import { Route as WorkBookBookIdChapterChapterIdViewImport } from './pages/work/book/$bookId/chapter/$chapterId/view'
|
import { Route as WorkBookBookIdChapterChapterIdViewImport } from './pages/work/book/$bookId/chapter/$chapterId/view'
|
||||||
|
|
@ -82,6 +83,13 @@ const WorkBookBookIdOverviewReviewsRoute =
|
||||||
getParentRoute: () => WorkBookBookIdOverviewRoute,
|
getParentRoute: () => WorkBookBookIdOverviewRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const WorkBookBookIdOverviewDetailsRoute =
|
||||||
|
WorkBookBookIdOverviewDetailsImport.update({
|
||||||
|
id: '/details',
|
||||||
|
path: '/details',
|
||||||
|
getParentRoute: () => WorkBookBookIdOverviewRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const WorkBookBookIdOverviewContentsRoute =
|
const WorkBookBookIdOverviewContentsRoute =
|
||||||
WorkBookBookIdOverviewContentsImport.update({
|
WorkBookBookIdOverviewContentsImport.update({
|
||||||
id: '/contents',
|
id: '/contents',
|
||||||
|
|
@ -183,6 +191,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof WorkBookBookIdOverviewContentsImport
|
preLoaderRoute: typeof WorkBookBookIdOverviewContentsImport
|
||||||
parentRoute: typeof WorkBookBookIdOverviewImport
|
parentRoute: typeof WorkBookBookIdOverviewImport
|
||||||
}
|
}
|
||||||
|
'/work/book/$bookId/overview/details': {
|
||||||
|
id: '/work/book/$bookId/overview/details'
|
||||||
|
path: '/details'
|
||||||
|
fullPath: '/work/book/$bookId/overview/details'
|
||||||
|
preLoaderRoute: typeof WorkBookBookIdOverviewDetailsImport
|
||||||
|
parentRoute: typeof WorkBookBookIdOverviewImport
|
||||||
|
}
|
||||||
'/work/book/$bookId/overview/reviews': {
|
'/work/book/$bookId/overview/reviews': {
|
||||||
id: '/work/book/$bookId/overview/reviews'
|
id: '/work/book/$bookId/overview/reviews'
|
||||||
path: '/reviews'
|
path: '/reviews'
|
||||||
|
|
@ -211,12 +226,14 @@ declare module '@tanstack/react-router' {
|
||||||
|
|
||||||
interface WorkBookBookIdOverviewRouteChildren {
|
interface WorkBookBookIdOverviewRouteChildren {
|
||||||
WorkBookBookIdOverviewContentsRoute: typeof WorkBookBookIdOverviewContentsRoute
|
WorkBookBookIdOverviewContentsRoute: typeof WorkBookBookIdOverviewContentsRoute
|
||||||
|
WorkBookBookIdOverviewDetailsRoute: typeof WorkBookBookIdOverviewDetailsRoute
|
||||||
WorkBookBookIdOverviewReviewsRoute: typeof WorkBookBookIdOverviewReviewsRoute
|
WorkBookBookIdOverviewReviewsRoute: typeof WorkBookBookIdOverviewReviewsRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkBookBookIdOverviewRouteChildren: WorkBookBookIdOverviewRouteChildren =
|
const WorkBookBookIdOverviewRouteChildren: WorkBookBookIdOverviewRouteChildren =
|
||||||
{
|
{
|
||||||
WorkBookBookIdOverviewContentsRoute: WorkBookBookIdOverviewContentsRoute,
|
WorkBookBookIdOverviewContentsRoute: WorkBookBookIdOverviewContentsRoute,
|
||||||
|
WorkBookBookIdOverviewDetailsRoute: WorkBookBookIdOverviewDetailsRoute,
|
||||||
WorkBookBookIdOverviewReviewsRoute: WorkBookBookIdOverviewReviewsRoute,
|
WorkBookBookIdOverviewReviewsRoute: WorkBookBookIdOverviewReviewsRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,6 +253,7 @@ export interface FileRoutesByFullPath {
|
||||||
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
||||||
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
||||||
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
||||||
|
'/work/book/$bookId/overview/details': typeof WorkBookBookIdOverviewDetailsRoute
|
||||||
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
||||||
|
|
@ -252,6 +270,7 @@ export interface FileRoutesByTo {
|
||||||
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
||||||
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
||||||
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
||||||
|
'/work/book/$bookId/overview/details': typeof WorkBookBookIdOverviewDetailsRoute
|
||||||
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
||||||
|
|
@ -269,6 +288,7 @@ export interface FileRoutesById {
|
||||||
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
'/work/book/$bookId/overview': typeof WorkBookBookIdOverviewRouteWithChildren
|
||||||
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
'/work/book/$bookId/chapter/new': typeof WorkBookBookIdChapterNewRoute
|
||||||
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
'/work/book/$bookId/overview/contents': typeof WorkBookBookIdOverviewContentsRoute
|
||||||
|
'/work/book/$bookId/overview/details': typeof WorkBookBookIdOverviewDetailsRoute
|
||||||
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
'/work/book/$bookId/overview/reviews': typeof WorkBookBookIdOverviewReviewsRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
'/work/book/$bookId/chapter/$chapterId/edit': typeof WorkBookBookIdChapterChapterIdEditRoute
|
||||||
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
'/work/book/$bookId/chapter/$chapterId/view': typeof WorkBookBookIdChapterChapterIdViewRoute
|
||||||
|
|
@ -287,6 +307,7 @@ export interface FileRouteTypes {
|
||||||
| '/work/book/$bookId/overview'
|
| '/work/book/$bookId/overview'
|
||||||
| '/work/book/$bookId/chapter/new'
|
| '/work/book/$bookId/chapter/new'
|
||||||
| '/work/book/$bookId/overview/contents'
|
| '/work/book/$bookId/overview/contents'
|
||||||
|
| '/work/book/$bookId/overview/details'
|
||||||
| '/work/book/$bookId/overview/reviews'
|
| '/work/book/$bookId/overview/reviews'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/edit'
|
| '/work/book/$bookId/chapter/$chapterId/edit'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/view'
|
| '/work/book/$bookId/chapter/$chapterId/view'
|
||||||
|
|
@ -302,6 +323,7 @@ export interface FileRouteTypes {
|
||||||
| '/work/book/$bookId/overview'
|
| '/work/book/$bookId/overview'
|
||||||
| '/work/book/$bookId/chapter/new'
|
| '/work/book/$bookId/chapter/new'
|
||||||
| '/work/book/$bookId/overview/contents'
|
| '/work/book/$bookId/overview/contents'
|
||||||
|
| '/work/book/$bookId/overview/details'
|
||||||
| '/work/book/$bookId/overview/reviews'
|
| '/work/book/$bookId/overview/reviews'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/edit'
|
| '/work/book/$bookId/chapter/$chapterId/edit'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/view'
|
| '/work/book/$bookId/chapter/$chapterId/view'
|
||||||
|
|
@ -317,6 +339,7 @@ export interface FileRouteTypes {
|
||||||
| '/work/book/$bookId/overview'
|
| '/work/book/$bookId/overview'
|
||||||
| '/work/book/$bookId/chapter/new'
|
| '/work/book/$bookId/chapter/new'
|
||||||
| '/work/book/$bookId/overview/contents'
|
| '/work/book/$bookId/overview/contents'
|
||||||
|
| '/work/book/$bookId/overview/details'
|
||||||
| '/work/book/$bookId/overview/reviews'
|
| '/work/book/$bookId/overview/reviews'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/edit'
|
| '/work/book/$bookId/chapter/$chapterId/edit'
|
||||||
| '/work/book/$bookId/chapter/$chapterId/view'
|
| '/work/book/$bookId/chapter/$chapterId/view'
|
||||||
|
|
@ -401,6 +424,7 @@ export const routeTree = rootRoute
|
||||||
"filePath": "work/book/$bookId/overview.tsx",
|
"filePath": "work/book/$bookId/overview.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/work/book/$bookId/overview/contents",
|
"/work/book/$bookId/overview/contents",
|
||||||
|
"/work/book/$bookId/overview/details",
|
||||||
"/work/book/$bookId/overview/reviews"
|
"/work/book/$bookId/overview/reviews"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -411,6 +435,10 @@ export const routeTree = rootRoute
|
||||||
"filePath": "work/book/$bookId/overview/contents.tsx",
|
"filePath": "work/book/$bookId/overview/contents.tsx",
|
||||||
"parent": "/work/book/$bookId/overview"
|
"parent": "/work/book/$bookId/overview"
|
||||||
},
|
},
|
||||||
|
"/work/book/$bookId/overview/details": {
|
||||||
|
"filePath": "work/book/$bookId/overview/details.tsx",
|
||||||
|
"parent": "/work/book/$bookId/overview"
|
||||||
|
},
|
||||||
"/work/book/$bookId/overview/reviews": {
|
"/work/book/$bookId/overview/reviews": {
|
||||||
"filePath": "work/book/$bookId/overview/reviews.tsx",
|
"filePath": "work/book/$bookId/overview/reviews.tsx",
|
||||||
"parent": "/work/book/$bookId/overview"
|
"parent": "/work/book/$bookId/overview"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import { UserOneResolver } from './resolvers/userOneResolver';
|
||||||
import { LoginResolver } from './resolvers/auth/login';
|
import { LoginResolver } from './resolvers/auth/login';
|
||||||
import { LogoutResolver } from './resolvers/auth/logout';
|
import { LogoutResolver } from './resolvers/auth/logout';
|
||||||
import { SignUpResolver } from './resolvers/auth/signUp';
|
import { SignUpResolver } from './resolvers/auth/signUp';
|
||||||
|
import { BookUpdateResolver } from './resolvers/bookUpdateResolver';
|
||||||
|
import { BookDeleteResolver } from './resolvers/bookDeleteResolver';
|
||||||
import { BookChapterCreateResolver } from './resolvers/bookChapterCreateResolver';
|
import { BookChapterCreateResolver } from './resolvers/bookChapterCreateResolver';
|
||||||
import { BookChapterDeleteResolver } from './resolvers/bookChapterDelete';
|
import { BookChapterDeleteResolver } from './resolvers/bookChapterDelete';
|
||||||
import { BookChapterUpdateResolver } from './resolvers/bookChapterUpdateResolver';
|
import { BookChapterUpdateResolver } from './resolvers/bookChapterUpdateResolver';
|
||||||
|
|
@ -36,6 +38,8 @@ export const Mutation: MutationResolvers = {
|
||||||
login: LoginResolver,
|
login: LoginResolver,
|
||||||
logout: LogoutResolver,
|
logout: LogoutResolver,
|
||||||
bookCreate: BookCreateResolver,
|
bookCreate: BookCreateResolver,
|
||||||
|
bookUpdate: BookUpdateResolver,
|
||||||
|
bookDelete: BookDeleteResolver,
|
||||||
bookChapterCreate: BookChapterCreateResolver,
|
bookChapterCreate: BookChapterCreateResolver,
|
||||||
bookChapterDelete: BookChapterDeleteResolver,
|
bookChapterDelete: BookChapterDeleteResolver,
|
||||||
bookChapterUpdate: BookChapterUpdateResolver,
|
bookChapterUpdate: BookChapterUpdateResolver,
|
||||||
|
|
|
||||||
22
app/server/graphql/resolvers/bookDeleteResolver.ts
Normal file
22
app/server/graphql/resolvers/bookDeleteResolver.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { ensureIdentity } from '@app/server/helpers';
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { MutationBookDeleteArgs } from '@generated/types';
|
||||||
|
|
||||||
|
export async function BookDeleteResolver(
|
||||||
|
_: any,
|
||||||
|
{ id }: MutationBookDeleteArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<void> {
|
||||||
|
ensureIdentity(identity);
|
||||||
|
|
||||||
|
const model = await prisma.book.findUniqueOrThrow({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identity.id !== model.authorId) throw new Error('Operation is not permitted.');
|
||||||
|
|
||||||
|
await prisma.book.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
34
app/server/graphql/resolvers/bookUpdateResolver.ts
Normal file
34
app/server/graphql/resolvers/bookUpdateResolver.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { ensureIdentity } from '@app/server/helpers';
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { Book, MutationBookUpdateArgs } from '@generated/types';
|
||||||
|
import { toBookSchema } from './mappers/bookMapper';
|
||||||
|
|
||||||
|
export async function BookUpdateResolver(
|
||||||
|
_: any,
|
||||||
|
{ input }: MutationBookUpdateArgs,
|
||||||
|
{ req, res, identity }: IdentityContext
|
||||||
|
): Promise<Book> {
|
||||||
|
ensureIdentity(identity);
|
||||||
|
|
||||||
|
const model = await prisma.book.findUniqueOrThrow({
|
||||||
|
where: { id: input.id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identity.id !== model.authorId) throw new Error('Operation is not permitted.');
|
||||||
|
|
||||||
|
if (input.title != null && input.title.length > 60) throw new Error('Title is too long.');
|
||||||
|
if (input.description != null && input.description.length > 900)
|
||||||
|
throw new Error('Description is too long.');
|
||||||
|
|
||||||
|
const modelUpdated = await prisma.book.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: {
|
||||||
|
title: input.title != null ? input.title : undefined,
|
||||||
|
description: input.description != null ? input.description : undefined,
|
||||||
|
progressStatus: input.progressStatus != null ? input.progressStatus : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return toBookSchema(modelUpdated, { req, res, identity });
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@ type Mutation {
|
||||||
login(input: LoginInput!): Session!
|
login(input: LoginInput!): Session!
|
||||||
logout: Void
|
logout: Void
|
||||||
bookCreate(input: BookCreateInput!): Book!
|
bookCreate(input: BookCreateInput!): Book!
|
||||||
|
bookUpdate(input: BookUpdateInput!): Book!
|
||||||
|
bookDelete(id: ID!): Void
|
||||||
bookChapterCreate(input: BookChapterCreateInput!): BookChapter!
|
bookChapterCreate(input: BookChapterCreateInput!): BookChapter!
|
||||||
bookChapterUpdate(input: BookChapterUpdateInput!): BookChapter!
|
bookChapterUpdate(input: BookChapterUpdateInput!): BookChapter!
|
||||||
bookChapterDelete(id: ID!): Book!
|
bookChapterDelete(id: ID!): Book!
|
||||||
|
|
@ -97,7 +99,7 @@ type Book {
|
||||||
personalRating: RatingType!
|
personalRating: RatingType!
|
||||||
}
|
}
|
||||||
|
|
||||||
input BookUpdate {
|
input BookUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String
|
title: String
|
||||||
description: String
|
description: String
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ model BookChapter {
|
||||||
|
|
||||||
wordsTotal Int @default(0)
|
wordsTotal Int @default(0)
|
||||||
|
|
||||||
book Book @relation(fields: [bookId], references: [id])
|
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
||||||
bookId String @db.VarChar(255)
|
bookId String @db.VarChar(255)
|
||||||
|
|
||||||
reviews Review[]
|
reviews Review[]
|
||||||
|
|
@ -94,10 +94,10 @@ model Review {
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
userId String @db.VarChar(255)
|
userId String @db.VarChar(255)
|
||||||
|
|
||||||
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id])
|
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id], onDelete: Cascade)
|
||||||
bookChapterId String? @db.VarChar(255)
|
bookChapterId String? @db.VarChar(255)
|
||||||
|
|
||||||
book Book? @relation(fields: [bookId], references: [id])
|
book Book? @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
||||||
bookId String?
|
bookId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue