mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 18:52:06 +00:00
add user context and likes
This commit is contained in:
@@ -1,20 +1,84 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import formatDistanceStrict from 'date-fns/formatDistanceStrict';
|
import formatDistanceStrict from 'date-fns/formatDistanceStrict';
|
||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { FaRegThumbsUp, FaThumbsUp } from 'react-icons/fa';
|
import { FaRegThumbsUp, FaThumbsUp } from 'react-icons/fa';
|
||||||
import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||||
|
import UserContext from '@/pages/contexts/userContext';
|
||||||
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost }) => {
|
const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost }) => {
|
||||||
const createdAtDate = new Date(beerPost.createdAt);
|
const createdAtDate = new Date(beerPost.createdAt);
|
||||||
const [timeDistance, setTimeDistance] = useState('');
|
const [timeDistance, setTimeDistance] = useState('');
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeDistance(formatDistanceStrict(new Date(beerPost.createdAt), new Date()));
|
setTimeDistance(formatDistanceStrict(new Date(beerPost.createdAt), new Date()));
|
||||||
}, [beerPost.createdAt]);
|
}, [beerPost.createdAt]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/api/beers/${beerPost.id}/like/is-liked`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
const parsed = APIResponseValidationSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new Error('Invalid API response.');
|
||||||
|
}
|
||||||
|
const { payload } = parsed.data;
|
||||||
|
|
||||||
|
const parsedPayload = z
|
||||||
|
.object({
|
||||||
|
isLiked: z.boolean(),
|
||||||
|
})
|
||||||
|
.safeParse(payload);
|
||||||
|
|
||||||
|
if (!parsedPayload.success) {
|
||||||
|
throw new Error('Invalid API response payload.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isLiked: alreadyLiked } = parsedPayload.data;
|
||||||
|
|
||||||
|
setIsLiked(alreadyLiked);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, [user, beerPost.id]);
|
||||||
|
|
||||||
|
const handleLike = async () => {
|
||||||
|
const response = await fetch(`/api/beers/${beerPost.id}/like`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Something went wrong.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const parsed = APIResponseValidationSchema.safeParse(data);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new Error('Invalid API response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { success, message } = parsed.data;
|
||||||
|
|
||||||
|
setIsLiked(!isLiked);
|
||||||
|
console.log({ success, message });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card flex flex-col justify-center bg-base-300">
|
<div className="card flex flex-col justify-center bg-base-300">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@@ -59,14 +123,16 @@ const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
|
{user && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn gap-2 rounded-2xl ${
|
className={`btn gap-2 rounded-2xl ${
|
||||||
!isLiked ? 'btn-ghost outline' : 'btn-primary'
|
!isLiked ? 'btn-ghost outline' : 'btn-primary'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsLiked(!isLiked);
|
handleLike();
|
||||||
}}
|
}}
|
||||||
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{isLiked ? (
|
{isLiked ? (
|
||||||
<>
|
<>
|
||||||
@@ -80,6 +146,7 @@ const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface FormTextAreaProps {
|
|||||||
error: boolean;
|
error: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
rows: number;
|
rows: number;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +18,7 @@ interface FormTextAreaProps {
|
|||||||
* error={true}
|
* error={true}
|
||||||
* placeholder="Test"
|
* placeholder="Test"
|
||||||
* rows={5}
|
* rows={5}
|
||||||
|
* disabled
|
||||||
* />;
|
* />;
|
||||||
*
|
*
|
||||||
* @param props
|
* @param props
|
||||||
@@ -25,6 +27,7 @@ interface FormTextAreaProps {
|
|||||||
* @param props.error Whether or not the textarea has an error.
|
* @param props.error Whether or not the textarea has an error.
|
||||||
* @param props.id The id of the textarea.
|
* @param props.id The id of the textarea.
|
||||||
* @param props.rows The number of rows to display in the textarea.
|
* @param props.rows The number of rows to display in the textarea.
|
||||||
|
* @param props.disabled Whether or not the textarea is disabled.
|
||||||
*/
|
*/
|
||||||
const FormTextArea: FunctionComponent<FormTextAreaProps> = ({
|
const FormTextArea: FunctionComponent<FormTextAreaProps> = ({
|
||||||
placeholder = '',
|
placeholder = '',
|
||||||
@@ -32,6 +35,7 @@ const FormTextArea: FunctionComponent<FormTextAreaProps> = ({
|
|||||||
error,
|
error,
|
||||||
id,
|
id,
|
||||||
rows,
|
rows,
|
||||||
|
disabled = false,
|
||||||
}) => (
|
}) => (
|
||||||
<textarea
|
<textarea
|
||||||
id={id}
|
id={id}
|
||||||
@@ -41,6 +45,7 @@ const FormTextArea: FunctionComponent<FormTextAreaProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
{...formValidationSchema}
|
{...formValidationSchema}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface FormInputProps {
|
|||||||
type: 'email' | 'password' | 'text' | 'date';
|
type: 'email' | 'password' | 'text' | 'date';
|
||||||
id: string;
|
id: string;
|
||||||
height?: string;
|
height?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +21,7 @@ interface FormInputProps {
|
|||||||
* error={!!errors.name}
|
* error={!!errors.name}
|
||||||
* type="text"
|
* type="text"
|
||||||
* id="name"
|
* id="name"
|
||||||
|
* disabled
|
||||||
* />;
|
* />;
|
||||||
*
|
*
|
||||||
* @param param0 The props for the FormTextInput component
|
* @param param0 The props for the FormTextInput component
|
||||||
@@ -30,6 +32,7 @@ interface FormInputProps {
|
|||||||
* @param param0.type The input type (email, password, text, date).
|
* @param param0.type The input type (email, password, text, date).
|
||||||
* @param param0.id The id of the input.
|
* @param param0.id The id of the input.
|
||||||
* @param param0.height The height of the input.
|
* @param param0.height The height of the input.
|
||||||
|
* @param param0.disabled Whether or not the input is disabled.
|
||||||
*/
|
*/
|
||||||
const FormTextInput: FunctionComponent<FormInputProps> = ({
|
const FormTextInput: FunctionComponent<FormInputProps> = ({
|
||||||
placeholder = '',
|
placeholder = '',
|
||||||
@@ -37,6 +40,7 @@ const FormTextInput: FunctionComponent<FormInputProps> = ({
|
|||||||
error,
|
error,
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
|
disabled = false,
|
||||||
}) => (
|
}) => (
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
@@ -46,6 +50,7 @@ const FormTextInput: FunctionComponent<FormInputProps> = ({
|
|||||||
error ? 'input-error' : ''
|
error ? 'input-error' : ''
|
||||||
}`}
|
}`}
|
||||||
{...formValidationSchema}
|
{...formValidationSchema}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchem
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { Options } from 'next-connect';
|
import { Options } from 'next-connect';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import logger from '../pino/logger';
|
|
||||||
import ServerError from '../util/ServerError';
|
import ServerError from '../util/ServerError';
|
||||||
|
|
||||||
const NextConnectConfig: Options<
|
const NextConnectConfig: Options<
|
||||||
@@ -17,7 +17,6 @@ const NextConnectConfig: Options<
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError(error, req, res) {
|
onError(error, req, res) {
|
||||||
logger.error(error);
|
|
||||||
const message = error instanceof Error ? error.message : 'Internal server error.';
|
const message = error instanceof Error ? error.message : 'Internal server error.';
|
||||||
const statusCode = error instanceof ServerError ? error.statusCode : 500;
|
const statusCode = error instanceof ServerError ? error.statusCode : 500;
|
||||||
res.status(statusCode).json({
|
res.status(statusCode).json({
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import useUser from '@/hooks/useUser';
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
|
import UserContext from './contexts/userContext';
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />;
|
const { user, isLoading, error } = useUser();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ user, isLoading, error }}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
72
pages/api/beers/[id]/like/index.ts
Normal file
72
pages/api/beers/[id]/like/index.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
|
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||||
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
|
import validateRequest from '@/config/zod/middleware/validateRequest';
|
||||||
|
import getCurrentUser from '@/config/auth/middleware/getCurrentUser';
|
||||||
|
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||||
|
import nextConnect from 'next-connect';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import ServerError from '@/config/util/ServerError';
|
||||||
|
|
||||||
|
const likeBeerPost = async (
|
||||||
|
req: UserExtendedNextApiRequest,
|
||||||
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
|
) => {
|
||||||
|
const user = req.user!;
|
||||||
|
const id = req.query.id as string;
|
||||||
|
|
||||||
|
const beer = await getBeerPostById(id);
|
||||||
|
if (!beer) {
|
||||||
|
throw new ServerError('Could not find a beer post with that id', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const alreadyLiked = await DBClient.instance.beerPostLikes.findFirst({
|
||||||
|
where: {
|
||||||
|
beerPostId: id,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (alreadyLiked) {
|
||||||
|
await DBClient.instance.beerPostLikes.delete({
|
||||||
|
where: {
|
||||||
|
id: alreadyLiked.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Successfully unliked beer post',
|
||||||
|
statusCode: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DBClient.instance.beerPostLikes.create({
|
||||||
|
data: {
|
||||||
|
beerPost: { connect: { id } },
|
||||||
|
user: { connect: { id: user.id } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Successfully liked beer post',
|
||||||
|
statusCode: 200,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = nextConnect(NextConnectConfig).post(
|
||||||
|
getCurrentUser,
|
||||||
|
validateRequest({
|
||||||
|
querySchema: z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
likeBeerPost,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default handler;
|
||||||
45
pages/api/beers/[id]/like/is-liked.ts
Normal file
45
pages/api/beers/[id]/like/is-liked.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import getCurrentUser from '@/config/auth/middleware/getCurrentUser';
|
||||||
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
|
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||||
|
import validateRequest from '@/config/zod/middleware/validateRequest';
|
||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import nextConnect from 'next-connect';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const checkIfLiked = async (
|
||||||
|
req: UserExtendedNextApiRequest,
|
||||||
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
|
) => {
|
||||||
|
const user = req.user!;
|
||||||
|
const id = req.query.id as string;
|
||||||
|
|
||||||
|
const alreadyLiked = await DBClient.instance.beerPostLikes.findFirst({
|
||||||
|
where: {
|
||||||
|
beerPostId: id,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Successfully checked if beer post is liked by the current user',
|
||||||
|
statusCode: 200,
|
||||||
|
payload: {
|
||||||
|
isLiked: !!alreadyLiked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = nextConnect(NextConnectConfig).get(
|
||||||
|
getCurrentUser,
|
||||||
|
validateRequest({
|
||||||
|
querySchema: z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
checkIfLiked,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default handler;
|
||||||
@@ -12,8 +12,8 @@ import { BeerPost } from '@prisma/client';
|
|||||||
import { NextPage, GetServerSideProps } from 'next';
|
import { NextPage, GetServerSideProps } from 'next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
import UserContext from '../contexts/userContext';
|
||||||
|
|
||||||
interface BeerPageProps {
|
interface BeerPageProps {
|
||||||
beerPost: BeerPostQueryResult;
|
beerPost: BeerPostQueryResult;
|
||||||
@@ -36,10 +36,13 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
|||||||
beerRecommendations,
|
beerRecommendations,
|
||||||
beerComments,
|
beerComments,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
const [comments, setComments] = useState(beerComments);
|
const [comments, setComments] = useState(beerComments);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setComments(beerComments);
|
setComments(beerComments);
|
||||||
}, [beerComments]);
|
}, [beerComments]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -63,8 +66,16 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
|||||||
<div className="mt-4 flex space-x-3">
|
<div className="mt-4 flex space-x-3">
|
||||||
<div className="w-[60%] space-y-3">
|
<div className="w-[60%] space-y-3">
|
||||||
<div className="card h-96 bg-base-300">
|
<div className="card h-96 bg-base-300">
|
||||||
<div className="card-body">
|
<div className="card-body h-full">
|
||||||
|
{user ? (
|
||||||
<BeerCommentForm beerPost={beerPost} setComments={setComments} />
|
<BeerCommentForm beerPost={beerPost} setComments={setComments} />
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
<span className="text-lg font-bold">
|
||||||
|
Log in to leave a comment.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card h-[135rem] bg-base-300">
|
<div className="card h-[135rem] bg-base-300">
|
||||||
|
|||||||
11
pages/contexts/userContext.ts
Normal file
11
pages/contexts/userContext.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import GetUserSchema from '@/services/user/schema/GetUserSchema';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const UserContext = createContext<{
|
||||||
|
user?: z.infer<typeof GetUserSchema>;
|
||||||
|
error?: unknown;
|
||||||
|
isLoading: boolean;
|
||||||
|
}>({ isLoading: true });
|
||||||
|
|
||||||
|
export default UserContext;
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import Layout from '@/components/ui/Layout';
|
import Layout from '@/components/ui/Layout';
|
||||||
import Spinner from '@/components/ui/Spinner';
|
import Spinner from '@/components/ui/Spinner';
|
||||||
import withPageAuthRequired from '@/config/auth/withPageAuthRequired';
|
import withPageAuthRequired from '@/config/auth/withPageAuthRequired';
|
||||||
import useUser from '@/hooks/useUser';
|
|
||||||
import { GetServerSideProps, NextPage } from 'next';
|
import { GetServerSideProps, NextPage } from 'next';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import UserContext from '../contexts/userContext';
|
||||||
|
|
||||||
const ProtectedPage: NextPage = () => {
|
const ProtectedPage: NextPage = () => {
|
||||||
const { user, isLoading, error } = useUser();
|
const { user, error, isLoading } = useContext(UserContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|||||||
16
prisma/migrations/20230208021759_/migration.sql
Normal file
16
prisma/migrations/20230208021759_/migration.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "BeerPostLikes" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"beerPostId" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMPTZ(3),
|
||||||
|
|
||||||
|
CONSTRAINT "BeerPostLikes_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BeerPostLikes" ADD CONSTRAINT "BeerPostLikes_beerPostId_fkey" FOREIGN KEY ("beerPostId") REFERENCES "BeerPost"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BeerPostLikes" ADD CONSTRAINT "BeerPostLikes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -25,6 +25,7 @@ model User {
|
|||||||
breweryPosts BreweryPost[]
|
breweryPosts BreweryPost[]
|
||||||
beerComments BeerComment[]
|
beerComments BeerComment[]
|
||||||
breweryComments BreweryComment[]
|
breweryComments BreweryComment[]
|
||||||
|
BeerPostLikes BeerPostLikes[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model BeerPost {
|
model BeerPost {
|
||||||
@@ -43,6 +44,17 @@ model BeerPost {
|
|||||||
updatedAt DateTime? @updatedAt @db.Timestamptz(3)
|
updatedAt DateTime? @updatedAt @db.Timestamptz(3)
|
||||||
beerComments BeerComment[]
|
beerComments BeerComment[]
|
||||||
beerImages BeerImage[]
|
beerImages BeerImage[]
|
||||||
|
BeerPostLikes BeerPostLikes[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model BeerPostLikes {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
beerPost BeerPost @relation(fields: [beerPostId], references: [id], onDelete: Cascade)
|
||||||
|
beerPostId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
userId String
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||||
|
updatedAt DateTime? @updatedAt @db.Timestamptz(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
model BeerComment {
|
model BeerComment {
|
||||||
|
|||||||
Reference in New Issue
Block a user