blob: a0af915209cd33df05aaa6719e48c13b32f81fa5 [file] [log] [blame]
package org.apache.esme.model
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import net.liftweb._
import mapper._
import util.Mailer.From
import scala.xml.{Node, Elem, NodeSeq, Text}
import sitemap._
import util._
import common._
import openid._
import http._
import js._
import JsCmds._
import Helpers._
import sitemap._
import Loc._
import org.openid4java.discovery.Identifier
import org.openid4java.consumer._
import org.openid4java.util._
import org.apache.esme._
import org.apache.esme.actor._
import java.net.URL
import java.util.logging._
//import com.twitter.stats.Stats
import com.twitter.ostrich.Stats
object User extends User with KeyedMetaMapper[Long, User] with Loggable {
override def afterSave = profileChanged _ :: notifyActors _ :: super.afterSave
override def afterCreate = createCryptoRecord _ :: super.afterCreate
private def notifyActors(in: User) {
Distributor ! Distributor.UserUpdated(in.id)
}
private def profileChanged(in: User) {
if (!in.needsChange_?)
Message.create.author(in.id).
when(Helpers.timeNow.getTime).
source("profile").
setTextAndTags(S.?("base_user_msg_change", in.nickname, in.wholeName, in.imageUrl), Nil, Empty).
foreach{ msg =>
if (msg.save) {
Distributor ! Distributor.AddMessageToMailbox(in.id, msg, ProfileReason(in.id))
}
}
}
private def createCryptoRecord(user: User) {
UserCryptoSig.create.user(user).save
}
def findFromWeb(uid: String): Box[User] =
User.find(By(User.nickname, uid)) or User.find(uid)
override def dbTableName = "users" // define the DB table name
def signupForm(xhtml: NodeSeq): NodeSeq = {
val from = S.referer openOr "/"
val user = User.create
val auth = UserAuth.defaultAuthModule.createHolder()
val snippetName = S.invokedAs
def processEntryAdd() {
logger.debug("processEntryAdd: " + firstName + ", " + lastName)
}
def doSubmit() {
S.mapSnippet(snippetName, genForm)
user.validate ::: auth.validate match {
case Nil =>
user.save
auth.save(user)
User.logUserIn(user)
S.notice(S.?("base_user_msg_welcome", user.niceName))
S.redirectTo(from)
case fe =>
S.error(fe)
}
}
def genForm(xhtml: NodeSeq): NodeSeq = {
bind("signup", xhtml,
"nickname" -%> user.nickname.toForm,
"firstname" -%> user.firstName.toForm,
"lastname" -%> user.lastName.toForm,
"image_url" -%> user.imageUrl.toForm,
"timezone" -%> user.timezone.toForm,
"locale" -%> user.locale.toForm,
"credentials" -> auth.toForm,
"submit" -%> SHtml.submit(S.?("base_user_ui_signup"), doSubmit _)
) ++ SHtml.hidden(() => { doSubmit})
}
genForm(xhtml)
}
def followerIdsForUserId(userId: Long): List[Long] =
Relationship.findAll(By(Relationship.target, userId)).map(_.owner.is)
/**
* Create and populate a new user
*/
def createAndPopulate: User = {
val user = this.create.
nickname("change"+Helpers.randomInt(1000000000)).firstName("Unknown").
lastName("Unknown").
// email(Helpers.randomInt(100000000)+"unknown@unknown.com").
saveMe
// create a crypto record for the user
UserCryptoSig.create.user(user).save
user
}
def findByNickname(str: String): List[User] =
findAll(By(nickname, str))
var onLogIn: List[User => Unit] = List(ExtSession.userDidLogin _)
var onLogOut: List[Box[User] => Unit] = List(ExtSession.userDidLogout _)
def loggedIn_? : Boolean = currentUserId.isDefined
def logUserIdIn(id: String) {
curUser.remove()
curUserId(Full(id))
}
def logUserIn(who: User) {
curUser.remove()
curUserId(Full(who.id.toString))
onLogIn.foreach(_(who))
Message.create.author(who.id).
when(Helpers.timeNow.getTime).
source("login").
setTextAndTags(S.?("base_user_msg_login", who.nickname), Nil, Empty).
foreach{ msg =>
if (msg.save) {
Distributor ! Distributor.AddMessageToMailbox(who.id, msg, LoginReason(who.id))
}
}
Stats incr "usersLoggedIn"
}
def logoutCurrentUser = logUserOut()
def logUserOut() {
onLogOut.foreach(_(curUser))
curUserId.remove()
curUser.remove()
S.request.foreach(_.request.session.terminate)
//Stats getCounter "usersLoggedIn" incr -1
Stats incr ("usersLoggedIn", -1)
}
private object curUserId extends SessionVar[Box[String]](Empty)
def currentUserId: Box[String] = curUserId.is
private object curUser extends RequestVar[Box[User]](currentUserId.flatMap(id => getSingleton.find(id)))
private object currentRole extends SessionVar[Box[String]](currentUser.flatMap(u => Props.get("role."+u.niceName)))
def setRole(role : String) = {
logger.debug("User.setRole() is being called. Role: '%s'".format(role))
currentRole.set(Full(role))
}
def checkRole(role: String): Boolean = {
val userRole:String = currentRole.openOr("")
logger.debug("User.checkRole() is being called. Role to check: '%s'. Current role: '%s'".format(role, userRole))
userRole.equals(role)
}
def currentUser: Box[User] = curUser.is
def getNickname(userId: Long) = {
User.find(userId) match {
case Full(u : User) => u.nickname.is
case _ => "ERROR"
}
}
}
/**
* An O-R mapped "User" class that includes first name, last name, password
*/
class User extends KeyedMapper[Long, User] with UserIdAsString with ManyToMany {// OpenIDProtoUser[User] {
import S._
def getSingleton = User // what's the "meta" server
object imageUrl extends MappedString(this, 256)
def authTokens: List[AuthToken] =
AuthToken.findAll(By(AuthToken. user, this),
OrderBy(AuthToken.description, Ascending))
override lazy val toXml =
<user id={id.toString} nickname={niceName} image={image} whole_name={wholeName} />
def follow(who: User): Boolean = {
if (who == this) false
else
Relationship.find(By(Relationship.owner, this),
By(Relationship.target, who)) match {
case Full(x) => true
case Empty =>
if (Relationship.create.owner(this).target(who).save)
Message.create.author(who.id).
when(Helpers.timeNow.getTime).
source("followed").
setTextAndTags("User @" + this.nickname + " followed @" + who.nickname, Nil, Empty).
foreach { msg =>
if (msg.save) {
Distributor ! Distributor.AddMessageToMailbox(who.id, msg, FollowedReason(this.id))
}
}
true
case _ => false
}
}
def unfollow(who: User): Boolean = {
Relationship.findAll(By(Relationship.owner, this),
By(Relationship.target, who)).foreach{ r =>
if (r.delete_!) Message.create.author(who.id).
when(Helpers.timeNow.getTime).
source("unfollowed").
setTextAndTags("User @" + this.nickname + " unfollowed @" + who.nickname, Nil, Empty).
foreach{ msg =>
if (msg.save) {
Distributor ! Distributor.AddMessageToMailbox(who.id, msg, UnfollowedReason(this.id))
}
}
}
true
}
def following_?(who: User): Boolean =
Relationship.find(By(Relationship.owner, this),
By(Relationship.target, who)).isDefined
def following(): List[User] =
User.findAll(In.fk(Relationship.target, By(Relationship.owner, this)))
def followers(): List[User] =
User.findAll(In.fk(Relationship.owner, By(Relationship.target, this)))
def wholeName: String = (firstName.is, lastName.is) match {
case (f, l) if f.length > 1 && l.length > 1 => f+" "+l
case (f, _) if f.length > 1 => f
case (_, l) if l.length > 1 => l
case (_, _) => niceName
}
/**
* URL to the image that the user has provided
*/
def image_url: String = (imageUrl.is) match {
case (f) if f.length > 1 => f
case (_) => LiftRules.context.path + "/images/avatar.jpg"
}
def needsChange_? : Boolean = this.nickname.is.startsWith("chang") &&
this.firstName.startsWith("Unkn") && this.lastName.startsWith("Unkn")
def image: Option[NodeSeq] = tryo(Text((new URL(imageUrl)).toString)).toOption
def tracking: List[Tracking] =
Tracking.findAll(By(Tracking.user, this),
By(Tracking.disabled, false),
By(Tracking.removed, false),
OrderBy(Tracking.id, Ascending))
def performing: List[Action] =
Action.findAll(By(Action.user, this),
By(Action.disabled, false),
By(Action.removed, false),
OrderBy(Action.id, Ascending))
def performingwithdisabled: List[Action] =
Action.findAll(By(Action.user, this),
By(Action.removed, false),
OrderBy(Action.id, Ascending))
override def primaryKeyField = id
// the primary key for the database
object id extends MappedLongIndex(this)
def userIdAsString: String = id.is.toString
// First Name
object firstName extends MappedString(this, 32) {
override def displayName = fieldOwner.firstNameDisplayName
override val fieldId = Some(Text("txtFirstName"))
}
def firstNameDisplayName = S.?("base_user_ui_first_name")
// Last Name
object lastName extends MappedString(this, 32) {
override def displayName = fieldOwner.lastNameDisplayName
override val fieldId = Some(Text("txtLastName"))
}
def lastNameDisplayName = S.?("base_user_ui_last_name")
def emailDisplayName = S.?("base_user_ui_email")
// Password
object superUser extends MappedBoolean(this) {
override def defaultValue = false
}
def shortName: String = (firstName.is, lastName.is) match {
case (f, l) if f.length > 1 && l.length > 1 => f+" "+l
case (f, _) if f.length > 1 => f
case (_, l) if l.length > 1 => l
case _ => nickname // email.is
}
def image_link = <img src={""+image_url} width="30px" />
object uniqueId extends MappedUniqueId(this, 32) {
override def dbIndexed_? = true
override def writePermission_? = true
}
object validated extends MappedBoolean[User](this) {
override def defaultValue = false
override val fieldId = Some(Text("txtValidated"))
}
object locale extends MappedLocale[User](this) {
override def displayName = fieldOwner.localeDisplayName
override val fieldId = Some(Text("txtLocale"))
}
object timezone extends MappedTimeZone[User](this) {
override def displayName = fieldOwner.timezoneDisplayName
override val fieldId = Some(Text("txtTimeZone"))
}
object nickname extends MappedPoliteString(this, 64) {
override def dbIndexed_? = true
def deDupUnderscore(in: String): String = in.indexOf("__") match {
case -1 => in
case pos => deDupUnderscore(in.substring(0, pos)+in.substring(pos + 1))
}
override def setFilter = notNull _ :: toLower _ :: trim _ ::
deDupUnderscore _ :: super.setFilter
private def validateNickname(str: String): List[FieldError] = {
val others = getSingleton.findByNickname(str).
// getSingleton.findAll(By(getSingleton.nickname, str)).
filter(_.id.is != fieldOwner.id.is)
others.map{u =>
val msg = S.?("base_user_err_duplicate_nickname", str)
S.error(msg)
FieldError(this, Text(msg))
}
}
private def validText(str: String): List[FieldError] =
if (ValidNickName(str)) Nil
else List(FieldError(this,
<xml:group>Invalid nickname. Must start with
a letter and contain only letters,
numbers or "_"</xml:group>))
override def validations = validText _ :: validateNickname _ :: super.validations
}
def niceName: String = nickname
def timezoneDisplayName = S.?("base_user_ui_timezone")
def localeDisplayName = S.?("base_user_ui_locale")
object tagsfollowing extends MappedManyToMany(UserTagFollow, UserTagFollow.user, UserTagFollow.tag, Tag)
object convsfollowing extends MappedManyToMany(UserConvFollow, UserConvFollow.user, UserConvFollow.conversation, Message)
}