2019-07-06 02:35:50 +02:00
|
|
|
|
require "uuid"
|
|
|
|
|
require "file_utils"
|
2019-10-01 12:15:47 +02:00
|
|
|
|
require "option_parser"
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
require "authd"
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
require "./project.cr"
|
|
|
|
|
require "./requests.cr"
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
authd_key = nil : String?
|
|
|
|
|
storage_directory = "./projects"
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
# It’ll get replaced later. But FIXME, this is only a temporary workaround.
|
|
|
|
|
authd = nil.unsafe_as AuthD::Client
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-10-01 12:15:47 +02:00
|
|
|
|
OptionParser.parse do |parser|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
parser.on "-k file", "--jwt-key file", "Provides the JWT key for authd." do |file|
|
|
|
|
|
authd_key = File.read(file).chomp
|
2019-07-06 02:35:50 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
parser.on "-S dir", "--storage dir", "Provides a directory in which to store kanband’s data and projects." do |dir|
|
|
|
|
|
storage_directory = dir
|
2019-07-06 02:35:50 +02:00
|
|
|
|
end
|
2019-10-01 12:15:47 +02:00
|
|
|
|
|
|
|
|
|
parser.on "-h", "--help", "Prints this help message." do
|
|
|
|
|
puts parser
|
|
|
|
|
exit 0
|
|
|
|
|
end
|
2019-07-06 02:35:50 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
class ConnectionArray < Array(Int32)
|
|
|
|
|
def <<(x : IPC::Connection)
|
|
|
|
|
self << x.fd
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def [](key) : IPC::Connection
|
|
|
|
|
fd = fetch key
|
|
|
|
|
|
|
|
|
|
connection = LibIPC::Connection.new# 0, 0, fd, 0, Pointer(UInt8).null
|
|
|
|
|
connection.fd = fd
|
|
|
|
|
IPC::Connection.new connection
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def each
|
|
|
|
|
super do |fd|
|
|
|
|
|
connection = LibIPC::Connection.new# 0, 0, fd, 0, Pointer(UInt8).null
|
|
|
|
|
connection.fd = fd
|
|
|
|
|
|
|
|
|
|
yield IPC::Connection.new connection
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class ConnectionHash < Hash(Int32, AuthD::User)
|
|
|
|
|
def [](key : IPC::Connection)
|
|
|
|
|
self[key.fd]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
sockets = ConnectionArray.new
|
|
|
|
|
users = ConnectionHash.new
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
authd = AuthD::Client.new
|
|
|
|
|
|
|
|
|
|
key = authd_key
|
|
|
|
|
authd.key = key if key.is_a? String
|
|
|
|
|
|
2019-10-01 12:44:11 +02:00
|
|
|
|
# FIXME: Upstream this?
|
|
|
|
|
class IPC::Service
|
|
|
|
|
def loop(&block : Proc(IPC::Event::Connection | IPC::Event::Disconnection | IPC::Event::Message | IPC::Exception, Nil))
|
|
|
|
|
previous_def do |event|
|
|
|
|
|
begin
|
|
|
|
|
block.call event
|
|
|
|
|
rescue e
|
|
|
|
|
STDERR.puts "IPC::Service caught an exception: #{e}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
IPC::Service.new "kanban" do |event|
|
|
|
|
|
case event
|
|
|
|
|
when IPC::Event::Connection
|
|
|
|
|
when IPC::Event::Disconnection
|
|
|
|
|
socket = event.connection
|
|
|
|
|
|
2019-07-06 02:35:50 +02:00
|
|
|
|
users.delete socket
|
|
|
|
|
sockets.delete socket
|
2019-09-30 21:11:42 +02:00
|
|
|
|
when IPC::Event::Message
|
|
|
|
|
message_as_s = String.new event.message.payload
|
|
|
|
|
begin
|
|
|
|
|
message = JSON.parse(message_as_s).as_h
|
|
|
|
|
rescue
|
|
|
|
|
next # Dropping malformed requests.
|
|
|
|
|
end
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection = event.connection
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
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
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "login-error",
|
|
|
|
|
error: "Invalid credentials."
|
|
|
|
|
}.to_json)
|
|
|
|
|
|
|
|
|
|
next
|
|
|
|
|
end
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
users[connection.fd] = user
|
|
|
|
|
sockets << connection.fd
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "login"
|
|
|
|
|
}.to_json)
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "list-projects",
|
2019-07-07 12:09:54 +02:00
|
|
|
|
projects: Project.all storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
}.to_json)
|
|
|
|
|
when "new-project"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::NewProject.from_json message_as_s
|
|
|
|
|
|
|
|
|
|
project = Project.new request.name
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
|
|
|
|
|
2019-07-06 02:35:50 +02:00
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-07 12:26:47 +02:00
|
|
|
|
type: "list-projects",
|
|
|
|
|
projects: Project.all storage_directory
|
|
|
|
|
}.to_json)
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "project"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::Tasks.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "edit-project"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::EditProject.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
if name = request.name
|
|
|
|
|
project.name = name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if color = request.color
|
|
|
|
|
project.color = color
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "new-column"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::NewColumn.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
project.columns << Column.new request.name
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "new-task"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::NewTask.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
column = project.columns.find(&.id.==(request.column)).not_nil!
|
|
|
|
|
|
|
|
|
|
project.tasks << Task.new request.title, user.uid, request.description, column.id
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "edit-task"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::EditTask.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
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
|
|
|
|
|
|
2019-07-08 06:31:13 +02:00
|
|
|
|
if color = request.color
|
|
|
|
|
task.color = color
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-06 02:35:50 +02:00
|
|
|
|
# FIXME: Check it’s 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
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "delete-task"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection]
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::DeleteTask.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
project.tasks.select! &.id.!=(request.task)
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "edit-column"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection] # FIXME: make it an authentication error
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::EditColumn.from_json message_as_s
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
column = project.columns.find(&.id.==(request.column)).not_nil!
|
|
|
|
|
|
|
|
|
|
if name = request.name
|
|
|
|
|
column.name = name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
puts project.columns.to_json
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "delete-column"
|
2019-09-30 21:11:42 +02:00
|
|
|
|
user = users[connection]
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
request = Requests::DeleteColumn.from_json message_as_s
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project = Project.get_from_id request.project, storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
project.columns.select! &.id.!=(request.column)
|
|
|
|
|
project.tasks.select! &.column.!=(request.column)
|
|
|
|
|
|
2019-07-07 12:09:54 +02:00
|
|
|
|
project.write! storage_directory
|
2019-07-06 02:35:50 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: Only notify concerned users.
|
2019-09-30 21:11:42 +02:00
|
|
|
|
sockets.each &.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "project",
|
|
|
|
|
project: project
|
|
|
|
|
}.to_json)
|
|
|
|
|
when "get-user"
|
|
|
|
|
request = Requests::GetUser.from_json message_as_s
|
|
|
|
|
|
|
|
|
|
user = authd.get_user? request.uid
|
|
|
|
|
|
2019-09-30 21:11:42 +02:00
|
|
|
|
connection.send(0, {
|
2019-07-06 02:35:50 +02:00
|
|
|
|
type: "user",
|
|
|
|
|
user: user
|
|
|
|
|
}.to_json)
|
2019-10-05 16:24:16 +02:00
|
|
|
|
else
|
|
|
|
|
raise "Unknown request type: '#{message["type"]}'"
|
2019-07-06 02:35:50 +02:00
|
|
|
|
end
|
2019-09-30 21:11:42 +02:00
|
|
|
|
else
|
|
|
|
|
pp event
|
2019-07-06 02:35:50 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|