Objectives

Complete the implementation of the Assignment 1

Logedin Status

The starting point of this lab is the solution from the previous lab:

Be sure to import this project - it will be named 'spacebook-assignment-part-2' when you import it.

User Model

To support logged in status - introduce a new field into the User class:

  public boolean online;

Account

In the account class, we need to maintain this field in the Authenticate method:

  public static void authenticate(String email, String password)
  {
    Logger.info("Attempting to authenticate with " + email + ":" + password);

    User user = User.findByEmail(email);
    if ((user != null) && (user.checkPassword(password) == true))
    {
      Logger.info("Authentication successful");
      session.put("logged_in_userid", user.id);
      user.online = true;
      user.save();
      Home.index();
    }
    else
    {
      Logger.info("Authentication failed");
      login();
    }
  }

and also in logout:

  public static void logout()
  {
    String userId = session.get("logged_in_userid");
    User user = User.findById(Long.parseLong(userId));
    user.online = false;
    user.save();
    session.clear();
    index();
  }

The friends and members components now need to be updated to represent the status:

views/components/friends.html

<section class="ui stacked segment">
  <h4 class="ui inverted red block header">Friends (${user.friendships.size()})</h4>
  <div class="ui list"> 
    #{list items:user.friendships, as:'friendship'}
      <div class="ui item"> 
        #{if friendship.targetUser.online}
          <i class="user green icon"></i>
        #{/if}
        #{else}
          <i class="user red icon"></i>
        #{/else}
        <div class="content">
          <a href="/publicprofile/${friendship.targetUser.id}"> ${friendship.targetUser.firstName} ${friendship.targetUser.lastName} </a>
            (<a href="/home/drop/${friendship.targetUser.id}"> drop </a>)
        </div>
      </div>
    #{/list}
  </div> 
</section> 

views/components/members.html

<section class="ui stacked segment">
  <div class="ui list"> 
    #{list items:users, as:'user'}
      <div class="ui item"> 
        #{if user.online}
          <i class="user green icon"></i>
        #{/if}
        #{else}
          <i class="user red icon"></i>
        #{/else}
        <div class="content">
          ${user.firstName} ${user.lastName}: : ${user.statusText}  <a href="members/follow/${user.id}"> (follow) </a> 
        </div>
      </div>
    #{/list}
  </div>
</section>

Look closely at the if statements above.

This should now work - verify by logging in to two separate accounts simultaneously (you will need to use 2 different browsers types).

Comment Date/Time

We already have the date/time recorded in the Comment class as the postedAt field:

Comment:

@Entity
public class Comment extends Model
{
  @Lob
  public String content;

  @OneToOne 
  public User author;

  public Date postedAt;

  public Comment(User author, String content)
  {
    this.author   = author;
    this.content  = content;
    this.postedAt = new Date();
  }
}

It is created (with the current actual time) when Comment objects are created.

To display the time/date, a small change to the BlogPost view:

Views/BlogPost/view.html

...
  <section class="ui stacked segment">
    <h5 class="ui inverted blue block header">Comments:</h5>
    #{list items:post.comments, as:'comment'}
      <p> 
        ${comment.content} : ${comment.author.firstName} : on : ${comment.postedAt.format('dd MMM yy HH:mm:ss')}
        <hr>
      </p>
    #{/list}  
  </section>
...

Try no to see if the date is accurately presented.

Public Blog

conf/route

Introduce a new route to support the public browsing of blogs:

GET   /blog/publicblog/{userid}                      Blog.publicblog

controllers/Blog

This is the new method in the Blog controller to service this route:

  public static void publicblog(Long userid)
  {
    User user = User.findById(userid);
    render(user);
  }

view/Blog/publicblog.html

We need a new view to be rendered by the above action:

#{extends 'main.html' /}
#{set title:'Blog' /}

<nav class="ui inverted menu">
  <a  class="item" href="/home">Spacebook</a>
</nav> 

<h1 class="ui inverted red block header" >${user.firstName} ${user.lastName} 's Blog</h1>

<section class="ui stacked segment">
  <h2 class="ui inverted blue block header">Published Posts</h4>
  #{list items:user.posts, as:'post'}
    <h3 class="ui inverted green block header"> ${post.title}</h4>
    <p> ${post.content} </a> 

    <section class="ui raised segment">
    <h5 class="ui inverted teal block header">Comments:</h5>
    #{list items:post.comments, as:'comment'}
      <p> 
        ${comment.content} : ${comment.author.firstName} : on : ${comment.postedAt.format('dd MMM yy HH:mm:ss')}
        <hr>
      </p>                 
    #{/list}  
    </section>   
  #{/list}
</section>

Look carefully at the above code - particularly the controller action. We need the user id to find the correct user object. We then pass this to the view, which walks the posts the user has made and displays them.

How would you try our the above? We can try it out by typing in correct routes directly into the browser title bar:

The above link will work if there is a user with id 1. Try out a few different ids.

Links to Public Blog

Views/compoments/members.html

Here is a replacement for the members component:

  <table class="ui table segment">
    <tbody>
      <tr>
        #{list items:users, as:'user'}
        <tr>
          <td>
            #{if user.online}
              <i class="user green icon"></i>
            #{/if}
            #{else}
              <i class="user red icon"></i>
            #{/else}
          </td>
          <td>
            ${user.firstName} ${user.lastName}
          </td>
          <td>
            ${user.statusText}  
          </td>
          <td>
            <a href="members/follow/${user.id}"> (follow) </a> 
          </td>  
          <td>
            <a href="/blog/publicblog/${user.id}"> Blog </a>
          </td>
        </tr>
        #{/list}
    </tbody>
  </table>  

This should present a link to each users blog on the members page.

views/Accounts/index.html

We can now also include this in the start page:

...
<section class="ui raised segment">
  <p> Signup or Log in to the Spacebook Service </p>
  #{include "components/members.html" /} 
</section>

controllers/Accounts:

For the above to work, we need to make sure all the users are sent to this view:

  public static void index()
  {
    List<User> users = User.findAll();
    render(users);
  }

Now all blogs are listed on the start page + on the members page.

Commenting on Public Blogs

conf/routes

To support commenting on public blogs, we need a new route:

POST  /blog/publicblog/{userid}/newcomment/{postid}  Blog.newComment

Note this route need 2 ids: the user and the blog post we are commenting on. Note also then way the url is constructed, with the user id in the middle of the path.

controllers/Blog

This is the action to support this route:

  public static void newComment(Long userid, Long postid, String content)
  {    
    Logger.info("Post ID = " + postid);
    Post post = Post.findById(postid);

    User user = Accounts.getLoggedInUser();
    Comment comment = new Comment(user, content);    
    post.comments.add(comment);
    post.save();
    publicblog(userid);
  }  

Read this action very carefully - particularly note the role of 'userid' in the method.

To go with the above, we also need a new version of publicBlog in the same class:

  public static void publicblog(Long userid)
  {
    User loggedInUser = null;
    String userId     = session.get("logged_in_userid");
    if (userId != null)
    {
      loggedInUser = User.findById(Long.parseLong(userId));
    }

    User user = User.findById(userid);
    render(user, loggedInUser);
  }

Again, read this carefully. Note that we are now passing two parameters to the view. What are they?

views/Blog/publicblog.html

This is a new version of this view:

#{extends 'main.html' /}
#{set title:'Blog' /}

<nav class="ui inverted menu">
  <a  class="item" href="/home">Spacebook</a>
  <div class="right menu">
    #{if loggedInUser}
      <div class="item"> Logged in as ${loggedInUser.firstName} ${loggedInUser.lastName} </div>
    #{/if}
    #{else}
      <a class="item" href="/login"> Log in to comment </a>
    #{/else}
  </div>
</nav> 

<h1 class="ui inverted red block header" >${user.firstName} ${user.lastName} 's Blog</h1>

<section class="ui stacked segment">
  <h2 class="ui inverted blue block header">Published Posts</h4>
  #{list items:user.posts, as:'post'}
    <h3 class="ui inverted green block header"> ${post.title}</h4>
    <p> ${post.content} </a> 

    <section class="ui raised segment">
    <h5 class="ui inverted teal block header">Comments:</h5>
    #{list items:post.comments, as:'comment'}
      <p> 
        ${comment.content} : ${comment.author.firstName} : on : ${comment.postedAt.format('dd MMM yy HH:mm:ss')}
        <hr>
      </p>                 
    #{/list}  
    #{if loggedInUser}
      <section class="ui form segment">
        <h4 class="ui inverted blue block header">Add a comment</h4>
        <form action="/blog/publicblog/${user.id}/newcomment/${post.id}" method="POST">
          <div class="field">
            <textarea name="content" placeholder="Content"></textarea>
          </div>
          <button class="ui button green submit labeled icon"><i class="icon edit"></i> Add Comment </button>
        </form>
      </section>
    #{/if}
    </section>   
  #{/list}
</section>

Study it carefuly - and try it out now

Exercises

This is the final version as at the end of this lab:

Import this into your workspace and run it to make sure all the feature behave as expected.

Exercise 1: Tags

Re-implement all of the templates in 'views/components' as tags. Look back on the previous labs to understand tags again and how they are implemented.