using Inversion; using Spry; using Spry.Authentication; using Statum; using ClankerSaddle.Services; namespace ClankerSaddle { /** Sealed project id carried by the project actions. */ public class ProjectActionPrivate : Object { public int64 project_id { get; set; } } /** Sealed link identity carried by {@link RemoveProjectLinkAction}. */ public class ProjectLinkActionPrivate : Object { public int64 project_id { get; set; } public int64 link_id { get; set; } } /** Sealed note identity carried by {@link DeleteProjectNoteAction}. */ public class ProjectNoteActionPrivate : Object { public int64 project_id { get; set; } public int64 note_id { get; set; } } /** * Creates a project from the projects form (name and git URL * required); default_branch falls back to 'main'. */ public class CreateProjectAction : StatumAction { private ProjectService projects = 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; } string name = Forms.field(request, "name"); string git_url = Forms.field(request, "git_url"); if (name.length == 0 || git_url.length == 0) { return directives().notify("error", "Name and git URL are required").navigate("/projects"); } try { var project = yield projects.create_project(name, git_url, Forms.field(request, "default_branch"), Forms.field(request, "summary")); return directives().notify("success", @"Project '$(project.name)' created") .navigate(@"/projects/$(project.id)"); } catch (Error e) { return directives().notify("error", @"Could not create the project: $(e.message)").navigate("/projects"); } } } /** * Saves the editable fields of a project; the id comes from the sealed * {@link ProjectActionPrivate} blob (the hidden form field is a * fallback). An empty summary clears the stored one. */ public class UpdateProjectAction : TypedStatumAction { private ProjectService projects = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(ProjectActionPrivate); } } 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 project_id = ctx != null ? ((!)ctx).project_id : Forms.integer(request, "project_id", 0); if (project_id <= 0) { return directives().navigate("/projects"); } try { yield projects.update_project(project_id, Forms.optional(request, "name"), Forms.optional(request, "git_url"), Forms.optional(request, "default_branch"), request.form != null ? Forms.field(request, "summary") : null); return directives().notify("success", "Project saved") .navigate(@"/projects/$(project_id)"); } catch (Error e) { return directives().notify("error", @"Could not save the project: $(e.message)") .navigate(@"/projects/$(project_id)"); } } } /** * Links another project to this one; the parent id is sealed, the * target and relationship come from the form selects. */ public class AddProjectLinkAction : TypedStatumAction { private ProjectService projects = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(ProjectActionPrivate); } } 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 project_id = ctx != null ? ((!)ctx).project_id : Forms.integer(request, "project_id", 0); int64 linked_id = Forms.integer(request, "linked_project_id", 0); string relationship = Forms.field(request, "relationship"); if (project_id <= 0 || linked_id <= 0) { return directives().notify("error", "Pick a project to link").navigate(@"/projects/$(project_id)"); } try { yield projects.add_link(project_id, linked_id, relationship); return directives().notify("success", "Link added").navigate(@"/projects/$(project_id)"); } catch (Error e) { return directives().notify("error", @"Could not add the link: $(e.message)") .navigate(@"/projects/$(project_id)"); } } } /** Removes a project link; both ids come from the sealed private blob. */ public class RemoveProjectLinkAction : TypedStatumAction { private ProjectService projects = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(ProjectLinkActionPrivate); } } 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("/projects"); } try { yield projects.remove_link(((!)ctx).link_id); return directives().notify("success", "Link removed") .navigate(@"/projects/$(((!)ctx).project_id)"); } catch (Error e) { return directives().notify("error", @"Could not remove the link: $(e.message)") .navigate(@"/projects/$(((!)ctx).project_id)"); } } } /** * Deletes a project note; both ids come from the sealed private blob. * Notes are LLM-authored (`saddle note` via the callback endpoint) — * the web UI only curates them. */ public class DeleteProjectNoteAction : TypedStatumAction { private ProjectService projects = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(ProjectNoteActionPrivate); } } 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("/projects"); } try { yield projects.delete_note(((!)ctx).note_id); return directives().notify("success", "Note deleted") .navigate(@"/projects/$(((!)ctx).project_id)"); } catch (Error e) { return directives().notify("error", @"Could not delete the note: $(e.message)") .navigate(@"/projects/$(((!)ctx).project_id)"); } } } }