using Inversion; using Spry; using Spry.Authentication; using Statum; using ClankerSaddle.Services; namespace ClankerSaddle { /** Sealed stack id carried by the stack actions. */ public class StackActionPrivate : Object { public int64 stack_id { get; set; } } /** Sealed stack-member identity carried by {@link RemoveStackMemberAction}. */ public class StackMemberActionPrivate : Object { public int64 stack_id { get; set; } public int64 member_id { get; set; } } /** Sealed stack + project identity for {@link ApplyStackToProjectAction}. */ public class StackProjectApplyPrivate : Object { public int64 stack_id { get; set; } public int64 project_id { get; set; } } /** Sealed stack + goal identity for {@link ApplyStackToGoalAction}. */ public class StackGoalApplyPrivate : Object { public int64 stack_id { get; set; } public int64 goal_id { get; set; } } /** * Creates a stack from the stacks form (name required, unique; * summary optional). */ public class CreateStackAction : StatumAction { private StackService stacks = 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"); if (name.length == 0) { return directives().notify("error", "A stack name is required").navigate("/stacks"); } try { var stack = yield stacks.create_stack(name, Forms.field(request, "summary")); return directives().notify("success", @"Stack '$(stack.name)' created") .navigate(@"/stacks/$(stack.id)"); } catch (Error e) { return directives().notify("error", @"Could not create the stack: $(e.message)").navigate("/stacks"); } } } /** * Saves a stack's editable fields (name, summary); the id comes * from the sealed {@link StackActionPrivate} blob (the hidden form * field is a fallback). An empty summary clears the stored one. */ public class UpdateStackAction : TypedStatumAction { private StackService stacks = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(StackActionPrivate); } } 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 stack_id = ctx != null ? ((!)ctx).stack_id : Forms.integer(request, "stack_id", 0); if (stack_id <= 0) { return directives().navigate("/stacks"); } try { yield stacks.update_stack(stack_id, Forms.optional(request, "name"), request.form != null ? Forms.field(request, "summary") : null); return directives().notify("success", "Stack saved") .navigate(@"/stacks/$(stack_id)"); } catch (Error e) { return directives().notify("error", @"Could not save the stack: $(e.message)") .navigate(@"/stacks/$(stack_id)"); } } } /** Deletes a stack; the id comes from the sealed private blob. */ public class DeleteStackAction : TypedStatumAction { private StackService stacks = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(StackActionPrivate); } } 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("/stacks"); } try { yield stacks.delete_stack(((!)ctx).stack_id); return directives().notify("success", "Stack deleted").navigate("/stacks"); } catch (Error e) { return directives().notify("error", @"Could not delete the stack: $(e.message)") .navigate("/stacks/$(((!)ctx).stack_id)"); } } } /** * Adds a project to a stack; the stack id is sealed, the target * and relationship come from the form selects. */ public class AddStackMemberAction : TypedStatumAction { private StackService stacks = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(StackActionPrivate); } } 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 stack_id = ctx != null ? ((!)ctx).stack_id : Forms.integer(request, "stack_id", 0); int64 project_id = Forms.integer(request, "project_id", 0); if (stack_id <= 0 || project_id <= 0) { return directives().notify("error", "Pick a project to add").navigate(@"/stacks/$(stack_id)"); } try { yield stacks.add_member(stack_id, project_id, Forms.field(request, "relationship")); return directives().notify("success", "Project added to the stack") .navigate(@"/stacks/$(stack_id)"); } catch (Error e) { return directives().notify("error", @"Could not add the project: $(e.message)") .navigate(@"/stacks/$(stack_id)"); } } } /** * Removes a stack member; both ids come from the sealed private * blob. */ public class RemoveStackMemberAction : TypedStatumAction { private StackService stacks = inject(); private UserService users = inject(); protected override Type private_type { get { return typeof(StackMemberActionPrivate); } } 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("/stacks"); } try { yield stacks.remove_member(((!)ctx).member_id); return directives().notify("success", "Project removed from the stack") .navigate(@"/stacks/$(((!)ctx).stack_id)"); } catch (Error e) { return directives().notify("error", @"Could not remove the project: $(e.message)") .navigate(@"/stacks/$(((!)ctx).stack_id)"); } } } /** * Applies a stack to a project (the project page's "Apply stack" * action): the stack's members land as project_links rows, * skipping exact duplicates. The stack comes from the form select; * the project id is sealed. */ public class ApplyStackToProjectAction : TypedStatumAction { private StackService stacks = 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 stack_id = Forms.integer(request, "stack_id", 0); if (project_id <= 0 || stack_id <= 0) { return directives().notify("error", "Pick a stack to apply").navigate(@"/projects/$(project_id)"); } try { int64 added = yield stacks.apply_to_project(stack_id, project_id); return directives().notify("success", added > 0 ? @"Stack applied — $(added) link(s) added" : "Stack applied — nothing new to add") .navigate(@"/projects/$(project_id)"); } catch (Error e) { return directives().notify("error", @"Could not apply the stack: $(e.message)") .navigate(@"/projects/$(project_id)"); } } } /** * Applies a stack to a goal (the goal page's "Apply stack" * action): the stack's members land as goal_links rows, skipping * exact duplicates. The stack comes from the form select; the * goal id is sealed. */ public class ApplyStackToGoalAction : SealedGoalAction { private StackService stacks = 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; } int64 goal_id = sealed_goal_id(); int64 stack_id = Forms.integer(request, "stack_id", 0); if (goal_id <= 0 || stack_id <= 0) { return directives().notify("error", "Pick a stack to apply").navigate(@"/goals/$(goal_id)"); } try { int64 added = yield stacks.apply_to_goal(stack_id, goal_id); return directives().notify("success", added > 0 ? @"Stack applied — $(added) link(s) added" : "Stack applied — nothing new to add") .navigate(@"/goals/$(goal_id)"); } catch (Error e) { return directives().notify("error", @"Could not apply the stack: $(e.message)") .navigate(@"/goals/$(goal_id)"); } } } }