StackActions.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. using Inversion;
  2. using Spry;
  3. using Spry.Authentication;
  4. using Statum;
  5. using ClankerSaddle.Services;
  6. namespace ClankerSaddle {
  7. /** Sealed stack id carried by the stack actions. */
  8. public class StackActionPrivate : Object {
  9. public int64 stack_id { get; set; }
  10. }
  11. /** Sealed stack-member identity carried by {@link RemoveStackMemberAction}. */
  12. public class StackMemberActionPrivate : Object {
  13. public int64 stack_id { get; set; }
  14. public int64 member_id { get; set; }
  15. }
  16. /** Sealed stack + project identity for {@link ApplyStackToProjectAction}. */
  17. public class StackProjectApplyPrivate : Object {
  18. public int64 stack_id { get; set; }
  19. public int64 project_id { get; set; }
  20. }
  21. /** Sealed stack + goal identity for {@link ApplyStackToGoalAction}. */
  22. public class StackGoalApplyPrivate : Object {
  23. public int64 stack_id { get; set; }
  24. public int64 goal_id { get; set; }
  25. }
  26. /**
  27. * Creates a stack from the stacks form (name required, unique;
  28. * summary optional).
  29. */
  30. public class CreateStackAction : StatumAction {
  31. private StackService stacks = inject<StackService>();
  32. private UserService users = inject<UserService>();
  33. public override async DirectiveBuilder handle() throws GLib.Error {
  34. var guard = yield SpryAuth.require_login(directives(), request, users);
  35. if (guard != null) {
  36. return (!)guard;
  37. }
  38. string name = Forms.field(request, "name");
  39. if (name.length == 0) {
  40. return directives().notify("error", "A stack name is required").navigate("/stacks");
  41. }
  42. try {
  43. var stack = yield stacks.create_stack(name,
  44. Forms.field(request, "summary"));
  45. return directives().notify("success", @"Stack '$(stack.name)' created")
  46. .navigate(@"/stacks/$(stack.id)");
  47. } catch (Error e) {
  48. return directives().notify("error", @"Could not create the stack: $(e.message)").navigate("/stacks");
  49. }
  50. }
  51. }
  52. /**
  53. * Saves a stack's editable fields (name, summary); the id comes
  54. * from the sealed {@link StackActionPrivate} blob (the hidden form
  55. * field is a fallback). An empty summary clears the stored one.
  56. */
  57. public class UpdateStackAction : TypedStatumAction<StackActionPrivate> {
  58. private StackService stacks = inject<StackService>();
  59. private UserService users = inject<UserService>();
  60. protected override Type private_type { get { return typeof(StackActionPrivate); } }
  61. public override async DirectiveBuilder handle() throws GLib.Error {
  62. var guard = yield SpryAuth.require_login(directives(), request, users);
  63. if (guard != null) {
  64. return (!)guard;
  65. }
  66. var ctx = request_private;
  67. int64 stack_id = ctx != null ? ((!)ctx).stack_id : Forms.integer(request, "stack_id", 0);
  68. if (stack_id <= 0) {
  69. return directives().navigate("/stacks");
  70. }
  71. try {
  72. yield stacks.update_stack(stack_id,
  73. Forms.optional(request, "name"),
  74. request.form != null ? Forms.field(request, "summary") : null);
  75. return directives().notify("success", "Stack saved")
  76. .navigate(@"/stacks/$(stack_id)");
  77. } catch (Error e) {
  78. return directives().notify("error", @"Could not save the stack: $(e.message)")
  79. .navigate(@"/stacks/$(stack_id)");
  80. }
  81. }
  82. }
  83. /** Deletes a stack; the id comes from the sealed private blob. */
  84. public class DeleteStackAction : TypedStatumAction<StackActionPrivate> {
  85. private StackService stacks = inject<StackService>();
  86. private UserService users = inject<UserService>();
  87. protected override Type private_type { get { return typeof(StackActionPrivate); } }
  88. public override async DirectiveBuilder handle() throws GLib.Error {
  89. var guard = yield SpryAuth.require_login(directives(), request, users);
  90. if (guard != null) {
  91. return (!)guard;
  92. }
  93. var ctx = request_private;
  94. if (ctx == null) {
  95. return directives().navigate("/stacks");
  96. }
  97. try {
  98. yield stacks.delete_stack(((!)ctx).stack_id);
  99. return directives().notify("success", "Stack deleted").navigate("/stacks");
  100. } catch (Error e) {
  101. return directives().notify("error", @"Could not delete the stack: $(e.message)")
  102. .navigate("/stacks/$(((!)ctx).stack_id)");
  103. }
  104. }
  105. }
  106. /**
  107. * Adds a project to a stack; the stack id is sealed, the target
  108. * and relationship come from the form selects.
  109. */
  110. public class AddStackMemberAction : TypedStatumAction<StackActionPrivate> {
  111. private StackService stacks = inject<StackService>();
  112. private UserService users = inject<UserService>();
  113. protected override Type private_type { get { return typeof(StackActionPrivate); } }
  114. public override async DirectiveBuilder handle() throws GLib.Error {
  115. var guard = yield SpryAuth.require_login(directives(), request, users);
  116. if (guard != null) {
  117. return (!)guard;
  118. }
  119. var ctx = request_private;
  120. int64 stack_id = ctx != null ? ((!)ctx).stack_id : Forms.integer(request, "stack_id", 0);
  121. int64 project_id = Forms.integer(request, "project_id", 0);
  122. if (stack_id <= 0 || project_id <= 0) {
  123. return directives().notify("error", "Pick a project to add").navigate(@"/stacks/$(stack_id)");
  124. }
  125. try {
  126. yield stacks.add_member(stack_id, project_id,
  127. Forms.field(request, "relationship"));
  128. return directives().notify("success", "Project added to the stack")
  129. .navigate(@"/stacks/$(stack_id)");
  130. } catch (Error e) {
  131. return directives().notify("error", @"Could not add the project: $(e.message)")
  132. .navigate(@"/stacks/$(stack_id)");
  133. }
  134. }
  135. }
  136. /**
  137. * Removes a stack member; both ids come from the sealed private
  138. * blob.
  139. */
  140. public class RemoveStackMemberAction : TypedStatumAction<StackMemberActionPrivate> {
  141. private StackService stacks = inject<StackService>();
  142. private UserService users = inject<UserService>();
  143. protected override Type private_type { get { return typeof(StackMemberActionPrivate); } }
  144. public override async DirectiveBuilder handle() throws GLib.Error {
  145. var guard = yield SpryAuth.require_login(directives(), request, users);
  146. if (guard != null) {
  147. return (!)guard;
  148. }
  149. var ctx = request_private;
  150. if (ctx == null) {
  151. return directives().navigate("/stacks");
  152. }
  153. try {
  154. yield stacks.remove_member(((!)ctx).member_id);
  155. return directives().notify("success", "Project removed from the stack")
  156. .navigate(@"/stacks/$(((!)ctx).stack_id)");
  157. } catch (Error e) {
  158. return directives().notify("error", @"Could not remove the project: $(e.message)")
  159. .navigate(@"/stacks/$(((!)ctx).stack_id)");
  160. }
  161. }
  162. }
  163. /**
  164. * Applies a stack to a project (the project page's "Apply stack"
  165. * action): the stack's members land as project_links rows,
  166. * skipping exact duplicates. The stack comes from the form select;
  167. * the project id is sealed.
  168. */
  169. public class ApplyStackToProjectAction : TypedStatumAction<ProjectActionPrivate> {
  170. private StackService stacks = inject<StackService>();
  171. private UserService users = inject<UserService>();
  172. protected override Type private_type { get { return typeof(ProjectActionPrivate); } }
  173. public override async DirectiveBuilder handle() throws GLib.Error {
  174. var guard = yield SpryAuth.require_login(directives(), request, users);
  175. if (guard != null) {
  176. return (!)guard;
  177. }
  178. var ctx = request_private;
  179. int64 project_id = ctx != null ? ((!)ctx).project_id : Forms.integer(request, "project_id", 0);
  180. int64 stack_id = Forms.integer(request, "stack_id", 0);
  181. if (project_id <= 0 || stack_id <= 0) {
  182. return directives().notify("error", "Pick a stack to apply").navigate(@"/projects/$(project_id)");
  183. }
  184. try {
  185. int64 added = yield stacks.apply_to_project(stack_id, project_id);
  186. return directives().notify("success",
  187. added > 0
  188. ? @"Stack applied — $(added) link(s) added"
  189. : "Stack applied — nothing new to add")
  190. .navigate(@"/projects/$(project_id)");
  191. } catch (Error e) {
  192. return directives().notify("error", @"Could not apply the stack: $(e.message)")
  193. .navigate(@"/projects/$(project_id)");
  194. }
  195. }
  196. }
  197. /**
  198. * Applies a stack to a goal (the goal page's "Apply stack"
  199. * action): the stack's members land as goal_links rows, skipping
  200. * exact duplicates. The stack comes from the form select; the
  201. * goal id is sealed.
  202. */
  203. public class ApplyStackToGoalAction : SealedGoalAction {
  204. private StackService stacks = inject<StackService>();
  205. private UserService users = inject<UserService>();
  206. public override async DirectiveBuilder handle() throws GLib.Error {
  207. var guard = yield SpryAuth.require_login(directives(), request, users);
  208. if (guard != null) {
  209. return (!)guard;
  210. }
  211. int64 goal_id = sealed_goal_id();
  212. int64 stack_id = Forms.integer(request, "stack_id", 0);
  213. if (goal_id <= 0 || stack_id <= 0) {
  214. return directives().notify("error", "Pick a stack to apply").navigate(@"/goals/$(goal_id)");
  215. }
  216. try {
  217. int64 added = yield stacks.apply_to_goal(stack_id, goal_id);
  218. return directives().notify("success",
  219. added > 0
  220. ? @"Stack applied — $(added) link(s) added"
  221. : "Stack applied — nothing new to add")
  222. .navigate(@"/goals/$(goal_id)");
  223. } catch (Error e) {
  224. return directives().notify("error", @"Could not apply the stack: $(e.message)")
  225. .navigate(@"/goals/$(goal_id)");
  226. }
  227. }
  228. }
  229. }