user configurable profile wip
parent
63fd5e3cbf
commit
a9129ef30d
|
@ -0,0 +1,68 @@
|
|||
package controllers
|
||||
|
||||
import play.api.mvc._, Results._
|
||||
|
||||
import lila.app._
|
||||
import lila.common.LilaCookie
|
||||
import lila.db.api.$find
|
||||
import lila.security.Permission
|
||||
import lila.user.tube.userTube
|
||||
import lila.user.{ Context, User ⇒ UserModel, UserRepo }
|
||||
import views._
|
||||
|
||||
object Account extends LilaController {
|
||||
|
||||
private def env = Env.user
|
||||
private def forms = lila.user.DataForm
|
||||
|
||||
def profile = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.account.profile(me, forms.profile)).fuccess
|
||||
}
|
||||
|
||||
def profileApply = AuthBody { implicit ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.body
|
||||
FormFuResult(forms.profile) { err ⇒
|
||||
fuccess(html.account.profile(me, err))
|
||||
} { profile ⇒
|
||||
UserRepo.setProfile(me.id, profile) inject Redirect(routes.User show me.username)
|
||||
}
|
||||
}
|
||||
|
||||
def passwd = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.account.passwd(me, forms.passwd)).fuccess
|
||||
}
|
||||
|
||||
def passwdApply = AuthBody { implicit ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.body
|
||||
FormFuResult(forms.passwd) { err ⇒
|
||||
fuccess(html.account.passwd(me, err))
|
||||
} { passwd ⇒
|
||||
for {
|
||||
ok ← UserRepo.checkPassword(me.id, passwd.oldPasswd)
|
||||
_ ← ok ?? UserRepo.passwd(me.id, passwd.newPasswd1)
|
||||
} yield ok.fold(
|
||||
Redirect(routes.User show me.username),
|
||||
BadRequest(html.account.passwd(me, forms.passwd))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def close = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.account.close(me)).fuccess
|
||||
}
|
||||
|
||||
def closeConfirm = Auth { ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.req
|
||||
(UserRepo disable me.id) >>
|
||||
Env.team.api.quitAll(me.id) >>
|
||||
(Env.security disconnect me.id) inject {
|
||||
Redirect(routes.User show me.username) withCookies LilaCookie.newSession
|
||||
}
|
||||
}
|
||||
}
|
|
@ -100,51 +100,6 @@ object User extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def setBio = AuthBody { ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.body
|
||||
forms.bio.bindFromRequest.fold(
|
||||
f ⇒ fulogwarn(f.errors.toString) inject ~me.bio,
|
||||
bio ⇒ UserRepo.setBio(me.id, bio) inject bio
|
||||
) map { Ok(_) }
|
||||
}
|
||||
|
||||
def passwd = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.user.passwd(me, forms.passwd)).fuccess
|
||||
}
|
||||
|
||||
def passwdApply = AuthBody { implicit ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.body
|
||||
FormFuResult(forms.passwd) { err ⇒
|
||||
fuccess(html.user.passwd(me, err))
|
||||
} { passwd ⇒
|
||||
for {
|
||||
ok ← UserRepo.checkPassword(me.id, passwd.oldPasswd)
|
||||
_ ← ok ?? UserRepo.passwd(me.id, passwd.newPasswd1)
|
||||
} yield ok.fold(
|
||||
Redirect(routes.User show me.username),
|
||||
BadRequest(html.user.passwd(me, forms.passwd))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def close = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.user.close(me)).fuccess
|
||||
}
|
||||
|
||||
def closeConfirm = Auth { ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.req
|
||||
(UserRepo disable me.id) >>
|
||||
Env.team.api.quitAll(me.id) >>
|
||||
(Env.security disconnect me.id) inject {
|
||||
Redirect(routes.User show me.username) withCookies LilaCookie.newSession
|
||||
}
|
||||
}
|
||||
|
||||
def export(username: String) = Open { implicit ctx ⇒
|
||||
OptionFuResult(UserRepo named username) { u ⇒
|
||||
Env.game export u map { url ⇒
|
||||
|
|
|
@ -11,7 +11,7 @@ robots = false) {
|
|||
<p class="explanation">
|
||||
Are you sure you want to close your account? You will no longer be able to login!
|
||||
</p>
|
||||
<form action="@routes.User.closeConfirm" method="POST">
|
||||
<form action="@routes.Account.closeConfirm" method="POST">
|
||||
<br /><br />
|
||||
<a href="@routes.User.show(u.username)">
|
||||
I changed my mind, don't close my account
|
|
@ -13,7 +13,7 @@ evenMoreCss = evenMoreCss) {
|
|||
<div class="content_box small_box">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">Change your password</h1>
|
||||
<form action="@routes.User.passwdApply" method="POST">
|
||||
<form action="@routes.Account.passwdApply" method="POST">
|
||||
<ul>
|
||||
@user.passwdFormField(form("oldPasswd"), "Current password")
|
||||
@user.passwdFormField(form("newPasswd1"), "New password")
|
|
@ -0,0 +1,31 @@
|
|||
@(u: User, form: Form[_])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s profile".format(u.username) }
|
||||
|
||||
@evenMoreCss = {
|
||||
@cssTag("user-profile.css")
|
||||
}
|
||||
|
||||
@user.layout(
|
||||
title = title,
|
||||
evenMoreCss = evenMoreCss) {
|
||||
<div class="content_box small_box">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">Edit your profile</h1>
|
||||
<form action="@routes.Account.profileApply" method="POST">
|
||||
<ul>
|
||||
@user.passwdFormField(form("firstName"), "First name")
|
||||
@user.passwdFormField(form("lastName"), "Last name")
|
||||
@user.passwdFormField(form("bio"), "Biography")
|
||||
@user.passwdFormField(form("country"), "Country")
|
||||
<li>
|
||||
@errMsg(form)
|
||||
</li>
|
||||
<li>
|
||||
<input type="submit" class="submit" value="Apply changes" />
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -7,33 +7,10 @@
|
|||
@jsTagCompiled("chart2.js")
|
||||
}
|
||||
|
||||
@evenMoreCss = {
|
||||
@if(ctx is u) {
|
||||
@cssTag("user-edit.css")
|
||||
}
|
||||
}
|
||||
|
||||
@bio = {
|
||||
@if(ctx is u) {
|
||||
<div class="user_bio">
|
||||
<div class="editable">@shorten(u.bio | "Click here to tell about yourself", 400)</div>
|
||||
<form action="@routes.User.setBio">
|
||||
<textarea name="bio"></textarea>
|
||||
<button class="button apply">@trans.apply()</button>
|
||||
<button class="button cancel">@trans.cancel()</button>
|
||||
</form>
|
||||
</div>
|
||||
} else {
|
||||
@u.nonEmptyBio.map { bio =>
|
||||
<div class="user_bio">@shorten(bio, 400)</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@actions = {
|
||||
@if(ctx is u) {
|
||||
<a class="small action" href="@routes.User.passwd">Change password</a>
|
||||
<a class="small action" href="@routes.User.close">Close account</a>
|
||||
<a class="small action" href="@routes.Account.passwd">Change password</a>
|
||||
<a class="small action" href="@routes.Account.close">Close account</a>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,8 +41,7 @@
|
|||
title = title,
|
||||
goodies = goodies.some,
|
||||
robots = false,
|
||||
evenMoreJs = evenMoreJs,
|
||||
evenMoreCss = evenMoreCss) {
|
||||
evenMoreJs = evenMoreJs) {
|
||||
<div class="content_box no_padding user_show">
|
||||
<div class="content_box_top">
|
||||
<div class="icon status @{isOnline(u.id).??("connected")}"></div>
|
||||
|
@ -76,11 +52,11 @@ evenMoreCss = evenMoreCss) {
|
|||
}
|
||||
@if(u.disabled) {
|
||||
<span class="staff">CLOSED</span>
|
||||
@if(isGranted(_.ReopenAccount)) {
|
||||
<form class="reopen" action="@routes.Mod.reopenAccount(u.username)" method="post">
|
||||
<button type="submit" class="button confirm">Reopen</button>
|
||||
</form>
|
||||
}
|
||||
@if(isGranted(_.ReopenAccount)) {
|
||||
<form class="reopen" action="@routes.Mod.reopenAccount(u.username)" method="post">
|
||||
<button type="submit" class="button confirm">Reopen</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="social content_box_inter">
|
||||
|
@ -118,7 +94,9 @@ evenMoreCss = evenMoreCss) {
|
|||
@if(u.engine && ctx.me.fold(true)(u !=)) {
|
||||
<div class="engine_warning">@trans.thisPlayerUsesChessComputerAssistance()</div>
|
||||
}
|
||||
@bio
|
||||
@u.profileOrDefault.nonEmptyBio.map { bio =>
|
||||
<div class="user_bio">@shorten(bio, 400)</div>
|
||||
}
|
||||
@info.confrontation.map { c =>
|
||||
@user.confrontation(c)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
var usersToMigrate = db.user2.find();
|
||||
var collection = db.user3;
|
||||
|
||||
print("Migrating " + usersToMigrate.count() + " users");
|
||||
|
||||
collection.drop();
|
||||
|
||||
function nn(x) {
|
||||
return (x | '') !== '';
|
||||
}
|
||||
|
||||
usersToMigrate.forEach(function(u) {
|
||||
|
||||
if ((u.bio | '') !== '') u.profile = {bio: u.bio};
|
||||
delete u.bio;
|
||||
|
||||
collection.insert(u);
|
||||
});
|
||||
|
||||
print("Done!");
|
14
conf/routes
14
conf/routes
|
@ -23,14 +23,18 @@ GET /@/:username/mod controllers.User.mod(username: String)
|
|||
GET /@/:username/mini controllers.User.showMini(username: String)
|
||||
GET /@/:username/:filterName controllers.User.showFilter(username: String, filterName: String, page: Int ?= 1)
|
||||
GET /@/:username controllers.User.show(username: String)
|
||||
|
||||
GET /people controllers.User.list(page: Int ?= 1)
|
||||
GET /people/online controllers.User.online
|
||||
GET /people/autocomplete controllers.User.autocomplete
|
||||
PUT /account/bio controllers.User.setBio
|
||||
GET /account/passwd controllers.User.passwd
|
||||
POST /account/passwd controllers.User.passwdApply
|
||||
GET /account/close controllers.User.close
|
||||
POST /account/closeConfirm controllers.User.closeConfirm
|
||||
|
||||
# Account
|
||||
GET /account/passwd controllers.Account.passwd
|
||||
POST /account/passwd controllers.Account.passwdApply
|
||||
GET /account/close controllers.Account.close
|
||||
POST /account/closeConfirm controllers.Account.closeConfirm
|
||||
GET /account/profile controllers.Account.profile
|
||||
POST /account/profile controllers.Account.profileApply
|
||||
|
||||
#Site
|
||||
GET /socket controllers.Main.websocket
|
||||
|
|
|
@ -201,4 +201,6 @@ object Countries {
|
|||
"zm" -> "Zambia",
|
||||
"zw" -> "Zimbabwe",
|
||||
"zz" -> "World")
|
||||
|
||||
val codeSet = all.keySet
|
||||
}
|
||||
|
|
|
@ -9,6 +9,15 @@ object DataForm {
|
|||
"bio" -> text(maxLength = 400)
|
||||
))
|
||||
|
||||
val profile = Form(mapping(
|
||||
"firstName" -> nameField,
|
||||
"lastName" -> nameField,
|
||||
"country" -> optional(nonEmptyText.verifying(Countries.codeSet contains _)),
|
||||
"bio" -> optional(nonEmptyText(maxLength = 400))
|
||||
)(Profile.apply)(Profile.unapply))
|
||||
|
||||
private def nameField = optional(nonEmptyText(minLength = 2, maxLength = 20))
|
||||
|
||||
val theme = Form(single(
|
||||
"theme" -> nonEmptyText.verifying(Theme contains _)
|
||||
))
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package lila.user
|
||||
|
||||
import scala._
|
||||
|
||||
case class Profile(
|
||||
firstName: Option[String] = None,
|
||||
lastName: Option[String] = None,
|
||||
bio: Option[String] = None,
|
||||
country: Option[String] = None) {
|
||||
|
||||
def realName = (firstName |@| lastName) apply { _ + " " + _ }
|
||||
|
||||
def nonEmptyBio = bio filter (_.nonEmpty)
|
||||
|
||||
def nonEmpty = List(
|
||||
firstName, lastName, bio, country
|
||||
).flatten.nonEmpty option this
|
||||
}
|
||||
|
||||
object Profile {
|
||||
|
||||
val default = Profile()
|
||||
|
||||
import lila.db.Tube
|
||||
import play.api.libs.json._
|
||||
|
||||
private[user] lazy val tube = Tube[Profile](
|
||||
Json.reads[Profile],
|
||||
Json.writes[Profile])
|
||||
}
|
|
@ -16,8 +16,7 @@ case class User(
|
|||
enabled: Boolean,
|
||||
roles: List[String],
|
||||
settings: Map[String, String] = Map.empty,
|
||||
bio: Option[String] = None,
|
||||
country: Option[String] = None,
|
||||
profile: Option[Profile] = None,
|
||||
engine: Boolean = false,
|
||||
toints: Int = 0,
|
||||
createdAt: DateTime,
|
||||
|
@ -41,7 +40,7 @@ case class User(
|
|||
|
||||
def setting(name: String): Option[Any] = settings get name
|
||||
|
||||
def nonEmptyBio = bio filter ("" !=)
|
||||
def profileOrDefault = profile | Profile.default
|
||||
|
||||
def hasGames = count.game > 0
|
||||
|
||||
|
@ -70,6 +69,7 @@ object User {
|
|||
private implicit def countTube = Count.tube
|
||||
private implicit def speedElosTube = SpeedElos.tube
|
||||
private implicit def variantElosTube = VariantElos.tube
|
||||
private implicit def profileTube = Profile.tube
|
||||
|
||||
private[user] lazy val tube = Tube[User](
|
||||
(__.json update (
|
||||
|
|
|
@ -66,6 +66,14 @@ object UserRepo {
|
|||
|
||||
def setEloOnly(id: ID, elo: Int): Funit = $update($select(id), $set("elo" -> elo))
|
||||
|
||||
def setProfile(id: ID, profile: Profile): Funit = {
|
||||
import tube.profileTube
|
||||
profile.nonEmpty match {
|
||||
case Some(p) => $update($select(id), $set("profile" -> p))
|
||||
case None => $update($select(id), $unset("profile"))
|
||||
}
|
||||
}
|
||||
|
||||
val enabledSelect = Json.obj("enabled" -> true)
|
||||
val noEngineSelect = Json.obj("engine" -> $ne(true))
|
||||
val goodLadQuery = $query(enabledSelect ++ noEngineSelect)
|
||||
|
@ -163,8 +171,6 @@ object UserRepo {
|
|||
|
||||
def setRoles(id: ID, roles: List[String]) = $update.field(id, "roles", roles)
|
||||
|
||||
def setBio(id: ID, bio: String) = $update.field(id, "bio", bio)
|
||||
|
||||
def setSpeedElos(id: ID, ses: SpeedElos) = {
|
||||
import tube.speedElosTube
|
||||
$update.field(id, "speedElos", ses)
|
||||
|
|
|
@ -11,6 +11,7 @@ package object user extends PackageObject with WithPlay {
|
|||
|
||||
private[user] implicit lazy val speedElosTube = SpeedElos.tube
|
||||
private[user] implicit lazy val variantElosTube = VariantElos.tube
|
||||
private[user] implicit lazy val profileTube = Profile.tube
|
||||
|
||||
private[user] implicit lazy val historyTube =
|
||||
Tube.json inColl Env.current.historyColl
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
$(function() {
|
||||
|
||||
if($searchForm = $('form.search_user_form').orNot()) {
|
||||
var $searchForm = $('form.search_user_form');
|
||||
|
||||
if($searchForm.length) {
|
||||
$searchInput = $searchForm.find('input.search_user');
|
||||
$searchInput.on('autocompleteselect', function(e, ui) {
|
||||
setTimeout(function() {$searchForm.submit();},10);
|
||||
|
@ -20,33 +22,6 @@ $(function() {
|
|||
return false;
|
||||
});
|
||||
|
||||
$('div.user_bio .editable').on('click', function() {
|
||||
$editable = $(this);
|
||||
$parent = $(this).parent().addClass('editing');
|
||||
$form = $parent.find('form').show();
|
||||
$form.find('textarea').val($editable.text());
|
||||
function unedit() {
|
||||
$form.find('button.cancel').off('click');
|
||||
$form.off('submit');
|
||||
$parent.removeClass('editing');
|
||||
}
|
||||
$form.find('button.cancel').on('click', function(e) {
|
||||
unedit();
|
||||
return false;
|
||||
});
|
||||
$form.on('submit', function() {
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
type: 'PUT',
|
||||
data: { bio: $form.find('textarea').val() },
|
||||
success: function(t) {
|
||||
$editable.text(t);
|
||||
unedit();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
function str_repeat(input, multiplier) {
|
||||
return new Array(multiplier + 1).join(input);
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
div.user_bio .editable {
|
||||
width: 290px;
|
||||
padding: 5px;
|
||||
border: 1px solid #88aaff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.user_bio .editable:hover {
|
||||
border-color: blue;
|
||||
}
|
||||
|
||||
div.user_bio form {
|
||||
display: none;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.user_bio textarea {
|
||||
width: 285px;
|
||||
padding: 5px;
|
||||
height: 5em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
div.user_bio button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.user_bio.editing .editable {
|
||||
display: none;
|
||||
}
|
||||
div.user_bio.editing form {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
.content_box form {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.content_box form li {
|
||||
display: block;
|
||||
list-style: none outside none;
|
||||
margin: 1em 0;
|
||||
position: relative;
|
||||
}
|
||||
.content_box form label {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
text-align: right;
|
||||
width: 150px;
|
||||
}
|
||||
.content_box form input.passwd {
|
||||
border: 1px solid #D4D4D4;
|
||||
padding: 3px 5px;
|
||||
width: 200px;
|
||||
}
|
||||
.content_box form input.submit {
|
||||
margin-left: 160px;
|
||||
margin-right: 20px;
|
||||
padding: 3px 1em;
|
||||
}
|
||||
form .error {
|
||||
color: red;
|
||||
margin-left: 160px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue