From 1da389a24a57dfc25a76c50fec42bdcc4f05dcf6 Mon Sep 17 00:00:00 2001 From: Timothy Armes Date: Wed, 10 Apr 2024 15:50:30 +0200 Subject: [PATCH] Database collection models --- src/app/[locale]/(main)/clubs/page.tsx | 13 ++----- src/app/[locale]/(main)/tournaments/page.tsx | 17 ++++---- src/auth.ts | 2 +- src/lib/mongodb.ts | 34 ---------------- src/server/addTournament.ts | 14 +++---- src/server/getTournamentDetails.ts | 14 +++---- src/server/models/clubModel.ts | 11 ++++++ src/server/models/tournamentModel.ts | 22 +++++++++++ src/server/models/userModel.ts | 6 +++ src/server/mongodb.ts | 41 ++++++++++++++++++++ src/server/searchTournaments.ts | 12 ++---- yarn.lock | 30 ++++++++++++++ 12 files changed, 139 insertions(+), 77 deletions(-) delete mode 100644 src/lib/mongodb.ts create mode 100644 src/server/models/clubModel.ts create mode 100644 src/server/models/tournamentModel.ts create mode 100644 src/server/models/userModel.ts create mode 100644 src/server/mongodb.ts diff --git a/src/app/[locale]/(main)/clubs/page.tsx b/src/app/[locale]/(main)/clubs/page.tsx index 0e47fe9..68a60aa 100644 --- a/src/app/[locale]/(main)/clubs/page.tsx +++ b/src/app/[locale]/(main)/clubs/page.tsx @@ -1,7 +1,7 @@ import { unstable_cache } from "next/cache"; -import clientPromise from "@/lib/mongodb"; -import { Club, ClubData } from "@/types"; +import { collections, dbConnect } from "@/server/mongodb"; +import { Club } from "@/types"; import { errorLog } from "@/utils/logger"; import ClubsDisplay from "./ClubsDisplay"; @@ -10,13 +10,8 @@ export const revalidate = 3600; // Revalidate cache every 6 hours const getClubs = async () => { try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB"); - const data = await db - .collection("clubs") - .find({}) - .sort({ name: 1 }) - .toArray(); + await dbConnect(); + const data = await collections.clubs!.find({}).sort({ name: 1 }).toArray(); return data .filter((c) => !!c.coordinates) diff --git a/src/app/[locale]/(main)/tournaments/page.tsx b/src/app/[locale]/(main)/tournaments/page.tsx index 57ed868..6f87cbb 100644 --- a/src/app/[locale]/(main)/tournaments/page.tsx +++ b/src/app/[locale]/(main)/tournaments/page.tsx @@ -9,9 +9,9 @@ import { fr } from "date-fns/locale"; import { groupBy } from "lodash"; import { unstable_cache } from "next/cache"; -import clientPromise from "@/lib/mongodb"; -import { TournamentData } from "@/types"; -import { TimeControl, Tournament, tcMap, tournamentDataSchema } from "@/types"; +import { tournamentModelSchema } from "@/server/models/tournamentModel"; +import { collections, dbConnect } from "@/server/mongodb"; +import { TimeControl, Tournament, tcMap } from "@/types"; import { errorLog } from "@/utils/logger"; import TournamentsDisplay from "./TournamentsDisplay"; @@ -20,11 +20,10 @@ setDefaultOptions({ locale: fr }); const getTournaments = async () => { try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB"); - const data = await db - .collection("tournaments") - .aggregate([ + await dbConnect(); + + const data = await collections + .tournaments!.aggregate([ { $addFields: { dateParts: { @@ -52,7 +51,7 @@ const getTournaments = async () => { .toArray(); const bad = data.filter((t) => { - const result = tournamentDataSchema.safeParse(t); + const result = tournamentModelSchema.safeParse(t); if (result.success === false) { console.log(JSON.stringify(result, null, 2)); console.log(JSON.stringify(t, null, 2)); diff --git a/src/auth.ts b/src/auth.ts index f4f3371..be3bccb 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,7 +1,7 @@ import { MongoDBAdapter } from "@auth/mongodb-adapter"; import NextAuth from "next-auth"; -import clientPromise from "@/lib/mongodb"; +import { clientPromise } from "@/server/mongodb"; import Email from "@/utils/nodemailerProvider"; export const { diff --git a/src/lib/mongodb.ts b/src/lib/mongodb.ts deleted file mode 100644 index 4db417c..0000000 --- a/src/lib/mongodb.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { MongoClient } from "mongodb"; - -if (!process.env.MONGODB_URI) { - throw new Error('Invalid environment variable: "MONGODB_URI"'); -} - -const uri = process.env.MONGODB_URI; -const options = {}; - -let client; -let clientPromise: Promise; - -if (!process.env.MONGODB_URI) { - throw new Error("Please add your Mongo URI to .env.local"); -} - -if (process.env.NODE_ENV === "development") { - // In development mode, use a global variable so that the value - // is preserved across module reloads caused by HMR (Hot Module Replacement). - //@ts-ignore - if (!global._mongoClientPromise) { - client = new MongoClient(uri, options); - //@ts-ignore - global._mongoClientPromise = client.connect(); - } - //@ts-ignore - clientPromise = global._mongoClientPromise; -} else { - // In production mode, it's best to not use a global variable. - client = new MongoClient(uri, options); - clientPromise = client.connect(); -} - -export default clientPromise; diff --git a/src/server/addTournament.ts b/src/server/addTournament.ts index 7055493..cc29327 100644 --- a/src/server/addTournament.ts +++ b/src/server/addTournament.ts @@ -1,17 +1,16 @@ "use server"; import { format } from "date-fns"; -import { z } from "zod"; -import clientPromise from "@/lib/mongodb"; import { addTournamentSchema } from "@/schemas"; -import { TournamentData } from "@/types"; +import { collections, dbConnect } from "@/server/mongodb"; import { TimeControl } from "@/types"; import { errorLog } from "@/utils/logger"; +import { TournamentModel } from "./models/tournamentModel"; import { action } from "./safeAction"; -const tcMap: Record = { +const tcMap: Record = { [TimeControl.Classic]: "Cadence Lente", [TimeControl.Rapid]: "Rapide", [TimeControl.Blitz]: "Blitz", @@ -20,12 +19,11 @@ const tcMap: Record = { export const addTournament = action(addTournamentSchema, async (input) => { try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB").collection("tournaments"); + await dbConnect(); const { name, email, message, tournament } = input; - const tournamentData: Omit = { + const tournamentData: Omit = { ...tournament, date: format(tournament.date, "dd/MM/yyyy"), start_date: format(tournament.date, "dd/MM/yyyy"), @@ -37,7 +35,7 @@ export const addTournament = action(addTournamentSchema, async (input) => { status: "scheduled", }; - const result = await db.insertOne(tournamentData); + const result = await collections.tournaments!.insertOne(tournamentData); if (result.insertedId) { const { tournament, country, date, time_control } = tournamentData; diff --git a/src/server/getTournamentDetails.ts b/src/server/getTournamentDetails.ts index 5f10321..6442279 100644 --- a/src/server/getTournamentDetails.ts +++ b/src/server/getTournamentDetails.ts @@ -2,8 +2,7 @@ import { z } from "zod"; -import clientPromise from "@/lib/mongodb"; -import { TournamentData } from "@/types"; +import { collections, dbConnect } from "@/server/mongodb"; import { TimeControl, tcMap } from "@/types"; import { errorLog } from "@/utils/logger"; @@ -15,11 +14,10 @@ const inputSchema = z.object({ export const getTournamentDetails = action(inputSchema, async (input) => { try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB"); - const t = await db - .collection("tournaments") - .findOne({ tournament_id: input.ffeId }); + await dbConnect(); + const t = await collections.tournaments!.findOne({ + tournament_id: input.ffeId, + }); if (t === null) { return null; @@ -29,7 +27,7 @@ export const getTournamentDetails = action(inputSchema, async (input) => { return { id: t._id.toString(), - ffeId: t.tournament_id, + ffeId: t.tournament_id!, tournament: t.tournament, town: t.town, department: t.department, diff --git a/src/server/models/clubModel.ts b/src/server/models/clubModel.ts new file mode 100644 index 0000000..1d15d3d --- /dev/null +++ b/src/server/models/clubModel.ts @@ -0,0 +1,11 @@ +import { ObjectId } from "mongodb"; + +export type ClubModel = { + _id: ObjectId; + name: string; + url?: string; + address?: string; + email?: string; + website?: string; + coordinates: [number, number]; +}; diff --git a/src/server/models/tournamentModel.ts b/src/server/models/tournamentModel.ts new file mode 100644 index 0000000..1c41179 --- /dev/null +++ b/src/server/models/tournamentModel.ts @@ -0,0 +1,22 @@ +import { ObjectId } from "mongodb"; +import { z } from "zod"; + +export const tournamentModelSchema = z.object({ + tournament_id: z.string().nullish(), + town: z.string(), + department: z.string(), + country: z.string(), + tournament: z.string(), + url: z.string(), + time_control: z.string(), + norm_tournament: z.boolean().optional(), + date: z.string(), + start_date: z.string(), + end_date: z.string(), + coordinates: z.array(z.number()).min(2).max(2), + entry_method: z.enum(["manual", "auto"]), + pending: z.boolean().optional(), + status: z.enum(["scheduled", "ongoing", "finished", "in-play"]), +}); + +export type TournamentModel = z.infer; diff --git a/src/server/models/userModel.ts b/src/server/models/userModel.ts new file mode 100644 index 0000000..acdff54 --- /dev/null +++ b/src/server/models/userModel.ts @@ -0,0 +1,6 @@ +import { ObjectId } from "mongodb"; + +export type UserModel = { + email: string; + emailVerified?: Date; +}; diff --git a/src/server/mongodb.ts b/src/server/mongodb.ts new file mode 100644 index 0000000..4eccd15 --- /dev/null +++ b/src/server/mongodb.ts @@ -0,0 +1,41 @@ +import mongoDB, { MongoClient } from "mongodb"; + +import { TournamentModel } from "@/server/models/tournamentModel"; +import { UserModel } from "@/server/models/userModel"; +import { ZoneModel } from "@/server/models/zoneModel"; + +import { ClubModel } from "./models/clubModel"; + +export const collections: { + tournaments?: mongoDB.Collection; + clubs?: mongoDB.Collection; + users?: mongoDB.Collection; +} = {}; + +if (!process.env.MONGODB_URI) { + throw new Error('Invalid environment variable: "MONGODB_URI"'); +} + +const uri = process.env.MONGODB_URI; +const options = {}; + +let client: mongoDB.MongoClient; +export let clientPromise: Promise; + +if (!process.env.MONGODB_URI) { + throw new Error("Please add your Mongo URI to .env.local"); +} + +client = new MongoClient(uri, options); +clientPromise = client.connect(); + +export async function dbConnect() { + await clientPromise; + + const userData: mongoDB.Db = client!.db("userData"); + collections.users = userData.collection("userData"); + + const tournamentData: mongoDB.Db = client!.db("tournamentsFranceDB"); + collections.tournaments = tournamentData.collection("tournaments"); + collections.clubs = tournamentData.collection("clubs"); +} diff --git a/src/server/searchTournaments.ts b/src/server/searchTournaments.ts index fa09c19..60320c5 100644 --- a/src/server/searchTournaments.ts +++ b/src/server/searchTournaments.ts @@ -3,10 +3,8 @@ import { endOfDay } from "date-fns"; import { z } from "zod"; -import clientPromise from "@/lib/mongodb"; -import { TournamentData } from "@/types"; +import { collections, dbConnect } from "@/server/mongodb"; import { TimeControl, Tournament, tcMap } from "@/types"; -import { ExtractSafeActionResult } from "@/types"; import { errorLog } from "@/utils/logger"; import { removeDiacritics } from "@/utils/string"; @@ -32,8 +30,7 @@ export type SearchedTournament = Pick< export const searchTournaments = action(inputSchema, async (input) => { try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB"); + await dbConnect(); const searchTerms = input.searchValue .split(" ") @@ -41,9 +38,8 @@ export const searchTournaments = action(inputSchema, async (input) => { .filter((s) => s !== "") .map((s) => ({ tournament_index: { $regex: s, $options: "i" } })); - const data = await db - .collection("tournaments") - .aggregate([ + const data = await collections + .tournaments!.aggregate([ { $addFields: { dateParts: { diff --git a/yarn.lock b/yarn.lock index 31d7db2..2257d4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1409,6 +1409,13 @@ resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz#a94e1028f180666f97fd51e35c4ad092d7704ef0" integrity sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g== +"@infra-blocks/zod-utils@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@infra-blocks/zod-utils/-/zod-utils-0.4.2.tgz#d2817aade012e26481091fa4b9092764b06fe28e" + integrity sha512-LXvfIA/tsHwfit2CBjkhA0MTr+JfpNMP2z3rTPWN734KeCyuaZPBP6yhTKyPY+X1MoPDbd3RQUVfoM+x8Rodxg== + dependencies: + zod "^3.22.4" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3308,6 +3315,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +geojson@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0" + integrity sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ== + get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" @@ -4018,6 +4030,11 @@ leaflet-defaulticon-compatibility@^0.1.2: resolved "https://registry.yarnpkg.com/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.2.tgz#f5e1a5841aeab9d1682d17887348855a741b3c2a" integrity sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q== +leaflet-draw@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-1.0.4.tgz#45be92f378ed253e7202fdeda1fcc71885198d46" + integrity sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ== + leaflet-gesture-handling@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/leaflet-gesture-handling/-/leaflet-gesture-handling-1.2.2.tgz#ea10afb94f2d477d77d47beb21e409ed327df07a" @@ -4089,6 +4106,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -4834,6 +4856,14 @@ react-leaflet-cluster@^2.1.0: dependencies: leaflet.markercluster "^1.5.3" +react-leaflet-draw@^0.20.4: + version "0.20.4" + resolved "https://registry.yarnpkg.com/react-leaflet-draw/-/react-leaflet-draw-0.20.4.tgz#e3f68dc783cbe00d24ff3f2e02d5208c49f7c971" + integrity sha512-u5JHdow2Z9G2AveyUEOTWHXhdhzXdEVQifkNfSaVbEn0gvD+2xW03TQN444zVqovDBvIrBcVWo1VajL4zgl6yg== + dependencies: + fast-deep-equal "^3.1.3" + lodash-es "^4.17.15" + react-leaflet@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-4.2.1.tgz#c300e9eccaf15cb40757552e181200aa10b94780"