pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

commit f265eaac76131dc6047b0e25a99371c9b07bc589
parent 6eba2233a6272ba726c7fb8a7bffa5cff0c2d45b
Author: Jul <jul@9o.is>
Date:   Thu, 17 Apr 2014 23:20:02 -0400

renamed com.pyd package to inc.pyc

Diffstat:
Mpackage.json | 2+-
Msrc/main/scala/bootstrap/liftweb/Boot.scala | 12++++++------
Dsrc/main/scala/com/pyd/config/ErrorHandler.scala | 64----------------------------------------------------------------
Dsrc/main/scala/com/pyd/config/GoogleAnalytics.scala | 62--------------------------------------------------------------
Dsrc/main/scala/com/pyd/config/MongoConfig.scala | 55-------------------------------------------------------
Dsrc/main/scala/com/pyd/config/Site.scala | 87-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/config/SmtpMailer.scala | 52----------------------------------------------------
Dsrc/main/scala/com/pyd/lib/DependencyFactory.scala | 56--------------------------------------------------------
Dsrc/main/scala/com/pyd/lib/NgUIRouter.scala | 197-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/lib/RogueMetaRecord.scala | 18------------------
Dsrc/main/scala/com/pyd/model/AtmApplication.scala | 65-----------------------------------------------------------------
Dsrc/main/scala/com/pyd/model/NearAtmNotify.scala | 58----------------------------------------------------------
Dsrc/main/scala/com/pyd/model/SearchedPostal.scala | 46----------------------------------------------
Dsrc/main/scala/com/pyd/model/User.scala | 170-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/model/field/USStateField.scala | 41-----------------------------------------
Dsrc/main/scala/com/pyd/snippet/AngularSnips.scala | 86-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/snippet/AtmApplicationSnip.scala | 45---------------------------------------------
Dsrc/main/scala/com/pyd/snippet/ForgotPasswordSnip.scala | 41-----------------------------------------
Dsrc/main/scala/com/pyd/snippet/NearAtmNotifySnip.scala | 49-------------------------------------------------
Dsrc/main/scala/com/pyd/snippet/SearchedPostalSnip.scala | 41-----------------------------------------
Dsrc/main/scala/com/pyd/snippet/Sitemap.scala | 47-----------------------------------------------
Dsrc/main/scala/com/pyd/snippet/UserLoginSnip.scala | 50--------------------------------------------------
Dsrc/main/scala/com/pyd/snippet/UserRegistrationSnip.scala | 46----------------------------------------------
Dsrc/main/scala/com/pyd/snippet/UserScreens.scala | 135-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/snippet/UserSnips.scala | 180-------------------------------------------------------------------------------
Dsrc/main/scala/com/pyd/snippet/UtilSnips.scala | 32--------------------------------
Asrc/main/scala/inc/pyc/config/ErrorHandler.scala | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/config/GoogleAnalytics.scala | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/config/MongoConfig.scala | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/config/Site.scala | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/config/SmtpMailer.scala | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/lib/DependencyFactory.scala | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/lib/NgUIRouter.scala | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/lib/RogueMetaRecord.scala | 18++++++++++++++++++
Asrc/main/scala/inc/pyc/model/AtmApplication.scala | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/NearAtmNotify.scala | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/SearchedPostal.scala | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/User.scala | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/field/USStateField.scala | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/AngularSnips.scala | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/AtmApplicationSnip.scala | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/ForgotPasswordSnip.scala | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/NearAtmNotifySnip.scala | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/SearchedPostalSnip.scala | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/Sitemap.scala | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/UserLoginSnip.scala | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/UserRegistrationSnip.scala | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/UserScreens.scala | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/UserSnips.scala | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/snippet/UtilSnips.scala | 31+++++++++++++++++++++++++++++++
50 files changed, 1733 insertions(+), 1730 deletions(-)

diff --git a/package.json b/package.json @@ -1,5 +1,5 @@ { - "name": "pyd", + "name": "pyc", "version": "0.0.1", "devDependencies": { "grunt": "~0.4.1", diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -9,9 +9,9 @@ import http._ import util._ import util.Helpers._ -import com.pyd.config._ -import com.pyd.model.{SystemUser, User} -import com.pyd.lib.NgUIRouterFactory +import inc.pyc.config._ +import inc.pyc.model.{SystemUser, User} +import inc.pyc.lib.NgUIRouterFactory import net.liftmodules.extras.{Gravatar, LiftExtras} import net.liftmodules.mongoauth.MongoAuth @@ -44,7 +44,7 @@ class Boot extends Loggable { SmtpMailer.init // where to search snippet - LiftRules.addToPackages("com.pyd") + LiftRules.addToPackages("inc.pyc") // set the default htmlProperties LiftRules.htmlProperties.default.set((r: Req) => new Html5Properties(r.userAgent)) @@ -65,7 +65,7 @@ class Boot extends Loggable { LiftRules.early.append(_.setCharacterEncoding("UTF-8")) // set name to generate correct minified js and css files - LiftExtras.artifactName.default.set("pyd-0.0.1") + LiftExtras.artifactName.default.set("pyc-0.0.1") // don't include the liftAjax.js code. It's served statically. LiftRules.autoIncludeAjaxCalc.default.set(() => (session: LiftSession) => false) @@ -77,7 +77,7 @@ class Boot extends Loggable { // Google Analytics GoogleAnalytics.init - LiftRules.statelessDispatch.append(com.pyd.snippet.Sitemap) + LiftRules.statelessDispatch.append(inc.pyc.snippet.Sitemap) } private def prettyPrintMime(m: MimeMessage): String = { diff --git a/src/main/scala/com/pyd/config/ErrorHandler.scala b/src/main/scala/com/pyd/config/ErrorHandler.scala @@ -1,64 +0,0 @@ -package com.pyd -package config - -import model.User - -import net.liftweb._ -import common.{Loggable, MDC} -import http.{Factory, LiftRules, RedirectResponse, Req, S, XhtmlResponse} -import util.Props - -object ErrorHandler extends Factory with Loggable { - // config - val errorUrl = new FactoryMaker[String]("/error") {} // where to send the user when an error occurs - - def init(): Unit = { - LiftRules.exceptionHandler.prepend { - case (Props.RunModes.Development, r, e) => - logException(r, e) - XhtmlResponse( - (<html><body>Exception occured while processing {r.uri}<pre>{showException(e)}</pre></body></html>), - S.htmlProperties.docType, - List("Content-Type" -> "text/html; charset=utf-8"), - Nil, - 500, - S.legacyIeCompatibilityMode - ) - case (_, r, e) => - logException(r, e) - RedirectResponse(errorUrl.vend) - } - } - - /* - * Log the exception with some user info. - */ - def logException(r: Req, e: Throwable) { - import java.net.InetAddress - val srvr = InetAddress.getLocalHost.getHostName - - MDC.put(("UserId", User.currentUserId openOr "GUEST")) - MDC.put(("Username", User.currentUser.map(_.username.get) openOr "GUEST")) - MDC.put(("User Agent", r.userAgent openOr "UNKNOWN")) - MDC.put(("Server", srvr)) - logger.error("Exception occurred while processing %s".format(r.uri), e) - } - - /** - * A utility method to convert an exception to a string of stack traces - * @param le the exception - * - * @return the stack trace - */ - def showException(le: Throwable): String = { - val ret = "Message: " + le.toString + "\n\t" + - le.getStackTrace.map(_.toString).mkString("\n\t") + "\n" - - val also = le.getCause match { - case null => "" - case sub: Throwable => "\nCaught and thrown by:\n" + showException(sub) - } - - ret + also - } -} diff --git a/src/main/scala/com/pyd/config/GoogleAnalytics.scala b/src/main/scala/com/pyd/config/GoogleAnalytics.scala @@ -1,61 +0,0 @@ -package com.pyd.config - -import net.liftweb.util.Props -import net.liftweb.common.Loggable -import net.liftweb.http.js.JsCmd -import net.liftweb.http.Req -import net.liftweb.http.LiftSession -import net.liftweb.http.S - -/* - * Taken from: - * https://github.com/d6y/liftmodules-googleanalytics/blob/master/src/main/scala/bootstrap/liftmodules/GoogleAnalytics.scala - * - * Needed Google Analytics Universal for Async. - * TODO: switch to module after enhancement has been implemented. (issue #4) - * https://github.com/d6y/liftmodules-googleanalytics/issues/4 - */ -object GoogleAnalytics extends Loggable { - - def init: Unit = init( ()⇒true ) - - def init(includeTest: () ⇒ Boolean): Unit = Props.get("google.analytics.id") map Async.headJs foreach { js => - def addTracking(s: LiftSession, r: Req) : Unit = if (includeTest()) S.putInHead(js) - LiftSession.onBeginServicing = addTracking _ :: LiftSession.onBeginServicing - } - - // noticeJs is by-name to allow you to side-effect (eg., set cookies) - def alertUser(cond: () ⇒ Boolean)(noticeJs: ⇒ JsCmd): Unit = { - - def addNotice(s: LiftSession, r: Req) : Unit = try { - if (cond()) S.appendJs(noticeJs) - } catch { - case e : Throwable => logger.error("Unhandled exception from alertUser", e) - } - - LiftSession.onBeginServicing = addNotice _ :: LiftSession.onBeginServicing - } - - object dsl { - object only { - def when(f: => Boolean) = f _ - } - } - - -} - -object Async { - - def headJs(id: String): xml.Elem = xml.XML.loadString(s""" -<script> -(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ -(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), -m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - -ga('create', '$id', 'auto'); -ga('send', 'pageview'); -</script> - """) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/config/MongoConfig.scala b/src/main/scala/com/pyd/config/MongoConfig.scala @@ -1,55 +0,0 @@ -package com.pyd -package config - -import net.liftweb._ -import common._ -import http._ -import json._ -import mongodb._ -import util.Props - -import com.mongodb.{DBAddress, MongoClient} - -object MongoConfig extends Factory with Loggable { - - // configure your MongoMetaRecords to use this. See lib/RogueMetaRecord.scala. - val defaultId = new FactoryMaker[MongoIdentifier](DefaultMongoIdentifier) {} - - def init() { - /** - * First checks for existence of mongo.default.url. If not found, then - * checks for mongo.default.host, port, and name. Uses defaults if those - * are not found. - */ - val defaultDbAddress = Props.get("mongo.default.url") - .map(url => new DBAddress(url)) - .openOr(new DBAddress( - Props.get("mongo.default.host", "127.0.0.1"), - Props.getInt("mongo.default.port", 27017), - Props.get("mongo.default.name", "pyd") - )) - - /* - * If mongo.default.user, and pwd are defined, configure Mongo using authentication. - */ - (Props.get("mongo.default.user"), Props.get("mongo.default.pwd")) match { - case (Full(user), Full(pwd)) => - MongoDB.defineDbAuth( - DefaultMongoIdentifier, - new MongoClient(defaultDbAddress), - defaultDbAddress.getDBName, - user, - pwd - ) - logger.info("MongoDB inited using authentication: %s".format(defaultDbAddress.toString)) - case _ => - MongoDB.defineDb( - DefaultMongoIdentifier, - new MongoClient(defaultDbAddress), - defaultDbAddress.getDBName - ) - logger.info("MongoDB inited: %s".format(defaultDbAddress.toString)) - } - } -} - diff --git a/src/main/scala/com/pyd/config/Site.scala b/src/main/scala/com/pyd/config/Site.scala @@ -1,87 +0,0 @@ -package com.pyd -package config - -import lib.NgUIRouterFactory._ -import model.User - -import net.liftweb._ -import common._ -import http.{S, OkResponse, RedirectResponse, RequestVar} -import sitemap._ -import sitemap.Loc._ -import net.liftmodules.mongoauth.Locs - -object MenuGroups { - val SettingsGroup = LocGroup("settings") - val TopBarGroup = LocGroup("topbar") - val SiteMapGroup = LocGroup("sitemap") -} - -/* - * Wrapper for Menu locations - */ -case class MenuLoc(menu: Menu) { - lazy val url: String = S.contextPath+menu.loc.calcDefaultHref - lazy val fullUrl: String = S.hostAndPath+menu.loc.calcDefaultHref -} - -object Site extends Locs { - import MenuGroups._ - - val domain = "pydcoin.com" - - // locations (for top group) - val home = MenuLoc(Menu.i("Bitcoin ATM Services") / "index" >> TopBarGroup) - val locations = MenuLoc(Menu.i("ATM Locations") / "locations" >> TopBarGroup >> SiteMapGroup) - val whatsBitcoin = MenuLoc(Menu.i("What Is Bitcoin") / "what-is-bitcoin" >> SiteMapGroup) - val about = MenuLoc(Menu.i("About Us") / "about" >> TopBarGroup >> SiteMapGroup) - val blog = MenuLoc(Menu.i("Blog") / "blog" >> TopBarGroup >> RedirectBlog >> SiteMapGroup) - val faqs = MenuLoc(Menu.i("FAQs") / "faqs" >> TopBarGroup >> SiteMapGroup) - val atmHowTo = MenuLoc(Menu.i("How To Use ATM") / "how-to-use-atm" >> SiteMapGroup) - - val loginToken = MenuLoc(buildLoginTokenMenu) - val logout = MenuLoc(buildLogoutMenu) - private val profileParamMenu = Menu.param[User]("User", "Profile", - User.findByUsername _, - _.username.get - ) / "user" >> Loc.CalcValue(() => User.currentUser) - lazy val profileLoc = profileParamMenu.toLoc - - val settings = MenuLoc(Menu.i("Settings") / "settings" >> SettingsGroup >> RequireLoggedIn) - val password = MenuLoc(Menu.i("Password") / "settings" / "password" >> SettingsGroup >> RequireLoggedIn) - - val account = MenuLoc(Menu.i("Account") / "settings" / "account" >> SettingsGroup >> RequireLoggedIn) - val editProfile = MenuLoc(Menu("EditProfile", "Profile") / "settings" / "profile" >> SettingsGroup >> RequireLoggedIn) - - val register = MenuLoc(Menu.i("Register") / "register" >> RequireNotLoggedIn >> SiteMapGroup) - val login = MenuLoc(Menu.i("Login") / "login" >> RequireNotLoggedIn >> SiteMapGroup) - val forgotPassword = MenuLoc(Menu.i("Forgot Password") / "forgot-password" >> RequireNotLoggedIn) - - private def menus = List( - home.menu, - locations.menu, - whatsBitcoin.menu, - about.menu, - blog.menu, - faqs.menu, - login.menu, - register.menu, - atmHowTo.menu, - loginToken.menu, - logout.menu, - forgotPassword.menu, - settings.menu, - password.menu, - Menu.i("Error") / "error" >> Hidden, - Menu.i("404") / "404" >> Hidden, - Menu.i("Status") / "ping" >> Hidden >> CalcStateless(() => true ) >> EarlyResponse(() => Full(OkResponse())), - Menu.i("Throw") / "throw" >> EarlyResponse(() => throw new Exception("This is only a test.")) - ) - - /* - * Return a SiteMap needed for Lift - */ - def siteMap: SiteMap = SiteMap(menus:_*) - - private def RedirectBlog = EarlyResponse(() => Full(RedirectResponse("http://blog."+domain))) -} diff --git a/src/main/scala/com/pyd/config/SmtpMailer.scala b/src/main/scala/com/pyd/config/SmtpMailer.scala @@ -1,52 +0,0 @@ -package com.pyd -package config - -import javax.mail.{Authenticator, PasswordAuthentication} -import javax.mail.internet.MimeMessage - -import net.liftweb._ -import common._ -import util._ - -/* - * A Mailer config object that uses Props and auto configures for gmail - * if detected. - */ -object SmtpMailer extends Loggable { - def init(): Unit = { - - var isAuth = Props.get("mail.smtp.auth", "false").toBoolean - - Mailer.customProperties = Props.get("mail.smtp.host", "localhost") match { - case "smtp.gmail.com" => // auto configure for gmail - isAuth = true - Map( - "mail.smtp.host" -> "smtp.gmail.com", - "mail.smtp.port" -> "587", - "mail.smtp.auth" -> "true", - "mail.smtp.starttls.enable" -> "true" - ) - case h => Map( - "mail.smtp.host" -> h, - "mail.smtp.port" -> Props.get("mail.smtp.port", "25"), - "mail.smtp.auth" -> isAuth.toString - ) - } - - //Mailer.devModeSend.default.set((m : MimeMessage) => logger.info("Sending Mime Message: "+m)) - - if (isAuth) { - (Props.get("mail.smtp.user"), Props.get("mail.smtp.pass")) match { - case (Full(username), Full(password)) => - logger.info("Smtp user: %s".format(username)) - logger.info("Smtp password length: %s".format(password.length)) - Mailer.authenticator = Full(new Authenticator() { - override def getPasswordAuthentication = new - PasswordAuthentication(username, password) - }) - logger.info("SmtpMailer inited") - case _ => logger.error("Username/password not supplied for Mailer.") - } - } - } -} diff --git a/src/main/scala/com/pyd/lib/DependencyFactory.scala b/src/main/scala/com/pyd/lib/DependencyFactory.scala @@ -1,56 +0,0 @@ -package com.pyd -package lib - -import java.util.Date - -import net.liftweb._ -import http._ -import util._ -import common._ - -/** - * A factory for generating new instances of Date. You can create - * factories for each kind of thing you want to vend in your application. - * An example is a payment gateway. You can change the default implementation, - * or override the default implementation on a session, request or current call - * stack basis. - */ -object DependencyFactory extends Factory { - implicit object time extends FactoryMaker(Helpers.now _) - - /** - * objects in Scala are lazily created. The init() - * method creates a List of all the objects. This - * results in all the objects getting initialized and - * registering their types with the dependency injector - */ - private def init() { - List(time) - } - init() -} - -/* -/** - * Examples of changing the implementation - */ -sealed abstract class Changer { - def changeDefaultImplementation() { - DependencyFactory.time.default.set(() => new Date()) - } - - def changeSessionImplementation() { - DependencyFactory.time.session.set(() => new Date()) - } - - def changeRequestImplementation() { - DependencyFactory.time.request.set(() => new Date()) - } - - def changeJustForCall(d: Date) { - DependencyFactory.time.doWith(d) { - // perform some calculations here - } - } -} -*/ diff --git a/src/main/scala/com/pyd/lib/NgUIRouter.scala b/src/main/scala/com/pyd/lib/NgUIRouter.scala @@ -1,196 +0,0 @@ -package com.pyd -package lib - -import scala.xml._ -import net.liftweb._ -import sitemap._ -import common._ -import http._ -import sitemap.Loc.{LocGroup, AnyLocParam} -import util._ -import Helpers._ -import net.liftmodules.extras.SnippetHelper - -/** - * Configure these settings during boot. - */ -object NgUIRouterFactory extends Factory { - - /** - * Ignore the UiRouter Loc Group and add all menus in sitemap. - */ - val html5mode = new FactoryMaker[Boolean](true) {} - - /** - * Ignore the UiRouter Loc Group and add all menus in sitemap. - */ - val ignoreUiRouterGroup = new FactoryMaker[Boolean](true) {} - - /** - * Default route when page lands on index. - */ - val defaultRoute = new FactoryMaker[Box[Menu]](Empty) {} - - /** - * List of all the routes. - */ - lazy val routes = new FactoryMaker[Seq[Menu]](findRoutes) {} - - /** - * Place this LocGroup on any Menu Items in Lift's SiteMap - * that will have a state in AngularJs UI-Router. - */ - val UiRouterGroup = LocGroup("uirouter") - - /* Finds menu items that are in UiRouterGroup group. */ - private def findRoutes = LiftRules.siteMap map { - siteMap => - if(ignoreUiRouterGroup.vend) - siteMap.menus - else - siteMap.menus.filter(_.loc.inGroup_?(UiRouterGroup.group.head)) - } openOr Nil -} - -trait NgUIRouterSnip extends SnippetHelper { - - /** - * Snippet to configures AngularJs module with all routes. - * - * Required attributes: - * - ngApp: Name of AngularJs module. - */ - def js: CssSel = - for{ - app <- S.attr("ngApp") ?~ "ngApp name is missing" - } yield "* *" #> { - import NgUIRouterFactory._ - - val otherwise = defaultRoute.vend map { - "$urlRouterProvider.otherwise('"+ - S.contextPath+_.loc.calcDefaultHref+"');" - } openOr "" - - app+".config(function($stateProvider, $urlRouterProvider,$locationProvider) {"+ - "$locationProvider.html5Mode("+html5mode.vend.toString+");"+ - otherwise+"$stateProvider"+ - routes.vend.map { menu => - - val templateUrl = S.contextPath+menu.loc.calcDefaultHref - val state: String = menu.loc.name.replaceAll(" ","").toLowerCase - - ".state('"+state+"',{url:'"+templateUrl+"',views:"+ - "{'viewA':{templateUrl:'"+templateUrl+".html?ajax'}}"+ - "})" - }.mkString+ - ";});" - } - - - /** - * Conditional Template Surround Snippet. Allows pages to be surrounded by a - * different template if it's accessed with ajax. - * - * Needed attributes are: - * - with: template name to surround page. - * - at: id of element in template to place the page. - * - withAjax: template name to surround page when accessed with ajax. - * - * When accessing with ajax, assure URL query parameter 'ajax' exists. - * - * NOTE: default.html in templates-hidden cannot exist - * (else Lift will auto-surround everything with default) - */ - def surround(ns: NodeSeq): NodeSeq = - for { - surroundWith <- S.attr("with") ?~ "Surround with not specified" - surroundWithAjax <- S.attr("withAjax") ?~ "Surround with ajax not specified" - at <- S.attr("at") ?~ "Surround at not specified" - } yield { - if(S.param("ajax").isDefined) - Templates("templates-hidden" :: surroundWithAjax :: Nil) map { - s"#$at" #> ns - } openOr Text("Template '"+surroundWithAjax+"' not found") - else - Templates("templates-hidden" :: surroundWith :: Nil) map { - s"#$at" #> ns - } openOr Text("Template '"+surroundWith+"' not found") - } -} - -/** - * Menu Group snippet. List of links grouped with LocGroup will - * have ui-sref attribute. If link points to /some/foo/bar, - * then ui-router state or ui-sref value will be some.foo.bar. - * - * Example snippet: data-lift="NgUIRouterMenu.group?group=topbar" - */ -trait NgUIRouterMenu extends SnippetHelper { - - private def buildUIRouterLink(name: String): NodeSeq = { - val options = for { - loc <- SiteMap.findAndTestLoc(name).toList - link <- loc.createDefaultLink - } yield { - - def uiSref(el: Elem): Elem = { - import NgUIRouterFactory._ - if(loc.inGroup_?(UiRouterGroup.group.head)) - el % ("ui-sref" -> loc.name.replaceAll(" ","").toLowerCase) - else - el - } - - val linkText = loc.linkText openOr Text(loc.name) - uiSref(<a href={link}>{linkText}</a>) - - } - options.headOption getOrElse NodeSeq.Empty - } - - def item: CssSel = { - val options = (for { - name: String <- S.attr("name") ?~ "Item name not specified" - } yield for { - loc <- SiteMap.findAndTestLoc(name) - link <- loc.createDefaultLink - } yield { - "* [ui-sref]" #> loc.name.replaceAll(" ","").toLowerCase & - "* [href]" #> link - }) openOr Empty - options - } - - /** - * Produces a menu UL from a group, for use with Bootstrap. - */ - def group = { - val menus: NodeSeq = - for { - group <- S.attr("group") ?~ "Group not specified" - sitemap <- LiftRules.siteMap ?~ "Sitemap is empty" - request <- S.request ?~ "Request is empty" - curLoc <- request.location ?~ "Current location is empty" - } yield ({ - sitemap.locForGroup(group) flatMap { loc => - val nonHiddenKids = loc.menu.kids.filterNot(_.loc.hidden) - - if (nonHiddenKids.length == 0) { - <li>{buildUIRouterLink(loc.name)}</li> - } - else { - val dropdown: NodeSeq = nonHiddenKids.map { kid => - <li>{buildUIRouterLink(kid.loc.name)}</li> - } - - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown">{loc.linkText.openOr(Text("Empty Name"))} <b class="caret"></b></a> - <ul class="dropdown-menu">{ dropdown }</ul> - </li> - } - } - }): NodeSeq - - "* *" #> menus - } -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/lib/RogueMetaRecord.scala b/src/main/scala/com/pyd/lib/RogueMetaRecord.scala @@ -1,18 +0,0 @@ -package com.pyd -package lib - -import config.MongoConfig - -import net.liftweb._ -import mongodb.record._ - -import com.foursquare.rogue._ - -/** - * A custom MongoMetaRecord that adds Rogue and an injectable MongoIdentifier. - */ -trait RogueMetaRecord[A <: MongoRecord[A]] extends MongoMetaRecord[A] with LiftRogue { - self: A => - - override def mongoIdentifier = MongoConfig.defaultId.vend -} diff --git a/src/main/scala/com/pyd/model/AtmApplication.scala b/src/main/scala/com/pyd/model/AtmApplication.scala @@ -1,64 +0,0 @@ -package com.pyd -package model - -import field.USStatesField -import lib.RogueMetaRecord -import net.liftweb._ -import mongodb.record._ -import mongodb.record.field._ -import record.field._ -import org.joda.time.DateTime - -class AtmApplication private () extends MongoRecord[AtmApplication] with ObjectIdPk[AtmApplication] { - def meta = AtmApplication - - object name extends StringField(this, 64) { - override def validations = - valMaxLen(64, "Business name must be 64 characters or less") _ :: - super.validations - } - - object email extends EmailField(this, 64) { - override def validations = - valMaxLen(64, "Email must be 64 characters or less") _ :: - super.validations - } - - object phone extends StringField(this, 25) { - override def validations = - valMaxLen(25, "Phone number must be 25 characters or less") _ :: - super.validations - } - - object bestTime extends TextareaField(this, 255) - - object address extends StringField(this, 255) { - override def validations = - valMaxLen(255, "Business address must be 255 characters or less") _ :: - super.validations - } - - object city extends StringField(this, 64) { - override def validations = - valMaxLen(64, "City must be 64 characters or less") _ :: - super.validations - } - - object state extends USStatesField(this) - - object website extends StringField(this, 255) { - override def validations = - valMaxLen(255, "Website must be 255 characters or less") _ :: - super.validations - } - - def whenCreated: DateTime = new DateTime(id.get.getTime) -} - -object AtmApplication extends AtmApplication with RogueMetaRecord[AtmApplication] { - import mongodb.BsonDSL._ - - override def collectionName = "atm.applications" - - ensureIndex((name.name -> 1), true) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/model/NearAtmNotify.scala b/src/main/scala/com/pyd/model/NearAtmNotify.scala @@ -1,57 +0,0 @@ -package com.pyd -package model - -import lib.RogueMetaRecord -import field.USStatesField -import net.liftweb._ -import mongodb.record._ -import mongodb.record.field.ObjectIdPk -import record.field._ -import org.joda.time.DateTime - -class NearAtmNotify private () extends MongoRecord[NearAtmNotify] with ObjectIdPk[NearAtmNotify] { - def meta = NearAtmNotify - - object fname extends StringField(this, 64) { - override def validations = - valMaxLen(64, "First name must be 64 characters or less") _ :: - super.validations - } - - object lname extends StringField(this, 64) { - override def validations = - valMaxLen(64, "Last name must be 64 characters or less") _ :: - super.validations - } - - object email extends EmailField(this, 64) { - override def validations = - valMaxLen(64, "Email must be 64 characters or less") _ :: - super.validations - } - - - object postal extends PostalCodeField(this, meta.usa) - - object city extends StringField(this, 64) { - override def validations = - valMaxLen(64, "City must be 64 characters or less") _ :: - super.validations - } - - object state extends USStatesField(this) - - def whenCreated: DateTime = new DateTime(id.get.getTime) -} - -object NearAtmNotify extends NearAtmNotify with RogueMetaRecord[NearAtmNotify] { - import mongodb.BsonDSL._ - - override def collectionName = "atm.nearnotify" - - ensureIndex((email.name -> 1), true) - - object usa extends CountryField(NearAtmNotify.createRecord) { - override def defaultValue = Countries.USA - } -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/model/SearchedPostal.scala b/src/main/scala/com/pyd/model/SearchedPostal.scala @@ -1,45 +0,0 @@ -package com.pyd -package model - -import lib.RogueMetaRecord -import field.USStatesField - -import net.liftweb._ -import mongodb.record._ -import mongodb.record.field.ObjectIdPk -import record.field._ - -class SearchedPostal private () extends MongoRecord[SearchedPostal] with ObjectIdPk[SearchedPostal] { - def meta = SearchedPostal - - /* The total amount of times postal code was searched. */ - object total extends IntField(this) - - /* The postal code that was searched. */ - object postal extends PostalCodeField(this, meta.usa) - - override def save = { - val qry = SearchedPostal.find(this) - - if(qry.get().isDefined) { - qry.modify(_.total inc 1).updateOne() - this - } else - total(total.get + 1) - super.save - } -} - -object SearchedPostal extends SearchedPostal with RogueMetaRecord[SearchedPostal] { - import mongodb.BsonDSL._ - - override def collectionName = "atm.find" - - ensureIndex((postal.name -> 1), true) - - def find(postal: SearchedPostal) = this.where(_.postal eqs postal.postal.get) - - object usa extends CountryField(SearchedPostal.createRecord) { - override def defaultValue = Countries.USA - } -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/model/User.scala b/src/main/scala/com/pyd/model/User.scala @@ -1,170 +0,0 @@ -package com.pyd -package model - -import lib.RogueMetaRecord - -import org.bson.types.ObjectId -import org.joda.time.DateTime - -import net.liftweb._ -import common._ -import http.{StringField => _, BooleanField => _, _} -import mongodb.record.field._ -import record.field._ - -import net.liftmodules.mongoauth._ -import net.liftmodules.mongoauth.field._ -import net.liftmodules.mongoauth.model._ - -class User private () extends ProtoAuthUser[User] with ObjectIdPk[User] { - def meta = User - - def userIdAsString: String = id.toString - - object fname extends StringField(this, 64) { - override def validations = - valMaxLen(64, "First Name must be 64 characters or less") _ :: - super.validations - } - - object lname extends StringField(this, 64) { - override def validations = - valMaxLen(64, "First Name must be 64 characters or less") _ :: - super.validations - } - - def whenCreated: DateTime = new DateTime(id.get.getTime) -} - -object User extends User with ProtoAuthUserMeta[User] with RogueMetaRecord[User] with Loggable { - import mongodb.BsonDSL._ - - override def collectionName = "user.users" - - ensureIndex((email.name -> 1), true) - ensureIndex((username.name -> 1), true) - - def findByEmail(in: String): Box[User] = find(email.name, in) - def findByUsername(in: String): Box[User] = find(username.name, in) - - def findByStringId(id: String): Box[User] = - if (ObjectId.isValid(id)) find(new ObjectId(id)) - else Empty - - override def onLogIn: List[User => Unit] = List(user => User.loginCredentials.remove()) - override def onLogOut: List[Box[User] => Unit] = List( - x => logger.debug("User.onLogOut called."), - boxedUser => boxedUser.foreach { u => - ExtSession.deleteExtCookie() - } - ) - - /* - * MongoAuth vars - */ - private lazy val siteName = MongoAuth.siteName.vend - private lazy val sysUsername = MongoAuth.systemUsername.vend - private lazy val indexUrl = MongoAuth.indexUrl.vend - private lazy val registerUrl = MongoAuth.registerUrl.vend - private lazy val loginTokenAfterUrl = MongoAuth.loginTokenAfterUrl.vend - - /* - * LoginToken - */ - override def handleLoginToken: Box[LiftResponse] = { - val resp = S.param("token").flatMap(LoginToken.findByStringId) match { - case Full(at) if (at.expires.isExpired) => { - at.delete_! - RedirectWithState(indexUrl, RedirectState(() => { S.error("Login token has expired") })) - } - case Full(at) => find(at.userId.get).map(user => { - if (user.validate.length == 0) { - user.verified(true) - user.save - logUserIn(user) - at.delete_! - RedirectResponse(loginTokenAfterUrl) - } - else { - at.delete_! - regUser(user) - RedirectWithState(registerUrl, RedirectState(() => { S.notice("Please complete the registration form") })) - } - }).openOr(RedirectWithState(indexUrl, RedirectState(() => { S.error("User not found") }))) - case _ => RedirectWithState(indexUrl, RedirectState(() => { S.warning("Login token not provided") })) - } - - Full(resp) - } - - // send an email to the user with a link for logging in - def sendLoginToken(user: User): Unit = { - import net.liftweb.util.Mailer._ - - val token = LoginToken.createForUserId(user.id.get) - - val msgTxt = - """ - |Someone requested a link to log in to your %s account. - | - |If you did not request this, you can safely ignore it. It will expire 48 hours from the time this message was sent. - | - |Follow the link below or copy and paste it into your internet browser. - | - |%s - | - |Kindest Regards, - | - |%s - """.format(siteName, token.url, sysUsername).stripMargin - - sendMail( - From(MongoAuth.systemFancyEmail), - Subject("%s Account: Login".format(siteName)), - To(user.fancyEmail), - PlainMailBodyType(msgTxt) - ) - } - - /* - * ExtSession - */ - def createExtSession(uid: ObjectId) = ExtSession.createExtSession(uid) - - /* - * Test for active ExtSession. - */ - def testForExtSession: Box[Req] => Unit = { - ignoredReq => { - if (currentUserId.isEmpty) { - ExtSession.handleExtSession match { - case Full(es) => find(es.userId.get).foreach { user => logUserIn(user, false) } - case Failure(msg, _, _) => - logger.warn("Error logging user in with ExtSession: %s".format(msg)) - case Empty => - } - } - } - } - - // used during login process - object loginCredentials extends SessionVar[LoginCredentials](LoginCredentials("")) - object regUser extends SessionVar[User](createRecord.email(loginCredentials.is.email)) -} - -case class LoginCredentials(email: String, isRememberMe: Boolean = false) - -object SystemUser { - private val username = "pyd" - private val email = "noreply@pydcoin.com" - - lazy val user: User = User.find("username", username) openOr { - User.createRecord - .fname("PYD") - .username(username) - .email(email) - .verified(true) - .password("abc123", true) - .save - } -} diff --git a/src/main/scala/com/pyd/model/field/USStateField.scala b/src/main/scala/com/pyd/model/field/USStateField.scala @@ -1,41 +0,0 @@ -package com.pyd.model.field - -import net.liftweb._ -import http._ -import util.Helpers._ -import record._ -import field._ -import json.JsonAST._ -import common._ - -object USStates extends Enumeration { - - val Alabama, Alaska, Arizona, Arkansas, California, Colorado, Connecticut, Delaware, Florida, Georgia, - Hawaii, Idaho, Illinois, Indiana, Iowa, Kansas, Kentucky, Louisiana, Maine, Maryland, - Massachusetts, Michigan, Minnesota, Mississippi, Missouri, Montana, Nebraska, Nevada, New_Hampshire, New_Jersey, - New_Mexico, New_York, North_Carolina, North_Dakota, Ohio, Oklahoma, Oregon, Pennsylvania, Rhode_Island, South_Carolina, - South_Dakota, Tennessee, Texas, Utah, Vermont, Virginia, Washington, West_Virginia, Wisconsin, Wyoming = Value - - class States extends Val { - override def toString = super.toString.replace("_", " ") - } - - def options: List[(String, String)] = - USStates.values.map(i => (i.toString, i.toString)).toList - - def stateSelect(s: String) = tryo(USStates.withName(s)) -} - - -class USStatesField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends EnumField(rec, USStates) { - override def setFromJValue(jvalue: JValue): Box[USStatesField.this.MyType] = { - val stringToInt = jvalue transform { - case JString(s) => - val value: Box[Int] = USStates.stateSelect(s).map(_.id) - value.map(JInt(_)).openOr(JNothing) - case _ => JNothing - } - super.setFromJValue(stringToInt) - } -} - diff --git a/src/main/scala/com/pyd/snippet/AngularSnips.scala b/src/main/scala/com/pyd/snippet/AngularSnips.scala @@ -1,85 +0,0 @@ -package com.pyd -package snippet - -import xml._ -import net.liftweb._ -import util._ -import Helpers._ -import common._ -import json.JsonAST._ -import http._ -import js._ -import JsCmds._ -import JE.JsVar - -/** - * Snippet classes with AngularJs server-side code need to extend trait. - * Its js function will be called at the end of Body element if it is added - * to AngularServerSide's 'snips' FactoryMaker. - */ -trait AngularSnippet { - def roundTrips: RoundTripInfo -} - -/** - * Place this snippet at the end of your end of Body - * - * <script data-lift="AngularServerSide.js"></script> - */ -class AngularServerSide extends Factory { - - /* All AngularJs server-side snippets. */ - private val snips = new FactoryMaker[List[AngularSnippet]](List( - new SearchedPostalSnip, - new AtmApplicationSnip, - new NearAtmNotifySnip, - new UserRegistrationSnip, - new UserLoginSnip, - new ForgotPasswordSnip - )) {} - - def js: CssSel = - "* *" #> (for { - sess <- S.session - } yield { - def roundTrips: List[RoundTripInfo] = snips.vend.map(_.roundTrips) - - val script = SetExp(JsVar("window", "backend"), - sess.buildRoundtrip(List[RoundTripInfo](roundTrips: _*))) - script - }) -} - -/** - * Builds Alert JSON messages for server-side responses. - */ -object NgAlert extends Logger { - - private def msgBox(msgType: String, msg: NodeSeq): JValue = - JObject(List( - JField("msg_type", JString(msgType)), - JField("msg", JString(msg.toString)))) - - def success(msg: NodeSeq): JValue = msgBox("success", msg) - def success(msg: String): JValue = success(Text(msg)) - def success: JValue = success(Text("")) - - def danger(msg: NodeSeq, errors: List[FieldError]): JValue = { - debug(errors) - - msgBox("danger", msg ++ - errors.foldLeft(<ul></ul>)((el,err) => el.copy(child = el.child :+ <li>{err.msg}</li>))) - } - - def danger(msg: String, errors: List[FieldError] = Nil): JValue = danger(Text(msg), errors) - def danger: JValue = danger(Text(""), Nil) - - def info(msg: NodeSeq): JValue = msgBox("info", msg) - def info(msg: String): JValue = info(Text(msg)) - def info: JValue = info(Text("")) - - def warning(msg: NodeSeq): JValue = msgBox("warning", msg) - def warning(msg: String): JValue = warning(Text(msg)) - def warning: JValue = warning(Text("")) - -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/AtmApplicationSnip.scala b/src/main/scala/com/pyd/snippet/AtmApplicationSnip.scala @@ -1,44 +0,0 @@ -package com.pyd -package snippet - -import model._ -import field._ -import net.liftweb._ -import common._ -import json.JsonAST._ -import util._ -import Helpers._ -import http._ -import js._ -import JsCmds._ -import SHtml._ -import JE.JsVar - -class AtmApplicationSnip extends AngularSnippet { - - def save(model: JValue): JValue = { - val rec = AtmApplication.createRecord - rec.setFieldsFromJValue(model) - - rec.validate match { - case Nil => - rec.save - NgAlert.success( - <i class="fa-fw fa fa-thumbs-o-up"></i> ++ - <strong>{ s"Your Bitcoin ATM application for ${rec.name.get} has been received." }</strong> ++ - <span>{s" We will contact you when we are ready. Thank you for applying."}</span> - ) - case errors => - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>{ s"Your application could not be processed." }</strong>, - errors - ) - } - } - - def render: CssSel = - "@state" #> select(USStates.options, Empty, USStates.stateSelect, "name" -> "state") - - override def roundTrips = ("saveAtmApplication" -> save _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/ForgotPasswordSnip.scala b/src/main/scala/com/pyd/snippet/ForgotPasswordSnip.scala @@ -1,40 +0,0 @@ -package com.pyd -package snippet - -import model._ -import net.liftweb._ -import common._ -import http.S -import json._ -import JsonAST.{JValue, JNull} - -class ForgotPasswordSnip extends AngularSnippet { - - def forgotPassword(model: JValue): JValue = { - (for { - JString(e) <- model \ "email" - } yield { - val email = e.toLowerCase.trim - - User.findByEmail(email) match { - case Full(user) => - User.sendLoginToken(user) - User.loginCredentials.remove() - - NgAlert.success( - <i class="fa-fw fa fa-thumbs-o-up"></i> ++ - <strong>An email has been sent to you with instructions for accessing your account.</strong>) - case _ => - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>Your email could not be found.</strong>, Nil) - } - }).headOption.getOrElse { - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>The information submitted could not be processed.</strong>, Nil) - } - } - - override def roundTrips = ("forgotPassword" -> forgotPassword _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/NearAtmNotifySnip.scala b/src/main/scala/com/pyd/snippet/NearAtmNotifySnip.scala @@ -1,48 +0,0 @@ -package com.pyd -package snippet - -import model._ -import field._ -import net.liftweb._ -import common._ -import json.JsonAST._ -import util._ -import Helpers._ -import http._ -import js._ -import JsCmds._ -import SHtml._ -import JE.JsVar - -class NearAtmNotifySnip extends AngularSnippet { - - def save(model: JValue): JValue = { - val rec = NearAtmNotify.createRecord - rec.setFieldsFromJValue(model) - - rec.validate match { - case Nil => - rec.save - NgAlert.success( - <div> - <i class="fa-fw fa fa-thumbs-o-up"></i> - <strong>{ s"Hi ${rec.fname.get}, your notification has been received." }</strong> - <span>{s" We will notify you when there is an ATM near ${rec.city.get}, ${rec.state.get}."}</span> - </div> - ) - case errors => - NgAlert.danger( - <div> - <i class="fa-fw fa fa-thumbs-o-down"></i> - <strong>{ s"Hi ${rec.fname.get}, your notification was not submitted successfully." }</strong> - </div>, - errors - ) - } - } - - def render: CssSel = - "@state" #> select(USStates.options, Empty, USStates.stateSelect, "name" -> "state") - - override def roundTrips = ("saveNearAtmNotify" -> save _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/SearchedPostalSnip.scala b/src/main/scala/com/pyd/snippet/SearchedPostalSnip.scala @@ -1,40 +0,0 @@ -package com.pyd -package snippet - -import model._ -import field._ -import xml._ -import net.liftweb._ -import common._ -import json.JsonAST._ -import util._ -import Helpers._ -import http._ -import js._ -import JsCmds._ -import SHtml._ -import JE.JsVar - -class SearchedPostalSnip extends AngularSnippet { - - def search(model: JValue): JValue = { - val rec = SearchedPostal.createRecord - rec.setFieldsFromJValue(model) - - rec.validate match { - case Nil => - rec.save - NgAlert.success - case errors => - NgAlert.danger( - <div> - <i class="fa-fw fa fa-thumbs-o-down"></i> - <strong>{"Something went wrong. We apologize for the inconvenience."}</strong> - </div>, - errors - ) - } - } - - override def roundTrips = ("searchAtmPostalCode" -> search _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/Sitemap.scala b/src/main/scala/com/pyd/snippet/Sitemap.scala @@ -1,46 +0,0 @@ -package com.pyd -package snippet - -import config._ -import org.joda.time.DateTime -import net.liftweb._ -import net.liftweb.http._ -import net.liftweb.http.rest.RestHelper -import net.liftweb.util._ -import net.liftweb.util.Helpers._ -import net.liftweb.common.Box.box2Option -import net.liftweb.http.LiftRulesMocker.toLiftRules -import com.pyd.lib.NgUIRouterFactory - -object Sitemap extends RestHelper { - serve { - case Req("sitemap" :: Nil, _, GetRequest) => - XmlResponse( - S.render(<lift:embed what="sitemap" />, - S.request.get.request).head) - } -} - -class SitemapContent { - - case class Post(url: String, date: DateTime) - - lazy val baseEntry = NgUIRouterFactory.defaultRoute.vend map { - _.loc.calcDefaultHref - } openOr "" - - lazy val entries = - LiftRules.siteMap map { - _.locForGroup("sitemap").map(_.calcDefaultHref).map(Post(_, new DateTime)) - } openOr Nil - - def base: CssSel = - "loc *" #> "http://%s%s".format(Site.domain, baseEntry) & - "lastmod *" #> (new DateTime).toString("yyyy-MM-dd'T'HH:mm:ss.SSSZZ") - - def list: CssSel = - "url *" #> entries.map(post => - "loc *" #> "http://%s%s".format(Site.domain, post.url) & - "lastmod *" #> post.date.toString("yyyy-MM-dd'T'HH:mm:ss.SSSZZ")) - -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/UserLoginSnip.scala b/src/main/scala/com/pyd/snippet/UserLoginSnip.scala @@ -1,49 +0,0 @@ -package com.pyd -package snippet - -import model._ -import net.liftweb._ -import common._ -import json._ -import JsonAST.{JValue, JString, JBool} -import net.liftmodules.mongoauth.model.ExtSession - -class UserLoginSnip extends AngularSnippet { - - def login(model: JValue): JValue = { - (for { - JString(e) <- model \ "email" - JString(password) <- model \ "password" - JBool(remember) <- model \ "remember" - } yield { - val email = e.toLowerCase.trim - User.loginCredentials(LoginCredentials(email, remember)) - - if(!(email.length > 0) || !(password.length > 0)) { - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>{"Your login information could not be processed."}</strong>, Nil) - } - else { - User.findByEmail(email) match { - case Full(user) if (user.password.isMatch(password)) => - User.logUserIn(user, remember) - if (remember) User.createExtSession(user.id.get) - else ExtSession.deleteExtCookie() - NgAlert.success - case _ => - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>{"Invalid Credentials: "}</strong> - <p>Assure your email and password are correct.</p>, Nil) - } - } - }).headOption.getOrElse { - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>{"Your login information could not be processed."}</strong>, Nil) - } - } - - override def roundTrips = ("loginUser" -> login _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/UserRegistrationSnip.scala b/src/main/scala/com/pyd/snippet/UserRegistrationSnip.scala @@ -1,45 +0,0 @@ -package com.pyd -package snippet - -import model._ -import field._ -import net.liftweb._ -import common._ -import json.JsonAST._ -import util._ -import Helpers._ -import http._ -import js._ -import JsCmds._ -import SHtml._ -import JE.JsVar - -class UserRegistrationSnip extends AngularSnippet { - - def register(model: JValue): JValue = { - val rec = User.createRecord - rec.setFieldsFromJValue(model) - rec.password(Helpers.randomString(20)) - rec.password.hashIt - - rec.validate match { - case Nil => - rec.save - User.sendLoginToken(rec) - - NgAlert.success( - <i class="fa-fw fa fa-thumbs-o-up"></i> ++ - <strong>{ s"Thank you for registering, ${rec.fname.get}." }</strong> ++ - <span>{" An email has been sent for you to log in."}</span> - ) - case errors => - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <strong>{"Your application could not be processed."}</strong>, - errors - ) - } - } - - override def roundTrips = ("registerUser" -> register _) -} -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/UserScreens.scala b/src/main/scala/com/pyd/snippet/UserScreens.scala @@ -1,134 +0,0 @@ -package com.pyd -package snippet - -import config.Site -import model._ - -import scala.xml._ - -import net.liftweb._ -import common._ -import http.{LiftScreen, S} -import util.FieldError -import util.Helpers._ - -import net.liftmodules.extras.Gravatar - -/* - * Use for editing the currently logged in user only. - */ -sealed trait BaseCurrentUserScreen extends BaseScreen { - object userVar extends ScreenVar(User.currentUser.openOr(User.createRecord)) - - override def localSetup { - Referer(Site.settings.url) - } -} - -/* -object AccountScreen extends BaseCurrentUserScreen { - addFields(() => userVar.is.accountScreenFields) - - def finish() { - userVar.is.save - S.notice("Account settings saved") - } -} -*/ - -sealed trait BasePasswordScreen { - this: LiftScreen => - - def pwdName: String = "Password" - def pwdMinLength: Int = 8 - def pwdMaxLength: Int = 32 - - val passwordField = password(pwdName, "", trim, - valMinLen(pwdMinLength, "Password must be at least "+pwdMinLength+" characters"), - valMaxLen(pwdMaxLength, "Password must be "+pwdMaxLength+" characters or less"), - "tabindex" -> "1" - ) - val confirmPasswordField = password("Confirm Password", "", trim, "tabindex" -> "1") - - def passwordsMustMatch(): Errors = { - if (passwordField.is != confirmPasswordField.is) - List(FieldError(confirmPasswordField, "Passwords must match")) - else Nil - } -} - - -object PasswordScreen extends BaseCurrentUserScreen with BasePasswordScreen { - override def pwdName = "New Password" - override def validations = passwordsMustMatch _ :: super.validations - - def finish() { - userVar.is.password(passwordField.is) - userVar.is.password.hashIt - userVar.is.save - } -} - -/* - * Use for editing the currently logged in user only. - */ -/* -object ProfileScreen extends BaseCurrentUserScreen { - def gravatarHtml = - <span> - <div class="gravatar"> - {Gravatar.imgTag(userVar.is.email.get, 60)} - </div> - <div class="gravatar"> - <h4>Change your avatar at <a href="http://gravatar.com" target="_blank">Gravatar.com</a></h4> - <p> - We're using {userVar.is.email.get}. It may take time for changes made on gravatar.com to appear on our site. - </p> - </div> - </span> - - val gravatar = displayOnly("Picture", gravatarHtml) - - addFields(() => userVar.is.profileScreenFields) - - def finish() { - userVar.is.save - S.notice("Profile settings saved") - } -} -*/ - -// this is needed to keep these fields and the password fields in the proper order -/* -trait BaseRegisterScreen extends BaseScreen { - object userVar extends ScreenVar(User.regUser.is) - - addFields(() => userVar.is.registerScreenFields) -} -*/ -/* - * Use for creating a new user. - */ -/* -object RegisterScreen extends BaseRegisterScreen with BasePasswordScreen { - override def validations = passwordsMustMatch _ :: super.validations - - val rememberMe = builder("", User.loginCredentials.is.isRememberMe, ("tabindex" -> "1")) - .help(Text("Remember me when I come back later.")) - .make - - override def localSetup { - Referer(Site.home.url) - } - - def finish() { - val user = userVar.is - user.password(passwordField.is) - user.password.hashIt - user.save - User.logUserIn(user, true) - if (rememberMe) User.createExtSession(user.id.get) - S.notice("Thanks for signing up!") - } -} -*/ -\ No newline at end of file diff --git a/src/main/scala/com/pyd/snippet/UserSnips.scala b/src/main/scala/com/pyd/snippet/UserSnips.scala @@ -1,180 +0,0 @@ -package com.pyd -package snippet - -import config.Site -import model.{User, LoginCredentials} - -import scala.xml._ - -import net.liftweb._ -import common._ -import http.{DispatchSnippet, S, SHtml, StatefulSnippet} -import http.js.JsCmd -import http.js.JsCmds._ -import util._ -import Helpers._ - -import net.liftmodules.extras.{Gravatar, SnippetHelper} -import net.liftmodules.mongoauth.LoginRedirect -import net.liftmodules.mongoauth.model.ExtSession - -sealed trait UserSnippet extends SnippetHelper with Loggable { - - protected def user: Box[User] - - protected def serve(snip: User => NodeSeq): NodeSeq = - (for { - u <- user ?~ "User not found" - } yield { - snip(u) - }): NodeSeq - - protected def serve(html: NodeSeq)(snip: User => CssSel): NodeSeq = - (for { - u <- user ?~ "User not found" - } yield { - snip(u)(html) - }): NodeSeq - - def username(xhtml: NodeSeq): NodeSeq = serve { user => - Text(user.username.get) - } - - def name(xhtml: NodeSeq): NodeSeq = serve { user => - val name = user.fname.get + " " + user.lname.get - if (name.length > 1) Text(name) - else Text(user.username.get) - } - - def title(xhtml: NodeSeq): NodeSeq = serve { user => - <title data-lift="Menu.title">PYD: %*% - {user.username.get}</title> - } -} - -object CurrentUser extends UserSnippet { - protected def user = User.currentUser -} - -object ProfileLocUser extends UserSnippet { - - protected def user = Site.profileLoc.currentValue - - import java.text.SimpleDateFormat - - val df = new SimpleDateFormat("MMM d, yyyy") - - def profile(html: NodeSeq): NodeSeq = serve(html) { user => - val editLink: NodeSeq = - if (User.currentUser.filter(_.id.get == user.id.get).isDefined) - <a href={Site.editProfile.url} class="btn btn-info"><i class="icon-edit icon-white"></i> Edit Your Profile</a> - else - NodeSeq.Empty - - "#id_avatar *" #> Gravatar.imgTag(user.email.get) & - "#id_name *" #> <h3>{user.fname.get}</h3> & - "#id_whencreated" #> df.format(user.whenCreated.toDate).toString & - "#id_editlink *" #> editLink - } -} - -object UserLogin extends Loggable { - - def render = { - // form vars - var password = "" - var hasPassword = false - var remember = User.loginCredentials.is.isRememberMe - - val radios = SHtml.radioElem[Boolean]( - Seq(false, true), - Full(hasPassword) - )(it => it.foreach(hasPassword = _)) - - def doSubmit(): JsCmd = { - S.param("email").map(e => { - val email = e.toLowerCase.trim - // save the email and remember entered in the session var - User.loginCredentials(LoginCredentials(email, remember)) - - if (hasPassword && email.length > 0 && password.length > 0) { - User.findByEmail(email) match { - case Full(user) if (user.password.isMatch(password)) => - logger.debug("pwd matched") - User.logUserIn(user, true) - if (remember) User.createExtSession(user.id.get) - else ExtSession.deleteExtCookie() - RedirectTo(LoginRedirect.openOr(Site.home.url)) - case _ => - S.error("Invalid credentials") - Noop - } - } - else if (hasPassword && email.length <= 0 && password.length > 0) { - S.error("id_email_err", "Please enter an email") - Noop - } - else if (hasPassword && password.length <= 0 && email.length > 0) { - S.error("id_password_err", "Please enter a password") - Noop - } - else if (hasPassword) { - S.error("id_email_err", "Please enter an email") - S.error("id_password_err", "Please enter a password") - Noop - } - else if (email.length > 0) { - // see if email exists in the database - User.findByEmail(email) match { - case Full(user) => - User.sendLoginToken(user) - User.loginCredentials.remove() - S.notice("An email has been sent to you with instructions for accessing your account") - Noop - case _ => - RedirectTo(Site.register.url) - } - } - else { - S.error("id_email_err", "Please enter an email address") - Noop - } - }) openOr { - S.error("id_email_err", "Please enter an email address") - Noop - } - } - - "#id_email [value]" #> User.loginCredentials.is.email & - "#id_password" #> SHtml.password(password, password = _) & - "#no_password" #> radios(0) & - "#yes_password" #> radios(1) & - "name=remember" #> SHtml.checkbox(remember, remember = _) & - "#id_submit" #> SHtml.hidden(doSubmit) - } -} - -object UserTopbar { - def render = { - User.currentUser match { - case Full(user) => - <ul class="nav navbar-nav navbar-right" id="user"> - <li class="dropdown" data-dropdown="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown"> - {Gravatar.imgTag(user.email.get, 20)} - <span>{user.username.get}</span> - <b class="caret"></b> - </a> - <ul class="dropdown-menu"> - <li><a href={Site.profileLoc.calcHref(user)}><i class="icon-user"></i> Profile</a></li> - <li><lift:Menu.item name="Account" donthide="true" linktoself="true"><i class="icon-cog"></i> Settings</lift:Menu.item></li> - <li class="divider"></li> - <li><lift:Menu.item name="Logout" donthide="true"><i class="icon-off"></i> Log Out</lift:Menu.item></li> - </ul> - </li> - </ul> - case _ if (S.request.flatMap(_.location).map(_.name).filterNot(it => List("Login", "Register").contains(it)).isDefined) => - <a href="/login" class="btn btn-default navbar-btn">Sign In</a> - case _ => NodeSeq.Empty - } - } -} diff --git a/src/main/scala/com/pyd/snippet/UtilSnips.scala b/src/main/scala/com/pyd/snippet/UtilSnips.scala @@ -1,32 +0,0 @@ -package com.pyd -package snippet - -import scala.xml.NodeSeq -import net.liftweb._ -import common._ -import util._ -import Helpers._ -import http._ -import js._ -import net.liftmodules.extras._ -import snippet._ -import com.pyd.lib.{NgUIRouterMenu, NgUIRouterSnip} - -/* - * Base all LiftScreens off this. Currently configured to use bootstrap 3. - */ -abstract class BaseScreen extends Bootstrap3Screen { - override def defaultToAjax_? = true -} - -object Assets extends AssetLoader - -object NgUIRouter extends NgUIRouterSnip - -object Menus extends NgUIRouterMenu - -object ProductionOnly { - def render(in: NodeSeq): NodeSeq = - if (Props.productionMode) in - else NodeSeq.Empty -} diff --git a/src/main/scala/inc/pyc/config/ErrorHandler.scala b/src/main/scala/inc/pyc/config/ErrorHandler.scala @@ -0,0 +1,64 @@ +package inc.pyc +package config + +import model.User + +import net.liftweb._ +import common.{Loggable, MDC} +import http.{Factory, LiftRules, RedirectResponse, Req, S, XhtmlResponse} +import util.Props + +object ErrorHandler extends Factory with Loggable { + // config + val errorUrl = new FactoryMaker[String]("/error") {} // where to send the user when an error occurs + + def init(): Unit = { + LiftRules.exceptionHandler.prepend { + case (Props.RunModes.Development, r, e) => + logException(r, e) + XhtmlResponse( + (<html><body>Exception occured while processing {r.uri}<pre>{showException(e)}</pre></body></html>), + S.htmlProperties.docType, + List("Content-Type" -> "text/html; charset=utf-8"), + Nil, + 500, + S.legacyIeCompatibilityMode + ) + case (_, r, e) => + logException(r, e) + RedirectResponse(errorUrl.vend) + } + } + + /* + * Log the exception with some user info. + */ + def logException(r: Req, e: Throwable) { + import java.net.InetAddress + val srvr = InetAddress.getLocalHost.getHostName + + MDC.put(("UserId", User.currentUserId openOr "GUEST")) + MDC.put(("Username", User.currentUser.map(_.username.get) openOr "GUEST")) + MDC.put(("User Agent", r.userAgent openOr "UNKNOWN")) + MDC.put(("Server", srvr)) + logger.error("Exception occurred while processing %s".format(r.uri), e) + } + + /** + * A utility method to convert an exception to a string of stack traces + * @param le the exception + * + * @return the stack trace + */ + def showException(le: Throwable): String = { + val ret = "Message: " + le.toString + "\n\t" + + le.getStackTrace.map(_.toString).mkString("\n\t") + "\n" + + val also = le.getCause match { + case null => "" + case sub: Throwable => "\nCaught and thrown by:\n" + showException(sub) + } + + ret + also + } +} diff --git a/src/main/scala/inc/pyc/config/GoogleAnalytics.scala b/src/main/scala/inc/pyc/config/GoogleAnalytics.scala @@ -0,0 +1,62 @@ +package inc.pyc +package config + +import net.liftweb.util.Props +import net.liftweb.common.Loggable +import net.liftweb.http.js.JsCmd +import net.liftweb.http.Req +import net.liftweb.http.LiftSession +import net.liftweb.http.S + +/* + * Taken from: + * https://github.com/d6y/liftmodules-googleanalytics/blob/master/src/main/scala/bootstrap/liftmodules/GoogleAnalytics.scala + * + * Needed Google Analytics Universal for Async. + * TODO: switch to module after enhancement has been implemented. (issue #4) + * https://github.com/d6y/liftmodules-googleanalytics/issues/4 + */ +object GoogleAnalytics extends Loggable { + + def init: Unit = init( ()⇒true ) + + def init(includeTest: () ⇒ Boolean): Unit = Props.get("google.analytics.id") map Async.headJs foreach { js => + def addTracking(s: LiftSession, r: Req) : Unit = if (includeTest()) S.putInHead(js) + LiftSession.onBeginServicing = addTracking _ :: LiftSession.onBeginServicing + } + + // noticeJs is by-name to allow you to side-effect (eg., set cookies) + def alertUser(cond: () ⇒ Boolean)(noticeJs: ⇒ JsCmd): Unit = { + + def addNotice(s: LiftSession, r: Req) : Unit = try { + if (cond()) S.appendJs(noticeJs) + } catch { + case e : Throwable => logger.error("Unhandled exception from alertUser", e) + } + + LiftSession.onBeginServicing = addNotice _ :: LiftSession.onBeginServicing + } + + object dsl { + object only { + def when(f: => Boolean) = f _ + } + } + + +} + +object Async { + + def headJs(id: String): xml.Elem = xml.XML.loadString(s""" +<script> +(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ +(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), +m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) +})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + +ga('create', '$id', 'auto'); +ga('send', 'pageview'); +</script> + """) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/config/MongoConfig.scala b/src/main/scala/inc/pyc/config/MongoConfig.scala @@ -0,0 +1,55 @@ +package inc.pyc +package config + +import net.liftweb._ +import common._ +import http._ +import json._ +import mongodb._ +import util.Props + +import com.mongodb.{DBAddress, MongoClient} + +object MongoConfig extends Factory with Loggable { + + // configure your MongoMetaRecords to use this. See lib/RogueMetaRecord.scala. + val defaultId = new FactoryMaker[MongoIdentifier](DefaultMongoIdentifier) {} + + def init() { + /** + * First checks for existence of mongo.default.url. If not found, then + * checks for mongo.default.host, port, and name. Uses defaults if those + * are not found. + */ + val defaultDbAddress = Props.get("mongo.default.url") + .map(url => new DBAddress(url)) + .openOr(new DBAddress( + Props.get("mongo.default.host", "127.0.0.1"), + Props.getInt("mongo.default.port", 27017), + Props.get("mongo.default.name", "pyd") + )) + + /* + * If mongo.default.user, and pwd are defined, configure Mongo using authentication. + */ + (Props.get("mongo.default.user"), Props.get("mongo.default.pwd")) match { + case (Full(user), Full(pwd)) => + MongoDB.defineDbAuth( + DefaultMongoIdentifier, + new MongoClient(defaultDbAddress), + defaultDbAddress.getDBName, + user, + pwd + ) + logger.info("MongoDB inited using authentication: %s".format(defaultDbAddress.toString)) + case _ => + MongoDB.defineDb( + DefaultMongoIdentifier, + new MongoClient(defaultDbAddress), + defaultDbAddress.getDBName + ) + logger.info("MongoDB inited: %s".format(defaultDbAddress.toString)) + } + } +} + diff --git a/src/main/scala/inc/pyc/config/Site.scala b/src/main/scala/inc/pyc/config/Site.scala @@ -0,0 +1,87 @@ +package inc.pyc +package config + +import lib.NgUIRouterFactory._ +import model.User + +import net.liftweb._ +import common._ +import http.{S, OkResponse, RedirectResponse, RequestVar} +import sitemap._ +import sitemap.Loc._ +import net.liftmodules.mongoauth.Locs + +object MenuGroups { + val SettingsGroup = LocGroup("settings") + val TopBarGroup = LocGroup("topbar") + val SiteMapGroup = LocGroup("sitemap") +} + +/* + * Wrapper for Menu locations + */ +case class MenuLoc(menu: Menu) { + lazy val url: String = S.contextPath+menu.loc.calcDefaultHref + lazy val fullUrl: String = S.hostAndPath+menu.loc.calcDefaultHref +} + +object Site extends Locs { + import MenuGroups._ + + val domain = "pydcoin.com" + + // locations (for top group) + val home = MenuLoc(Menu.i("Bitcoin ATM Services") / "index" >> TopBarGroup) + val locations = MenuLoc(Menu.i("ATM Locations") / "locations" >> TopBarGroup >> SiteMapGroup) + val whatsBitcoin = MenuLoc(Menu.i("What Is Bitcoin") / "what-is-bitcoin" >> SiteMapGroup) + val about = MenuLoc(Menu.i("About Us") / "about" >> TopBarGroup >> SiteMapGroup) + val blog = MenuLoc(Menu.i("Blog") / "blog" >> TopBarGroup >> RedirectBlog >> SiteMapGroup) + val faqs = MenuLoc(Menu.i("FAQs") / "faqs" >> TopBarGroup >> SiteMapGroup) + val atmHowTo = MenuLoc(Menu.i("How To Use ATM") / "how-to-use-atm" >> SiteMapGroup) + + val loginToken = MenuLoc(buildLoginTokenMenu) + val logout = MenuLoc(buildLogoutMenu) + private val profileParamMenu = Menu.param[User]("User", "Profile", + User.findByUsername _, + _.username.get + ) / "user" >> Loc.CalcValue(() => User.currentUser) + lazy val profileLoc = profileParamMenu.toLoc + + val settings = MenuLoc(Menu.i("Settings") / "settings" >> SettingsGroup >> RequireLoggedIn) + val password = MenuLoc(Menu.i("Password") / "settings" / "password" >> SettingsGroup >> RequireLoggedIn) + + val account = MenuLoc(Menu.i("Account") / "settings" / "account" >> SettingsGroup >> RequireLoggedIn) + val editProfile = MenuLoc(Menu("EditProfile", "Profile") / "settings" / "profile" >> SettingsGroup >> RequireLoggedIn) + + val register = MenuLoc(Menu.i("Register") / "register" >> RequireNotLoggedIn >> SiteMapGroup) + val login = MenuLoc(Menu.i("Login") / "login" >> RequireNotLoggedIn >> SiteMapGroup) + val forgotPassword = MenuLoc(Menu.i("Forgot Password") / "forgot-password" >> RequireNotLoggedIn) + + private def menus = List( + home.menu, + locations.menu, + whatsBitcoin.menu, + about.menu, + blog.menu, + faqs.menu, + login.menu, + register.menu, + atmHowTo.menu, + loginToken.menu, + logout.menu, + forgotPassword.menu, + settings.menu, + password.menu, + Menu.i("Error") / "error" >> Hidden, + Menu.i("404") / "404" >> Hidden, + Menu.i("Status") / "ping" >> Hidden >> CalcStateless(() => true ) >> EarlyResponse(() => Full(OkResponse())), + Menu.i("Throw") / "throw" >> EarlyResponse(() => throw new Exception("This is only a test.")) + ) + + /* + * Return a SiteMap needed for Lift + */ + def siteMap: SiteMap = SiteMap(menus:_*) + + private def RedirectBlog = EarlyResponse(() => Full(RedirectResponse("http://blog."+domain))) +} diff --git a/src/main/scala/inc/pyc/config/SmtpMailer.scala b/src/main/scala/inc/pyc/config/SmtpMailer.scala @@ -0,0 +1,52 @@ +package inc.pyc +package config + +import javax.mail.{Authenticator, PasswordAuthentication} +import javax.mail.internet.MimeMessage + +import net.liftweb._ +import common._ +import util._ + +/* + * A Mailer config object that uses Props and auto configures for gmail + * if detected. + */ +object SmtpMailer extends Loggable { + def init(): Unit = { + + var isAuth = Props.get("mail.smtp.auth", "false").toBoolean + + Mailer.customProperties = Props.get("mail.smtp.host", "localhost") match { + case "smtp.gmail.com" => // auto configure for gmail + isAuth = true + Map( + "mail.smtp.host" -> "smtp.gmail.com", + "mail.smtp.port" -> "587", + "mail.smtp.auth" -> "true", + "mail.smtp.starttls.enable" -> "true" + ) + case h => Map( + "mail.smtp.host" -> h, + "mail.smtp.port" -> Props.get("mail.smtp.port", "25"), + "mail.smtp.auth" -> isAuth.toString + ) + } + + //Mailer.devModeSend.default.set((m : MimeMessage) => logger.info("Sending Mime Message: "+m)) + + if (isAuth) { + (Props.get("mail.smtp.user"), Props.get("mail.smtp.pass")) match { + case (Full(username), Full(password)) => + logger.info("Smtp user: %s".format(username)) + logger.info("Smtp password length: %s".format(password.length)) + Mailer.authenticator = Full(new Authenticator() { + override def getPasswordAuthentication = new + PasswordAuthentication(username, password) + }) + logger.info("SmtpMailer inited") + case _ => logger.error("Username/password not supplied for Mailer.") + } + } + } +} diff --git a/src/main/scala/inc/pyc/lib/DependencyFactory.scala b/src/main/scala/inc/pyc/lib/DependencyFactory.scala @@ -0,0 +1,56 @@ +package inc.pyc +package lib + +import java.util.Date + +import net.liftweb._ +import http._ +import util._ +import common._ + +/** + * A factory for generating new instances of Date. You can create + * factories for each kind of thing you want to vend in your application. + * An example is a payment gateway. You can change the default implementation, + * or override the default implementation on a session, request or current call + * stack basis. + */ +object DependencyFactory extends Factory { + implicit object time extends FactoryMaker(Helpers.now _) + + /** + * objects in Scala are lazily created. The init() + * method creates a List of all the objects. This + * results in all the objects getting initialized and + * registering their types with the dependency injector + */ + private def init() { + List(time) + } + init() +} + +/* +/** + * Examples of changing the implementation + */ +sealed abstract class Changer { + def changeDefaultImplementation() { + DependencyFactory.time.default.set(() => new Date()) + } + + def changeSessionImplementation() { + DependencyFactory.time.session.set(() => new Date()) + } + + def changeRequestImplementation() { + DependencyFactory.time.request.set(() => new Date()) + } + + def changeJustForCall(d: Date) { + DependencyFactory.time.doWith(d) { + // perform some calculations here + } + } +} +*/ diff --git a/src/main/scala/inc/pyc/lib/NgUIRouter.scala b/src/main/scala/inc/pyc/lib/NgUIRouter.scala @@ -0,0 +1,196 @@ +package inc.pyc +package lib + +import scala.xml._ +import net.liftweb._ +import sitemap._ +import common._ +import http._ +import sitemap.Loc.{LocGroup, AnyLocParam} +import util._ +import Helpers._ +import net.liftmodules.extras.SnippetHelper + +/** + * Configure these settings during boot. + */ +object NgUIRouterFactory extends Factory { + + /** + * Ignore the UiRouter Loc Group and add all menus in sitemap. + */ + val html5mode = new FactoryMaker[Boolean](true) {} + + /** + * Ignore the UiRouter Loc Group and add all menus in sitemap. + */ + val ignoreUiRouterGroup = new FactoryMaker[Boolean](true) {} + + /** + * Default route when page lands on index. + */ + val defaultRoute = new FactoryMaker[Box[Menu]](Empty) {} + + /** + * List of all the routes. + */ + lazy val routes = new FactoryMaker[Seq[Menu]](findRoutes) {} + + /** + * Place this LocGroup on any Menu Items in Lift's SiteMap + * that will have a state in AngularJs UI-Router. + */ + val UiRouterGroup = LocGroup("uirouter") + + /* Finds menu items that are in UiRouterGroup group. */ + private def findRoutes = LiftRules.siteMap map { + siteMap => + if(ignoreUiRouterGroup.vend) + siteMap.menus + else + siteMap.menus.filter(_.loc.inGroup_?(UiRouterGroup.group.head)) + } openOr Nil +} + +trait NgUIRouterSnip extends SnippetHelper { + + /** + * Snippet to configures AngularJs module with all routes. + * + * Required attributes: + * - ngApp: Name of AngularJs module. + */ + def js: CssSel = + for{ + app <- S.attr("ngApp") ?~ "ngApp name is missing" + } yield "* *" #> { + import NgUIRouterFactory._ + + val otherwise = defaultRoute.vend map { + "$urlRouterProvider.otherwise('"+ + S.contextPath+_.loc.calcDefaultHref+"');" + } openOr "" + + app+".config(function($stateProvider, $urlRouterProvider,$locationProvider) {"+ + "$locationProvider.html5Mode("+html5mode.vend.toString+");"+ + otherwise+"$stateProvider"+ + routes.vend.map { menu => + + val templateUrl = S.contextPath+menu.loc.calcDefaultHref + val state: String = menu.loc.name.replaceAll(" ","").toLowerCase + + ".state('"+state+"',{url:'"+templateUrl+"',views:"+ + "{'viewA':{templateUrl:'"+templateUrl+".html?ajax'}}"+ + "})" + }.mkString+ + ";});" + } + + + /** + * Conditional Template Surround Snippet. Allows pages to be surrounded by a + * different template if it's accessed with ajax. + * + * Needed attributes are: + * - with: template name to surround page. + * - at: id of element in template to place the page. + * - withAjax: template name to surround page when accessed with ajax. + * + * When accessing with ajax, assure URL query parameter 'ajax' exists. + * + * NOTE: default.html in templates-hidden cannot exist + * (else Lift will auto-surround everything with default) + */ + def surround(ns: NodeSeq): NodeSeq = + for { + surroundWith <- S.attr("with") ?~ "Surround with not specified" + surroundWithAjax <- S.attr("withAjax") ?~ "Surround with ajax not specified" + at <- S.attr("at") ?~ "Surround at not specified" + } yield { + if(S.param("ajax").isDefined) + Templates("templates-hidden" :: surroundWithAjax :: Nil) map { + s"#$at" #> ns + } openOr Text("Template '"+surroundWithAjax+"' not found") + else + Templates("templates-hidden" :: surroundWith :: Nil) map { + s"#$at" #> ns + } openOr Text("Template '"+surroundWith+"' not found") + } +} + +/** + * Menu Group snippet. List of links grouped with LocGroup will + * have ui-sref attribute. If link points to /some/foo/bar, + * then ui-router state or ui-sref value will be some.foo.bar. + * + * Example snippet: data-lift="NgUIRouterMenu.group?group=topbar" + */ +trait NgUIRouterMenu extends SnippetHelper { + + private def buildUIRouterLink(name: String): NodeSeq = { + val options = for { + loc <- SiteMap.findAndTestLoc(name).toList + link <- loc.createDefaultLink + } yield { + + def uiSref(el: Elem): Elem = { + import NgUIRouterFactory._ + if(loc.inGroup_?(UiRouterGroup.group.head)) + el % ("ui-sref" -> loc.name.replaceAll(" ","").toLowerCase) + else + el + } + + val linkText = loc.linkText openOr Text(loc.name) + uiSref(<a href={link}>{linkText}</a>) + + } + options.headOption getOrElse NodeSeq.Empty + } + + def item: CssSel = { + val options = (for { + name: String <- S.attr("name") ?~ "Item name not specified" + } yield for { + loc <- SiteMap.findAndTestLoc(name) + link <- loc.createDefaultLink + } yield { + "* [ui-sref]" #> loc.name.replaceAll(" ","").toLowerCase & + "* [href]" #> link + }) openOr Empty + options + } + + /** + * Produces a menu UL from a group, for use with Bootstrap. + */ + def group = { + val menus: NodeSeq = + for { + group <- S.attr("group") ?~ "Group not specified" + sitemap <- LiftRules.siteMap ?~ "Sitemap is empty" + request <- S.request ?~ "Request is empty" + curLoc <- request.location ?~ "Current location is empty" + } yield ({ + sitemap.locForGroup(group) flatMap { loc => + val nonHiddenKids = loc.menu.kids.filterNot(_.loc.hidden) + + if (nonHiddenKids.length == 0) { + <li>{buildUIRouterLink(loc.name)}</li> + } + else { + val dropdown: NodeSeq = nonHiddenKids.map { kid => + <li>{buildUIRouterLink(kid.loc.name)}</li> + } + + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown">{loc.linkText.openOr(Text("Empty Name"))} <b class="caret"></b></a> + <ul class="dropdown-menu">{ dropdown }</ul> + </li> + } + } + }): NodeSeq + + "* *" #> menus + } +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/lib/RogueMetaRecord.scala b/src/main/scala/inc/pyc/lib/RogueMetaRecord.scala @@ -0,0 +1,18 @@ +package inc.pyc +package lib + +import config.MongoConfig + +import net.liftweb._ +import mongodb.record._ + +import com.foursquare.rogue._ + +/** + * A custom MongoMetaRecord that adds Rogue and an injectable MongoIdentifier. + */ +trait RogueMetaRecord[A <: MongoRecord[A]] extends MongoMetaRecord[A] with LiftRogue { + self: A => + + override def mongoIdentifier = MongoConfig.defaultId.vend +} diff --git a/src/main/scala/inc/pyc/model/AtmApplication.scala b/src/main/scala/inc/pyc/model/AtmApplication.scala @@ -0,0 +1,65 @@ +package inc.pyc +package model + +import field._ +import lib.RogueMetaRecord + +import net.liftweb._ +import mongodb.record._ +import mongodb.record.field._ +import record.field._ +import org.joda.time.DateTime + +class AtmApplication private () extends MongoRecord[AtmApplication] with ObjectIdPk[AtmApplication] { + def meta = AtmApplication + + object name extends StringField(this, 64) { + override def validations = + valMaxLen(64, "Business name must be 64 characters or less") _ :: + super.validations + } + + object email extends EmailField(this, 64) { + override def validations = + valMaxLen(64, "Email must be 64 characters or less") _ :: + super.validations + } + + object phone extends StringField(this, 25) { + override def validations = + valMaxLen(25, "Phone number must be 25 characters or less") _ :: + super.validations + } + + object bestTime extends TextareaField(this, 255) + + object address extends StringField(this, 255) { + override def validations = + valMaxLen(255, "Business address must be 255 characters or less") _ :: + super.validations + } + + object city extends StringField(this, 64) { + override def validations = + valMaxLen(64, "City must be 64 characters or less") _ :: + super.validations + } + + object state extends USStatesField(this) + + object website extends StringField(this, 255) { + override def validations = + valMaxLen(255, "Website must be 255 characters or less") _ :: + super.validations + } + + def whenCreated: DateTime = new DateTime(id.get.getTime) +} + +object AtmApplication extends AtmApplication with RogueMetaRecord[AtmApplication] { + import mongodb.BsonDSL._ + + override def collectionName = "atm.applications" + + ensureIndex((name.name -> 1), true) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/NearAtmNotify.scala b/src/main/scala/inc/pyc/model/NearAtmNotify.scala @@ -0,0 +1,58 @@ +package inc.pyc +package model + +import field._ +import lib.RogueMetaRecord + +import net.liftweb._ +import mongodb.record._ +import mongodb.record.field._ +import record.field._ +import org.joda.time.DateTime + +class NearAtmNotify private () extends MongoRecord[NearAtmNotify] with ObjectIdPk[NearAtmNotify] { + def meta = NearAtmNotify + + object fname extends StringField(this, 64) { + override def validations = + valMaxLen(64, "First name must be 64 characters or less") _ :: + super.validations + } + + object lname extends StringField(this, 64) { + override def validations = + valMaxLen(64, "Last name must be 64 characters or less") _ :: + super.validations + } + + object email extends EmailField(this, 64) { + override def validations = + valMaxLen(64, "Email must be 64 characters or less") _ :: + super.validations + } + + + object postal extends PostalCodeField(this, meta.usa) + + object city extends StringField(this, 64) { + override def validations = + valMaxLen(64, "City must be 64 characters or less") _ :: + super.validations + } + + object state extends USStatesField(this) + + def whenCreated: DateTime = new DateTime(id.get.getTime) +} + +object NearAtmNotify extends NearAtmNotify with RogueMetaRecord[NearAtmNotify] { + import mongodb.BsonDSL._ + + override def collectionName = "atm.nearnotify" + + ensureIndex((email.name -> 1), true) + + object usa extends CountryField(NearAtmNotify.createRecord) { + override def defaultValue = Countries.USA + } +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/SearchedPostal.scala b/src/main/scala/inc/pyc/model/SearchedPostal.scala @@ -0,0 +1,44 @@ +package inc.pyc +package model + +import lib.RogueMetaRecord + +import net.liftweb._ +import mongodb.record._ +import mongodb.record.field._ +import record.field._ + +class SearchedPostal private () extends MongoRecord[SearchedPostal] with ObjectIdPk[SearchedPostal] { + def meta = SearchedPostal + + /* The total amount of times postal code was searched. */ + object total extends IntField(this) + + /* The postal code that was searched. */ + object postal extends PostalCodeField(this, meta.usa) + + override def save = { + val qry = SearchedPostal.find(this) + + if(qry.get().isDefined) { + qry.modify(_.total inc 1).updateOne() + this + } else + total(total.get + 1) + super.save + } +} + +object SearchedPostal extends SearchedPostal with RogueMetaRecord[SearchedPostal] { + import mongodb.BsonDSL._ + + override def collectionName = "atm.find" + + ensureIndex((postal.name -> 1), true) + + def find(postal: SearchedPostal) = this.where(_.postal eqs postal.postal.get) + + object usa extends CountryField(SearchedPostal.createRecord) { + override def defaultValue = Countries.USA + } +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/User.scala b/src/main/scala/inc/pyc/model/User.scala @@ -0,0 +1,170 @@ +package inc.pyc +package model + +import lib.RogueMetaRecord + +import org.bson.types.ObjectId +import org.joda.time.DateTime + +import net.liftweb._ +import common._ +import http.{StringField => _, BooleanField => _, _} +import mongodb.record.field._ +import record.field._ + +import net.liftmodules.mongoauth._ +import net.liftmodules.mongoauth.field._ +import net.liftmodules.mongoauth.model._ + +class User private () extends ProtoAuthUser[User] with ObjectIdPk[User] { + def meta = User + + def userIdAsString: String = id.toString + + object fname extends StringField(this, 64) { + override def validations = + valMaxLen(64, "First Name must be 64 characters or less") _ :: + super.validations + } + + object lname extends StringField(this, 64) { + override def validations = + valMaxLen(64, "First Name must be 64 characters or less") _ :: + super.validations + } + + def whenCreated: DateTime = new DateTime(id.get.getTime) +} + +object User extends User with ProtoAuthUserMeta[User] with RogueMetaRecord[User] with Loggable { + import mongodb.BsonDSL._ + + override def collectionName = "user.users" + + ensureIndex((email.name -> 1), true) + ensureIndex((username.name -> 1), true) + + def findByEmail(in: String): Box[User] = find(email.name, in) + def findByUsername(in: String): Box[User] = find(username.name, in) + + def findByStringId(id: String): Box[User] = + if (ObjectId.isValid(id)) find(new ObjectId(id)) + else Empty + + override def onLogIn: List[User => Unit] = List(user => User.loginCredentials.remove()) + override def onLogOut: List[Box[User] => Unit] = List( + x => logger.debug("User.onLogOut called."), + boxedUser => boxedUser.foreach { u => + ExtSession.deleteExtCookie() + } + ) + + /* + * MongoAuth vars + */ + private lazy val siteName = MongoAuth.siteName.vend + private lazy val sysUsername = MongoAuth.systemUsername.vend + private lazy val indexUrl = MongoAuth.indexUrl.vend + private lazy val registerUrl = MongoAuth.registerUrl.vend + private lazy val loginTokenAfterUrl = MongoAuth.loginTokenAfterUrl.vend + + /* + * LoginToken + */ + override def handleLoginToken: Box[LiftResponse] = { + val resp = S.param("token").flatMap(LoginToken.findByStringId) match { + case Full(at) if (at.expires.isExpired) => { + at.delete_! + RedirectWithState(indexUrl, RedirectState(() => { S.error("Login token has expired") })) + } + case Full(at) => find(at.userId.get).map(user => { + if (user.validate.length == 0) { + user.verified(true) + user.save + logUserIn(user) + at.delete_! + RedirectResponse(loginTokenAfterUrl) + } + else { + at.delete_! + regUser(user) + RedirectWithState(registerUrl, RedirectState(() => { S.notice("Please complete the registration form") })) + } + }).openOr(RedirectWithState(indexUrl, RedirectState(() => { S.error("User not found") }))) + case _ => RedirectWithState(indexUrl, RedirectState(() => { S.warning("Login token not provided") })) + } + + Full(resp) + } + + // send an email to the user with a link for logging in + def sendLoginToken(user: User): Unit = { + import net.liftweb.util.Mailer._ + + val token = LoginToken.createForUserId(user.id.get) + + val msgTxt = + """ + |Someone requested a link to log in to your %s account. + | + |If you did not request this, you can safely ignore it. It will expire 48 hours from the time this message was sent. + | + |Follow the link below or copy and paste it into your internet browser. + | + |%s + | + |Kindest Regards, + | + |%s + """.format(siteName, token.url, sysUsername).stripMargin + + sendMail( + From(MongoAuth.systemFancyEmail), + Subject("%s Account: Login".format(siteName)), + To(user.fancyEmail), + PlainMailBodyType(msgTxt) + ) + } + + /* + * ExtSession + */ + def createExtSession(uid: ObjectId) = ExtSession.createExtSession(uid) + + /* + * Test for active ExtSession. + */ + def testForExtSession: Box[Req] => Unit = { + ignoredReq => { + if (currentUserId.isEmpty) { + ExtSession.handleExtSession match { + case Full(es) => find(es.userId.get).foreach { user => logUserIn(user, false) } + case Failure(msg, _, _) => + logger.warn("Error logging user in with ExtSession: %s".format(msg)) + case Empty => + } + } + } + } + + // used during login process + object loginCredentials extends SessionVar[LoginCredentials](LoginCredentials("")) + object regUser extends SessionVar[User](createRecord.email(loginCredentials.is.email)) +} + +case class LoginCredentials(email: String, isRememberMe: Boolean = false) + +object SystemUser { + private val username = "pyd" + private val email = "noreply@pydcoin.com" + + lazy val user: User = User.find("username", username) openOr { + User.createRecord + .fname("PYD") + .username(username) + .email(email) + .verified(true) + .password("abc123", true) + .save + } +} diff --git a/src/main/scala/inc/pyc/model/field/USStateField.scala b/src/main/scala/inc/pyc/model/field/USStateField.scala @@ -0,0 +1,43 @@ +package inc.pyc +package model +package field + +import net.liftweb._ +import http._ +import util.Helpers._ +import record._ +import field._ +import json.JsonAST._ +import common._ + +object USStates extends Enumeration { + + val Alabama, Alaska, Arizona, Arkansas, California, Colorado, Connecticut, Delaware, Florida, Georgia, + Hawaii, Idaho, Illinois, Indiana, Iowa, Kansas, Kentucky, Louisiana, Maine, Maryland, + Massachusetts, Michigan, Minnesota, Mississippi, Missouri, Montana, Nebraska, Nevada, New_Hampshire, New_Jersey, + New_Mexico, New_York, North_Carolina, North_Dakota, Ohio, Oklahoma, Oregon, Pennsylvania, Rhode_Island, South_Carolina, + South_Dakota, Tennessee, Texas, Utah, Vermont, Virginia, Washington, West_Virginia, Wisconsin, Wyoming = Value + + class States extends Val { + override def toString = super.toString.replace("_", " ") + } + + def options: List[(String, String)] = + USStates.values.map(i => (i.toString, i.toString)).toList + + def stateSelect(s: String) = tryo(USStates.withName(s)) +} + + +class USStatesField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends EnumField(rec, USStates) { + override def setFromJValue(jvalue: JValue): Box[USStatesField.this.MyType] = { + val stringToInt = jvalue transform { + case JString(s) => + val value: Box[Int] = USStates.stateSelect(s).map(_.id) + value.map(JInt(_)).openOr(JNothing) + case _ => JNothing + } + super.setFromJValue(stringToInt) + } +} + diff --git a/src/main/scala/inc/pyc/snippet/AngularSnips.scala b/src/main/scala/inc/pyc/snippet/AngularSnips.scala @@ -0,0 +1,85 @@ +package inc.pyc +package snippet + +import xml._ +import net.liftweb._ +import util._ +import Helpers._ +import common._ +import json.JsonAST._ +import http._ +import js._ +import JsCmds._ +import JE.JsVar + +/** + * Snippet classes with AngularJs server-side code need to extend trait. + * Its js function will be called at the end of Body element if it is added + * to AngularServerSide's 'snips' FactoryMaker. + */ +trait AngularSnippet { + def roundTrips: RoundTripInfo +} + +/** + * Place this snippet at the end of your end of Body + * + * <script data-lift="AngularServerSide.js"></script> + */ +class AngularServerSide extends Factory { + + /* All AngularJs server-side snippets. */ + private val snips = new FactoryMaker[List[AngularSnippet]](List( + new SearchedPostalSnip, + new AtmApplicationSnip, + new NearAtmNotifySnip, + new UserRegistrationSnip, + new UserLoginSnip, + new ForgotPasswordSnip + )) {} + + def js: CssSel = + "* *" #> (for { + sess <- S.session + } yield { + def roundTrips: List[RoundTripInfo] = snips.vend.map(_.roundTrips) + + val script = SetExp(JsVar("window", "backend"), + sess.buildRoundtrip(List[RoundTripInfo](roundTrips: _*))) + script + }) +} + +/** + * Builds Alert JSON messages for server-side responses. + */ +object NgAlert extends Logger { + + private def msgBox(msgType: String, msg: NodeSeq): JValue = + JObject(List( + JField("msg_type", JString(msgType)), + JField("msg", JString(msg.toString)))) + + def success(msg: NodeSeq): JValue = msgBox("success", msg) + def success(msg: String): JValue = success(Text(msg)) + def success: JValue = success(Text("")) + + def danger(msg: NodeSeq, errors: List[FieldError]): JValue = { + debug(errors) + + msgBox("danger", msg ++ + errors.foldLeft(<ul></ul>)((el,err) => el.copy(child = el.child :+ <li>{err.msg}</li>))) + } + + def danger(msg: String, errors: List[FieldError] = Nil): JValue = danger(Text(msg), errors) + def danger: JValue = danger(Text(""), Nil) + + def info(msg: NodeSeq): JValue = msgBox("info", msg) + def info(msg: String): JValue = info(Text(msg)) + def info: JValue = info(Text("")) + + def warning(msg: NodeSeq): JValue = msgBox("warning", msg) + def warning(msg: String): JValue = warning(Text(msg)) + def warning: JValue = warning(Text("")) + +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/AtmApplicationSnip.scala b/src/main/scala/inc/pyc/snippet/AtmApplicationSnip.scala @@ -0,0 +1,44 @@ +package inc.pyc +package snippet + +import model._ +import field._ +import net.liftweb._ +import common._ +import json.JsonAST._ +import util._ +import Helpers._ +import http._ +import js._ +import JsCmds._ +import SHtml._ +import JE.JsVar + +class AtmApplicationSnip extends AngularSnippet { + + def save(model: JValue): JValue = { + val rec = AtmApplication.createRecord + rec.setFieldsFromJValue(model) + + rec.validate match { + case Nil => + rec.save + NgAlert.success( + <i class="fa-fw fa fa-thumbs-o-up"></i> ++ + <strong>{ s"Your Bitcoin ATM application for ${rec.name.get} has been received." }</strong> ++ + <span>{s" We will contact you when we are ready. Thank you for applying."}</span> + ) + case errors => + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>{ s"Your application could not be processed." }</strong>, + errors + ) + } + } + + def render: CssSel = + "@state" #> select(USStates.options, Empty, USStates.stateSelect, "name" -> "state") + + override def roundTrips = ("saveAtmApplication" -> save _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/ForgotPasswordSnip.scala b/src/main/scala/inc/pyc/snippet/ForgotPasswordSnip.scala @@ -0,0 +1,40 @@ +package inc.pyc +package snippet + +import model._ +import net.liftweb._ +import common._ +import http.S +import json._ +import JsonAST.{JValue, JNull} + +class ForgotPasswordSnip extends AngularSnippet { + + def forgotPassword(model: JValue): JValue = { + (for { + JString(e) <- model \ "email" + } yield { + val email = e.toLowerCase.trim + + User.findByEmail(email) match { + case Full(user) => + User.sendLoginToken(user) + User.loginCredentials.remove() + + NgAlert.success( + <i class="fa-fw fa fa-thumbs-o-up"></i> ++ + <strong>An email has been sent to you with instructions for accessing your account.</strong>) + case _ => + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>Your email could not be found.</strong>, Nil) + } + }).headOption.getOrElse { + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>The information submitted could not be processed.</strong>, Nil) + } + } + + override def roundTrips = ("forgotPassword" -> forgotPassword _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/NearAtmNotifySnip.scala b/src/main/scala/inc/pyc/snippet/NearAtmNotifySnip.scala @@ -0,0 +1,48 @@ +package inc.pyc +package snippet + +import model._ +import field._ +import net.liftweb._ +import common._ +import json.JsonAST._ +import util._ +import Helpers._ +import http._ +import js._ +import JsCmds._ +import SHtml._ +import JE.JsVar + +class NearAtmNotifySnip extends AngularSnippet { + + def save(model: JValue): JValue = { + val rec = NearAtmNotify.createRecord + rec.setFieldsFromJValue(model) + + rec.validate match { + case Nil => + rec.save + NgAlert.success( + <div> + <i class="fa-fw fa fa-thumbs-o-up"></i> + <strong>{ s"Hi ${rec.fname.get}, your notification has been received." }</strong> + <span>{s" We will notify you when there is an ATM near ${rec.city.get}, ${rec.state.get}."}</span> + </div> + ) + case errors => + NgAlert.danger( + <div> + <i class="fa-fw fa fa-thumbs-o-down"></i> + <strong>{ s"Hi ${rec.fname.get}, your notification was not submitted successfully." }</strong> + </div>, + errors + ) + } + } + + def render: CssSel = + "@state" #> select(USStates.options, Empty, USStates.stateSelect, "name" -> "state") + + override def roundTrips = ("saveNearAtmNotify" -> save _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/SearchedPostalSnip.scala b/src/main/scala/inc/pyc/snippet/SearchedPostalSnip.scala @@ -0,0 +1,40 @@ +package inc.pyc +package snippet + +import model._ +import field._ +import xml._ +import net.liftweb._ +import common._ +import json.JsonAST._ +import util._ +import Helpers._ +import http._ +import js._ +import JsCmds._ +import SHtml._ +import JE.JsVar + +class SearchedPostalSnip extends AngularSnippet { + + def search(model: JValue): JValue = { + val rec = SearchedPostal.createRecord + rec.setFieldsFromJValue(model) + + rec.validate match { + case Nil => + rec.save + NgAlert.success + case errors => + NgAlert.danger( + <div> + <i class="fa-fw fa fa-thumbs-o-down"></i> + <strong>{"Something went wrong. We apologize for the inconvenience."}</strong> + </div>, + errors + ) + } + } + + override def roundTrips = ("searchAtmPostalCode" -> search _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/Sitemap.scala b/src/main/scala/inc/pyc/snippet/Sitemap.scala @@ -0,0 +1,46 @@ +package inc.pyc +package snippet + +import config._ +import lib._ + +import org.joda.time.DateTime +import net.liftweb._ +import http._ +import rest.RestHelper +import util._ +import Helpers._ +import common._ + +object Sitemap extends RestHelper { + serve { + case Req("sitemap" :: Nil, _, GetRequest) => + XmlResponse( + S.render(<lift:embed what="sitemap" />, + S.request.get.request).head) + } +} + +class SitemapContent { + + case class Post(url: String, date: DateTime) + + lazy val baseEntry = NgUIRouterFactory.defaultRoute.vend map { + _.loc.calcDefaultHref + } openOr "" + + lazy val entries = + LiftRules.siteMap map { + _.locForGroup("sitemap").map(_.calcDefaultHref).map(Post(_, new DateTime)) + } openOr Nil + + def base: CssSel = + "loc *" #> "http://%s%s".format(Site.domain, baseEntry) & + "lastmod *" #> (new DateTime).toString("yyyy-MM-dd'T'HH:mm:ss.SSSZZ") + + def list: CssSel = + "url *" #> entries.map(post => + "loc *" #> "http://%s%s".format(Site.domain, post.url) & + "lastmod *" #> post.date.toString("yyyy-MM-dd'T'HH:mm:ss.SSSZZ")) + +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/UserLoginSnip.scala b/src/main/scala/inc/pyc/snippet/UserLoginSnip.scala @@ -0,0 +1,49 @@ +package inc.pyc +package snippet + +import model._ +import net.liftweb._ +import common._ +import json._ +import JsonAST.{JValue, JString, JBool} +import net.liftmodules.mongoauth.model.ExtSession + +class UserLoginSnip extends AngularSnippet { + + def login(model: JValue): JValue = { + (for { + JString(e) <- model \ "email" + JString(password) <- model \ "password" + JBool(remember) <- model \ "remember" + } yield { + val email = e.toLowerCase.trim + User.loginCredentials(LoginCredentials(email, remember)) + + if(!(email.length > 0) || !(password.length > 0)) { + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>{"Your login information could not be processed."}</strong>, Nil) + } + else { + User.findByEmail(email) match { + case Full(user) if (user.password.isMatch(password)) => + User.logUserIn(user, remember) + if (remember) User.createExtSession(user.id.get) + else ExtSession.deleteExtCookie() + NgAlert.success + case _ => + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>{"Invalid Credentials: "}</strong> + <p>Assure your email and password are correct.</p>, Nil) + } + } + }).headOption.getOrElse { + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>{"Your login information could not be processed."}</strong>, Nil) + } + } + + override def roundTrips = ("loginUser" -> login _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/UserRegistrationSnip.scala b/src/main/scala/inc/pyc/snippet/UserRegistrationSnip.scala @@ -0,0 +1,45 @@ +package inc.pyc +package snippet + +import model._ +import field._ +import net.liftweb._ +import common._ +import json.JsonAST._ +import util._ +import Helpers._ +import http._ +import js._ +import JsCmds._ +import SHtml._ +import JE.JsVar + +class UserRegistrationSnip extends AngularSnippet { + + def register(model: JValue): JValue = { + val rec = User.createRecord + rec.setFieldsFromJValue(model) + rec.password(Helpers.randomString(20)) + rec.password.hashIt + + rec.validate match { + case Nil => + rec.save + User.sendLoginToken(rec) + + NgAlert.success( + <i class="fa-fw fa fa-thumbs-o-up"></i> ++ + <strong>{ s"Thank you for registering, ${rec.fname.get}." }</strong> ++ + <span>{" An email has been sent for you to log in."}</span> + ) + case errors => + NgAlert.danger( + <i class="fa-fw fa fa-thumbs-o-down"></i> ++ + <strong>{"Your application could not be processed."}</strong>, + errors + ) + } + } + + override def roundTrips = ("registerUser" -> register _) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/UserScreens.scala b/src/main/scala/inc/pyc/snippet/UserScreens.scala @@ -0,0 +1,134 @@ +package inc.pyc +package snippet + +import config.Site +import model._ + +import scala.xml._ + +import net.liftweb._ +import common._ +import http.{LiftScreen, S} +import util.FieldError +import util.Helpers._ + +import net.liftmodules.extras.Gravatar + +/* + * Use for editing the currently logged in user only. + */ +sealed trait BaseCurrentUserScreen extends BaseScreen { + object userVar extends ScreenVar(User.currentUser.openOr(User.createRecord)) + + override def localSetup { + Referer(Site.settings.url) + } +} + +/* +object AccountScreen extends BaseCurrentUserScreen { + addFields(() => userVar.is.accountScreenFields) + + def finish() { + userVar.is.save + S.notice("Account settings saved") + } +} +*/ + +sealed trait BasePasswordScreen { + this: LiftScreen => + + def pwdName: String = "Password" + def pwdMinLength: Int = 8 + def pwdMaxLength: Int = 32 + + val passwordField = password(pwdName, "", trim, + valMinLen(pwdMinLength, "Password must be at least "+pwdMinLength+" characters"), + valMaxLen(pwdMaxLength, "Password must be "+pwdMaxLength+" characters or less"), + "tabindex" -> "1" + ) + val confirmPasswordField = password("Confirm Password", "", trim, "tabindex" -> "1") + + def passwordsMustMatch(): Errors = { + if (passwordField.is != confirmPasswordField.is) + List(FieldError(confirmPasswordField, "Passwords must match")) + else Nil + } +} + + +object PasswordScreen extends BaseCurrentUserScreen with BasePasswordScreen { + override def pwdName = "New Password" + override def validations = passwordsMustMatch _ :: super.validations + + def finish() { + userVar.is.password(passwordField.is) + userVar.is.password.hashIt + userVar.is.save + } +} + +/* + * Use for editing the currently logged in user only. + */ +/* +object ProfileScreen extends BaseCurrentUserScreen { + def gravatarHtml = + <span> + <div class="gravatar"> + {Gravatar.imgTag(userVar.is.email.get, 60)} + </div> + <div class="gravatar"> + <h4>Change your avatar at <a href="http://gravatar.com" target="_blank">Gravatar.com</a></h4> + <p> + We're using {userVar.is.email.get}. It may take time for changes made on gravatar.com to appear on our site. + </p> + </div> + </span> + + val gravatar = displayOnly("Picture", gravatarHtml) + + addFields(() => userVar.is.profileScreenFields) + + def finish() { + userVar.is.save + S.notice("Profile settings saved") + } +} +*/ + +// this is needed to keep these fields and the password fields in the proper order +/* +trait BaseRegisterScreen extends BaseScreen { + object userVar extends ScreenVar(User.regUser.is) + + addFields(() => userVar.is.registerScreenFields) +} +*/ +/* + * Use for creating a new user. + */ +/* +object RegisterScreen extends BaseRegisterScreen with BasePasswordScreen { + override def validations = passwordsMustMatch _ :: super.validations + + val rememberMe = builder("", User.loginCredentials.is.isRememberMe, ("tabindex" -> "1")) + .help(Text("Remember me when I come back later.")) + .make + + override def localSetup { + Referer(Site.home.url) + } + + def finish() { + val user = userVar.is + user.password(passwordField.is) + user.password.hashIt + user.save + User.logUserIn(user, true) + if (rememberMe) User.createExtSession(user.id.get) + S.notice("Thanks for signing up!") + } +} +*/ +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/UserSnips.scala b/src/main/scala/inc/pyc/snippet/UserSnips.scala @@ -0,0 +1,180 @@ +package inc.pyc +package snippet + +import config.Site +import model.{User, LoginCredentials} + +import scala.xml._ + +import net.liftweb._ +import common._ +import http.{DispatchSnippet, S, SHtml, StatefulSnippet} +import http.js.JsCmd +import http.js.JsCmds._ +import util._ +import Helpers._ + +import net.liftmodules.extras.{Gravatar, SnippetHelper} +import net.liftmodules.mongoauth.LoginRedirect +import net.liftmodules.mongoauth.model.ExtSession + +sealed trait UserSnippet extends SnippetHelper with Loggable { + + protected def user: Box[User] + + protected def serve(snip: User => NodeSeq): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + snip(u) + }): NodeSeq + + protected def serve(html: NodeSeq)(snip: User => CssSel): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + snip(u)(html) + }): NodeSeq + + def username(xhtml: NodeSeq): NodeSeq = serve { user => + Text(user.username.get) + } + + def name(xhtml: NodeSeq): NodeSeq = serve { user => + val name = user.fname.get + " " + user.lname.get + if (name.length > 1) Text(name) + else Text(user.username.get) + } + + def title(xhtml: NodeSeq): NodeSeq = serve { user => + <title data-lift="Menu.title">PYD: %*% - {user.username.get}</title> + } +} + +object CurrentUser extends UserSnippet { + protected def user = User.currentUser +} + +object ProfileLocUser extends UserSnippet { + + protected def user = Site.profileLoc.currentValue + + import java.text.SimpleDateFormat + + val df = new SimpleDateFormat("MMM d, yyyy") + + def profile(html: NodeSeq): NodeSeq = serve(html) { user => + val editLink: NodeSeq = + if (User.currentUser.filter(_.id.get == user.id.get).isDefined) + <a href={Site.editProfile.url} class="btn btn-info"><i class="icon-edit icon-white"></i> Edit Your Profile</a> + else + NodeSeq.Empty + + "#id_avatar *" #> Gravatar.imgTag(user.email.get) & + "#id_name *" #> <h3>{user.fname.get}</h3> & + "#id_whencreated" #> df.format(user.whenCreated.toDate).toString & + "#id_editlink *" #> editLink + } +} + +object UserLogin extends Loggable { + + def render = { + // form vars + var password = "" + var hasPassword = false + var remember = User.loginCredentials.is.isRememberMe + + val radios = SHtml.radioElem[Boolean]( + Seq(false, true), + Full(hasPassword) + )(it => it.foreach(hasPassword = _)) + + def doSubmit(): JsCmd = { + S.param("email").map(e => { + val email = e.toLowerCase.trim + // save the email and remember entered in the session var + User.loginCredentials(LoginCredentials(email, remember)) + + if (hasPassword && email.length > 0 && password.length > 0) { + User.findByEmail(email) match { + case Full(user) if (user.password.isMatch(password)) => + logger.debug("pwd matched") + User.logUserIn(user, true) + if (remember) User.createExtSession(user.id.get) + else ExtSession.deleteExtCookie() + RedirectTo(LoginRedirect.openOr(Site.home.url)) + case _ => + S.error("Invalid credentials") + Noop + } + } + else if (hasPassword && email.length <= 0 && password.length > 0) { + S.error("id_email_err", "Please enter an email") + Noop + } + else if (hasPassword && password.length <= 0 && email.length > 0) { + S.error("id_password_err", "Please enter a password") + Noop + } + else if (hasPassword) { + S.error("id_email_err", "Please enter an email") + S.error("id_password_err", "Please enter a password") + Noop + } + else if (email.length > 0) { + // see if email exists in the database + User.findByEmail(email) match { + case Full(user) => + User.sendLoginToken(user) + User.loginCredentials.remove() + S.notice("An email has been sent to you with instructions for accessing your account") + Noop + case _ => + RedirectTo(Site.register.url) + } + } + else { + S.error("id_email_err", "Please enter an email address") + Noop + } + }) openOr { + S.error("id_email_err", "Please enter an email address") + Noop + } + } + + "#id_email [value]" #> User.loginCredentials.is.email & + "#id_password" #> SHtml.password(password, password = _) & + "#no_password" #> radios(0) & + "#yes_password" #> radios(1) & + "name=remember" #> SHtml.checkbox(remember, remember = _) & + "#id_submit" #> SHtml.hidden(doSubmit) + } +} + +object UserTopbar { + def render = { + User.currentUser match { + case Full(user) => + <ul class="nav navbar-nav navbar-right" id="user"> + <li class="dropdown" data-dropdown="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown"> + {Gravatar.imgTag(user.email.get, 20)} + <span>{user.username.get}</span> + <b class="caret"></b> + </a> + <ul class="dropdown-menu"> + <li><a href={Site.profileLoc.calcHref(user)}><i class="icon-user"></i> Profile</a></li> + <li><lift:Menu.item name="Account" donthide="true" linktoself="true"><i class="icon-cog"></i> Settings</lift:Menu.item></li> + <li class="divider"></li> + <li><lift:Menu.item name="Logout" donthide="true"><i class="icon-off"></i> Log Out</lift:Menu.item></li> + </ul> + </li> + </ul> + case _ if (S.request.flatMap(_.location).map(_.name).filterNot(it => List("Login", "Register").contains(it)).isDefined) => + <a href="/login" class="btn btn-default navbar-btn">Sign In</a> + case _ => NodeSeq.Empty + } + } +} diff --git a/src/main/scala/inc/pyc/snippet/UtilSnips.scala b/src/main/scala/inc/pyc/snippet/UtilSnips.scala @@ -0,0 +1,31 @@ +package inc.pyc +package snippet + +import lib._ +import scala.xml.NodeSeq +import net.liftweb._ +import common._ +import util._ +import Helpers._ +import http._ +import js._ +import net.liftmodules.extras._, snippet._ + +/* + * Base all LiftScreens off this. Currently configured to use bootstrap 3. + */ +abstract class BaseScreen extends Bootstrap3Screen { + override def defaultToAjax_? = true +} + +object Assets extends AssetLoader + +object NgUIRouter extends NgUIRouterSnip + +object Menus extends NgUIRouterMenu + +object ProductionOnly { + def render(in: NodeSeq): NodeSeq = + if (Props.productionMode) in + else NodeSeq.Empty +}