Created
October 4, 2020 20:45
-
-
Save leovegas/6230033810d4ca8f3d724556169b3527 to your computer and use it in GitHub Desktop.
Simple Web Chat ver 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package actors | |
import actors.ChatActor.{SendMessage, Tick} | |
import akka.actor.{Actor, ActorRef, ActorSystem, Props} | |
import tasks.KeepAliveTask | |
class ChatActor(out: ActorRef, manager: ActorRef) extends Actor{ | |
manager ! ChatManager.NewChatter(self) | |
override def receive: Receive = { | |
case s: String => manager ! ChatManager.Message(s) | |
case SendMessage(msg) => out ! msg | |
case Tick => println("tick") | |
} | |
} | |
object ChatActor { | |
def props(out: ActorRef,manager: ActorRef) = Props(new ChatActor(out, manager)) | |
case class SendMessage(msg: String) | |
object Tick | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package actors | |
import actors.ChatActor.Tick | |
import actors.ChatManager.{LogoutMessage, Message, NewChatter} | |
import akka.actor.{Actor, ActorRef} | |
class ChatManager extends Actor{ | |
private var chatters = List.empty[ActorRef] | |
override def receive: Receive = { | |
case "tick009" => for (c <- chatters) c ! "tick009" | |
case NewChatter(chatter) => chatters ::= chatter | |
case Message(msg) => for (c <- chatters) c ! ChatActor.SendMessage(msg) | |
case LogoutMessage(username) => for (c <- chatters) c ! ChatActor.SendMessage("Logged out user: "+username) | |
} | |
} | |
object ChatManager { | |
case class LogoutMessage(username: String) | |
case class NewChatter(chatter: ActorRef) | |
case class Message(msg: String) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package actors | |
import actors.TickActor.{SendMessage, Tick} | |
import akka.actor.{Actor, ActorRef, Props} | |
class TickActor(out: ActorRef, manager: ActorRef) extends Actor{ | |
manager ! ChatManager.NewChatter(self) | |
override def receive: Receive = { | |
case s: String => manager ! ChatManager.Message(s) | |
case SendMessage(msg) => out ! msg | |
case Tick => out ! "tick" | |
} | |
} | |
object TickActor { | |
def props(out: ActorRef,manager: ActorRef) = Props(new TickActor(out, manager)) | |
case class SendMessage(msg: String) | |
object Tick | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject._ | |
import akka.actor.ActorSystem | |
import play.api.mvc._ | |
import scala.concurrent.duration._ | |
import scala.concurrent.{ExecutionContext, Future, Promise} | |
/** | |
* This controller creates an `Action` that demonstrates how to write | |
* simple asynchronous code in a controller. It uses a timer to | |
* asynchronously delay sending a response for 1 second. | |
* | |
* @param cc standard controller components | |
* @param actorSystem We need the `ActorSystem`'s `Scheduler` to | |
* run code after a delay. | |
* @param exec We need an `ExecutionContext` to execute our | |
* asynchronous code. When rendering content, you should use Play's | |
* default execution context, which is dependency injected. If you are | |
* using blocking operations, such as database or network access, then you should | |
* use a different custom execution context that has a thread pool configured for | |
* a blocking API. | |
*/ | |
@Singleton | |
class AsyncController @Inject()(cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends AbstractController(cc) { | |
/** | |
* Creates an Action that returns a plain text message after a delay | |
* of 1 second. | |
* | |
* The configuration in the `routes` file means that this method | |
* will be called when the application receives a `GET` request with | |
* a path of `/message`. | |
*/ | |
def message = Action.async { | |
getFutureMessage(1.second).map { msg => Ok(msg) } | |
} | |
private def getFutureMessage(delayTime: FiniteDuration): Future[String] = { | |
val promise: Promise[String] = Promise[String]() | |
actorSystem.scheduler.scheduleOnce(delayTime) { | |
promise.success("Hi!") | |
}(actorSystem.dispatcher) // run scheduled tasks using the actor system's dispatcher | |
promise.future | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject._ | |
import play.api.mvc._ | |
import services.Counter | |
/** | |
* This controller demonstrates how to use dependency injection to | |
* bind a component into a controller class. The class creates an | |
* `Action` that shows an incrementing count to users. The [[Counter]] | |
* object is injected by the Guice dependency injection system. | |
*/ | |
@Singleton | |
class CountController @Inject() (cc: ControllerComponents, | |
counter: Counter) extends AbstractController(cc) { | |
/** | |
* Create an action that responds with the [[Counter]]'s current | |
* count. The result is plain text. This `Action` is mapped to | |
* `GET /count` requests by an entry in the `routes` config file. | |
*/ | |
def count = Action { Ok(counter.nextCount().toString) } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject._ | |
import play.api.mvc._ | |
/** | |
* This controller creates an `Action` to handle HTTP requests to the | |
* application's home page. | |
*/ | |
@Singleton | |
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { | |
/** | |
* Create an Action to render an HTML page with a welcome message. | |
* The configuration in the `routes` file means that this method | |
* will be called when the application receives a `GET` request with | |
* a path of `/`. | |
*/ | |
def index = Action { | |
Ok(views.html.index("Your new application is ready.")) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject.{Inject, Singleton} | |
import models.{TaskListInMemoryModel, UserData} | |
import play.api.libs.json.{JsError, JsSuccess, Json, Reads} | |
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, Request, Result} | |
@Singleton | |
class TaskList3 @Inject()(cc: ControllerComponents) extends AbstractController(cc) with play.api.i18n.I18nSupport { | |
implicit val UserDataReads: Reads[UserData] = Json.reads[UserData] | |
def load = Action { implicit request => | |
Ok(views.html.version4Main()) | |
} | |
def withJsonBody[A](f: A => Result)(implicit request: Request[AnyContent], reads: Reads[A]) = { | |
request.body.asJson.map { body => | |
Json.fromJson[A](body) match { | |
case JsSuccess(a, path) => f(a) | |
case e @ JsError(_) => Redirect(routes.TaskList3.load()) | |
} | |
}.getOrElse(Redirect(routes.TaskList3.load())) | |
} | |
def withSessionUsername(f: String => Result)(implicit request: Request[AnyContent]) = { | |
request.session.get("username").map(f).getOrElse(Ok(Json.toJson(Seq.empty[String]))) | |
} | |
def validate = Action { implicit request => | |
request.body.asJson.map { body=> | |
Json.fromJson[UserData](body) match { | |
case JsSuccess(ud, path) => | |
val username = ud.username | |
val password = ud.password | |
if (TaskListInMemoryModel.validateUser(username, password)) { | |
Ok(Json.toJson(true)) | |
.withSession("username" -> username, "csrfToken" -> play.filters.csrf.CSRF.getToken.get.value) | |
}else { | |
Ok(Json.toJson(false)) | |
} | |
case e @ JsError(_) => Redirect(routes.TaskList3.load()) | |
} | |
}.getOrElse(Redirect(routes.TaskList3.load())) | |
} | |
def createUser = Action { implicit request => | |
request.body.asJson.map { body=> | |
Json.fromJson[UserData](body) match { | |
case JsSuccess(ud, path) => | |
val username = ud.username | |
val password = ud.password | |
if (TaskListInMemoryModel.createUser(username, password)) { | |
Ok(Json.toJson(true)) | |
.withSession("username" -> username, "csrfToken" -> play.filters.csrf.CSRF.getToken.get.value) | |
}else { | |
Ok(Json.toJson(false)) | |
} | |
case e @ JsError(_) => Redirect(routes.TaskList3.load()) | |
} | |
}.getOrElse(Redirect(routes.TaskList3.load())) | |
} | |
def taskList = Action {implicit request => | |
val usernameOption = request.session.get("username") | |
usernameOption.map { username => | |
Ok(Json.toJson(TaskListInMemoryModel.getTasks(username))) | |
}.getOrElse(Ok(Json.toJson(Seq.empty[String]))) | |
} | |
def addTask = Action { implicit request => | |
val usernameOption = request.session.get("username") | |
usernameOption.map { username => | |
request.body.asJson.map { body => | |
Json.fromJson[String](body) match { | |
case JsSuccess(task, path) => { | |
TaskListInMemoryModel.addTask(username, task); | |
Ok(Json.toJson(true)) | |
} | |
case e @ JsError(_) => Redirect(routes.TaskList3.load()) | |
} | |
}.getOrElse(Ok(Json.toJson(false))) | |
}.getOrElse(Ok(Json.toJson(false))) | |
} | |
def delete = Action { implicit request => | |
val usernameOption = request.session.get("username") | |
usernameOption.map { username => | |
request.body.asJson.map { body => | |
Json.fromJson[Int](body) match { | |
case JsSuccess(taskNumber, path) => { | |
TaskListInMemoryModel.removeTask(username, taskNumber); | |
Ok(Json.toJson(true)) | |
} | |
case e @ JsError(_) => Redirect(routes.TaskList3.load()) | |
} | |
}.getOrElse(Ok(Json.toJson(false))) | |
}.getOrElse(Ok(Json.toJson(false))) | |
} | |
def logout = Action { | |
Redirect(routes.TaskList3.load()).withNewSession | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject.{Inject, Singleton} | |
import models.{TaskListInMemoryModel, UserData} | |
import play.api.libs.json.{JsError, JsSuccess, Json, Reads} | |
import play.api.mvc.{AbstractController, ControllerComponents} | |
@Singleton | |
class TaskList4 @Inject()(cc: ControllerComponents) extends AbstractController(cc) with play.api.i18n.I18nSupport { | |
def load = Action { implicit request => | |
Ok(views.html.version5Main()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import actors.ChatManager | |
import actors.ChatManager.LogoutMessage | |
import akka.actor.{ActorSystem, Props} | |
import akka.stream.Materializer | |
import javax.inject.{Inject, Singleton} | |
import models.{TaskItem, TaskListDatabaseModel, UserData} | |
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} | |
import play.api.libs.json.{JsError, JsSuccess, Json, Reads, Writes} | |
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, Request, Result} | |
import play.mvc.Action | |
import slick.jdbc.JdbcProfile | |
import slick.jdbc.PostgresProfile.api._ | |
import scala.concurrent.{ExecutionContext, Future} | |
@Singleton | |
class TaskList5 @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, cc: ControllerComponents)(implicit ec: ExecutionContext) | |
extends AbstractController(cc) with play.api.i18n.I18nSupport with HasDatabaseConfigProvider[JdbcProfile] { | |
private val model = new TaskListDatabaseModel(db) | |
def load = Action { implicit request => | |
Ok(views.html.version6Main()) | |
} | |
implicit val UserDataReads: Reads[UserData] = Json.reads[UserData] | |
implicit val itemDataWrites: Writes[TaskItem] = Json.writes[TaskItem] | |
def withJsonBody[A](f: A => Future[Result])(implicit request: Request[AnyContent], reads: Reads[A]): Future[Result] = { | |
request.body.asJson.map { body => | |
Json.fromJson[A](body) match { | |
case JsSuccess(a, path) => f(a) | |
case e@JsError(_) => Future.successful(Redirect(routes.TaskList5.load())) | |
} | |
}.getOrElse(Future.successful(Redirect(routes.TaskList5.load()))) | |
} | |
def withSessionUsername(f: String => Future[Result])(implicit request: Request[AnyContent]): Future[Result] = { | |
request.session.get("username").map(f).getOrElse(Future.successful(Ok(Json.toJson(Seq.empty[String])))) | |
} | |
def withSessionUserid(f: Int => Future[Result])(implicit request: Request[AnyContent]): Future[Result] = { | |
request.session.get("userid").map(userid => f(userid.toInt)).getOrElse(Future.successful(Ok(Json.toJson(Seq.empty[String])))) | |
} | |
def validate = Action.async { implicit request => | |
withJsonBody[UserData] { ud => | |
model.validateUser(ud.username, ud.password, true).map { | |
case Some(userid) => | |
Ok(Json.toJson(true)) | |
.withSession("username" -> ud.username, "userid" -> userid.toString, "csrfToken" -> play.filters.csrf.CSRF.getToken.map(_.value).getOrElse("")) | |
case None => | |
Ok(Json.toJson(false)) | |
} | |
} | |
} | |
def createUser = Action.async { implicit request => | |
withJsonBody[UserData] { ud => | |
model.createUser(ud.username, ud.password, false).map { | |
case Some(userid) => | |
Ok(Json.toJson(true)) | |
.withSession("username" -> ud.username, "userid" -> userid.toString, "csrfToken" -> play.filters.csrf.CSRF.getToken.map(_.value).getOrElse("")) | |
case None => | |
Ok(Json.toJson(false)) | |
} | |
} | |
} | |
def taskList = Action.async { implicit request => | |
withSessionUsername { username => | |
model.getTasks(username).map(tasks => Ok(Json.toJson(tasks))) | |
} | |
} | |
def addTask = Action.async { implicit request => | |
withSessionUserid { userid => | |
withJsonBody[String] { task => | |
model.addTask(userid, task).map(count => Ok(Json.toJson(count > 0))) | |
} | |
} | |
} | |
def delete = Action.async { implicit request => | |
withSessionUsername { username => | |
withJsonBody[Int] { itemId => | |
model.removeTask(itemId).map(removed => Ok(Json.toJson(removed))) | |
} | |
} | |
} | |
// | |
// def logout = Action { implicit request => | |
// Ok(Json.toJson(true)).withSession(request.session - "username") | |
// } | |
def logout = Action.async { implicit request => | |
withSessionUsername {username => | |
model.logoutUpdateStatus(username, false).map { | |
case Some(_) => | |
Redirect(routes.TaskList3.load()).withSession(request.session - "username") | |
case None => | |
Ok("") | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import java.util.concurrent.atomic.AtomicInteger | |
import actors.ChatManager.LogoutMessage | |
import actors.{ChatActor, ChatManager, TickActor} | |
import akka.actor.{ActorSystem, Props} | |
import akka.http.scaladsl.Http | |
import akka.http.scaladsl.model.ws.{Message, WebSocketRequest} | |
import akka.http.scaladsl.settings.ClientConnectionSettings | |
import akka.stream.Materializer | |
import akka.stream.scaladsl.{Flow, Sink, Source} | |
import akka.util.ByteString | |
import javax.inject.{Inject, Singleton} | |
import models.{TaskListInMemoryModel, UserData} | |
import play.api.libs.json.{JsError, JsSuccess, Json, Reads} | |
import play.api.libs.streams.ActorFlow | |
import play.api.mvc.{AbstractController, ControllerComponents, WebSocket} | |
import tasks.KeepAliveTask | |
import scala.concurrent.duration._ | |
@Singleton | |
class WebSocketChat @Inject()(cc: ControllerComponents)(implicit system:ActorSystem, mat: Materializer) extends AbstractController(cc) with play.api.i18n.I18nSupport { | |
var currentUsername = ""; | |
import system.dispatcher | |
val manager = system.actorOf(Props[ChatManager], "Manager") | |
val keepAlive = new KeepAliveTask(system, manager) | |
def index(username: String) = Action { implicit request => | |
currentUsername = username | |
Ok(views.html.chatPage(username)) | |
} | |
def logoutMessage() = Action { implicit request => | |
manager ! LogoutMessage(currentUsername) | |
Redirect(routes.TaskList5.logout()) | |
} | |
def socket() = WebSocket.accept[String,String] { implicit request => | |
ActorFlow.actorRef { out => | |
ChatActor.props(out, manager) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package filters | |
import akka.stream.Materializer | |
import javax.inject._ | |
import play.api.mvc._ | |
import scala.concurrent.{ExecutionContext, Future} | |
/** | |
* This is a simple filter that adds a header to all requests. It's | |
* added to the application's list of filters by the | |
* [[Filters]] class. | |
* | |
* @param mat This object is needed to handle streaming of requests | |
* and responses. | |
* @param exec This class is needed to execute code asynchronously. | |
* It is used below by the `map` method. | |
*/ | |
@Singleton | |
class ExampleFilter @Inject()( | |
implicit override val mat: Materializer, | |
exec: ExecutionContext) extends Filter { | |
override def apply(nextFilter: RequestHeader => Future[Result]) | |
(requestHeader: RequestHeader): Future[Result] = { | |
// Run the next filter in the chain. This will call other filters | |
// and eventually call the action. Take the result and modify it | |
// by adding a new header. | |
nextFilter(requestHeader).map { result => | |
result.withHeaders("X-ExampleFilter" -> "foo") | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
object CodeGen extends App { | |
slick.codegen.SourceCodeGenerator.run( | |
"slick.jdbc.PostgresProfile", | |
"org.postgresql.Driver", | |
"jdbc:postgresql://", | |
"/home/leonid/Programs/SimpleWebChat/app", | |
"models", None, None, true, false | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
// AUTO-GENERATED Slick data model | |
import slick.jdbc.{ MySQLProfile => profile } | |
/** Stand-alone Slick data model for immediate use */ | |
object Tables extends Tables { | |
val profile = slick.jdbc.PostgresProfile | |
} | |
/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */ | |
trait Tables { | |
val profile: slick.jdbc.JdbcProfile | |
import profile.api._ | |
import slick.model.ForeignKeyAction | |
// NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns. | |
import slick.jdbc.{GetResult => GR} | |
/** DDL for all tables. Call .create to execute. */ | |
lazy val schema: profile.SchemaDescription = Items.schema ++ Users.schema | |
@deprecated("Use .schema instead of .ddl", "3.0") | |
def ddl = schema | |
/** Entity class storing rows of table Items | |
* @param itemId Database column item_id SqlType(serial), AutoInc, PrimaryKey | |
* @param userId Database column user_id SqlType(int4) | |
* @param text Database column text SqlType(varchar), Length(2000,true) */ | |
case class ItemsRow(itemId: Int, userId: Int, text: String) | |
/** GetResult implicit for fetching ItemsRow objects using plain SQL queries */ | |
implicit def GetResultItemsRow(implicit e0: GR[Int], e1: GR[String]): GR[ItemsRow] = GR{ | |
prs => import prs._ | |
ItemsRow.tupled((<<[Int], <<[Int], <<[String])) | |
} | |
/** Table description of table items. Objects of this class serve as prototypes for rows in queries. */ | |
class Items(_tableTag: Tag) extends profile.api.Table[ItemsRow](_tableTag, "items") { | |
def * = (itemId, userId, text) <> (ItemsRow.tupled, ItemsRow.unapply) | |
/** Maps whole row to an option. Useful for outer joins. */ | |
def ? = ((Rep.Some(itemId), Rep.Some(userId), Rep.Some(text))).shaped.<>({r=>import r._; _1.map(_=> ItemsRow.tupled((_1.get, _2.get, _3.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) | |
/** Database column item_id SqlType(serial), AutoInc, PrimaryKey */ | |
val itemId: Rep[Int] = column[Int]("item_id", O.AutoInc, O.PrimaryKey) | |
/** Database column user_id SqlType(int4) */ | |
val userId: Rep[Int] = column[Int]("user_id") | |
/** Database column text SqlType(varchar), Length(2000,true) */ | |
val text: Rep[String] = column[String]("text", O.Length(2000,varying=true)) | |
/** Foreign key referencing Users (database name items_user_id_fkey) */ | |
lazy val usersFk = foreignKey("items_user_id_fkey", userId, Users)(r => r.id, onUpdate=ForeignKeyAction.NoAction, onDelete=ForeignKeyAction.Cascade) | |
} | |
/** Collection-like TableQuery object for table Items */ | |
lazy val Items = new TableQuery(tag => new Items(tag)) | |
/** Entity class storing rows of table Users | |
* @param id Database column id SqlType(serial), AutoInc, PrimaryKey | |
* @param username Database column username SqlType(varchar), Length(20,true) | |
* @param password Database column password SqlType(varchar), Length(200,true) | |
* @param online Database column online SqlType(bool) */ | |
case class UsersRow(id: Int, username: String, password: String, online: Boolean) | |
/** GetResult implicit for fetching UsersRow objects using plain SQL queries */ | |
implicit def GetResultUsersRow(implicit e0: GR[Int], e1: GR[String], e2: GR[Boolean]): GR[UsersRow] = GR{ | |
prs => import prs._ | |
UsersRow.tupled((<<[Int], <<[String], <<[String], <<[Boolean])) | |
} | |
/** Table description of table users. Objects of this class serve as prototypes for rows in queries. */ | |
class Users(_tableTag: Tag) extends profile.api.Table[UsersRow](_tableTag, "users") { | |
def * = (id, username, password, online) <> (UsersRow.tupled, UsersRow.unapply) | |
/** Maps whole row to an option. Useful for outer joins. */ | |
def ? = ((Rep.Some(id), Rep.Some(username), Rep.Some(password), Rep.Some(online))).shaped.<>({r=>import r._; _1.map(_=> UsersRow.tupled((_1.get, _2.get, _3.get, _4.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) | |
/** Database column id SqlType(serial), AutoInc, PrimaryKey */ | |
val id: Rep[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey) | |
/** Database column username SqlType(varchar), Length(20,true) */ | |
val username: Rep[String] = column[String]("username", O.Length(20,varying=true)) | |
/** Database column password SqlType(varchar), Length(200,true) */ | |
val password: Rep[String] = column[String]("password", O.Length(200,varying=true)) | |
/** Database column online SqlType(bool) */ | |
val online: Rep[Boolean] = column[Boolean]("online") | |
} | |
/** Collection-like TableQuery object for table Users */ | |
lazy val Users = new TableQuery(tag => new Users(tag)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
case class TaskItem(id:Int, text:String) { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
import scala.collection.mutable | |
import slick.jdbc.PostgresProfile.api._ | |
import scala.concurrent.{ExecutionContext, Future} | |
import models.Tables._ | |
import org.mindrot.jbcrypt.BCrypt | |
class TaskListDatabaseModel(db: Database)(implicit ec: ExecutionContext) { | |
def validateUser(username: String, password: String, status: Boolean): Future[Option[Int]] = { | |
db.run( | |
(for { | |
user <- Users if user.username === username | |
} yield { | |
user | |
}).map(_.online).update(status)) | |
val matches = db.run(Users.filter(userRow => userRow.username === username).result) | |
matches.map(userRows => userRows.headOption.flatMap { | |
userRow => if (BCrypt.checkpw(password, userRow.password)) Some(userRow.id) else None | |
}) | |
} | |
def createUser(username: String, password: String, status: Boolean): Future[Option[Int]] = { | |
val matches = db.run(Users.filter(userRow => userRow.username === username).result) | |
matches.flatMap { userRows => | |
if (userRows.isEmpty) { | |
db.run(Users += UsersRow(-1, username, BCrypt.hashpw(password, BCrypt.gensalt()), status)) | |
.flatMap { addCount => | |
if (addCount > 0) db.run(Users.filter(userRow => userRow.username === username).result) | |
.map(_.headOption.map(_.id)) | |
else Future.successful(None) | |
} | |
} else Future.successful(None) | |
} | |
} | |
def getTasks(username: String): Future[Seq[TaskItem]] = { | |
db.run( | |
(for { | |
user <- Users if user.username === username | |
item <- Items if item.userId === user.id | |
} yield { | |
// TaskItem(item.itemId, item.text) | |
item | |
}).result | |
).map(_.map(itemRow => TaskItem(itemRow.itemId, itemRow.text))) | |
} | |
def addTask(userid: Int, task: String): Future[Int] = { | |
db.run(Items += ItemsRow(-1, userid, task)) | |
} | |
def removeTask(itemId: Int): Future[Boolean] = { | |
db.run(Items.filter(_.itemId === itemId).delete).map(count => count > 0) | |
} | |
def logoutUpdateStatus(username: String, status: Boolean): Future[Option[Int]] = { | |
db.run( | |
(for { | |
user <- Users if user.username === username | |
} yield { | |
user | |
}).map(_.online).update(status) | |
).flatMap { addCount => | |
if (addCount > 0) db.run(Users.filter(userRow => userRow.username === username).result) | |
.map(_.headOption.map(_.id)) | |
else Future.successful(None) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
import scala.collection.mutable | |
object TaskListInMemoryModel { | |
private val users = mutable.Map[String, String]("Mark" -> "pass") | |
private val tasks = mutable.Map[String, List[String]]("Mark" -> List("Make videos", "eat","sleep","code")) | |
def validateUser(username: String, password: String): Boolean = { | |
users.get(username).map(_ == password).getOrElse(false) | |
} | |
def createUser(username: String, password: String): Boolean = { | |
if (users.contains(username)) false else { | |
users(username) = password | |
true | |
} | |
} | |
def getTasks(username: String): Seq[String] = { | |
tasks.get(username).getOrElse(Nil) | |
} | |
def addTask(username: String, task: String): Unit = { | |
tasks(username) = task :: tasks.get(username).getOrElse(Nil) | |
} | |
def removeTask(username: String, index: Int): Boolean = { | |
if (index < 0 || tasks.get(username).isEmpty || index >= tasks(username).length) false | |
else { | |
tasks(username) = tasks(username).patch(index, Nil, 1) | |
true | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
case class UserData(username:String, password:String, status: Boolean) { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.inject.AbstractModule | |
import java.time.Clock | |
import services.{ApplicationTimer, AtomicCounter, Counter} | |
/** | |
* This class is a Guice module that tells Guice how to bind several | |
* different types. This Guice module is created when the Play | |
* application starts. | |
* Play will automatically use any class called `Module` that is in | |
* the root package. You can create modules in other locations by | |
* adding `play.modules.enabled` settings to the `application.conf` | |
* configuration file. | |
*/ | |
class Module extends AbstractModule { | |
override def configure() = { | |
// Use the system clock as the default implementation of Clock | |
bind(classOf[Clock]).toInstance(Clock.systemDefaultZone) | |
// Ask Guice to create an instance of ApplicationTimer when the | |
// application starts. | |
bind(classOf[ApplicationTimer]).asEagerSingleton() | |
// Set AtomicCounter as the implementation for Counter. | |
bind(classOf[Counter]).to(classOf[AtomicCounter]) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package services | |
import java.time.{Clock, Instant} | |
import javax.inject._ | |
import play.api.Logger | |
import play.api.inject.ApplicationLifecycle | |
import scala.concurrent.Future | |
/** | |
* This class demonstrates how to run code when the | |
* application starts and stops. It starts a timer when the | |
* application starts. When the application stops it prints out how | |
* long the application was running for. | |
* | |
* This class is registered for Guice dependency injection in the | |
* [[Module]] class. We want the class to start when the application | |
* starts, so it is registered as an "eager singleton". See the code | |
* in the [[Module]] class to see how this happens. | |
* | |
* This class needs to run code when the server stops. It uses the | |
* application's [[ApplicationLifecycle]] to register a stop hook. | |
*/ | |
@Singleton | |
class ApplicationTimer @Inject() (clock: Clock, appLifecycle: ApplicationLifecycle) { | |
private val logger: Logger = Logger(this.getClass) | |
// This code is called when the application starts. | |
private val start: Instant = clock.instant | |
logger.info(s"ApplicationTimer demo: Starting application at $start.") | |
// When the application starts, register a stop hook with the | |
// ApplicationLifecycle object. The code inside the stop hook will | |
// be run when the application stops. | |
appLifecycle.addStopHook { () => | |
val stop: Instant = clock.instant | |
val runningTime: Long = stop.getEpochSecond - start.getEpochSecond | |
logger.info(s"ApplicationTimer demo: Stopping application at ${clock.instant} after ${runningTime}s.") | |
Future.successful(()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package services | |
import java.util.concurrent.atomic.AtomicInteger | |
import javax.inject._ | |
/** | |
* This trait demonstrates how to create a component that is injected | |
* into a controller. The trait represents a counter that returns a | |
* incremented number each time it is called. | |
*/ | |
trait Counter { | |
def nextCount(): Int | |
} | |
/** | |
* This class is a concrete implementation of the [[Counter]] trait. | |
* It is configured for Guice dependency injection in the [[Module]] | |
* class. | |
* | |
* This class has a `Singleton` annotation because we need to make | |
* sure we only use one counter per application. Without this | |
* annotation we would get a new instance every time a [[Counter]] is | |
* injected. | |
*/ | |
@Singleton | |
class AtomicCounter extends Counter { | |
private val atomicCounter = new AtomicInteger() | |
override def nextCount(): Int = atomicCounter.getAndIncrement() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tasks | |
import akka.actor.{ActorRef, ActorSystem} | |
import javax.inject.{Inject, Named} | |
import scala.concurrent.ExecutionContext | |
import scala.concurrent.duration._ | |
class KeepAliveTask @Inject() (actorSystem: ActorSystem, @Named("some-actor") someActor: ActorRef)( | |
implicit executionContext: ExecutionContext | |
) { | |
actorSystem.scheduler.scheduleAtFixedRate( | |
initialDelay = 0.microseconds, | |
interval = 5.seconds, | |
receiver = someActor, | |
message = "tick009" | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@(v:String)(implicit requestHeader: RequestHeader, flash: Flash) | |
@main("WebSocket Chat") { | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Login Activity Control API</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!--===============================================================================================--> | |
<link rel="icon" type="@routes.Assets.versioned("login_v1/image/png")" href="@routes.Assets.versioned("login_v1/images/icons/favicon.ico")"/> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/bootstrap/css/bootstrap.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/fonts/font-awesome-4.7.0/css/font-awesome.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/fonts/Linearicons-Free-v1.0.0/icon-font.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/animate/animate.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/css-hamburgers/hamburgers.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/select2/select2.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/css/util.css")"> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/css/main.css")"> | |
<!--===============================================================================================--> | |
<!--===============================================================================================--> | |
</head> | |
<body> | |
<input type="hidden" id="csrfToken" value="@helper.CSRF.getToken.value"> | |
<input type="hidden" id="validateRoute" value="@routes.TaskList3.validate()"> | |
<input type="hidden" id="tasksRoute" value="@routes.TaskList3.taskList()"> | |
<input type="hidden" id="createRoute" value="@routes.TaskList3.createUser()"> | |
<input type="hidden" id="deleteRoute" value="@routes.TaskList3.delete()"> | |
<input type="hidden" id="addRoute" value="@routes.TaskList3.addTask()"> | |
<input type="hidden" id="ws-route" value="@routes.WebSocketChat.socket.absoluteURL()"> | |
<input type="hidden" id="chat-route" value="@routes.WebSocketChat.index(v)"> | |
<input type="hidden" id="username" value=@v> | |
<div class="limiter"> | |
<div class="container-login100" style="background-image: url('images/img-01.jpg');"> | |
<div class="wrap-login100 p-t-130 p-b-30"> | |
@* <form class="login100-form validate-form">*@ | |
<div class="login100-form-avatar"> | |
<img src="@routes.Assets.versioned("login_v1/images/icons/fire.png")" alt="AVATAR"> | |
</div> | |
<span class="login100-form-title p-t-20 p-b-45"> | |
Simple Chat | |
</span> | |
<div class="wrap-input100 validate-input m-b-10"> | |
<textarea id="chat-area" class="input101" rows="15" cols="200"></textarea> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
</span> | |
</div> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Username is required"> | |
<input id="chat-input" class="input100" size="150" type="text" placeholder="Text"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
</span> | |
</div> | |
<div class="container-login100-form-btn p-t-10"> | |
<button id="sendButton" onclick="login()" class="login100-form-btn"> | |
Send | |
</button> | |
</div> | |
<div class="text-center w-full p-t-25 p-b-230"> | |
<div class="text-center w-full"> | |
<a href="@routes.WebSocketChat.logoutMessage()">Logout</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/jquery/jquery-3.2.1.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/bootstrap/js/popper.js")"></script> | |
<script src="@routes.Assets.versioned("login_v1/vendor/bootstrap/js/bootstrap.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/select2/select2.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/js/main.js")"></script> | |
<script src="@routes.Assets.versioned("javascripts/version3.js")"></script> | |
<script src="@routes.Assets.versioned("javascripts/chat.js")"></script> | |
</body> | |
</html> | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@* | |
* This template takes a single argument, a String containing a | |
* message to display. | |
*@ | |
@(message: String) | |
@* | |
* Call the `main` template with two arguments. The first | |
* argument is a `String` with the title of the page, the second | |
* argument is an `Html` object containing the body of the page. | |
*@ | |
@main("Welcome to Play") { | |
@* | |
* Get an `Html` object by calling the built-in Play welcome | |
* template and passing a `String` message. | |
*@ | |
@welcome(message, style = "scala") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@* | |
* This template is called from the `index` template. This template | |
* handles the rendering of the page header and body tags. It takes | |
* two arguments, a `String` for the title of the page and an `Html` | |
* object to insert into the body of the page. | |
*@ | |
@(title: String)(content: Html) | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
@* Here's where we render the page title `String`. *@ | |
<title>@title</title> | |
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")"> | |
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")"> | |
<script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script> | |
</head> | |
<body> | |
@* And here's where we render the `Html` object containing | |
* the page content. *@ | |
@content | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@()(implicit request: RequestHeader, flash: Flash) | |
@main("Task List (Version 3)") { | |
<div id="contents"></div> | |
@* ------------------------------------*@ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Login Activity Control API</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!--===============================================================================================--> | |
<link rel="icon" type="@routes.Assets.versioned("login_v1/image/png")" href="@routes.Assets.versioned("login_v1/images/icons/favicon.ico")"/> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/bootstrap/css/bootstrap.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/fonts/font-awesome-4.7.0/css/font-awesome.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/fonts/Linearicons-Free-v1.0.0/icon-font.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/animate/animate.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/css-hamburgers/hamburgers.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/vendor/select2/select2.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/css/util.css")"> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("login_v1/css/main.css")"> | |
<!--===============================================================================================--> | |
</head> | |
<body> | |
<input type="hidden" id="csrfToken" value="@helper.CSRF.getToken.value"> | |
<input type="hidden" id="validateRoute" value="@routes.TaskList5.validate()"> | |
<input type="hidden" id="tasksRoute" value="@routes.TaskList5.taskList()"> | |
<input type="hidden" id="createRoute" value="@routes.TaskList5.createUser()"> | |
<input type="hidden" id="deleteRoute" value="@routes.TaskList5.delete()"> | |
<input type="hidden" id="addRoute" value="@routes.TaskList5.addTask()"> | |
<div id="register-section" hidden> | |
<div class="limiter"> | |
<div class="container-login100" style="background-image: url('images/img-01.jpg');"> | |
<div class="wrap-login100 p-t-130 p-b-30"> | |
@* <form class="login100-form validate-form">*@ | |
<div class="login100-form-avatar"> | |
<img src="@routes.Assets.versioned("login_v1/images/icons/fire.png")" alt="AVATAR"> | |
</div> | |
<span class="login100-form-title p-t-20 p-b-45"> | |
Register on Simple Chat | |
</span> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Email is required"> | |
<input id="email" class="input100" type="text" placeholder="Email"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
<i class="fa fa-envelope-o"></i> | |
</span> | |
</div> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Username is required"> | |
<input id="createName" class="input100" type="text" placeholder="Username"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
<i class="fa fa-user"></i> | |
</span> | |
</div> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Password is required"> | |
<input id="createPass" class="input100" type="password" placeholder="Password"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
<i class="fa fa-lock"></i> | |
</span> | |
</div> | |
<div class="container-login100-form-btn p-t-10"> | |
<button onclick="createUser()" class="login100-form-btn"> | |
Register | |
</button> | |
</div> | |
<div class="text-center w-full p-t-25 p-b-100"> | |
<a href="#" class="txt1"> | |
</a> | |
</div> | |
<br> | |
<div class="text-center w-full"> | |
<a href="#" class="txt1" onclick="visibleLogin()"> | |
Go to login page | |
</a> | |
</div> | |
@* </form>*@ | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="login-section"> | |
<div class="limiter"> | |
<div class="container-login100" style="background-image: url('images/img-01.jpg');"> | |
<div class="wrap-login100 p-t-190 p-b-30"> | |
@* <form class="login100-form validate-form">*@ | |
<div class="login100-form-avatar"> | |
<img src="@routes.Assets.versioned("login_v1/images/icons/fire.png")" alt="AVATAR"> | |
</div> | |
<span class="login100-form-title p-t-20 p-b-45"> | |
Welcome to Simple Chat | |
</span> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Username is required"> | |
<input id="loginName" class="input100" type="text" placeholder="Username"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
<i class="fa fa-user"></i> | |
</span> | |
</div> | |
<div class="wrap-input100 validate-input m-b-10" data-validate = "Password is required"> | |
<input id="loginPass" class="input100" type="password" placeholder="Password"> | |
<span class="focus-input100"></span> | |
<span class="symbol-input100"> | |
<i class="fa fa-lock"></i> | |
</span> | |
</div> | |
<div class="container-login100-form-btn p-t-10"> | |
<button onclick="login()" class="login100-form-btn"> | |
Login | |
</button> | |
</div> | |
<div class="text-center w-full p-t-25 p-b-50"> | |
<a href="#" class="txt1"> | |
Forgot Username / Password? | |
</a> | |
</div> | |
<div class="text-center w-full"> | |
<a href="#" class="txt1" onclick="visibleCreateUser()"> | |
Create new account | |
</a> | |
</div> | |
@* </form>*@ | |
</div> | |
</div> | |
</div> | |
</div> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/jquery/jquery-3.2.1.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/bootstrap/js/popper.js")"></script> | |
<script src="@routes.Assets.versioned("login_v1/vendor/bootstrap/js/bootstrap.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/vendor/select2/select2.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("login_v1/js/main.js")"></script> | |
<script src="@routes.Assets.versioned("javascripts/version3.js")"></script> | |
</body> | |
</html> | |
@* ------------------------------------*@ | |
</div> | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@()(implicit request: RequestHeader, flash: Flash) | |
@main("Task List (Version 4)") { | |
<div id="contents"></div> | |
@* ------------------------------------*@ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Login Activity Control API</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!--===============================================================================================--> | |
<link rel="icon" type="@routes.Assets.versioned("version1/login_v1/image/png")" href="@routes.Assets.versioned("version1/login_v1/images/icons/favicon.ico")"/> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/css/bootstrap.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/fonts/font-awesome-4.7.0/css/font-awesome.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/fonts/Linearicons-Free-v1.0.0/icon-font.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/animate/animate.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/css-hamburgers/hamburgers.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/select2/select2.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/css/util.css")"> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/css/main.css")"> | |
<!--===============================================================================================--> | |
</head> | |
<body> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/jquery/jquery-3.2.1.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/js/popper.js")"></script> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/js/bootstrap.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/select2/select2.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/js/main.js")"></script> | |
<script src="https://unpkg.com/react@@16/umd/react.development.js"></script> | |
<script src="https://unpkg.com/react-dom@@16/umd/react-dom.development.js"></script> | |
</body> | |
</html> | |
@* ------------------------------------*@ | |
<input type="hidden" id="csrfToken" value="@helper.CSRF.getToken.value"> | |
<input type="hidden" id="validateRoute" value="@routes.TaskList3.validate()"> | |
<input type="hidden" id="tasksRoute" value="@routes.TaskList3.taskList()"> | |
<input type="hidden" id="createRoute" value="@routes.TaskList3.createUser()"> | |
<input type="hidden" id="deleteRoute" value="@routes.TaskList3.delete()"> | |
<input type="hidden" id="addRoute" value="@routes.TaskList3.addTask()"> | |
<div id="react-root"></div> | |
<script src="@routes.Assets.versioned("javascripts/version4.js")"></script> | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@()(implicit request: RequestHeader, flash: Flash) | |
@main("Task List (Version 4)") { | |
<div id="contents"></div> | |
@* ------------------------------------*@ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Login Activity Control API</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!--===============================================================================================--> | |
<link rel="icon" type="@routes.Assets.versioned("version1/login_v1/image/png")" href="@routes.Assets.versioned("version1/login_v1/images/icons/favicon.ico")"/> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/css/bootstrap.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/fonts/font-awesome-4.7.0/css/font-awesome.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/fonts/Linearicons-Free-v1.0.0/icon-font.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/animate/animate.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/css-hamburgers/hamburgers.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/vendor/select2/select2.min.css")"> | |
<!--===============================================================================================--> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/css/util.css")"> | |
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("version1/login_v1/css/main.css")"> | |
<!--===============================================================================================--> | |
</head> | |
<body> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/jquery/jquery-3.2.1.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/js/popper.js")"></script> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/bootstrap/js/bootstrap.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/vendor/select2/select2.min.js")"></script> | |
<!--===============================================================================================--> | |
<script src="@routes.Assets.versioned("version1/login_v1/js/main.js")"></script> | |
<script src="https://unpkg.com/react@@16/umd/react.development.js"></script> | |
<script src="https://unpkg.com/react-dom@@16/umd/react-dom.development.js"></script> | |
</body> | |
</html> | |
@* ------------------------------------*@ | |
<input type="hidden" id="csrfToken" value="@helper.CSRF.getToken.value"> | |
<input type="hidden" id="validateRoute" value="@routes.TaskList5.validate()"> | |
<input type="hidden" id="tasksRoute" value="@routes.TaskList5.taskList()"> | |
<input type="hidden" id="createRoute" value="@routes.TaskList5.createUser()"> | |
<input type="hidden" id="deleteRoute" value="@routes.TaskList5.delete()"> | |
<input type="hidden" id="addRoute" value="@routes.TaskList5.addTask()"> | |
<input type="hidden" id="logoutRoute" value="@routes.TaskList5.logout()"> | |
<div id="react-root"></div> | |
<script src="@routes.Assets.versioned("javascripts/version5.js")"></script> | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@(message: String, style: String = "scala") | |
@defining(play.core.PlayVersion.current) { version => | |
<section id="top"> | |
<div class="wrapper"> | |
<h1><a href="https://playframework.com/documentation/@version/Home">@message</a></h1> | |
</div> | |
</section> | |
<div id="content" class="wrapper doc"> | |
<article> | |
<h1>Welcome to Play</h1> | |
<p> | |
Congratulations, you’ve just created a new Play application. This page will help you with the next few steps. | |
</p> | |
<blockquote> | |
<p> | |
You’re using Play @version | |
</p> | |
</blockquote> | |
<h2>Why do you see this page?</h2> | |
<p> | |
The <code>conf/routes</code> file defines a route that tells Play to invoke the <code>HomeController.index</code> action | |
whenever a browser requests the <code>/</code> URI using the GET method: | |
</p> | |
<pre><code># Home page | |
GET / controllers.HomeController.index</code></pre> | |
<p> | |
Play has invoked the <code>controllers.HomeController.index</code> method to obtain the <code>Action</code> to execute: | |
</p> | |
<pre><code>def index = Action { implicit request: Request[AnyContent] => | |
Ok(views.html.index("Your new application is ready!")) | |
}</code></pre> | |
<p> | |
An action is a function that handles the incoming HTTP request, and returns the HTTP result to send back to the web client. | |
Here we send a <code>200 OK</code> response, using a template to fill its content. | |
</p> | |
<p> | |
The template is defined in the <code>app/views/index.scala.html</code> file and compiled as a Scala function. | |
</p> | |
<pre><code>@@(message: String) | |
@@main("Welcome to Play") { | |
@@welcome(message) | |
}</code></pre> | |
<p> | |
The first line of the template defines the function signature. Here it just takes a single <code>String</code> parameter. | |
This template then calls another function defined in <code>app/views/main.scala.html</code>, which displays the HTML | |
layout, and another function that displays this welcome message. You can freely add any HTML fragment mixed with Scala | |
code in this file. | |
</p> | |
<p>You can read more about <a href="https://www.playframework.com/documentation/@version/ScalaTemplates">Twirl</a>, the template language used by Play, and how Play handles <a href="https://www.playframework.com/documentation/@version/ScalaActions">actions</a>.</p> | |
<h2>Async Controller</h2> | |
Now that you've seen how Play renders a page, take a look at <code>AsyncController.scala</code>, which shows how to do asynchronous programming when handling a request. The code is almost exactly the same as <code>HomeController.scala</code>, but instead of returning <code>Result</code>, the action returns <code>Future[Result]</code> to Play. When the execution completes, Play can use a thread to render the result without blocking the thread in the mean time. | |
<p> | |
<a href="@routes.AsyncController.message">Click here for the AsyncController action!</a> | |
</p> | |
<p> | |
You can read more about <a href="https://www.playframework.com/documentation/@version/ScalaAsync">asynchronous actions</a> in the documentation. | |
</p> | |
<h2>Count Controller</h2> | |
<p> | |
Both the HomeController and AsyncController are very simple, and typically controllers present the results of the interaction of several services. As an example, see the <code>CountController</code>, which shows how to inject a component into a controller and use the component when handling requests. The count controller increments every time you click on it, so keep clicking to see the numbers go up. | |
</p> | |
<p> | |
<a href="@routes.CountController.count">Click here for the CountController action!</a> | |
</p> | |
<p> | |
You can read more about <a href="https://www.playframework.com/documentation/@version/ScalaDependencyInjection">dependency injection</a> in the documentation. | |
</p> | |
<h2>Need more info on the console?</h2> | |
<p> | |
For more information on the various commands you can run on Play, i.e. running tests and packaging applications for production, see <a href="https://playframework.com/documentation/@version/PlayConsole">Using the Play console</a>. | |
</p> | |
<h2>Need to set up an IDE?</h2> | |
<p> | |
You can start hacking your application right now using any text editor. Any changes will be automatically reloaded at each page refresh, | |
including modifications made to Scala source files. | |
</p> | |
<p> | |
If you want to set-up your application in <strong>IntelliJ IDEA</strong> or any other Java IDE, check the | |
<a href="https://www.playframework.com/documentation/@version/IDE">Setting up your preferred IDE</a> page. | |
</p> | |
<h2>Need more documentation?</h2> | |
<p> | |
Play documentation is available at <a href="https://www.playframework.com/documentation/@version">https://www.playframework.com/documentation</a>. | |
</p> | |
<p> | |
Play comes with lots of example templates showcasing various bits of Play functionality at <a href="https://www.playframework.com/download#examples">https://www.playframework.com/download#examples</a>. | |
</p> | |
<h2>Need more help?</h2> | |
<p> | |
Play questions are asked and answered on Stackoverflow using the "playframework" tag: <a href="https://stackoverflow.com/questions/tagged/playframework">https://stackoverflow.com/questions/tagged/playframework</a> | |
</p> | |
<p> | |
The <a href="https://groups.google.com/group/play-framework">Play Google Group</a> is where Play users come to seek help, | |
announce projects, and discuss issues and new features. If you don’t have a Google account, you can still join the mailing | |
list by sending an e-mail to | |
<strong>play-framework+subscribe@@googlegroups.com</strong>. | |
</p> | |
<p> | |
Gitter is a real time chat channel, like IRC. The <a href="https://gitter.im/playframework/playframework">playframework/playframework</a> channel is used by Play users to discuss the ins and outs of writing great Play applications. | |
</p> | |
</article> | |
<aside> | |
<h3>Browse</h3> | |
<ul> | |
<li><a href="https://playframework.com/documentation/@version">Documentation</a></li> | |
<li><a href="https://playframework.com/documentation/@version/api/@style/index.html">Browse the @{style.capitalize} API</a></li> | |
</ul> | |
<h3>Start here</h3> | |
<ul> | |
<li><a href="https://playframework.com/documentation/@version/PlayConsole">Using the Play console</a></li> | |
<li><a href="https://playframework.com/documentation/@version/IDE">Setting up your preferred IDE</a></li> | |
<li><a href="https://playframework.com/download#examples">Example Projects</a> | |
</ul> | |
<h3>Help here</h3> | |
<ul> | |
<li><a href="https://stackoverflow.com/questions/tagged/playframework">Stack Overflow</a></li> | |
<li><a href="https://groups.google.com/group/play-framework">Mailing List</a></li> | |
<li><a href="https://gitter.im/playframework/playframework">Gitter Channel</a></li> | |
</ul> | |
</aside> | |
</div> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment