From aef78410632f75d5314ad760613424e32d04ea62 Mon Sep 17 00:00:00 2001 From: tonpascual Date: Sun, 13 Oct 2024 22:56:06 +0800 Subject: [PATCH] refactor: updated middleware, auth, db conn etc. refactor: updated from pr comments fix: updated run commands and types refactor: remove unused code --- .nvmrc | 1 + apps/isomorphic-i18n/package.json | 5 +- .../(hydrogen)/catch/aggregated/page.tsx | 2 +- .../app/[lang]/(hydrogen)/groups/aia/page.tsx | 8 + .../app/[lang]/(hydrogen)/groups/cia/page.tsx | 8 + .../[lang]/(hydrogen)/groups/control/page.tsx | 8 + .../app/[lang]/(hydrogen)/groups/iia/page.tsx | 8 + .../[lang]/(hydrogen)/groups/wbcia/page.tsx | 8 + .../app/[lang]/{signin => sign-in}/page.tsx | 5 +- .../{signin => sign-in}/sign-in-form.tsx | 41 +- .../src/app/_components/alert.tsx | 23 ++ .../src/app/api/aggregated-catch/route.ts | 97 +++-- .../api/auth/[...nextauth]/auth-options.ts | 111 +++-- .../auth/[...nextauth]/mongoose-adapter.ts | 176 ++++++++ .../src/app/i18n/locales/en/nav.json | 3 +- apps/isomorphic-i18n/src/app/mongodb.ts | 24 -- .../app/shared/error/InvalidPayloadError.ts | 9 + .../src/app/shared/error/UserNotFoundError.ts | 9 + apps/isomorphic-i18n/src/config/routes.ts | 7 + apps/isomorphic-i18n/src/global.d.ts | 5 - .../layouts/lithium/lithium-menu-items.tsx | 31 ++ .../src/layouts/lithium/lithium-menu.tsx | 14 + apps/isomorphic-i18n/src/middleware.ts | 69 +--- apps/isomorphic-i18n/src/middleware/const.ts | 1 + apps/isomorphic-i18n/src/middleware/types.ts | 3 + .../isomorphic-i18n/src/middleware/withJwt.ts | 41 ++ .../src/middleware/withLang.ts | 24 ++ .../src/middleware/withPermission.ts | 48 +++ package.json | 3 +- packages/nosql/.eslintrc.json | 11 + .../1724935562431-auth_collections_init.ts | 169 ++++++++ packages/nosql/migrations/connect.ts | 21 + packages/nosql/package.json | 25 ++ packages/nosql/src/index.ts | 48 +++ packages/nosql/src/migrate.ts | 5 + packages/nosql/src/schema/auth.ts | 161 ++++++++ packages/nosql/tsconfig.json | 5 + pnpm-lock.yaml | 382 ++++++++++++++++-- 38 files changed, 1386 insertions(+), 233 deletions(-) create mode 100644 .nvmrc create mode 100644 apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/aia/page.tsx create mode 100644 apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/cia/page.tsx create mode 100644 apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/control/page.tsx create mode 100644 apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/iia/page.tsx create mode 100644 apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/wbcia/page.tsx rename apps/isomorphic-i18n/src/app/[lang]/{signin => sign-in}/page.tsx (96%) rename apps/isomorphic-i18n/src/app/[lang]/{signin => sign-in}/sign-in-form.tsx (69%) create mode 100644 apps/isomorphic-i18n/src/app/_components/alert.tsx create mode 100644 apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/mongoose-adapter.ts delete mode 100644 apps/isomorphic-i18n/src/app/mongodb.ts create mode 100644 apps/isomorphic-i18n/src/app/shared/error/InvalidPayloadError.ts create mode 100644 apps/isomorphic-i18n/src/app/shared/error/UserNotFoundError.ts delete mode 100644 apps/isomorphic-i18n/src/global.d.ts create mode 100644 apps/isomorphic-i18n/src/middleware/const.ts create mode 100644 apps/isomorphic-i18n/src/middleware/types.ts create mode 100644 apps/isomorphic-i18n/src/middleware/withJwt.ts create mode 100644 apps/isomorphic-i18n/src/middleware/withLang.ts create mode 100644 apps/isomorphic-i18n/src/middleware/withPermission.ts create mode 100644 packages/nosql/.eslintrc.json create mode 100644 packages/nosql/migrations/1724935562431-auth_collections_init.ts create mode 100644 packages/nosql/migrations/connect.ts create mode 100644 packages/nosql/package.json create mode 100644 packages/nosql/src/index.ts create mode 100644 packages/nosql/src/migrate.ts create mode 100644 packages/nosql/src/schema/auth.ts create mode 100644 packages/nosql/tsconfig.json diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..25bf17f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/apps/isomorphic-i18n/package.json b/apps/isomorphic-i18n/package.json index 7fcc38f..686605c 100644 --- a/apps/isomorphic-i18n/package.json +++ b/apps/isomorphic-i18n/package.json @@ -42,6 +42,7 @@ "@react-email/section": "0.0.12", "@react-email/tailwind": "0.0.16", "@react-email/text": "0.0.8", + "@repo/nosql": "workspace:*", "@t3-oss/env-nextjs": "^0.10.1", "@tailwindcss/container-queries": "^0.1.1", "@tanstack/react-query": "^5.59.9", @@ -51,6 +52,7 @@ "@trpc/server": "11.0.0-next-beta.264", "@uploadthing/react": "^6.5.1", "accept-language": "^3.0.18", + "bcryptjs": "^2.4.3", "clsx": "^2.1.1", "d3-dsv": "^3.0.1", "date-arithmetic": "^4.1.0", @@ -69,8 +71,6 @@ "lodash": "^4.17.21", "mapbox-gl": "^3.5.2", "maplibre-gl": "^4.5.0", - "mongodb": "^6.8.0", - "mongoose": "^8.5.1", "next": "14.2.3", "next-auth": "^4.24.7", "next-themes": "^0.3.0", @@ -129,6 +129,7 @@ "@repo/typescript-config": "workspace:*", "@tailwindcss/forms": "^0.5.7", "@total-typescript/ts-reset": "^0.5.1", + "@types/bcryptjs": "^2.4.6", "@types/date-arithmetic": "^4.1.4", "@types/google.maps": "^3.55.8", "@types/js-cookie": "^3.0.6", diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/catch/aggregated/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/catch/aggregated/page.tsx index f11f1a6..671a4c8 100644 --- a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/catch/aggregated/page.tsx +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/catch/aggregated/page.tsx @@ -29,7 +29,7 @@ export default function NewPage({
diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/aia/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/aia/page.tsx new file mode 100644 index 0000000..7445996 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/aia/page.tsx @@ -0,0 +1,8 @@ + +export default function AIAPage() { + return ( +
+ AIA +
+ ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/cia/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/cia/page.tsx new file mode 100644 index 0000000..087f806 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/cia/page.tsx @@ -0,0 +1,8 @@ + +export default function CIAPage() { + return ( +
+ CIA +
+ ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/control/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/control/page.tsx new file mode 100644 index 0000000..17a3788 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/control/page.tsx @@ -0,0 +1,8 @@ + +export default function ControlPage() { + return ( +
+ Control +
+ ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/iia/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/iia/page.tsx new file mode 100644 index 0000000..012b3c6 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/iia/page.tsx @@ -0,0 +1,8 @@ + +export default function IIAPage() { + return ( +
+ IIA +
+ ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/wbcia/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/wbcia/page.tsx new file mode 100644 index 0000000..d0c11f6 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/[lang]/(hydrogen)/groups/wbcia/page.tsx @@ -0,0 +1,8 @@ + +export default function WBCIAPage() { + return ( +
+ CIA +
+ ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/[lang]/signin/page.tsx b/apps/isomorphic-i18n/src/app/[lang]/sign-in/page.tsx similarity index 96% rename from apps/isomorphic-i18n/src/app/[lang]/signin/page.tsx rename to apps/isomorphic-i18n/src/app/[lang]/sign-in/page.tsx index 25a09a1..5b8a885 100644 --- a/apps/isomorphic-i18n/src/app/[lang]/signin/page.tsx +++ b/apps/isomorphic-i18n/src/app/[lang]/sign-in/page.tsx @@ -1,6 +1,7 @@ -import SignInForm from '@/app/[lang]/signin/sign-in-form'; -import AuthWrapperOne from '@/app/shared/auth-layout/auth-wrapper-one'; import Image from 'next/image'; + +import SignInForm from '@/app/[lang]/sign-in/sign-in-form'; +import AuthWrapperOne from '@/app/shared/auth-layout/auth-wrapper-one'; import UnderlineShape from '@components/shape/underline'; import { metaObject } from '@/config/site.config'; diff --git a/apps/isomorphic-i18n/src/app/[lang]/signin/sign-in-form.tsx b/apps/isomorphic-i18n/src/app/[lang]/sign-in/sign-in-form.tsx similarity index 69% rename from apps/isomorphic-i18n/src/app/[lang]/signin/sign-in-form.tsx rename to apps/isomorphic-i18n/src/app/[lang]/sign-in/sign-in-form.tsx index 6a9a355..bc4395f 100644 --- a/apps/isomorphic-i18n/src/app/[lang]/signin/sign-in-form.tsx +++ b/apps/isomorphic-i18n/src/app/[lang]/sign-in/sign-in-form.tsx @@ -1,30 +1,45 @@ 'use client'; import Link from 'next/link'; -import { useState } from 'react'; +import { useState, Fragment } from 'react'; import { signIn } from 'next-auth/react'; import { SubmitHandler } from 'react-hook-form'; import { PiArrowRightBold } from 'react-icons/pi'; -import { Checkbox, Password, Button, Input, Text } from 'rizzui'; +import { Checkbox, Password, Button, Input, Text, Loader } from 'rizzui'; +import { useRouter } from 'next/navigation' + import { Form } from '@ui/form'; import { routes } from '@/config/routes'; import { loginSchema, LoginSchema } from '@/validators/login.schema'; +import Alert from '@/app/_components/alert'; const initialValues: LoginSchema = { - email: 'admin@admin.com', - password: 'admin', + email: 'anthony@mountaindev.com', + password: '1234qwer', rememberMe: true, }; export default function SignInForm() { //TODO: why we need to reset it here - const [reset, setReset] = useState({}); + const [loading, setLoading] = useState(false) + const [loginErr, setLoginErr] = useState('') + const [reset, setReset] = useState({}) + const router = useRouter() - const onSubmit: SubmitHandler = (data) => { - console.log(data); - signIn('credentials', { + const onSubmit: SubmitHandler = async (data) => { + setLoading(true) + setLoginErr('') + const resp = await signIn('credentials', { ...data, - }); + redirect: false + }) + + if (resp?.ok) { + router.push('/') + } else if (!resp?.ok && resp?.error) { + setLoginErr(resp?.error) + } + setLoading(false) }; return ( @@ -39,6 +54,7 @@ export default function SignInForm() { > {({ register, formState: { errors } }) => (
+ {loginErr && }
)} diff --git a/apps/isomorphic-i18n/src/app/_components/alert.tsx b/apps/isomorphic-i18n/src/app/_components/alert.tsx new file mode 100644 index 0000000..e932476 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/_components/alert.tsx @@ -0,0 +1,23 @@ +import { Alert as RAlert, Text } from "rizzui" + +import cn from "@utils/class-names"; + +export default function Alert({ + color, + message, + className, +}: { + color: 'danger' | 'success' | 'warning'; + message: string; + className?: string; +}) { + return ( + + {message} + + ) +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/api/aggregated-catch/route.ts b/apps/isomorphic-i18n/src/app/api/aggregated-catch/route.ts index 8103d84..26366a0 100644 --- a/apps/isomorphic-i18n/src/app/api/aggregated-catch/route.ts +++ b/apps/isomorphic-i18n/src/app/api/aggregated-catch/route.ts @@ -1,53 +1,52 @@ -import { NextRequest, NextResponse } from "next/server"; -import clientPromise from "@/app/mongodb"; +import { NextRequest, NextResponse } from 'next/server'; -export async function GET(req: NextRequest) { - try { - const client = await clientPromise; - const db = client.db("kenya"); - const collection = db.collection("legacy_data"); +/** + * TODO: migrate to mongoose + */ - // Filter and aggregate data - const data = await collection - .aggregate([ - { - $match: { - landing_site: "Kenyatta", - }, - }, - { - $project: { - landing_date: { - $dateTrunc: { - date: "$landing_date", - unit: "month", - }, - }, - fish_category: 1, - catch_kg: 1, - }, - }, - { - $group: { - _id: { - landing_date: "$landing_date", - fish_category: "$fish_category", - }, - catch_kg: { $sum: "$catch_kg" }, - }, - }, - { - $sort: { "_id.landing_date": 1, "_id.fish_category": 1 }, - }, - ]) - .toArray(); +export async function GET(req: NextRequest) { + // try { + // const client = await clientPromise; + // const db = client.db('kenya'); + // const collection = db.collection('legacy_data'); + + // // Filter and aggregate data + // const data = await collection.aggregate([ + // { + // $match: { + // landing_site: "Kenyatta" + // } + // }, + // { + // $project: { + // landing_date: { + // $dateTrunc: { + // date: "$landing_date", + // unit: "month" + // } + // }, + // fish_category: 1, + // catch_kg: 1 + // } + // }, + // { + // $group: { + // _id: { + // landing_date: "$landing_date", + // fish_category: "$fish_category" + // }, + // catch_kg: { $sum: "$catch_kg" } + // } + // }, + // { + // $sort: { "_id.landing_date": 1, "_id.fish_category": 1 } + // } + // ]).toArray(); - return NextResponse.json(data); - } catch (error) { - console.error("Error fetching data:", (error as Error).message); - return NextResponse.json( - { error: "Internal Server Error", details: (error as Error).message }, - { status: 500 } - ); - } + // return NextResponse.json(data); + // } catch (error) { + // console.error('Error fetching data:', (error as Error).message); + // return NextResponse.json({ error: 'Internal Server Error', details: (error as Error).message }, { status: 500 }); + // } + return NextResponse.json({}); } diff --git a/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/auth-options.ts b/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/auth-options.ts index 8cf7192..db53577 100644 --- a/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/auth-options.ts +++ b/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/auth-options.ts @@ -1,45 +1,53 @@ import type { NextAuthOptions } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import GoogleProvider from 'next-auth/providers/google'; -import isEqual from 'lodash/isEqual'; -import { pagesOptions } from './pages-options'; +import get from 'lodash/get'; +import pick from 'lodash/pick'; +import bcryptjs from "bcryptjs"; + +import getDb from "@repo/nosql"; +import { UserModel } from "@repo/nosql/schema/auth"; +import { loginSchema } from '@/validators/login.schema'; import { env } from '@/env.mjs'; +import InvalidPayloadError from "@/app/shared/error/InvalidPayloadError"; +import UserNotFoundError from "@/app/shared/error/UserNotFoundError"; +import { MDMongooseAdapter } from "./mongoose-adapter"; export const authOptions: NextAuthOptions = { - // debug: true, - pages: { - ...pagesOptions, - }, + adapter: MDMongooseAdapter(), session: { strategy: 'jwt', - maxAge: 30 * 24 * 60 * 60, // 30 days }, callbacks: { - async session({ session, token }) { + async jwt({ token, user }) { + if (user) { + token.id = user.id + token.email = user.email + token.maxAge = get(user, 'maxAge') + token.groups = get(user, 'groups') + } + return token; + }, + async session({ session, token, user }) { + const expiry = get(token, 'maxAge') + ? { + maxAge: token.maxAge as number, + expires: new Date(Date.now() + ((token.maxAge as number) * 1000)).toISOString(), + } + : {} + return { ...session, + ...expiry, user: { ...session.user, - id: token.idToken as string, + id: token.id as string | undefined, + email: token.email, }, }; }, - async jwt({ token, user }) { - if (user) { - // return user as JWT - token.user = user; - } - return token; - }, - async redirect({ url, baseUrl }) { - const parsedUrl = new URL(url, baseUrl); - if (parsedUrl.searchParams.has('callbackUrl')) { - return `${baseUrl}${parsedUrl.searchParams.get('callbackUrl')}`; - } - if (parsedUrl.origin === baseUrl) { - return url; - } - return baseUrl; + async redirect({ baseUrl }) { + return baseUrl }, }, providers: [ @@ -47,24 +55,45 @@ export const authOptions: NextAuthOptions = { id: 'credentials', name: 'Credentials', credentials: {}, - async authorize(credentials: any) { - // You need to provide your own logic here that takes the credentials - // submitted and returns either a object representing a user or value - // that is false/null if the credentials are invalid - const user = { - email: 'admin@admin.com', - password: 'admin', - }; - - if ( - isEqual(user, { - email: credentials?.email, - password: credentials?.password, + async authorize(credentials) { + const rememberMe = get(credentials, 'rememberMe') === 'true' + const parsedCredentials = + loginSchema.safeParse({ + ...credentials, + rememberMe }) - ) { - return user as any; + + if (parsedCredentials.success) { + const { email, password } = parsedCredentials.data + await getDb() + const user = await UserModel.findOne({ email: email }) + .populate({ + path: 'groups', + populate: { + path: 'permission_id', + model: 'Permission' + } + }) + .lean() + if (!user) throw new UserNotFoundError() + + const passwordsMatch = !user.password + ? false + : await bcryptjs.compare(password, user.password); + + if (passwordsMatch) { + /** + * If remember me is enabled, maxAge is 1 month. + * Otherwise 1 day only. + */ + const maxAge = rememberMe ? 30 * 24 * 60 * 60 : 24 * 60 * 60; + return { + ...pick(user, ['id', 'email', 'groups']), + maxAge + } + } } - return null; + throw new InvalidPayloadError() }, }), GoogleProvider({ diff --git a/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/mongoose-adapter.ts b/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/mongoose-adapter.ts new file mode 100644 index 0000000..c535001 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/api/auth/[...nextauth]/mongoose-adapter.ts @@ -0,0 +1,176 @@ +import type { ObjectId } from "mongoose"; +import type { + Adapter, + AdapterAccount, + AdapterSession, + AdapterUser, + VerificationToken, +} from "next-auth/adapters"; +import mongoose from "mongoose"; + +import getDb from "@repo/nosql"; +import { + AccountModel, + SessionModel, + UserModel, + VerificationTokenModel, +} from "@repo/nosql/schema/auth"; + +export const format = { + /** Takes a MongoDB object and returns a plain old JavaScript object */ + from>(object: Record): T { + const newObject: Record = {}; + for (const key in object) { + if (key === "_id" && object[key] instanceof mongoose.Types.ObjectId) { + newObject.id = object[key].toHexString(); + } else if ( + key === "userId" && + object[key] instanceof mongoose.Types.ObjectId + ) { + newObject[key] = object[key].toHexString(); + } else { + newObject[key] = object[key]; + } + } + return newObject as T; + }, + + /** Takes a plain old JavaScript object and turns it into a MongoDB object */ + to>(object: Record) { + const newObject: Record = { + _id: _id(`${object.id}`), + }; + for (const key in object) { + if (key === "userId") newObject[key] = _id(`${object[key]}`); + else if (key === "id") continue; + else newObject[key] = object[key]; + } + return newObject as T & { _id: ObjectId }; + }, +}; + +/** @internal */ +export function _id(hex?: string) { + if (hex?.length !== 24) return new mongoose.Types.ObjectId(); + return new mongoose.Types.ObjectId(hex); +} + +export function MDMongooseAdapter(): Adapter { + const { from, to } = format; + + return { + async createUser(data) { + await getDb(); + const user = await UserModel.create(data); + return from(user); + }, + async getUser(id) { + await getDb(); + const user = await UserModel.findById(id).lean(); + if (!user) return null; + + return from(user); + }, + async getUserByEmail(email) { + await getDb(); + const user = await UserModel.findOne({ email: email }).lean(); + if (!user) return null; + user.id = user?._id.toString(); + + return from(user); + }, + async createSession(data) { + await getDb(); + const session = to(data); + await SessionModel.create(session); + + return from(session); + }, + async getSessionAndUser(sessionToken) { + await getDb(); + const session = await SessionModel.findOne({ + sessionToken: sessionToken, + }).lean(); + if (!session) return null; + + const user = await UserModel.findById({ + _id: new mongoose.Types.ObjectId(session.userId), + }).lean(); + if (!user) return null; + + return { + user: from(user), + session: from(session), + }; + }, + async updateUser(data) { + await getDb(); + const { _id, ...user } = to(data); + const result = await UserModel.findByIdAndUpdate( + _id, + { user }, + { new: true }, + ).exec(); + return from(result!); + }, + async updateSession(data) { + await getDb(); + const session = await SessionModel.findOneAndUpdate({ + sessionToken: data.sessionToken, + expires: data.expires, + }); + + return from(session!); + }, + async linkAccount(data) { + await getDb(); + const account = await AccountModel.create(data); + + return from(account); + }, + async getUserByAccount(data) { + await getDb(); + const account = await AccountModel.findOne(data); + if (!account) return null; + const user = await UserModel.findById(account.userId).lean(); + if (!user) return null; + + return from(user); + }, + async deleteSession(sessionToken) { + await getDb(); + const session = await SessionModel.findOneAndDelete({ + sessionToken: sessionToken, + }); + + return from(session!); + }, + async createVerificationToken(token) { + await getDb(); + const verificationToken = await VerificationTokenModel.create(token); + + return from(verificationToken); + }, + async useVerificationToken(token) { + await getDb(); + const verificationToken = + await VerificationTokenModel.findOneAndDelete(token).lean(); + if (!verificationToken) return null; + const { _id, ...rest } = verificationToken; + return from(rest); + }, + async deleteUser(id) { + await getDb(); + await Promise.all([ + AccountModel.deleteMany({ userId: id }), + SessionModel.deleteMany({ userId: id }), + UserModel.findByIdAndDelete({ _id: new mongoose.Types.ObjectId(id) }), + ]); + }, + async unlinkAccount(data) { + await getDb(); + const account = await AccountModel.findOneAndDelete(data); + return from(account!); + }, + }; +} diff --git a/apps/isomorphic-i18n/src/app/i18n/locales/en/nav.json b/apps/isomorphic-i18n/src/app/i18n/locales/en/nav.json index 37f22b5..ebe81ee 100644 --- a/apps/isomorphic-i18n/src/app/i18n/locales/en/nav.json +++ b/apps/isomorphic-i18n/src/app/i18n/locales/en/nav.json @@ -121,5 +121,6 @@ "sidebar-menu-support-description": "Effortless Assistance at your Fingertips!", "sidebar-menu-invoice-description": "Professional-looking invoices for each customer order", "sidebar-menu-logistics-description": "Streamline Shipments: Discover Efficiency with our Logistics!", - "sidebar-menu-file-manager-description": "Organize, Access, and Share: Simplify your Digital World with us!" + "sidebar-menu-file-manager-description": "Organize, Access, and Share: Simplify your Digital World with us!", + "sidebar-menu-groups": "Groups" } diff --git a/apps/isomorphic-i18n/src/app/mongodb.ts b/apps/isomorphic-i18n/src/app/mongodb.ts deleted file mode 100644 index 41e3dbd..0000000 --- a/apps/isomorphic-i18n/src/app/mongodb.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MongoClient } from 'mongodb'; - -const uri = process.env.MONGODB_URI; -if (!uri) { - throw new Error('Please add your Mongo URI to .env.local'); -} - -const options = {}; - -let client: MongoClient; -let clientPromise: Promise; - -if (process.env.NODE_ENV === 'development') { - if (!global._mongoClientPromise) { - client = new MongoClient(uri, options); - global._mongoClientPromise = client.connect(); - } - clientPromise = global._mongoClientPromise; -} else { - client = new MongoClient(uri, options); - clientPromise = client.connect(); -} - -export default clientPromise; diff --git a/apps/isomorphic-i18n/src/app/shared/error/InvalidPayloadError.ts b/apps/isomorphic-i18n/src/app/shared/error/InvalidPayloadError.ts new file mode 100644 index 0000000..6780e1f --- /dev/null +++ b/apps/isomorphic-i18n/src/app/shared/error/InvalidPayloadError.ts @@ -0,0 +1,9 @@ +export default class InvalidPayloadError extends Error { + constructor() { + super('Invalid username or password.'); + + this.name = 'InvalidPayloadError'; + + Object.setPrototypeOf(this, InvalidPayloadError.prototype); + } +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/app/shared/error/UserNotFoundError.ts b/apps/isomorphic-i18n/src/app/shared/error/UserNotFoundError.ts new file mode 100644 index 0000000..6208b07 --- /dev/null +++ b/apps/isomorphic-i18n/src/app/shared/error/UserNotFoundError.ts @@ -0,0 +1,9 @@ +export default class UserNotFoundError extends Error { + constructor() { + super("User doesn't exist."); + + this.name = 'UserNotFoundError'; + + Object.setPrototypeOf(this, UserNotFoundError.prototype); + } +} \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/config/routes.ts b/apps/isomorphic-i18n/src/config/routes.ts index f4fa3e1..36fce3d 100644 --- a/apps/isomorphic-i18n/src/config/routes.ts +++ b/apps/isomorphic-i18n/src/config/routes.ts @@ -90,6 +90,13 @@ export const routes = { aggregated: '/catch/aggregated', composition: '/catch/composition', }, + groups: { + control: '/groups/control', + iia: '/groups/iia', + cia: '/groups/cia', + wbcia: '/groups/wbcia', + aia: '/groups/aia', + }, tables: { basic: '/tables/basic', collapsible: '/tables/collapsible', diff --git a/apps/isomorphic-i18n/src/global.d.ts b/apps/isomorphic-i18n/src/global.d.ts deleted file mode 100644 index cd94e3f..0000000 --- a/apps/isomorphic-i18n/src/global.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MongoClient } from 'mongodb'; - -declare global { - var _mongoClientPromise: Promise; -} diff --git a/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu-items.tsx b/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu-items.tsx index 1473f6f..6745031 100644 --- a/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu-items.tsx +++ b/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu-items.tsx @@ -107,6 +107,37 @@ export const lithiumMenuItems: LithiumMenuItem = { }, ], }, + groups: { + name: 'sidebar-menu-groups', + type: 'link', + dropdownItems: [ + { + name: 'Control', + href: routes.groups.control, + icon: 'PageBlankIcon', + }, + { + name: 'IIA', + href: routes.groups.iia, + icon: 'PageBlankIcon', + }, + { + name: 'CIA', + href: routes.groups.cia, + icon: 'PageBlankIcon', + }, + { + name: 'WBCIA', + href: routes.groups.wbcia, + icon: 'PageBlankIcon', + }, + { + name: 'AIA', + href: routes.groups.aia, + icon: 'PageBlankIcon', + }, + ], + }, }; export type LithiumMenuItemsKeys = keyof typeof lithiumMenuItems; diff --git a/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu.tsx b/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu.tsx index 8b71997..449a7eb 100644 --- a/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu.tsx +++ b/apps/isomorphic-i18n/src/layouts/lithium/lithium-menu.tsx @@ -277,6 +277,20 @@ export default function HeaderMenuLeft({ lang }: { lang?: string }) { + + + + + +
+ +
+
+
); diff --git a/apps/isomorphic-i18n/src/middleware.ts b/apps/isomorphic-i18n/src/middleware.ts index 0493d5b..0993876 100644 --- a/apps/isomorphic-i18n/src/middleware.ts +++ b/apps/isomorphic-i18n/src/middleware.ts @@ -1,62 +1,19 @@ -import { pagesOptions } from "@/app/api/auth/[...nextauth]/pages-options"; -import withAuth from "next-auth/middleware"; -import { NextResponse } from "next/server"; -import acceptLanguage from "accept-language"; -import { fallbackLng, languages } from "./app/i18n/settings"; +import { NextResponse } from "next/server" -acceptLanguage.languages(languages); +import withJwt from "@/middleware/withJwt" +import withLang from "@/middleware/withLang" +import withPermission from "@/middleware/withPermission" -export default withAuth({ - pages: { - ...pagesOptions, - }, -}); +export function defaultMiddleware() { + return NextResponse.next(); +} + +export default withPermission(withJwt(withLang(defaultMiddleware))) export const config = { - // restricted routes matcher: [ - "/", - "/analytics", - "/logistics/:path*", - "/ecommerce/:path*", - "/support/:path*", - "/file/:path*", - "/file-manager", - "/invoice/:path*", - "/forms/profile-settings/:path*", - "/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js).*)", + '/', + '/sign-in', + '/(en|de|es|ar|he|zh)/:path*', ], -}; - -const cookieName = "i18next"; - -export function middleware(req: any) { - if ( - // req.nextUrl.pathname.indexOf('icon') > -1 || - req.nextUrl.pathname.indexOf("chrome") > -1 - ) - return NextResponse.next(); - let lang; - - if (req.cookies.has(cookieName)) lang = acceptLanguage.get(req.cookies.get(cookieName).value); - if (!lang) lang = acceptLanguage.get(req.headers.get("Accept-Language")); - if (!lang) lang = fallbackLng; - - // Redirect if lng in path is not supported - if ( - !languages.some((local) => req.nextUrl.pathname.startsWith(`/${local}`)) && - !req.nextUrl.pathname.startsWith("/_next") - ) { - return NextResponse.redirect(new URL(`/${lang}${req.nextUrl.pathname}`, req.url)); - } - - if (req.headers.has("referer")) { - const refererUrl = new URL(req.headers.get("referer")); - const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`)); - const response = NextResponse.next(); - if (lngInReferer) response.cookies.set(cookieName, lngInReferer); - return response; - } - - return NextResponse.next(); -} +}; \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/middleware/const.ts b/apps/isomorphic-i18n/src/middleware/const.ts new file mode 100644 index 0000000..6666d93 --- /dev/null +++ b/apps/isomorphic-i18n/src/middleware/const.ts @@ -0,0 +1 @@ +export const JWT_COOKIE_NAME = 'next-auth.session-token' \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/middleware/types.ts b/apps/isomorphic-i18n/src/middleware/types.ts new file mode 100644 index 0000000..1ec2b70 --- /dev/null +++ b/apps/isomorphic-i18n/src/middleware/types.ts @@ -0,0 +1,3 @@ +import type { NextMiddleware } from "next/server"; + +export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware; diff --git a/apps/isomorphic-i18n/src/middleware/withJwt.ts b/apps/isomorphic-i18n/src/middleware/withJwt.ts new file mode 100644 index 0000000..16572ae --- /dev/null +++ b/apps/isomorphic-i18n/src/middleware/withJwt.ts @@ -0,0 +1,41 @@ +import type { NextFetchEvent, NextMiddleware, NextRequest } from "next/server" +import { NextResponse } from "next/server" + +import type { MiddlewareFactory } from "./types" +import { JWT_COOKIE_NAME } from "./const" + +const withJwt: MiddlewareFactory = (next: NextMiddleware) => { + return async (request: NextRequest, _next: NextFetchEvent) => { + const res = await next(request, _next) + const [cookieToken] = request.cookies + .getAll() + .filter((o) => o.name.indexOf(JWT_COOKIE_NAME) > -1) + + const loginPageMatches = /.*\/(?(sign-in))$/gim.exec( + request.nextUrl.pathname, + ) + if (loginPageMatches?.groups?.page && + !cookieToken + ) { + return res + } + + if (loginPageMatches?.groups?.page && + cookieToken + ) { + return NextResponse.redirect( + new URL(`/`, request.url), + ) + } + + if (!cookieToken) { + return NextResponse.redirect( + new URL(`/sign-in`, request.url), + ) + } + + return res + } +} + +export default withJwt diff --git a/apps/isomorphic-i18n/src/middleware/withLang.ts b/apps/isomorphic-i18n/src/middleware/withLang.ts new file mode 100644 index 0000000..49bed13 --- /dev/null +++ b/apps/isomorphic-i18n/src/middleware/withLang.ts @@ -0,0 +1,24 @@ +import type { NextFetchEvent, NextMiddleware, NextRequest } from "next/server" +import { NextResponse } from "next/server" + +import { fallbackLng, languages } from "@/app/i18n/settings"; +import type { MiddlewareFactory } from "./types"; + +const withLang: MiddlewareFactory = (next: NextMiddleware) => { + return async (request: NextRequest, _next: NextFetchEvent) => { + const res = await next(request, _next) + const cookieName = "i18next"; + const lang = + request.cookies.has(cookieName) + ? request.cookies.get(cookieName)?.value + : fallbackLng + + if (!languages.some((local) => request.nextUrl.pathname.startsWith(`/${local}`))) { + return NextResponse.redirect(new URL(`/${lang}${request.nextUrl.pathname}${request.nextUrl.search}`, request.url)); + } + + return res + }; +}; + +export default withLang; \ No newline at end of file diff --git a/apps/isomorphic-i18n/src/middleware/withPermission.ts b/apps/isomorphic-i18n/src/middleware/withPermission.ts new file mode 100644 index 0000000..1455427 --- /dev/null +++ b/apps/isomorphic-i18n/src/middleware/withPermission.ts @@ -0,0 +1,48 @@ +import type { NextFetchEvent, NextMiddleware, NextRequest } from "next/server" +import { NextResponse } from "next/server" +import { getToken } from 'next-auth/jwt' + +import { TPermission } from "@repo/nosql/schema/auth" +import type { MiddlewareFactory } from "./types" +import { JWT_COOKIE_NAME } from "./const" + +const withPermission: MiddlewareFactory = (next: NextMiddleware) => { + return async (request: NextRequest, _next: NextFetchEvent) => { + const res = await next(request, _next) + const groupPageMatches = /.*\/groups\/(?[^/]+)/gim.exec( + request.nextUrl.pathname, + ) + + if (!groupPageMatches?.groups?.group) { + return res + } + + const [cookieToken] = request.cookies + .getAll() + .filter((o) => o.name.indexOf(JWT_COOKIE_NAME) > -1) + const secret = process.env.NEXTAUTH_SECRET + if (!secret) { + throw new Error('Secret is not set.') + } + + const payload = await getToken({ + req: request, + secret, + cookieName: cookieToken.name, + }) + + const groups: TPermission[] = payload?.groups as TPermission[] + const hasGroup = groups + .filter(item => (item.name).toLowerCase() === (groupPageMatches?.groups?.group ?? '').toLowerCase()) + + if (hasGroup.length === 0) { + return NextResponse.redirect( + new URL(`/?error=10001`, request.url), + ) + } + + return res; + }; +}; + +export default withPermission; diff --git a/package.json b/package.json index 14a3f57..5bea16a 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,11 @@ "i18n:clean": "turbo clean --filter=i18n" }, "devDependencies": { + "@repo/typescript-config": "workspace:*", + "dotenv": "^16.4.5", "eslint": "^8.57.0", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", - "@repo/typescript-config": "workspace:*", "rimraf": "^5.0.5", "turbo": "2.0.1" }, diff --git a/packages/nosql/.eslintrc.json b/packages/nosql/.eslintrc.json new file mode 100644 index 0000000..f84b797 --- /dev/null +++ b/packages/nosql/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + // "no-undef": "error", + // "no-unused-vars": "warn", + "no-duplicate-imports": "warn", + // "import/no-unresolved": "error", + "react/react-in-jsx-scope": "off", + "react/no-unknown-property": "error" + } +} diff --git a/packages/nosql/migrations/1724935562431-auth_collections_init.ts b/packages/nosql/migrations/1724935562431-auth_collections_init.ts new file mode 100644 index 0000000..e234bc7 --- /dev/null +++ b/packages/nosql/migrations/1724935562431-auth_collections_init.ts @@ -0,0 +1,169 @@ +import find from "lodash/find"; +import mongoose from "mongoose"; + +import getModels from "./connect"; + +export async function up(): Promise { + const { UserModel, PermissionModel, GroupModel } = await getModels(); + + const id1 = new mongoose.Types.ObjectId(); + const user1 = await UserModel.create({ + _id: id1, + id: id1.toString(), + name: "tontest", + email: "anthony@mountaindev.com", + password: "$2a$10$KSPht5iRaWqfWrdwQGQeBeGs9/qk2v0XBjqvldWGP7bKYE8TWPmAu", + }); + + const user2 = await UserModel.findOne({ email: "tonpascual@gmail.com" }); + const id3 = new mongoose.Types.ObjectId(); + const _user3 = await UserModel.create({ + _id: id3, + id: id3, + name: "declan", + email: "declan@mountaindev.com", + password: "$2a$12$JTZ/EFwL9wikzJD5ggFpy.9GEDC6CWE43VxVxyDxednGiWHfGYS/C", + }); + + const groups = await GroupModel.insertMany([ + { name: "Admin" }, + { name: "Control" }, + { name: "IIA" }, + { name: "CIA" }, + { name: "WBCIA" }, + { name: "AIA" }, + ]); + + const permissions = await PermissionModel.insertMany([ + { + name: "Admin", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["admin"], + }, + { + name: "Control", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["read"], + }, + { + name: "IIA", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["read", "write"], + }, + { + name: "CIA", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["read", "write", "submit"], + }, + { + name: "WBCIA", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["read", "write", "submit", "receive"], + }, + { + name: "AIA", + domain: [ + { + country: "*", + BMU: "*", + person: "*", + }, + ], + actions: ["read", "write", "submit", "receive", "review"], + }, + ]); + + const adminPerm = find(permissions, { name: "Admin" }); + const controlPerm = find(permissions, { name: "Control" }); + const iiaPerm = find(permissions, { name: "IIA" }); + const ciaPerm = find(permissions, { name: "CIA" }); + const wbciaPerm = find(permissions, { name: "WBCIA" }); + const aiaPerm = find(permissions, { name: "AIA" }); + + const adminGroup = find(groups, { name: "Admin" }); + const controlGroup = find(groups, { name: "Control" }); + const iiaGroup = find(groups, { name: "IIA" }); + const ciaGroup = find(groups, { name: "CIA" }); + const wbciaGroup = find(groups, { name: "WBCIA" }); + const aiaGroup = find(groups, { name: "AIA" }); + + await UserModel.findByIdAndUpdate(user1._id, { + groups: [controlGroup?._id, iiaGroup?._id], + }); + await UserModel.findByIdAndUpdate(user2?._id, { groups: [adminGroup?._id] }); + + await GroupModel.findByIdAndUpdate(adminGroup?._id, { + permission_id: adminPerm?._id, + }); + await GroupModel.findByIdAndUpdate(controlGroup?._id, { + permission_id: controlPerm?._id, + }); + await GroupModel.findByIdAndUpdate(iiaGroup?._id, { + permission_id: iiaPerm?._id, + }); + await GroupModel.findByIdAndUpdate(ciaGroup?._id, { + permission_id: ciaPerm?._id, + }); + await GroupModel.findByIdAndUpdate(wbciaGroup?._id, { + permission_id: wbciaPerm?._id, + }); + await GroupModel.findByIdAndUpdate(aiaGroup?._id, { + permission_id: aiaPerm?._id, + }); + + await PermissionModel.findByIdAndUpdate(adminPerm?._id, { + group_id: adminGroup?._id, + }); + await PermissionModel.findByIdAndUpdate(controlPerm?._id, { + group_id: controlGroup?._id, + }); + await PermissionModel.findByIdAndUpdate(iiaPerm?._id, { + group_id: iiaGroup?._id, + }); + await PermissionModel.findByIdAndUpdate(ciaPerm?._id, { + group_id: ciaGroup?._id, + }); + await PermissionModel.findByIdAndUpdate(wbciaPerm?._id, { + group_id: wbciaGroup?._id, + }); + await PermissionModel.findByIdAndUpdate(aiaPerm?._id, { + group_id: aiaGroup?._id, + }); +} + +export async function down(): Promise { + const { UserModel, PermissionModel, GroupModel } = await getModels(); + await UserModel.deleteMany(); + await PermissionModel.deleteMany(); + await GroupModel.deleteMany(); +} diff --git a/packages/nosql/migrations/connect.ts b/packages/nosql/migrations/connect.ts new file mode 100644 index 0000000..4be30ed --- /dev/null +++ b/packages/nosql/migrations/connect.ts @@ -0,0 +1,21 @@ +import mongoose from "mongoose"; + +import { + GroupModel, + PermissionModel, + UserModel, +} from "@repo/nosql/schema/auth"; + +const getModels = async () => { + await mongoose.connect( + process.env.MIGRATE_MONGO_URI ?? "mongodb://localhost/my-db", + ); + return { + mongoose, + UserModel, + PermissionModel, + GroupModel, + }; +}; + +export default getModels; diff --git a/packages/nosql/package.json b/packages/nosql/package.json new file mode 100644 index 0000000..2b841d3 --- /dev/null +++ b/packages/nosql/package.json @@ -0,0 +1,25 @@ +{ + "name": "@repo/nosql", + "version": "1.0.0", + "license": "MIT", + "exports": { + ".": "./src/index.ts", + "./schema/auth": "./src/schema/auth.ts" + }, + "scripts": { + "format": "prettier . --ignore-path ../../.gitignore --ignore-path ../../.prettierignore", + "lint": "eslint .", + "typecheck": "tsc --noEmit", + "generate": "migrate -f src/migrate.ts create", + "migrate": "migrate -f src/migrate.ts" + }, + "devDependencies": { + "eslint": "8.57.0", + "prettier": "3.2.5", + "ts-migrate-mongoose": "^3.8.4", + "typescript": "5.4.5" + }, + "dependencies": { + "mongoose": "^8.7.1" + } +} \ No newline at end of file diff --git a/packages/nosql/src/index.ts b/packages/nosql/src/index.ts new file mode 100644 index 0000000..61ae4f3 --- /dev/null +++ b/packages/nosql/src/index.ts @@ -0,0 +1,48 @@ +import type Mongoose from "mongoose"; +import mongoose from "mongoose"; + +declare global { + // eslint-disable-next-line no-var + var mongoose: { + conn: typeof Mongoose | null; + promise: Promise | null; + }; +} + +const databaseUrl = process.env.MONGODB_URI; +if (!databaseUrl) throw new Error("DATABASE_URL is not defined"); + +let cached = global.mongoose; + +if (!cached) { + cached = global.mongoose = { conn: null, promise: null }; +} + +async function getDb() { + if (process.env.NODE_ENV !== 'production') { + cached.conn = null + cached.promise = null + } + + if (cached.conn) { + return cached.conn; + } + if (!cached.promise) { + const opts = { + bufferCommands: false, + }; + cached.promise = mongoose + .connect(databaseUrl as string, opts) + .then((mongoose) => mongoose); + } + try { + cached.conn = await cached.promise; + } catch (e) { + cached.promise = null; + throw e; + } + + return cached.conn; +} + +export default getDb; diff --git a/packages/nosql/src/migrate.ts b/packages/nosql/src/migrate.ts new file mode 100644 index 0000000..f6ebfaa --- /dev/null +++ b/packages/nosql/src/migrate.ts @@ -0,0 +1,5 @@ +export default { + collection: "migrations", + migrationsPath: "./migrations", + autosync: false, +}; diff --git a/packages/nosql/src/schema/auth.ts b/packages/nosql/src/schema/auth.ts new file mode 100644 index 0000000..b4f6996 --- /dev/null +++ b/packages/nosql/src/schema/auth.ts @@ -0,0 +1,161 @@ +import type { Types } from "mongoose"; +import mongoose, { Schema } from "mongoose"; + +/* eslint-disable @typescript-eslint/consistent-type-definitions */ +export type TUser = { + _id: Types.ObjectId; + id: string; + name: string; + email: string; + password?: string; + emailVerified: Date; + image: string; + roleId: Types.ObjectId; + groups: TGroup[]; + created_at: Date; + updated_at: Date; +}; + +export type TAccount = { + userId: Types.ObjectId; + type: string; + provider: string; + providerAccountId: string; + expires_at: Date; + access_token: string; + expires_in?: number; + refresh_token?: string; + scope?: string; +}; + +export type TSession = { + sessionToken: string; + userId: Types.ObjectId; + expires: Date; +}; + +export type TVerificationToken = { + identifier: string; + expires: Date; + token: string; +}; + +export type TGroup = { + _id: string; + name: string; + permission_id: Types.ObjectId; +}; + +export const PERMISSION_ACTIONS = [ + "admin", + "read", + "write", + "submit", + "receive", + "review", +] as const; + +export type TAction = (typeof PERMISSION_ACTIONS)[number]; + +export type TPermission = { + name: string; + domain: { + country: string; + BMU: string[] | "*"; + person: string; + }[]; + actions: TAction[]; + group_id: Types.ObjectId; +}; + +/** + * Schemas + */ +const userSchema = new Schema({ + id: String, + name: String, + email: String, + password: { type: String, required: false }, + emailVerified: Date, + image: String, + groups: [{ type: Schema.Types.ObjectId, ref: "Group" }], + created_at: { type: Date, default: Date.now }, + updated_at: Date, +}); + +const accountSchema = new Schema({ + userId: { type: Schema.Types.ObjectId, ref: "User" }, + type: String, + provider: String, + providerAccountId: String, + access_token: String, + expires_at: Date, + expires_in: Number, + refresh_token: String, + scope: String, +}); + +const sessionSchema = new Schema({ + sessionToken: String, + userId: { type: Schema.Types.ObjectId, ref: "User" }, + expires: Date, +}); + +const verificationTokenSchema = new Schema( + { + identifier: String, + expires: Date, + token: String, + }, + { + collection: "verification_tokens", + }, +); + +const groupSchema = new Schema({ + name: String, + permission_id: { type: Schema.Types.ObjectId, ref: "Permission" }, +}); + +const permissionSchema = new Schema({ + name: String, + domain: [ + { + country: String, + BMU: Schema.Types.Mixed, + person: String, + }, + ], + actions: [{ type: String, enum: PERMISSION_ACTIONS }], + group_id: { type: Schema.Types.ObjectId, ref: "Group" }, +}); + +/** + * Models + */ +export const AccountModel = + (mongoose.models.Account as mongoose.Model) ?? + mongoose.model("Account", accountSchema); + +export const SessionModel = + (mongoose.models.Session as mongoose.Model) ?? + mongoose.model("Session", sessionSchema); + +export const UserModel = + (mongoose.models.User as mongoose.Model) ?? + mongoose.model("User", userSchema); + +export const VerificationTokenModel = + (mongoose.models.VerificationToken as mongoose.Model) ?? + mongoose.model( + "VerificationToken", + verificationTokenSchema, + ); + +export const PermissionModel = + (mongoose.models.Permission as mongoose.Model) ?? + mongoose.model("Permission", permissionSchema); + +export const GroupModel = + (mongoose.models.Group as mongoose.Model) ?? + mongoose.model("Group", groupSchema); diff --git a/packages/nosql/tsconfig.json b/packages/nosql/tsconfig.json new file mode 100644 index 0000000..07a3fb1 --- /dev/null +++ b/packages/nosql/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@repo/typescript-config/base.json", + "include": ["src", "migrations"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7d01a2..495769f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:packages/config-typescript + dotenv: + specifier: ^16.4.5 + version: 16.4.5 eslint: specifier: ^8.57.0 version: 8.57.0 @@ -121,7 +124,7 @@ importers: version: 8.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@uploadthing/react': specifier: ^6.5.1 - version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) + version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) ahooks: specifier: ^3.8.0 version: 3.8.0(react@18.3.1) @@ -280,7 +283,7 @@ importers: version: 5.4.5 uploadthing: specifier: ^6.10.1 - version: 6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) + version: 6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) zod: specifier: ^3.23.7 version: 3.23.8 @@ -456,6 +459,9 @@ importers: '@react-email/text': specifier: 0.0.8 version: 0.0.8(react@18.3.1) + '@repo/nosql': + specifier: workspace:* + version: link:../../packages/nosql '@t3-oss/env-nextjs': specifier: ^0.10.1 version: 0.10.1(typescript@5.4.5)(zod@3.23.8) @@ -479,10 +485,13 @@ importers: version: 11.0.0-next-beta.264 '@uploadthing/react': specifier: ^6.5.1 - version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) + version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) accept-language: specifier: ^3.0.18 version: 3.0.18 + bcryptjs: + specifier: ^2.4.3 + version: 2.4.3 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -537,12 +546,6 @@ importers: maplibre-gl: specifier: ^4.5.0 version: 4.5.0 - mongodb: - specifier: ^6.8.0 - version: 6.8.0(socks@2.8.3) - mongoose: - specifier: ^8.5.1 - version: 8.5.1(socks@2.8.3) next: specifier: 14.2.3 version: 14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -686,7 +689,7 @@ importers: version: 5.4.5 uploadthing: specifier: ^6.10.1 - version: 6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) + version: 6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) zod: specifier: ^3.23.7 version: 3.23.8 @@ -712,6 +715,9 @@ importers: '@total-typescript/ts-reset': specifier: ^0.5.1 version: 0.5.1 + '@types/bcryptjs': + specifier: ^2.4.6 + version: 2.4.6 '@types/date-arithmetic': specifier: ^4.1.4 version: 4.1.4 @@ -855,7 +861,7 @@ importers: version: 0.1.1(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) '@uploadthing/react': specifier: ^6.5.1 - version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) + version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1011,7 +1017,7 @@ importers: version: 5.4.5 uploadthing: specifier: ^6.10.1 - version: 6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) + version: 6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) zod: specifier: ^3.23.7 version: 3.23.8 @@ -1216,7 +1222,7 @@ importers: version: 0.1.1(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) '@uploadthing/react': specifier: ^6.5.1 - version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) + version: 6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)))) ahooks: specifier: ^3.8.0 version: 3.8.0(react@18.3.1) @@ -1384,7 +1390,7 @@ importers: version: 5.4.5 uploadthing: specifier: ^6.10.1 - version: 6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) + version: 6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) zod: specifier: ^3.23.7 version: 3.23.8 @@ -1459,6 +1465,25 @@ importers: specifier: ^3.4.3 version: 3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5)) + packages/nosql: + dependencies: + mongoose: + specifier: ^8.7.1 + version: 8.7.1(socks@2.8.3) + devDependencies: + eslint: + specifier: 8.57.0 + version: 8.57.0 + prettier: + specifier: 3.2.5 + version: 3.2.5 + ts-migrate-mongoose: + specifier: ^3.8.4 + version: 3.8.4(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6)(socks@2.8.3)(typescript@5.4.5) + typescript: + specifier: 5.4.5 + version: 5.4.5 + packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -1665,9 +1690,15 @@ packages: effect: ^3.1.3 fast-check: ^3.13.2 + '@emnapi/core@1.3.0': + resolution: {integrity: sha512-9hRqVlhwqBqCoToZ3hFcNVqL+uyHV06Y47ax4UB8L6XgVRqYz7MFnfessojo6+5TK89pKwJnpophwjTMOeKI9Q==} + '@emnapi/runtime@1.1.1': resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==} + '@emnapi/wasi-threads@1.0.1': + resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + '@emotion/babel-plugin@11.11.0': resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} @@ -2197,6 +2228,9 @@ packages: '@mongodb-js/saslprep@1.1.8': resolution: {integrity: sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==} + '@napi-rs/wasm-runtime@0.2.5': + resolution: {integrity: sha512-kwUxR7J9WLutBbulqg1dfOrMTwhMdXLdcGUhcbCcGwnPLt3gz19uHVdwH1syKVDbE022ZS2vZxOWflFLS0YTjw==} + '@next/env@14.1.0': resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==} @@ -2333,6 +2367,61 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oxc-resolver/binding-darwin-arm64@1.12.0': + resolution: {integrity: sha512-wYe+dlF8npM7cwopOOxbdNjtmJp17e/xF5c0K2WooQXy5VOh74icydM33+Uh/SZDgwyum09/U1FVCX5GdeQk+A==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@1.12.0': + resolution: {integrity: sha512-FZxxp99om+SlvBr1cjzF8A3TjYcS0BInCqjUlM+2f9m9bPTR2Bng9Zq5Q09ZQyrKJjfGKqlOEHs3akuVOnrx3Q==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@1.12.0': + resolution: {integrity: sha512-BZi0iU6IEOnXGSkqt1OjTTkN9wfyaK6kTpQwL/axl8eCcNDc7wbv1vloHgILf7ozAY1TP75nsLYlASYI4B5kGA==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@1.12.0': + resolution: {integrity: sha512-L2qnMEnZAqxbG9b1J3di/w/THIm+1fMVfbbTMWIQNMMXdMeqqDN6ojnOLDtuP564rAh4TBFPdLyEfGhMz6ipNA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@1.12.0': + resolution: {integrity: sha512-otVbS4zeo3n71zgGLBYRTriDzc0zpruC0WI3ICwjpIk454cLwGV0yzh4jlGYWQJYJk0BRAmXFd3ooKIF+bKBHw==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@1.12.0': + resolution: {integrity: sha512-IStQDjIT7Lzmqg1i9wXvPL/NsYsxF24WqaQFS8b8rxra+z0VG7saBOsEnOaa4jcEY8MVpLYabFhTV+fSsA2vnA==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@1.12.0': + resolution: {integrity: sha512-SipT7EVORz8pOQSFwemOm91TpSiBAGmOjG830/o+aLEsvQ4pEy223+SAnCfITh7+AahldYsJnVoIs519jmIlKQ==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@1.12.0': + resolution: {integrity: sha512-mGh0XfUzKdn+WFaqPacziNraCWL5znkHRfQVxG9avGS9zb2KC/N1EBbPzFqutDwixGDP54r2gx4q54YCJEZ4iQ==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-wasm32-wasi@1.12.0': + resolution: {integrity: sha512-SZN6v7apKmQf/Vwiqb6e/s3Y2Oacw8uW8V2i1AlxtyaEFvnFE0UBn89zq6swEwE3OCajNWs0yPvgAXUMddYc7Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@1.12.0': + resolution: {integrity: sha512-GRe4bqCfFsyghruEn5bv47s9w3EWBdO2q72xCz5kpQ0LWbw+enPHtTjw3qX5PUcFYpKykM55FaO0hFDs1yzatw==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@1.12.0': + resolution: {integrity: sha512-Z3llHH0jfJP4mlWq3DT7bK6qV+/vYe0+xzCgfc67+Tc/U3eYndujl880bexeGdGNPh87JeYznpZAOJ44N7QVVQ==} + cpu: [x64] + os: [win32] + '@panva/hkdf@1.1.1': resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} @@ -2861,6 +2950,22 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@swc-node/core@1.13.3': + resolution: {integrity: sha512-OGsvXIid2Go21kiNqeTIn79jcaX4l0G93X2rAnas4LFoDyA9wAwVK7xZdm+QsKoMn5Mus2yFLCc4OtX2dD/PWA==} + engines: {node: '>= 10'} + peerDependencies: + '@swc/core': '>= 1.4.13' + '@swc/types': '>= 0.1' + + '@swc-node/register@1.10.9': + resolution: {integrity: sha512-iXy2sjP0phPEpK2yivjRC3PAgoLaT4sjSk0LDWCTdcTBJmR4waEog0E6eJbvoOkLkOtWw37SB8vCkl/bbh4+8A==} + peerDependencies: + '@swc/core': '>= 1.4.13' + typescript: '>= 4.3' + + '@swc-node/sourcemap-support@0.5.1': + resolution: {integrity: sha512-JxIvIo/Hrpv0JCHSyRpetAdQ6lB27oFYhv0PKCNf1g2gUXOjpeR1exrXccRxLMuAV5WAmGFBwRnNOJqN38+qtg==} + '@swc/core-darwin-arm64@1.3.101': resolution: {integrity: sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ==} engines: {node: '>=10'} @@ -3054,6 +3159,12 @@ packages: resolution: {integrity: sha512-QYZ8g3IVQebqNM8IsBlWYOWmOKjBZY55e6lx4EDOLuch1iWmyk+U8CLAI9UomMrSaKTs1Sx+PDkt63EgakvhUw==} hasBin: true + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/bcryptjs@2.4.6': + resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==} + '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -3550,6 +3661,9 @@ packages: resolution: {integrity: sha512-JnkkL4GUpOvvanH9AZPX38CxhiLsXMBicBY2IAtqiVN8YulGDQybUydWA4W6yAMtw6iShtw+8HEF6cfrTHU+UQ==} engines: {node: '>=0.10'} + bcryptjs@2.4.3: + resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3712,6 +3826,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -3720,6 +3837,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -3913,6 +4034,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -4023,6 +4153,10 @@ packages: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + earcut@2.2.4: resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} @@ -5227,8 +5361,8 @@ packages: mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} - mongodb@6.7.0: - resolution: {integrity: sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==} + mongodb@6.8.0: + resolution: {integrity: sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==} engines: {node: '>=16.20.1'} peerDependencies: '@aws-sdk/credential-providers': ^3.188.0 @@ -5254,8 +5388,8 @@ packages: socks: optional: true - mongodb@6.8.0: - resolution: {integrity: sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==} + mongodb@6.9.0: + resolution: {integrity: sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==} engines: {node: '>=16.20.1'} peerDependencies: '@aws-sdk/credential-providers': ^3.188.0 @@ -5281,8 +5415,12 @@ packages: socks: optional: true - mongoose@8.5.1: - resolution: {integrity: sha512-OhVcwVl91A1G6+XpjDcpkGP7l7ikZkxa0DylX7NT/lcEqAjggzSdqDxb48A+xsDxqNAr0ntSJ1yiE3+KJTOd5Q==} + mongoose@8.6.2: + resolution: {integrity: sha512-ErbDVvuUzUfyQpXvJ6sXznmZDICD8r6wIsa0VKjJtB6/LZncqwUn5Um040G1BaNo6L3Jz+xItLSwT0wZmSmUaQ==} + engines: {node: '>=16.20.1'} + + mongoose@8.7.1: + resolution: {integrity: sha512-RpNMyhyzLVCVbf8xTVbrf/18G3MqQzNw5pJdvOJ60fzbCa3cOZzz9L+8XpqzBXtRlgZGWv0T7MmOtvrT8ocp1Q==} engines: {node: '>=16.20.1'} mpath@0.9.0: @@ -5523,6 +5661,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + oxc-resolver@1.12.0: + resolution: {integrity: sha512-YlaCIArvWNKCWZFRrMjhh2l5jK80eXnpYP+bhRc1J/7cW3TiyEY0ngJo73o/5n8hA3+4yLdTmXLNTQ3Ncz50LQ==} + p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} @@ -6794,6 +6935,11 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-migrate-mongoose@3.8.4: + resolution: {integrity: sha512-jOxPONLRXGaC/BiCyMziNsM6ZKdSKyzoriIIv4fINqu5Caj07l5OggZwPeF3S3cqGWxZMwJQftQ9NdfvX4BmLg==} + engines: {node: '>=16'} + hasBin: true + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -6817,6 +6963,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + turbo-darwin-64@2.0.1: resolution: {integrity: sha512-GO391pUmI6c6l/EpUIaXNzwbVDWRvYahm5oLB176dAWRYKYO+Osqs/XBdOM0G3l7ZFdR6nUtRJc8qinJp7qDUQ==} cpu: [x64] @@ -7424,11 +7573,22 @@ snapshots: effect: 3.1.4 fast-check: 3.18.0 + '@emnapi/core@1.3.0': + dependencies: + '@emnapi/wasi-threads': 1.0.1 + tslib: 2.7.0 + optional: true + '@emnapi/runtime@1.1.1': dependencies: tslib: 2.6.2 optional: true + '@emnapi/wasi-threads@1.0.1': + dependencies: + tslib: 2.7.0 + optional: true + '@emotion/babel-plugin@11.11.0': dependencies: '@babel/helper-module-imports': 7.22.15 @@ -7679,7 +7839,7 @@ snapshots: dependencies: '@babel/core': 7.24.6 '@babel/generator': 7.24.6 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.6 '@babel/traverse': 7.24.6 '@babel/types': 7.24.6 prettier: 3.2.5 @@ -7923,6 +8083,13 @@ snapshots: dependencies: sparse-bitfield: 3.0.3 + '@napi-rs/wasm-runtime@0.2.5': + dependencies: + '@emnapi/core': 1.3.0 + '@emnapi/runtime': 1.1.1 + '@tybys/wasm-util': 0.9.0 + optional: true + '@next/env@14.1.0': {} '@next/env@14.2.3': {} @@ -8001,6 +8168,41 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@oxc-resolver/binding-darwin-arm64@1.12.0': + optional: true + + '@oxc-resolver/binding-darwin-x64@1.12.0': + optional: true + + '@oxc-resolver/binding-freebsd-x64@1.12.0': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@1.12.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@1.12.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@1.12.0': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@1.12.0': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@1.12.0': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@1.12.0': + dependencies: + '@napi-rs/wasm-runtime': 0.2.5 + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@1.12.0': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@1.12.0': + optional: true + '@panva/hkdf@1.1.1': {} '@paralleldrive/cuid2@2.2.2': @@ -8534,6 +8736,31 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@swc-node/core@1.13.3(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6)': + dependencies: + '@swc/core': 1.3.101(@swc/helpers@0.5.5) + '@swc/types': 0.1.6 + + '@swc-node/register@1.10.9(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6)(typescript@5.4.5)': + dependencies: + '@swc-node/core': 1.13.3(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6) + '@swc-node/sourcemap-support': 0.5.1 + '@swc/core': 1.3.101(@swc/helpers@0.5.5) + colorette: 2.0.20 + debug: 4.3.7 + oxc-resolver: 1.12.0 + pirates: 4.0.6 + tslib: 2.7.0 + typescript: 5.4.5 + transitivePeerDependencies: + - '@swc/types' + - supports-color + + '@swc-node/sourcemap-support@0.5.1': + dependencies: + source-map-support: 0.5.21 + tslib: 2.7.0 + '@swc/core-darwin-arm64@1.3.101': optional: true @@ -8723,6 +8950,13 @@ snapshots: semver: 7.6.2 update-check: 1.5.4 + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.7.0 + optional: true + + '@types/bcryptjs@2.4.6': {} + '@types/cookie@0.4.1': {} '@types/cors@2.8.17': @@ -8933,7 +9167,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.12.0 '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -8958,14 +9192,14 @@ snapshots: '@uploadthing/mime-types@0.2.10': {} - '@uploadthing/react@6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))))': + '@uploadthing/react@6.5.4(@uploadthing/mime-types@0.2.10)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))))': dependencies: '@uploadthing/dropzone': 0.4.1(react@18.3.1) '@uploadthing/shared': 6.7.4(@uploadthing/mime-types@0.2.10) file-selector: 0.6.0 react: 18.3.1 tailwind-merge: 2.3.0 - uploadthing: 6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) + uploadthing: 6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))) optionalDependencies: next: 14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: @@ -9091,7 +9325,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -9282,6 +9516,8 @@ snapshots: bcp47@1.1.2: {} + bcryptjs@2.4.3: {} + binary-extensions@2.2.0: {} bl@4.1.0: @@ -9464,10 +9700,14 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@2.0.20: {} + commander@10.0.1: {} commander@11.1.0: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -9632,6 +9872,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decimal.js-light@2.5.1: {} deep-equal@1.1.2: @@ -9747,6 +9991,8 @@ snapshots: dotenv@16.0.3: {} + dotenv@16.4.5: {} + earcut@2.2.4: {} earcut@3.0.0: {} @@ -9771,7 +10017,7 @@ snapshots: engine.io-client@6.5.3: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.4 + debug: 4.3.7 engine.io-parser: 5.2.2 ws: 8.11.0 xmlhttprequest-ssl: 2.0.0 @@ -9791,7 +10037,7 @@ snapshots: base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 - debug: 4.3.4 + debug: 4.3.7 engine.io-parser: 5.2.2 ws: 8.11.0 transitivePeerDependencies: @@ -10341,7 +10587,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 fs-extra: 11.2.0 transitivePeerDependencies: - supports-color @@ -10552,14 +10798,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -11152,7 +11398,7 @@ snapshots: '@types/whatwg-url': 11.0.5 whatwg-url: 13.0.0 - mongodb@6.7.0(socks@2.8.3): + mongodb@6.8.0(socks@2.8.3): dependencies: '@mongodb-js/saslprep': 1.1.8 bson: 6.8.0 @@ -11160,7 +11406,7 @@ snapshots: optionalDependencies: socks: 2.8.3 - mongodb@6.8.0(socks@2.8.3): + mongodb@6.9.0(socks@2.8.3): dependencies: '@mongodb-js/saslprep': 1.1.8 bson: 6.8.0 @@ -11168,11 +11414,30 @@ snapshots: optionalDependencies: socks: 2.8.3 - mongoose@8.5.1(socks@2.8.3): + mongoose@8.6.2(socks@2.8.3): + dependencies: + bson: 6.8.0 + kareem: 2.6.3 + mongodb: 6.8.0(socks@2.8.3) + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 17.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + mongoose@8.7.1(socks@2.8.3): dependencies: bson: 6.8.0 kareem: 2.6.3 - mongodb: 6.7.0(socks@2.8.3) + mongodb: 6.9.0(socks@2.8.3) mpath: 0.9.0 mquery: 5.0.0 ms: 2.1.3 @@ -11191,7 +11456,7 @@ snapshots: mquery@5.0.0: dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -11474,6 +11739,20 @@ snapshots: os-tmpdir@1.0.2: {} + oxc-resolver@1.12.0: + optionalDependencies: + '@oxc-resolver/binding-darwin-arm64': 1.12.0 + '@oxc-resolver/binding-darwin-x64': 1.12.0 + '@oxc-resolver/binding-freebsd-x64': 1.12.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 1.12.0 + '@oxc-resolver/binding-linux-arm64-gnu': 1.12.0 + '@oxc-resolver/binding-linux-arm64-musl': 1.12.0 + '@oxc-resolver/binding-linux-x64-gnu': 1.12.0 + '@oxc-resolver/binding-linux-x64-musl': 1.12.0 + '@oxc-resolver/binding-wasm32-wasi': 1.12.0 + '@oxc-resolver/binding-win32-arm64-msvc': 1.12.0 + '@oxc-resolver/binding-win32-x64-msvc': 1.12.0 + p-defer@1.0.0: {} p-limit@3.1.0: @@ -11492,7 +11771,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 get-uri: 6.0.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 @@ -12513,7 +12792,7 @@ snapshots: socket.io-adapter@2.5.4: dependencies: - debug: 4.3.4 + debug: 4.3.7 ws: 8.11.0 transitivePeerDependencies: - bufferutil @@ -12534,7 +12813,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -12555,7 +12834,7 @@ snapshots: socks-proxy-agent@8.0.3: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -12937,6 +13216,27 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-migrate-mongoose@3.8.4(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6)(socks@2.8.3)(typescript@5.4.5): + dependencies: + '@swc-node/register': 1.10.9(@swc/core@1.3.101(@swc/helpers@0.5.5))(@swc/types@0.1.6)(typescript@5.4.5) + chalk: 4.1.2 + commander: 12.1.0 + dotenv: 16.4.5 + inquirer: 8.2.6 + mongoose: 8.6.2(socks@2.8.3) + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - '@swc/core' + - '@swc/types' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + - typescript + ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -12968,6 +13268,8 @@ snapshots: tslib@2.6.2: {} + tslib@2.7.0: {} + turbo-darwin-64@2.0.1: optional: true @@ -13105,7 +13407,7 @@ snapshots: registry-auth-token: 3.3.2 registry-url: 3.1.0 - uploadthing@6.10.4(h3@1.13.0)(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))): + uploadthing@6.10.4(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.5))(@types/node@20.12.10)(typescript@5.4.5))): dependencies: '@effect/schema': 0.66.16(effect@3.1.4)(fast-check@3.18.0) '@uploadthing/mime-types': 0.2.10