Skip to content

Commit

Permalink
Gamify init
Browse files Browse the repository at this point in the history
  • Loading branch information
Tamoziit committed Jan 3, 2025
1 parent fea2b42 commit fcfe3b5
Show file tree
Hide file tree
Showing 15 changed files with 946 additions and 5 deletions.
159 changes: 158 additions & 1 deletion backend/controllers/elevatedUser.controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
import Image from "../models/image.model.js";
import Metadata from "../models/metadata.model.js";
import Prediction from "../models/predictions.model.js";
import { client } from "../redis/client.js";
import regexHandler from "../utils/regexHandler.js";

export const updatePrediction = async (req, res) => {
try {
const predictions = await Prediction.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});

if (predictions) {
await client.del(`userHistory:${predictions.userId}`)
res.status(200).json(predictions);
} else {
res.status(400).json({ error: "Couldn't Update the entry" });
}
} catch (error) {
console.log("Error in updating prediction", error.message);
res.status(500).json({ error: "Internal Server error" })
}
}

export const createMetaData = async (req, res) => {
try {
Expand Down Expand Up @@ -27,14 +50,148 @@ export const getMetaData = async (req, res) => {
if (metadata) {
res.status(201).json(metadata);
} else {
res.status(200).json({ message: "Metadata for the user DNE" });
res.status(400).json({ error: "Error in fetching metadata" });
}
} catch (error) {
console.log("Error in get metadata", error.message);
res.status(500).json({ error: "Internal Server error" });
}
}

export const getImageFromBuckets = async (req, res) => {
try {
const bucketCount = 5;
const bucketsExist = [];

// Checking if all buckets already exist
for (let i = 0; i < bucketCount; i++) {
const bucketName = `image_bucket_${i + 1}`;
const bucketExists = await client.exists(bucketName);
bucketsExist.push(bucketExists);
}

if (bucketsExist.every((exists) => exists)) {
// If all buckets exist, fetching 2 images from each bucket
const selectedImages = [];
for (let i = 0; i < bucketCount; i++) {
const bucketName = `image_bucket_${i + 1}`;
const bucketData = await client.get(bucketName);

if (bucketData) {
const bucketImages = JSON.parse(bucketData);
const randomImages = bucketImages
.sort(() => Math.random() - 0.5)
.slice(0, 2); // Selecting 2 random images from each bucket
selectedImages.push(...randomImages); // pushing the selected images in final array
}
}
const finalSet = selectedImages.sort(() => Math.random() - 0.5); // final shuffle

return res.status(200).json(finalSet);
}

// If buckets don't exist, fetching images with pagination
const limit = 200; // Fetching 200 images per batch
let page = 0;
let images = [];
let hasMore = true;

// handling pagination
while (hasMore) {
const batch = await Image.find({})
.skip(page * limit)
.limit(limit);
images.push(...batch);
hasMore = batch.length === limit;
page++;
}

if (!images.length) {
return res.status(400).json({ error: "No images found" });
}

const imagesPerBucket = Math.ceil(images.length / bucketCount);

// Creating new buckets and storing images
for (let i = 0; i < bucketCount; i++) {
const bucketName = `image_bucket_${i + 1}`;
const bucketImages = images.slice(
i * imagesPerBucket,
(i + 1) * imagesPerBucket
); // aiming for equal distribution. ie., if bucketSize = 100 & bucket 1 has 0-99 images, bucket 2 has 100-199 images & so on.
await client.set(bucketName, JSON.stringify(bucketImages));
await client.expire(bucketName, 30 * 24 * 60 * 60);
}

// Selecting 2 random images from each bucket
const selectedImages = [];
for (let i = 0; i < bucketCount; i++) {
const bucketName = `image_bucket_${i + 1}`;
const bucketData = await client.get(bucketName);
if (bucketData) {
const bucketImages = JSON.parse(bucketData);
const randomImages = bucketImages
.sort(() => Math.random() - 0.5)
.slice(0, 2);
selectedImages.push(...randomImages);
}
}
const finalSet = selectedImages.sort(() => Math.random() - 0.5);

return res.status(200).json(finalSet);
} catch (error) {
console.error("Error in getImagesFromBuckets controller", error);
return res.status(500).json({ error: "Internal Server error" });
}
}

export const updateMetadata = async (req, res) => {
try {
const metadata = await Metadata.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});

if (metadata) {
res.status(200).json(metadata);
} else {
res.status(400).json({ error: "Couldn't Update your stats" });
}
} catch (error) {
console.log("Error in updating metadata", error.message);
res.status(500).json({ error: "Internal Server error" })
}
}

export const createImage = async (req, res) => {
try {
const { image_url, crop, disease } = req.body;
if (!image_url || !crop || !disease) return res.status(400).json({ error: "All fields are required" });
const formattedDisease = regexHandler(disease);

const newImage = await Image({
image_url,
crop,
disease: formattedDisease
});

if (newImage) {
await newImage.save();
res.status(201).json({
_id: newImage._id,
image_url: newImage.image_url,
crop: newImage.crop,
disease: newImage.disease
});
} else {
res.status(400).json({ error: "Couldn't create new Image" });
}
} catch (error) {
console.log("Error in creating image", error);
res.status(500).json({ error: "Internal Server error" });
}
}

export const isElevatedUser = async (req, res) => {
try {
const user = await Metadata.findOne({ user: req.user._id });
Expand Down
29 changes: 29 additions & 0 deletions backend/middlewares/elevatedUser.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import jwt from "jsonwebtoken";
import Metadata from "../models/metadata.model.js";

const verifyElevatedUser = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "Unauthorized - No Token Provided" });
}

const decodedUser = jwt.verify(token, process.env.JWT_SECRET);
if (!decodedUser) {
return res.status(401).json({ error: "Unauthorized - Invalid Token" });
}

const user = await Metadata.findOne({ user: decodedUser.userId });
console.log(user);
if (user.totalGP < parseFloat(200)) {
return res.status(400).json({ error: "User does not possess the permission to perform the following action!" });
}

next();
} catch (err) {
console.log("Error in verifyElevatedUser middleware", err.message);
res.status(500).json({ error: "Internal Server Error" });
}
};

export default verifyElevatedUser;
29 changes: 29 additions & 0 deletions backend/middlewares/enrolledUser.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import jwt from "jsonwebtoken";
import Metadata from "../models/metadata.model.js";

const verifyUser = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "Unauthorized - No Token Provided" });
}

const decodedUser = jwt.verify(token, process.env.JWT_SECRET);
if (!decodedUser) {
return res.status(401).json({ error: "Unauthorized - Invalid Token" });
}

const user = await Metadata.findOne({ user: decodedUser.userId });
console.log(user);
if (!user) {
return res.status(400).json({ error: "User Not Enrolled!" });
}

next();
} catch (err) {
console.log("Error in verifyUser middleware", err.message);
res.status(500).json({ error: "Internal Server Error" });
}
};

export default verifyUser;
8 changes: 7 additions & 1 deletion backend/routes/elevatedUser.routes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import express from "express";
import verifyToken from "../middlewares/auth.middleware.js";
import { createMetaData, getMetaData, isElevatedUser } from "../controllers/elevatedUser.controller.js";
import { createImage, createMetaData, getImageFromBuckets, getMetaData, isElevatedUser, updateMetadata, updatePrediction } from "../controllers/elevatedUser.controller.js";
import verifyUser from "../middlewares/enrolledUser.middleware.js";
import verifyElevatedUser from "../middlewares/elevatedUser.middleware.js";

const router = express.Router();

router.patch("/update/:id", verifyToken, verifyUser, verifyElevatedUser, updatePrediction);
router.post("/metadata", verifyToken, createMetaData);
router.get("/get-metadata", verifyToken, getMetaData);
router.get("/get-images", verifyToken, verifyUser, getImageFromBuckets);
router.patch("/update-metadata/:id", verifyToken, verifyUser, updateMetadata);
router.post("/create-image", verifyToken, verifyUser, createImage);
router.get("/isElevatedUser", verifyToken, isElevatedUser);

export default router;
12 changes: 12 additions & 0 deletions backend/utils/regexHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const regexHandler = (input) => {
return input
.replace(/([a-z])([A-Z])/g, '$1_$2')
.replace(/[\s\-]+/g, '_')
.replace(/_+/g, '_')
.toLowerCase()
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('_');
}

export default regexHandler;
24 changes: 22 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEnrollmentContext } from "./context/EnrollmentContext";
import { Navigate, Route, Routes } from "react-router-dom"
import { useAuthContext } from "./context/AuthContext";
import { Toaster } from "react-hot-toast";
Expand All @@ -18,9 +19,16 @@ import MarketplaceBuy from "./pages/marketplace/MarketplaceBuy";
import CompletePayment from "./pages/payment/CompletePayment";
import CancelPayment from "./pages/payment/CancelPayment";
import Gratitude from "./pages/gratitude/Gratitude";
import Orders from "./pages/marketplace/Orders";
import MyListings from "./pages/marketplace/MyListings";
import MarketplaceSell from "./pages/marketplace/MarketplaceSell";
import Contribute from "./pages/elevatedUser/gamify/Contribute";
import GamePage from "./pages/elevatedUser/gamify/GamePage";
import ImagePage from "./pages/elevatedUser/gamify/ImagePage";

function App() {
const { authUser } = useAuthContext();
const {enrolledUser} = useEnrollmentContext();

return (
<>
Expand All @@ -38,9 +46,21 @@ function App() {
<Route path="/profile" element={authUser ? <Profile /> : <Navigate to={"/"} />} />
<Route path="/marketplace" element={authUser ? <MarketPlace /> : <Navigate to={"/"} />} />
<Route path="/marketplace/buy/:id" element={authUser ? <MarketplaceBuy /> : <Navigate to={"/"} />} exact />
<Route path="/marketplace/sell" element={authUser ? <MarketplaceSell /> : <Navigate to={"/"} />} />
<Route path="/marketplace/my-listings" element={authUser ? <MyListings /> : <Navigate to={"/"} />} />
<Route path="/marketplace/orders" element={authUser ? <Orders /> : <Navigate to={"/"} />} />
<Route path="/complete-order" element={authUser ? <CompletePayment /> : <Navigate to={"/"} />} />
<Route path="/cancel-order" element={authUser ? <CancelPayment /> : <Navigate to={"/"} />} />
<Route path="/gratitude" element={authUser ? <Gratitude /> : <Navigate to={"/"} />} />
<Route path="/cancel-order" element={authUser ? <CancelPayment /> : <Navigate to={"/"} />} />
<Route path="/gratitude" element={authUser ? <Gratitude /> : <Navigate to={"/"} />} />
<Route path="/elevated-user/contribute" element={authUser ? <Contribute /> : <Navigate to={"/"} />} />
<Route
path="/elevated-user/play"
element={authUser ? (enrolledUser ? <GamePage /> : <Navigate to="/elevated-user/contribute" />) : <Navigate to="/" />}
/>
<Route
path="/elevated-user/images"
element={authUser ? (enrolledUser ? <ImagePage /> : <Navigate to="/elevated-user/contribute" />) : <Navigate to="/" />}
/>

<Route path="*" element={authUser ? <Navigate to="/home" /> : <Navigate to="/" />} />
</Routes>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ParticlesContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const ParticlesContainer = () =>{
},
particles:{
color:{
value :'#ff781f',
value :'#ffb280',
},
links :{
color : '#ff9d5c',
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/hooks/useCreateMetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useState } from "react";
import toast from "react-hot-toast";
import { useEnrollmentContext } from "../context/EnrollmentContext"

const useCreateMetadata = () => {
const [loading, setLoading] = useState();
const apiUrl = import.meta.env.VITE_API_URL;
const { setEnrolledUser } = useEnrollmentContext();

const createMetadata = async () => {
setLoading(true);
try {
const res = await fetch(`${apiUrl}/elevatedUser/metadata`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("KS-token")}`,
}
});

const data = await res.json();
if (data.error) {
throw new Error(data.error);
}

if (data) {
localStorage.setItem("KS-enrolledUser", JSON.stringify(data));
setEnrolledUser(data);
toast.success("Enrolled successfully");
}
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
}

return { loading, createMetadata };
}

export default useCreateMetadata;
Loading

0 comments on commit fcfe3b5

Please sign in to comment.