Jake Rawsthorne avatar

Development Update #2: Ratings, Subscriptions and Profiles

jrawsthorne

Published: 15 May 2018 › Updated: 15 May 2018Development Update #2: Ratings, Subscriptions and Profiles

Development Update #2: Ratings, Subscriptions and Profiles

Repository

https://github.com/jrawsthorne/review.app

New Features

  • Better filters for pages - store list of posts matching filter in redux store
  • Post formatting - youtube/dtube/more complex markdown/html
  • User profile pages
  • Allow all users to give star rating
  • Subscribe to certain show
  • Subscribed page showing all posts from subscribed shows/movies

#1 User profile pages GitHub Link

You can visit a user's profile page by going to /@username like with most steem apps. The profile page shows the name, username, website, location and bio at the top with the avatar to the left and cover in the background. Below that is posts by that user.

image.png

profiles.gif

#2 Star ratings

I decided that a user should be able to rate any media item on the site so I needed to create a place for ratings in the database.

This is the schema (GitHub Link)

const schema = new Schema(
  {
    user: {
      type: Schema.Types.ObjectId,
      ref: 'users',
      required: true,
    },
    score: {
      type: Number,
      required: true,
    },
    mediaType: {
      type: String,
      required: true,
    },
    tmdbid: {
      type: Number,
      required: true,
    },
    seasonNum: {
      type: Number,
    },
    episodeNum: {
      type: Number,
    },
  },
  {
    timestamps: true,
  },
);

Once logged in a user can go to any post and with the rest of the metadata at the top there are 5 stars. Clicking one will immediately add this rating to your account. In the future when there are sufficient ratings I hope to display an average as well. Clicking the same number of stars again will remove the rating so the minimum you can give is 1 star.

image.png

ratings.gif

When a user sets the score to 0 it is completely deleted from the database. GitHub Link

if (parseInt(value, 10) === 0) {
          return Rating
            .findOne({
              user,
              mediaType,
              tmdbid,
              seasonNum,
              episodeNum,
            })
            .remove()
            .then(() => {
              User.update({ _id: user.id }, { $pull: { ratings: rating.id } })
                .then(() => res.json({}));
            });
        }

#3 Subscriptions

A user is also able to subscribe to certain shows or movies if they want to produce a feed with just posts from those shows/movies. I decided to implement this and will provide it as well as a general steem feed of content from users you follow.

This is the schema (GitHub Link)

const schema = new Schema(
  {
    user: {
      type: Schema.Types.ObjectId,
      ref: 'users',
      required: true,
    },
    tmdbid: {
      type: Number,
      required: true,
    },
    type: {
      type: String,
      required: true,
    },
  },
);

To display all the posts for a subscription I loop through each one and find all the posts matching that criteria and add it to an array. (GithHub Link)

router.get('/subscriptions', passport.authenticate('jwt', { session: false }), (req, res) => {
  const { user } = req;
  let count = 0;
  Subscription.find({ user })
    .then((subscriptions) => {
      Promise.all(subscriptions.map(subscription =>
        Post.count({ type: subscription.type, tmdbid: subscription.tmdbid }).then(c =>
          Post.find({ type: subscription.type, tmdbid: subscription.tmdbid })
            .then((posts) => { count += c; return posts; }))))
        .then(posts => res.json({
          count,
          results: orderBy(flatten(posts), 'createdAt', 'desc'),
        }));
    })
    .catch(() => res.status(404).json({ error: 'Error fetching subscriptions' }));
});

image.png

image.png

subscriptions.gif

#4 JSON web token

To facilitate the previous two new features I added a new sign in flow so that I don't have to rely on steem authentication for every protected action. Having to access the steem api for every rating and subscription was very slow. As those two things don't rely on the steem blockchain it was unnecessary to authenticate using a steemconnect token so I now store a JSON web token with the users details in it. (GitHub Link)

router.post('/login', (req, res) => {
  const { accessToken } = req.body;
  if (!accessToken) res.status(400).json({ error: 'Not authenticated' });
  steemConnectAPI.setAccessToken(accessToken);
  steemConnectAPI.me()
    .then((steemUser) => {
      User.findOne({ username: steemUser.name }).populate({ path: 'ratings', select: 'score mediaType tmdbid seasonNum episodeNum' }).populate('subscriptions', 'tmdbid type')
        .then((user) => {
          if (user) {
            const token = jwt.sign({
              id: user.id,
              username: user.username,
            }, process.env.JWT_SECRET, { expiresIn: '1h' });
            res.json({
              user: {
                ...steemUser,
                account: {
                  ...steemUser.account,
                  ratings: {
                    scores: user.ratings,
                  },
                  subscriptions: {
                    items: user.subscriptions,
                  },
                },
              },
              token,
            });
          } else {
            new User({ username: steemUser.name }).save().then((newUser) => {
              const token = jwt.sign({
                id: newUser.id,
                username: newUser.username,
              }, process.env.JWT_SECRET, { expiresIn: '1h' });
              res.json({
                user: steemUser,
                token,
              });
            });
          }
        });
    })
    .catch(() => res.status(400).json({ error: 'Not authenticated' }));
});

#5 Updated styling

The interface for switching between episodes and seasons has been improved with a clickable tooltip. Although this doesn't provide as much information for the user, it saves a lot of space that was wasted before in my opinion. (GitHub Link)

{props.seasons &&
  <div className="MediaHeader__info__selectors">
    {props.seasons &&
      <Popover
        onVisibleChange={props.handleSeasonVisibleChange}
        visible={props.showSeasons}
        placement="bottom"
        content={Object.values(props.seasons).map(season =>
      (
        <p
          onClick={() => handleSeasonClick(season.season_number)}
          className="Filter__option"
          key={`season-${season.season_number}`}
        >{season.name}
        </p>
      ))}
        trigger="click"
      >
        <span className="Filter__dropdown" style={{ marginLeft: 0 }}>
      Seasons <Icon type="down" style={{ fontSize: 15 }} />
        </span>
      </Popover>
    }
    {props.episodes &&
      <Popover
        onVisibleChange={props.handleEpisodeVisibleChange}
        visible={props.showEpisodes}
        placement="bottom"
        content={Object.values(props.episodes).map(episode =>
      (
        <p
          onClick={() => handleEpisodeClick(episode.episode_number)}
          className="Filter__option"
          key={`season-${episode.episode_number}`}
        >{episode.name}
        </p>
      ))}
        trigger="click"
      >
        <span className="Filter__dropdown">
      Episodes <Icon type="down" style={{ fontSize: 15 }} />
        </span>
      </Popover>
    }
  </div>
}

image.png

selectors.gif

Roadmap

  • Ability to actually write new posts
  • Store all database info in JSON metadata of a post as backup
  • User actions (like, comment)

Proof of Work Done https://github.com/jrawsthorne

Leave Development Update #2: Ratings, Subscriptions and Profiles to:

Written by

Developer, entrepreneur and Utopian contributor

Read more #utopian-io posts


Best Posts From Jake Rawsthorne

We have not curated any of jrawsthorne's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From Jake Rawsthorne