├── .gitignore
├── README
├── project
├── build.properties
└── build
│ └── LiftProject.scala
└── src
├── main
├── resources
│ ├── .keep
│ └── props
│ │ └── default.props
├── scala
│ ├── bootstrap
│ │ └── liftweb
│ │ │ └── Boot.scala
│ └── code
│ │ ├── comet
│ │ └── .keep
│ │ ├── db
│ │ └── TodoDB.scala
│ │ ├── lib
│ │ └── MongoProtoUser.scala
│ │ ├── model
│ │ ├── ToDo.scala
│ │ └── User.scala
│ │ ├── snippet
│ │ ├── TD.scala
│ │ └── Util.scala
│ │ └── view
│ │ └── .keep
└── webapp
│ ├── WEB-INF
│ └── web.xml
│ ├── images
│ └── ajax-loader.gif
│ ├── index.html
│ ├── static
│ └── index.html
│ └── templates-hidden
│ ├── default.html
│ └── wizard-all.html
└── test
├── resources
└── .keep
└── scala
├── LiftConsole.scala
├── RunWebApp.scala
└── code
└── AppTest.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | # use glob syntax.
2 | syntax: glob
3 | *.ser
4 | *.class
5 | *~
6 | *.bak
7 | *.off
8 | *.old
9 | .DS_Store
10 |
11 | # logs
12 | derby.log
13 |
14 | # eclipse conf file
15 | .settings
16 | .classpath
17 | .project
18 | .manager
19 |
20 | # building
21 | target
22 | build
23 | null
24 | tmp*
25 | dist
26 | test-output
27 |
28 | # sbt
29 | target
30 | lib_managed
31 | src_managed
32 | project/boot
33 |
34 | # db
35 | lift_proto*
36 |
37 | # other scm
38 | .svn
39 | .CVS
40 | .hg*
41 |
42 | # switch to regexp syntax.
43 | # syntax: regexp
44 | # ^\.pc/
45 |
46 | # IntelliJ
47 | *.iml
48 | *.ipr
49 | *.iws
50 | .idea
51 |
52 | # Pax Runner (for easy OSGi launching)
53 | runner
54 |
55 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Install Java, Simple Build Tool, and Git
2 |
3 | Clone this repository with:
4 |
5 | git clone git://github.com/ghostm/lift-todo-mongodb.git
6 |
7 | Then cd into lift-todo-mongodb and type:
8 | sbt
9 |
10 | At the sbt prompt, type:
11 | update
12 |
13 | Then:
14 | jetty-run
15 |
16 | Make sure MongoDB is running
17 |
18 | Point your browser to:
19 | http://localhost:8080/
20 |
21 | Voila: Lift's ToDo app using MongoDB
22 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | #Project properties
2 | #Fri Apr 23 11:24:20 PDT 2010
3 | project.organization=Lift
4 | project.name=Lift ToDo MongoDB
5 | sbt.version=0.7.4
6 | project.version=0.1
7 | def.scala.version=2.7.7
8 | build.scala.versions=2.8.1
9 | project.initialize=false
10 |
--------------------------------------------------------------------------------
/project/build/LiftProject.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 |
3 | class LiftProject(info: ProjectInfo) extends DefaultWebProject(info) {
4 | val liftVersion = "2.3-RC3"
5 |
6 | // uncomment the following if you want to use the snapshot repo
7 | //val scalatoolsSnapshot = ScalaToolsSnapshots
8 |
9 | // If you're using JRebel for Lift development, uncomment
10 | // this line
11 | // override def scanDirectories = Nil
12 |
13 | override def libraryDependencies = Set(
14 | "net.liftweb" %% "lift-webkit" % liftVersion % "compile->default",
15 | "net.liftweb" %% "lift-mongodb" % liftVersion % "compile->default",
16 | "net.liftweb" %% "lift-mongodb-record" % liftVersion % "compile->default",
17 | "org.mortbay.jetty" % "jetty" % "6.1.22" % "test->default",
18 | "junit" % "junit" % "4.5" % "test->default",
19 | "org.scala-tools.testing" %% "specs" % "1.6.7" % "test->default",
20 | "ch.qos.logback" % "logback-classic" % "0.9.26"
21 | ) ++ super.libraryDependencies
22 | }
--------------------------------------------------------------------------------
/src/main/resources/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/resources/.keep
--------------------------------------------------------------------------------
/src/main/resources/props/default.props:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/resources/props/default.props
--------------------------------------------------------------------------------
/src/main/scala/bootstrap/liftweb/Boot.scala:
--------------------------------------------------------------------------------
1 | package bootstrap.liftweb
2 |
3 | import _root_.net.liftweb.util._
4 | import _root_.net.liftweb.common._
5 | import _root_.net.liftweb.http._
6 | import _root_.net.liftweb.http.provider._
7 | import _root_.net.liftweb.sitemap._
8 | import _root_.net.liftweb.sitemap.Loc._
9 | import Helpers._
10 | import _root_.code.model._
11 | import _root_.code.db._
12 |
13 |
14 | /**
15 | * A class that's instantiated early and run. It allows the application
16 | * to modify lift's environment
17 | */
18 | class Boot {
19 | def boot {
20 | // where to search snippet
21 | LiftRules.addToPackages("code")
22 | // Build SiteMap
23 | val entries = Menu(Loc("Home", List("index"), "Home")) ::
24 | Menu(Loc("Static", Link(List("static"), true, "/static/index"),
25 | "Static Content")) ::
26 | User.sitemap
27 | LiftRules.setSiteMap(SiteMap(entries:_*))
28 |
29 | /*
30 | * Show the spinny image when an Ajax call starts
31 | */
32 | LiftRules.ajaxStart = Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)
33 |
34 | /*
35 | * Make the spinny image go away when it ends
36 | */
37 | LiftRules.ajaxEnd =
38 | Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd)
39 |
40 | LiftRules.early.append(makeUtf8)
41 |
42 | LiftRules.loggedInTest = Full(() => User.loggedIn_?)
43 |
44 | //Set up MongoDB
45 | TodoDB.setup
46 | }
47 |
48 | /**
49 | * Force the request to be UTF-8
50 | */
51 | private def makeUtf8(req: HTTPRequest) {
52 | req.setCharacterEncoding("UTF-8")
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/scala/code/comet/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/scala/code/comet/.keep
--------------------------------------------------------------------------------
/src/main/scala/code/db/TodoDB.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 WorldWide Conferencing, LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | package code {
15 | package db {
16 |
17 | import _root_.net.liftweb.mongodb._
18 | import _root_.net.liftweb.util._
19 | import _root_.net.liftweb.common._
20 | import net.liftweb.json.DefaultFormats
21 | import net.liftweb.json.JsonAST._
22 | import net.liftweb.json.JsonParser._
23 | import net.liftweb.json.JsonDSL._
24 |
25 | import com.mongodb.{BasicDBObject, BasicDBObjectBuilder, DBObject}
26 |
27 | object TodoDB {
28 | def setup {
29 | MongoDB.defineDb(DefaultMongoIdentifier, MongoAddress(MongoHost(), "test_todo"))
30 | }
31 |
32 | def isMongoRunning: Boolean = {
33 | try {
34 | MongoDB.use(DefaultMongoIdentifier) ( db => { db.getLastError } )
35 | true
36 | }
37 | catch {
38 | case e => false
39 | }
40 | }
41 | }
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/scala/code/lib/MongoProtoUser.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2010-2012 Matthew Henderson
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package code.lib
17 |
18 | import _root_.scala.xml.{NodeSeq, Node, Text, Elem}
19 | import net.liftweb.mongodb.record.field.MongoPasswordField
20 | import net.liftweb.record.Field
21 | import net.liftweb.common._
22 | import _root_.net.liftweb.http.{S, js, SHtml}
23 | import js._
24 | import JsCmds._
25 | import _root_.scala.xml.{NodeSeq, Node, Text, Elem}
26 | import _root_.scala.xml.transform._
27 | import _root_.net.liftweb.sitemap._
28 | import _root_.net.liftweb.sitemap.Loc._
29 | import _root_.net.liftweb.util.Helpers._
30 | import _root_.net.liftweb.util._
31 | import _root_.net.liftweb.common._
32 | import _root_.net.liftweb.util.Mailer._
33 | import S._
34 | import _root_.net.liftweb.proto.{ProtoUser => GenProtoUser}
35 | import net.liftweb.mongodb.record.{MongoRecord, MongoId, MongoMetaRecord}
36 | import net.liftweb.mongodb._
37 | import _root_.net.liftweb.json.DefaultFormats
38 | import _root_.net.liftweb.json.JsonDSL._
39 | import _root_.net.liftweb.json.JsonAST.JObject
40 |
41 | import net.liftweb.record.field._
42 | trait UserIdAsString {
43 | def userIdAsString: String
44 | }
45 | /**
46 | * ProtoUser is a base class that gives you a "User" that has a first name,
47 | * last name, email, etc.
48 | */
49 | trait ProtoUser[T <: ProtoUser[T]] extends MongoRecord[T] with UserIdAsString with MongoId[T]{
50 | self: T =>
51 |
52 | /**
53 | * The primary key field for the User. You can override the behavior
54 | * of this field:
55 | *
56 | * override lazy val id = new MyMappedLongClass(this) {
57 | * println("I am doing something different")
58 | * }
59 | *
60 | */
61 |
62 | //protected class MyMappedLongClass(obj: T) extends LongField(obj)
63 |
64 | /**
65 | * Convert the id to a String
66 | */
67 | def userIdAsString: String = _id.is.toString
68 |
69 | /**
70 | * The first name field for the User. You can override the behavior
71 | * of this field:
72 | *
73 | * override lazy val firstName = new MyFirstName(this, 32) {
74 | * println("I am doing something different")
75 | * }
76 | *
77 | */
78 |
79 | lazy val firstName: StringField[T] = new MyFirstName(this, 32)
80 |
81 | protected class MyFirstName(obj: T, size: Int) extends StringField(obj, size) {
82 | override def displayName = owner.firstNameDisplayName
83 | override val fieldId = Some(Text("txtFirstName"))
84 | }
85 |
86 | /**
87 | * The string name for the first name field
88 | */
89 | def firstNameDisplayName = ??("first.name")
90 |
91 | /**
92 | * The last field for the User. You can override the behavior
93 | * of this field:
94 | *
95 | * override lazy val lastName = new MyLastName(this, 32) {
96 | * println("I am doing something different")
97 | * }
98 | *
99 | */
100 | lazy val lastName: StringField[T] = new MyLastName(this, 32)
101 |
102 | protected class MyLastName(obj: T, size: Int) extends StringField(obj, size) {
103 | override def displayName = owner.lastNameDisplayName
104 | override val fieldId = Some(Text("txtLastName"))
105 | }
106 |
107 | /**
108 | * The last name string
109 | */
110 | def lastNameDisplayName = ??("last.name")
111 |
112 | /**
113 | * The email field for the User. You can override the behavior
114 | * of this field:
115 | *
116 | * override lazy val email = new MyEmail(this, 48) {
117 | * println("I am doing something different")
118 | * }
119 | *
120 | */
121 | lazy val email: EmailField[T] = new MyEmail(this, 48)
122 |
123 | protected class MyEmail(obj: T, size: Int) extends EmailField(obj, size) {
124 | private def valUnique(emailValue: ValueType): List[FieldError] =
125 | toBoxMyType(emailValue) match {
126 | case Full(email) => {
127 | owner.meta.findAll("email", email) match {
128 | case Nil => Nil
129 | case usr :: Nil if (usr.id == owner.id) => Empty
130 | case _ => Text(S.??("unique.email.address"))
131 | }
132 | }
133 | case _ => Text(S.??("unique.email.address"))
134 | }
135 |
136 | override def displayName = owner.emailDisplayName
137 | override def validations = valUnique _ :: super.validations
138 | override val fieldId = Some(Text("txtEmail"))
139 | }
140 |
141 | /**
142 | * The email first name
143 | */
144 | def emailDisplayName = ??("email.address")
145 |
146 | /**
147 | * The password field for the User. You can override the behavior
148 | * of this field:
149 | *
150 | * override lazy val password = new MyPassword(this) {
151 | * println("I am doing something different")
152 | * }
153 | *
154 | */
155 | lazy val password: MongoPasswordField[T] = new MyPassword(this)
156 |
157 | protected class MyPassword(obj: T) extends MongoPasswordField(obj) {
158 | override def displayName = owner.passwordDisplayName
159 | }
160 |
161 | /**
162 | * The display name for the password field
163 | */
164 | def passwordDisplayName = ??("password")
165 |
166 | /**
167 | * The superuser field for the User. You can override the behavior
168 | * of this field:
169 | *
170 | * override lazy val superUser = new MySuperUser(this) {
171 | * println("I am doing something different")
172 | * }
173 | *
174 | */
175 | lazy val superUser: BooleanField[T] = new MySuperUser(this)
176 |
177 | protected class MySuperUser(obj: T) extends BooleanField(obj) {
178 | override def defaultValue = false
179 | }
180 |
181 | def niceName: String = (firstName.is, lastName.is, email.is) match {
182 | case (f, l, e) if f.length > 1 && l.length > 1 => f+" "+l+" ("+e+")"
183 | case (f, _, e) if f.length > 1 => f+" ("+e+")"
184 | case (_, l, e) if l.length > 1 => l+" ("+e+")"
185 | case (_, _, e) => e
186 | }
187 |
188 | def shortName: String = (firstName.is, lastName.is) match {
189 | case (f, l) if f.length > 1 && l.length > 1 => f+" "+l
190 | case (f, _) if f.length > 1 => f
191 | case (_, l) if l.length > 1 => l
192 | case _ => email.is
193 | }
194 |
195 | def niceNameWEmailLink = {niceName}
196 | }
197 |
198 | trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends MongoMetaRecord[ModelType] with MongoId[ModelType] with GenProtoUser {
199 | self: ModelType =>
200 |
201 | type TheUserType = ModelType
202 | //ensureIndex(("email" -> 1), true) // unique email
203 | /**
204 | * What's a field pointer for the underlying CRUDify
205 | */
206 | type FieldPointerType = Field[_, TheUserType]
207 |
208 | /**
209 | * Based on a FieldPointer, build a FieldPointerBridge
210 | */
211 | protected implicit def buildFieldBridge(from: FieldPointerType): FieldPointerBridge = new MyPointer(from)
212 |
213 |
214 | protected class MyPointer(from: FieldPointerType) extends FieldPointerBridge {
215 | /**
216 | * What is the display name of this field?
217 | */
218 | def displayHtml: NodeSeq = from.displayHtml
219 |
220 | /**
221 | * Does this represent a pointer to a Password field
222 | */
223 | def isPasswordField_? : Boolean = from match {
224 | case a: MongoPasswordField[_] => true
225 | case _ => false
226 | }
227 | }
228 |
229 | /**
230 | * Convert an instance of TheUserType to the Bridge trait
231 | */
232 | protected implicit def typeToBridge(in: TheUserType): UserBridge =
233 | new MyUserBridge(in)
234 |
235 | /**
236 | * Bridges from TheUserType to methods used in this class
237 | */
238 | protected class MyUserBridge(in: TheUserType) extends UserBridge {
239 | /**
240 | * Convert the user's primary key to a String
241 | */
242 | def userIdAsString: String = in.id.toString
243 |
244 | /**
245 | * Return the user's first name
246 | */
247 | def getFirstName: String = in.firstName.is
248 |
249 | /**
250 | * Return the user's last name
251 | */
252 | def getLastName: String = in.lastName.is
253 |
254 | /**
255 | * Get the user's email
256 | */
257 | def getEmail: String = in.email.is
258 |
259 | /**
260 | * Is the user a superuser
261 | */
262 | def superUser_? : Boolean = in.superUser.is
263 |
264 | /**
265 | * Has the user been validated?
266 | */
267 | def validated_? : Boolean = in.validated.is
268 |
269 | /**
270 | * Does the supplied password match the actual password?
271 | */
272 | def testPassword(toTest: Box[String]): Boolean =
273 | toTest.map(in.password.isMatch) openOr false
274 |
275 | /**
276 | * Set the validation flag on the user and return the user
277 | */
278 | def setValidated(validation: Boolean): TheUserType =
279 | in.validated(validation)
280 |
281 | /**
282 | * Set the unique ID for this user to a new value
283 | */
284 | //TODO
285 | def resetUniqueId(): TheUserType = {
286 | in
287 | }
288 |
289 | /**
290 | * Return the unique ID for the user
291 | */
292 | def getUniqueId(): String = in._id.toString
293 |
294 | /**
295 | * Validate the user
296 | */
297 | def validate: List[FieldError] = in.validate
298 |
299 | /**
300 | * Given a list of string, set the password
301 | */
302 | def setPasswordFromListString(pwd: List[String]): TheUserType = {
303 | pwd match {
304 | case x1 :: x2 :: Nil if x1 == x2 => in.password.setPassword(x1)
305 | case _ => Nil
306 | }
307 | in
308 | }
309 |
310 | /**
311 | * Save the user to backing store
312 | */
313 | def save(): Boolean = {
314 | in.save(true)
315 | true
316 | }
317 | }
318 |
319 | /**
320 | * Given a field pointer and an instance, get the field on that instance
321 | */
322 | protected def computeFieldFromPointer(instance: TheUserType, pointer: FieldPointerType): Box[BaseField] = {
323 | println(instance.fieldByName(pointer.name))
324 | instance.fieldByName(pointer.name)
325 | }
326 |
327 |
328 | /**
329 | * Given an username (probably email address), find the user
330 | */
331 | protected def findUserByEmail(email: String): Box[TheUserType] = {
332 | var searchListHeadOption = meta.findAll(("email" -> email)).headOption
333 | searchListHeadOption match {
334 | case Some(x) => Full(x)
335 | case None => return Empty
336 | }
337 | }
338 |
339 | protected def findUserByUserName(email: String): Box[TheUserType] = findUserByEmail(email)
340 |
341 | /**
342 | * Given a unique id, find the user
343 | */
344 | protected def findUserByUniqueId(id: String): Box[TheUserType] = {
345 | var searchListHeadOption = meta.findAll(("_id" -> id)).headOption
346 | searchListHeadOption match {
347 | case Some(x) => Full(x)
348 | case None => return Empty
349 | }
350 | }
351 |
352 | /**
353 | * Create a new instance of the User
354 | */
355 | protected def createNewUserInstance(): TheUserType = createRecord
356 |
357 | /**
358 | * Given a String representing the User ID, find the user
359 | */
360 | protected def userFromStringId(id: String): Box[TheUserType] = find(id)
361 |
362 | /**
363 | * The list of fields presented to the user at sign-up
364 | */
365 | def signupFields: List[FieldPointerType] = List(firstName,
366 | lastName,
367 | email,
368 | locale,
369 | timezone,
370 | password)
371 |
372 | /**
373 | * The list of fields presented to the user for editing
374 | */
375 | def editFields: List[FieldPointerType] = List(firstName,
376 | lastName,
377 | email,
378 | locale,
379 | timezone)
380 |
381 | }
382 | /**
383 | * ProtoUser is bare bones. MetaProtoUser contains a bunch
384 | * more fields including a validated flag, locale, timezone, etc.
385 | */
386 | trait MegaProtoUser[T <: MegaProtoUser[T]] extends ProtoUser[T]{
387 | self: T =>
388 |
389 | /**
390 | * The has the user been validated.
391 | * You can override the behavior
392 | * of this field:
393 | *
394 | * override lazy val validated = new MyValidated(this, 32) {
395 | * println("I am doing something different")
396 | * }
397 | *
398 | */
399 | lazy val validated: BooleanField[T] = new MyValidated(this)
400 |
401 | protected class MyValidated(obj: T) extends BooleanField(obj) {
402 | override def defaultValue = false
403 | override val fieldId = Some(Text("txtValidated"))
404 | }
405 |
406 | /**
407 | * The locale field for the User.
408 | * You can override the behavior
409 | * of this field:
410 | *
411 | * override lazy val locale = new MyLocale(this, 32) {
412 | * println("I am doing something different")
413 | * }
414 | *
415 | */
416 | lazy val locale = new MyLocale(this)
417 |
418 | protected class MyLocale(obj: T) extends LocaleField(obj) {
419 | override def displayName = owner.localeDisplayName
420 | override val fieldId = Some(Text("txtLocale"))
421 | }
422 |
423 | /**
424 | * The time zone field for the User.
425 | * You can override the behavior
426 | * of this field:
427 | *
428 | * override lazy val timezone = new MyTimeZone(this, 32) {
429 | * println("I am doing something different")
430 | * }
431 | *