added resolvers, updated model, updated navbar
This commit is contained in:
parent
a8375b99c2
commit
1d40d7e15f
23 changed files with 439 additions and 301 deletions
84
app/client/components/BookSearchList.tsx
Normal file
84
app/client/components/BookSearchList.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import { Skeleton, Stack } from '@chakra-ui/react';
|
||||||
|
import { BooksListQuery } from '@generated/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Pagination } from '../components/Pagination';
|
||||||
|
import { WorkingList } from '../components/WorkingList';
|
||||||
|
import { WorkingListSearch } from '../components/WorkingListSearch';
|
||||||
|
import { usePaginatedQuery } from '../hooks/usePaginatedQuery';
|
||||||
|
|
||||||
|
export function BookSearchList() {
|
||||||
|
const [workingSearchValue, setWorkingSearchValue] = useState<string>('');
|
||||||
|
const [workingsListPage, setWorkingsListPage] = useState<number>(1);
|
||||||
|
const [workingsListPageSize, setWorkingsListPageSize] = useState<number>(2);
|
||||||
|
const startIndex = (workingsListPage - 1) * workingsListPageSize;
|
||||||
|
|
||||||
|
const { data } = usePaginatedQuery<BooksListQuery>({
|
||||||
|
query,
|
||||||
|
input: {
|
||||||
|
filter: {
|
||||||
|
terms: workingSearchValue,
|
||||||
|
offset: startIndex,
|
||||||
|
size: workingsListPageSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{data != null ? (
|
||||||
|
<>
|
||||||
|
<WorkingListSearch
|
||||||
|
onSubmit={value => {
|
||||||
|
setWorkingSearchValue(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<WorkingList workingsList={[...data.bookMany.items]} />
|
||||||
|
|
||||||
|
{data.bookMany.items.length != 0 && (
|
||||||
|
<Pagination
|
||||||
|
total={data.bookMany.total}
|
||||||
|
page={workingsListPage}
|
||||||
|
setPage={setWorkingsListPage}
|
||||||
|
pageSize={workingsListPageSize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Stack direction="row" justifyContent="center" alignItems="center" gap={2}>
|
||||||
|
<Skeleton height={10} width="80%" />
|
||||||
|
<Skeleton height={10} width="calc(20% - 6px)" />
|
||||||
|
</Stack>
|
||||||
|
<Skeleton height={20} />
|
||||||
|
<Skeleton height={20} />
|
||||||
|
<Skeleton height={20} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = gql`
|
||||||
|
query BooksList($input: BookManyInput!) {
|
||||||
|
bookMany(input: $input) {
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
updatedAt
|
||||||
|
progressStatus
|
||||||
|
ratingTotal
|
||||||
|
reviewsTotal
|
||||||
|
wordsTotal
|
||||||
|
chaptersTotal
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
27
app/client/components/IconBadge.tsx
Normal file
27
app/client/components/IconBadge.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Badge, BadgeProps, Box, Icon, Stack } from '@chakra-ui/react';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function IconBadge({
|
||||||
|
children,
|
||||||
|
iconClass,
|
||||||
|
backgroundColor,
|
||||||
|
size = 'lg'
|
||||||
|
}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
iconClass: string;
|
||||||
|
backgroundColor?: BadgeProps['backgroundColor'];
|
||||||
|
size?: 'sm' | 'md' | 'lg' | 'xs';
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box flex={1} direction="row" justifyContent="center" alignItems="center">
|
||||||
|
<Badge variant="solid" size={size} backgroundColor={backgroundColor}>
|
||||||
|
<Stack direction="row">
|
||||||
|
<Icon fontSize="xl">
|
||||||
|
<i className={iconClass} />
|
||||||
|
</Icon>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</Badge>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
import { List } from '@chakra-ui/react';
|
import { Card, List, Text } from '@chakra-ui/react';
|
||||||
import { WorkingCardProps, WorkingListCard } from './WorkingListCard';
|
import { WorkingCardProps, WorkingListCard } from './WorkingListCard';
|
||||||
|
|
||||||
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
||||||
return (
|
return (
|
||||||
<List.Root variant="plain" gap={4}>
|
<List.Root variant="plain" gap={4}>
|
||||||
{workingsList.map(workingData => (
|
{workingsList.length != 0 ? (
|
||||||
|
workingsList.map(workingData => (
|
||||||
<List.Item key={workingData.id}>
|
<List.Item key={workingData.id}>
|
||||||
<WorkingListCard {...workingData} />
|
<WorkingListCard {...workingData} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<List.Item>
|
||||||
|
<Card.Root
|
||||||
|
variant="outline"
|
||||||
|
width="100%"
|
||||||
|
padding={4}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems={'center'}
|
||||||
|
>
|
||||||
|
<Text fontSize="md">Nothing is here yet!</Text>
|
||||||
|
</Card.Root>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
</List.Root>
|
</List.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Badge, BadgeProps, Box, Card, Heading, Icon, Link, Stack, Text } from '@chakra-ui/react';
|
import { Badge, Box, Card, Heading, Link, Stack, Text } from '@chakra-ui/react';
|
||||||
import { WorkingProgressStatus } from '@generated/types';
|
import { WorkingProgressStatus } from '@generated/types';
|
||||||
import { ReactNode } from 'react';
|
import { IconBadge } from './IconBadge';
|
||||||
import { WorkingProgressStatusBadge } from './WorkingProgressStatusBadge';
|
import { WorkingProgressStatusBadge } from './WorkingProgressStatusBadge';
|
||||||
import { WorkingRatingBadge } from './WorkingRatingBadge';
|
import { WorkingRatingBadge } from './WorkingRatingBadge';
|
||||||
|
|
||||||
|
|
@ -88,28 +88,3 @@ export function WorkingListCard({
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconBadge({
|
|
||||||
children,
|
|
||||||
iconClass,
|
|
||||||
backgroundColor,
|
|
||||||
size = 'lg'
|
|
||||||
}: {
|
|
||||||
children?: ReactNode;
|
|
||||||
iconClass: string;
|
|
||||||
backgroundColor?: BadgeProps['backgroundColor'];
|
|
||||||
size?: 'sm' | 'md' | 'lg' | 'xs';
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Box flex={1} direction="row" justifyContent="center" alignItems="center">
|
|
||||||
<Badge variant="solid" size={size} backgroundColor={backgroundColor}>
|
|
||||||
<Stack direction="row">
|
|
||||||
<Icon fontSize="xl">
|
|
||||||
<i className={iconClass} />
|
|
||||||
</Icon>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
import { Button, Icon, Input, InputGroup, Stack } from '@chakra-ui/react';
|
import { Button, Icon, Input, InputGroup, Stack } from '@chakra-ui/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
export function WorkingListSearch({
|
export function WorkingListSearch({ onSubmit }: { onSubmit: (value: string) => void }) {
|
||||||
value,
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
onChange,
|
|
||||||
onSubmit
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
onSubmit: (value: string) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={event => {
|
onSubmit={event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit(value);
|
onSubmit(searchValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
|
|
@ -26,8 +20,8 @@ export function WorkingListSearch({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
value={value}
|
value={searchValue}
|
||||||
onChange={event => onChange(event.currentTarget.value)}
|
onChange={event => setSearchValue(event.currentTarget.value)}
|
||||||
placeholder="Search works..."
|
placeholder="Search works..."
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,6 @@ export function WorkingProgressStatusBadge({
|
||||||
function toWorkingProgressStatusIcon(value: WorkingProgressStatus): string {
|
function toWorkingProgressStatusIcon(value: WorkingProgressStatus): string {
|
||||||
if (value === WorkingProgressStatus.InProgress) return 'nf nf-fa-clock';
|
if (value === WorkingProgressStatus.InProgress) return 'nf nf-fa-clock';
|
||||||
if (value === WorkingProgressStatus.Frozen) return 'nf nf-fa-snowflake';
|
if (value === WorkingProgressStatus.Frozen) return 'nf nf-fa-snowflake';
|
||||||
if (value === WorkingProgressStatus.Finished) return 'nf nf-fa-circle_check';
|
if (value === WorkingProgressStatus.Complete) return 'nf nf-fa-circle_check';
|
||||||
assertUnreachable(value);
|
assertUnreachable(value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { BadgeProps, Text } from '@chakra-ui/react';
|
import { BadgeProps, Text } from '@chakra-ui/react';
|
||||||
import { IconBadge } from './WorkingListCard';
|
import { IconBadge } from './IconBadge';
|
||||||
|
|
||||||
interface WorkingRatingParams {
|
interface WorkingRatingParams {
|
||||||
rating: string;
|
rating: string;
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,20 @@ export function assertUnreachable(value: never): never {
|
||||||
export function toBgColorByProgressStatus(value: WorkingProgressStatus): string {
|
export function toBgColorByProgressStatus(value: WorkingProgressStatus): string {
|
||||||
if (value === WorkingProgressStatus.InProgress) return 'yellow.500';
|
if (value === WorkingProgressStatus.InProgress) return 'yellow.500';
|
||||||
if (value === WorkingProgressStatus.Frozen) return 'blue.500';
|
if (value === WorkingProgressStatus.Frozen) return 'blue.500';
|
||||||
if (value === WorkingProgressStatus.Finished) return 'green.500';
|
if (value === WorkingProgressStatus.Complete) return 'green.500';
|
||||||
assertUnreachable(value);
|
assertUnreachable(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toFgColorByProgressStatus(value: WorkingProgressStatus): string {
|
export function toFgColorByProgressStatus(value: WorkingProgressStatus): string {
|
||||||
if (value === WorkingProgressStatus.InProgress) return 'fg.warning';
|
if (value === WorkingProgressStatus.InProgress) return 'fg.warning';
|
||||||
if (value === WorkingProgressStatus.Frozen) return 'fg.info';
|
if (value === WorkingProgressStatus.Frozen) return 'fg.info';
|
||||||
if (value === WorkingProgressStatus.Finished) return 'fg.success';
|
if (value === WorkingProgressStatus.Complete) return 'fg.success';
|
||||||
assertUnreachable(value);
|
assertUnreachable(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toWorkingProgressStatusLabel(value: WorkingProgressStatus): string {
|
export function toWorkingProgressStatusLabel(value: WorkingProgressStatus): string {
|
||||||
if (value === WorkingProgressStatus.InProgress) return 'In progress';
|
if (value === WorkingProgressStatus.InProgress) return 'In progress';
|
||||||
if (value === WorkingProgressStatus.Frozen) return 'Frozen';
|
if (value === WorkingProgressStatus.Frozen) return 'Frozen';
|
||||||
if (value === WorkingProgressStatus.Finished) return 'Finished';
|
if (value === WorkingProgressStatus.Complete) return 'Complete';
|
||||||
assertUnreachable(value);
|
assertUnreachable(value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import { Icon, Input, InputGroup, Kbd, Menu, Separator, Stack, Text } from '@chakra-ui/react';
|
import {
|
||||||
|
Link as ChakraLink,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
Kbd,
|
||||||
|
Menu,
|
||||||
|
Separator,
|
||||||
|
Stack,
|
||||||
|
Text
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router';
|
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar } from '../components/ui/avatar';
|
import { Avatar } from '../components/ui/avatar';
|
||||||
|
|
@ -8,6 +18,7 @@ import { Tooltip } from '../components/ui/tooltip';
|
||||||
import { useAuth } from '../hooks/auth';
|
import { useAuth } from '../hooks/auth';
|
||||||
import { useIdentity } from '../hooks/identity';
|
import { useIdentity } from '../hooks/identity';
|
||||||
import { appName } from '../main';
|
import { appName } from '../main';
|
||||||
|
// import { Link as ChakraLink } from '../components/ui/link';
|
||||||
|
|
||||||
const TanStackRouterDevtools =
|
const TanStackRouterDevtools =
|
||||||
process.env.NODE_ENV === 'production'
|
process.env.NODE_ENV === 'production'
|
||||||
|
|
@ -33,7 +44,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
||||||
return (
|
return (
|
||||||
<Stack padding={3}>
|
<Stack padding={3}>
|
||||||
<Stack flexDirection="row" justifyContent="space-between" alignItems="center">
|
<Stack flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
<Stack flexDirection="row" justifyContent="center" alignItems="center" gap={4}>
|
<Stack flexDirection="row" justifyContent="center" alignItems="center" gap={10}>
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Stack direction="row" justifyContent="center" alignItems="center" gap={1}>
|
<Stack direction="row" justifyContent="center" alignItems="center" gap={1}>
|
||||||
<Icon fontSize={24} color="colorPalette.500">
|
<Icon fontSize={24} color="colorPalette.500">
|
||||||
|
|
@ -51,6 +62,21 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
||||||
{/* </Icon> */}
|
{/* </Icon> */}
|
||||||
{/* </Link> */}
|
{/* </Link> */}
|
||||||
{/* </Stack> */}
|
{/* </Stack> */}
|
||||||
|
|
||||||
|
{identity != null && (
|
||||||
|
<>
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<Link to="/editor">
|
||||||
|
<ChakraLink>editor</ChakraLink>
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<Link to="/new/book">
|
||||||
|
<ChakraLink>create</ChakraLink>
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack flexDirection="row" alignItems="center" gap={4}>
|
<Stack flexDirection="row" alignItems="center" gap={4}>
|
||||||
|
|
|
||||||
|
|
@ -1,105 +1,21 @@
|
||||||
import { gql } from '@apollo/client';
|
import { Stack } from '@chakra-ui/react';
|
||||||
import { Skeleton, Stack } from '@chakra-ui/react';
|
|
||||||
import { WorkingListQuery } from '@generated/types';
|
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { useState } from 'react';
|
import { BookSearchList } from '../components/BookSearchList';
|
||||||
import { Pagination } from '../components/Pagination';
|
|
||||||
import { WorkingList } from '../components/WorkingList';
|
|
||||||
import { WorkingListSearch } from '../components/WorkingListSearch';
|
|
||||||
import { usePaginatedQuery } from '../hooks/usePaginatedQuery';
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
export const Route = createFileRoute('/')({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const [workingSearchValue, setWorkingSearchValue] = useState<string>('');
|
|
||||||
const [workingsListPage, setWorkingsListPage] = useState<number>(1);
|
|
||||||
const [workingsListPageSize, setWorkingsListPageSize] = useState<number>(2);
|
|
||||||
const startIndex = (workingsListPage - 1) * workingsListPageSize;
|
|
||||||
|
|
||||||
const { data } = usePaginatedQuery<WorkingListQuery>({
|
|
||||||
query,
|
|
||||||
input: {
|
|
||||||
filter: {
|
|
||||||
terms: workingSearchValue,
|
|
||||||
offset: startIndex,
|
|
||||||
size: workingsListPageSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack justifyContent="center" alignItems="center">
|
<Stack justifyContent="center" alignItems="center">
|
||||||
<Stack width="90%">
|
<Stack width="90%">
|
||||||
{data != null ? (
|
<BookSearchList />
|
||||||
<>
|
|
||||||
<WorkingListSearch
|
|
||||||
value={workingSearchValue}
|
|
||||||
onSubmit={value => {
|
|
||||||
alert(value);
|
|
||||||
}}
|
|
||||||
onChange={value => setWorkingSearchValue(value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WorkingList
|
|
||||||
workingsList={data.workingMany.items.map(item => ({
|
|
||||||
...item,
|
|
||||||
chaptersTotal: item.chapters.total,
|
|
||||||
reviewsTotal: item.reviews.total
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Pagination
|
|
||||||
total={data.workingMany.total}
|
|
||||||
page={workingsListPage}
|
|
||||||
setPage={setWorkingsListPage}
|
|
||||||
pageSize={workingsListPageSize}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Stack direction="row" justifyContent="center" alignItems="center" gap={2}>
|
|
||||||
<Skeleton height={10} width="80%" />
|
|
||||||
<Skeleton height={10} width="calc(20% - 6px)" />
|
|
||||||
</Stack>
|
|
||||||
<Skeleton height={20} />
|
|
||||||
<Skeleton height={20} />
|
|
||||||
<Skeleton height={20} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = gql`
|
|
||||||
query WorkingList($input: WorkingManyInput!) {
|
|
||||||
workingMany(input: $input) {
|
|
||||||
items {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
title
|
|
||||||
description
|
|
||||||
author {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
}
|
|
||||||
updatedAt
|
|
||||||
progressStatus
|
|
||||||
ratingTotal
|
|
||||||
reviewsTotal
|
|
||||||
book {
|
|
||||||
id
|
|
||||||
chaptersTotal
|
|
||||||
wordsTotal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// const workingData1: WorkingCardProps = {
|
// const workingData1: WorkingCardProps = {
|
||||||
// id: '1',
|
// id: '1',
|
||||||
// title: 'Worth the Candle',
|
// title: 'Worth the Candle',
|
||||||
|
|
|
||||||
66
app/client/pages/new/book.tsx
Normal file
66
app/client/pages/new/book.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Heading, Input, Stack } from '@chakra-ui/react';
|
||||||
|
import { Button } from '@client/components/ui/button';
|
||||||
|
import { Field } from '@client/components/ui/field';
|
||||||
|
import { valibotResolver } from '@hookform/resolvers/valibot';
|
||||||
|
import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import * as v from 'valibot';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/new/book')({
|
||||||
|
component: RouteComponent,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
if (!context.isAuthenticated) throw redirect({ to: '/' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<Stack flexDirection={'column'} justifyContent={'center'} alignItems={'center'}>
|
||||||
|
<Heading>New book</Heading>
|
||||||
|
<form></form>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function LoginForm() {
|
||||||
|
// const schema = v.pipe(
|
||||||
|
// v.object({
|
||||||
|
// email: v.pipe(v.string(), v.nonEmpty('Email is required'), v.email('Invalid email')),
|
||||||
|
// password: v.pipe(v.string(), v.nonEmpty('Password is required'))
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// const {
|
||||||
|
// register,
|
||||||
|
// handleSubmit,
|
||||||
|
// formState: { errors }
|
||||||
|
// } = useForm<FormValues>({ resolver: valibotResolver(schema) });
|
||||||
|
//
|
||||||
|
// const { login } = useAuth();
|
||||||
|
//
|
||||||
|
// return (
|
||||||
|
// <form
|
||||||
|
// onSubmit={handleSubmit(async values => {
|
||||||
|
// const session = await login({
|
||||||
|
// email: values.email,
|
||||||
|
// password: values.password
|
||||||
|
// });
|
||||||
|
// if (session != null) window.history.back();
|
||||||
|
// })}
|
||||||
|
// >
|
||||||
|
// <Stack>
|
||||||
|
// <Field label="Email" invalid={errors.email != null} errorText={errors.email?.message}>
|
||||||
|
// <Input {...register('email')} />
|
||||||
|
// </Field>
|
||||||
|
// <Field
|
||||||
|
// label="Password"
|
||||||
|
// invalid={errors.password != null}
|
||||||
|
// errorText={errors.password?.message}
|
||||||
|
// >
|
||||||
|
// <PasswordInput {...register('password')} />
|
||||||
|
// </Field>
|
||||||
|
// <Button type="submit">Sign in</Button>
|
||||||
|
// </Stack>
|
||||||
|
// </form>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
@ -15,11 +15,11 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { Route as rootRoute } from './pages/__root'
|
import { Route as rootRoute } from './pages/__root'
|
||||||
import { Route as RegisterImport } from './pages/register'
|
import { Route as RegisterImport } from './pages/register'
|
||||||
import { Route as LoginImport } from './pages/login'
|
import { Route as LoginImport } from './pages/login'
|
||||||
import { Route as ExampleImport } from './pages/example'
|
|
||||||
import { Route as EditorImport } from './pages/editor'
|
import { Route as EditorImport } from './pages/editor'
|
||||||
import { Route as AboutImport } from './pages/about'
|
import { Route as AboutImport } from './pages/about'
|
||||||
import { Route as IndexImport } from './pages/index'
|
import { Route as IndexImport } from './pages/index'
|
||||||
import { Route as ProfileUsernameImport } from './pages/profile/$username'
|
import { Route as ProfileUsernameImport } from './pages/profile/$username'
|
||||||
|
import { Route as NewBookImport } from './pages/new/book'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
|
|
@ -45,12 +45,6 @@ const LoginRoute = LoginImport.update({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const ExampleRoute = ExampleImport.update({
|
|
||||||
id: '/example',
|
|
||||||
path: '/example',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const EditorRoute = EditorImport.update({
|
const EditorRoute = EditorImport.update({
|
||||||
id: '/editor',
|
id: '/editor',
|
||||||
path: '/editor',
|
path: '/editor',
|
||||||
|
|
@ -75,6 +69,12 @@ const ProfileUsernameRoute = ProfileUsernameImport.update({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const NewBookRoute = NewBookImport.update({
|
||||||
|
id: '/new/book',
|
||||||
|
path: '/new/book',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
// Populate the FileRoutesByPath interface
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
|
|
@ -100,13 +100,6 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof EditorImport
|
preLoaderRoute: typeof EditorImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/example': {
|
|
||||||
id: '/example'
|
|
||||||
path: '/example'
|
|
||||||
fullPath: '/example'
|
|
||||||
preLoaderRoute: typeof ExampleImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/login': {
|
'/login': {
|
||||||
id: '/login'
|
id: '/login'
|
||||||
path: '/login'
|
path: '/login'
|
||||||
|
|
@ -128,6 +121,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof BookLazyImport
|
preLoaderRoute: typeof BookLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/new/book': {
|
||||||
|
id: '/new/book'
|
||||||
|
path: '/new/book'
|
||||||
|
fullPath: '/new/book'
|
||||||
|
preLoaderRoute: typeof NewBookImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/profile/$username': {
|
'/profile/$username': {
|
||||||
id: '/profile/$username'
|
id: '/profile/$username'
|
||||||
path: '/profile/$username'
|
path: '/profile/$username'
|
||||||
|
|
@ -144,10 +144,10 @@ export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/example': typeof ExampleRoute
|
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
'/book': typeof BookLazyRoute
|
||||||
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,10 +155,10 @@ export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/example': typeof ExampleRoute
|
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
'/book': typeof BookLazyRoute
|
||||||
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,10 +167,10 @@ export interface FileRoutesById {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/example': typeof ExampleRoute
|
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
'/book': typeof BookLazyRoute
|
||||||
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,30 +180,30 @@ export interface FileRouteTypes {
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/example'
|
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
| '/book'
|
||||||
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/example'
|
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
| '/book'
|
||||||
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/example'
|
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
| '/book'
|
||||||
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
|
|
@ -212,10 +212,10 @@ export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AboutRoute: typeof AboutRoute
|
AboutRoute: typeof AboutRoute
|
||||||
EditorRoute: typeof EditorRoute
|
EditorRoute: typeof EditorRoute
|
||||||
ExampleRoute: typeof ExampleRoute
|
|
||||||
LoginRoute: typeof LoginRoute
|
LoginRoute: typeof LoginRoute
|
||||||
RegisterRoute: typeof RegisterRoute
|
RegisterRoute: typeof RegisterRoute
|
||||||
BookLazyRoute: typeof BookLazyRoute
|
BookLazyRoute: typeof BookLazyRoute
|
||||||
|
NewBookRoute: typeof NewBookRoute
|
||||||
ProfileUsernameRoute: typeof ProfileUsernameRoute
|
ProfileUsernameRoute: typeof ProfileUsernameRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,10 +223,10 @@ const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AboutRoute: AboutRoute,
|
AboutRoute: AboutRoute,
|
||||||
EditorRoute: EditorRoute,
|
EditorRoute: EditorRoute,
|
||||||
ExampleRoute: ExampleRoute,
|
|
||||||
LoginRoute: LoginRoute,
|
LoginRoute: LoginRoute,
|
||||||
RegisterRoute: RegisterRoute,
|
RegisterRoute: RegisterRoute,
|
||||||
BookLazyRoute: BookLazyRoute,
|
BookLazyRoute: BookLazyRoute,
|
||||||
|
NewBookRoute: NewBookRoute,
|
||||||
ProfileUsernameRoute: ProfileUsernameRoute,
|
ProfileUsernameRoute: ProfileUsernameRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,10 +243,10 @@ export const routeTree = rootRoute
|
||||||
"/",
|
"/",
|
||||||
"/about",
|
"/about",
|
||||||
"/editor",
|
"/editor",
|
||||||
"/example",
|
|
||||||
"/login",
|
"/login",
|
||||||
"/register",
|
"/register",
|
||||||
"/book",
|
"/book",
|
||||||
|
"/new/book",
|
||||||
"/profile/$username"
|
"/profile/$username"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -259,9 +259,6 @@ export const routeTree = rootRoute
|
||||||
"/editor": {
|
"/editor": {
|
||||||
"filePath": "editor.tsx"
|
"filePath": "editor.tsx"
|
||||||
},
|
},
|
||||||
"/example": {
|
|
||||||
"filePath": "example.tsx"
|
|
||||||
},
|
|
||||||
"/login": {
|
"/login": {
|
||||||
"filePath": "login.tsx"
|
"filePath": "login.tsx"
|
||||||
},
|
},
|
||||||
|
|
@ -271,6 +268,9 @@ export const routeTree = rootRoute
|
||||||
"/book": {
|
"/book": {
|
||||||
"filePath": "book.lazy.tsx"
|
"filePath": "book.lazy.tsx"
|
||||||
},
|
},
|
||||||
|
"/new/book": {
|
||||||
|
"filePath": "new/book.tsx"
|
||||||
|
},
|
||||||
"/profile/$username": {
|
"/profile/$username": {
|
||||||
"filePath": "profile/$username.tsx"
|
"filePath": "profile/$username.tsx"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
import { MutationResolvers, QueryResolvers } from '@generated/types';
|
import { MutationResolvers, QueryResolvers } from '@generated/types';
|
||||||
|
|
||||||
/* Query */
|
/* Query */
|
||||||
import { BookOneResolver } from './resolvers/bookOneResolver';
|
|
||||||
import { BooksManyResolver } from './resolvers/booksManyResolver';
|
|
||||||
import { IdentityResolver } from './resolvers/identityResolver';
|
import { IdentityResolver } from './resolvers/identityResolver';
|
||||||
import { UserOneResolver } from './resolvers/userOneResolver';
|
import { UserOneResolver } from './resolvers/userOneResolver';
|
||||||
|
import { BookChapterManyResolver } from './resolvers/bookChapterMany';
|
||||||
|
import { BookChapterOneResolver } from './resolvers/bookChapterOne';
|
||||||
|
import { BookManyResolver } from './resolvers/bookManyResolver';
|
||||||
|
import { BookOneResolver } from './resolvers/bookOneResolver';
|
||||||
|
|
||||||
/* Mutation */
|
/* Mutation */
|
||||||
import { LoginResolver } from './resolvers/auth/login';
|
import { LoginResolver } from './resolvers/auth/login';
|
||||||
|
|
@ -13,11 +15,12 @@ import { LogoutResolver } from './resolvers/auth/logout';
|
||||||
import { SignUpResolver } from './resolvers/auth/signUp';
|
import { SignUpResolver } from './resolvers/auth/signUp';
|
||||||
|
|
||||||
export const Query: QueryResolvers = {
|
export const Query: QueryResolvers = {
|
||||||
booksMany: BooksManyResolver,
|
|
||||||
bookOne: BookOneResolver,
|
bookOne: BookOneResolver,
|
||||||
|
bookMany: BookManyResolver,
|
||||||
|
bookChapterOne: BookChapterOneResolver,
|
||||||
|
bookChapterMany: BookChapterManyResolver,
|
||||||
identity: IdentityResolver,
|
identity: IdentityResolver,
|
||||||
userOne: UserOneResolver
|
userOne: UserOneResolver
|
||||||
// workingMany:
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Mutation: MutationResolvers = {
|
export const Mutation: MutationResolvers = {
|
||||||
|
|
|
||||||
25
app/server/graphql/resolvers/bookChapterMany.ts
Normal file
25
app/server/graphql/resolvers/bookChapterMany.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { BookChapterManyOutput, QueryBookChapterManyArgs } from '@generated/types';
|
||||||
|
import { toBookChapterSchema } from './mappers/bookChapterMapper';
|
||||||
|
|
||||||
|
export async function BookChapterManyResolver(
|
||||||
|
_: any,
|
||||||
|
{ input }: QueryBookChapterManyArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<BookChapterManyOutput> {
|
||||||
|
const { bookId, filter } = input;
|
||||||
|
|
||||||
|
const items = await prisma.bookChapter.findMany({
|
||||||
|
where: {
|
||||||
|
bookId
|
||||||
|
},
|
||||||
|
skip: filter.offset ?? 0,
|
||||||
|
take: filter.size ?? 10
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: await Promise.all(items.map(value => toBookChapterSchema(value))),
|
||||||
|
total: items.length
|
||||||
|
};
|
||||||
|
}
|
||||||
17
app/server/graphql/resolvers/bookChapterOne.ts
Normal file
17
app/server/graphql/resolvers/bookChapterOne.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { BookChapter, QueryBookChapterOneArgs } from '@generated/types';
|
||||||
|
import { toBookChapterSchema } from './mappers/bookChapterMapper';
|
||||||
|
|
||||||
|
export async function BookChapterOneResolver(
|
||||||
|
_: any,
|
||||||
|
{ id }: QueryBookChapterOneArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<BookChapter> {
|
||||||
|
const model = await prisma.bookChapter.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toBookChapterSchema(model);
|
||||||
|
}
|
||||||
29
app/server/graphql/resolvers/bookManyResolver.ts
Normal file
29
app/server/graphql/resolvers/bookManyResolver.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { BookManyOutput, QueryBookManyArgs } from '@generated/types';
|
||||||
|
import { toBookSchema } from './mappers/bookMapper';
|
||||||
|
|
||||||
|
export async function BookManyResolver(
|
||||||
|
_: any,
|
||||||
|
{ input }: QueryBookManyArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<BookManyOutput> {
|
||||||
|
const { filter } = input;
|
||||||
|
const terms = filter.terms?.toLowerCase();
|
||||||
|
|
||||||
|
const items = await prisma.book.findMany({
|
||||||
|
where: {
|
||||||
|
OR:
|
||||||
|
terms != null
|
||||||
|
? [{ title: { contains: terms } }, { author: { username: terms } }]
|
||||||
|
: undefined
|
||||||
|
},
|
||||||
|
skip: filter.offset ?? 0,
|
||||||
|
take: filter.size ?? 10
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: await Promise.all(items.map(value => toBookSchema(value))),
|
||||||
|
total: items.length
|
||||||
|
};
|
||||||
|
}
|
||||||
17
app/server/graphql/resolvers/bookOneResolver.ts
Normal file
17
app/server/graphql/resolvers/bookOneResolver.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma } from '@app/server/prisma/client';
|
||||||
|
import { Book, QueryBookOneArgs } from '@generated/types';
|
||||||
|
import { toBookSchema } from './mappers/bookMapper';
|
||||||
|
|
||||||
|
export async function BookOneResolver(
|
||||||
|
_: any,
|
||||||
|
{ id }: QueryBookOneArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<Book> {
|
||||||
|
const model = await prisma.book.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toBookSchema(model);
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { prisma } from '@app/server/prisma/client';
|
||||||
import { BookChapter as BookChapterSchema } from '@generated/types';
|
import { BookChapter as BookChapterSchema } from '@generated/types';
|
||||||
import { BookChapter } from '@prisma/client';
|
import { BookChapter } from '@prisma/client';
|
||||||
|
|
||||||
export async function toBookChapterMapping(model: BookChapter): Promise<BookChapterSchema> {
|
export async function toBookChapterSchema(model: BookChapter): Promise<BookChapterSchema> {
|
||||||
return {
|
return {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
title: model.title,
|
title: model.title,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
|
import { assertUnreachable } from '@app/client/helpers';
|
||||||
import { call } from '@app/common';
|
import { call } from '@app/common';
|
||||||
import { prisma } from '@app/server/prisma/client';
|
import { prisma } from '@app/server/prisma/client';
|
||||||
import { Book as BookSchema } from '@generated/types';
|
import {
|
||||||
import { Book } from '@prisma/client';
|
Book as BookSchema,
|
||||||
|
WorkingProgressStatus as WorkingProgressStatusSchema
|
||||||
|
} from '@generated/types';
|
||||||
|
import { Book, WorkingProgressStatus } from '@prisma/client';
|
||||||
|
import { toUserSchema } from './userMapper';
|
||||||
|
|
||||||
export async function toBookSchema(model: Book): Promise<BookSchema> {
|
export async function toBookSchema(model: Book): Promise<BookSchema> {
|
||||||
return {
|
return {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
workingId: model.workingId,
|
title: model.title,
|
||||||
|
description: model.description,
|
||||||
|
author: await call(async () => {
|
||||||
|
const userModel = await prisma.user.findUniqueOrThrow({ where: { id: model.authorId } });
|
||||||
|
return toUserSchema(userModel);
|
||||||
|
}),
|
||||||
createdAt: model.createdAt.toISOString(),
|
createdAt: model.createdAt.toISOString(),
|
||||||
updatedAt: model.updatedAt.toISOString(),
|
updatedAt: model.updatedAt.toISOString(),
|
||||||
|
progressStatus: call(() => {
|
||||||
|
ensureWorkingProgressStatus(model.progressStatus);
|
||||||
|
return model.progressStatus;
|
||||||
|
}),
|
||||||
wordsTotal: model.wordsTotal,
|
wordsTotal: model.wordsTotal,
|
||||||
chaptersTotal: await call(async () => {
|
chaptersTotal: await call(async () => {
|
||||||
const chaptersCount = await prisma.bookChapter.count({
|
const chaptersCount = await prisma.bookChapter.count({
|
||||||
|
|
@ -17,6 +31,32 @@ export async function toBookSchema(model: Book): Promise<BookSchema> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return chaptersCount;
|
return chaptersCount;
|
||||||
|
}),
|
||||||
|
// todo: ratings implementation
|
||||||
|
ratingTotal: await call(async () => {
|
||||||
|
const ratingsCount = await prisma.rating.count({
|
||||||
|
where: {
|
||||||
|
bookId: model.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ratingsCount;
|
||||||
|
}),
|
||||||
|
reviewsTotal: await call(async () => {
|
||||||
|
const reviewsCount = await prisma.review.count({
|
||||||
|
where: {
|
||||||
|
bookId: model.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reviewsCount;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureWorkingProgressStatus(
|
||||||
|
value: WorkingProgressStatus
|
||||||
|
): asserts value is WorkingProgressStatusSchema {
|
||||||
|
if (value === WorkingProgressStatus.inProgress) return;
|
||||||
|
if (value === WorkingProgressStatus.frozen) return;
|
||||||
|
if (value === WorkingProgressStatus.complete) return;
|
||||||
|
assertUnreachable(value);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export async function toReviewSchema(model: Review): Promise<ReviewSchema> {
|
||||||
return toUserSchema(userModel);
|
return toUserSchema(userModel);
|
||||||
}),
|
}),
|
||||||
text: model.text,
|
text: model.text,
|
||||||
//todo: ratings implementation
|
// todo: ratings implementation
|
||||||
ratingTotal: await call(async () => {
|
ratingTotal: await call(async () => {
|
||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import { assertUnreachable } from '@app/client/helpers';
|
|
||||||
import { call } from '@app/common';
|
|
||||||
import { prisma } from '@app/server/prisma/client';
|
|
||||||
import {
|
|
||||||
WorkingProgressStatus as WorkingProgressStatusSchema,
|
|
||||||
Working as WorkingSchema,
|
|
||||||
WorkingType as WorkingTypeSchema
|
|
||||||
} from '@generated/types';
|
|
||||||
import { Working, WorkingProgressStatus, WorkingType } from '@prisma/client';
|
|
||||||
import { toUserSchema } from './userMapper';
|
|
||||||
|
|
||||||
export async function toWorkingSchema(model: Working): Promise<WorkingSchema> {
|
|
||||||
return {
|
|
||||||
id: model.id,
|
|
||||||
title: model.title,
|
|
||||||
description: model.description,
|
|
||||||
author: await call(async () => {
|
|
||||||
const userModel = await prisma.user.findUniqueOrThrow({ where: { id: model.authorId } });
|
|
||||||
return toUserSchema(userModel);
|
|
||||||
}),
|
|
||||||
createdAt: model.createdAt.toISOString(),
|
|
||||||
updatedAt: model.updatedAt.toISOString(),
|
|
||||||
type: call(() => {
|
|
||||||
ensureWorkingType(model.type);
|
|
||||||
return model.type;
|
|
||||||
}),
|
|
||||||
progressStatus: call(() => {
|
|
||||||
ensureWorkingProgressStatus(model.progressStatus);
|
|
||||||
return model.progressStatus;
|
|
||||||
}),
|
|
||||||
//todo: ratings implementation
|
|
||||||
//todo: book/notes implementation
|
|
||||||
ratingTotal: await call(async () => {
|
|
||||||
const ratingsCount = await prisma.rating.count({
|
|
||||||
where: {
|
|
||||||
workingId: model.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return ratingsCount;
|
|
||||||
}),
|
|
||||||
reviewsTotal: await call(async () => {
|
|
||||||
const reviewsCount = await prisma.review.count({
|
|
||||||
where: {
|
|
||||||
workingId: model.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return reviewsCount;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureWorkingType(value: WorkingType): asserts value is WorkingTypeSchema {
|
|
||||||
if (value == WorkingType.book) return;
|
|
||||||
if (value == WorkingType.notepad) return;
|
|
||||||
assertUnreachable(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureWorkingProgressStatus(
|
|
||||||
value: WorkingProgressStatus
|
|
||||||
): asserts value is WorkingProgressStatusSchema {
|
|
||||||
if (value == WorkingProgressStatus.inProgress) return;
|
|
||||||
if (value == WorkingProgressStatus.frozen) return;
|
|
||||||
if (value == WorkingProgressStatus.complete) return;
|
|
||||||
assertUnreachable(value);
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,11 @@ scalar Void
|
||||||
type Query {
|
type Query {
|
||||||
identity: Identity!
|
identity: Identity!
|
||||||
userOne(id: ID!): User!
|
userOne(id: ID!): User!
|
||||||
workingMany(input: WorkingManyInput!): WorkingManyOutput!
|
bookOne(id: ID!): Book!
|
||||||
|
bookMany(input: BookManyInput!): BookManyOutput!
|
||||||
|
bookChapterOne(id: ID!): BookChapter!
|
||||||
|
bookChapterMany(input: BookChapterManyInput!): BookChapterManyOutput!
|
||||||
|
reviewMany(input: ReviewManyInput!): ReviewManyOutput!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
|
@ -48,14 +52,8 @@ input PaginationFilterInput {
|
||||||
size: Int
|
size: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
input WorkingManyInput {
|
input BookManyInput {
|
||||||
filter: PaginationFilterInput!
|
filter: PaginationFilterInput!
|
||||||
type: WorkingType!
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WorkingType {
|
|
||||||
book
|
|
||||||
notepad
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WorkingProgressStatus {
|
enum WorkingProgressStatus {
|
||||||
|
|
@ -64,33 +62,23 @@ enum WorkingProgressStatus {
|
||||||
complete
|
complete
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkingManyOutput {
|
type BookManyOutput {
|
||||||
items: [Working!]!
|
items: [Book!]!
|
||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Working {
|
type Book {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String!
|
title: String!
|
||||||
description: String!
|
description: String!
|
||||||
author: User!
|
author: User!
|
||||||
createdAt: String!
|
createdAt: String!
|
||||||
updatedAt: String!
|
updatedAt: String!
|
||||||
type: WorkingType!
|
|
||||||
book: Book
|
|
||||||
notepad: Notepad
|
|
||||||
progressStatus: WorkingProgressStatus!
|
progressStatus: WorkingProgressStatus!
|
||||||
ratingTotal: Int!
|
|
||||||
reviewsTotal: Int!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Book {
|
|
||||||
id: ID!
|
|
||||||
workingId: ID!
|
|
||||||
createdAt: String!
|
|
||||||
updatedAt: String!
|
|
||||||
wordsTotal: Int!
|
wordsTotal: Int!
|
||||||
chaptersTotal: Int!
|
chaptersTotal: Int!
|
||||||
|
ratingTotal: Int!
|
||||||
|
reviewsTotal: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type BookChapter {
|
type BookChapter {
|
||||||
|
|
@ -104,6 +92,7 @@ type BookChapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
input BookChapterManyInput {
|
input BookChapterManyInput {
|
||||||
|
bookId: String!
|
||||||
filter: PaginationFilterInput!
|
filter: PaginationFilterInput!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,10 +101,6 @@ type BookChapterManyOutput {
|
||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notepad {
|
|
||||||
id: ID!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Review {
|
type Review {
|
||||||
id: ID!
|
id: ID!
|
||||||
author: User!
|
author: User!
|
||||||
|
|
@ -124,6 +109,8 @@ type Review {
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReviewManyInput {
|
input ReviewManyInput {
|
||||||
|
bookId: String
|
||||||
|
bookChapterId: String
|
||||||
filter: PaginationFilterInput!
|
filter: PaginationFilterInput!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,37 +21,12 @@ model User {
|
||||||
|
|
||||||
isDarkMode Boolean @default(true)
|
isDarkMode Boolean @default(true)
|
||||||
|
|
||||||
workings Working[]
|
books Book[]
|
||||||
reviews Review[]
|
|
||||||
ratings Rating[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Working {
|
|
||||||
id String @id
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
title String @db.VarChar(255)
|
|
||||||
description String @db.MediumText
|
|
||||||
progressStatus WorkingProgressStatus @default(inProgress)
|
|
||||||
|
|
||||||
type WorkingType
|
|
||||||
|
|
||||||
author User @relation(fields: [authorId], references: [id])
|
|
||||||
authorId String
|
|
||||||
|
|
||||||
book Book?
|
|
||||||
|
|
||||||
reviews Review[]
|
reviews Review[]
|
||||||
ratings Rating[]
|
ratings Rating[]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WorkingType {
|
|
||||||
book
|
|
||||||
notepad
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WorkingProgressStatus {
|
enum WorkingProgressStatus {
|
||||||
inProgress
|
inProgress
|
||||||
frozen
|
frozen
|
||||||
|
|
@ -64,12 +39,18 @@ model Book {
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
title String @db.VarChar(255)
|
||||||
|
description String @db.MediumText
|
||||||
|
progressStatus WorkingProgressStatus @default(inProgress)
|
||||||
|
|
||||||
|
author User @relation(fields: [authorId], references: [id])
|
||||||
|
authorId String
|
||||||
|
|
||||||
wordsTotal Int @default(0)
|
wordsTotal Int @default(0)
|
||||||
|
|
||||||
working Working @relation(fields: [workingId], references: [id])
|
|
||||||
workingId String @unique @db.VarChar(255)
|
|
||||||
|
|
||||||
chapters BookChapter[]
|
chapters BookChapter[]
|
||||||
|
|
||||||
|
reviews Review[]
|
||||||
|
ratings Rating[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model BookChapter {
|
model BookChapter {
|
||||||
|
|
@ -82,6 +63,8 @@ model BookChapter {
|
||||||
foreword String @db.MediumText
|
foreword String @db.MediumText
|
||||||
footnote String @db.MediumText
|
footnote String @db.MediumText
|
||||||
|
|
||||||
|
wordsTotal Int @default(0)
|
||||||
|
|
||||||
book Book @relation(fields: [bookId], references: [id])
|
book Book @relation(fields: [bookId], references: [id])
|
||||||
bookId String @db.VarChar(255)
|
bookId String @db.VarChar(255)
|
||||||
|
|
||||||
|
|
@ -100,11 +83,11 @@ 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)
|
||||||
|
|
||||||
working Working? @relation(fields: [workingId], references: [id])
|
|
||||||
workingId String? @db.VarChar(255)
|
|
||||||
|
|
||||||
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id])
|
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id])
|
||||||
bookChapterId String? @db.VarChar(255)
|
bookChapterId String? @db.VarChar(255)
|
||||||
|
|
||||||
|
book Book? @relation(fields: [bookId], references: [id])
|
||||||
|
bookId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RatingType {
|
enum RatingType {
|
||||||
|
|
@ -123,9 +106,9 @@ model Rating {
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
userId String @db.VarChar(255)
|
userId String @db.VarChar(255)
|
||||||
|
|
||||||
working Working? @relation(fields: [workingId], references: [id])
|
|
||||||
workingId String? @db.VarChar(255)
|
|
||||||
|
|
||||||
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id])
|
bookChapter BookChapter? @relation(fields: [bookChapterId], references: [id])
|
||||||
bookChapterId String? @db.VarChar(255)
|
bookChapterId String? @db.VarChar(255)
|
||||||
|
|
||||||
|
book Book? @relation(fields: [bookId], references: [id])
|
||||||
|
bookId String?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue