CapabilityActions.vala 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. using Inversion;
  2. using Spry;
  3. using Spry.Authentication;
  4. using Statum;
  5. using ClankerSaddle.Services;
  6. namespace ClankerSaddle {
  7. /** Sealed capability id carried by {@link DeleteCapabilityAction}. */
  8. public class CapabilityActionPrivate : Object {
  9. public int64 capability_id { get; set; }
  10. }
  11. /** Sealed saddle-capability attachment identity. */
  12. public class SaddleCapabilityActionPrivate : Object {
  13. public int64 saddle_id { get; set; }
  14. public int64 link_id { get; set; }
  15. }
  16. /** Sealed task-capability attachment identity. */
  17. public class TaskCapabilityActionPrivate : Object {
  18. public int64 task_id { get; set; }
  19. public int64 link_id { get; set; }
  20. }
  21. /**
  22. * Creates a capability (tag) from the saddles page's capabilities
  23. * section; names are unique.
  24. */
  25. public class CreateCapabilityAction : StatumAction {
  26. private CapabilityService capabilities = inject<CapabilityService>();
  27. private UserService users = inject<UserService>();
  28. public override async DirectiveBuilder handle() throws GLib.Error {
  29. // The roles decision: capabilities are admin-managed (the
  30. // catalogue feeds the dispatcher's routing).
  31. var guard = yield SpryAuth.require_permission(directives(), request, users,
  32. SpryAuth.ADMIN_PERMISSION, "/login");
  33. if (guard != null) {
  34. return (!)guard;
  35. }
  36. string name = Forms.field(request, "name");
  37. if (name.length == 0) {
  38. return directives().notify("error", "A capability name is required").navigate("/saddles");
  39. }
  40. try {
  41. var capability = yield capabilities.create_capability(name);
  42. return directives().notify("success", @"Capability '$(capability.name)' created")
  43. .navigate("/saddles");
  44. } catch (Error e) {
  45. return directives().notify("error", @"Could not create the capability: $(e.message)")
  46. .navigate("/saddles");
  47. }
  48. }
  49. }
  50. /**
  51. * Deletes a capability everywhere (saddles stop carrying it, tasks
  52. * stop requiring it); the id is sealed.
  53. */
  54. public class DeleteCapabilityAction : TypedStatumAction<CapabilityActionPrivate> {
  55. private CapabilityService capabilities = inject<CapabilityService>();
  56. private UserService users = inject<UserService>();
  57. protected override Type private_type { get { return typeof(CapabilityActionPrivate); } }
  58. public override async DirectiveBuilder handle() throws GLib.Error {
  59. // The roles decision: capabilities are admin-managed.
  60. var guard = yield SpryAuth.require_permission(directives(), request, users,
  61. SpryAuth.ADMIN_PERMISSION, "/login");
  62. if (guard != null) {
  63. return (!)guard;
  64. }
  65. var ctx = request_private;
  66. if (ctx == null) {
  67. return directives().navigate("/saddles");
  68. }
  69. try {
  70. yield capabilities.delete_capability(((!)ctx).capability_id);
  71. return directives().notify("success", "Capability deleted").navigate("/saddles");
  72. } catch (Error e) {
  73. return directives().notify("error", @"Could not delete the capability: $(e.message)")
  74. .navigate("/saddles");
  75. }
  76. }
  77. }
  78. /**
  79. * Attaches the form-selected capability to the sealed saddle; the
  80. * saddle card's capability chips.
  81. */
  82. public class AttachSaddleCapabilityAction : TypedStatumAction<SaddleActionPrivate> {
  83. private CapabilityService capabilities = inject<CapabilityService>();
  84. private UserService users = inject<UserService>();
  85. protected override Type private_type { get { return typeof(SaddleActionPrivate); } }
  86. public override async DirectiveBuilder handle() throws GLib.Error {
  87. var guard = yield SpryAuth.require_login(directives(), request, users);
  88. if (guard != null) {
  89. return (!)guard;
  90. }
  91. var ctx = request_private;
  92. int64 saddle_id = ctx != null ? ((!)ctx).saddle_id : Forms.integer(request, "saddle_id", 0);
  93. int64 capability_id = Forms.integer(request, "capability_id", 0);
  94. if (saddle_id <= 0 || capability_id <= 0) {
  95. return directives().notify("error", "Pick a capability to attach").navigate("/saddles");
  96. }
  97. try {
  98. yield capabilities.attach_to_saddle(capability_id, saddle_id);
  99. return directives().notify("success", "Capability attached").navigate("/saddles");
  100. } catch (Error e) {
  101. return directives().notify("error", @"Could not attach the capability: $(e.message)")
  102. .navigate("/saddles");
  103. }
  104. }
  105. }
  106. /** Detaches a saddle capability; both ids are sealed. */
  107. public class DetachSaddleCapabilityAction : TypedStatumAction<SaddleCapabilityActionPrivate> {
  108. private CapabilityService capabilities = inject<CapabilityService>();
  109. private UserService users = inject<UserService>();
  110. protected override Type private_type { get { return typeof(SaddleCapabilityActionPrivate); } }
  111. public override async DirectiveBuilder handle() throws GLib.Error {
  112. var guard = yield SpryAuth.require_login(directives(), request, users);
  113. if (guard != null) {
  114. return (!)guard;
  115. }
  116. var ctx = request_private;
  117. if (ctx == null) {
  118. return directives().navigate("/saddles");
  119. }
  120. try {
  121. yield capabilities.detach_from_saddle(((!)ctx).link_id);
  122. return directives().notify("success", "Capability removed").navigate("/saddles");
  123. } catch (Error e) {
  124. return directives().notify("error", @"Could not remove the capability: $(e.message)")
  125. .navigate("/saddles");
  126. }
  127. }
  128. }
  129. /**
  130. * True when the task's required capabilities are still editable:
  131. * in draft, or queued before any worker has the job. Once a
  132. * saddle is assigned (or the task is parked on one) the pairing
  133. * the dispatcher negotiated is locked, and terminal tasks lock
  134. * everything.
  135. */
  136. private static async bool task_capabilities_editable(TaskService tasks, int64 task_id)
  137. throws GLib.Error {
  138. var detail = yield tasks.get_task(task_id);
  139. if (detail == null) {
  140. return false;
  141. }
  142. string status = ((!)detail).task.status;
  143. return status == STATUS_DRAFT || status == STATUS_QUEUED;
  144. }
  145. /**
  146. * Attaches the form-selected capability to the sealed task as a
  147. * required capability; the task page's capabilities section. Only
  148. * editable while the task is draft or queued (before a worker has
  149. * the job).
  150. */
  151. public class AttachTaskCapabilityAction : SealedTaskAction {
  152. private CapabilityService capability_service = inject<CapabilityService>();
  153. private TaskService task_service = inject<TaskService>();
  154. private UserService users = inject<UserService>();
  155. public override async DirectiveBuilder handle() throws GLib.Error {
  156. var guard = yield SpryAuth.require_login(directives(), request, users);
  157. if (guard != null) {
  158. return (!)guard;
  159. }
  160. int64 task_id = sealed_task_id();
  161. int64 capability_id = Forms.integer(request, "capability_id", 0);
  162. if (task_id <= 0 || capability_id <= 0) {
  163. return directives().notify("error", "Pick a capability to require")
  164. .navigate(@"/tasks/$(task_id)");
  165. }
  166. if (!(yield task_capabilities_editable(task_service, task_id))) {
  167. return directives().notify("error",
  168. "Capabilities are locked once a worker has the job")
  169. .navigate(@"/tasks/$(task_id)");
  170. }
  171. try {
  172. yield capability_service.attach_to_task(capability_id, task_id);
  173. return directives().notify("success", "Capability required")
  174. .navigate(@"/tasks/$(task_id)");
  175. } catch (Error e) {
  176. return directives().notify("error", @"Could not attach the capability: $(e.message)")
  177. .navigate(@"/tasks/$(task_id)");
  178. }
  179. }
  180. }
  181. /** Detaches a task's required capability; both ids are sealed. */
  182. public class DetachTaskCapabilityAction : TypedStatumAction<TaskCapabilityActionPrivate> {
  183. private CapabilityService capabilities = inject<CapabilityService>();
  184. private TaskService task_service = inject<TaskService>();
  185. private UserService users = inject<UserService>();
  186. protected override Type private_type { get { return typeof(TaskCapabilityActionPrivate); } }
  187. public override async DirectiveBuilder handle() throws GLib.Error {
  188. var guard = yield SpryAuth.require_login(directives(), request, users);
  189. if (guard != null) {
  190. return (!)guard;
  191. }
  192. var ctx = request_private;
  193. if (ctx == null) {
  194. return directives().navigate("/tasks");
  195. }
  196. if (!(yield task_capabilities_editable(task_service, ((!)ctx).task_id))) {
  197. return directives().notify("error",
  198. "Capabilities are locked once a worker has the job")
  199. .navigate(@"/tasks/$(((!)ctx).task_id)");
  200. }
  201. try {
  202. yield capabilities.detach_from_task(((!)ctx).link_id);
  203. return directives().notify("success", "Requirement removed")
  204. .navigate(@"/tasks/$(((!)ctx).task_id)");
  205. } catch (Error e) {
  206. return directives().notify("error", @"Could not remove the requirement: $(e.message)")
  207. .navigate(@"/tasks/$(((!)ctx).task_id)");
  208. }
  209. }
  210. }
  211. }