Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Is a "user rating" feature possible with Payload?

default discord avatar
vibl2 years ago
1 2

How can I create something akin to a "user rating" feature? I.e., each user can create and edit their own row for each article, either as a many-to-many relationship article_user_rating or as a hasMany relationship field in the article or in the user collection. In both case, the row should includes a field for the rating (integer).

I couldn't find a way to constrain the [article_id, user_id] pair to be unique. Is something like a composite key unique constraint possible?

Also, how would I display the 5-stars widget for the current user to be able to rate the article in the admin edit page?

  • Selected Answer
    default discord avatar
    cbrualdi2 years ago

    I'd approach it this way:

    • A beforeChange hook on RatingCollection to save the user who is rating in a readonly or disabled field
    • An access:read function to show the user only his own ratings
    • A validate function on the product relation to check the uniqueness of the [user, products] rating

    Something like this:

    const ProductsCollection: CollectionConfig = {
      slug: 'products-for-rating',
      labels: { plural: 'Products' },
      admin: {
        group: 'Rating',
        useAsTitle: 'productName'
      },
      fields: [
        {
          name: 'productName',
          type: 'text',
        }
      ],
    }
    const RatingsCollection: CollectionConfig = {
      slug: 'ratings',
      admin: {
        group: 'Rating',
      },
      fields: [
        {
          name: 'product',
          type: 'relationship',
          relationTo: 'products-for-rating',
          validate: async (val, { user }) => {
            const query = qs.stringify({
              where: {
                and: [
                  {
                    product: { equals: val }
                  },
                  {
                    user: { equals: user.id }
                  }
                ]
              } 
            }, { addQueryPrefix: true })
            
            const response = await fetch(`http://localhost:3000/api/ratings${query}`)
            const sameRating = await response.json()
    
            if (!sameRating.totalDocs) {
              return true;
            }
    
            return 'You have already rated this product.';
          },
        },
        {
          name: 'rating',
          type: 'number',
          min: 1, max: 5,
          // admin: {
          //   components: {
          //     Field: FiveStarsField
          //   }
          // }
        },
        {
          name: 'user',
          type: 'relationship',
          relationTo: 'users',
          admin: {
            readOnly: true
          },
        },
      ],
      access: {
        read: ({ req: { user }, id }) => { 
          return {
            user: { 
              equals: user.id 
            }
          } 
        }
      },
      hooks: {
        beforeChange: [async ({
          data,
          req, 
        }) => {
          data.user = req.user.id
          return data;
        }]
      }
    }

    To show the 5-stars field you can provide a custom Field Component to the rating field.

    If I understand your problem correctly, this work for me, but I'm curious to know if there's a better approach.

  • discord user avatar
    JarrodMFlesch
    2 years ago

    @vibl You could create a ratings collection, and store your data there. You would have a field for the user, the article and the rating.

    To make them unique you could create a hidden field (set it to unique in the config) and use a beforeValidate hook to generate the value for that field by concatenating the userID and the articleID.

    To display it on an article page within the admin panel, you could create a custom ui field, that fetches the users rating based on the article they are viewing (the useDocumentInfo hook would be useful here). If there is a rating, set that as your value in your customStarComponent field. On change make a POST req to the ratings collection where user = userID and article = articleID.

    How does that sound to you?

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.