diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index 0b3d515..189aa89 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -11,7 +11,7 @@ const Navbar = () => { Logo -

Frasier Gifs

+

I'm pro opera and I vote!

Upload diff --git a/package.json b/package.json index ac85451..33b2411 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "node ./server.js", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/pages/_app.tsx b/pages/_app.tsx index 4b25ef2..79520d5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,11 +1,12 @@ import React from 'react' import '../styles/globals.css' -import type {AppProps} from 'next/app' -import {PageLayout} from 'components/layout' -import {generateBrowserId} from 'utils/browser' +import type { AppProps } from 'next/app' +import { PageLayout } from 'components/layout' +import { generateBrowserId } from 'utils/browser' import Cookies from 'js-cookie' +import Head from 'next/head' -function MyApp({Component, pageProps}: AppProps) { +function MyApp({ Component, pageProps }: AppProps) { React.useEffect(() => { const checkBrowserId = async () => { const storedId = localStorage.getItem('__effp') @@ -18,9 +19,22 @@ function MyApp({Component, pageProps}: AppProps) { .catch(console.error); }, []) return ( - - - ) + <> + + Frasier Gifs + + + + + + + + + + + + + ) } export default MyApp diff --git a/pages/api/votes.ts b/pages/api/votes.ts index 3e7d4e8..224f2b3 100644 --- a/pages/api/votes.ts +++ b/pages/api/votes.ts @@ -1,37 +1,102 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from "next"; -import {PrismaClient} from "@prisma/client"; -import {getBrowserId} from "utils/browser"; -import qs from 'querystring' +import type { NextApiRequest, NextApiResponse } from "next"; +import { PrismaClient } from "@prisma/client"; +import { getBrowserId } from "utils/browser"; +import qs from "querystring"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse ) { - const { - query: {gifId, isUp}, - } = req; - const prisma = new PrismaClient(); - const browserId = req.cookies.bid; - - const exists = await prisma.votes.count({ - where: { - gifId: gifId as string, - browserId: browserId, - }, - }); - - if (exists !== 0) { - res.status(403).json({ - message: "You have already voted on this gif", - }); - } - const result = await prisma.votes.create({ - data: { - isUp: isUp === "1", - browserId: browserId as string, - gifId: gifId as string, - }, - }); - res.status(200).json(result); + const { + query: { gifId, isUp }, + } = req; + const browserId = req.cookies.bid; + if (!gifId || !browserId) { + return res.status(400); + } + if (isUp === "1") { + return _processUpvote(res, gifId as string, browserId); + } else { + return _processDownvote(res, gifId as string, browserId); + } } +const _processDownvote = async ( + res: NextApiResponse, + gifId: string, + browserId: string +) => { + const prisma = new PrismaClient(); + //check for existing downvote + const exists = await prisma.downVotes.count({ + where: { + gifId: gifId, + browserId: browserId, + }, + }); + + if (exists !== 0) { + return res.status(403).json({ + message: "You have already downvoted on this gif", + }); + } + + //delete any upvotes + try { + await prisma.upVotes.delete({ + where: { + browserId_gifId: { + gifId: gifId, + browserId: browserId, + }, + }, + }); + } catch {} + const result = await prisma.downVotes.create({ + data: { + browserId: browserId as string, + gifId: gifId as string, + }, + }); + return res.status(200).json(result); +}; +const _processUpvote = async ( + res: NextApiResponse, + gifId: string, + browserId: string +) => { + const prisma = new PrismaClient(); + //check for existing upvote + const exists = await prisma.upVotes.count({ + where: { + gifId: gifId, + browserId: browserId, + }, + }); + + if (exists !== 0) { + return res.status(403).json({ + message: "You have already upvoted this gif", + }); + } + + //delete any downvotes + try { + await prisma.downVotes.delete({ + where: { + browserId_gifId: { + gifId: gifId, + browserId: browserId, + }, + }, + }); + } catch {} + + const result = await prisma.upVotes.create({ + data: { + browserId: browserId as string, + gifId: gifId as string, + }, + }); + return res.status(200).json(result); +}; diff --git a/pages/index.tsx b/pages/index.tsx index 3b13acf..af120b3 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,21 +1,21 @@ -import {PrismaClient} from "@prisma/client"; -import type {GetServerSideProps, NextPage} from "next"; -import {Gif} from "models" -import {GifContainer} from "components"; -import {getBrowserId} from "../utils/browser"; +import { PrismaClient } from "@prisma/client"; +import type { GetServerSideProps, NextPage } from "next"; +import { Gif } from "models" +import { GifContainer } from "components"; +import { getBrowserId } from "../utils/browser"; interface IHomeProps { gifs: Gif[] } -const Home: NextPage = ({gifs}) => { +const Home: NextPage = ({ gifs }) => { return (
{gifs.map((gif: Gif) => { return (
- +
) })} @@ -24,51 +24,31 @@ const Home: NextPage = ({gifs}) => { ); }; -export const getServerSideProps: GetServerSideProps = async ({req}) => { +export const getServerSideProps: GetServerSideProps = async ({ req }) => { const browserId = getBrowserId(req.headers.cookie || ''); const prisma = new PrismaClient(); const results = await prisma.gif.findMany({ - take: 12, orderBy: {title: 'asc'}, + take: 12, orderBy: { title: 'asc' }, include: { _count: { select: { - votes: {where: {isUp: true}}, - // votes: { where: { isUp: false } }, //how to achieve + upVotes: true, + downVotes: true, } } } }); const gifs = await Promise.all(results.map(async (gif): Promise => { - const votes = await prisma.votes.count({ - where: { - gifId: gif.id as string, - browserId: browserId, - }, - }) - const upVotes = await prisma.votes.count({ - where: { - gifId: gif.id as string, - browserId: browserId, - isUp: true - }, - }) - const downVotes = await prisma.votes.count({ - where: { - gifId: gif.id as string, - browserId: browserId, - isUp: false - }, - }) return { id: gif.id, title: gif.title, description: gif.description, fileName: gif.fileName, dateCreated: gif.createdAt.toISOString(), - upVotes: upVotes, - downVotes: downVotes, - hasVoted: votes !== 0 + upVotes: gif._count.upVotes, + downVotes: gif._count.downVotes, + hasVoted: false } })) return { diff --git a/prisma/migrations/20221001162558_add_counts/migration.sql b/prisma/migrations/20221001162558_add_counts/migration.sql new file mode 100644 index 0000000..3e7c159 --- /dev/null +++ b/prisma/migrations/20221001162558_add_counts/migration.sql @@ -0,0 +1,39 @@ +/* + Warnings: + + - You are about to drop the `Votes` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Votes" DROP CONSTRAINT "Votes_gifId_fkey"; + +-- DropTable +DROP TABLE "Votes"; + +-- CreateTable +CREATE TABLE "UpVotes" ( + "id" TEXT NOT NULL, + "isUp" BOOLEAN NOT NULL, + "browserId" VARCHAR(1000) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "gifId" TEXT NOT NULL, + + CONSTRAINT "UpVotes_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "DownVotes" ( + "id" TEXT NOT NULL, + "isUp" BOOLEAN NOT NULL, + "browserId" VARCHAR(1000) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "gifId" TEXT NOT NULL, + + CONSTRAINT "DownVotes_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "UpVotes" ADD CONSTRAINT "UpVotes_gifId_fkey" FOREIGN KEY ("gifId") REFERENCES "Gif"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DownVotes" ADD CONSTRAINT "DownVotes_gifId_fkey" FOREIGN KEY ("gifId") REFERENCES "Gif"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20221001163420_remove_direction/migration.sql b/prisma/migrations/20221001163420_remove_direction/migration.sql new file mode 100644 index 0000000..a4f2b55 --- /dev/null +++ b/prisma/migrations/20221001163420_remove_direction/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `isUp` on the `DownVotes` table. All the data in the column will be lost. + - You are about to drop the column `isUp` on the `UpVotes` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "DownVotes" DROP COLUMN "isUp"; + +-- AlterTable +ALTER TABLE "UpVotes" DROP COLUMN "isUp"; diff --git a/prisma/migrations/20221001190625_add_unique_constraintes/migration.sql b/prisma/migrations/20221001190625_add_unique_constraintes/migration.sql new file mode 100644 index 0000000..f18b799 --- /dev/null +++ b/prisma/migrations/20221001190625_add_unique_constraintes/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[browserId,gifId]` on the table `DownVotes` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[browserId,gifId]` on the table `UpVotes` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "DownVotes_browserId_gifId_key" ON "DownVotes"("browserId", "gifId"); + +-- CreateIndex +CREATE UNIQUE INDEX "UpVotes_browserId_gifId_key" ON "UpVotes"("browserId", "gifId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2259c2b..381178c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,17 +20,30 @@ model Gif { //this is temporary, filenames should always match the GUID above fileName String @db.VarChar(100) - votes Votes[] + upVotes UpVotes[] + downVotes DownVotes[] createdAt DateTime @default(now()) } -model Votes { - id String @id @default(uuid()) - isUp Boolean @db.Boolean() - browserId String @db.VarChar(1000) +model UpVotes { + id String @id @default(uuid()) + browserId String @db.VarChar(1000) createdAt DateTime @default(now()) gif Gif @relation(fields: [gifId], references: [id]) gifId String + + @@unique([browserId, gifId]) +} + +model DownVotes { + id String @id @default(uuid()) + browserId String @db.VarChar(1000) + + createdAt DateTime @default(now()) + gif Gif @relation(fields: [gifId], references: [id]) + gifId String + + @@unique([browserId, gifId]) } diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..521cda0 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..9230a01 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..6e230fb Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..027975d Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..e47fad0 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 718d6fe..a1bf24c 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png new file mode 100644 index 0000000..54b000a Binary files /dev/null and b/public/mstile-150x150.png differ diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 0000000..ca4c58d --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,155 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index fbf0e25..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..8c3c857 --- /dev/null +++ b/server.js @@ -0,0 +1,22 @@ +var https = require("https"); +var fs = require("fs"); + +const next = require("next"); +const port = 3000; +const dev = process.env.NODE_ENV !== "production"; +const app = next({ dev, dir: __dirname }); +const handle = app.getRequestHandler(); + +var options = { + key: fs.readFileSync("/etc/letsencrypt/live/fergl.ie/privkey.pem"), + cert: fs.readFileSync("/etc/letsencrypt/live/fergl.ie/cert.pem"), +}; + +app.prepare().then(() => { + https + .createServer(options, (req, res) => handle(req, res)) + .listen(port, (err) => { + if (err) throw err; + console.log(`> Ready on localhost:${port}`); + }); +});