[]>;
17 | desiredUser?: { username: string; _id: string; image: string };
18 | }
19 |
20 | export default function Home({
21 | session,
22 | movies,
23 | singleMovieData,
24 | desiredUser,
25 | }: HomePageProps): React.ReactNode {
26 | if (!session?.user) {
27 | return (
28 |
32 | );
33 | }
34 | if (session?.user?.isBanned) {
35 | return ;
36 | }
37 | // eslint-disable-next-line react-hooks/rules-of-hooks
38 | const { data, error } = useQuery(`movies`, getMovies, {
39 | initialData: movies,
40 | });
41 |
42 | if (error) {
43 | return There was an error locating movie data :(
;
44 | }
45 |
46 | if (!data) {
47 | return ;
48 | }
49 |
50 | return ;
51 | }
52 |
53 | export const getServerSideProps = async (
54 | ctx: GetServerSidePropsContext
55 | ): Promise<{
56 | redirect?: { destination: string; permanent: boolean };
57 |
58 | props?: {
59 | session?: Session | null;
60 | movies?: SerializedMovieType[]>[] | null;
61 | singleMovieData?: SerializedMovieType<
62 | ReviewType[]
63 | > | null;
64 | desiredUser?: { username: string; sub: string; image: string } | null;
65 | };
66 | }> => {
67 | const session = await getSession(ctx);
68 | if (!session?.user) {
69 | let singleMovieData: SerializedMovieType<
70 | ReviewType[]
71 | > | null = null;
72 | let desiredUser = null;
73 |
74 | if (ctx.query.movie) {
75 | singleMovieData = await getMovie(ctx.query.movie, true);
76 | }
77 | if (ctx.query.user) {
78 | try {
79 | desiredUser = await user.findById(ctx.query.user).lean();
80 | } catch (e) {
81 | return { props: { session, movies: [] } };
82 | }
83 | }
84 | return {
85 | props: {
86 | session,
87 | movies: [],
88 | singleMovieData: singleMovieData ? singleMovieData : null,
89 | desiredUser: desiredUser
90 | ? {
91 | username: desiredUser.username,
92 | sub: desiredUser._id.toString(),
93 | image: desiredUser.image,
94 | }
95 | : null,
96 | },
97 | };
98 | }
99 | if (ctx.query.movie) {
100 | return {
101 | redirect: {
102 | destination: `/movie/${ctx.query.movie}${
103 | ctx.query.review === 'true' ? '?review=true' : ''
104 | }`,
105 | permanent: false,
106 | },
107 | };
108 | }
109 | if (ctx.query.user) {
110 | return {
111 | redirect: {
112 | destination: `/user/${ctx.query.user}`,
113 | permanent: false,
114 | },
115 | };
116 | }
117 | let movies = null;
118 | if (session?.user) {
119 | movies = await getMovies();
120 | }
121 |
122 | return { props: { session, movies } };
123 | };
124 |
--------------------------------------------------------------------------------
/pages/movie/[id].tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSidePropsContext } from 'next';
2 | import { Session } from 'next-auth';
3 | import { getSession, useSession } from 'next-auth/client';
4 | import { useRouter } from 'next/router';
5 | import React from 'react';
6 | import { useQuery } from 'react-query';
7 | import AppLayout from '../../components/AppLayout';
8 | import MovieDetailsSection from '../../components/MovieDetailsSection';
9 | import MovieReviewSection from '../../components/MovieReviewSection';
10 | import { ReviewType, SerializedMovieType } from '../../models/movie';
11 | import { PopulatedUserType } from '../../models/user';
12 | import { getMovie } from '../../utils/queries';
13 | import { NextSeo } from 'next-seo';
14 | import ErrorPage from '@components/ErrorPage';
15 | interface MoviePageProps {
16 | movie: SerializedMovieType[]>;
17 | error?: string;
18 | }
19 |
20 | export default function MoviePage({
21 | error,
22 | ...props
23 | }: MoviePageProps): JSX.Element | null {
24 | const [session, loading] = useSession();
25 |
26 | const router = useRouter();
27 | const { id } = router.query;
28 |
29 | const { data, isLoading } = useQuery(
30 | `movie-${props?.movie?.name}`,
31 | async () => {
32 | return await getMovie(id, true);
33 | },
34 |
35 | { initialData: props?.movie }
36 | );
37 |
38 | if ((typeof window !== 'undefined' && loading) || !session) return null;
39 | if (!id) return ;
40 | if (!data) {
41 | if (isLoading) {
42 | return Loading
;
43 | }
44 | return (
45 |
46 | );
47 | }
48 |
49 | const user = session.user;
50 | if (error) {
51 | return There was an error
;
52 | }
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | interface SSRProps {
64 | props?: {
65 | session: Session | null;
66 | movie: SerializedMovieType[]> | null;
67 | };
68 | redirect?: {
69 | destination: string;
70 | permanent: boolean;
71 | };
72 | }
73 |
74 | export async function getServerSideProps(
75 | ctx: GetServerSidePropsContext
76 | ): Promise {
77 | const { id, review } = ctx.query;
78 |
79 | if (!id)
80 | return {
81 | props: { session: null, movie: null },
82 | };
83 | const session = await getSession({ req: ctx.req });
84 | if (!session)
85 | return {
86 | redirect: {
87 | destination: `/?movie=${id}&review=${review}`,
88 | permanent: false,
89 | },
90 | };
91 |
92 | if (session?.user?.isBanned)
93 | return {
94 | redirect: {
95 | destination: `/`,
96 | permanent: false,
97 | },
98 | };
99 |
100 | const movie = await getMovie(id, true);
101 |
102 | return {
103 | props: {
104 | movie,
105 | session,
106 | },
107 | };
108 | }
109 |
--------------------------------------------------------------------------------
/pages/user/[uID].tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Divider, Flex } from '@chakra-ui/react';
3 | import AppLayout from '../../components/AppLayout';
4 | import AboutUserSection from '../../components/AboutUserSection';
5 | import User, { PopulatedUserType, SerializedUser } from '../../models/user';
6 | import { getMovies } from '../../utils/queries';
7 | import { ReviewType, SerializedMovieType } from '../../models/movie';
8 | import UserReviewSection from '../../components/UserReviewSection';
9 | import type { GetServerSidePropsContext } from 'next';
10 | import dbConnect from '../../utils/dbConnect';
11 | import { getSession, useSession } from 'next-auth/client';
12 | import type { Session } from 'next-auth';
13 | import { useQuery } from 'react-query';
14 | import { NextSeo } from 'next-seo';
15 | import ErrorPage from '@components/ErrorPage';
16 |
17 | export interface UserPageUser extends SerializedUser {
18 | sub: string;
19 | }
20 | interface EditUserProps {
21 | desiredUser: UserPageUser | null;
22 | movies: SerializedMovieType[]>[];
23 | }
24 |
25 | function EditUser({ desiredUser, ...props }: EditUserProps): React.ReactNode {
26 | const { data } = useQuery(
27 | 'movies',
28 | async () => {
29 | return await getMovies();
30 | },
31 |
32 | { initialData: props.movies }
33 | );
34 | const movies = data;
35 | const [session, loading] = useSession();
36 | if ((typeof window !== 'undefined' && loading) || !session) return null;
37 | const user = session.user;
38 |
39 | if (!desiredUser) {
40 | return (
41 |
45 | );
46 | }
47 | desiredUser.sub = desiredUser._id as string;
48 | if (!movies) {
49 | return Loading movies :(
;
50 | }
51 |
52 | const allRatings: (
53 | | (ReviewType & {
54 | movie?: { name: string; image?: string; _id: string };
55 | })
56 | | null
57 | )[] = movies
58 | ?.map((movie) => {
59 | const rev:
60 | | (ReviewType & {
61 | movie?: { name: string; image?: string; _id: string };
62 | })
63 | | undefined = movie?.reviews?.find((review) => {
64 | if (!review.user) return false; // If user is deleted and has made a review the user object is null in the review.
65 | return review.user._id === desiredUser._id;
66 | });
67 | if (!rev) {
68 | return null;
69 | }
70 | rev.movie = {
71 | _id: movie._id,
72 | name: movie.name,
73 | image: movie.image,
74 | };
75 | return rev;
76 | })
77 | .filter((x) => x)
78 | .sort((a, b) => (a && b ? a.rating - b.rating : 0))
79 | .reverse();
80 |
81 | return (
82 |
83 |
84 |
85 |
86 |
87 |
88 | {/* */}
89 |
90 |
91 | );
92 | }
93 |
94 | interface SSRProps {
95 | props: {
96 | session: Session | null;
97 | desiredUser: Omit | null;
98 | movies: SerializedMovieType[]>[] | null;
99 | };
100 | }
101 |
102 | interface returnProps {
103 | redirect: {
104 | destination: string;
105 | permanent: boolean;
106 | };
107 | }
108 |
109 | export async function getServerSideProps(
110 | ctx: GetServerSidePropsContext
111 | ): Promise {
112 | const { uID } = ctx.query;
113 | await dbConnect();
114 | const session = await getSession(ctx);
115 | if (!session || session.user.isBanned) {
116 | return { redirect: { destination: `/?user=${uID}`, permanent: false } };
117 | }
118 | let desiredUser;
119 | try {
120 | desiredUser = await User.findById(uID).lean();
121 | } catch (e) {
122 | return { props: { desiredUser: null, session, movies: [] } };
123 | }
124 | if (!desiredUser)
125 | return { props: { desiredUser: null, session, movies: [] } };
126 |
127 | desiredUser._id = desiredUser._id.toString();
128 |
129 | desiredUser.createdAt =
130 | typeof desiredUser.createdAt === 'string'
131 | ? desiredUser.createdAt
132 | : desiredUser.createdAt.toISOString();
133 | desiredUser.updatedAt =
134 | typeof desiredUser.updatedAt === 'string'
135 | ? desiredUser.updatedAt
136 | : desiredUser.updatedAt.toISOString();
137 |
138 | assertsIsSerializedUser(desiredUser);
139 | const movies = await getMovies();
140 | return {
141 | props: {
142 | session,
143 | desiredUser: desiredUser || null,
144 | movies: movies,
145 | },
146 | };
147 | }
148 |
149 | export default EditUser;
150 |
151 | function assertsIsSerializedUser(
152 | user: unknown
153 | ): asserts user is SerializedUser {
154 | if (typeof user === 'object') {
155 | if (user) {
156 | if ('createdAt' in user && 'updatedAt' in user && '_id' in user) {
157 | const { createdAt, updatedAt, _id } = user as {
158 | createdAt: any;
159 | updatedAt: any;
160 | _id: any;
161 | };
162 | if (
163 | typeof createdAt === 'string' &&
164 | typeof updatedAt === 'string' &&
165 | typeof _id === 'string'
166 | ) {
167 | return;
168 | }
169 | }
170 | }
171 | }
172 | throw new Error('User is not serialized');
173 | }
174 |
--------------------------------------------------------------------------------
/pages/users.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { GetServerSidePropsContext } from 'next';
3 | import {
4 | Button,
5 | chakra,
6 | Flex,
7 | Heading,
8 | Menu,
9 | MenuButton,
10 | MenuItem,
11 | MenuList,
12 | } from '@chakra-ui/react';
13 | import UserTable from '../components/UserTable';
14 | import AppLayout from '../components/AppLayout';
15 | import { SerializedUser } from '../models/user';
16 | import { NextSeo } from 'next-seo';
17 | import { getSession, useSession } from 'next-auth/client';
18 | import dbConnect from '../utils/dbConnect';
19 | import { getUsers } from '../utils/queries';
20 | import { useQuery } from 'react-query';
21 | import { Session } from 'next-auth';
22 | import Wave from '../components/Wave';
23 | import { BiChevronDown } from 'react-icons/bi';
24 |
25 | interface UsersProps {
26 | users: SerializedUser[];
27 | }
28 |
29 | function Users({ users }: UsersProps): React.ReactNode {
30 | const [session, loading] = useSession();
31 | const [sort, setSort] = useState('recent');
32 | const { data } = useQuery(`users`, getUsers, { initialData: users });
33 |
34 | if ((typeof window !== 'undefined' && loading) || !session) return null;
35 |
36 | let sortedUsers = data?.sort(
37 | (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
38 | );
39 |
40 | if (sort === 'recent') {
41 | sortedUsers = sortedUsers?.reverse();
42 | }
43 |
44 | return (
45 | <>
46 |
47 |
48 |
55 |
56 | All Users
57 |
58 |
59 |
60 |
81 |
82 |
83 |
84 |
85 | >
86 | );
87 | }
88 |
89 | export const getServerSideProps = async (
90 | ctx: GetServerSidePropsContext
91 | ): Promise<
92 | | {
93 | props: {
94 | session: Session | null;
95 | users: SerializedUser[];
96 | };
97 | }
98 | | {
99 | redirect: {
100 | destination: '/';
101 | permanent: false;
102 | };
103 | }
104 | > => {
105 | await dbConnect();
106 | const session = await getSession(ctx);
107 | if (!session || !session.user.isAdmin || session.user.isBanned)
108 | return { redirect: { destination: '/', permanent: false } };
109 | const users = await getUsers();
110 |
111 | return { props: { session, users } };
112 | };
113 |
114 | export default Users;
115 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mah51/ScuffedMDB/44fb6f0d700b107865dd1b9cc33092f454297aa4/public/favicon.ico
--------------------------------------------------------------------------------
/public/sitePicture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mah51/ScuffedMDB/44fb6f0d700b107865dd1b9cc33092f454297aa4/public/sitePicture.jpg
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .main {
11 | padding: 5rem 0;
12 | flex: 1;
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
19 | .footer {
20 | width: 100%;
21 | height: 100px;
22 | border-top: 1px solid #eaeaea;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | }
27 |
28 | .footer img {
29 | margin-left: 0.5rem;
30 | }
31 |
32 | .footer a {
33 | display: flex;
34 | justify-content: center;
35 | align-items: center;
36 | }
37 |
38 | .title a {
39 | color: #0070f3;
40 | text-decoration: none;
41 | }
42 |
43 | .title a:hover,
44 | .title a:focus,
45 | .title a:active {
46 | text-decoration: underline;
47 | }
48 |
49 | .title {
50 | margin: 0;
51 | line-height: 1.15;
52 | font-size: 4rem;
53 | }
54 |
55 | .title,
56 | .description {
57 | text-align: center;
58 | }
59 |
60 | .description {
61 | line-height: 1.5;
62 | font-size: 1.5rem;
63 | }
64 |
65 | .code {
66 | background: #fafafa;
67 | border-radius: 5px;
68 | padding: 0.75rem;
69 | font-size: 1.1rem;
70 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
71 | Bitstream Vera Sans Mono, Courier New, monospace;
72 | }
73 |
74 | .grid {
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | flex-wrap: wrap;
79 | max-width: 800px;
80 | margin-top: 3rem;
81 | }
82 |
83 | .card {
84 | margin: 1rem;
85 | flex-basis: 45%;
86 | padding: 1.5rem;
87 | text-align: left;
88 | color: inherit;
89 | text-decoration: none;
90 | border: 1px solid #eaeaea;
91 | border-radius: 10px;
92 | transition: color 0.15s ease, border-color 0.15s ease;
93 | }
94 |
95 | .card:hover,
96 | .card:focus,
97 | .card:active {
98 | color: #0070f3;
99 | border-color: #0070f3;
100 | }
101 |
102 | .card h3 {
103 | margin: 0 0 1rem 0;
104 | font-size: 1.5rem;
105 | }
106 |
107 | .card p {
108 | margin: 0;
109 | font-size: 1.25rem;
110 | line-height: 1.5;
111 | }
112 |
113 | .logo {
114 | height: 1em;
115 | }
116 |
117 | @media (max-width: 600px) {
118 | .grid {
119 | width: 100%;
120 | flex-direction: column;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/styles/component_styles/Button.ts:
--------------------------------------------------------------------------------
1 | import { mode, transparentize } from '@chakra-ui/theme-tools';
2 | type Dict = Record;
3 | // eslint-disable-next-line import/no-anonymous-default-export
4 | const Button = {
5 | variants: {
6 | IMDB: (props: Dict): Dict => ({
7 | color: '#F5C518',
8 | bg: 'transparent',
9 | _hover: {
10 | bg: mode(
11 | transparentize(`#F5C518`, 0.25)(props.theme),
12 | transparentize(`#F5C518`, 0.12)(props.theme)
13 | )(props),
14 | },
15 | }),
16 | },
17 | };
18 |
19 | export default Button;
20 |
--------------------------------------------------------------------------------
/styles/component_styles/Switch.ts:
--------------------------------------------------------------------------------
1 | const Switch = {
2 | variants: {
3 | square: (): Record => ({
4 | track: {
5 | borderRadius: 'md',
6 | bg: 'whiteAlpha.200',
7 | },
8 | thumb: {
9 | borderRadius: 'md',
10 | position: 'relative',
11 | },
12 | }),
13 | },
14 | };
15 |
16 | export default Switch;
17 |
--------------------------------------------------------------------------------
/styles/component_styles/index.ts:
--------------------------------------------------------------------------------
1 | import Button from './Button';
2 | import Switch from './Switch';
3 | export { Button, Switch };
4 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #__next {
4 | overflow-x: hidden;
5 | padding: 0;
6 | margin: 0;
7 | height: 100vh;
8 | width: 100vw;
9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
11 | }
12 |
13 | body {
14 | max-height: 100vh !important;
15 | overflow-y: hidden;
16 | }
17 |
18 | a {
19 | color: inherit;
20 | text-decoration: none;
21 | }
22 |
23 | * {
24 | box-sizing: border-box;
25 | }
26 |
27 | .borderRadius-2xl {
28 | border-radius: var(--chakra-radii-2xl);
29 | }
30 |
31 | .borderRadius-xl {
32 | border-radius: var(--chakra-radii-xl);
33 | }
34 |
35 | .borderRadius-md {
36 | border-radius: var(--chakra-radii-md);
37 | }
38 |
39 | .bouncing-arrow {
40 | -webkit-animation: bounce 2s ease-in-out;
41 | animation: bounce 2s ease-in-out;
42 | animation-delay: 5s;
43 | -webkit-animation-delay: 5s;
44 | -webkit-animation-iteration-count: 3;
45 | animation-iteration-count: 3;
46 | }
47 |
48 | @-webkit-keyframes bounce {
49 | 0%,
50 | 25%,
51 | 50%,
52 | 75%,
53 | 100% {
54 | -webkit-transform: translateY(0);
55 | transform: translateY(0);
56 | }
57 | 40% {
58 | -webkit-transform: translateY(-20px);
59 | transform: translateY(-20px);
60 | }
61 | 60% {
62 | -webkit-transform: translateY(-12px);
63 | transform: translateY(-12px);
64 | }
65 | }
66 |
67 | @keyframes bounce {
68 | 0%,
69 | 25%,
70 | 50%,
71 | 75%,
72 | 100% {
73 | -webkit-transform: translateY(0);
74 | transform: translateY(0);
75 | }
76 | 40% {
77 | -webkit-transform: translateY(20px);
78 | transform: translateY(20px);
79 | }
80 | 60% {
81 | -webkit-transform: translateY(12px);
82 | transform: translateY(12px);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/styles/theme.ts:
--------------------------------------------------------------------------------
1 | // 1. import `extendTheme` function
2 | import { extendTheme, ThemeConfig } from '@chakra-ui/react';
3 | import { createBreakpoints } from '@chakra-ui/theme-tools';
4 | import { Button, Switch } from './component_styles';
5 |
6 | // 2. Add your color mode config
7 |
8 | interface CustomTheme extends ThemeConfig {
9 | useSystemColorMode: boolean;
10 | }
11 |
12 | const breakpoints = createBreakpoints({
13 | sm: '30em',
14 | md: '48em',
15 | lg: '62em',
16 | xl: '80em', //1280px
17 | '2xl': '96em',
18 | '3xl': '120em', //1920px
19 | '4xl': '160em', // 2560px
20 | });
21 |
22 | const config: CustomTheme = {
23 | useSystemColorMode: true,
24 | };
25 |
26 | const components = {
27 | Button,
28 | Switch,
29 | };
30 |
31 | // 3. extend the theme
32 | const theme = extendTheme({
33 | config,
34 | breakpoints,
35 | components,
36 | colors: {
37 | brand: {
38 | 300: `#84C9FB`,
39 | },
40 | },
41 | });
42 | export default theme;
43 |
--------------------------------------------------------------------------------
/tsconfig.jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "types": ["next", "jest"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "strictNullChecks": true,
10 | "noImplicitAny": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "allowSyntheticDefaultImports": true,
19 | //"typeRoots": ["./types"],
20 | "baseUrl": ".",
21 | "resolveJsonModule": true,
22 | "paths": {
23 | "@components/*": ["./components/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
27 | "exclude": ["node_modules", "./cypress"],
28 | "typeAcquisition": {
29 | "include": ["react", "next"]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/types/APITypes.ts:
--------------------------------------------------------------------------------
1 | export interface MovieEndpointBodyType {
2 | id: string;
3 | }
4 |
5 | export interface ReviewEndpointBodyType {
6 | movieID: string;
7 | comment?: string;
8 | rating: number;
9 | }
10 |
--------------------------------------------------------------------------------
/types/generalTypes.ts:
--------------------------------------------------------------------------------
1 | export interface DiscordUser {
2 | _id: string;
3 | id: string;
4 | username: string;
5 | avatar: string;
6 | discriminator: string;
7 | public_flags: number;
8 | flags: number;
9 | email: string;
10 | locale: string;
11 | mfa_enabled: boolean;
12 | premium_type: number;
13 | }
14 |
--------------------------------------------------------------------------------
/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import { SerializedUser } from './../models/user';
2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
3 | import NextAuth from 'next-auth';
4 |
5 | declare module 'next-auth' {
6 | /**
7 | * Returned by `useSession`, `getSession` and received as a prop on the `Provider` React Context
8 | */
9 | /**
10 | * The shape of the user object returned in the OAuth providers' `profile` callback,
11 | * or the second parameter of the `session` callback, when using a database.
12 | */
13 |
14 | interface User extends SerializedUser {
15 | id: string;
16 | name: string;
17 | email: string;
18 | image: string;
19 | sub: string;
20 | iat: number;
21 | exp: number;
22 | }
23 |
24 | type UserAuthType = User;
25 | interface Session {
26 | user: User;
27 | }
28 | /**
29 | * Usually contains information about the provider being used
30 | * and also extends `TokenSet`, which is different tokens returned by OAuth Providers.
31 | */
32 | }
33 |
--------------------------------------------------------------------------------
/utils/ModalContext.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-empty-function */
2 | import { PopulatedUserType } from './../models/user';
3 | import React, { Dispatch, SetStateAction } from 'react';
4 | import { ReviewType, SerializedMovieType } from '../models/movie';
5 |
6 | interface ReviewContext {
7 | isOpen: boolean;
8 | onOpen: () => void;
9 | onClose: () => void;
10 | movie: null | SerializedMovieType[]>;
11 | setMovie: Dispatch<
12 | SetStateAction[]> | null>
13 | >;
14 | }
15 |
16 | export const ReviewModalContext = React.createContext({
17 | isOpen: false,
18 | onOpen: () => {},
19 | onClose: () => {},
20 | movie: null,
21 | setMovie: () => {},
22 | });
23 |
--------------------------------------------------------------------------------
/utils/dbConnect.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import movie from '../models/movie';
3 | import user from '../models/user';
4 |
5 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
6 | async function dbConnect(connectionURL = process.env.MONGODB_URI) {
7 | // check if we have a connection to the database or if it's currently
8 | // connecting or disconnecting (readyState 1, 2 and 3)
9 |
10 | if (!mongoose.models?.User) {
11 | user.schema;
12 | }
13 | if (!mongoose.models?.Movie) {
14 | movie.schema;
15 | }
16 | try {
17 | if (mongoose.connection.readyState >= 1) {
18 | return;
19 | }
20 |
21 | if (!connectionURL) {
22 | throw new Error(
23 | 'MONGODB_URI not set in .env.local, cannot connect to the db'
24 | );
25 | }
26 |
27 | return mongoose.connect(connectionURL, {
28 | useNewUrlParser: true,
29 | useUnifiedTopology: true,
30 | useFindAndModify: false,
31 | useCreateIndex: true,
32 | });
33 | } catch (e) {
34 | console.error(e);
35 | }
36 | }
37 |
38 | export default dbConnect;
39 |
--------------------------------------------------------------------------------
/utils/myAdapter.ts:
--------------------------------------------------------------------------------
1 | //@ts-ignore
2 | import { TypeORMLegacyAdapter } from '@next-auth/typeorm-legacy-adapter';
3 | import { AppOptions } from 'next-auth/internals';
4 | import { UserAuthType } from 'next-auth';
5 | import User from '../models/user';
6 | import dbConnect from './dbConnect';
7 |
8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
9 | export default function myAdapter(config: any, options = {}): any {
10 | const { getAdapter: oldAdapter } = TypeORMLegacyAdapter(config, options);
11 | const getAdapter = async (appOptions: AppOptions) => {
12 | const funcs = await oldAdapter(appOptions);
13 | funcs.getUser = async (id: string) => {
14 | const user = await User.findById(id);
15 | return user;
16 | };
17 | funcs.getUserByEmail = async (email: string) => {
18 | const user = await User.findOne({ email });
19 | return user;
20 | };
21 | funcs.createUser = async (profile: UserAuthType) => {
22 | await dbConnect();
23 | const user = new User({
24 | ...profile,
25 | });
26 | await user.save();
27 | return user;
28 | };
29 | return funcs;
30 | };
31 |
32 | return { getAdapter };
33 | }
34 |
--------------------------------------------------------------------------------
/utils/queries.ts:
--------------------------------------------------------------------------------
1 | import { SerializedUser, PopulatedUserType } from './../models/user';
2 | import { ReviewType, SerializedMovieType } from '../models/movie';
3 |
4 | export const getMovies = async (): Promise<
5 | SerializedMovieType[]>[] | null
6 | > => {
7 | const res: Response = await fetch(
8 | `${process.env.NEXT_PUBLIC_APP_URI}/api/movie`
9 | );
10 | // eslint-disable-next-line no-return-await
11 | const unsortedMovies: {
12 | data: SerializedMovieType[]>[];
13 | } = await res.json();
14 |
15 | if (!unsortedMovies?.data) return null;
16 |
17 | const movies: SerializedMovieType[]>[] =
18 | unsortedMovies.data;
19 |
20 | return movies;
21 | };
22 |
23 | export const getMovie = async (
24 | id: string | string[] | undefined,
25 | isLean: boolean
26 | ): Promise[]> | null> => {
27 | if (!id) {
28 | return null;
29 | }
30 | if (Array.isArray(id)) {
31 | id = id.join('');
32 | }
33 | const res: Response = await fetch(
34 | `${process.env.NEXT_PUBLIC_APP_URI}/api/movie/${id}/${
35 | isLean && '?isLean=true'
36 | }`
37 | );
38 |
39 | // eslint-disable-next-line no-return-await
40 | const movie: SerializedMovieType<
41 | ReviewType[]
42 | > = await res.json();
43 |
44 | if (((movie as unknown) as { error: string })?.error) {
45 | return null;
46 | }
47 |
48 | return movie;
49 | };
50 |
51 | export const getUsers = async (): Promise => {
52 | const res: Response = await fetch(
53 | `${process.env.NEXT_PUBLIC_APP_URI}/api/users`
54 | );
55 |
56 | const { users }: { users: SerializedUser[] } = await res.json();
57 |
58 | return users;
59 | };
60 |
--------------------------------------------------------------------------------
/utils/userFlags.ts:
--------------------------------------------------------------------------------
1 | const flags: { [key: string]: number } = {
2 | DISCORD_EMPLOYEE: 1 << 0,
3 | PARTNERED_SERVER_OWNER: 1 << 1,
4 | HYPESQUAD_EVENTS: 1 << 2,
5 | BUGHUNTER_LEVEL_1: 1 << 3,
6 | HOUSE_BRAVERY: 1 << 6,
7 | HOUSE_BRILLIANCE: 1 << 7,
8 | HOUSE_BALANCE: 1 << 8,
9 | EARLY_SUPPORTER: 1 << 9,
10 | TEAM_USER: 1 << 10,
11 | BUGHUNTER_LEVEL_2: 1 << 14,
12 | VERIFIED_BOT: 1 << 16,
13 | EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17,
14 | };
15 |
16 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
17 | export const getFlags = (int: number): string[] => {
18 | const userFlags: string[] = [];
19 | // eslint-disable-next-line no-restricted-syntax
20 | for (const flag in flags) {
21 | if (int & flags[flag]) {
22 | const arr = flag.toLowerCase().split(`_`);
23 | const push = `${
24 | arr[0].slice(0, 1).toUpperCase() + arr[0].slice(1)
25 | } ${arr[1].slice(0, 1).toUpperCase()}${arr[1].slice(1)}`;
26 | userFlags.push(push);
27 | }
28 | }
29 | return userFlags;
30 | };
31 |
--------------------------------------------------------------------------------
/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import { UserAuthType } from 'next-auth';
2 | import { SerializedMovieType, MovieType, ReviewType } from './../models/movie';
3 |
4 | export const getTotalCharCode = (phrase: string): number => {
5 | return phrase?.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
6 | };
7 |
8 | /**
9 | * @description Returns a color based on the hash of the string
10 | * @author mah51
11 | * @param {string} phrase
12 | * @param {string[]} [colors=[
13 | * 'red',
14 | * 'orange',
15 | * 'yellow',
16 | * 'green',
17 | * 'teal',
18 | * 'blue',
19 | * 'cyan',
20 | * 'pink',
21 | * 'purple',
22 | * ]]
23 | * @return {*} {string}
24 | */
25 | export const getColorSchemeCharCode = (
26 | phrase: string,
27 | colors: string[] = themeColors
28 | ): string => {
29 | return colors[getTotalCharCode(phrase) % colors.length];
30 | };
31 |
32 | /**
33 | * @description Returns an array of non-duplicate genres from provided movies.
34 | * @author mah51
35 | * @param {SerializedMovieType[]} movies
36 | * @return {*} {string[]}
37 | */
38 |
39 | export const getMovieGenres = (movies: SerializedMovieType[]): string[] => {
40 | return movies.reduce((a: string[], c: SerializedMovieType) => {
41 | const genres = c.genres;
42 | //@ts-ignore
43 | return [...new Set([...a, ...genres])];
44 | }, []);
45 | };
46 |
47 | interface WebhookData {
48 | type: 'movie' | 'review';
49 | action: 'added' | 'modified' | 'deleted';
50 | user?: UserAuthType;
51 | movie: MovieType[]> | MovieType;
52 | review?: ReviewType;
53 | }
54 |
55 | export const postDataToWebhook = (data: WebhookData): void => {
56 | if (!process.env.WEBHOOK_URL) return;
57 | if (!process.env.WEBHOOK_TOKEN) {
58 | return console.error('Must provide a webhook token to send webhooks');
59 | }
60 |
61 | fetch(process.env.WEBHOOK_URL + `/api/event/${data?.type}`, {
62 | method: 'POST',
63 | headers: {
64 | 'Content-Type': 'application/json',
65 | Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`,
66 | },
67 | body: JSON.stringify(data),
68 | })
69 | // eslint-disable-next-line no-console
70 | .then(() => console.log('posted data to webhook'))
71 | .catch(() => console.error('failed to post data to webhook'));
72 | };
73 |
74 | export const themeColors = [
75 | 'red',
76 | 'orange',
77 | 'yellow',
78 | 'green',
79 | 'teal',
80 | 'blue',
81 | 'cyan',
82 | 'pink',
83 | 'gray',
84 | 'purple',
85 | ];
86 |
87 | export const secondaryThemeColors: Record = {
88 | red: 'yellow',
89 | orange: 'yellow',
90 | yellow: 'red',
91 | green: 'teal',
92 | teal: 'purple',
93 | blue: 'pink',
94 | cyan: 'purple',
95 | pink: 'purple',
96 | purple: 'cyan',
97 | gray: 'red',
98 | };
99 |
100 | export const getSecondaryAccentColor = (): string => {
101 | if (process.env.SECONDARY_COLOR_THEME) {
102 | return process.env.SECONDARY_COLOR_THEME;
103 | }
104 | return secondaryThemeColors[process.env.COLOR_THEME || 'purple'];
105 | };
106 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------