added book details, update, delete. fixes

This commit is contained in:
lnn0q 2025-06-01 19:37:13 +03:00
parent d1da980501
commit b5738c610d
12 changed files with 290 additions and 16 deletions

View file

@ -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>
); );

View file

@ -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 />
<RouterProviderWrapper authHeaders={authHeaders} /> <Stack maxWidth="100vw">
<RouterProviderWrapper authHeaders={authHeaders} />
</Stack>
</ChakraProvider> </ChakraProvider>
</ApolloProvider> </ApolloProvider>
</AuthProvider> </AuthProvider>

View file

@ -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>;
} }

View file

@ -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}

View 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)
}
`;

View file

@ -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) {

View file

@ -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"

View file

@ -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,

View 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 }
});
}

View 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 });
}

View file

@ -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

View file

@ -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?
} }