diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..45e53b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.next +.git +Dockerfile +npm-debug.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f42c1e6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# 1. Base image +FROM node:20-alpine + +# 2. Set working directory +WORKDIR /app + +# 3. Copy package files +COPY package*.json ./ + +# 4. Install dependencies +RUN npm install + +# 5. Copy all project files +COPY . . + +# 6. Build Next.js app +RUN npm run build + +# 7. Expose port +EXPOSE 3000 + +# 8. Start app +CMD ["npm", "run", "start"] diff --git a/database/database.js b/database/database.js index 29a422e..315c8d9 100644 --- a/database/database.js +++ b/database/database.js @@ -1,5 +1,8 @@ import mongoose from "mongoose"; +const MONGO_URL = process.env.MONGO_URL; + + let cached = global.mongoose; if (!cached) { @@ -7,8 +10,9 @@ if (!cached) { } export async function connectDatabase() { - if (!process.env.MONGO_URL) { - throw new Error("Couldn't find MONGO_URL in environment variables"); + if (!MONGO_URL) { + console.log("Skipping mongo connection."); + return null; } // Prevent reconnecting to mongoose on every request @@ -17,7 +21,7 @@ export async function connectDatabase() { } if (!cached.promise) { - cached.promise = mongoose.connect(process.env.MONGO_URL, { + cached.promise = mongoose.connect(MONGO_URL, { // Prevent queries from hanging when mongodb isn't connected bufferCommands: false, }); diff --git a/pages/api/artworks.js b/pages/api/artworks.js new file mode 100644 index 0000000..dbad535 --- /dev/null +++ b/pages/api/artworks.js @@ -0,0 +1,26 @@ +import { connectDatabase } from '@/database/database'; +import Post from '@/database/schemas/Post'; + +export default async function handler (req, res) { + try { + if (!process.env.MONGO_URL) { + return res.status(200).json([]); + } + await connectDatabase(); + + const limit = parseInt(req.query.limit); + + const artworksRaw = await Post.find( + { type: 'art' }, + { _id: 0, metadata: 0, type: 0, page: 0, hype: 0, __v: 0 } + ) + .sort({ date: -1 }) + .limit(limit) + .lean(); + + res.status(200).json(artworksRaw); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch artworks' }); + } +} diff --git a/pages/api/members.js b/pages/api/members.js new file mode 100644 index 0000000..6beda0f --- /dev/null +++ b/pages/api/members.js @@ -0,0 +1,23 @@ +import { connectDatabase } from '@/database/database'; +import Member from '@/database/schemas/Member'; + +export default async function handler (req, res) { + try { + if (!process.env.MONGO_URL) { + return res.status(200).json([]); + } + await connectDatabase(); + + + const membersData = await Member.find( + {}, + { _id: 0 } + ) + .lean(); + + res.status(200).json(membersData); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch members' }); + } +} diff --git a/pages/art.jsx b/pages/art.jsx index 138ae16..8376e63 100644 --- a/pages/art.jsx +++ b/pages/art.jsx @@ -4,38 +4,26 @@ import SeasonFilter from '@/components/ArtSeasonFilter'; import Carousel from '@/components/Carousel'; import ArtCarouselCard from '@/components/ArtCarosuelCard'; import styles from '@/styles/ArtPage.module.css'; -import { connectDatabase } from '@/database/database'; -import Post from '@/database/schemas/Post'; - -export async function getStaticProps () { - await connectDatabase(); - let artworksRaw = await Post.find( - { type: 'art' }, - { _id: 0, metadata: 0, type: 0, page: 0, hype: 0, __v: 0 } - ).sort({ date: -1 }).lean(); - artworksRaw = artworksRaw.map(item => ({ - ...item, - date: item.date instanceof Date ? item.date.toISOString() : item.date - })); + +export async function getServerSideProps (context) { + const host = context.req.headers.host; + const res = await fetch(`http://${host}/api/artworks`); + const artworksRaw = await res.json(); const artworks = artworksRaw.map(item => { - const d = new Date(item.date); - - return { - src: `/assets/art/${item.link}`, - year: String(d.getFullYear()), - author: item.attr?.join(', ') || '', - description: item.name, - season: getSeason(d.getMonth()) - }; - }); + const d = new Date(item.date); - return { - props: { artworks }, + return { + src: `/assets/art/${item.link}`, + year: String(d.getFullYear()), + author: item.attr?.join(', ') || '', + description: item.name, + season: getSeason(d.getMonth()) + }; + }); - revalidate: process.env.NODE_ENV === 'production' - ? 60 * 60 // Once per hour - : 1 // every second for development + return { + props: { artworks } }; } diff --git a/pages/index.jsx b/pages/index.jsx index e9ccf83..7ff94dc 100644 --- a/pages/index.jsx +++ b/pages/index.jsx @@ -4,35 +4,26 @@ import RecentEventsSection from '@/components/home/RecentEventsSection'; import FanartSection from '@/components/home/FanartSection'; import AMVSection from '@/components/home/AMVSection'; import AboutUsSection from '@/components/home/AboutUsSection'; -import { connectDatabase } from '@/database/database'; -import Post from '@/database/schemas/Post'; // TODO: Move this to database import events from '@/data/Events.json'; -export async function getStaticProps () { - await connectDatabase(); - const artworksRaw = await Post.find( - { type: 'art' }, - { _id: 0, metadata: 0, type: 0, page: 0, hype: 0, __v: 0 } - ).sort({ date: -1 }).limit(6).lean(); +export async function getServerSideProps (context) { + const host = context.req.headers.host; + const res = await fetch(`http://${host}/api/artworks?limit=6`); + const artworksRaw = await res.json(); const artworks = artworksRaw.map(item => { - const d = item.date instanceof Date ? item.date : new Date(item.date); return { src: `/assets/art/${item.link}` }; }); return { - props: { artworks, events }, - - revalidate: process.env.NODE_ENV === 'production' - ? 60 * 60 - : 1 + props: { artworks } }; } -export default function Home ({ artworks, events }) { +export default function Home ({ artworks }) { return (
diff --git a/pages/members.jsx b/pages/members.jsx index 02ea7b8..3069c77 100644 --- a/pages/members.jsx +++ b/pages/members.jsx @@ -4,19 +4,16 @@ import { FaCaretDown } from 'react-icons/fa'; import { useState } from 'react'; import styles from '@/styles/MembersPage.module.css'; import MemberCard from '@/components/MemberCard'; -import { connectDatabase } from '@/database/database'; -import Member from '@/database/schemas/Member'; -export async function getStaticProps () { - await connectDatabase(); - const membersData = await Member.find({}, { _id: 0 }).lean(); - return { - props: { membersData }, +export async function getServerSideProps (context) { + const host = context.req.headers.host; + const res = await fetch(`http://${host}/api/members`); + const membersData = await res.json(); - revalidate: process.env.NODE_ENV === 'production' - ? 60 * 60 // Once per hour - : 1 // every second for development + return { + props: { membersData } }; + } function getMembers (membersData, selectedYear) { diff --git a/tests/site.test.js b/tests/site.test.js index 9f564c6..8d0b179 100644 --- a/tests/site.test.js +++ b/tests/site.test.js @@ -2,7 +2,6 @@ import { describe, test, expect, beforeAll, afterAll } from "vitest"; import { createServer } from "http"; import next from "next"; import { connectDatabase } from "../database/database.js"; -import Member from "../database/schemas/Member.js"; let appServer; let PORT; @@ -12,6 +11,8 @@ beforeAll(async () => { const handle = app.getRequestHandler(); await app.prepare(); + await connectDatabase(); + appServer = createServer((req, res) => handle(req, res)); await new Promise((resolve) => appServer.listen(0, resolve)); PORT = appServer.address().port; @@ -24,19 +25,10 @@ afterAll(async () => { const pages = ["", "events", "members", "newsletters", "videos", "art"]; describe("Server", () => { - beforeAll(async () => { - await connectDatabase(); - }); - describe("Database", () => { test("should connect without throwing an error", async () => { await expect(connectDatabase()).resolves.not.toThrow(); }); - - test("should find the MASK user", async () => { - const member = await Member.findOne({ name: "Tadi Joshua Raj" }); - expect(member?.name).toBe("Tadi Joshua Raj"); - }); }); pages.forEach(page => {