├── project ├── build.properties └── plugins.sbt ├── public ├── images │ ├── favicon.png │ ├── external.png │ └── header-pattern.png ├── javascripts │ └── hello.js └── stylesheets │ └── main.css ├── conf ├── application.test.conf ├── logback.xml ├── routes ├── evolutions │ └── default │ │ ├── 1.sql │ │ └── 2.sql └── application.conf ├── test ├── UnitTest.java ├── BrowserTest.java ├── FunctionalTest.java ├── UserServiceTest.java ├── PostsPagerTest.java └── SecurityTest.java ├── app ├── services │ ├── CommentService.java │ ├── UserService.java │ ├── PostService.java │ └── impl │ │ ├── CommentServiceImpl.java │ │ ├── UserServiceImpl.java │ │ └── PostServiceImpl.java ├── annotations │ ├── Authenticated.java │ ├── NotAuthenticated.java │ └── PostExistsAndUserIsOwner.java ├── dto │ ├── LoginDTO.java │ ├── CommentDTO.java │ ├── PostDTO.java │ └── UserDTO.java ├── views │ ├── index.scala.html │ ├── newPost.scala.html │ ├── posts.scala.html │ ├── editPost.scala.html │ ├── blog.scala.html │ ├── post_form.scala.html │ ├── usersBlog.scala.html │ ├── comment.scala.html │ ├── post.scala.html │ ├── login.scala.html │ ├── main.scala.html │ ├── register.scala.html │ └── welcome.scala.html ├── models │ ├── Role.java │ ├── Comment.java │ ├── Post.java │ └── User.java ├── controllers │ ├── HomeController.java │ ├── CommentController.java │ ├── BlogController.java │ ├── UserController.java │ └── PostController.java ├── actions │ ├── NotAuthenticatedAction.java │ ├── AuthenticatedAction.java │ └── PostExistsAndUserIsOwnerAction.java ├── Module.java ├── filters │ └── LoggingFilter.java └── util │ └── PostsPager.java ├── scripts └── run_docker.sh ├── docker └── Dockerfile ├── .gitignore ├── README.md └── LICENSE /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.2 2 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reljicd/play-framework-blog/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/external.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reljicd/play-framework-blog/HEAD/public/images/external.png -------------------------------------------------------------------------------- /public/images/header-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reljicd/play-framework-blog/HEAD/public/images/header-pattern.png -------------------------------------------------------------------------------- /public/javascripts/hello.js: -------------------------------------------------------------------------------- 1 | if (window.console) { 2 | console.log("Welcome to your Play application's JavaScript!"); 3 | } 4 | -------------------------------------------------------------------------------- /conf/application.test.conf: -------------------------------------------------------------------------------- 1 | include "application.conf" 2 | 3 | db { 4 | default.driver=org.h2.Driver 5 | default.url="jdbc:h2:mem:play" 6 | } 7 | -------------------------------------------------------------------------------- /test/UnitTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Test; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | /** 6 | * Unit testing does not require Play application start up. 7 | *

8 | * https://www.playframework.com/documentation/latest/JavaTest 9 | */ 10 | public class UnitTest { 11 | 12 | @Test 13 | public void simpleCheck() { 14 | int a = 1 + 1; 15 | assertThat(a).isEqualTo(2); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/services/CommentService.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | 4 | import dto.CommentDTO; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Service class for {@link models.Comment} domain objects 11 | * 12 | * @author Dusan 13 | */ 14 | public interface CommentService { 15 | 16 | CommentDTO saveComment(CommentDTO commentDTO); 17 | 18 | Optional> findCommentsForPost(Long postId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/annotations/Authenticated.java: -------------------------------------------------------------------------------- 1 | package annotations; 2 | 3 | import actions.AuthenticatedAction; 4 | import play.mvc.With; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @With(AuthenticatedAction.class) 12 | @Target({ElementType.TYPE, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Authenticated { 15 | } 16 | -------------------------------------------------------------------------------- /app/annotations/NotAuthenticated.java: -------------------------------------------------------------------------------- 1 | package annotations; 2 | 3 | import actions.NotAuthenticatedAction; 4 | import play.mvc.With; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @With(NotAuthenticatedAction.class) 12 | @Target({ElementType.TYPE, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NotAuthenticated { 15 | } 16 | -------------------------------------------------------------------------------- /public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #0000FF; 3 | } 4 | 5 | h2 { 6 | color: #FF0000; 7 | } 8 | 9 | footer { 10 | margin-top: 60px; 11 | } 12 | 13 | .validation-message { 14 | font-style: normal; 15 | font-size: 10px; 16 | color: #FF1C19; 17 | } 18 | 19 | a { 20 | transition: all .2s; 21 | } 22 | 23 | a:hover, a:focus, a:active { 24 | color: purple; 25 | text-decoration: none; 26 | } 27 | 28 | .pagination { 29 | display: block; 30 | text-align: center; 31 | } -------------------------------------------------------------------------------- /app/annotations/PostExistsAndUserIsOwner.java: -------------------------------------------------------------------------------- 1 | package annotations; 2 | 3 | import actions.PostExistsAndUserIsOwnerAction; 4 | import play.mvc.With; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @With(PostExistsAndUserIsOwnerAction.class) 12 | @Target({ElementType.TYPE, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface PostExistsAndUserIsOwner { 15 | } 16 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // The Play plugin 2 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") 3 | 4 | // Ebean 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "4.1.0") 6 | // Play enhancer - this automatically generates getters/setters for public fields 7 | // and rewrites accessors of these fields to use the getters/setters. Remove this 8 | // plugin if you prefer not to have this feature, or disable on a per project 9 | // basis using disablePlugins(PlayEnhancer) in your build.sbt 10 | addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.2.2") -------------------------------------------------------------------------------- /app/dto/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package dto; 2 | 3 | import play.data.validation.Constraints; 4 | 5 | /** 6 | * @author Dusan 7 | */ 8 | public class LoginDTO { 9 | 10 | @Constraints.Required(message = "*Please provide your username") 11 | public String username; 12 | 13 | @Constraints.Required(message = "*Please provide your password") 14 | public String password; 15 | 16 | public LoginDTO() { 17 | } 18 | 19 | public LoginDTO(String username, String password) { 20 | this.username = username; 21 | this.password = password; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a String containing a 3 | * message to display. 4 | *@ 5 | @(message: String) 6 | 7 | @* 8 | * Call the `main` template with two arguments. The first 9 | * argument is a `String` with the title of the page, the second 10 | * argument is an `Html` object containing the body of the page. 11 | *@ 12 | @main { 13 | 14 | @* 15 | * Get an `Html` object by calling the built-in Play welcome 16 | * template and passing a `String` message. 17 | *@ 18 | @welcome(message, style = "java") 19 | 20 | } 21 | -------------------------------------------------------------------------------- /scripts/run_docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE_NAME=reljicd/play-blog 4 | echo -e "\nSet docker image name as ${IMAGE_NAME}\n" 5 | PORT=9000 6 | echo -e "Set docker image PORT to ${PORT}\n" 7 | 8 | echo -e "\nStop running Docker containers with image name ${IMAGE_NAME}...\n" 9 | docker stop $(docker ps -a | grep ${IMAGE_NAME} | awk '{print $1}') 10 | 11 | echo -e "\nDocker build image with name ${IMAGE_NAME}...\n" 12 | docker build -t ${IMAGE_NAME} -f docker/Dockerfile . 13 | 14 | echo -e "\nStart Docker container of the image ${IMAGE_NAME}...\n" 15 | docker run --rm -i -p ${PORT}:${PORT} ${IMAGE_NAME} -------------------------------------------------------------------------------- /app/views/newPost.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.PostDTO 5 | @import helper.CSRF 6 | 7 | @(postForm: Form[PostDTO]) 8 | 9 | @main { 10 |

11 |
12 |
13 | 14 | 15 | @helper.form(action = CSRF(routes.PostController.createPost()), 'class -> "form-horizontal") { 16 | @post_form(postForm) 17 | } 18 | 19 |
20 |
21 |
22 | } 23 | -------------------------------------------------------------------------------- /app/models/Role.java: -------------------------------------------------------------------------------- 1 | package models; 2 | 3 | import io.ebean.Finder; 4 | import io.ebean.Model; 5 | 6 | import javax.persistence.*; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Dusan 11 | */ 12 | @Entity 13 | public class Role extends Model { 14 | 15 | @Id 16 | public Long id; 17 | 18 | @Column(unique = true) 19 | public String role; 20 | 21 | @ManyToMany(cascade = CascadeType.ALL, mappedBy = "roles") 22 | public List users; 23 | 24 | public static final Finder find = new Finder<>(Role.class); 25 | 26 | public Role(String role) { 27 | this.role = role; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/services/UserService.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import dto.LoginDTO; 4 | import dto.UserDTO; 5 | import models.User; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * Service class for {@link models.User} domain objects 11 | * 12 | * @author Dusan 13 | */ 14 | public interface UserService { 15 | 16 | Optional findUserByUsername(String username); 17 | 18 | Optional findUserEntityByUsername(String username); 19 | 20 | Optional findUserByEmail(String email); 21 | 22 | Optional authenticateUser(LoginDTO loginDTO); 23 | 24 | Optional saveUser(UserDTO userDTO); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/dto/CommentDTO.java: -------------------------------------------------------------------------------- 1 | package dto; 2 | 3 | import play.data.validation.Constraints.Required; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author Dusan 9 | */ 10 | public class CommentDTO { 11 | 12 | @Required(message = "*Please write something") 13 | public String body; 14 | 15 | public String username; 16 | 17 | public Long postId; 18 | 19 | public Date createDate; 20 | 21 | public CommentDTO() { 22 | } 23 | 24 | public CommentDTO(String body, String username, Long postId, Date createDate) { 25 | this.body = body; 26 | this.username = username; 27 | this.postId = postId; 28 | this.createDate = createDate; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/views/posts.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import util.PostsPager 5 | 6 | @(postsPager: PostsPager) 7 | 8 | 9 |
10 | @for(postDTO <- postsPager.getList) { 11 |
12 |
13 |

@postDTO.title

14 |
Created @postDTO.createDate by @postDTO.username 15 |
16 |
17 |

@postDTO.body

18 |
19 | } 20 |
21 | -------------------------------------------------------------------------------- /app/services/PostService.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import dto.PostDTO; 4 | import models.Post; 5 | import util.PostsPager; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | /** 11 | * Service class for {@link models.Post} domain objects 12 | * 13 | * @author Dusan 14 | */ 15 | public interface PostService { 16 | 17 | PostsPager findNLatestPosts(int n, int page); 18 | 19 | Optional findNLatestPostsForUsername(int n, int page, String username); 20 | 21 | Optional getPost(Long postId); 22 | 23 | Optional getPostEntity(Long postId); 24 | 25 | PostDTO savePost(PostDTO postDTO); 26 | 27 | Optional editPost(PostDTO postDTO); 28 | 29 | void delete(Long postId); 30 | } 31 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | 3 | EXPOSE 9000 4 | 5 | ENV WORKING_DIR /app 6 | ENV SBT_VERSION 1.0.2 7 | 8 | RUN \ 9 | curl -L -o sbt-$SBT_VERSION.deb http://dl.bintray.com/sbt/debian/sbt-$SBT_VERSION.deb && \ 10 | dpkg -i sbt-$SBT_VERSION.deb && \ 11 | rm sbt-$SBT_VERSION.deb && \ 12 | apt-get update && \ 13 | apt-get install sbt && \ 14 | sbt sbtVersion 15 | 16 | WORKDIR $WORKING_DIR 17 | 18 | COPY app $WORKING_DIR/app 19 | COPY conf $WORKING_DIR/conf 20 | COPY public $WORKING_DIR/public 21 | COPY test $WORKING_DIR/test 22 | 23 | COPY project/build.properties $WORKING_DIR/project/build.properties 24 | COPY project/plugins.sbt $WORKING_DIR/project/plugins.sbt 25 | COPY build.sbt $WORKING_DIR/build.sbt 26 | 27 | ENTRYPOINT ["sbt"] 28 | CMD ["run"] -------------------------------------------------------------------------------- /app/views/editPost.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.PostDTO 5 | @import helper.CSRF 6 | 7 | @(postForm: Form[PostDTO], postId: Long) 8 | 9 | @main { 10 |
11 |
12 |
13 | 14 | 15 | @helper.form(action = CSRF(routes.PostController.editPost(postId)), 'class -> "form-horizontal") { 16 | @post_form(postForm) 17 | 18 | 19 | } 20 | 21 |
22 |
23 |
24 | } 25 | -------------------------------------------------------------------------------- /app/controllers/HomeController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import play.mvc.Controller; 4 | import play.mvc.Http; 5 | import play.mvc.Result; 6 | import views.html.index; 7 | 8 | /** 9 | * This controller contains an action to handle HTTP requests 10 | * to the application's home page. 11 | */ 12 | public class HomeController extends Controller { 13 | 14 | /** 15 | * An action that renders an HTML page with a welcome message. 16 | * The configuration in the routes file means that 17 | * this method will be called when the application receives a 18 | * GET request with a path of /. 19 | */ 20 | public Result index() { 21 | Http.Context ctx = ctx(); 22 | return ok(index.render("Your new application is ready.")); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/dto/PostDTO.java: -------------------------------------------------------------------------------- 1 | package dto; 2 | 3 | import play.data.validation.Constraints.MinLength; 4 | import play.data.validation.Constraints.Required; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * @author Dusan 10 | */ 11 | public class PostDTO { 12 | 13 | public Long id; 14 | 15 | @MinLength(value = 5, message = "*Your title must have at least 5 characters") 16 | @Required(message = "*Please provide title") 17 | public String title; 18 | 19 | @Required(message = "*Please provide body") 20 | public String body; 21 | 22 | public Date createDate; 23 | 24 | public String username; 25 | 26 | public PostDTO() { 27 | } 28 | 29 | public PostDTO(Long id, String title, String body, Date createDate, String username) { 30 | this.id = id; 31 | this.title = title; 32 | this.body = body; 33 | this.createDate = createDate; 34 | this.username = username; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/BrowserTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Test; 2 | import play.Application; 3 | import play.test.Helpers; 4 | import play.test.TestBrowser; 5 | import play.test.WithBrowser; 6 | 7 | import static org.junit.Assert.assertTrue; 8 | import static play.test.Helpers.*; 9 | 10 | public class BrowserTest extends WithBrowser { 11 | 12 | protected Application provideApplication() { 13 | return fakeApplication(inMemoryDatabase()); 14 | } 15 | 16 | protected TestBrowser provideBrowser(int port) { 17 | return Helpers.testBrowser(port); 18 | } 19 | 20 | /** 21 | * add your integration test here 22 | * in this example we just check if the welcome page is being shown 23 | */ 24 | @Test 25 | public void test() { 26 | browser.goTo("http://localhost:" + play.api.test.Helpers.testServerPort()); 27 | assertTrue(browser.pageSource().contains("Your new application is ready.")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/actions/NotAuthenticatedAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import annotations.NotAuthenticated; 4 | import play.mvc.Action; 5 | import play.mvc.Http; 6 | import play.mvc.Result; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.CompletionStage; 10 | 11 | /** 12 | * Action that checks if User is not authenticated 13 | * 14 | * @author Dusan 15 | */ 16 | public class NotAuthenticatedAction extends Action { 17 | 18 | public CompletionStage call(final Http.Context ctx) { 19 | String username = ctx.session().get("username"); 20 | if (username != null) { 21 | // User is authenticated, redirect him to home page 22 | Result login = redirect(controllers.routes.BlogController.usersBlog(username, 1)); 23 | return CompletableFuture.completedFuture(login); 24 | } else { 25 | // User is not authenticated, call delegate 26 | return delegate.call(ctx); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/models/Comment.java: -------------------------------------------------------------------------------- 1 | package models; 2 | 3 | import io.ebean.Finder; 4 | import io.ebean.Model; 5 | import io.ebean.annotation.CreatedTimestamp; 6 | import io.ebean.annotation.NotNull; 7 | 8 | import javax.persistence.*; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author Dusan 13 | */ 14 | @Entity 15 | public class Comment extends Model { 16 | 17 | @Id 18 | public Long id; 19 | 20 | @Column(columnDefinition = "TEXT") 21 | public String body; 22 | 23 | @NotNull 24 | @Temporal(TemporalType.TIMESTAMP) 25 | @CreatedTimestamp 26 | @Column(updatable = false) 27 | public Date createDate; 28 | 29 | @NotNull 30 | @ManyToOne 31 | public Post post; 32 | 33 | @NotNull 34 | @ManyToOne 35 | public User user; 36 | 37 | public static final Finder find = new Finder<>(Comment.class); 38 | 39 | public Comment(String body, Post post, User user) { 40 | this.body = body; 41 | this.post = post; 42 | this.user = user; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Module.java: -------------------------------------------------------------------------------- 1 | import com.google.inject.AbstractModule; 2 | import services.CommentService; 3 | import services.PostService; 4 | import services.UserService; 5 | import services.impl.CommentServiceImpl; 6 | import services.impl.PostServiceImpl; 7 | import services.impl.UserServiceImpl; 8 | 9 | /** 10 | * This class is a Guice module that tells Guice how to bind several 11 | * different types. This Guice module is created when the Play 12 | * application starts. 13 | *

14 | * Play will automatically use any class called `Module` that is in 15 | * the root package. You can create modules in other locations by 16 | * adding `play.modules.enabled` settings to the `application.conf` 17 | * configuration file. 18 | */ 19 | public class Module extends AbstractModule { 20 | 21 | @Override 22 | public void configure() { 23 | bind(CommentService.class).to(CommentServiceImpl.class); 24 | bind(PostService.class).to(PostServiceImpl.class); 25 | bind(UserService.class).to(UserServiceImpl.class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/FunctionalTest.java: -------------------------------------------------------------------------------- 1 | import models.Role; 2 | import org.junit.Ignore; 3 | import org.junit.Test; 4 | import play.test.WithApplication; 5 | import play.twirl.api.Content; 6 | import services.CommentService; 7 | 8 | import javax.inject.Inject; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * A functional test starts a Play application for every test. 14 | *

15 | * https://www.playframework.com/documentation/latest/JavaFunctionalTest 16 | */ 17 | public class FunctionalTest extends WithApplication { 18 | 19 | @Test 20 | @Ignore 21 | public void renderTemplate() { 22 | // If you are calling out to Assets, then you must instantiate an application 23 | // because it makes use of assets metadata that is configured from 24 | // the application. 25 | 26 | Content html = views.html.index.render("Your new application is ready."); 27 | assertThat("text/html").isEqualTo(html.contentType()); 28 | assertThat(html.body()).contains("Your new application is ready."); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/views/blog.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import util.PostsPager 5 | 6 | @(postsPager: PostsPager) 7 | 8 | @main { 9 | 10 | @posts(postsPager) 11 | 12 | 13 |

32 | } 33 | -------------------------------------------------------------------------------- /app/models/Post.java: -------------------------------------------------------------------------------- 1 | package models; 2 | 3 | import io.ebean.Finder; 4 | import io.ebean.Model; 5 | import io.ebean.annotation.CreatedTimestamp; 6 | import io.ebean.annotation.NotNull; 7 | 8 | import javax.persistence.*; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | /** 13 | * @author Dusan 14 | */ 15 | @Entity 16 | public class Post extends Model { 17 | 18 | @Id 19 | public Long id; 20 | 21 | @NotNull 22 | public String title; 23 | 24 | @Column(columnDefinition = "TEXT") 25 | public String body; 26 | 27 | @NotNull 28 | @Temporal(TemporalType.TIMESTAMP) 29 | @CreatedTimestamp 30 | @Column(updatable = false) 31 | public Date createDate; 32 | 33 | @NotNull 34 | @ManyToOne 35 | public User user; 36 | 37 | @OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE) 38 | public List comments; 39 | 40 | public static final Finder find = new Finder<>(Post.class); 41 | 42 | public Post(String title, String body, User user) { 43 | this.title = title; 44 | this.body = body; 45 | this.user = user; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /test/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | import dto.LoginDTO; 2 | import dto.UserDTO; 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import play.test.WithApplication; 6 | import services.UserService; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | public class UserServiceTest extends WithApplication { 11 | 12 | private static final String USERNAME = "username"; 13 | private static final String PASSWORD = "password"; 14 | private static final String EMAIL = "testuser@testuser.com"; 15 | 16 | private UserService userService; 17 | 18 | @Before 19 | public void setUp() { 20 | userService = app.injector().instanceOf(UserService.class); 21 | } 22 | 23 | @Test 24 | public void saveUserInDatabaseAndThenAuthenticateUser() { 25 | UserDTO userDTO = new UserDTO(USERNAME, PASSWORD, EMAIL, "firstName", "lastName"); 26 | LoginDTO loginDTO = new LoginDTO(USERNAME, PASSWORD); 27 | 28 | userService.saveUser(userDTO); 29 | 30 | assertThat(userService.findUserByUsername(USERNAME).isPresent()).isTrue(); 31 | assertThat(userService.findUserByEmail(EMAIL).isPresent()).isTrue(); 32 | assertThat(userService.authenticateUser(loginDTO).isPresent()).isTrue(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/models/User.java: -------------------------------------------------------------------------------- 1 | package models; 2 | 3 | import io.ebean.Finder; 4 | import io.ebean.Model; 5 | import io.ebean.annotation.NotNull; 6 | 7 | import javax.persistence.*; 8 | import java.util.List; 9 | 10 | /** 11 | * @author Dusan 12 | */ 13 | @Entity 14 | public class User extends Model { 15 | 16 | @Id 17 | public Long id; 18 | 19 | @NotNull 20 | @Column(unique = true) 21 | public String username; 22 | 23 | @NotNull 24 | public String password; 25 | 26 | @NotNull 27 | @Column(unique = true) 28 | public String email; 29 | 30 | public String firstName; 31 | 32 | public String lastName; 33 | 34 | @NotNull 35 | public int active; 36 | 37 | @ManyToMany(cascade = CascadeType.ALL) 38 | public List roles; 39 | 40 | @OneToMany(mappedBy = "user") 41 | public List posts; 42 | 43 | public static final Finder find = new Finder<>(User.class); 44 | 45 | public User(String username, String password, String email, String firstName, String lastName, int active) { 46 | this.username = username; 47 | this.password = password; 48 | this.email = email; 49 | this.firstName = firstName; 50 | this.lastName = lastName; 51 | this.active = active; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package dto; 2 | 3 | import play.data.validation.Constraints.Email; 4 | import play.data.validation.Constraints.MinLength; 5 | import play.data.validation.Constraints.Required; 6 | 7 | /** 8 | * @author Dusan 9 | */ 10 | public class UserDTO { 11 | 12 | @MinLength(value = 5, message = "*Your username must have at least 5 characters") 13 | @Required(message = "*Please provide your username") 14 | public String username; 15 | 16 | @MinLength(value = 5, message = "*Your password must have at least 5 characters") 17 | @Required(message = "*Please provide your password") 18 | public String password; 19 | 20 | @Email(message = "*Please provide a valid Email") 21 | @Required(message = "*Please provide an email") 22 | public String email; 23 | 24 | @Required(message = "*Please provide your name") 25 | public String firstName; 26 | 27 | @Required(message = "*Please provide your last name") 28 | public String lastName; 29 | 30 | public UserDTO() { 31 | } 32 | 33 | public UserDTO(String username, String password, String email, String firstName, String lastName) { 34 | this.username = username; 35 | this.password = password; 36 | this.email = email; 37 | this.firstName = firstName; 38 | this.lastName = lastName; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/views/post_form.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.PostDTO 5 | 6 | @(postForm: Form[PostDTO]) 7 | 8 |
9 |
10 | 11 | 12 | @for(error <- postForm("title").errors) { 13 |

@error.format(messages())

14 | } 15 | 16 | 17 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | @for(error <- postForm("body").errors) { 31 |

@error.format(messages())

32 | } 33 | 34 | 35 | 38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 | 46 | 47 | 48 |
49 |
50 | -------------------------------------------------------------------------------- /app/actions/AuthenticatedAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import annotations.Authenticated; 4 | import dto.LoginDTO; 5 | import play.data.Form; 6 | import play.data.FormFactory; 7 | import play.mvc.Action; 8 | import play.mvc.Http; 9 | import play.mvc.Result; 10 | import play.mvc.Security; 11 | 12 | import javax.inject.Inject; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.concurrent.CompletionStage; 15 | 16 | /** 17 | * Action that checks if User is authenticated 18 | * 19 | * @author Dusan 20 | */ 21 | public class AuthenticatedAction extends Action { 22 | 23 | private final Form loginDTOForm; 24 | 25 | @Inject 26 | public AuthenticatedAction(FormFactory formFactory) { 27 | this.loginDTOForm = formFactory.form(LoginDTO.class); 28 | } 29 | 30 | public CompletionStage call(final Http.Context ctx) { 31 | String username = ctx.session().get("username"); 32 | if (username == null) { 33 | // User is not authenticated, show him Login form 34 | Result login = unauthorized(views.html.login.render(loginDTOForm.withGlobalError("Please login to see this page."))); 35 | return CompletableFuture.completedFuture(login); 36 | } else { 37 | // User is authenticated, call delegate 38 | return delegate.call(ctx); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/views/usersBlog.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.UserDTO 5 | @import util.PostsPager 6 | 7 | @(userDTO: UserDTO, postsPager: PostsPager) 8 | 9 | @main { 10 |
11 |

@userDTO.firstName @userDTO.lastName - Blog

12 |
13 | 14 | @posts(postsPager) 15 | 16 | 17 | 36 | } 37 | -------------------------------------------------------------------------------- /app/filters/LoggingFilter.java: -------------------------------------------------------------------------------- 1 | package filters; 2 | 3 | import akka.stream.Materializer; 4 | import play.Logger; 5 | import play.mvc.Filter; 6 | import play.mvc.Http; 7 | import play.mvc.Result; 8 | 9 | import javax.inject.Inject; 10 | import java.util.concurrent.CompletionStage; 11 | import java.util.function.Function; 12 | 13 | /** 14 | * Logging Filter. Intercepts all HTTP requests, and prints useful request info to Logger. 15 | * 16 | * @author Dusan 17 | */ 18 | public class LoggingFilter extends Filter { 19 | 20 | @Inject 21 | public LoggingFilter(Materializer mat) { 22 | super(mat); 23 | } 24 | 25 | @Override 26 | public CompletionStage apply( 27 | Function> nextFilter, 28 | Http.RequestHeader requestHeader) { 29 | long startTime = System.currentTimeMillis(); 30 | return nextFilter.apply(requestHeader).thenApply(result -> { 31 | long endTime = System.currentTimeMillis(); 32 | long requestTime = endTime - startTime; 33 | 34 | if (!requestHeader.uri().contains("assets")) { 35 | Logger.info("{} {} from {} took {}ms and returned {}", 36 | requestHeader.method(), requestHeader.uri(), requestHeader.remoteAddress(), requestTime, result.status()); 37 | } 38 | 39 | return result; 40 | }); 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${application.home:-.}/logs/application.log 8 | 9 | %date [%level] from %logger in %thread - %message%n%xException 10 | 11 | 12 | 13 | 14 | 15 | %coloredLevel %logger{15} - %message%n%xException{10} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/views/comment.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.CommentDTO 5 | @import helper.CSRF 6 | 7 | @(commentForm: Form[CommentDTO], postId: Long) 8 | 9 | @main { 10 |
11 |
12 |
13 | 14 | 15 | @helper.form(action = CSRF(routes.CommentController.createComment(postId)), 'class -> "form-horizontal") { 16 | 17 |
18 |
19 | 20 | 21 | @for(error <- commentForm("body").errors) { 22 |

@error.format(messages())

23 | } 24 | 25 | 26 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | } 43 | 44 |
45 |
46 |
47 | } 48 | -------------------------------------------------------------------------------- /app/util/PostsPager.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import dto.PostDTO; 4 | import io.ebean.PagedList; 5 | import models.Post; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Util class for Posts pagination. 12 | * Keeps reference to {@link PagedList} of Posts, 13 | * delegates some calls to it, 14 | * and has util methods that facilitate implementation of pagination on templates. 15 | * 16 | * @author Dusan 17 | */ 18 | public class PostsPager { 19 | 20 | private final PagedList postPagedList; 21 | 22 | public PostsPager(PagedList postPagedList) { 23 | this.postPagedList = postPagedList; 24 | } 25 | 26 | public List getList() { 27 | return postPagedList.getList() 28 | .stream() 29 | .map(this::convertToDTO) 30 | .collect(Collectors.toList()); 31 | } 32 | 33 | public int getPageIndex() { 34 | return postPagedList.getPageIndex() + 1; 35 | } 36 | 37 | public int getPageSize() { 38 | return postPagedList.getPageSize(); 39 | } 40 | 41 | public boolean hasNext() { 42 | return getPageIndex() >= 1 && getPageIndex() < getTotalPageCount(); 43 | } 44 | 45 | public boolean hasPrev() { 46 | return getPageIndex() > 1 && getPageIndex() <= getTotalPageCount(); 47 | } 48 | 49 | public int getTotalPageCount() { 50 | return postPagedList.getTotalPageCount(); 51 | } 52 | 53 | public int getTotalCount() { 54 | return postPagedList.getTotalCount(); 55 | } 56 | 57 | public boolean indexOutOfBounds() { 58 | return getPageIndex() < 0 || getPageIndex() > getTotalCount(); 59 | } 60 | 61 | private PostDTO convertToDTO(Post post) { 62 | return new PostDTO(post.id, post.title, post.body, post.createDate, post.user.username); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/services/impl/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package services.impl; 2 | 3 | import dto.CommentDTO; 4 | import models.Comment; 5 | import models.Post; 6 | import models.User; 7 | import services.CommentService; 8 | import services.PostService; 9 | import services.UserService; 10 | 11 | import javax.inject.Inject; 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | import static java.util.stream.Collectors.toList; 16 | 17 | /** 18 | * @author Dusan 19 | */ 20 | public class CommentServiceImpl implements CommentService { 21 | 22 | private final UserService userService; 23 | private final PostService postService; 24 | 25 | @Inject 26 | public CommentServiceImpl(UserService userService, PostService postService) { 27 | this.userService = userService; 28 | this.postService = postService; 29 | } 30 | 31 | @Override 32 | public CommentDTO saveComment(CommentDTO commentDTO) { 33 | Comment comment = convertToEntity(commentDTO); 34 | comment.save(); 35 | return convertToDTO(comment); 36 | } 37 | 38 | @Override 39 | public Optional> findCommentsForPost(Long postId) { 40 | return Optional.ofNullable(Post.find.byId(postId)) 41 | .map(post -> post.comments 42 | .stream() 43 | .map(this::convertToDTO) 44 | .collect(toList())); 45 | } 46 | 47 | private CommentDTO convertToDTO(Comment comment) { 48 | return new CommentDTO(comment.body, comment.user.username, comment.post.id, comment.createDate); 49 | } 50 | 51 | private Comment convertToEntity(CommentDTO commentDTO) { 52 | User user = userService.findUserEntityByUsername(commentDTO.username).orElse(null); 53 | Post post = postService.getPostEntity(commentDTO.postId).orElse(null); 54 | return new Comment(commentDTO.body, post, user); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/controllers/CommentController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import annotations.Authenticated; 4 | import dto.CommentDTO; 5 | import play.data.Form; 6 | import play.data.FormFactory; 7 | import play.mvc.Controller; 8 | import play.mvc.Result; 9 | import services.CommentService; 10 | import views.html.comment; 11 | 12 | import javax.inject.Inject; 13 | 14 | /** 15 | * Controller with actions related to Comments. 16 | * 17 | * @author Dusan 18 | */ 19 | public class CommentController extends Controller { 20 | 21 | private final CommentService commentService; 22 | private final Form commentForm; 23 | 24 | @Inject 25 | public CommentController(CommentService commentService, FormFactory formFactory) { 26 | this.commentService = commentService; 27 | this.commentForm = formFactory.form(CommentDTO.class); 28 | } 29 | 30 | /** 31 | * GET new Comment form. 32 | * Only authenticated users can get this form. 33 | */ 34 | @Authenticated 35 | public Result getCommentForm(Long postId) { 36 | CommentDTO commentDTO = new CommentDTO(); 37 | commentDTO.postId = postId; 38 | return ok(comment.render(commentForm.fill(commentDTO), postId)); 39 | } 40 | 41 | /** 42 | * POST new Comment. 43 | * Only authenticated users can post this form. 44 | */ 45 | @Authenticated 46 | public Result createComment(Long postId) { 47 | Form commentForm = this.commentForm.bindFromRequest(); 48 | if (commentForm.hasErrors()) { 49 | return badRequest(comment.render(commentForm, postId)); 50 | } else { 51 | CommentDTO commentDTO = commentForm.get(); 52 | commentDTO.username = session("username"); 53 | commentDTO.postId = postId; 54 | commentService.saveComment(commentDTO); 55 | return redirect(routes.PostController.getPost(postId)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | 27 | # Mongo Explorer plugin: 28 | .idea/**/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | .idea 37 | out/ 38 | *iml 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | fabric.properties 54 | ### PlayFramework template 55 | # Ignore Play! working directory # 56 | bin/ 57 | /db 58 | .eclipse 59 | /lib/ 60 | /logs/ 61 | /modules 62 | /project/project 63 | /project/target 64 | /target 65 | tmp/ 66 | test-result 67 | server.pid 68 | *.eml 69 | /dist/ 70 | .cache 71 | ### SBT template 72 | # Simple Build Tool 73 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 74 | 75 | dist/* 76 | target/ 77 | lib_managed/ 78 | src_managed/ 79 | project/boot/ 80 | project/plugins/project/ 81 | .history 82 | .cache 83 | .lib/ 84 | ### Scala template 85 | *.class 86 | *.log 87 | 88 | # H2 databases 89 | *.lock.db 90 | *.mv.db 91 | *.trace.db 92 | 93 | ## Project files 94 | play-blog/ -------------------------------------------------------------------------------- /app/controllers/BlogController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import play.mvc.Controller; 4 | import play.mvc.Result; 5 | import play.mvc.Results; 6 | import services.PostService; 7 | import services.UserService; 8 | import views.html.blog; 9 | import views.html.usersBlog; 10 | 11 | import javax.inject.Inject; 12 | 13 | /** 14 | * Blog Controller, with actions that return blog for username, and home page (list of posts of all users) 15 | * 16 | * @author Dusan 17 | */ 18 | public class BlogController extends Controller { 19 | 20 | private static final int N_OF_LATEST_POSTS = 5; 21 | private final PostService postService; 22 | private final UserService userService; 23 | 24 | @Inject 25 | public BlogController(PostService postService, UserService userService) { 26 | this.postService = postService; 27 | this.userService = userService; 28 | } 29 | 30 | /** 31 | * Home page, list of all Posts by all users, ordered by create date, paginated. 32 | * 33 | * @param page - page index 34 | * @return blog template 35 | */ 36 | public Result blog(int page) { 37 | return ok(blog.render(postService.findNLatestPosts(N_OF_LATEST_POSTS, page))); 38 | } 39 | 40 | /** 41 | * Blog of User with username, list of all Posts by that user, ordered by date of creation, paginated. 42 | * 43 | * @param username - username of User 44 | * @param page - page index 45 | * @return usersBlog template, or notFound if User with username doesn't exists 46 | */ 47 | public Result usersBlog(String username, int page) { 48 | return userService.findUserByUsername(username) 49 | .map(userDTO -> 50 | postService.findNLatestPostsForUsername(N_OF_LATEST_POSTS, page, username) 51 | .map(postDTOs -> ok(usersBlog.render(userDTO, postDTOs))) 52 | .orElseGet(Results::notFound)) 53 | .orElseGet(Results::notFound); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/services/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package services.impl; 2 | 3 | import dto.LoginDTO; 4 | import dto.UserDTO; 5 | import models.User; 6 | import org.mindrot.jbcrypt.BCrypt; 7 | import play.Logger; 8 | import services.UserService; 9 | 10 | import java.util.Optional; 11 | 12 | /** 13 | * @author Dusan 14 | */ 15 | public class UserServiceImpl implements UserService { 16 | 17 | private final Logger.ALogger logger = Logger.of(this.getClass()); 18 | 19 | @Override 20 | public Optional findUserByUsername(String username) { 21 | return findUserEntityByUsername(username) 22 | .map(this::convertToDTO); 23 | } 24 | 25 | @Override 26 | public Optional findUserEntityByUsername(String username) { 27 | return User.find.query().where().eq("username", username).findOneOrEmpty(); 28 | } 29 | 30 | @Override 31 | public Optional findUserByEmail(String email) { 32 | return User.find.query().where().eq("email", email).findOneOrEmpty() 33 | .map(this::convertToDTO); 34 | } 35 | 36 | @Override 37 | public Optional authenticateUser(LoginDTO loginDTO) { 38 | return User.find.query().where().eq("username", loginDTO.username).findOneOrEmpty() 39 | .filter(user -> BCrypt.checkpw(loginDTO.password, user.password)) 40 | .map(this::convertToDTO); 41 | } 42 | 43 | @Override 44 | public Optional saveUser(UserDTO userDTO) { 45 | User user = convertToEntity(userDTO); 46 | try { 47 | user.save(); 48 | } catch (Exception e) { 49 | logger.error("Exception thrown during saveUser:", e); 50 | return Optional.empty(); 51 | } 52 | return Optional.of(convertToDTO(user)); 53 | } 54 | 55 | private UserDTO convertToDTO(User user) { 56 | return new UserDTO(user.username, null, user.email, user.firstName, user.lastName); 57 | } 58 | 59 | private User convertToEntity(UserDTO userDTO) { 60 | return new User(userDTO.username, BCrypt.hashpw(userDTO.password, BCrypt.gensalt()), userDTO.email, userDTO.firstName, userDTO.lastName, 1); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/actions/PostExistsAndUserIsOwnerAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import annotations.PostExistsAndUserIsOwner; 4 | import dto.LoginDTO; 5 | import dto.PostDTO; 6 | import play.data.Form; 7 | import play.data.FormFactory; 8 | import play.mvc.Action; 9 | import play.mvc.Http; 10 | import play.mvc.Result; 11 | import services.PostService; 12 | 13 | import javax.inject.Inject; 14 | import java.util.Optional; 15 | import java.util.concurrent.CompletableFuture; 16 | import java.util.concurrent.CompletionStage; 17 | 18 | /** 19 | * Util Action that checks if Post exists and if logged in User is the owner of Post. 20 | *

21 | * Returns notFound if Post doesn't exists or unauthorized if User is not the owner of Post. 22 | * 23 | * @author Dusan 24 | */ 25 | public class PostExistsAndUserIsOwnerAction extends Action { 26 | 27 | private final PostService postService; 28 | private final Form loginDTOForm; 29 | 30 | @Inject 31 | public PostExistsAndUserIsOwnerAction(PostService postService, FormFactory formFactory) { 32 | this.postService = postService; 33 | this.loginDTOForm = formFactory.form(LoginDTO.class); 34 | } 35 | 36 | public CompletionStage call(final Http.Context ctx) { 37 | String username = ctx.session().get("username"); 38 | Long postId = Long.parseLong(ctx.request().getQueryString("id")); 39 | Optional optionalPost = postService.getPost(postId); 40 | if (!optionalPost.isPresent()) { 41 | // Post doesn't exists, return notFound 42 | return CompletableFuture.completedFuture(notFound()); 43 | } else if (!optionalPost.get().username.equals(username)) { 44 | // User is not the owner of Post, show him Login form 45 | Result login = unauthorized(views.html.login.render( 46 | loginDTOForm.withGlobalError("Please login with proper credentials to modify this post"))); 47 | return CompletableFuture.completedFuture(login); 48 | } else { 49 | // Post exists and User is the owner of Post, call delegate 50 | return delegate.call(ctx); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/views/post.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import java.util.List 5 | @import dto.{CommentDTO, PostDTO} 6 | 7 | @(postDTO: PostDTO, commentDTOs: List[CommentDTO]) 8 | 9 | @main { 10 | 11 |

12 | 13 | 14 |

@postDTO.title

15 | 16 |
Created @postDTO.createDate by 17 | @postDTO.username 18 |
19 | 20 | 21 |

@postDTO.body

22 | 23 | 24 | @if(session.get("username") == postDTO.username) { 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | } 39 | 40 |
41 | 42 |

Comments:

43 | 44 | 45 | @for(commentDTO <- commentDTOs) { 46 |
47 | 48 |
Created @commentDTO.createDate by 49 | @commentDTO.username 50 |
51 | 52 | 53 |

@commentDTO.body

54 | 55 |
56 | } 57 | 58 | 59 | @if(session.get("username") != null) { 60 | 61 | 62 | 63 | 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # An example controller showing a sample home page 6 | GET / controllers.HomeController.index 7 | 8 | # Map static resources from the /public folder to the /assets URL path 9 | GET /assets/*file controllers.Assets.at(path="/public", file) 10 | 11 | # Home page of the application, showing blog posts from all users in reversed order of the date of publication 12 | GET /blog controllers.BlogController.blog(page: Int ?= 1) 13 | # Blog page of user :username, showing blog posts from this users in reversed order of the date of publication 14 | GET /blog/:username controllers.BlogController.usersBlog(username, page: Int ?= 1) 15 | 16 | # Present "login" page 17 | GET /login controllers.UserController.getLoginForm 18 | # Submit "login" form 19 | POST /login controllers.UserController.login 20 | # Logout user 21 | GET /logout controllers.UserController.logout 22 | # Present "registration" page 23 | GET /register controllers.UserController.getRegistrationForm 24 | # Create new user 25 | POST /register controllers.UserController.register 26 | 27 | # Present "create a post" page 28 | GET /post/new controllers.PostController.getNewPostForm 29 | # Blog post page for id of :id 30 | GET /post controllers.PostController.getPost(id: Long) 31 | # Create new post 32 | POST /post controllers.PostController.createPost 33 | # Present "edit a post" page 34 | GET /post/edit controllers.PostController.getEditPostForm(id: Long) 35 | # Update post with id of :id 36 | POST /post/edit controllers.PostController.editPost(id: Long) 37 | # Deletes post with id of :id 38 | GET /post/delete controllers.PostController.deletePost(id: Long) 39 | 40 | # Present "leave a comment" page 41 | GET /comment controllers.CommentController.getCommentForm(postId: Long) 42 | # Create new comment 43 | POST /comment controllers.CommentController.createComment(postId: Long) -------------------------------------------------------------------------------- /app/services/impl/PostServiceImpl.java: -------------------------------------------------------------------------------- 1 | package services.impl; 2 | 3 | import dto.PostDTO; 4 | import models.Post; 5 | import models.User; 6 | import services.PostService; 7 | import services.UserService; 8 | import util.PostsPager; 9 | 10 | import javax.inject.Inject; 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author Dusan 15 | */ 16 | public class PostServiceImpl implements PostService { 17 | 18 | private final UserService userService; 19 | 20 | @Inject 21 | public PostServiceImpl(UserService userService) { 22 | this.userService = userService; 23 | } 24 | 25 | @Override 26 | public PostsPager findNLatestPosts(int n, int page) { 27 | return new PostsPager(Post.find.query().orderBy("create_date desc").setFirstRow(n * (page - 1)).setMaxRows(n).findPagedList()); 28 | } 29 | 30 | @Override 31 | public Optional findNLatestPostsForUsername(int n, int page, String username) { 32 | return User.find.query().where().eq("username", username).findOneOrEmpty() 33 | .map(user -> new PostsPager( 34 | Post.find.query() 35 | .where().eq("user_id", user.id) 36 | .orderBy("create_date desc") 37 | .setFirstRow(n * (page - 1)) 38 | .setMaxRows(n) 39 | .findPagedList())); 40 | } 41 | 42 | @Override 43 | public Optional getPost(Long postId) { 44 | return getPostEntity(postId) 45 | .map(this::convertToDTO); 46 | } 47 | 48 | @Override 49 | public Optional getPostEntity(Long postId) { 50 | return Optional.ofNullable(Post.find.byId(postId)); 51 | } 52 | 53 | @Override 54 | public PostDTO savePost(PostDTO postDTO) { 55 | Post post = convertToEntity(postDTO); 56 | post.save(); 57 | return convertToDTO(post); 58 | } 59 | 60 | @Override 61 | public Optional editPost(PostDTO postDTO) { 62 | return getPostEntity(postDTO.id) 63 | .map(post -> { 64 | post.body = postDTO.body; 65 | post.title = postDTO.title; 66 | post.save(); 67 | return convertToDTO(post); 68 | }); 69 | } 70 | 71 | @Override 72 | public void delete(Long postId) { 73 | Post.find.deleteById(postId); 74 | } 75 | 76 | private PostDTO convertToDTO(Post post) { 77 | return new PostDTO(post.id, post.title, post.body, post.createDate, post.user.username); 78 | } 79 | 80 | private Post convertToEntity(PostDTO postDTO) { 81 | User user = userService.findUserEntityByUsername(postDTO.username).orElse(null); 82 | return new Post(postDTO.title, postDTO.body, user); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /app/views/login.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.LoginDTO 5 | @import helper.CSRF 6 | 7 | @(loginForm: Form[LoginDTO]) 8 | 9 | @main { 10 |
11 |
12 |
13 | 14 | 15 |
16 | 17 | @if(loginForm.hasGlobalErrors) { 18 | @for(error <- loginForm.globalErrors) { 19 |
@error.format(messages())
20 | } 21 | } 22 | 23 |
24 | 25 | 26 | @helper.form(action = CSRF(routes.UserController.login()), 'class -> "form-horizontal") { 27 | 28 |
29 |
30 | 31 | 32 | @for(error <- loginForm("username").errors) { 33 |

@error.format(messages())

34 | } 35 | 36 | 37 | 42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 | 50 | @for(error <- loginForm("password").errors) { 51 |

@error.format(messages())

52 | } 53 | 54 | 55 | 60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 68 | 69 |
70 |
71 | } 72 | 73 |
74 |
75 | 76 |
77 | } 78 | -------------------------------------------------------------------------------- /app/controllers/UserController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import annotations.Authenticated; 4 | import annotations.NotAuthenticated; 5 | import dto.LoginDTO; 6 | import dto.UserDTO; 7 | import play.data.Form; 8 | import play.data.FormFactory; 9 | import play.mvc.Controller; 10 | import play.mvc.Result; 11 | import services.UserService; 12 | import views.html.login; 13 | import views.html.register; 14 | 15 | import javax.inject.Inject; 16 | 17 | /** 18 | * Controller with actions related to Users, including login and register actions. 19 | * 20 | * @author Dusan 21 | */ 22 | public class UserController extends Controller { 23 | 24 | private final UserService userService; 25 | private final Form loginForm; 26 | private final Form registrationForm; 27 | 28 | @Inject 29 | public UserController(UserService userService, FormFactory formFactory) { 30 | this.userService = userService; 31 | this.loginForm = formFactory.form(LoginDTO.class); 32 | this.registrationForm = formFactory.form(UserDTO.class); 33 | } 34 | 35 | /** 36 | * GET login form. 37 | */ 38 | public Result getLoginForm() { 39 | return ok(login.render(loginForm)); 40 | } 41 | 42 | /** 43 | * POST login form. 44 | */ 45 | public Result login() { 46 | Form loginDTOForm = loginForm.bindFromRequest(); 47 | if (loginDTOForm.hasErrors()) { 48 | return badRequest(login.render(loginDTOForm)); 49 | } else { 50 | return userService.authenticateUser(loginDTOForm.get()) 51 | .map(loginDTO -> { 52 | session("username", loginDTO.username); 53 | return redirect(routes.BlogController.blog(1)); 54 | }) 55 | .orElse(badRequest(login.render(loginDTOForm.withGlobalError( 56 | "Your username and password didn't match. Please try again.")))); 57 | } 58 | } 59 | 60 | /** 61 | * Logout action. 62 | * Only authenticated users can logout. 63 | */ 64 | @Authenticated 65 | public Result logout() { 66 | session().clear(); 67 | return redirect(routes.BlogController.blog(1)); 68 | } 69 | 70 | /** 71 | * GET registration Form. 72 | * Only unauthenticated users can see this page. 73 | */ 74 | @NotAuthenticated 75 | public Result getRegistrationForm() { 76 | return ok(register.render(registrationForm)); 77 | } 78 | 79 | /** 80 | * POST registration Form. 81 | * Only unauthenticated users can use this. 82 | */ 83 | @NotAuthenticated 84 | public Result register() { 85 | Form registrationForm = this.registrationForm.bindFromRequest(); 86 | if (registrationForm.hasErrors()) { 87 | return badRequest(register.render(registrationForm)); 88 | } else { 89 | return userService.saveUser(registrationForm.get()) 90 | .map(userDTO -> redirect(routes.UserController.login())) 91 | .orElse(badRequest(register.render(registrationForm.withGlobalError( 92 | "Username with those credentials already exists. Please try again")))); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/PostsPagerTest.java: -------------------------------------------------------------------------------- 1 | import dto.PostDTO; 2 | import dto.UserDTO; 3 | import models.Post; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import play.test.WithApplication; 7 | import services.PostService; 8 | import services.UserService; 9 | import util.PostsPager; 10 | 11 | import java.util.stream.IntStream; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | public class PostsPagerTest extends WithApplication { 16 | 17 | private static final String USERNAME = "username"; 18 | private static final String PASSWORD = "password"; 19 | private static final String EMAIL = "testuser@testuser.com"; 20 | 21 | private static final int NUMBERS_OF_POSTS_PER_PAGE = 10; 22 | private static final int NUMBER_OF_DUMMY_POSTS = 20; 23 | 24 | private PostService postService; 25 | 26 | @Before 27 | public void setUp() { 28 | postService = app.injector().instanceOf(PostService.class); 29 | UserService userService = app.injector().instanceOf(UserService.class); 30 | 31 | UserDTO userDTO = new UserDTO(USERNAME, PASSWORD, EMAIL, "firstName", "lastName"); 32 | userService.saveUser(userDTO); 33 | 34 | // Populate DB with some dummy posts 35 | IntStream.rangeClosed(1, NUMBER_OF_DUMMY_POSTS) 36 | .forEach(i -> postService.savePost(new PostDTO(null, "Title" + i, "body", null, USERNAME))); 37 | } 38 | 39 | @Test 40 | public void testGeneralFunctionsOfPostsPager() { 41 | int numberOfPosts = Post.find.all().size(); 42 | int numberOfPages = numberOfPosts / NUMBERS_OF_POSTS_PER_PAGE + 1; 43 | int pageIndex = 1; 44 | PostsPager postsPager = postService.findNLatestPosts(NUMBERS_OF_POSTS_PER_PAGE, pageIndex); 45 | assertThat(postsPager.getPageSize()).isEqualTo(NUMBERS_OF_POSTS_PER_PAGE); 46 | assertThat(postsPager.getPageIndex()).isEqualTo(pageIndex); 47 | assertThat(postsPager.getTotalPageCount()).isEqualTo(numberOfPages); 48 | assertThat(postsPager.getTotalCount()).isEqualTo(numberOfPosts); 49 | } 50 | 51 | @Test 52 | public void testPostsPagerFirstPage() { 53 | int pageIndex = 1; 54 | PostsPager postsPager = postService.findNLatestPosts(NUMBERS_OF_POSTS_PER_PAGE, pageIndex); 55 | assertThat(postsPager.hasPrev()).isFalse(); 56 | assertThat(postsPager.hasNext()).isTrue(); 57 | assertThat(postsPager.indexOutOfBounds()).isFalse(); 58 | } 59 | 60 | @Test 61 | public void testPostsPagerLastPage() { 62 | int pageIndex = 1; 63 | PostsPager postsPager = postService.findNLatestPosts(NUMBERS_OF_POSTS_PER_PAGE, pageIndex); 64 | postsPager = postService.findNLatestPosts(NUMBERS_OF_POSTS_PER_PAGE, postsPager.getTotalPageCount()); 65 | assertThat(postsPager.hasPrev()).isTrue(); 66 | assertThat(postsPager.hasNext()).isFalse(); 67 | assertThat(postsPager.indexOutOfBounds()).isFalse(); 68 | } 69 | 70 | @Test 71 | public void testPostsPagerPositiveOutOfBoundsPage() { 72 | testOutOfBoundsPage(200); 73 | } 74 | 75 | @Test 76 | public void testPostsPagerNegativeOutOfBoundsPage() { 77 | testOutOfBoundsPage(-200); 78 | } 79 | 80 | private void testOutOfBoundsPage(int pageIndex) { 81 | PostsPager postsPager = postService.findNLatestPosts(NUMBERS_OF_POSTS_PER_PAGE, pageIndex); 82 | assertThat(postsPager.hasPrev()).isFalse(); 83 | assertThat(postsPager.hasNext()).isFalse(); 84 | assertThat(postsPager.indexOutOfBounds()).isTrue(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /conf/evolutions/default/1.sql: -------------------------------------------------------------------------------- 1 | # --- Created by Ebean DDL 2 | # To stop Ebean DDL generation, remove this comment and start using Evolutions 3 | 4 | # --- !Ups 5 | 6 | create table comment ( 7 | id bigint auto_increment not null, 8 | body TEXT, 9 | post_id bigint, 10 | user_id bigint, 11 | create_date timestamp not null, 12 | constraint pk_comment primary key (id) 13 | ); 14 | 15 | create table post ( 16 | id bigint auto_increment not null, 17 | title varchar(255) not null, 18 | body TEXT, 19 | user_id bigint, 20 | create_date timestamp not null, 21 | constraint pk_post primary key (id) 22 | ); 23 | 24 | create table role ( 25 | id bigint auto_increment not null, 26 | role varchar(255), 27 | constraint uq_role_role unique (role), 28 | constraint pk_role primary key (id) 29 | ); 30 | 31 | create table user ( 32 | id bigint auto_increment not null, 33 | username varchar(255) not null, 34 | password varchar(255) not null, 35 | email varchar(255) not null, 36 | first_name varchar(255), 37 | last_name varchar(255), 38 | active integer not null, 39 | constraint uq_user_username unique (username), 40 | constraint uq_user_email unique (email), 41 | constraint pk_user primary key (id) 42 | ); 43 | 44 | create table user_role ( 45 | user_id bigint not null, 46 | role_id bigint not null, 47 | constraint pk_user_role primary key (user_id,role_id) 48 | ); 49 | 50 | alter table comment add constraint fk_comment_post_id foreign key (post_id) references post (id) on delete restrict on update restrict; 51 | create index ix_comment_post_id on comment (post_id); 52 | 53 | alter table comment add constraint fk_comment_user_id foreign key (user_id) references user (id) on delete restrict on update restrict; 54 | create index ix_comment_user_id on comment (user_id); 55 | 56 | alter table post add constraint fk_post_user_id foreign key (user_id) references user (id) on delete restrict on update restrict; 57 | create index ix_post_user_id on post (user_id); 58 | 59 | alter table user_role add constraint fk_user_role_user foreign key (user_id) references user (id) on delete restrict on update restrict; 60 | create index ix_user_role_user on user_role (user_id); 61 | 62 | alter table user_role add constraint fk_user_role_role foreign key (role_id) references role (id) on delete restrict on update restrict; 63 | create index ix_user_role_role on user_role (role_id); 64 | 65 | 66 | # --- !Downs 67 | 68 | alter table comment drop constraint if exists fk_comment_post_id; 69 | drop index if exists ix_comment_post_id; 70 | 71 | alter table comment drop constraint if exists fk_comment_user_id; 72 | drop index if exists ix_comment_user_id; 73 | 74 | alter table post drop constraint if exists fk_post_user_id; 75 | drop index if exists ix_post_user_id; 76 | 77 | alter table user_role drop constraint if exists fk_user_role_user; 78 | drop index if exists ix_user_role_user; 79 | 80 | alter table user_role drop constraint if exists fk_user_role_role; 81 | drop index if exists ix_user_role_role; 82 | 83 | drop table if exists comment; 84 | 85 | drop table if exists post; 86 | 87 | drop table if exists role; 88 | 89 | drop table if exists user; 90 | 91 | drop table if exists user_role; 92 | 93 | -------------------------------------------------------------------------------- /test/SecurityTest.java: -------------------------------------------------------------------------------- 1 | import dto.PostDTO; 2 | import dto.UserDTO; 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import play.mvc.Http; 6 | import play.mvc.Result; 7 | import play.test.Helpers; 8 | import play.test.WithApplication; 9 | import services.PostService; 10 | import services.UserService; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static play.mvc.Http.Status.*; 14 | import static play.test.Helpers.GET; 15 | import static play.test.Helpers.route; 16 | 17 | public class SecurityTest extends WithApplication { 18 | 19 | private static final String USERNAME = "username"; 20 | private static final String PASSWORD = "password"; 21 | private static final String EMAIL = "testuser@testuser.com"; 22 | 23 | private PostService postService; 24 | private UserService userService; 25 | 26 | @Before 27 | public void setUp() { 28 | userService = app.injector().instanceOf(UserService.class); 29 | postService = app.injector().instanceOf(PostService.class); 30 | 31 | UserDTO userDTO = new UserDTO(USERNAME, PASSWORD, EMAIL, "firstName", "lastName"); 32 | userService.saveUser(userDTO); 33 | } 34 | 35 | @Test 36 | public void testUnauthenticatedUserRequestingProtectedPage() { 37 | Http.RequestBuilder request = Helpers.fakeRequest() 38 | .method(GET) 39 | .uri(controllers.routes.PostController.getNewPostForm().url()); 40 | 41 | Result result = route(app, request); 42 | assertThat(result.status()).isEqualTo(UNAUTHORIZED); 43 | } 44 | 45 | @Test 46 | public void testAuthenticatedUserRequestingProtectedPage() { 47 | Http.RequestBuilder request = Helpers.fakeRequest() 48 | .method(GET) 49 | .session("username", USERNAME) 50 | .uri(controllers.routes.PostController.getNewPostForm().url()); 51 | 52 | Result result = route(app, request); 53 | assertThat(result.status()).isEqualTo(OK); 54 | } 55 | 56 | @Test 57 | public void testAuthenticatedUserRequestingRegisterPage() { 58 | Http.RequestBuilder request = Helpers.fakeRequest() 59 | .method(GET) 60 | .session("username", USERNAME) 61 | .uri(controllers.routes.UserController.getRegistrationForm().url()); 62 | 63 | Result result = route(app, request); 64 | assertThat(result.status()).isEqualTo(SEE_OTHER); 65 | } 66 | 67 | @Test 68 | public void testAuthenticatedUserRequestingToEditHisPost() { 69 | PostDTO postDTO = postService.savePost(new PostDTO(null, "TEST", "TEST", null, USERNAME)); 70 | Http.RequestBuilder request = Helpers.fakeRequest() 71 | .method(GET) 72 | .session("username", USERNAME) 73 | .uri(controllers.routes.PostController.getEditPostForm(postDTO.id).url()); 74 | 75 | Result result = route(app, request); 76 | assertThat(result.status()).isEqualTo(OK); 77 | } 78 | 79 | @Test 80 | public void testAuthenticatedUserRequestingToEditPostByOtherUser() { 81 | UserDTO userDTO = new UserDTO("TEST", "TEST", "TEST@test.com", "firstName", "lastName"); 82 | userService.saveUser(userDTO); 83 | PostDTO postDTO = postService.savePost(new PostDTO(null, "TEST", "TEST", null, userDTO.username)); 84 | 85 | Http.RequestBuilder request = Helpers.fakeRequest() 86 | .method(GET) 87 | .session("username", USERNAME) 88 | .uri(controllers.routes.PostController.getEditPostForm(postDTO.id).url()); 89 | 90 | Result result = route(app, request); 91 | assertThat(result.status()).isEqualTo(UNAUTHORIZED); 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /app/controllers/PostController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import annotations.Authenticated; 4 | import annotations.PostExistsAndUserIsOwner; 5 | import dto.PostDTO; 6 | import play.data.Form; 7 | import play.data.FormFactory; 8 | import play.mvc.Controller; 9 | import play.mvc.Result; 10 | import play.mvc.Results; 11 | import services.CommentService; 12 | import services.PostService; 13 | import views.html.editPost; 14 | import views.html.newPost; 15 | import views.html.post; 16 | 17 | import javax.inject.Inject; 18 | 19 | /** 20 | * Controller with actions related to Posts. 21 | * 22 | * @author Dusan 23 | */ 24 | public class PostController extends Controller { 25 | 26 | private final PostService postService; 27 | private final CommentService commentService; 28 | private final Form postForm; 29 | 30 | @Inject 31 | public PostController(PostService postService, CommentService commentService, FormFactory formFactory) { 32 | this.postService = postService; 33 | this.commentService = commentService; 34 | this.postForm = formFactory.form(PostDTO.class); 35 | } 36 | 37 | /** 38 | * GET Post page for post with id = postId. 39 | */ 40 | public Result getPost(Long postId) { 41 | return commentService.findCommentsForPost(postId) 42 | .map(commentDTOs -> 43 | postService.getPost(postId) 44 | .map(postDTO -> ok(post.render(postDTO, commentDTOs))) 45 | .orElseGet(Results::notFound)) 46 | .orElseGet(Results::notFound); 47 | } 48 | 49 | /** 50 | * GET new Post form. 51 | * Only authenticated users can get this form. 52 | */ 53 | @Authenticated 54 | public Result getNewPostForm() { 55 | return ok(newPost.render(postForm)); 56 | } 57 | 58 | /** 59 | * GET edit Post form. 60 | * Only authenticated users and users who are the owners of post can get this form. 61 | */ 62 | @Authenticated 63 | @PostExistsAndUserIsOwner 64 | public Result getEditPostForm(Long postId) { 65 | return postService.getPost(postId) 66 | .map(postDTO -> ok(editPost.render(postForm.fill(postDTO), postId))) 67 | .orElseGet(Results::notFound); 68 | } 69 | 70 | /** 71 | * POST new Post form. 72 | * Only authenticated users can post this form. 73 | */ 74 | @Authenticated 75 | public Result createPost() { 76 | Form postForm = this.postForm.bindFromRequest(); 77 | if (postForm.hasErrors()) { 78 | return badRequest(newPost.render(postForm)); 79 | } else { 80 | PostDTO postDTO = postForm.get(); 81 | postDTO.username = session("username"); 82 | postDTO = postService.savePost(postDTO); 83 | return redirect(routes.PostController.getPost(postDTO.id)); 84 | } 85 | } 86 | 87 | /** 88 | * POST edit Post form. 89 | * Only authenticated users and users who are the owners of post can post this form. 90 | */ 91 | @Authenticated 92 | @PostExistsAndUserIsOwner 93 | public Result editPost(Long postId) { 94 | Form postForm = this.postForm.bindFromRequest(); 95 | if (postForm.hasErrors()) { 96 | return badRequest(editPost.render(postForm, postId)); 97 | } else { 98 | PostDTO postDTO = postForm.get(); 99 | postDTO.id = postId; 100 | return postService.editPost(postDTO) 101 | .map(x -> redirect(routes.PostController.getPost(postId))) 102 | .orElseGet(Results::notFound); 103 | } 104 | } 105 | 106 | /** 107 | * DELETE Post. 108 | * Only authenticated users and users who are the owners of post can delete post. 109 | */ 110 | @Authenticated 111 | @PostExistsAndUserIsOwner 112 | public Result deletePost(Long postId) { 113 | postService.delete(postId); 114 | return redirect(routes.BlogController.usersBlog(session("username"), 1)); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template is called from the `index` template. This template 3 | * handles the rendering of the page header and body tags. It takes 4 | * two arguments, a `String` for the title of the page and an `Html` 5 | * object to insert into the body of the page. 6 | *@ 7 | @(content: Html) 8 | 9 | 10 | 11 | 12 | Play Framework Blog 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 61 | 62 |
63 | @* And here's where we render the `Html` object containing 64 | * the page content. *@ 65 | @content 66 |
67 | 68 |
69 | 70 | 71 |
72 |
73 |
74 | 75 | 76 | @if(session.get("username") != null) { 77 |

78 | | Logged user: @session.get("username") 79 | @*| Email: {{ user.email }}*@ 80 | @*| Admin:{{ user.is_superuser }}*@ 81 | | Sign Out 82 |

83 | } 84 |
85 |

© 2017 Dusan Reljic

86 |
87 | 88 |
89 |
90 | 91 | 92 | 93 |
94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Play Framework Blog Demo 2 | 3 | ## About 4 | 5 | This is a demo project for practicing Play Framework + Twirl. 6 | The idea was to build some basic blogging platform. 7 | 8 | It was made using **Java 8**, **Play Framework**, **Twirl**, **Ebean**, **Guice**. 9 | Database is in memory **H2**. 10 | Testing is done using **Junit**. 11 | 12 | There is a login and registration functionality included. 13 | 14 | User has his own blog page, where he can add new blog posts. 15 | Every authenticated user can comment on posts made by other users. 16 | Home page is paginated list of all posts. 17 | Non-authenticated users can see all blog posts, but cannot add new posts or comment. 18 | 19 | ## How to run 20 | 21 | You can run the application from the command line with [SBT](http://www.scala-sbt.org/download.html). 22 | 23 | ### Default 24 | 25 | Go to the root folder of the application and type: 26 | ```bash 27 | $ cd my-first-app 28 | $ sbt run 29 | ``` 30 | 31 | ### Using Play console 32 | 33 | Alternatively, you can run Play console. Change to the directory of your project, and run sbt: 34 | ```bash 35 | $ cd my-first-app 36 | $ sbt 37 | ``` 38 | And you will see something like: 39 | ```bash 40 | [info] Loading global plugins from /Users/play-developer/.sbt/0.13/plugins 41 | [info] Loading project definition from /Users/play-developer/my-first-app/project 42 | [info] Updating {file:/Users/play-developer/my-first-app/project/}my-first-app-build... 43 | [info] Resolving org.fusesource.jansi#jansi;1.4 ... 44 | [info] Done updating. 45 | [info] Set current project to my-first-app (in build file:/Users/play-developer/my-first-app/) 46 | [my-first-app] $ 47 | ``` 48 | To run the current application in development mode, use the run command: 49 | ```bash 50 | [my-first-app] $ run 51 | ``` 52 | 53 | ### Docker 54 | 55 | It is also possible to run the blog app using Docker: 56 | 57 | Build the Docker image: 58 | ```bash 59 | $ docker build -t reljicd/play-blog -f docker\Dockerfile . 60 | ``` 61 | 62 | Run the Docker container: 63 | ```bash 64 | $ docker run --rm -i -p 9000:9000 reljicd/play-blog 65 | ``` 66 | 67 | #### Helper Script 68 | 69 | It is possible to run all of the above with helper script: 70 | 71 | ```bash 72 | $ chmod +x scripts/run_docker.sh 73 | $ scripts/run_docker.sh 74 | ``` 75 | 76 | ## Post Installation 77 | 78 | The application should be up and running within a few seconds. 79 | 80 | Go to the web browser and visit `http://localhost:9000/blog` 81 | 82 | User username: **user** 83 | 84 | User password: **password** 85 | 86 | ## How to test 87 | 88 | The location for tests is in the **test** folder. 89 | 90 | ### Using SBT console 91 | 92 | You can run tests from the SBT console. 93 | Change to the directory of your project, and run *sbt*: 94 | ```bash 95 | $ cd my-first-app 96 | $ sbt 97 | ``` 98 | To run all tests, run *test*: 99 | ```bash 100 | [my-first-app] $ test 101 | ``` 102 | To run only one test class, run *testOnly* followed by the name of the class i.e. *testOnly my.namespace.MyTest*: 103 | ```bash 104 | [my-first-app] $ testOnly my.namespace.MyTest 105 | ``` 106 | To run only the tests that have failed, run *testQuick*: 107 | ```bash 108 | [my-first-app] $ testQuick 109 | ``` 110 | To run tests continually, run a command with a tilde in front, i.e. *~testQuick*: 111 | ```bash 112 | [my-first-app] $ ~testQuick 113 | ``` 114 | To access test helpers such as FakeApplication in console, run *test:console*: 115 | ```bash 116 | [my-first-app] $ test:console 117 | ``` 118 | 119 | Testing in Play is based on [SBT](http://www.scala-sbt.org/download.html), and a full description is available in the [testing documentation](http://www.scala-sbt.org/release/docs/Testing.html). 120 | 121 | ### Default 122 | 123 | Go to the root folder of the application and type: 124 | ```bash 125 | $ cd my-first-app 126 | $ sbt test 127 | ``` 128 | 129 | ### Docker 130 | 131 | It is also possible to run tests using Docker: 132 | 133 | Build the Docker image: 134 | ```bash 135 | $ docker build -t reljicd/play-blog -f docker\Dockerfile . 136 | ``` 137 | 138 | Run the Docker container: 139 | ```bash 140 | $ docker run --rm reljicd/play-blog test 141 | ``` 142 | 143 | ## Helper Tools 144 | 145 | ### H2 Database web interface 146 | 147 | You can browse the contents of your database by typing *h2-browser* at the sbt shell: 148 | ```bash 149 | $ cd my-first-app 150 | $ sbt run 151 | [my-first-app] $ h2-browser 152 | ``` 153 | An SQL browser will run in your web browser. 154 | 155 | In field **JDBC URL** put 156 | ``` 157 | jdbc:h2:file:$WORKING_DIRECTORY/play;AUTO_SERVER=TRUE 158 | ``` 159 | where *$WORKING_DIRECTORY* should be substituted with the full path of app directory. -------------------------------------------------------------------------------- /app/views/register.scala.html: -------------------------------------------------------------------------------- 1 | @* 2 | * This template takes a single argument, a list of Posts to display. 3 | *@ 4 | @import dto.UserDTO 5 | @import views.html.helper.CSRF 6 | 7 | @(registrationForm: Form[UserDTO]) 8 | 9 | @main { 10 |
11 |
12 |
13 | 14 | 15 |
16 | 17 | @if(registrationForm.hasGlobalErrors) { 18 | @for(error <- registrationForm.globalErrors) { 19 |
@error.format(messages())
20 | } 21 | } 22 | 23 |
24 | 25 | 26 | @helper.form(action = CSRF(routes.UserController.register()), 'class -> "form-horizontal") { 27 | 28 |
29 |
30 | 31 | 32 | @for(error <- registrationForm("username").errors) { 33 |

@error.format(messages())

34 | } 35 | 36 | 37 | 42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 | 50 | @for(error <- registrationForm("password").errors) { 51 |

@error.format(messages())

52 | } 53 | 54 | 55 | 60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 68 | @for(error <- registrationForm("firstName").errors) { 69 |

@error.format(messages())

70 | } 71 | 72 | 73 | 78 | 79 |
80 |
81 | 82 |
83 |
84 | 85 | 86 | @for(error <- registrationForm("lastName").errors) { 87 |

@error.format(messages())

88 | } 89 | 90 | 91 | 96 | 97 |
98 |
99 | 100 |
101 |
102 | 103 | 104 | @for(error <- registrationForm("email").errors) { 105 |

@error.format(messages())

106 | } 107 | 108 | 109 | 114 | 115 |
116 |
117 | 118 |
119 |
120 | 121 | 122 | 123 | 124 |
125 |
126 | 127 | } 128 | 129 |
130 |
131 |
132 | } 133 | -------------------------------------------------------------------------------- /app/views/welcome.scala.html: -------------------------------------------------------------------------------- 1 | @(message: String, style: String = "java") 2 | 3 | @defining(play.core.PlayVersion.current) { version => 4 | 5 |
6 |
7 |

@message

8 |
9 |
10 | 11 |
12 |
13 | 14 |

Welcome to Play

15 | 16 |

17 | Congratulations, you’ve just created a new Play application. This page will help you with the next few steps. 18 |

19 | 20 |
21 |

22 | You’re using Play @version 23 |

24 |
25 | 26 |
27 |

28 | @*User: @(if (ctx().args.get("user") == null) "None" else "User")*@ 29 |

30 |
31 | 32 |

Why do you see this page?

33 | 34 |

35 | The conf/routes file defines a route that tells Play to invoke the HomeController.index action 36 | whenever a browser requests the / URI using the GET method: 37 |

38 | 39 |
# Home page
 40 | GET     /               controllers.HomeController.index
41 | 42 | 43 |

44 | Play has invoked the controllers.HomeController.index method: 45 |

46 | 47 |
public Result index() {
 48 |     return ok(index.render("Your new application is ready."));
 49 | }
50 | 51 |

52 | An action method handles the incoming HTTP request, and returns the HTTP result to send back to the web client. 53 | Here we send a 200 OK response, using a template to fill its content. 54 |

55 | 56 |

57 | The template is defined in the app/views/index.scala.html file and compiled as a standard Java class. 58 |

59 | 60 |
@@(message: String)
 61 | 
 62 |   @@main("Welcome to Play") {
 63 | 
 64 |   @@play20.welcome(message, style = "Java")
 65 | 
 66 | }
67 | 68 |

69 | The first line of the template defines the function signature. Here it just takes a single String parameter. 70 | Then this template calls another function defined in app/views/main.scala.html which displays the HTML layout, and another 71 | function that displays this welcome message. You can freely add any HTML fragment mixed with Scala code in this file. 72 |

73 | 74 |
75 |

76 | Note that Scala is fully compatible with Java, so if you don’t know Scala don’t panic, a Scala statement is very similar to a Java one. 77 |

78 |
79 | 80 |

You can read more about Twirl, the template language used by Play, and how Play handles actions.

81 | 82 |

Need more info on the console?

83 | 84 |

85 | For more information on the various commands you can run on Play, i.e. running tests and packaging applications for production, see Using the Play console. 86 |

87 | 88 |

Need to set up an IDE?

89 | 90 |

91 | You can start hacking your application right now using any text editor. Any changes will be automatically reloaded at each page refresh, 92 | including modifications made to Scala source files. 93 |

94 | 95 |

96 | If you want to set-up your application in IntelliJ IDEA or any other Java IDE, check the 97 | Setting up your preferred IDE page. 98 |

99 | 100 |

Need more documentation?

101 | 102 |

103 | Play documentation is available at https://www.playframework.com/documentation. 104 |

105 | 106 |

107 | Play comes with lots of example templates showcasing various bits of Play functionality at https://www.playframework.com/download#examples. 108 |

109 | 110 |

Need more help?

111 | 112 |

113 | Play questions are asked and answered on Stackoverflow using the "playframework" tag: https://stackoverflow.com/questions/tagged/playframework 114 |

115 | 116 |

117 | The Play Google Group is where Play users come to seek help, 118 | announce projects, and discuss issues and new features. If you don’t have a Google account, you can still join the mailing 119 | list by sending an e-mail to 120 | play-framework+subscribe@@googlegroups.com. 121 |

122 | 123 |

124 | Gitter is a real time chat channel, like IRC. The playframework/playframework channel is used by Play users to discuss the ins and outs of writing great Play applications. 125 |

126 | 127 |
128 | 129 | 149 | 150 |
151 | } -------------------------------------------------------------------------------- /conf/evolutions/default/2.sql: -------------------------------------------------------------------------------- 1 | # --- Sample dataset 2 | 3 | # --- !Ups 4 | 5 | ALTER TABLE POST 6 | ALTER COLUMN create_date SET DEFAULT CURRENT_TIMESTAMP; 7 | 8 | -- Users 9 | -- password in plaintext: "password" 10 | INSERT INTO USER (id, password, email, username, first_name, last_name, active) 11 | VALUES 12 | (1, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'user@mail.com', 'user', 'Name', 'Surname', 13 | 1); 14 | -- password in plaintext: "password" 15 | INSERT INTO USER (id, password, email, username, first_name, last_name, active) 16 | VALUES 17 | (2, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'johndoe@gmail.com', 'johndoe', 'John', 'Doe', 1); 18 | -- password in plaintext: "password" 19 | INSERT INTO USER (id, password, email, username, first_name, last_name, active) 20 | VALUES (3, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'ana@mail.com', 'ana', 'Ana', 'Surname', 1); 21 | 22 | -- Roles 23 | INSERT INTO ROLE (id, role) 24 | VALUES (1, 'ROLE_ADMIN'); 25 | INSERT INTO ROLE (id, role) 26 | VALUES (2, 'ROLE_USER'); 27 | 28 | -- User Roles 29 | INSERT INTO USER_ROLE (user_id, role_id) 30 | VALUES (1, 1); 31 | INSERT INTO USER_ROLE (user_id, role_id) 32 | VALUES (1, 2); 33 | INSERT INTO USER_ROLE (user_id, role_id) 34 | VALUES (2, 2); 35 | INSERT INTO USER_ROLE (user_id, role_id) 36 | VALUES (3, 2); 37 | 38 | -- Posts 39 | INSERT INTO POST (id, user_id, title, body, create_date) 40 | VALUES (1, 1, 'Title 1', 41 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 42 | {ts '2016-10-19 11:10:13.247'}); 43 | INSERT INTO POST (id, user_id, title, body, create_date) 44 | VALUES (2, 1, 'Title 2', 45 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 46 | {ts '2016-11-10 11:10:13.247'}); 47 | INSERT INTO POST (id, user_id, title, body, create_date) 48 | VALUES (3, 1, 'Title 3', 49 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 50 | CURRENT_TIMESTAMP()); 51 | INSERT INTO POST (id, user_id, title, body, create_date) 52 | VALUES (4, 1, 'Title 4', 53 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 54 | CURRENT_TIMESTAMP()); 55 | INSERT INTO POST (id, user_id, title, body, create_date) 56 | VALUES (5, 1, 'Title 5', 57 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 58 | CURRENT_TIMESTAMP()); 59 | INSERT INTO POST (id, user_id, title, body, create_date) 60 | VALUES (6, 1, 'Title 6', 61 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 62 | CURRENT_TIMESTAMP()); 63 | INSERT INTO POST (id, user_id, title, body, create_date) 64 | VALUES (7, 2, 'Title 7', 65 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 66 | CURRENT_TIMESTAMP()); 67 | INSERT INTO POST (id, user_id, title, body, create_date) 68 | VALUES (8, 2, 'Title 8', 69 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 70 | CURRENT_TIMESTAMP()); 71 | INSERT INTO POST (id, user_id, title, body, create_date) 72 | VALUES (9, 2, 'Title 9', 73 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 74 | CURRENT_TIMESTAMP()); 75 | INSERT INTO POST (id, user_id, title, body, create_date) 76 | VALUES (10, 2, 'Title 10', 77 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 78 | CURRENT_TIMESTAMP()); 79 | INSERT INTO POST (id, user_id, title, body, create_date) 80 | VALUES (11, 3, 'Title 11', 81 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 82 | CURRENT_TIMESTAMP()); 83 | INSERT INTO POST (id, user_id, title, body, create_date) 84 | VALUES (12, 3, 'Title 12', 85 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 86 | CURRENT_TIMESTAMP()); 87 | 88 | -- Comments 89 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 90 | VALUES (1, 1, 91 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 92 | CURRENT_TIMESTAMP()); 93 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 94 | VALUES (1, 2, 95 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 96 | CURRENT_TIMESTAMP()); 97 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 98 | VALUES (1, 3, 99 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 100 | CURRENT_TIMESTAMP()); 101 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 102 | VALUES (6, 1, 103 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 104 | CURRENT_TIMESTAMP()); 105 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 106 | VALUES (6, 2, 107 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 108 | CURRENT_TIMESTAMP()); 109 | INSERT INTO COMMENT (post_id, user_id, body, create_date) 110 | VALUES (6, 3, 111 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."', 112 | CURRENT_TIMESTAMP()); 113 | 114 | # --- !Downs 115 | 116 | delete from comment; 117 | delete from post; 118 | delete from user_role; 119 | delete from user; 120 | delete from role; 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # https://www.playframework.com/documentation/latest/ConfigFile 3 | # ~~~~~ 4 | # Play uses HOCON as its configuration file format. HOCON has a number 5 | # of advantages over other config formats, but there are two things that 6 | # can be used when modifying settings. 7 | # 8 | # You can include other configuration files in this main application.conf file: 9 | #include "extra-config.conf" 10 | # 11 | # You can declare variables and substitute for them: 12 | #mykey = ${some.value} 13 | # 14 | # And if an environment variable exists when there is no other subsitution, then 15 | # HOCON will fall back to substituting environment variable: 16 | #mykey = ${JAVA_HOME} 17 | 18 | ## Akka 19 | # https://www.playframework.com/documentation/latest/ScalaAkka#Configuration 20 | # https://www.playframework.com/documentation/latest/JavaAkka#Configuration 21 | # ~~~~~ 22 | # Play uses Akka internally and exposes Akka Streams and actors in Websockets and 23 | # other streaming HTTP responses. 24 | akka { 25 | # "akka.log-config-on-start" is extraordinarly useful because it log the complete 26 | # configuration at INFO level, including defaults and overrides, so it s worth 27 | # putting at the very top. 28 | # 29 | # Put the following in your conf/logback.xml file: 30 | # 31 | # 32 | # 33 | # And then uncomment this line to debug the configuration. 34 | # 35 | #log-config-on-start = true 36 | } 37 | 38 | ## Secret key 39 | # http://www.playframework.com/documentation/latest/ApplicationSecret 40 | # ~~~~~ 41 | # The secret key is used to sign Play's session cookie. 42 | # This must be changed for production, but we don't recommend you change it in this file. 43 | play.http.secret.key = "changeme" 44 | 45 | ## Modules 46 | # https://www.playframework.com/documentation/latest/Modules 47 | # ~~~~~ 48 | # Control which modules are loaded when Play starts. Note that modules are 49 | # the replacement for "GlobalSettings", which are deprecated in 2.5.x. 50 | # Please see https://www.playframework.com/documentation/latest/GlobalSettings 51 | # for more information. 52 | # 53 | # You can also extend Play functionality by using one of the publically available 54 | # Play modules: https://playframework.com/documentation/latest/ModuleDirectory 55 | play.modules { 56 | # By default, Play will load any class called Module that is defined 57 | # in the root package (the "app" directory), or you can define them 58 | # explicitly below. 59 | # If there are any built-in modules that you want to disable, you can list them here. 60 | #enabled += my.application.Module 61 | 62 | # If there are any built-in modules that you want to disable, you can list them here. 63 | #disabled += "" 64 | } 65 | 66 | ## IDE 67 | # https://www.playframework.com/documentation/latest/IDE 68 | # ~~~~~ 69 | # Depending on your IDE, you can add a hyperlink for errors that will jump you 70 | # directly to the code location in the IDE in dev mode. The following line makes 71 | # use of the IntelliJ IDEA REST interface: 72 | play.editor="http://localhost:63342/api/file/?file=%s&line=%s" 73 | 74 | ## Internationalisation 75 | # https://www.playframework.com/documentation/latest/JavaI18N 76 | # https://www.playframework.com/documentation/latest/ScalaI18N 77 | # ~~~~~ 78 | # Play comes with its own i18n settings, which allow the user's preferred language 79 | # to map through to internal messages, or allow the language to be stored in a cookie. 80 | play.i18n { 81 | # The application languages 82 | langs = [ "en" ] 83 | 84 | # Whether the language cookie should be secure or not 85 | #langCookieSecure = true 86 | 87 | # Whether the HTTP only attribute of the cookie should be set to true 88 | #langCookieHttpOnly = true 89 | } 90 | 91 | ## Play HTTP settings 92 | # ~~~~~ 93 | play.http { 94 | ## Router 95 | # https://www.playframework.com/documentation/latest/JavaRouting 96 | # https://www.playframework.com/documentation/latest/ScalaRouting 97 | # ~~~~~ 98 | # Define the Router object to use for this application. 99 | # This router will be looked up first when the application is starting up, 100 | # so make sure this is the entry point. 101 | # Furthermore, it's assumed your route file is named properly. 102 | # So for an application router like `my.application.Router`, 103 | # you may need to define a router file `conf/my.application.routes`. 104 | # Default to Routes in the root package (aka "apps" folder) (and conf/routes) 105 | #router = my.application.Router 106 | 107 | ## Action Creator 108 | # https://www.playframework.com/documentation/latest/JavaActionCreator 109 | # ~~~~~ 110 | #actionCreator = null 111 | 112 | ## ErrorHandler 113 | # https://www.playframework.com/documentation/latest/JavaRouting 114 | # https://www.playframework.com/documentation/latest/ScalaRouting 115 | # ~~~~~ 116 | # If null, will attempt to load a class called ErrorHandler in the root package, 117 | #errorHandler = null 118 | 119 | ## Session & Flash 120 | # https://www.playframework.com/documentation/latest/JavaSessionFlash 121 | # https://www.playframework.com/documentation/latest/ScalaSessionFlash 122 | # ~~~~~ 123 | session { 124 | # Sets the cookie to be sent only over HTTPS. 125 | #secure = true 126 | 127 | # Sets the cookie to be accessed only by the server. 128 | #httpOnly = true 129 | 130 | # Sets the max-age field of the cookie to 5 minutes. 131 | # NOTE: this only sets when the browser will discard the cookie. Play will consider any 132 | # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout, 133 | # you need to put a timestamp in the session and check it at regular intervals to possibly expire it. 134 | #maxAge = 300 135 | 136 | # Sets the domain on the session cookie. 137 | #domain = "example.com" 138 | } 139 | 140 | flash { 141 | # Sets the cookie to be sent only over HTTPS. 142 | #secure = true 143 | 144 | # Sets the cookie to be accessed only by the server. 145 | #httpOnly = true 146 | } 147 | } 148 | 149 | ## Netty Provider 150 | # https://www.playframework.com/documentation/latest/SettingsNetty 151 | # ~~~~~ 152 | play.server.netty { 153 | # Whether the Netty wire should be logged 154 | #log.wire = true 155 | 156 | # If you run Play on Linux, you can use Netty's native socket transport 157 | # for higher performance with less garbage. 158 | #transport = "native" 159 | } 160 | 161 | ## WS (HTTP Client) 162 | # https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS 163 | # ~~~~~ 164 | # The HTTP client primarily used for REST APIs. The default client can be 165 | # configured directly, but you can also create different client instances 166 | # with customized settings. You must enable this by adding to build.sbt: 167 | # 168 | # libraryDependencies += ws // or javaWs if using java 169 | # 170 | play.ws { 171 | # Sets HTTP requests not to follow 302 requests 172 | #followRedirects = false 173 | 174 | # Sets the maximum number of open HTTP connections for the client. 175 | #ahc.maxConnectionsTotal = 50 176 | 177 | ## WS SSL 178 | # https://www.playframework.com/documentation/latest/WsSSL 179 | # ~~~~~ 180 | ssl { 181 | # Configuring HTTPS with Play WS does not require programming. You can 182 | # set up both trustManager and keyManager for mutual authentication, and 183 | # turn on JSSE debugging in development with a reload. 184 | #debug.handshake = true 185 | #trustManager = { 186 | # stores = [ 187 | # { type = "JKS", path = "exampletrust.jks" } 188 | # ] 189 | #} 190 | } 191 | } 192 | 193 | ## Cache 194 | # https://www.playframework.com/documentation/latest/JavaCache 195 | # https://www.playframework.com/documentation/latest/ScalaCache 196 | # ~~~~~ 197 | # Play comes with an integrated cache API that can reduce the operational 198 | # overhead of repeated requests. You must enable this by adding to build.sbt: 199 | # 200 | # libraryDependencies += cache 201 | # 202 | play.cache { 203 | # If you want to bind several caches, you can bind the individually 204 | #bindCaches = ["db-cache", "user-cache", "session-cache"] 205 | } 206 | 207 | ## Filter Configuration 208 | # https://www.playframework.com/documentation/latest/Filters 209 | # ~~~~~ 210 | # There are a number of built-in filters that can be enabled and configured 211 | # to give Play greater security. 212 | # 213 | play.filters { 214 | 215 | # Enabled filters are run automatically against Play. 216 | # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default. 217 | enabled += filters.LoggingFilter 218 | 219 | # Disabled filters remove elements from the enabled list. 220 | #disabled += filters.ExampleFilter 221 | 222 | ## CORS filter configuration 223 | # https://www.playframework.com/documentation/latest/CorsFilter 224 | # ~~~~~ 225 | # CORS is a protocol that allows web applications to make requests from the browser 226 | # across different domains. 227 | # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has 228 | # dependencies on CORS settings. 229 | cors { 230 | # Filter paths by a whitelist of path prefixes 231 | #pathPrefixes = ["/some/path", ...] 232 | 233 | # The allowed origins. If null, all origins are allowed. 234 | #allowedOrigins = ["http://www.example.com"] 235 | 236 | # The allowed HTTP methods. If null, all methods are allowed 237 | #allowedHttpMethods = ["GET", "POST"] 238 | } 239 | 240 | ## CSRF Filter 241 | # https://www.playframework.com/documentation/latest/ScalaCsrf#Applying-a-global-CSRF-filter 242 | # https://www.playframework.com/documentation/latest/JavaCsrf#Applying-a-global-CSRF-filter 243 | # ~~~~~ 244 | # Play supports multiple methods for verifying that a request is not a CSRF request. 245 | # The primary mechanism is a CSRF token. This token gets placed either in the query string 246 | # or body of every form submitted, and also gets placed in the users session. 247 | # Play then verifies that both tokens are present and match. 248 | csrf { 249 | # Sets the cookie to be sent only over HTTPS 250 | #cookie.secure = true 251 | 252 | # Defaults to CSRFErrorHandler in the root package. 253 | #errorHandler = MyCSRFErrorHandler 254 | } 255 | 256 | ## Security headers filter configuration 257 | # https://www.playframework.com/documentation/latest/SecurityHeaders 258 | # ~~~~~ 259 | # Defines security headers that prevent XSS attacks. 260 | # If enabled, then all options are set to the below configuration by default: 261 | headers { 262 | # The X-Frame-Options header. If null, the header is not set. 263 | #frameOptions = "DENY" 264 | 265 | # The X-XSS-Protection header. If null, the header is not set. 266 | #xssProtection = "1; mode=block" 267 | 268 | # The X-Content-Type-Options header. If null, the header is not set. 269 | #contentTypeOptions = "nosniff" 270 | 271 | # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set. 272 | #permittedCrossDomainPolicies = "master-only" 273 | 274 | # The Content-Security-Policy header. If null, the header is not set. 275 | #contentSecurityPolicy = "default-src 'self'" 276 | } 277 | 278 | ## Allowed hosts filter configuration 279 | # https://www.playframework.com/documentation/latest/AllowedHostsFilter 280 | # ~~~~~ 281 | # Play provides a filter that lets you configure which hosts can access your application. 282 | # This is useful to prevent cache poisoning attacks. 283 | hosts { 284 | # Allow requests to example.com, its subdomains, and localhost:9000. 285 | #allowed = [".example.com", "localhost:9000"] 286 | } 287 | } 288 | 289 | ## Evolutions 290 | # https://www.playframework.com/documentation/latest/Evolutions 291 | # ~~~~~ 292 | # Evolutions allows database scripts to be automatically run on startup in dev mode 293 | # for database migrations. You must enable this by adding to build.sbt: 294 | # 295 | # libraryDependencies += evolutions 296 | # 297 | play.evolutions { 298 | # You can disable evolutions for a specific datasource if necessary 299 | #db.default.enabled = false 300 | } 301 | 302 | ## Database Connection Pool 303 | # https://www.playframework.com/documentation/latest/SettingsJDBC 304 | # ~~~~~ 305 | # Play doesn't require a JDBC database to run, but you can easily enable one. 306 | # 307 | # libraryDependencies += jdbc 308 | # 309 | play.db { 310 | # The combination of these two settings results in "db.default" as the 311 | # default JDBC pool: 312 | #config = "db" 313 | #default = "default" 314 | 315 | # Play uses HikariCP as the default connection pool. You can override 316 | # settings by changing the prototype: 317 | prototype { 318 | # Sets a fixed JDBC connection pool size of 50 319 | #hikaricp.minimumIdle = 50 320 | #hikaricp.maximumPoolSize = 50 321 | } 322 | } 323 | 324 | ## JDBC Datasource 325 | # https://www.playframework.com/documentation/latest/JavaDatabase 326 | # https://www.playframework.com/documentation/latest/ScalaDatabase 327 | # ~~~~~ 328 | # Once JDBC datasource is set up, you can work with several different 329 | # database options: 330 | # 331 | # Slick (Scala preferred option): https://www.playframework.com/documentation/latest/PlaySlick 332 | # JPA (Java preferred option): https://playframework.com/documentation/latest/JavaJPA 333 | # EBean: https://playframework.com/documentation/latest/JavaEbean 334 | # Anorm: https://www.playframework.com/documentation/latest/ScalaAnorm 335 | # 336 | db { 337 | # You can declare as many datasources as you want. 338 | # By convention, the default datasource is named `default` 339 | 340 | # https://www.playframework.com/documentation/latest/Developing-with-the-H2-Database 341 | default.driver=org.h2.Driver 342 | default.url="jdbc:h2:./play;AUTO_SERVER=TRUE" 343 | # default.url="jdbc:h2:mem:play" 344 | # default.username = sa 345 | # default.password = "" 346 | 347 | # Default database configuration using SQLite database engine 348 | # default.driver=org.sqlite.JDBC 349 | # default.url="jdbc:sqlite:./db-file" 350 | 351 | # You can turn on SQL logging for any datasource 352 | # https://www.playframework.com/documentation/latest/Highlights25#Logging-SQL-statements 353 | #default.logSql=true 354 | } 355 | 356 | # Ebean configuration 357 | # ~~~~~ 358 | # You can declare as many Ebean servers as you want. 359 | # By convention, the default server is named `default` 360 | ebean.default="models.*" --------------------------------------------------------------------------------