From 8c013c1192791db6aae3755b6d9a2fe91b4836ed Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Mon, 31 Mar 2014 15:48:22 -0700
Subject: [PATCH] Fixed 101: added level completion status to /play.

---
 app/styles/play.sass                          | 10 +++++++-
 app/templates/play.jade                       |  2 +-
 app/views/play_view.coffee                    | 24 ++++++++++++++++++-
 server/commons/Handler.coffee                 |  3 +--
 .../sessions/level_session_handler.coffee     |  7 +++++-
 server/users/user_handler.coffee              | 15 ++++++++++++
 6 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/app/styles/play.sass b/app/styles/play.sass
index 83611f8b8..e3e31d8fa 100644
--- a/app/styles/play.sass
+++ b/app/styles/play.sass
@@ -15,6 +15,14 @@
   a[disabled] .level
     opacity: 0.7
 
+  a.complete h3:after
+    content: " - Complete!"
+    color: green
+
+  a.started h3:after
+    content: " - Started"
+    color: desaturate(green, 50%)
+
   .level
     @include box-sizing(border-box)
     border: 1px solid transparent
@@ -40,4 +48,4 @@
 
   .alert-warning h2
     color: black
-    text-align: center
\ No newline at end of file
+    text-align: center
diff --git a/app/templates/play.jade b/app/templates/play.jade
index 6f3a77edd..911f14c92 100644
--- a/app/templates/play.jade
+++ b/app/templates/play.jade
@@ -22,7 +22,7 @@ block content
           a(href="/play/#{campaign.levels[0].levelPath || 'level'}/#{campaign.levels[0].id}", data-i18n="play.campaign_#{campaign.id}")= campaign.name
         p.campaign-description(data-i18n="[html]play.campaign_#{campaign.id}_description")!= campaign.description
         each level in campaign.levels
-          a(href=level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled)
+          a(href=level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, class=levelStatusMap[level.id] || '')
             .level.row
               if level.image
                 img.level-image(src="#{level.image}", alt="#{level.name}")
diff --git a/app/views/play_view.coffee b/app/views/play_view.coffee
index 6a92978e3..349f900c7 100644
--- a/app/views/play_view.coffee
+++ b/app/views/play_view.coffee
@@ -1,10 +1,32 @@
 View = require 'views/kinds/RootView'
 template = require 'templates/play'
+LevelSession = require 'models/LevelSession'
+CocoCollection = require 'models/CocoCollection'
+
+class LevelSessionsCollection extends CocoCollection
+  url: ''
+  model: LevelSession
+
+  constructor: (model) ->
+    super()
+    @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID"
 
 module.exports = class PlayView extends View
   id: "play-view"
   template: template
 
+  constructor: (options) ->
+    super options
+    @levelStatusMap = {}
+    @sessions = new LevelSessionsCollection()
+    @sessions.fetch()
+    @listenToOnce @sessions, 'sync', @onSessionsLoaded
+
+  onSessionsLoaded: (e) ->
+    for session in @sessions.models
+      @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
+    @render()
+
   getRenderData: (context={}) ->
     context = super(context)
     context.home = true
@@ -198,7 +220,7 @@ module.exports = class PlayView extends View
       {id: "dev", name: "Random Harder Levels", description: "... in which you learn the interface while doing something a little harder.", levels: experienced}
       {id: "player_created", name: "Player-Created", description: "... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>.", levels: playerCreated}
     ]
-
+    context.levelStatusMap = @levelStatusMap
     context
 
   afterRender: ->
diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee
index 0787ca9d2..1f40ce397 100644
--- a/server/commons/Handler.coffee
+++ b/server/commons/Handler.coffee
@@ -112,8 +112,7 @@ module.exports = class Handler
         log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
       else
         projection = {}
-        for field in req.query.project.split(',')
-          projection[field] = 1
+        projection[field] = 1 for field in req.query.project.split(',')
     for filter in filters
       callback = (err, results) =>
         return @sendDatabaseError(res, err) if err
diff --git a/server/levels/sessions/level_session_handler.coffee b/server/levels/sessions/level_session_handler.coffee
index d3ab07830..ca8680a17 100644
--- a/server/levels/sessions/level_session_handler.coffee
+++ b/server/levels/sessions/level_session_handler.coffee
@@ -1,5 +1,6 @@
 LevelSession = require('./LevelSession')
 Handler = require('../../commons/Handler')
+log = require 'winston'
 
 TIMEOUT = 1000 * 30 # no activity for 30 seconds means it's not active
 
@@ -10,7 +11,11 @@ class LevelSessionHandler extends Handler
                        'chat', 'teamSpells', 'submitted', 'unsubscribed']
 
   getByRelationship: (req, res, args...) ->
-    return @sendNotFoundError(res) unless args.length is 2 and args[1] is 'active'
+    return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
+    return @sendNotFoundError(res)
+
+  getActiveSessions: (req, res) ->
+    return @sendUnauthorizedError(res) unless req.user.isAdmin()
     start = new Date()
     start = new Date(start.getTime() - TIMEOUT)
     query = @modelClass.find({'changed': {$gt: start}})
diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee
index 4abfb65e3..168f10d91 100644
--- a/server/users/user_handler.coffee
+++ b/server/users/user_handler.coffee
@@ -8,6 +8,7 @@ config = require '../../server_config'
 errors = require '../commons/errors'
 async = require 'async'
 log = require 'winston'
+LevelSession = require('../levels/sessions/LevelSession')
 
 serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset']
 privateProperties = [
@@ -169,6 +170,7 @@ UserHandler = class UserHandler extends Handler
     return @avatar(req, res, args[0]) if args[1] is 'avatar'
     return @getNamesByIds(req, res) if args[1] is 'names'
     return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
+    return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
     return @sendNotFoundError(res)
 
   agreeToCLA: (req, res) ->
@@ -194,4 +196,17 @@ UserHandler = class UserHandler extends Handler
       res.redirect(document?.get('photoURL') or '/images/generic-wizard-icon.png')
       res.end()
 
+  getLevelSessions: (req, res, userID) ->
+    return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin()
+    query = {'creator': userID}
+    projection = null
+    if req.query.project
+      projection = {}
+      projection[field] = 1 for field in req.query.project.split(',')
+    LevelSession.find(query).select(projection).exec (err, documents) =>
+      return @sendDatabaseError(res, err) if err
+      documents = (@formatEntity(req, doc) for doc in documents)
+      @sendSuccess(res, documents)
+
+
 module.exports = new UserHandler()