Rewrite with ningle
Replaces restas with ningle. Restas had 2 problem I could not overcome: 1) It would only let me return a status code or a response, not, for example, a 403 status code with some json. 2) It would not allow me to place it under a subdirectory. Both of these problems possibly (likely) have solutions with restas, but I already found out how to do these things with ningle. This rewrite is sloppy and messy. The code should be cleaned up soon.
This commit is contained in:
parent
0b13436d1a
commit
781befeb23
9 changed files with 368 additions and 406 deletions
115
data.lisp
Normal file
115
data.lisp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
;; scrumli --- A simple scrum web application
|
||||||
|
;; Copyright (C) 2013 Tom Willemse
|
||||||
|
|
||||||
|
;; scrumli is free software: you can redistribute it and/or modify
|
||||||
|
;; it under the terms of the GNU Affero General Public License as published by
|
||||||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||||||
|
;; (at your option) any later version.
|
||||||
|
|
||||||
|
;; scrumli is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;; GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
;; You should have received a copy of the GNU Affero General Public License
|
||||||
|
;; along with scrumli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
(in-package :scrumli)
|
||||||
|
|
||||||
|
(defclass story ()
|
||||||
|
((id :col-type serial :reader story-id)
|
||||||
|
(state :col-type string :reader state :initform "TODO")
|
||||||
|
(role :col-type string :reader role :initarg :role)
|
||||||
|
(necessity :col-type string :reader necessity :initarg :necessity)
|
||||||
|
(title :col-type string :reader title :initarg :title)
|
||||||
|
(priority :col-type integer :reader priority :initarg :priority)
|
||||||
|
(content :col-type string :reader content :initarg :content)
|
||||||
|
(reporter :col-type string :reader reporter :initarg :reporter)
|
||||||
|
(assignee :col-type string :reader assignee :initarg :assignee))
|
||||||
|
(:metaclass dao-class)
|
||||||
|
(:keys id))
|
||||||
|
|
||||||
|
(defclass task ()
|
||||||
|
((id :col-type serial :reader story-id)
|
||||||
|
(state :col-type string :reader state :initform "TODO")
|
||||||
|
(description :col-type string :reader description :initarg :description)
|
||||||
|
(priority :col-type integer :reader priority :initarg :priority)
|
||||||
|
(reporter :col-type string :reader reporter :initarg :reporter)
|
||||||
|
(assignee :col-type string :reader assignee :initarg :assignee)
|
||||||
|
(story-id :col-type integer :reader story-id :initarg :story-id))
|
||||||
|
(:metaclass dao-class)
|
||||||
|
(:keys id))
|
||||||
|
|
||||||
|
(deftable task
|
||||||
|
(!dao-def)
|
||||||
|
(!foreign 'story 'story-id 'id))
|
||||||
|
|
||||||
|
(defun datainit ()
|
||||||
|
(unless (table-exists-p 'story)
|
||||||
|
(execute (dao-table-definition 'story)))
|
||||||
|
(unless (table-exists-p 'task) (create-table 'task)))
|
||||||
|
|
||||||
|
(defun get-all-stories ()
|
||||||
|
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5)
|
||||||
|
:from 'story) 'priority) :alists))
|
||||||
|
|
||||||
|
(defun get-stories-for (username)
|
||||||
|
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5)
|
||||||
|
:from 'story
|
||||||
|
:where (:= 'assignee username))
|
||||||
|
'priority) :alists))
|
||||||
|
|
||||||
|
(defun get-story (id)
|
||||||
|
(append (query (:select :* (:as (:md5 'assignee) 'md5) :from 'story
|
||||||
|
:where (:= 'id id)) :alist)
|
||||||
|
`((tasks . ,(get-tasks-for-story id)))))
|
||||||
|
|
||||||
|
(defun get-tasks-for-story (id)
|
||||||
|
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5) :from 'task
|
||||||
|
:where (:= 'story-id id))
|
||||||
|
'priority)
|
||||||
|
:alists))
|
||||||
|
|
||||||
|
(defun post-story (role necessity title content reporter)
|
||||||
|
(let ((obj (make-instance
|
||||||
|
'story :role role :necessity necessity :title title
|
||||||
|
:priority (+ 1 (or (query (:select
|
||||||
|
(:coalesce (:max 'priority) 0)
|
||||||
|
:from 'story) :single)
|
||||||
|
0))
|
||||||
|
:content content :assignee "" :reporter reporter)))
|
||||||
|
(save-dao obj)))
|
||||||
|
|
||||||
|
(defun post-task (story-id description reporter)
|
||||||
|
(let ((obj (make-instance
|
||||||
|
'task :description description
|
||||||
|
:priority (+ 1 (query (:select
|
||||||
|
(:coalesce (:max 'priority) 0)
|
||||||
|
:from 'task) :single))
|
||||||
|
:reporter reporter :story-id (parse-integer story-id)
|
||||||
|
:assignee "")))
|
||||||
|
(save-dao obj)))
|
||||||
|
|
||||||
|
(defun story-get-state (type id)
|
||||||
|
(query (:select 'state :from type :where (:= 'id id)) :single))
|
||||||
|
|
||||||
|
(defun story-set-state (type id state)
|
||||||
|
(execute (:update type :set 'state state :where (:= 'id id))))
|
||||||
|
|
||||||
|
(defun story-change-priority (type id dir)
|
||||||
|
(let* ((current-priority (query (:select 'priority :from type
|
||||||
|
:where (:= 'id id))
|
||||||
|
:single))
|
||||||
|
(next-priority (funcall (ecase dir (:up #'-) (:down #'+))
|
||||||
|
current-priority 1))
|
||||||
|
(max-priority (query (:select (:max 'priority) :from type)
|
||||||
|
:single)))
|
||||||
|
(execute (:update type :set 'priority current-priority
|
||||||
|
:where (:= 'priority next-priority)))
|
||||||
|
(execute (:update type :set
|
||||||
|
'priority (max 1 (min next-priority max-priority))
|
||||||
|
:where (:= 'id id)))))
|
||||||
|
|
||||||
|
(defun set-assignee (type id assignee)
|
||||||
|
(execute (:update type :set 'assignee assignee
|
||||||
|
:where (:= 'id id))))
|
|
@ -1,66 +0,0 @@
|
||||||
;; scrumli --- A simple scrum web application
|
|
||||||
;; Copyright (C) 2013 Tom Willemse
|
|
||||||
|
|
||||||
;; scrumli is free software: you can redistribute it and/or modify
|
|
||||||
;; it under the terms of the GNU Affero General Public License as published by
|
|
||||||
;; the Free Software Foundation, either version 3 of the License, or
|
|
||||||
;; (at your option) any later version.
|
|
||||||
|
|
||||||
;; scrumli is distributed in the hope that it will be useful,
|
|
||||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
;; GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
;; You should have received a copy of the GNU Affero General Public License
|
|
||||||
;; along with scrumli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
(restas:define-policy datastore
|
|
||||||
(:interface-package #:scrumli.policy.datastore)
|
|
||||||
(:interface-method-template "DATASTORE-~A")
|
|
||||||
(:internal-package #:scrumli.datastore)
|
|
||||||
|
|
||||||
(define-method init ()
|
|
||||||
"Initiate the datastore.")
|
|
||||||
|
|
||||||
(define-method get-all-stories ()
|
|
||||||
"Get all of the stories in the datastore.")
|
|
||||||
|
|
||||||
(define-method get-stories-for (username)
|
|
||||||
"Get all of the storiess for USERNAME.")
|
|
||||||
|
|
||||||
(define-method get-story (id)
|
|
||||||
"Get a story from the datastore.")
|
|
||||||
|
|
||||||
(define-method get-tasks-for-story (id)
|
|
||||||
"Get the tasks associated with a story.")
|
|
||||||
|
|
||||||
(define-method post-story (role necessity title content reporter)
|
|
||||||
"Post a new story.")
|
|
||||||
|
|
||||||
(define-method post-task (story-id description reporter)
|
|
||||||
"Post a new task for a story.")
|
|
||||||
|
|
||||||
(define-method story-get-state (type id)
|
|
||||||
"Get the state of a story.")
|
|
||||||
|
|
||||||
(define-method story-set-state (type id state)
|
|
||||||
"Set the state of a story.")
|
|
||||||
|
|
||||||
(define-method story-change-priority (type id dir)
|
|
||||||
"Change the priority of a story in direction DIR.")
|
|
||||||
|
|
||||||
(define-method set-assignee (type id assignee)
|
|
||||||
"Change the assigned person for a story or task."))
|
|
||||||
|
|
||||||
(restas:define-module #:scrumli
|
|
||||||
(:use #:cl #:restas #:json #:scrumli.datastore #:drakma)
|
|
||||||
(:export #:start-scrumli))
|
|
||||||
|
|
||||||
(defpackage #:scrumli.pg-datastore
|
|
||||||
(:use #:cl #:postmodern #:scrumli.policy.datastore)
|
|
||||||
(:export #:pg-datastore))
|
|
||||||
|
|
||||||
(in-package #:scrumli)
|
|
||||||
|
|
||||||
(defparameter *static-directory*
|
|
||||||
(merge-pathnames #P"static/" scrumli-config:*base-directory*))
|
|
25
packages.lisp
Normal file
25
packages.lisp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
;; scrumli --- A simple scrum web application
|
||||||
|
;; Copyright (C) 2013 Tom Willemse
|
||||||
|
|
||||||
|
;; scrumli is free software: you can redistribute it and/or modify
|
||||||
|
;; it under the terms of the GNU Affero General Public License as published by
|
||||||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||||||
|
;; (at your option) any later version.
|
||||||
|
|
||||||
|
;; scrumli is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;; GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
;; You should have received a copy of the GNU Affero General Public License
|
||||||
|
;; along with scrumli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
(defpackage #:scrumli
|
||||||
|
(:use :cl :ningle :drakma :clack.builder :clack.middleware.session
|
||||||
|
:clack.response :clack.request :clack.middleware.static
|
||||||
|
:postmodern :clack.middleware.postmodern :parenscript)
|
||||||
|
(:import-from :clack :clackup)
|
||||||
|
(:import-from :json :encode-json-to-string :decode-json)
|
||||||
|
(:export #:start-scrumli))
|
||||||
|
|
||||||
|
(in-package #:scrumli)
|
|
@ -1,136 +0,0 @@
|
||||||
;; scrumli --- A simple scrum web application
|
|
||||||
;; Copyright (C) 2013 Tom Willemse
|
|
||||||
|
|
||||||
;; scrumli is free software: you can redistribute it and/or modify
|
|
||||||
;; it under the terms of the GNU Affero General Public License as published by
|
|
||||||
;; the Free Software Foundation, either version 3 of the License, or
|
|
||||||
;; (at your option) any later version.
|
|
||||||
|
|
||||||
;; scrumli is distributed in the hope that it will be useful,
|
|
||||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
;; GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
;; You should have received a copy of the GNU Affero General Public License
|
|
||||||
;; along with scrumli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
(in-package #:scrumli.pg-datastore)
|
|
||||||
|
|
||||||
(defclass pg-datastore ()
|
|
||||||
((connection-spec :initarg :connection-spec
|
|
||||||
:accessor connection-spec)))
|
|
||||||
|
|
||||||
(defclass story ()
|
|
||||||
((id :col-type serial :reader story-id)
|
|
||||||
(state :col-type string :reader state :initform "TODO")
|
|
||||||
(role :col-type string :reader role :initarg :role)
|
|
||||||
(necessity :col-type string :reader necessity :initarg :necessity)
|
|
||||||
(title :col-type string :reader title :initarg :title)
|
|
||||||
(priority :col-type integer :reader priority :initarg :priority)
|
|
||||||
(content :col-type string :reader content :initarg :content)
|
|
||||||
(reporter :col-type string :reader reporter :initarg :reporter)
|
|
||||||
(assignee :col-type string :reader assignee :initarg :assignee))
|
|
||||||
(:metaclass dao-class)
|
|
||||||
(:keys id))
|
|
||||||
|
|
||||||
(defclass task ()
|
|
||||||
((id :col-type serial :reader story-id)
|
|
||||||
(state :col-type string :reader state :initform "TODO")
|
|
||||||
(description :col-type string :reader description :initarg :description)
|
|
||||||
(priority :col-type integer :reader priority :initarg :priority)
|
|
||||||
(reporter :col-type string :reader reporter :initarg :reporter)
|
|
||||||
(assignee :col-type string :reader assignee :initarg :assignee)
|
|
||||||
(story-id :col-type integer :reader story-id :initarg :story-id))
|
|
||||||
(:metaclass dao-class)
|
|
||||||
(:keys id))
|
|
||||||
|
|
||||||
(deftable task
|
|
||||||
(!dao-def)
|
|
||||||
(!foreign 'story 'story-id 'id))
|
|
||||||
|
|
||||||
(defmethod datastore-init ((datastore pg-datastore))
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(unless (table-exists-p 'story)
|
|
||||||
(execute (dao-table-definition 'story)))
|
|
||||||
(unless (table-exists-p 'task)
|
|
||||||
(execute (dao-table-definition 'task)))))
|
|
||||||
|
|
||||||
(defmethod datastore-get-all-stories ((datastore pg-datastore))
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5)
|
|
||||||
:from 'story) 'priority) :alists)))
|
|
||||||
|
|
||||||
(defmethod datastore-get-stories-for ((datastore pg-datastore) username)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5)
|
|
||||||
:from 'story
|
|
||||||
:where (:= 'assignee username))
|
|
||||||
'priority) :alists)))
|
|
||||||
|
|
||||||
(defmethod datastore-get-story ((datastore pg-datastore) id)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(append (query (:select :* (:as (:md5 'assignee) 'md5) :from 'story
|
|
||||||
:where (:= 'id id)) :alist)
|
|
||||||
`((tasks . ,(datastore-get-tasks-for-story datastore id))))))
|
|
||||||
|
|
||||||
(defmethod datastore-get-tasks-for-story ((datastore pg-datastore) id)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(query (:order-by (:select :* (:as (:md5 'assignee) 'md5) :from 'task
|
|
||||||
:where (:= 'story-id id))
|
|
||||||
'priority)
|
|
||||||
:alists)))
|
|
||||||
|
|
||||||
(defmethod datastore-post-story
|
|
||||||
((datastore pg-datastore) role necessity title content reporter)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(let ((obj (make-instance
|
|
||||||
'story :role role :necessity necessity :title title
|
|
||||||
:priority (+ 1 (or (query (:select
|
|
||||||
(:coalesce (:max 'priority) 0)
|
|
||||||
:from 'story) :single)
|
|
||||||
0))
|
|
||||||
:content content :assignee "" :reporter reporter)))
|
|
||||||
(save-dao obj))))
|
|
||||||
|
|
||||||
(defmethod datastore-post-task
|
|
||||||
((datastore pg-datastore) story-id description reporter)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(let ((obj (make-instance
|
|
||||||
'task :description description
|
|
||||||
:priority (+ 1 (query (:select
|
|
||||||
(:coalesce (:max 'priority) 0)
|
|
||||||
:from 'task) :single))
|
|
||||||
:reporter reporter :story-id (parse-integer story-id)
|
|
||||||
:assignee "")))
|
|
||||||
(save-dao obj))))
|
|
||||||
|
|
||||||
(defmethod datastore-story-get-state ((datastore pg-datastore) type id)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(query (:select 'state :from type :where (:= 'id id)) :single)))
|
|
||||||
|
|
||||||
(defmethod datastore-story-set-state
|
|
||||||
((datastore pg-datastore) type id state)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(execute (:update type :set 'state state :where (:= 'id id)))))
|
|
||||||
|
|
||||||
(defmethod datastore-story-change-priority
|
|
||||||
((datastore pg-datastore) type id dir)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(let* ((current-priority (query (:select 'priority :from type
|
|
||||||
:where (:= 'id id))
|
|
||||||
:single))
|
|
||||||
(next-priority (funcall (ecase dir (:up #'-) (:down #'+))
|
|
||||||
current-priority 1))
|
|
||||||
(max-priority (query (:select (:max 'priority) :from type)
|
|
||||||
:single)))
|
|
||||||
(execute (:update type :set 'priority current-priority
|
|
||||||
:where (:= 'priority next-priority)))
|
|
||||||
(execute (:update type :set
|
|
||||||
'priority (max 1 (min next-priority max-priority))
|
|
||||||
:where (:= 'id id))))))
|
|
||||||
|
|
||||||
(defmethod datastore-set-assignee
|
|
||||||
((datastore pg-datastore) type id assignee)
|
|
||||||
(with-connection (connection-spec datastore)
|
|
||||||
(execute (:update type :set 'assignee assignee
|
|
||||||
:where (:= 'id id)))))
|
|
15
scrumli.asd
15
scrumli.asd
|
@ -23,11 +23,16 @@
|
||||||
:description "Scrum with Lisp"
|
:description "Scrum with Lisp"
|
||||||
:author "Tom Willemse"
|
:author "Tom Willemse"
|
||||||
:license "AGPLv3"
|
:license "AGPLv3"
|
||||||
:depends-on (:restas :postmodern :cl-json :drakma :closure-template
|
:depends-on (:ningle
|
||||||
:md5)
|
:postmodern
|
||||||
|
:cl-json
|
||||||
|
:drakma
|
||||||
|
:closure-template
|
||||||
|
:md5
|
||||||
|
:clack-middleware-postmodern
|
||||||
|
:parenscript)
|
||||||
:defsystem-depends-on (:closure-template)
|
:defsystem-depends-on (:closure-template)
|
||||||
:components ((:closure-template "templates/scrumli")
|
:components ((:closure-template "templates/scrumli")
|
||||||
(:file "defmodule")
|
(:file "packages")
|
||||||
(:file "pg-datastore")
|
(:file "data")
|
||||||
(:file "util")
|
|
||||||
(:file "scrumli")))
|
(:file "scrumli")))
|
||||||
|
|
343
scrumli.lisp
343
scrumli.lisp
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
(in-package #:scrumli)
|
(in-package #:scrumli)
|
||||||
|
|
||||||
(defvar *scrumli-host* "http://localhost:8080"
|
(defvar *scrumli-host* "http://localhost:5000"
|
||||||
"The host currently running Scrumli. Used by Mozilla Persona.")
|
"The host currently running Scrumli. Used by Mozilla Persona.")
|
||||||
|
|
||||||
(defvar *scrumli-bootstrap-css-location*
|
(defvar *scrumli-bootstrap-css-location*
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"The location of the JSX Transformer JS file.")
|
"The location of the JSX Transformer JS file.")
|
||||||
|
|
||||||
(defun logged-in-p ()
|
(defun logged-in-p ()
|
||||||
(hunchentoot:session-value :username))
|
(gethash :username (getf (env *request*) :clack.session)))
|
||||||
|
|
||||||
(defun page-title (title)
|
(defun page-title (title)
|
||||||
(concatenate 'string title " | scrumli"))
|
(concatenate 'string title " | scrumli"))
|
||||||
|
@ -53,127 +53,6 @@
|
||||||
(string-downcase (format nil "~{~2,'0x~}"
|
(string-downcase (format nil "~{~2,'0x~}"
|
||||||
(coerce (md5:md5sum-string str) 'list))))
|
(coerce (md5:md5sum-string str) 'list))))
|
||||||
|
|
||||||
(define-route main ("")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(scrumli-templates:main
|
|
||||||
`(:title ,(page-title "Backlog")
|
|
||||||
:csss ,(list *scrumli-bootstrap-css-location*
|
|
||||||
*scrumli-font-awesome-css-location*
|
|
||||||
(genurl 'scrumli-css))
|
|
||||||
:jss ,(list *scrumli-jquery-js-location*
|
|
||||||
*scrumli-bootstrap-js-location*
|
|
||||||
*scrumli-react-js-location*
|
|
||||||
*scrumli-jsxtransformer-js-location*)
|
|
||||||
:username ,(hunchentoot:session-value :username)
|
|
||||||
:usermd5 ,(md5-hash (hunchentoot:session-value :username))
|
|
||||||
:ulogout ,(genurl 'logout-page)
|
|
||||||
:umainjs ,(genurl 'main-js)))
|
|
||||||
(redirect 'login-page)))
|
|
||||||
|
|
||||||
(defmacro serve-static (name relpath)
|
|
||||||
`(define-route ,name (,relpath :content-type "application/ecmascript")
|
|
||||||
(merge-pathnames ,relpath *static-directory*)))
|
|
||||||
|
|
||||||
(serve-static main-js "js/main.js")
|
|
||||||
(serve-static login-js "js/login.js")
|
|
||||||
(serve-static scrumli-css "css/scrumli.css")
|
|
||||||
|
|
||||||
(define-route stories-json ("stories" :content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(encode-json-to-string (get-all-stories))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route my-stories-json ("stories/mine" :content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(encode-json-to-string (get-stories-for
|
|
||||||
(hunchentoot:session-value :username)))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(defmacro with-post-parameters (parameters &body body)
|
|
||||||
`(let ,(mapcar (lambda (p)
|
|
||||||
(list (intern (string-upcase p))
|
|
||||||
`(hunchentoot:post-parameter ,p)))
|
|
||||||
parameters)
|
|
||||||
,@body))
|
|
||||||
|
|
||||||
(define-route stories-new ("stories/new" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(with-post-parameters ("role" "necessity" "headline" "content")
|
|
||||||
(post-story role necessity headline content
|
|
||||||
(hunchentoot:session-value :username))
|
|
||||||
(encode-json-to-string '((status . "ok"))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route tasks-new ("stories/tasks/new" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(with-post-parameters ("storyId" "description")
|
|
||||||
(post-task storyid description
|
|
||||||
(hunchentoot:session-value :username))
|
|
||||||
(encode-json-to-string '((status . "ok"))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route stories-state ("stories/state" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(let* ((id (hunchentoot:post-parameter "id"))
|
|
||||||
(current-state (story-get-state 'story id))
|
|
||||||
(next (ecase (intern current-state :scrumli)
|
|
||||||
(todo "DOING")
|
|
||||||
(doing "DONE")
|
|
||||||
(done "TODO"))))
|
|
||||||
(story-set-state 'story id next)
|
|
||||||
(encode-json-to-string `((status . "ok") (state . ,next))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route task-state ("tasks/state" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(let* ((id (hunchentoot:post-parameter "id"))
|
|
||||||
(current-state (story-get-state 'task id))
|
|
||||||
(next (ecase (intern current-state :scrumli)
|
|
||||||
(todo "DOING")
|
|
||||||
(doing "DONE")
|
|
||||||
(done "TODO"))))
|
|
||||||
(story-set-state 'task id next)
|
|
||||||
(encode-json-to-string `((status . "ok") (state . ,next))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route stories-priority ("stories/:dir" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(let* ((id (hunchentoot:post-parameter "id")))
|
|
||||||
(story-change-priority
|
|
||||||
'story id (intern (string-upcase dir) :keyword))
|
|
||||||
(encode-json-to-string '((status . "ok"))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route task-priority ("tasks/:dir" :method :post
|
|
||||||
:content-type "text/json")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(let* ((id (hunchentoot:post-parameter "id")))
|
|
||||||
(story-change-priority
|
|
||||||
'task id (intern (string-upcase dir) :keyword))
|
|
||||||
(encode-json-to-string '((status . "ok"))))
|
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route login-page ("login")
|
|
||||||
(if (not (logged-in-p))
|
|
||||||
(scrumli-templates:login
|
|
||||||
`(:title ,(page-title "Login")
|
|
||||||
:csss ,(list *scrumli-bootstrap-css-location*
|
|
||||||
*scrumli-font-awesome-css-location*)
|
|
||||||
:jss ,(list *scrumli-bootstrap-js-location*
|
|
||||||
"https://login.persona.org/include.js"
|
|
||||||
(genurl 'login-js))))
|
|
||||||
(redirect 'main)))
|
|
||||||
|
|
||||||
(define-route logout-page ("logout")
|
|
||||||
(if (logged-in-p)
|
|
||||||
(setf (hunchentoot:session-value :username) nil))
|
|
||||||
(redirect 'login-page))
|
|
||||||
|
|
||||||
(defun verify-credentials (audience assertion)
|
(defun verify-credentials (audience assertion)
|
||||||
(let ((response
|
(let ((response
|
||||||
(http-request "https://verifier.login.persona.org/verify"
|
(http-request "https://verifier.login.persona.org/verify"
|
||||||
|
@ -184,37 +63,207 @@
|
||||||
:want-stream t)))
|
:want-stream t)))
|
||||||
(decode-json response)))
|
(decode-json response)))
|
||||||
|
|
||||||
(define-route login-page/post ("login" :method :post)
|
(defclass scrumli-app (<app>) ())
|
||||||
|
|
||||||
|
(defvar *app* (make-instance 'scrumli-app))
|
||||||
|
|
||||||
|
(defun make-tpl-parameters (&rest args)
|
||||||
|
(append (list :prefix (script-name *request*)) args))
|
||||||
|
|
||||||
|
(setf (route *app* "/")
|
||||||
|
(lambda (params)
|
||||||
|
(declare (ignore params))
|
||||||
|
(if (logged-in-p)
|
||||||
|
(scrumli-templates:main
|
||||||
|
(make-tpl-parameters
|
||||||
|
:title (page-title "Backlog")
|
||||||
|
:csss (list *scrumli-bootstrap-css-location*
|
||||||
|
*scrumli-font-awesome-css-location*
|
||||||
|
(concatenate 'string (script-name *request*) "static/css/scrumli.css"))
|
||||||
|
:jss (list *scrumli-jquery-js-location*
|
||||||
|
*scrumli-bootstrap-js-location*
|
||||||
|
*scrumli-react-js-location*
|
||||||
|
*scrumli-jsxtransformer-js-location*
|
||||||
|
(concatenate 'string (script-name *request*) "js/bridge.js"))
|
||||||
|
:username (gethash :username (getf (env *request*) :clack.session))
|
||||||
|
:usermd5 (md5-hash (gethash :username (getf (env *request*) :clack.session)))
|
||||||
|
:ulogout (concatenate 'string (script-name *request*) "logout")
|
||||||
|
:umainjs (concatenate 'string (script-name *request*) "static/js/main.js")))
|
||||||
|
(redirect *response*
|
||||||
|
(concatenate 'string (script-name *request*)
|
||||||
|
"login")))))
|
||||||
|
|
||||||
|
(setf (route *app* "/login")
|
||||||
|
(lambda (params)
|
||||||
|
(declare (ignore params))
|
||||||
|
(if (not (logged-in-p))
|
||||||
|
(scrumli-templates:login
|
||||||
|
(make-tpl-parameters
|
||||||
|
:title (page-title "Login")
|
||||||
|
:csss (list *scrumli-bootstrap-css-location*
|
||||||
|
*scrumli-font-awesome-css-location*)
|
||||||
|
:jss (list *scrumli-bootstrap-js-location*
|
||||||
|
"https://login.persona.org/include.js"
|
||||||
|
(concatenate 'string (script-name *request*)
|
||||||
|
"js/bridge.js")
|
||||||
|
(concatenate 'string (script-name *request*)
|
||||||
|
"static/js/login.js"))))
|
||||||
|
(redirect *response* (if (equal (script-name *request*) "")
|
||||||
|
"/" (script-name *request*))))))
|
||||||
|
|
||||||
|
(setf (route *app* "/login" :method :post)
|
||||||
|
(lambda (params)
|
||||||
(let ((result (verify-credentials
|
(let ((result (verify-credentials
|
||||||
*scrumli-host*
|
*scrumli-host* (getf params :|assertion|))))
|
||||||
(hunchentoot:post-parameter "assertion"))))
|
|
||||||
(if (equal (cdr (assoc :status result)) "okay")
|
(if (equal (cdr (assoc :status result)) "okay")
|
||||||
(progn
|
(progn
|
||||||
(hunchentoot:start-session)
|
(setf (gethash :username
|
||||||
(setf (hunchentoot:session-value :username)
|
(getf (env *request*) :clack.session))
|
||||||
(cdr (assoc :email result)))
|
(cdr (assoc :email result)))
|
||||||
(redirect 'main))
|
(redirect *response*
|
||||||
403)))
|
(if (equal (script-name *request*) "")
|
||||||
|
"/" (script-name *request*))))
|
||||||
|
'(403)))))
|
||||||
|
|
||||||
(define-route scrumli-story ("stories/:id" :content-type "json")
|
(setf (route *app* "/logout")
|
||||||
|
(lambda (params)
|
||||||
|
(declare (ignore params))
|
||||||
(if (logged-in-p)
|
(if (logged-in-p)
|
||||||
(encode-json-to-string (get-story id))
|
(setf (gethash :username
|
||||||
403))
|
(getf (env *request*) :clack.session)) nil))
|
||||||
|
(redirect *response* (concatenate 'string (script-name *request*)
|
||||||
|
"login"))))
|
||||||
|
|
||||||
(define-route scrumli-story-set-assignee ("story/assignee"
|
(setf (route *app* "/stories")
|
||||||
:content-type "json"
|
(lambda (params)
|
||||||
:method :post)
|
(declare (ignore params))
|
||||||
(if (logged-in-p)
|
(if (logged-in-p)
|
||||||
(with-post-parameters ("id" "assignee")
|
(list 200 '(:content-type "text/json")
|
||||||
(set-assignee 'story id assignee)
|
(encode-json-to-string (get-all-stories)))
|
||||||
(encode-json-to-string '((status . "ok"))))
|
'(403))))
|
||||||
403))
|
|
||||||
|
|
||||||
(define-route scrumli-task-set-assignee ("tasks/assignee"
|
(setf (route *app* "/stories/mine")
|
||||||
:content-type "json"
|
(lambda (params)
|
||||||
:method :post)
|
(declare (ignore params))
|
||||||
(if (logged-in-p)
|
(if (logged-in-p)
|
||||||
(with-post-parameters ("id" "assignee")
|
(list 200 '(:content-type "text/json")
|
||||||
(set-assignee 'task id assignee)
|
(encode-json-to-string
|
||||||
(encode-json-to-string '((status . "ok"))))
|
(get-stories-for
|
||||||
403))
|
(gethash :username (getf (env *request*) :clack.session)))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/stories/new" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let ((role (getf params :|role|))
|
||||||
|
(necessity (getf params :|necessity|))
|
||||||
|
(headline (getf params :|headline|))
|
||||||
|
(content (getf params :|content|)))
|
||||||
|
(post-story role necessity headline content
|
||||||
|
(gethash :username (getf (env *request*) :clack.session)))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/stories/tasks/new" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let ((story-id (getf params :|storyId|))
|
||||||
|
(description (getf params :|description|)))
|
||||||
|
(post-task story-id description
|
||||||
|
(gethash :username (getf (env *request*) :clack.session)))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/stories/state" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let* ((id (getf params :|id|))
|
||||||
|
(current-state (story-get-state 'story id))
|
||||||
|
(next (ecase (intern current-state :scrumli)
|
||||||
|
(todo "DOING")
|
||||||
|
(doing "DONE")
|
||||||
|
(done "TODO"))))
|
||||||
|
(story-set-state 'story id next)
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string `((status . "ok")
|
||||||
|
(state . ,next)))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/tasks/state" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let* ((id (getf params :|id|))
|
||||||
|
(current-state (story-get-state 'task id))
|
||||||
|
(next (ecase (intern current-state :scrumli)
|
||||||
|
(todo "DOING")
|
||||||
|
(doing "DONE")
|
||||||
|
(done "TODO"))))
|
||||||
|
(story-set-state 'task id next)
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string `((status . "ok")
|
||||||
|
(state . ,next)))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/stories/:dir" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let ((id (getf params :|id|))
|
||||||
|
(dir (getf params :dir)))
|
||||||
|
(story-change-priority
|
||||||
|
'story id (intern (string-upcase dir) :keyword))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/tasks/:dir" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(let ((id (getf params :|id|)))
|
||||||
|
(story-change-priority
|
||||||
|
'task id (intern (string-upcase (getf params :dir)) :keyword))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/stories/:id")
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string (get-story (getf params :id))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/story/assignee" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(progn
|
||||||
|
(set-assignee 'story (getf params :|id|) (getf params :|assignee|))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/task/assignee" :method :post)
|
||||||
|
(lambda (params)
|
||||||
|
(if (logged-in-p)
|
||||||
|
(progn
|
||||||
|
(set-assignee 'task (getf params :|id|) (getf params :|assignee|))
|
||||||
|
(list 200 '(:content-type "text/json")
|
||||||
|
(encode-json-to-string '((status . "ok")))))
|
||||||
|
'(403))))
|
||||||
|
|
||||||
|
(setf (route *app* "/js/bridge.js")
|
||||||
|
(lambda (params)
|
||||||
|
(declare (ignore params))
|
||||||
|
(list 200 '(:content-type "text/javascript")
|
||||||
|
(ps (var base-url (lisp (if (equal (script-name *request*) "")
|
||||||
|
"/" (script-name *request*))))))))
|
||||||
|
|
||||||
|
(defun get-app ()
|
||||||
|
(builder
|
||||||
|
(<clack-middleware-static> :path "/static/" :root "static/")
|
||||||
|
(<clack-middleware-session>
|
||||||
|
:state (make-instance 'clack.session.state.cookie:<clack-session-state-cookie>))
|
||||||
|
(<clack-middleware-postmodern>
|
||||||
|
:database "scrumli" :user "slash" :password nil :host "localhost")
|
||||||
|
*app*))
|
||||||
|
|
22
start
22
start
|
@ -1,22 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# scrumli --- A simple scrum web application
|
|
||||||
# Copyright (C) 2013 Tom Willemse
|
|
||||||
|
|
||||||
# scrumli is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# scrumli is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with scrumli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
sbcl --load scrumli.asd \
|
|
||||||
--eval "(asdf:operate 'asdf:load-op \"scrumli\")" \
|
|
||||||
--eval "(scrumli:start-scrumli \
|
|
||||||
:datastore-init '(:connection-spec \
|
|
||||||
(\"scrumli\" \"slash\" nil \"localhost\")))"
|
|
|
@ -43,7 +43,7 @@ var AssigneeIcon = React.createClass({
|
||||||
|
|
||||||
var StoryTaskRow = React.createClass({
|
var StoryTaskRow = React.createClass({
|
||||||
changeState: React.autoBind(function(event) {
|
changeState: React.autoBind(function(event) {
|
||||||
$.post("/tasks/state", {'id': this.props.task.id})
|
$.post(baseUrl + "tasks/state", {'id': this.props.task.id})
|
||||||
.done(function (data, textStatus, jqXHR) {
|
.done(function (data, textStatus, jqXHR) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.setState({state: data.state});
|
this.setState({state: data.state});
|
||||||
|
@ -53,21 +53,21 @@ var StoryTaskRow = React.createClass({
|
||||||
return {state: this.props.task.state};
|
return {state: this.props.task.state};
|
||||||
},
|
},
|
||||||
moveUp: React.autoBind(function(event) {
|
moveUp: React.autoBind(function(event) {
|
||||||
$.post("/tasks/up", {'id': this.props.task.id})
|
$.post(baseUrl + "tasks/up", {'id': this.props.task.id})
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.props.onMoved(1);
|
this.props.onMoved(1);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
moveDown: React.autoBind(function(event) {
|
moveDown: React.autoBind(function(event) {
|
||||||
$.post("/tasks/down", {'id': this.props.task.id})
|
$.post(baseUrl + "tasks/down", {'id': this.props.task.id})
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.props.onMoved(-1);
|
this.props.onMoved(-1);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
handleAssigneeClick: React.autoBind(function(event) {
|
handleAssigneeClick: React.autoBind(function(event) {
|
||||||
this.props.onAssigneeClicked({url: "/tasks/assignee",
|
this.props.onAssigneeClicked({url: baseUrl + "task/assignee",
|
||||||
id: this.props.task.id,
|
id: this.props.task.id,
|
||||||
assignee: this.props.task.assignee,
|
assignee: this.props.task.assignee,
|
||||||
md5: this.props.task.md5});
|
md5: this.props.task.md5});
|
||||||
|
@ -149,14 +149,14 @@ var StoryTaskForm = React.createClass({
|
||||||
var StoryData = React.createClass({
|
var StoryData = React.createClass({
|
||||||
handleTaskSubmit: React.autoBind(function (task) {
|
handleTaskSubmit: React.autoBind(function (task) {
|
||||||
task.storyId = this.state.data.id;
|
task.storyId = this.state.data.id;
|
||||||
$.post("/stories/tasks/new", task)
|
$.post(baseUrl + "stories/tasks/new", task)
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.loadStoryFromServer();
|
this.loadStoryFromServer();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
loadStoryFromServer: function() {
|
loadStoryFromServer: function() {
|
||||||
$.get("/stories/" + this.state.data.id)
|
$.get(baseUrl + "stories/" + this.state.data.id)
|
||||||
.done(this.setData.bind(this));
|
.done(this.setData.bind(this));
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -195,7 +195,7 @@ var StoryData = React.createClass({
|
||||||
|
|
||||||
var StoryRow = React.createClass({
|
var StoryRow = React.createClass({
|
||||||
handleAssigneeClick: React.autoBind(function(event) {
|
handleAssigneeClick: React.autoBind(function(event) {
|
||||||
this.props.onAssigneeClicked({url: "/story/assignee",
|
this.props.onAssigneeClicked({url: baseUrl + "story/assignee",
|
||||||
id: this.props.story.id,
|
id: this.props.story.id,
|
||||||
assignee: this.props.story.assignee,
|
assignee: this.props.story.assignee,
|
||||||
md5: this.props.story.md5});
|
md5: this.props.story.md5});
|
||||||
|
@ -240,21 +240,21 @@ var StoryRow = React.createClass({
|
||||||
this.props.onTitleClicked(this.props.story.id);
|
this.props.onTitleClicked(this.props.story.id);
|
||||||
}),
|
}),
|
||||||
changeState: React.autoBind(function(event) {
|
changeState: React.autoBind(function(event) {
|
||||||
$.post("/stories/state", {'id': this.props.story.id})
|
$.post(baseUrl + "stories/state", {'id': this.props.story.id})
|
||||||
.done(function(data, textStatus, jqXHR) {
|
.done(function(data, textStatus, jqXHR) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.setState({state: data.state});
|
this.setState({state: data.state});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
moveUp: React.autoBind(function(event) {
|
moveUp: React.autoBind(function(event) {
|
||||||
$.post("/stories/up", {'id': this.props.story.id})
|
$.post(baseUrl + "stories/up", {'id': this.props.story.id})
|
||||||
.done(function (data, textStatus, jqXHR) {
|
.done(function (data, textStatus, jqXHR) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.props.onMoved(1);
|
this.props.onMoved(1);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
moveDown: React.autoBind(function(event) {
|
moveDown: React.autoBind(function(event) {
|
||||||
$.post("/stories/down", {'id': this.props.story.id})
|
$.post(baseUrl + "stories/down", {'id': this.props.story.id})
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.props.onMoved(-1);
|
this.props.onMoved(-1);
|
||||||
|
@ -416,7 +416,7 @@ var StoryPage = React.createClass({
|
||||||
this.loadStoriesFromServer();
|
this.loadStoriesFromServer();
|
||||||
}),
|
}),
|
||||||
handleStorySubmit: React.autoBind(function (story) {
|
handleStorySubmit: React.autoBind(function (story) {
|
||||||
$.post("/stories/new", story)
|
$.post(baseUrl + "stories/new", story)
|
||||||
.done(function (data, textStatus, jqXHR) {
|
.done(function (data, textStatus, jqXHR) {
|
||||||
if (data.status == "ok")
|
if (data.status == "ok")
|
||||||
this.loadStoriesFromServer();
|
this.loadStoriesFromServer();
|
||||||
|
@ -426,7 +426,7 @@ var StoryPage = React.createClass({
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}),
|
}),
|
||||||
handleStorySelected: React.autoBind(function (storyId) {
|
handleStorySelected: React.autoBind(function (storyId) {
|
||||||
$.get('/stories/' + storyId)
|
$.get(baseUrl + 'stories/' + storyId)
|
||||||
.done(function (data, textStatus, jqXHR) {
|
.done(function (data, textStatus, jqXHR) {
|
||||||
this.refs.data.setData(data);
|
this.refs.data.setData(data);
|
||||||
}.bind(this), 'json');
|
}.bind(this), 'json');
|
||||||
|
@ -466,8 +466,8 @@ var StoryFilter = React.createClass({
|
||||||
handleClick: React.autoBind(function(event) {
|
handleClick: React.autoBind(function(event) {
|
||||||
this.setState({filter: (this.state.filter != 'all'
|
this.setState({filter: (this.state.filter != 'all'
|
||||||
? 'all' : 'user')});
|
? 'all' : 'user')});
|
||||||
scrumli_page.setUrl((this.state.filter == "all"
|
scrumli_page.setUrl(baseUrl + (this.state.filter == "all"
|
||||||
? "/stories" : "/stories/mine"));
|
? "stories" : "stories/mine"));
|
||||||
}),
|
}),
|
||||||
render: function() {
|
render: function() {
|
||||||
var classes = {all: ['icon-group', 'All'],
|
var classes = {all: ['icon-group', 'All'],
|
||||||
|
@ -480,7 +480,7 @@ var StoryFilter = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var scrumli_page = <StoryPage url="/stories" pollInterval={5000} />;
|
var scrumli_page = <StoryPage url={baseUrl + "stories"} pollInterval={5000} />;
|
||||||
|
|
||||||
React.renderComponent(
|
React.renderComponent(
|
||||||
scrumli_page,
|
scrumli_page,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
(in-package #:scrumli)
|
|
||||||
|
|
||||||
(defun start-scrumli (&key hostname (port 8080)
|
|
||||||
(datastore 'scrumli.pg-datastore:pg-datastore)
|
|
||||||
datastore-init)
|
|
||||||
(setf *datastore* (apply #'make-instance datastore datastore-init))
|
|
||||||
(init)
|
|
||||||
(start '#:scrumli :port port :hostname hostname))
|
|
Loading…
Reference in a new issue