Adjust rankings based on the number of upvotes received by a project

I'm facing a challenge with ranking projects based on the number of votes they receive. No matter the vote count, the project always ends up getting ranked as 1.

To address this issue, I developed a function to manage the rank count and a return handler for updating the vote count.

Here is the complete code snippet:

import clientPromise from '../../mongo/mongoDB';
import { ObjectId } from 'mongodb';

// Import collections and DB name from configuration
import { collections, DB } from '@/dynamic/mongoDB';

async function calculateRank(userVotesCollection, projectId) {
  const rankData = await userVotesCollection.aggregate([
    {
      $match: {
        project_id: new ObjectId(projectId),
        vote_type: "up"
      }
    },
    {
      $group: {
        _id: "$project_id",
        totalUpVotes: { $sum: 1 }
      }
    },
    {
      $sort: {
        totalUpVotes: -1
      }
    }
  ]).toArray();

  if (rankData.length === 0) {
    return null; // No up-votes for this project
  }

  // Assign ranks based on sorted order
  rankData.forEach((doc, index) => {
    doc.rank = index + 1;
  });

  return rankData[0].rank; // Return rank from the first document (highest up-votes)
}

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { project_id, vote_type, user_address } = req.body;

    try {
      const client = await clientPromise;
      const db = client.db(DB);

      const projectCollection = db.collection(collections.Token);
      const votesCollection = db.collection(collections.userVotes);

      // Validate the project ID
      if (!ObjectId.isValid(project_id)) {
        return res.status(400).json({ error: 'Invalid project ID' });
      }

      // Update the project's vote counts
      let updateFields = {};
      if (vote_type === 'up') {
        updateFields = { $inc: { project_up_votes: 1 } };

        const rank = await calculateRank(votesCollection, project_id);
        if (rank !== null) {
          const updateResult = await projectCollection.updateOne(
            { _id: new ObjectId(project_id) },
            { $set: { project_rank: rank } }
          );
          console.log("Updated rank:", updateResult);
        }
      } else if (vote_type === 'down') {
        updateFields = { $inc: { project_down_votes: 1 } };
      } else if (vote_type === '10-up') {
        updateFields = { $inc: { project_up_votes: 10 } };
      } else if (vote_type === '20-up') {
        updateFields = { $inc: { project_up_votes: 20 } };
      } else {
        return res.status(400).json({ error: 'Invalid vote type' });
      }

      const updateResult = await projectCollection.updateOne(
        { _id: new ObjectId(project_id) },
        updateFields
      );

      if (updateResult.modifiedCount === 0) {
        return res.status(404).json({ error: 'Project not found' });
      }

      // Create a new UserVote document object
      const newUserVote = {
        user_address,
        project_id: new ObjectId(project_id),
        vote_type,
      };

      // Save the UserVote document to the votesCollection
      const insertResult = await votesCollection.insertOne(newUserVote);

      if (insertResult.insertedCount !== 1) {
        return res.status(500).json({ error: 'Failed to save vote' });
      }

      // Send a success response to the client
      res.status(200).json({ message: 'Vote cast successfully' });

    } catch (error) {
      // Log any errors that occur and send a 500 error response
      console.error('Cast vote error: ', error);
      res.status(500).json({ error: 'Failed to cast vote' });
    }
  } else {
    res.setHeader('Allow', ['POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

The highest rank corresponds to 1, inversely proportional to the number of votes received by the project.

I have attempted various methods to accurately calculate the votes.

Answer №1

Make sure that the aggregation query accurately computes the total number of up-votes for each project across all projects.

The current method calculates the rank for individual projects, which may lead to incorrect rankings.

To update the Rank: The update logic should guarantee that ranks are recalculated for all projects after every vote.

We should refactor the calculateRank function to correctly handle ranking for all projects


async function calculateRank(userVotesCollection) {
  const rankData = await userVotesCollection.aggregate([
    {
      $match: {
        vote_type: "up"
      }
    },
    {
      $group: {
        _id: "$project_id",
        totalUpVotes: { $sum: 1 }
      }
    },
    {
      $sort: {
        totalUpVotes: -1
      }
    }
  ]).toArray();

  // Assign ranks based on sorted order
  rankData.forEach((doc, index) => {
    doc.rank = index + 1;
  });

  return rankData; // Return the full rank data
}

Update the handler to adjust the rank for all projects:


export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { project_id, vote_type, user_address } = req.body;

    try {
      const client = await clientPromise;
      const db = client.db(DB);

      const projectCollection = db.collection(collections.Token);
      const votesCollection = db.collection(collections.userVotes);

      // Validate the project ID
      if (!ObjectId.isValid(project_id)) {
        return res.status(400).json({ error: 'Invalid project ID' });
      }

      // Update the project's vote counts
      let updateFields = {};
      if (vote_type === 'up') {
        updateFields = { $inc: { project_up_votes: 1 } };
      } else if (vote_type === 'down') {
        updateFields = { $inc: { project_down_votes: 1 } };
      } else if (vote_type === '10-up') {
        updateFields = { $inc: { project_up_votes: 10 } };
      } else if (vote_type === '20-up') {
        updateFields = { $inc: { project_up_votes: 20 } };
      } else {
        return res.status(400).json({ error: 'Invalid vote type' });
      }

      const updateResult = await projectCollection.updateOne(
        { _id: new ObjectId(project_id) },
        updateFields
      );

      // Recalculate ranks for all projects
      const rankData = await calculateRank(votesCollection);

      // Update each project's rank in the database
      const bulkOps = rankData.map(doc => ({
        updateOne: {
          filter: { _id: new ObjectId(doc._id) },
          update: { $set: { project_rank: doc.rank } }
        }
      }));

      if (bulkOps.length > 0) {
        await projectCollection.bulkWrite(bulkOps);
      }

      return res.status(200).json({ message: 'Vote registered and ranks updated' });

    } catch (error) {
      console.error('Error updating vote count:', error);
      return res.status(500).json({ error: 'Internal server error' });
    }
  } else {
    res.setHeader('Allow', ['POST']);
    return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
  }
}

Now it calculates the total up-votes for all projects and arranges them accordingly. Ranks are assigned according to the sorted order for each project. The function returns the rank data for all projects.

Updates the vote count for the specified project. Re-calculates ranks for all projects following a vote being cast. Adjusts the rank for each project in the database using a bulk write operation. This method ensures that the ranks are updated accurately based on the total up-votes for all projects.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to enhance a class in JavaScript by adding a dynamic property

Within my code, I've defined a class which looks like this: class classA { id: number; name: string; constructor(data){ this.id = data?.id || 0; this.name = data?.name || ''; } } What I aim to do now is add ...

Teaching Selenium how to input text into Google's login field (programming with Python)

Encountering an issue with sending keys to the username and password fields in Google's sign-in box using Selenium. Despite locating the web elements with the IDs "Email" and "Passwd", I'm unable to input any keys into them. Here is the code sni ...

Is it possible to execute functions inline with conditional logic in react?

Is there a way to shorten the long conditions inside an inline if-else statement in React by putting a function inside it? I attempted to do this but encountered an error stating that "discount is not defined." function getDiscount(props) { const d ...

The React snackbar is mysteriously peeking out from behind the popup

While using the react-notifications-component snack bar, I encountered an issue where my snack bar was appearing behind the pop-up. Is there a way to fix this with z-index? I tried using <ReactNotification style={{ zIndex: 10000 }}/>, but it didn&ap ...

JavaScript AJAX function is returning an undefined value rather than a boolean true or false

My AJAX call function in jQuery has a complete section with the following code: complete: function(XMLHttpRequest, textStatus) { if(textStatus == "success") { return(true); } else { return(false); } } However, when ...

Using MongoDB map-reduce with Node.js: Incorporating intricate modules (along with their dependencies) into scopeObj

I am currently immersed in developing a complex map-reduce process for a mongodb database. To make the code more manageable, I have organized some intricate sections into modules that are then integrated into my map/reduce/finalize functions through my sco ...

The attribute "value" for Material-UI autocomplete cannot be used in conjunction with the "getOptionLabel" attribute

<Autocomplete id="license-select" options={licReqList} value = {licReqList[0] ? licReqList[0].licReqStr : null} getOptionLabel={(option) => option.licReqStr} onChange={ha ...

What is the best way to synchronize the state of a single React component across various pages?

I am currently working on a React Component that includes a toggle feature (on or off) with the state being managed by the component's own state (this.state). My dilemma is ensuring that this state remains when the user navigates from one page to ano ...

Choose from a variety of video play options

Being new to javascript, I've successfully combined two functions that control video playback, but they seem to be conflicting with each other. The first function is designed to offer custom pause and play controls for the video // VIDEO CONTROLS STA ...

"Encountered an error: Unable to interpret URL from (URL).vercel.app/api/getMessages" while deploying Next.js 13 using TypeScript on Vercel

Hello to all members of the StackOverflow Community: I am encountering an error message stating "TypeError: Failed to parse URL from next-chat-lenx51hr5-gregory-buffard.vercel.app/api/getMessages" while attempting to build my Next.js 13 application using T ...

REACT performance impacted by slow array filtering

I have a custom listbox feature, where a div holds a vertical list of other div elements. There is also an input field for searching within the list. While it works fine with small data sets, it becomes extremely slow with large amounts of data. In additi ...

The information stored in the useRef hook experiences a delay when accessed through the useImperativeHandle function

After implementing useImperativeHandle from the Input Component to transfer ref data to the Login Component, I encountered an issue where the emailInputRef data was delayed compared to the inputRef data. const Login = () => { const router = useRouter( ...

Tips for automatically incorporating animation upon page initialization

I'm looking to add an automatic image effect when the page is loaded. I currently have this code in my js file: $(window).ready(function(){ $(pin).click(function(){ $("#pin01").show().animate({left: '650px'}); }) }); Here is the HTML wit ...

Obtain the Text Content from a Knockout Dropdown Menu

Currently, I am facing an issue with extracting the text value of a selected option from a dropdown menu on my webpage. The dropdown contains various image categories and is defined as follows: <select class="form-control" data-bind="options: imageCate ...

Using PHP to send asynchronous requests to the server can greatly enhance

I have almost completed my project, but I am facing an issue with reading the data sent to the server. function main() { jQ(document).on("keyup", "form input", function () { var data = new FormData(); var value = jQ(this).val(); da ...

Make the textarea larger and bring it to the forefront when it is selected

I would like to make a textarea expand (increase its height) when it is in focus. The expanded textarea should not push the content down, but rather be displayed above other content. Currently, this is the code I am using (check out the example here): $( ...

Set the array back to its initial value of 1 using jQuery and

After making substantial edits to this question, I find myself in need of a reset button for the code. The current item needs to be reset back to 1 so that I can use it again, but the issue lies in the fact that only the current item is reset while the hig ...

Error: Attempting to access property 'setData' of an undefined object results in a TypeError [Slider]

I encountered an error with my slider that says Uncaught TypeError: Cannot read property 'setData' of undefined. The error occurs when I use material ui as a component along with redux-form. This issue happens specifically when the slider is bein ...

Tips for Achieving Observable Synchronization

I've encountered a coding challenge that has led me to this code snippet: ngOnInit(): void { this.categories = this.categoryService.getCategories(); var example = this.categories.flatMap((categor) => categor.map((categories) = ...

Node.js express version 4.13.3 is experiencing an issue where the serveStatic method is not properly serving mp3 or

I am currently utilizing Express 4.13.3 along with the serve-static npm module to serve static assets successfully, except for files with mp3 or ogg extensions. Despite reviewing the documentation, I have not come across any information indicating that thi ...