Is a "user rating" feature possible with Payload?

default discord avatar
vibllast year
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
    cbrualdilast year

    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
    last year

    @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 help straight from the Payload team with an Enterprise License.