using Inversion; using Spry; using Spry.Authentication; using Statum; using ClankerSaddle.Services; namespace ClankerSaddle { /** Sealed capability id carried by {@link DeleteCapabilityAction}. */ public class CapabilityActionPrivate : Object { public int64 capability_id { get; set; } } /** Sealed saddle-capability attachment identity. */ public class SaddleCapabilityActionPrivate : Object { public int64 saddle_id { get; set; } public int64 link_id { get; set; } } /** Sealed task-capability attachment identity. */ public class TaskCapabilityActionPrivate : Object { public int64 task_id { get; set; } public int64 link_id { get; set; } } /** * Creates a capability (tag) from the saddles page's capabilities * section; names are unique. */ public class CreateCapabilityAction : StatumAction { private CapabilityService capabilities = inject(); private UserService users = inject(); public override async DirectiveBuilder handle() throws GLib.Error { // The roles decision: capabilities are admin-managed (the // catalogue feeds the dispatcher's routing). var guard = yield SpryAuth.require_permission(directives(), request, users, SpryAuth.ADMIN_PERMISSION, "/login"); if (guard != null) { return (!)guard; } string name = Forms.field(request, "name"); if (name.length == 0) { return directives().notify("error", "A capability name is required").navigate("/saddles"); } try { var capability = yield capabilities.create_capability(name); return directives().notify("success", @"Capability '$(capability.name)' created") .navigate("/saddles"); } catch (Error e) { return directives().notify("error", @"Could not create the capability: $(e.message)") .navigate("/saddles"); } } } /** * Deletes a capability everywhere (saddles stop carrying it, tasks * stop requiring it); the id is sealed. */ public class DeleteCapabilityAction : TypedStatumAction { private CapabilityService capabilities = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(CapabilityActionPrivate); } } public override async DirectiveBuilder handle() throws GLib.Error { // The roles decision: capabilities are admin-managed. var guard = yield SpryAuth.require_permission(directives(), request, users, SpryAuth.ADMIN_PERMISSION, "/login"); if (guard != null) { return (!)guard; } var ctx = request_private; if (ctx == null) { return directives().navigate("/saddles"); } try { yield capabilities.delete_capability(((!)ctx).capability_id); return directives().notify("success", "Capability deleted").navigate("/saddles"); } catch (Error e) { return directives().notify("error", @"Could not delete the capability: $(e.message)") .navigate("/saddles"); } } } /** * Attaches the form-selected capability to the sealed saddle; the * saddle card's capability chips. */ public class AttachSaddleCapabilityAction : TypedStatumAction { private CapabilityService capabilities = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(SaddleActionPrivate); } } public override async DirectiveBuilder handle() throws GLib.Error { var guard = yield SpryAuth.require_login(directives(), request, users); if (guard != null) { return (!)guard; } var ctx = request_private; int64 saddle_id = ctx != null ? ((!)ctx).saddle_id : Forms.integer(request, "saddle_id", 0); int64 capability_id = Forms.integer(request, "capability_id", 0); if (saddle_id <= 0 || capability_id <= 0) { return directives().notify("error", "Pick a capability to attach").navigate("/saddles"); } try { yield capabilities.attach_to_saddle(capability_id, saddle_id); return directives().notify("success", "Capability attached").navigate("/saddles"); } catch (Error e) { return directives().notify("error", @"Could not attach the capability: $(e.message)") .navigate("/saddles"); } } } /** Detaches a saddle capability; both ids are sealed. */ public class DetachSaddleCapabilityAction : TypedStatumAction { private CapabilityService capabilities = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(SaddleCapabilityActionPrivate); } } public override async DirectiveBuilder handle() throws GLib.Error { var guard = yield SpryAuth.require_login(directives(), request, users); if (guard != null) { return (!)guard; } var ctx = request_private; if (ctx == null) { return directives().navigate("/saddles"); } try { yield capabilities.detach_from_saddle(((!)ctx).link_id); return directives().notify("success", "Capability removed").navigate("/saddles"); } catch (Error e) { return directives().notify("error", @"Could not remove the capability: $(e.message)") .navigate("/saddles"); } } } /** * True when the task's required capabilities are still editable: * in draft, or queued before any worker has the job. Once a * saddle is assigned (or the task is parked on one) the pairing * the dispatcher negotiated is locked, and terminal tasks lock * everything. */ private static async bool task_capabilities_editable(TaskService tasks, int64 task_id) throws GLib.Error { var detail = yield tasks.get_task(task_id); if (detail == null) { return false; } string status = ((!)detail).task.status; return status == STATUS_DRAFT || status == STATUS_QUEUED; } /** * Attaches the form-selected capability to the sealed task as a * required capability; the task page's capabilities section. Only * editable while the task is draft or queued (before a worker has * the job). */ public class AttachTaskCapabilityAction : SealedTaskAction { private CapabilityService capability_service = inject(); private TaskService task_service = inject(); private UserService users = inject(); public override async DirectiveBuilder handle() throws GLib.Error { var guard = yield SpryAuth.require_login(directives(), request, users); if (guard != null) { return (!)guard; } int64 task_id = sealed_task_id(); int64 capability_id = Forms.integer(request, "capability_id", 0); if (task_id <= 0 || capability_id <= 0) { return directives().notify("error", "Pick a capability to require") .navigate(@"/tasks/$(task_id)"); } if (!(yield task_capabilities_editable(task_service, task_id))) { return directives().notify("error", "Capabilities are locked once a worker has the job") .navigate(@"/tasks/$(task_id)"); } try { yield capability_service.attach_to_task(capability_id, task_id); return directives().notify("success", "Capability required") .navigate(@"/tasks/$(task_id)"); } catch (Error e) { return directives().notify("error", @"Could not attach the capability: $(e.message)") .navigate(@"/tasks/$(task_id)"); } } } /** Detaches a task's required capability; both ids are sealed. */ public class DetachTaskCapabilityAction : TypedStatumAction { private CapabilityService capabilities = inject(); private TaskService task_service = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(TaskCapabilityActionPrivate); } } public override async DirectiveBuilder handle() throws GLib.Error { var guard = yield SpryAuth.require_login(directives(), request, users); if (guard != null) { return (!)guard; } var ctx = request_private; if (ctx == null) { return directives().navigate("/tasks"); } if (!(yield task_capabilities_editable(task_service, ((!)ctx).task_id))) { return directives().notify("error", "Capabilities are locked once a worker has the job") .navigate(@"/tasks/$(((!)ctx).task_id)"); } try { yield capabilities.detach_from_task(((!)ctx).link_id); return directives().notify("success", "Requirement removed") .navigate(@"/tasks/$(((!)ctx).task_id)"); } catch (Error e) { return directives().notify("error", @"Could not remove the requirement: $(e.message)") .navigate(@"/tasks/$(((!)ctx).task_id)"); } } } }