diff --git a/.gitignore b/.gitignore
index 5ef6a52..7f3fcc5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# dependencies
/node_modules
+.env
/.pnp
.pnp.*
.yarn/*
diff --git a/package-lock.json b/package-lock.json
index f15a1ff..5ab3150 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "project2-f25",
"version": "0.1.0",
"dependencies": {
+ "argon2": "^0.44.0",
"next": "16.0.1",
"react": "19.2.0",
"react-dom": "19.2.0"
@@ -44,6 +45,12 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@epic-web/invariant": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
+ "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
+ "license": "MIT"
+ },
"node_modules/@img/colour": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
@@ -656,6 +663,15 @@
"node": ">= 10"
}
},
+ "node_modules/@phc/format": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
+ "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -952,6 +968,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -966,6 +983,22 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/argon2": {
+ "version": "0.44.0",
+ "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz",
+ "integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@phc/format": "^1.0.0",
+ "cross-env": "^10.0.0",
+ "node-addon-api": "^8.5.0",
+ "node-gyp-build": "^4.8.4"
+ },
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001751",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
@@ -992,6 +1025,37 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/cross-env": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
+ "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@epic-web/invariant": "^1.0.0",
+ "cross-spawn": "^7.0.6"
+ },
+ "bin": {
+ "cross-env": "dist/bin/cross-env.js",
+ "cross-env-shell": "dist/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -1030,6 +1094,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -1409,6 +1479,35 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-addon-api": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz",
+ "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18 || ^20 || >= 21"
+ }
+ },
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -1449,6 +1548,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1458,6 +1558,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -1527,6 +1628,27 @@
"@img/sharp-win32-x64": "0.34.4"
}
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1606,6 +1728,21 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
}
}
}
diff --git a/package.json b/package.json
index 693e5fc..4a30477 100644
--- a/package.json
+++ b/package.json
@@ -8,16 +8,17 @@
"start": "next start"
},
"dependencies": {
+ "argon2": "^0.44.0",
+ "next": "16.0.1",
"react": "19.2.0",
- "react-dom": "19.2.0",
- "next": "16.0.1"
+ "react-dom": "19.2.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4"
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/src/app/favicon.ico and /dev/null differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
deleted file mode 100644
index f7fa87e..0000000
--- a/src/app/layout.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
-export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/src/components/.gitadd b/src/components/.gitadd
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 0000000..61f64e1
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,6 @@
+import "@/styles/globals.css";
+import type { AppProps } from "next/app";
+
+export default function App({ Component, pageProps }: AppProps) {
+ return ;
+}
\ No newline at end of file
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
new file mode 100644
index 0000000..93812e7
--- /dev/null
+++ b/src/pages/_document.tsx
@@ -0,0 +1,16 @@
+import { Html, Head, Main, NextScript } from "next/document";
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/api/admin/animals.js b/src/pages/api/admin/animals.js
new file mode 100644
index 0000000..9e7cb2f
--- /dev/null
+++ b/src/pages/api/admin/animals.js
@@ -0,0 +1,14 @@
+import { getAllAnimals } from "../../../../webapp/server/mongodb/actions/animalActions.js";
+
+export default async function handler(req, res) {
+ if (req.method === "GET") {
+ try {
+ const animals = await getAllAnimals();
+ return res.status(200).json(animals);
+ } catch (err) {
+ return res.status(500).json({ error: err.message });
+ }
+ }
+
+ return res.status(405).json({ error: "Method not allowed" });
+}
diff --git a/src/pages/api/admin/users.ts b/src/pages/api/admin/users.ts
new file mode 100644
index 0000000..7db80ee
--- /dev/null
+++ b/src/pages/api/admin/users.ts
@@ -0,0 +1,25 @@
+// allows you to get all user information, given that you are an admin
+
+import { NextApiRequest, NextApiResponse } from 'next';
+import connectDB from '../../../../webapp/server/mongodb';
+import User from '../../../../webapp/server/mongodb/models/user';
+
+// we need the id to be passed in of the person requesting this info
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ if (req.method !== 'GET') {
+ throw new Error('Invalid HTTP method');
+ }
+ await connectDB();
+ const requesterId = req.headers['user-id'];
+ const requester = await User.findById(requesterId);
+
+ if (!requester || !requester.admin) {
+ return res.status(500).json({ error: "Unauthorized"});
+ }
+ const users = await User.find().select('-password');
+ return res.status(200).json(users);
+ } catch (error) {
+ return res.status(500).json({ error: 'There was an error' });
+ }
+}
diff --git a/src/pages/api/animal.js b/src/pages/api/animal.js
new file mode 100644
index 0000000..a8ea6d3
--- /dev/null
+++ b/src/pages/api/animal.js
@@ -0,0 +1,25 @@
+import { createAnimal, updateAnimal } from "../../../server/mongodb/actions/animalActions.js";
+
+export default async function handler(req, res) {
+ if (req.method === "POST") {
+ try {
+ const animal = await createAnimal(req.body);
+ return res.status(201).json(animal);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ if (req.method === "PATCH") {
+ try {
+ const { id, ...data } = req.body;
+ const animal = await updateAnimal(id, data);
+ if (!animal) return res.status(404).json({ error: "Animal not found" });
+ return res.status(200).json(animal);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ return res.status(405).json({ error: "Method not allowed" });
+}
diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts
new file mode 100644
index 0000000..d02daf0
--- /dev/null
+++ b/src/pages/api/user.ts
@@ -0,0 +1,53 @@
+// creates and deletes accounts
+
+import { NextApiRequest, NextApiResponse } from 'next';
+import { createUser, deleteUser } from '../../../webapp/server/mongodb/actions/userActions';
+import User from '../../../webapp/server/mongodb/models/user';
+import * as argon2 from 'argon2';
+import connectDB from '../../../webapp/server/mongodb';
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method === 'POST') {
+ try {
+ await connectDB();
+ const { fullName, email, password } = req.body;
+ if (!fullName || !email || !password) {
+ return res.status(400).json({ error: 'All required user information is not present.' });
+ }
+ const newUser = await createUser(req.body);
+ return res.status(200).json({ message: 'User was created successfully', user: newUser });
+ } catch(err) {
+ if (err instanceof Error) {
+ if (err.message === "Email is already used") {
+ return res.status(400).json({ err: err.message });
+ }
+ }
+ return res.status(500).json({ err: 'There was an error'});
+ }
+ }
+ else if (req.method === 'DELETE') {
+ try {
+ const { id, email, password } = req.body;
+ if (!id || !email || !password) {
+ return res.status(400).json({ error: "Missing required fields" });
+ }
+ // we need to check if the inputted password is the same
+ await connectDB();
+ const accessor = await User.findById(id);
+ if (!accessor) {
+ return res.status(500).json({ error: "Account not created" });
+ }
+ const passwordValidity = await argon2.verify(accessor.password, password);
+ if (!passwordValidity || email !== accessor.email) {
+ return res.status(500).json({ error: 'Invalid credentials' });
+ }
+ const result = await deleteUser(id);
+ return res.status(200).json(result);
+ } catch (err) {
+ return res.status(500).json({ err: 'There was an error deleting the user.' });
+ }
+ }
+ else {
+ return res.status(500).json({ error: 'Invalid HTTP method'});
+ }
+}
diff --git a/src/pages/api/user/verify.ts b/src/pages/api/user/verify.ts
new file mode 100644
index 0000000..c0b7771
--- /dev/null
+++ b/src/pages/api/user/verify.ts
@@ -0,0 +1,30 @@
+// verifies if person logging in puts in correct credentials
+
+import { NextApiRequest, NextApiResponse } from "next";
+import connectDB from "../../../../webapp/server/mongodb";
+import User from "../../../../webapp/server/mongodb/models/user";
+import * as argon2 from 'argon2';
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ if (req.method !== 'POST') {
+ throw new Error('Invalid HTTP method');
+ }
+ await connectDB();
+ const { email, password } = req.body;
+ const user = await User.findOne({ email });
+ if (!user) {
+ return res.status(500).json({ error: 'Invalid Username/Password'} );
+ }
+ const isPassValid = await argon2.verify(user.password, password);
+ if (!isPassValid) {
+ return res.status(500).json({ error: 'Invalid Username/Password'} );
+ }
+ return res.status(200).json({
+ id: user._id,
+ admin: user.admin
+ }); // whenever the user logs in, their id and admin status is saved for the duration of their stay on the site
+ } catch(error) {
+ return res.status(500).json({ error: 'There was an error' });
+ }
+}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/pages/index.js
similarity index 83%
rename from src/app/page.tsx
rename to src/pages/index.js
index 295f8fd..459dc21 100644
--- a/src/app/page.tsx
+++ b/src/pages/index.js
@@ -1,8 +1,27 @@
+export default function Home() {
+ return Animal Training App
;
+}
+
+
+// i dont know if this is right because the index.tsx file in the github parent repo is like this:
+
+/*
import Image from "next/image";
+import { Geist, Geist_Mono } from "next/font/google";
+
+const geistSans = Geist({
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
+});
export default function Home() {
return (
-
+
);
}
+*/
\ No newline at end of file
diff --git a/src/app/globals.css b/src/styles/globals.css
similarity index 99%
rename from src/app/globals.css
rename to src/styles/globals.css
index a2dc41e..9159b88 100644
--- a/src/app/globals.css
+++ b/src/styles/globals.css
@@ -23,4 +23,4 @@ body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
-}
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index cf9c65d..821e1f1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -19,7 +19,7 @@
}
],
"paths": {
- "@/*": ["./src/*"]
+ "@/*": ["src/*"]
}
},
"include": [
diff --git a/webapp/server/mongodb/actions/animalActions.js b/webapp/server/mongodb/actions/animalActions.js
new file mode 100644
index 0000000..afbecf1
--- /dev/null
+++ b/webapp/server/mongodb/actions/animalActions.js
@@ -0,0 +1,20 @@
+import connectDB from "../index.js";
+import Animal from "../models/Animal.js";
+
+export async function createAnimal(data) {
+ await connectDB();
+ const animal = await Animal.create(data);
+ return animal;
+}
+
+export async function updateAnimal(id, data) {
+ await connectDB();
+ const animal = await Animal.findByIdAndUpdate(id, data, { new: true });
+ return animal;
+}
+
+export async function getAllAnimals() {
+ await connectDB();
+ const animals = await Animal.find({}).populate("owner", "fullName email");
+ return animals;
+}
diff --git a/webapp/server/mongodb/actions/userActions.js b/webapp/server/mongodb/actions/userActions.js
new file mode 100644
index 0000000..814000d
--- /dev/null
+++ b/webapp/server/mongodb/actions/userActions.js
@@ -0,0 +1,45 @@
+// contains functions for each part of CRUD (specifically for users)
+
+import connectDB from "..";
+import User from "../models/user";
+import * as argon2 from 'argon2';
+
+export async function createUser(data) {
+ await connectDB();
+ const existing = await User.findOne({ email: data.email });
+ if (existing) {
+ throw new Error("Email is already used");
+ }
+ const hashPass = await argon2.hash(data["password"]);
+ const user = await User.create({ ...data, password: hashPass });
+ return user;
+}
+export async function updateUser(id, data) {
+ await connectDB();
+ if (data.password) {
+ data.password = await argon2.hash(data.password);
+ }
+ const updatedUser = await User.findByIdAndUpdate(id, data, { new: true });
+ if (!updatedUser) {
+ throw new Error("User not found.");
+ }
+ return updatedUser;
+}
+export async function getUser(id) {
+ await connectDB();
+ const inDB = await User.findById(id).select('-password'); // filters out password
+ if (!inDB) {
+ throw new Error("User not found.");
+ }
+ return inDB; // don't need to do inDB.data
+}
+export async function deleteUser(id) {
+ await connectDB();
+ const inDB = await User.findById(id); // i think you can also do User.findByIdAndDelete(id);
+ if (!inDB) {
+ throw new Error("User not found.");
+ }
+ await inDB.deleteOne();
+ return { success: true,
+ message: "User was deleted" };
+}
diff --git a/webapp/server/mongodb/index.js b/webapp/server/mongodb/index.js
new file mode 100644
index 0000000..8a0ab8a
--- /dev/null
+++ b/webapp/server/mongodb/index.js
@@ -0,0 +1,30 @@
+import mongoose from "mongoose";
+
+const MONGODB_URI = process.env.MONGODB_URI;
+
+if (!MONGODB_URI) {
+ throw new Error("Please define the MONGODB_URI environment variable");
+}
+
+let cached = global.mongoose;
+
+if (!cached) {
+ cached = global.mongoose = { conn: null, promise: null };
+}
+
+async function connectDB() {
+ if (cached.conn) {
+ return cached.conn;
+ }
+
+ if (!cached.promise) {
+ cached.promise = mongoose.connect(MONGODB_URI).then((mongoose) => {
+ return mongoose;
+ });
+ }
+
+ cached.conn = await cached.promise;
+ return cached.conn;
+}
+
+export default connectDB;
\ No newline at end of file
diff --git a/webapp/server/mongodb/models/Animal.js b/webapp/server/mongodb/models/Animal.js
new file mode 100644
index 0000000..6c839b6
--- /dev/null
+++ b/webapp/server/mongodb/models/Animal.js
@@ -0,0 +1,11 @@
+import mongoose from "mongoose";
+
+const AnimalSchema = new mongoose.Schema({
+ name: { type: String, required: true },
+ breed: { type: String, required: true },
+ owner: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
+ hoursTrained: { type: Number, default: 0 },
+ profilePicture: { type: String, default: "" },
+});
+
+export default mongoose.models.Animal || mongoose.model("Animal", AnimalSchema);
diff --git a/webapp/server/mongodb/models/User.js b/webapp/server/mongodb/models/User.js
new file mode 100644
index 0000000..989cf13
--- /dev/null
+++ b/webapp/server/mongodb/models/User.js
@@ -0,0 +1,11 @@
+// contains the schema required for a user (necesitates that users have baseline attributes like name, email, password, and if they are an admin)
+import mongoose from 'mongoose';
+
+const userSchema = new mongoose.Schema({
+ fullName: { type: String, required: true },
+ email: { type: String, required: true, unique: true },
+ password: { type: String, required: true }, // reminder: shouldn't be pure raw password (use argon hashing)
+ admin: { type: Boolean, default: false }
+});
+
+export default mongoose.models.User || mongoose.model('User', userSchema);
\ No newline at end of file
diff --git a/webapp/server/utils/.gitadd b/webapp/server/utils/.gitadd
new file mode 100644
index 0000000..e69de29