Incorporate Sessions into the latest Playlist Application
The Todolist application evolved Member
model + an Accounts
controller that we used to manage signup/login features. These are the classes here:
package controllers;
import models.Member;
import play.Logger;
import play.mvc.Controller;
public class Accounts extends Controller
{
public static void signup()
{
render("signup.html");
}
public static void login()
{
render("login.html");
}
public static void register(String firstname, String lastname, String email, String password)
{
Logger.info("Registering new user " + email);
Member member = new Member(firstname, lastname, email, password);
member.save();
redirect("/");
}
public static void authenticate(String email, String password)
{
Logger.info("Attempting to authenticate with " + email + ":" + password);
Member member = Member.findByEmail(email);
if ((member != null) && (member.checkPassword(password) == true)) {
Logger.info("Authentication successful");
session.put("logged_in_Memberid", member.id);
redirect ("/dashboard");
} else {
Logger.info("Authentication failed");
redirect("/login");
}
}
public static void logout()
{
session.clear();
redirect ("/");
}
public static Member getLoggedInMember()
{
Member member = null;
if (session.contains("logged_in_Memberid")) {
String memberId = session.get("logged_in_Memberid");
member = Member.findById(Long.parseLong(memberId));
} else {
login();
}
return member;
}
}
package models;
import play.db.jpa.Model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Member extends Model
{
public String firstname;
public String lastname;
public String email;
public String password;
@OneToMany(cascade = CascadeType.ALL)
public List<Todo> todolist = new ArrayList<Todo>();
public Member(String firstname, String lastname, String email, String password)
{
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
this.password = password;
}
public static Member findByEmail(String email)
{
return find("email", email).first();
}
public boolean checkPassword(String password)
{
return this.password.equals(password);
}
}
The Member class will have an error:
public List<Todo> todolist = new ArrayList<Todo>();
This is a holdover from the Todo application. Replace the above line with this:
public List<Playlist> playlists = new ArrayList<Playlist>();
Each member will have a list of Playlist
objects, called playlists. This is the members personal collection of playlists.
The project should now be without errors:
Todolist also has signup and login views - which implement simple forms for this purpose:
#{extends 'main.html' /}
#{set title:'Signup' /}
#{welcomemenu id:"signup"/}
<div class="ui two column grid basic middle aligned segment">
<div class="column">
<form class="ui stacked segment form" action="/register" method="POST">
<h3 class="ui header">Register</h3>
<div class="two fields">
<div class="field">
<label>First Name</label>
<input placeholder="First Name" type="text" name="firstname">
</div>
<div class="field">
<label>Last Name</label>
<input placeholder="Last Name" type="text" name="lastname">
</div>
</div>
<div class="field">
<label>Email</label>
<input placeholder="Email" type="text" name="email">
</div>
<div class="field">
<label>Password</label>
<input type="password" name="password">
</div>
<button class="ui blue submit button">Submit</button>
</form>
</div>
<div class="column">
<img class="ui image" src="/public/images/todo-1.png">
</div>
</div>
#{extends 'main.html' /}
#{set title:'login' /}
#{welcomemenu id:"login"/}
<div class="ui two column middle aligned grid basic segment">
<div class="column">
<form class="ui stacked segment form" action="/authenticate" method="POST">
<h3 class="ui header">Log-in</h3>
<div class="field">
<label>Email</label> <input placeholder="Email" name="email">
</div>
<div class="field">
<label>Password</label> <input type="password" name="password">
</div>
<button class="ui blue submit button">Login</button>
</form>
</div>
<div class="column">
<img class="ui image" src="/public/images/todo-2.jpg">
</div>
</div>
It also has this partial - a menu to support signup + login options:
<nav class="ui menu">
<header class="ui header item"> <a href="#"> Playlist 5 </a></header>
<div class="right menu">
<a id="signup" class="item" href="/signup"> Signup </a>
<a id="login" class="item" href="/login"> Login </a>
</div>
</nav>
<script>
$("#${_id}").addClass("active item");
</script>
Bring this in now.
The existing menu.html
will need an additional option to support log out:
<nav class="ui menu">
<header class="ui header item"> <a href="#"> Playlist 5 </a></header>
<div class="right menu">
<a id="dashboard" class="item" href="/dashboard"> Dashboard </a>
<a id="about" class="item" href="/about"> About </a>
<a id="logout" class="item" href="/logout"> Logout </a>
</div>
</nav>
<script>
$("#${_id}").addClass("active item");
</script>
We should change the start
view to include this menu instead of the main menu:
#{extends 'main.html' /}
#{set title:'Start' /}
#{welcomemenu id:"start"/}
<section class="ui center aligned middle aligned segment">
<h1 class="ui header">
Welcome to Playlist 5
</h1>
<p>
A small app to let you compose playlists. This app will allow you to create, manage and share your playlists. Simple enter the playlist details one the dashboard.
Please Signup or Login using the menu above.
</p>
</section>
The view folder of the project should now look like this:
These forms require these additional routes:
GET /signup Accounts.signup
GET /login Accounts.login
POST /register Accounts.register
POST /authenticate Accounts.authenticate
GET /logout Accounts.logout
Make sure to place these additional route towards the top of the file:
# Home page
GET / Start.index
GET /signup Accounts.signup
GET /login Accounts.login
POST /register Accounts.register
POST /authenticate Accounts.authenticate
GET /logout Accounts.logout
...
Run the application now - and sign up 2 users and log in as each user in turn. Experiment with the various forms.
Do you notice the slightly odd behaviour? We seem to have a single playlist - regardless of who is logged in.
Clearly this is not what we intended - each user should have their own playlist, separate from other users. We will tackle this in the next step.
This is the current version of the Dashboard controller:
package controllers;
import java.util.ArrayList;
import java.util.List;
import models.Playlist;
import models.Song;
import play.Logger;
import play.mvc.Controller;
public class Dashboard extends Controller
{
public static void index()
{
Logger.info("Rendering Admin");
List<Playlist> playlists = Playlist.findAll();
render ("dashboard.html", playlists);
}
public static void deletePlaylist (Long id)
{
Playlist playlist = Playlist.findById(id);
Logger.info ("Removing" + playlist.title);
playlist.delete();
redirect ("/dashboard");
}
public static void addPlaylist (String title)
{
Playlist playlist = new Playlist (title, 0);
Logger.info ("Adding a new playlist called " + title);
playlist.save();
redirect ("/dashboard");
}
}
In the above, there is no reference to the currently logged in user - a single global collection of playlists is manipulated.
Replace this controller with the following version:
package controllers;
import java.util.ArrayList;
import java.util.List;
import models.Member;
import models.Playlist;
import models.Song;
import play.Logger;
import play.mvc.Controller;
public class Dashboard extends Controller
{
public static void index()
{
Logger.info("Rendering Dasboard");
Member member = Accounts.getLoggedInMember();
List<Playlist> playlists = member.playlists;
render ("dashboard.html", playlists);
}
public static void deletePlaylist (Long id)
{
Logger.info("Deleting a Playlist");
Member member = Accounts.getLoggedInMember();
Playlist playlist = Playlist.findById(id);
member.playlists.remove(playlist);
member.save();
playlist.delete();
redirect ("/dashboard");
}
public static void addPlaylist (String title)
{
Logger.info("Adding a Playlist");
Member member = Accounts.getLoggedInMember();
Playlist playlist = new Playlist (title, 0);
member.playlists.add(playlist);
member.save();
redirect ("/dashboard");
}
}
This is modelled on the equivalent controller in the Todolist-2 application. Each controller method always carries out the following:
Run the application now again, and verify that you can create and delete playlists. Keep a note of the names of the playlists you create for a given user - and switch users a few times. Verify that you are only manipulating the playlists for the logged in user.
This is the current version of PlaylistCtrl
public class PlaylistCtrl extends Controller
{
public static void index(Long id)
{
Playlist playlist = Playlist.findById(id);
Logger.info ("Playlist id = " + id);
render("playlist.html", playlist);
}
public static void deletesong (Long id, Long songid)
{
Playlist playlist = Playlist.findById(id);
Song song = Song.findById(songid);
Logger.info ("Removing" + song.title);
playlist.songs.remove(song);
playlist.save();
song.delete();
render("playlist.html", playlist);
}
public static void addSong(Long id, String title, String artist, int duration)
{
Song song = new Song(title, artist, duration);
Playlist playlist = Playlist.findById(id);
playlist.songs.add(song);
playlist.save();
redirect ("/playlists/" + id);
}
}
Without making any changes to this class, log in as 2 different users, and populate a few playlists for each. Verify that when you switch users that the playlists are always appropriate to the logged in user.
Consider the following questions:
This is our current data.yml
file:
Song(s1):
title: Piano Sonata No. 3
artist: Beethoven
duration: 5
Song(s2):
title: Piano Sonata No. 7
artist: Beethoven
duration: 6
Song(s3):
title: Piano Sonata No. 10
artist: Beethoven
duration: 8
Song(s4):
title: Piano Concerto No. 27
artist: Beethoven
duration: 8
Song(s5):
title: Piano Concertos No. 17
artist: Beethoven
Song(s6):
title: Piano Concerto No. 10
artist: Beethoven
duration: 12
Song(s7):
title: Opus 34 Six variations on a theme in F major
artist: Beethoven
Song(s8):
title: Opus 120 Thirty-three variations on a waltz by Diabelli in C major
artist: Beethoven
Playlist(p1):
title: Bethoven Sonatas
duration: 19
songs:
- s1
- s2
- s3
Playlist(p2):
title: Bethoven Concertos
duration: 23
songs:
- s4
- s5
- s6
Playlist(p3):
title: Beethoven Variations
duration: 26
songs:
- s7
- s8
The contents of this file are still quite valid, but we can extend it to include members as well.
Append the following to the end of the file:
Member(m1):
firstname: homer
lastname: simpson
email: homer@simpson.com
password: secret
Member(m2):
firstname: marge
lastname: simpson
email: marge@simpson.com
password: secret
Restart the app - and verify that you can log in these users without signing up.
Revise the homer entry in the yml file:
Member(m1):
firstname: homer
lastname: simpson
email: homer@simpson.com
password: secret
playlists:
- p1
- p2
Restart the app - and verify that when you log in homer has the contents of playlists p1 and p2
Archive of the project so far:
In the yml file, add playlist p3
to the marge user. Verify (by restarting) that this has worked.
Add some more songs, playlists and users to the yml file. Verify they are loaded (after an application restart)
If you look at the Signup or Login views in the app:
Notice there seems to be a missing image on the right. Try to ensure that some stock image appears here.
HINTL: Review Todolist 2, which has a solution to this issue.