todod/src/main.cr

407 lines
7.6 KiB
Crystal
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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