ProjectsEntrypoint.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Spry;
  4. using Spry.Authentication;
  5. using Statum;
  6. using ClankerSaddle.Data;
  7. using ClankerSaddle.Services;
  8. namespace ClankerSaddle {
  9. /** A project row on the projects list, with task counts and a link to its page. */
  10. public class ProjectRowView : Object {
  11. public int64 id { get; set; }
  12. public string name { get; set; default = ""; }
  13. public string git_url { get; set; default = ""; }
  14. public string default_branch { get; set; default = ""; }
  15. public string summary { get; set; default = ""; }
  16. public int64 open_tasks { get; set; }
  17. public int64 done_tasks { get; set; }
  18. }
  19. /** Public data of the `projects` slot. */
  20. public class ProjectsPublic : Object {
  21. public Statum.Model.ActionDto create { get; set; }
  22. }
  23. /**
  24. * Entrypoint for /projects: lists every project with open/done task
  25. * counts and authors the create-project action (login required).
  26. */
  27. public class ProjectsEntrypoint : StatumEntrypoint {
  28. private ProjectService project_service = Inversion.inject<ProjectService>();
  29. private TaskService task_service = Inversion.inject<TaskService>();
  30. private UserService users = Inversion.inject<UserService>();
  31. public override async DirectiveBuilder handle() throws GLib.Error {
  32. var guard = yield SpryAuth.require_login(directives(), request, users);
  33. if (guard != null) {
  34. return (!)guard;
  35. }
  36. var rows = new Vector<ProjectRowView>();
  37. var tasks = yield task_service.list_tasks(null, null);
  38. foreach (var project in yield project_service.list_projects()) {
  39. var view = new ProjectRowView() {
  40. id = project.id,
  41. name = project.name,
  42. git_url = project.git_url,
  43. default_branch = project.default_branch,
  44. summary = project.summary ?? ""
  45. };
  46. foreach (var task in tasks) {
  47. if (task.project_id != project.id) {
  48. continue;
  49. }
  50. if (task.status == STATUS_DONE || task.status == STATUS_FAILED) {
  51. view.done_tasks++;
  52. } else {
  53. view.open_tasks++;
  54. }
  55. }
  56. rows.add(view);
  57. }
  58. var pub = new ProjectsPublic();
  59. pub.create = action_registry.author<CreateProjectAction>();
  60. var props = (PropertyDictionary) GObjectMapping.to_properties(pub);
  61. props["projects"] = StateJson.list<ProjectRowView>(rows.to_immutable_buffer());
  62. var slot = state_service.new_slot(Scope.PAGE, new State() {
  63. type_name = "projects",
  64. public_data = props,
  65. private_data = new PropertyDictionary()
  66. });
  67. return directives().set(slot);
  68. }
  69. }
  70. /** A linked project on the project detail page, with a remove action. */
  71. public class ProjectLinkView : Object {
  72. public int64 link_id { get; set; }
  73. public int64 project_id { get; set; }
  74. public string name { get; set; default = ""; }
  75. public string git_url { get; set; default = ""; }
  76. public string relationship { get; set; default = ""; }
  77. public bool co_developed { get; set; }
  78. public Statum.Model.ActionDto? remove { get; set; }
  79. }
  80. /** A task row on the project detail page. */
  81. public class ProjectTaskRowView : Object {
  82. public int64 id { get; set; }
  83. public string title { get; set; default = ""; }
  84. public string status { get; set; default = ""; }
  85. public int64 priority { get; set; }
  86. public bool is_design_task { get; set; }
  87. }
  88. /** A rule attached to the project, with its detach action. */
  89. public class ProjectRuleRowView : Object {
  90. public int64 link_id { get; set; }
  91. public int64 rule_id { get; set; }
  92. public string name { get; set; default = ""; }
  93. public string body { get; set; default = ""; }
  94. public Statum.Model.ActionDto? detach { get; set; }
  95. }
  96. /** A rule option in the attach selects (project and task pages). */
  97. public class RuleOptionView : Object {
  98. public int64 id { get; set; }
  99. public string name { get; set; default = ""; }
  100. }
  101. /** Public data of the `project` slot. */
  102. public class ProjectDetailPublic : Object {
  103. public bool exists { get; set; }
  104. public int64 id { get; set; }
  105. public string name { get; set; default = ""; }
  106. public string git_url { get; set; default = ""; }
  107. public string default_branch { get; set; default = ""; }
  108. public string summary { get; set; default = ""; }
  109. public string created_display { get; set; default = ""; }
  110. public Statum.Model.ActionDto? update { get; set; }
  111. public Statum.Model.ActionDto? add_link { get; set; }
  112. public Statum.Model.ActionDto? attach_rule { get; set; }
  113. }
  114. /**
  115. * Entrypoint for /projects/{project_id}: the project's editable card,
  116. * its co-developed/reference links (with remove actions), its rules
  117. * (with detach actions and the attach form), the other projects
  118. * available for linking, and its task list.
  119. */
  120. public class ProjectDetailEntrypoint : StatumEntrypoint {
  121. private ProjectService project_service = Inversion.inject<ProjectService>();
  122. private TaskService task_service = Inversion.inject<TaskService>();
  123. private RuleService rule_service = Inversion.inject<RuleService>();
  124. private UserService users = Inversion.inject<UserService>();
  125. public override async DirectiveBuilder handle() throws GLib.Error {
  126. var guard = yield SpryAuth.require_login(directives(), request, users);
  127. if (guard != null) {
  128. return (!)guard;
  129. }
  130. int64 project_id = int64.parse(request.route("project_id") ?? "0");
  131. var project = yield project_service.get_project(project_id);
  132. if (project == null) {
  133. return directives().set_typed<ProjectDetailPublic>("project", Scope.PAGE,
  134. new ProjectDetailPublic() { exists = false });
  135. }
  136. var sealed = new ProjectActionPrivate() { project_id = project_id };
  137. var pub = new ProjectDetailPublic() {
  138. exists = true,
  139. id = ((!)project).id,
  140. name = ((!)project).name,
  141. git_url = ((!)project).git_url,
  142. default_branch = ((!)project).default_branch,
  143. summary = ((!)project).summary ?? "",
  144. created_display = ((!)project).created_at != null
  145. ? ((!)((!)project).created_at).format("%Y-%m-%d %H:%M") : "",
  146. update = action_registry.author_private<UpdateProjectAction, ProjectActionPrivate>(sealed),
  147. add_link = action_registry.author_private<AddProjectLinkAction, ProjectActionPrivate>(sealed),
  148. attach_rule = action_registry.author_private<AttachProjectRuleAction, ProjectActionPrivate>(sealed)
  149. };
  150. var links = new Vector<ProjectLinkView>();
  151. foreach (var link in yield project_service.list_links(project_id)) {
  152. var linked = yield project_service.get_project(link.linked_project_id);
  153. if (linked == null) {
  154. continue;
  155. }
  156. var private_blob = new ProjectLinkActionPrivate() {
  157. project_id = project_id,
  158. link_id = link.id
  159. };
  160. links.add(new ProjectLinkView() {
  161. link_id = link.id,
  162. project_id = link.linked_project_id,
  163. name = ((!)linked).name,
  164. git_url = ((!)linked).git_url,
  165. relationship = link.relationship,
  166. co_developed = link.relationship == LINK_CO_DEVELOPED,
  167. remove = action_registry.author_private<RemoveProjectLinkAction, ProjectLinkActionPrivate>(private_blob)
  168. });
  169. }
  170. var linkable = new Vector<ProjectRowView>();
  171. foreach (var other in yield project_service.list_projects()) {
  172. if (other.id != project_id) {
  173. linkable.add(new ProjectRowView() {
  174. id = other.id,
  175. name = other.name,
  176. git_url = other.git_url,
  177. default_branch = other.default_branch,
  178. summary = other.summary ?? ""
  179. });
  180. }
  181. }
  182. var task_rows = new Vector<ProjectTaskRowView>();
  183. foreach (var task in yield task_service.list_tasks(null, project_id)) {
  184. task_rows.add(new ProjectTaskRowView() {
  185. id = task.id,
  186. title = task.title,
  187. status = task.status,
  188. priority = task.priority,
  189. is_design_task = task.is_design_task
  190. });
  191. }
  192. var rule_rows = new Vector<ProjectRuleRowView>();
  193. foreach (var link in yield rule_service.list_project_attachments(project_id)) {
  194. var rule = yield rule_service.get_rule(link.rule_id);
  195. if (rule == null) {
  196. continue;
  197. }
  198. var private_blob = new ProjectRuleActionPrivate() {
  199. project_id = project_id,
  200. link_id = link.id
  201. };
  202. rule_rows.add(new ProjectRuleRowView() {
  203. link_id = link.id,
  204. rule_id = link.rule_id,
  205. name = ((!)rule).name,
  206. body = ((!)rule).body,
  207. detach = action_registry.author_private<DetachProjectRuleAction, ProjectRuleActionPrivate>(private_blob)
  208. });
  209. }
  210. var rule_options = new Vector<RuleOptionView>();
  211. foreach (var rule in yield rule_service.list_rules()) {
  212. rule_options.add(new RuleOptionView() {
  213. id = rule.id,
  214. name = rule.name
  215. });
  216. }
  217. var props = (PropertyDictionary) GObjectMapping.to_properties(pub);
  218. props["links"] = StateJson.list<ProjectLinkView>(links.to_immutable_buffer());
  219. props["linkable"] = StateJson.list<ProjectRowView>(linkable.to_immutable_buffer());
  220. props["tasks"] = StateJson.list<ProjectTaskRowView>(task_rows.to_immutable_buffer());
  221. props["rules"] = StateJson.list<ProjectRuleRowView>(rule_rows.to_immutable_buffer());
  222. props["rule_options"] = StateJson.list<RuleOptionView>(rule_options.to_immutable_buffer());
  223. var slot = state_service.new_slot(Scope.PAGE, new State() {
  224. type_name = "project",
  225. public_data = props,
  226. private_data = new PropertyDictionary()
  227. });
  228. return directives().set(slot);
  229. }
  230. }
  231. }