Initial commit.

This commit is contained in:
Luka Vandervelden 2019-07-06 02:35:50 +02:00
commit 8b5e0a0c02
9 changed files with 1252 additions and 0 deletions

147
Makefile Normal file
View File

@ -0,0 +1,147 @@
PACKAGE = 'kanban'
VERSION = '0.1'
PREFIX := /usr/local
BINDIR := $(PREFIX)/bin
LIBDIR := $(PREFIX)/lib
SHAREDIR := $(PREFIX)/share
INCLUDEDIR := $(PREFIX)/include
MANDIR := $(SHAREDIR)/man
CC := cc
AR := ar
RANLIB := ranlib
CFLAGS :=
LDFLAGS :=
Q := @
all: public/main.js public/style.css
@:
public/main.js: public/main.bundle.js public
@echo ' MIN > public/main.js'
$(Q)npx babel --minified public/main.bundle.js -o public/main.js
public/main.bundle.js: client/index.ls public
@echo ' BUN > public/main.bundle.js'
$(Q)npx browserify -t browserify-livescript client/index.ls -o public/main.bundle.js
public/main.js.clean:
@echo ' RM > public/main.js'
$(Q)rm -f public/main.js
@echo ' RM > public/main.bundle.js'
$(Q)rm -f public/main.bundle.js
public/style.css: client/style.sass public
@echo ' CSS > public/style.css'
$(Q)sassc client/style.sass > public/style.css
public/style.css.clean:
public:
$(Q)mkdir -p public
$(DESTDIR)$(PREFIX):
@echo ' DIR > $(PREFIX)'
$(Q)mkdir -p $(DESTDIR)$(PREFIX)
$(DESTDIR)$(BINDIR):
@echo ' DIR > $(BINDIR)'
$(Q)mkdir -p $(DESTDIR)$(BINDIR)
$(DESTDIR)$(LIBDIR):
@echo ' DIR > $(LIBDIR)'
$(Q)mkdir -p $(DESTDIR)$(LIBDIR)
$(DESTDIR)$(SHAREDIR):
@echo ' DIR > $(SHAREDIR)'
$(Q)mkdir -p $(DESTDIR)$(SHAREDIR)
$(DESTDIR)$(INCLUDEDIR):
@echo ' DIR > $(INCLUDEDIR)'
$(Q)mkdir -p $(DESTDIR)$(INCLUDEDIR)
$(DESTDIR)$(MANDIR):
@echo ' DIR > $(MANDIR)'
$(Q)mkdir -p $(DESTDIR)$(MANDIR)
install: subdirs.install public/main.js.install public/style.css.install
@:
subdirs.install:
uninstall: subdirs.uninstall public/main.js.uninstall public/style.css.uninstall
@:
subdirs.uninstall:
test: all subdirs subdirs.test
@:
subdirs.test:
clean: public/main.js.clean public/style.css.clean
distclean: clean
dist: dist-gz dist-xz dist-bz2
$(Q)rm -- $(PACKAGE)-$(VERSION)
distdir:
$(Q)rm -rf -- $(PACKAGE)-$(VERSION)
$(Q)ln -s -- . $(PACKAGE)-$(VERSION)
dist-gz: $(PACKAGE)-$(VERSION).tar.gz
$(PACKAGE)-$(VERSION).tar.gz: distdir
@echo ' TAR > $(PACKAGE)-$(VERSION).tar.gz'
$(Q)tar czf $(PACKAGE)-$(VERSION).tar.gz \
$(PACKAGE)-$(VERSION)/client/index.ls \
$(PACKAGE)-$(VERSION)/client/style.sass
dist-xz: $(PACKAGE)-$(VERSION).tar.xz
$(PACKAGE)-$(VERSION).tar.xz: distdir
@echo ' TAR > $(PACKAGE)-$(VERSION).tar.xz'
$(Q)tar cJf $(PACKAGE)-$(VERSION).tar.xz \
$(PACKAGE)-$(VERSION)/client/index.ls \
$(PACKAGE)-$(VERSION)/client/style.sass
dist-bz2: $(PACKAGE)-$(VERSION).tar.bz2
$(PACKAGE)-$(VERSION).tar.bz2: distdir
@echo ' TAR > $(PACKAGE)-$(VERSION).tar.bz2'
$(Q)tar cjf $(PACKAGE)-$(VERSION).tar.bz2 \
$(PACKAGE)-$(VERSION)/client/index.ls \
$(PACKAGE)-$(VERSION)/client/style.sass
help:
@echo ' :: kanban-0.1'
@echo ''
@echo 'Generic targets:'
@echo ' - help  Prints this help message.'
@echo ' - all  Builds all targets.'
@echo ' - dist  Creates tarballs of the files of the project.'
@echo ' - install  Installs the project.'
@echo ' - clean  Removes compiled files.'
@echo ' - uninstall  Deinstalls the project.'
@echo ''
@echo 'CLI-modifiable variables:'
@echo ' - CC  ${CC}'
@echo ' - CFLAGS  ${CFLAGS}'
@echo ' - LDFLAGS  ${LDFLAGS}'
@echo ' - DESTDIR  ${DESTDIR}'
@echo ' - PREFIX  ${PREFIX}'
@echo ' - BINDIR  ${BINDIR}'
@echo ' - LIBDIR  ${LIBDIR}'
@echo ' - SHAREDIR  ${SHAREDIR}'
@echo ' - INCLUDEDIR  ${INCLUDEDIR}'
@echo ' - MANDIR  ${MANDIR}'
@echo ''
@echo 'Project targets: '
@echo ' - public/main.js livescript'
@echo ' - public/style.css css'
@echo ''
@echo 'Makefile options:'
@echo ' - gnu: false'
@echo ' - colors: true'
@echo ''
@echo 'Rebuild the Makefile with:'
@echo ' zsh ./build.zsh -c'
.PHONY: all subdirs clean distclean dist install uninstall help

39
client/bulma.ls Normal file
View File

@ -0,0 +1,39 @@
h = require 'maquette' .h
module.exports = {
box: (args, children) ->
h \div.box args, children
title: (level, args, label) ->
if not label
label = args
args = {}
h "div.title.is-#{level}", args, [label]
label: (args, label) ->
if not label
label = args
args = {}
h \label.label args, [label]
input: (args, children) ->
h \input.input args, children
# FIXME: Use only args and add args.label and args.input?
# Or maybe args.name and args.type could be used directly?
field: (args, children) ->
h \div.field args, children
modal: (args, content) ->
h \div.modal args, [
h \div.modal-background args.background
h \div.modal-content [args.content]
]
form: (method, url, content) ->
h \form.form {
action: url
method: method
}, content
}

485
client/index.ls Normal file
View File

@ -0,0 +1,485 @@
maquette = require "maquette"
nmd = require "nano-markdown"
{create-projector, h} = maquette
projector = create-projector!
bulma = require "./bulma.ls"
get-previous = (collection, element) ->
var previous
for item in collection
if item == element
return previous
previous = item
get-next = (collection, element) ->
var found-element
for item in collection
if found-element
return item
if item == element
found-element := true
Task = (self, project) ->
self.render = ->
author = model.users[self.author]
if typeof(author) != "object" and author != "request sent"
model.users[self.author] = "request sent"
# FIXME: This should go directly to authd.
socket.send JSON.stringify {
type: "get-user"
uid: self.author
}
assigned_to = model.users[self.assigned_to]
if assigned_to and typeof(assigned_to) != "object" and assigned_to != "request sent"
model.users[self.assigned_to] = "request sent"
# FIXME: This should go directly to authd.
socket.send JSON.stringify {
type: "get-user"
uid: self.assigned_to
}
is-selected = model.selected == self.id
h \div.card {
key: self.id
classes: {
"is-selected": is-selected
}
onclick: ->
model.selected := self.id
} [
h \div.card-content [
h \div.media [
h \div.media-left [
h \img.image.is-48x48.avatar {
alt: "user image"
src: if typeof(assigned_to) == "object"
assigned_to.avatar
else
"https://bulma.io/images/placeholders/96x96.png"
}
]
h \div.media-content [
if model.editing == self.id + ".title"
h \input.input {
value: self.title
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
title: e.target.value
}
} [ self.title ]
else
bulma.title 4 self.title
if typeof(model.users[self.assigned_to]) == "object"
user = model.users[self.assigned_to]
h \div.subtitle.is-6 [
"@" + (user.full_name || user.login)
]
]
if is-selected
h \div.media-right {key: "edit"} [
h \span.small {
onclick: ->
if model.editing == self.id + ".title"
model.editing := undefined
else
model.editing := self.id + ".title"
} [
"Edit"
]
]
if is-selected
h \div.media-right {key: "delete"} [
h \span.small {
onclick: ->
model.editing := self.id + ".delete"
} [
"Delete"
]
]
]
if is-selected
h \div.content {
after-create: (dom) ->
dom.innerHTML = nmd self.description
} [
if model.editing == self.id + ".description"
h \form.form [
h \textarea.textarea {
value: self.description
oninput: (e) ->
model.editing-data := e.target.value
}
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
description: model.editing-data
}
model.editing-data := undefined
model.editing := undefined
} [ "Update" ]
]
]
if is-selected
h \span.button.is-small {
onclick: ->
model.editing := self.id + ".description"
} [
"edit"
]
]
if is-selected
h \div.card-footer {key: "assign"} [
if model.editing == self.id + ".assigned_to"
h \div.card-footer-item {
key: "assign.clicked"
} [
h \input.input {
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
assigned_to: Number e.target.value
}
}
]
else
h \div.card-footer-item {
key: "assign"
onclick: ->
model.editing := self.id + ".assigned_to"
} [ "Assign" ]
]
if is-selected
h \div.card-footer {key: "move"} [
h \div.card-footer-item {
key: "⇐"
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
column: get-previous project.columns.map((.id)), self.column
}
} [ "⇐" ]
if model.editing == self.id + ".delete"
h \div.card-footer-item {
key: "delete"
} [
h \div.button.is-danger {
onclick: ->
socket.send JSON.stringify {
type: "delete-task"
project: project.id
task: self.id
}
} [ "Delete! For real!" ]
]
h \div.card-footer-item {
key: "⇒"
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
column: get-next project.columns.map((.id)), self.column
}
} [ "⇒" ]
]
]
self
Project = (self) ->
self.tasks = self.tasks.map (e) -> Task e, self
self.render-column = (column) ->
h \div.column.is-2 {
key: column.id
} [
h \div.card.is-column-header {
key: column.id
} [
h \div.card-header [
if model.editing == column.id + ".title"
h \input.input {
type: "text",
value: column.name
onchange: (e) ->
console.log "onchange??"
model.editing := undefined
socket.send JSON.stringify {
type: "edit-column"
project: self.id
column: column.id
name: e.target.value
}
}
else
h \div.card-header-title [
bulma.title 3 column.name
]
h \div.card-header-icon {
key: "edit"
onclick: ->
if model.editing == column.id + ".title"
model.editing := undefined
else
model.editing := column.id + ".title"
} [
"Edit"
]
if self.tasks.filter((.column == column.id)).length == 0
h \div.card-header-icon {
key: "delete"
onclick: ->
model.editing := column.id + ".delete"
} [
"Delete"
]
]
if model.editing == column.id + ".delete"
h \div.card-content [
h \div.button.is-fullwidth.is-danger {
onclick: ->
socket.send JSON.stringify {
type: "delete-column"
project: self.id
column: column.id
}
} [ "Delete me!"]
]
]
for task in self.tasks
continue if task.column != column.id
task.render!
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-task"
project: self.id
column: column.id
title: "General Kenobi…"
description: ""
}
} [ "New task" ]
]
self.render = ->
h \div.project {
key: self.id
} [
h \div.hero.is-dark { key: "title" } [
h \div.hero-body [
# FIXME: Consider using a .level for this.
h \div.is-pulled-right {
onclick: ->
model.editing := self.id + ".name"
} [
"Edit"
]
if model.editing == self.id + ".name"
h \input.input {
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-project"
project: self.id
name: e.target.value
}
value: self.name
}
else
h \div.title [ self.name ]
]
]
h \div.columns [
for dom in self.columns.map((column) -> self.render-column(column))
dom
h \div.column.is-2 {
key: "new-column"
} [
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-column"
project: self.id
name: "Hello, there!"
}
} [ "New Column" ]
]
]
]
self
model = {
current-view: "login"
editing: undefined
selected: undefined
users: {}
projects: {}
projects-list: []
}
socket = new WebSocket "ws://localhost:8888/socket"
socket.onopen = (event) ->
# Nothing to do here ATM.
socket.onerror = (event) ->
model.state = "websocket-error"
projector.schedule-render!
socket.onclase = (event) ->
model.state = "websocket-error"
model.websocket-error = event.reason
projector.schedule-render!
socket.onmessage = (event) ->
console.log event.data
message = JSON.parse event.data
switch message.type
when "login"
model.current-view := "projects-list"
when "list-projects"
for project in message.projects
model.projects[project.id] = Project project
model.projects-list := message.projects
when "project"
model.projects[message.project.id] = Project message.project
when "user"
model.users[message.user.uid] := message.user
else
console.log "RECEIVED UNKNOWN MESSAGE TYPE: #{message.type}"
projector.schedule-render!
console.log message
renderer = ->
h \div.section [
switch model.current-view
when "login"
h \div.container [
h \div.box [
h \form [
bulma.field [
bulma.label "Login"
bulma.input {
oninput: (e) ->
model.login = e.target.value
name: \login
id: \login-input
}
]
bulma.field [
bulma.label "Password"
bulma.input {
oninput: (e) ->
model.password = e.target.value
name: \password
type: \password
id: \password-input
}
]
h \button.button.is-fullwidth.is-primary {
onclick: (e) ->
e.prevent-default!
socket.send JSON.stringify {
type: "login",
login: model.login
password: model.password
}
} [ "Letsu go!" ]
]
]
]
when "project"
h \div [
h \div.navbar [
h \div.navbar-end [
h \a.navbar-item {
onclick: ->
model.viewed-project := undefined
model.current-view := "projects-list"
} [ "Go back" ]
]
]
if model.projects[model.viewed-project]
model.projects[model.viewed-project].render!
]
when "projects-list"
h \div#projects-list [
for project in (model.projects-list || [])
h \div.box {
key: project.id
onclick: ->
model.current-view := "project"
model.viewed-project := project.id
socket.send JSON.stringify {
type: "project",
project: project.id
}
} [
bulma.title 3 project.name
]
h \div.button.is-primary.is-large.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-project"
name: "Hello, there!"
}
} [ "New project! (random atm)" ]
]
else
h \div.notification.is-error [
"Wait, what? Internal error!"
]
]
document.add-event-listener 'DOMContentLoaded' ->
projector.append document.body, renderer

27
client/style.sass Normal file
View File

@ -0,0 +1,27 @@
@charset "utf-8"
@import "../node_modules/bulmaswatch/superhero/_variables.scss"
// Import Bulma core
@import "../node_modules/bulma/bulma"
@import "../node_modules/bulmaswatch/superhero/_overrides.scss"
.columns
overflow-x: scroll
.avatar
border-radius: 4px
.card.is-selected
border-width: 4px
.is-column-header + .card, .card + .button
margin-top: 12px
.project > .hero
margin-bottom: 12px
.project
margin-top: 12px

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>Kanban</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta charset="utf-8"/>
<link rel="stylesheet" href="public/style.css"/>
</head>
<body>
<script src="public/main.js"></script>
</body>
</html>

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "kanban",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel": "^6.23.0",
"babel-cli": "^6.26.0",
"browserify": "^16.2.3",
"browserify-livescript": "^0.2.4",
"bulma": "^0.7.5",
"bulmaswatch": "^0.7.2",
"livescript": "^1.6.0",
"maquette": "^3.3.4",
"nano-markdown": "^1.2.0"
}
}

85
project.zsh Normal file
View File

@ -0,0 +1,85 @@
package=kanban
version=0.1
targets=()
targets+=(public/main.js)
type[public/main.js]=livescript
sources[public/main.js]=client/index.ls
depends[public/main.js]="$(ls client/*.ls | grep -v /index.ls$ | tr '\n' ' ')"
targets+=(public/style.css)
type[public/style.css]=css
sources[public/style.css]=client/style.sass
#
# implementation details below, bruh~
# (seriously, dont read that unless you want to read that)
# (I mean it, dude)
#
function LSC {
echo "${fg_bold[blue]} LSC > ${fg_bold[white]}$@${reset_color}"
}
function BUN {
echo "${fg_bold[green]} BUN > ${fg_bold[white]}$@${reset_color}"
}
function MIN {
echo "${fg_bold[red]} MIN > ${fg_bold[white]}$@${reset_color}"
}
function CSS {
echo "${fg_bold[yellow]} CSS > ${fg_bold[white]}$@${reset_color}"
}
function livescript.build {
write "${target}: ${target%.js}.bundle.js $(dirname $target)"
write "\t@echo '$(MIN ${target%.js}.js)'"
write "\t${Q}npx babel --minified ${target%.js}.bundle.js -o ${target}"
write "\n"
write "${target%.js}.bundle.js: ${sources[$target]} ${depends[$target]} $(dirname $target)"
write "\t@echo '$(BUN ${target%.js}.bundle.js)'"
write "\t${Q}npx browserify -t browserify-livescript ${sources[$target]} -o ${target%.js}.bundle.js"
write "\n"
}
function livescript.clean {
write "${target}.clean:"
for file in ${target} ${target%.js}.bundle.js; do
write "\t@echo '$(RM ${file})'"
write "\t${Q}rm -f ${file}"
done
write "\n"
}
function livescript.install {
: FIXME
}
function livescript.uninstall {
: FIXME
}
function css.build {
write "${target}: ${sources[$target]} $(dirname $target)"
write "\t@echo '$(CSS ${target})'"
write "\t${Q}sassc ${sources[$target]} > ${target}"
write "\n"
}
function css.clean {
script.clean "$@"
}
function css.install {
: FIXME
}
function css.uninstall {
: FIXME
}

26
shard.yml Normal file
View File

@ -0,0 +1,26 @@
name: kanban
version: 0.1.0
# authors:
# - name <email@example.com>
# description: |
# Short description of kanban
dependencies:
kemal:
github: kemalcr/kemal
authd:
github: Lukc/authd
fs:
git: https://git.karchnu.fr/JunkOS/fs.cr
# pg:
# github: will/crystal-pg
# version: "~> 0.5"
# development_dependencies:
# webmock:
# github: manastech/webmock.cr
# license: MIT

406
src/main.cr Normal file
View File

@ -0,0 +1,406 @@
require "kemal"
require "uuid"
require "file_utils"
require "authd"
class Task
JSON.mapping({
id: String,
author: Int32,
title: String,
description: String,
column: String,
assigned_to: Int32?
})
def initialize(@title, @author, @description, @column)
@id = UUID.random.to_s
end
end
class Column
JSON.mapping({
id: String,
name: String
})
def initialize(@name)
@id = UUID.random.to_s
end
end
class Project
JSON.mapping({
id: String,
name: String,
columns: Array(Column),
tasks: Array(Task),
color: {
type: String,
default: "dark"
}
})
def initialize(@name)
@id = UUID.random.to_s
@columns = [Column.new "To do"]
@tasks = [] of Task
@color = "dark"
write!
end
def write!
Dir.mkdir_p "./projects/#{@id}"
File.write "./projects/#{@id}/project.json.new", to_json
FileUtils.mv "./projects/#{@id}/project.json.new",
"./projects/#{@id}/project.json"
end
# FIXME: Transform that exception somehow.
# FIXME: Update to not read everything.
def self.get_from_id(id)
self.all.find(&.id.==(id)).not_nil!
end
def self.all : Array(Project)
# FIXME: switch to an index file per project in a storage directory.
Dir.children("./projects").compact_map do |filename|
begin
Project.from_json File.read "./projects/#{filename}/project.json"
rescue
end
end
rescue
[] of Project
end
end
class Requests::Login
JSON.mapping({
type: String,
login: String,
password: String
})
end
class Requests::Tasks
JSON.mapping({
type: String,
project: String
})
end
class Requests::NewProject
JSON.mapping({
type: String,
name: String
})
end
class Requests::EditProject
JSON.mapping({
type: String,
project: String,
name: String?,
color: String?
})
end
class Requests::NewColumn
JSON.mapping({
type: String,
project: String,
name: String
})
end
class Requests::NewTask
JSON.mapping({
type: String,
project: String,
column: String,
title: String,
description: String
})
end
class Requests::EditTask
JSON.mapping({
type: String,
project: String,
task: String,
column: String?,
title: String?,
description: String?,
assigned_to: Int32?
})
end
class Requests::DeleteTask
JSON.mapping({
type: String,
project: String,
task: String
})
end
class Requests::EditColumn
JSON.mapping({
type: String,
project: String,
column: String,
name: String?
})
end
class Requests::DeleteColumn
JSON.mapping({
type: String,
project: String,
column: String
})
end
class Requests::GetUser
JSON.mapping({
type: String,
uid: Int32
})
end
authd = AuthD::Client.new
Kemal.config.extra_options do |parser|
parser.on "-k file", "--jwt-key file", "Provides the JWT key for authd." do |file|
authd.key = File.read(file).chomp
end
end
sockets = [] of HTTP::WebSocket
users = {} of HTTP::WebSocket => AuthD::User
ws "/socket" do |socket|
socket.on_close do
users.delete socket
sockets.delete socket
end
socket.on_message do |message_as_s|
message = JSON.parse(message_as_s).as_h
case message["type"]
when "login"
request = Requests::Login.from_json message_as_s
login = request.login
password = request.password
user = authd.get_user? login, password
unless user
socket.send({
type: "login-error",
error: "Invalid credentials."
}.to_json)
next
end
users[socket] = user
sockets << socket
socket.send({
type: "login"
}.to_json)
socket.send({
type: "list-projects",
projects: Project.all
}.to_json)
when "new-project"
user = users[socket] # FIXME: make it an authentication error
request = Requests::NewProject.from_json message_as_s
project = Project.new request.name
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "project"
user = users[socket] # FIXME: make it an authentication error
request = Requests::Tasks.from_json message_as_s
project = Project.get_from_id request.project
socket.send({
type: "project",
project: project
}.to_json)
when "edit-project"
user = users[socket] # FIXME: make it an authentication error
request = Requests::EditProject.from_json message_as_s
project = Project.get_from_id request.project
if name = request.name
project.name = name
end
if color = request.color
project.color = color
end
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "new-column"
user = users[socket] # FIXME: make it an authentication error
request = Requests::NewColumn.from_json message_as_s
project = Project.get_from_id request.project
project.columns << Column.new request.name
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "new-task"
user = users[socket] # FIXME: make it an authentication error
request = Requests::NewTask.from_json message_as_s
project = Project.get_from_id request.project
column = project.columns.find(&.id.==(request.column)).not_nil!
project.tasks << Task.new request.title, user.uid, request.description, column.id
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "edit-task"
user = users[socket] # FIXME: make it an authentication error
request = Requests::EditTask.from_json message_as_s
project = Project.get_from_id request.project
task = project.tasks.find(&.id.==(request.task)).not_nil!
if column = request.column
column = project.columns.find(&.id.==(column)).not_nil!
task.column = column.id
end
if title = request.title
task.title = title
end
if description = request.description
task.description = description
end
# FIXME: Check its a valid UID.
if assigned_to = request.assigned_to
# FIXME: Probably not the best way to handle this corner-case.
assigned_to = nil if assigned_to == -1
task.assigned_to = assigned_to
end
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "delete-task"
user = users[socket]
request = Requests::DeleteTask.from_json message_as_s
project = Project.get_from_id request.project
project.tasks.select! &.id.!=(request.task)
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "edit-column"
user = users[socket] # FIXME: make it an authentication error
request = Requests::EditColumn.from_json message_as_s
project = Project.get_from_id request.project
column = project.columns.find(&.id.==(request.column)).not_nil!
if name = request.name
column.name = name
end
puts project.columns.to_json
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "delete-column"
user = users[socket]
request = Requests::DeleteColumn.from_json message_as_s
project = Project.get_from_id request.project
project.columns.select! &.id.!=(request.column)
project.tasks.select! &.column.!=(request.column)
project.write!
# FIXME: Only notify concerned users.
sockets.each &.send({
type: "project",
project: project
}.to_json)
when "get-user"
request = Requests::GetUser.from_json message_as_s
user = authd.get_user? request.uid
socket.send({
type: "user",
user: user
}.to_json)
end
end
end
Kemal.run