require "uuid" require "file_utils" require "option_parser" require "authd" require "./project.cr" require "./requests.cr" authd_key = nil : String? storage_directory = "./projects" # It’ll get replaced later. But FIXME, this is only a temporary workaround. authd = nil.unsafe_as AuthD::Client OptionParser.parse do |parser| parser.on "-k file", "--jwt-key file", "Provides the JWT key for authd." do |file| authd_key = File.read(file).chomp end parser.on "-S dir", "--storage dir", "Provides a directory in which to store kanband’s data and projects." do |dir| storage_directory = dir end parser.on "-h", "--help", "Prints this help message." do puts parser exit 0 end end 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 authd = AuthD::Client.new key = authd_key authd.key = key if key.is_a? String # 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 IPC::Service.new "kanban" do |event| case event when IPC::Event::Connection when IPC::Event::Disconnection socket = event.connection users.delete socket sockets.delete socket 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 connection = event.connection 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 connection.send(0, { type: "login-error", error: "Invalid credentials." }.to_json) next end users[connection.fd] = user sockets << connection.fd connection.send(0, { type: "login" }.to_json) connection.send(0, { type: "list-projects", projects: Project.all storage_directory }.to_json) when "new-project" user = users[connection] # FIXME: make it an authentication error request = Requests::NewProject.from_json message_as_s project = Project.new request.name project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "list-projects", projects: Project.all storage_directory }.to_json) sockets.each &.send(0, { type: "project", project: project }.to_json) when "project" user = users[connection] # FIXME: make it an authentication error request = Requests::Tasks.from_json message_as_s project = Project.get_from_id request.project, storage_directory connection.send(0, { type: "project", project: project }.to_json) when "edit-project" user = users[connection] # FIXME: make it an authentication error request = Requests::EditProject.from_json message_as_s project = Project.get_from_id request.project, storage_directory if name = request.name project.name = name end if color = request.color project.color = color end project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "new-column" user = users[connection] # FIXME: make it an authentication error request = Requests::NewColumn.from_json message_as_s project = Project.get_from_id request.project, storage_directory project.columns << Column.new request.name project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "new-task" user = users[connection] # FIXME: make it an authentication error request = Requests::NewTask.from_json message_as_s project = Project.get_from_id request.project, storage_directory column = project.columns.find(&.id.==(request.column)).not_nil! project.tasks << Task.new request.title, user.uid, request.description, column.id project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "edit-task" user = users[connection] # FIXME: make it an authentication error request = Requests::EditTask.from_json message_as_s project = Project.get_from_id request.project, storage_directory 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 if color = request.color task.color = color end # 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 project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "delete-task" user = users[connection] request = Requests::DeleteTask.from_json message_as_s project = Project.get_from_id request.project, storage_directory project.tasks.select! &.id.!=(request.task) project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "edit-column" user = users[connection] # FIXME: make it an authentication error request = Requests::EditColumn.from_json message_as_s project = Project.get_from_id request.project, storage_directory column = project.columns.find(&.id.==(request.column)).not_nil! if name = request.name column.name = name end puts project.columns.to_json project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "delete-column" user = users[connection] request = Requests::DeleteColumn.from_json message_as_s project = Project.get_from_id request.project, storage_directory project.columns.select! &.id.!=(request.column) project.tasks.select! &.column.!=(request.column) project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send(0, { type: "project", project: project }.to_json) when "get-user" request = Requests::GetUser.from_json message_as_s user = authd.get_user? request.uid connection.send(0, { type: "user", user: user }.to_json) else raise "Unknown request type: '#{message["type"]}'" end else pp event end end