ProjectActions.vala 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. using Inversion;
  2. using Spry;
  3. using Spry.Authentication;
  4. using Statum;
  5. using ClankerSaddle.Services;
  6. namespace ClankerSaddle {
  7. /** Sealed project id carried by the project actions. */
  8. public class ProjectActionPrivate : Object {
  9. public int64 project_id { get; set; }
  10. }
  11. /** Sealed link identity carried by {@link RemoveProjectLinkAction}. */
  12. public class ProjectLinkActionPrivate : Object {
  13. public int64 project_id { get; set; }
  14. public int64 link_id { get; set; }
  15. }
  16. /** Sealed note identity carried by {@link DeleteProjectNoteAction}. */
  17. public class ProjectNoteActionPrivate : Object {
  18. public int64 project_id { get; set; }
  19. public int64 note_id { get; set; }
  20. }
  21. /**
  22. * Creates a project from the projects form (name and git URL
  23. * required); default_branch falls back to 'main'.
  24. */
  25. public class CreateProjectAction : StatumAction {
  26. private ProjectService projects = inject<ProjectService>();
  27. private UserService users = inject<UserService>();
  28. public override async DirectiveBuilder handle() throws GLib.Error {
  29. var guard = yield SpryAuth.require_login(directives(), request, users);
  30. if (guard != null) {
  31. return (!)guard;
  32. }
  33. string name = Forms.field(request, "name");
  34. string git_url = Forms.field(request, "git_url");
  35. if (name.length == 0 || git_url.length == 0) {
  36. return directives().notify("error", "Name and git URL are required").navigate("/projects");
  37. }
  38. try {
  39. var project = yield projects.create_project(name, git_url,
  40. Forms.field(request, "default_branch"), Forms.field(request, "summary"));
  41. return directives().notify("success", @"Project '$(project.name)' created")
  42. .navigate(@"/projects/$(project.id)");
  43. } catch (Error e) {
  44. return directives().notify("error", @"Could not create the project: $(e.message)").navigate("/projects");
  45. }
  46. }
  47. }
  48. /**
  49. * Saves the editable fields of a project; the id comes from the sealed
  50. * {@link ProjectActionPrivate} blob (the hidden form field is a
  51. * fallback). An empty summary clears the stored one.
  52. */
  53. public class UpdateProjectAction : TypedStatumAction<ProjectActionPrivate> {
  54. private ProjectService projects = inject<ProjectService>();
  55. private UserService users = inject<UserService>();
  56. protected override Type private_type { get { return typeof(ProjectActionPrivate); } }
  57. public override async DirectiveBuilder handle() throws GLib.Error {
  58. var guard = yield SpryAuth.require_login(directives(), request, users);
  59. if (guard != null) {
  60. return (!)guard;
  61. }
  62. var ctx = request_private;
  63. int64 project_id = ctx != null ? ((!)ctx).project_id : Forms.integer(request, "project_id", 0);
  64. if (project_id <= 0) {
  65. return directives().navigate("/projects");
  66. }
  67. try {
  68. yield projects.update_project(project_id,
  69. Forms.optional(request, "name"), Forms.optional(request, "git_url"),
  70. Forms.optional(request, "default_branch"),
  71. request.form != null ? Forms.field(request, "summary") : null);
  72. return directives().notify("success", "Project saved")
  73. .navigate(@"/projects/$(project_id)");
  74. } catch (Error e) {
  75. return directives().notify("error", @"Could not save the project: $(e.message)")
  76. .navigate(@"/projects/$(project_id)");
  77. }
  78. }
  79. }
  80. /**
  81. * Links another project to this one; the parent id is sealed, the
  82. * target and relationship come from the form selects.
  83. */
  84. public class AddProjectLinkAction : TypedStatumAction<ProjectActionPrivate> {
  85. private ProjectService projects = inject<ProjectService>();
  86. private UserService users = inject<UserService>();
  87. protected override Type private_type { get { return typeof(ProjectActionPrivate); } }
  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. int64 project_id = ctx != null ? ((!)ctx).project_id : Forms.integer(request, "project_id", 0);
  95. int64 linked_id = Forms.integer(request, "linked_project_id", 0);
  96. string relationship = Forms.field(request, "relationship");
  97. if (project_id <= 0 || linked_id <= 0) {
  98. return directives().notify("error", "Pick a project to link").navigate(@"/projects/$(project_id)");
  99. }
  100. try {
  101. yield projects.add_link(project_id, linked_id, relationship);
  102. return directives().notify("success", "Link added").navigate(@"/projects/$(project_id)");
  103. } catch (Error e) {
  104. return directives().notify("error", @"Could not add the link: $(e.message)")
  105. .navigate(@"/projects/$(project_id)");
  106. }
  107. }
  108. }
  109. /** Removes a project link; both ids come from the sealed private blob. */
  110. public class RemoveProjectLinkAction : TypedStatumAction<ProjectLinkActionPrivate> {
  111. private ProjectService projects = inject<ProjectService>();
  112. private UserService users = inject<UserService>();
  113. protected override Type private_type { get { return typeof(ProjectLinkActionPrivate); } }
  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. if (ctx == null) {
  121. return directives().navigate("/projects");
  122. }
  123. try {
  124. yield projects.remove_link(((!)ctx).link_id);
  125. return directives().notify("success", "Link removed")
  126. .navigate(@"/projects/$(((!)ctx).project_id)");
  127. } catch (Error e) {
  128. return directives().notify("error", @"Could not remove the link: $(e.message)")
  129. .navigate(@"/projects/$(((!)ctx).project_id)");
  130. }
  131. }
  132. }
  133. /**
  134. * Deletes a project note; both ids come from the sealed private blob.
  135. * Notes are LLM-authored (`saddle note` via the callback endpoint) —
  136. * the web UI only curates them.
  137. */
  138. public class DeleteProjectNoteAction : TypedStatumAction<ProjectNoteActionPrivate> {
  139. private ProjectService projects = inject<ProjectService>();
  140. private UserService users = inject<UserService>();
  141. protected override Type private_type { get { return typeof(ProjectNoteActionPrivate); } }
  142. public override async DirectiveBuilder handle() throws GLib.Error {
  143. var guard = yield SpryAuth.require_login(directives(), request, users);
  144. if (guard != null) {
  145. return (!)guard;
  146. }
  147. var ctx = request_private;
  148. if (ctx == null) {
  149. return directives().navigate("/projects");
  150. }
  151. try {
  152. yield projects.delete_note(((!)ctx).note_id);
  153. return directives().notify("success", "Note deleted")
  154. .navigate(@"/projects/$(((!)ctx).project_id)");
  155. } catch (Error e) {
  156. return directives().notify("error", @"Could not delete the note: $(e.message)")
  157. .navigate(@"/projects/$(((!)ctx).project_id)");
  158. }
  159. }
  160. }
  161. }