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

Oops! Looks like we couldn't locate the request token in the session when attempting to access the Twitter API

Every time I attempt to connect to the Twitter API using Passport OAuth, an issue arises that redirects me to an error page displaying this message: Error: Failed to locate request token in session at SessionStore.get (/Users/youcefchergui/Work/ESP/socialb ...

When the user clicks on an organizational chart, a new organizational chart will appear in a modal popup

Currently, I am developing a project with Highcharts where I have a specific requirement to display a modal popup when a node on an org chart is clicked. The popup should also contain another org chart. Below is the code snippet I am working with: [link to ...

The lightSlider is only destroyed and rebuilt once during its operation

I am facing a challenge with multiple buttons having the same class while trying to destroy and rebuild the lightSlider script. In the CMS where I have implemented this, images and ajax are being loaded. However, as the JavaScript is triggered by the read ...

Testing NextJS App Router API routes with Jest: A comprehensive guide

Looking to test a basic API route: File ./src/app/api/name import { NextResponse } from 'next/server'; export async function GET() { const name = process.env.NAME; return NextResponse.json({ name, }); } Attempting to test ...

Stop event bubbling in Vue.js for router link

I'm working with the following HTML template... <template> <li class="nav-item" style="z-index:9"> <router-link :to="link.path" @click.native="linkClick" ...

Tips for including subjects in JSON data

I am trying to include the subject in JSON data so that I can fetch it using $.each(data.subject). Below is my API code where I am retrieving all the data encoded in JSON format. Any assistance would be greatly appreciated. [{"id":"79","FirstName":"Elon", ...

Having trouble with your jQuery AJAX function not receiving the text returned from your PHP file?

After extensive searching, I have come across several individuals facing the same issue as me, but unfortunately, none of them seem to have found a solution. The problem at hand is that PHP is not providing any data to the AJAX function. When I try to dis ...

The npm outdated -g command is producing an error message that states "Unable to read the length property of undefined"

I am currently facing an issue while trying to check the version status of my npm installed global packages. When I run the command npm outdated -g --depth=0 in the terminal, I encounter the following error: npm ERR! Cannot read property 'length&apos ...

What is the best way to halt the current event handler thread's execution when another event is triggered that calls the same handler at

One of the functions in my code filters and sorts contents of a select dropdown based on input text entered by the user. The function filterSort() is triggered on each keyup event from the input field. Code $(inputTextField).keyup(function() { / ...

Achieve resumable uploads effortlessly with google-cloud-node

While this code works well for regular uploads, I am curious about how the resumable upload feature functions when a user loses connection during a large upload. Does simply setting 'resumable' to true in the writeStream options make it work effe ...

If an element with a "hidden" display property is loaded in the browser window, will it be visible?

Is an element in a hidden display still using memory when the page is loaded? It's convenient to have many elements on a page, but if 99 elements are hidden and only 1 is displayed, does that impact the loading of the page? I'm curious if the pa ...

Running Javascript based on the output of PHP code

I have been working on my code with test.php that should trigger some Javascript when my PHP code, conditional.php, detects input and submits it. However, instead of executing the expected "Do Something in Javascript," it outputs "Not empty" instead. I fin ...

Browserify is unable to locate the 'jquery' module

While attempting to package my app with browserify, I encountered the following error message: Cannot find module 'jquery' from '/home/test/node_modules/backbone' I have searched for solutions to this issue, but none of them seem to ...

Repetitive occurrences of events being emitted from a VueJS component

As my mouse cursor hovers over and exits my VueJS component, specific methods are triggered accordingly. The methods that execute when the cursor enters and leaves my component: // Included in the "methods" section of my Vue component file onMouseEnter( ...

Attempting to transmit information to database using AJAX in the context of CodeIgniter

I'm having some trouble with my AJAX setup. It doesn't seem to be posting any data to the database, despite trying various solutions I found online. That's why I've turned to this platform for help. When testing in Postman and sending ...

Run code once the Firestore get method has completed its execution

Is it possible to execute code after the get method is finished in JavaScript, similar to how it can be done in Java (Android)? Below is an example of my code: mColRef.get().then(function(querySnapshot){ querySnapshot.forEach(function(doc) { ...

Minimize the entire project by compressing the .css, .js, and .html files

After recently incorporating Grunt into my workflow, I was thrilled with how it streamlined the process of minifying/concatenating .css files and minifying/uglify/concatenating .js files. With Grunt watch and express, I was able to automate compiling and ...

Error message: Unable to locate module when using a variable to import an image in React

I've encountered an issue with my React code that I can't seem to figure out. I am integrating the Accuweather API and trying to display the weather icon on my app. Initially, everything seemed to be working fine as I constructed the image path l ...

Using Python to interact with forms and click JavaScript buttons

Is there a way to automate form filling on a website by setting specific parameters that will bring up products matching those parameters? I attempted to use mechanize in python, but it does not support javascript. It seems like the process of entering par ...

Exploring Time Scaling and Range Adjustment in D3 Barcharts

My dataset looks something like this: dateRange = [ { date: 2017-03-23, value: 10 }, { date: 2017-03-25, value: 15 }, { date: 2017-04-01, value: 13 }, { date: 2017-04-02, value: 19 } ]; The results can be viewed here: https://embed.plnkr.co/iOBAuCZmo ...