diff --git a/src/column.cr b/src/column.cr new file mode 100644 index 0000000..091dcd0 --- /dev/null +++ b/src/column.cr @@ -0,0 +1,13 @@ +require "uuid" + +class Column + JSON.mapping({ + id: String, + name: String + }) + + def initialize(@name) + @id = UUID.random.to_s + end +end + diff --git a/src/main.cr b/src/main.cr index cbad105..f160f2f 100644 --- a/src/main.cr +++ b/src/main.cr @@ -4,186 +4,40 @@ require "file_utils" require "authd" -class Task - JSON.mapping({ - id: String, - author: Int32, - title: String, - description: String, - column: String, - assigned_to: Int32? - }) +require "./project.cr" +require "./requests.cr" - def initialize(@title, @author, @description, @column) - @id = UUID.random.to_s - end -end +authd_key = nil : String? +storage_directory = "./projects" -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 +# It’ll get replaced later. But FIXME, this is only a temporary workaround. +authd = nil.unsafe_as AuthD::Client 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 + 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 end sockets = [] of HTTP::WebSocket users = {} of HTTP::WebSocket => AuthD::User +get "/" do + File.read "index.html" +end + +get "/style.css" do + File.read "style.css" +end + +get "/main.js" do + File.read "main.js" +end + ws "/socket" do |socket| socket.on_close do users.delete socket @@ -220,7 +74,7 @@ ws "/socket" do |socket| socket.send({ type: "list-projects", - projects: Project.all + projects: Project.all storage_directory }.to_json) when "new-project" user = users[socket] # FIXME: make it an authentication error @@ -229,6 +83,8 @@ ws "/socket" do |socket| project = Project.new request.name + project.write! storage_directory + # FIXME: Only notify concerned users. sockets.each &.send({ type: "project", @@ -239,7 +95,7 @@ ws "/socket" do |socket| request = Requests::Tasks.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory socket.send({ type: "project", @@ -250,7 +106,7 @@ ws "/socket" do |socket| request = Requests::EditProject.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory if name = request.name project.name = name @@ -260,7 +116,7 @@ ws "/socket" do |socket| project.color = color end - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -272,10 +128,10 @@ ws "/socket" do |socket| request = Requests::NewColumn.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory project.columns << Column.new request.name - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -287,12 +143,12 @@ ws "/socket" do |socket| request = Requests::NewTask.from_json message_as_s - project = Project.get_from_id request.project + 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! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -304,7 +160,7 @@ ws "/socket" do |socket| request = Requests::EditTask.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory task = project.tasks.find(&.id.==(request.task)).not_nil! if column = request.column @@ -329,7 +185,7 @@ ws "/socket" do |socket| task.assigned_to = assigned_to end - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -341,11 +197,11 @@ ws "/socket" do |socket| request = Requests::DeleteTask.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory project.tasks.select! &.id.!=(request.task) - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -357,7 +213,7 @@ ws "/socket" do |socket| request = Requests::EditColumn.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory column = project.columns.find(&.id.==(request.column)).not_nil! if name = request.name @@ -366,7 +222,7 @@ ws "/socket" do |socket| puts project.columns.to_json - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -377,12 +233,12 @@ ws "/socket" do |socket| user = users[socket] request = Requests::DeleteColumn.from_json message_as_s - project = Project.get_from_id request.project + project = Project.get_from_id request.project, storage_directory project.columns.select! &.id.!=(request.column) project.tasks.select! &.column.!=(request.column) - project.write! + project.write! storage_directory # FIXME: Only notify concerned users. sockets.each &.send({ @@ -402,5 +258,10 @@ ws "/socket" do |socket| end end -Kemal.run +Kemal.run do + authd = AuthD::Client.new + + key = authd_key + authd.key = key if key.is_a? String +end diff --git a/src/project.cr b/src/project.cr new file mode 100644 index 0000000..2ebcb20 --- /dev/null +++ b/src/project.cr @@ -0,0 +1,54 @@ +require "uuid" +require "file_utils" + +require "./task.cr" +require "./column.cr" + +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" + end + + def write!(storage) + Dir.mkdir_p "#{storage}/#{@id}" + + File.write "#{storage}/#{@id}/project.json.new", to_json + FileUtils.mv "#{storage}/#{@id}/project.json.new", + "#{storage}/#{@id}/project.json" + end + + # FIXME: Transform that exception somehow. + # FIXME: Update to not read everything. + def self.get_from_id(id, storage) + self.all(storage).find(&.id.==(id)).not_nil! + end + + def self.all(storage) : Array(Project) + # FIXME: switch to an index file per project in a storage directory. + Dir.children(storage).compact_map do |filename| + begin + Project.from_json File.read "#{storage}/#{filename}/project.json" + rescue + end + end + rescue + [] of Project + end +end + diff --git a/src/requests.cr b/src/requests.cr new file mode 100644 index 0000000..354c042 --- /dev/null +++ b/src/requests.cr @@ -0,0 +1,95 @@ +require "json" + +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 + diff --git a/src/task.cr b/src/task.cr new file mode 100644 index 0000000..2031c48 --- /dev/null +++ b/src/task.cr @@ -0,0 +1,17 @@ +require "uuid" + +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 +