UsersEntrypoint.vala 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Spry;
  4. using Spry.Authentication;
  5. using Statum;
  6. namespace ClankerSaddle {
  7. /** One row of the users table, with its row actions. */
  8. public class ManagedUserRowView : Object {
  9. public int64 id { get; set; }
  10. public string username { get; set; default = ""; }
  11. public string email { get; set; default = ""; }
  12. public string forename { get; set; default = ""; }
  13. public string surname { get; set; default = ""; }
  14. public string name_display { get; set; default = ""; }
  15. public string date_of_birth { get; set; default = ""; }
  16. public bool enabled { get; set; }
  17. public bool is_admin { get; set; }
  18. public bool is_me { get; set; }
  19. public string created_display { get; set; default = ""; }
  20. public Statum.Model.ActionDto? alter { get; set; }
  21. public Statum.Model.ActionDto? set_enabled { get; set; }
  22. public Statum.Model.ActionDto? grant_admin { get; set; }
  23. public Statum.Model.ActionDto? revoke_admin { get; set; }
  24. public Statum.Model.ActionDto? delete { get; set; }
  25. }
  26. /** Public data of the `users` slot. */
  27. public class UsersPublic : Object {
  28. public Statum.Model.ActionDto create { get; set; }
  29. }
  30. /**
  31. * Entrypoint for /users: the account table every row of which
  32. * carries its admin role badge, enabled state and row actions (edit
  33. * expander, enable/disable, grant/revoke admin, delete). Admin-only
  34. * (the authoritative guard; the nav link is hidden for non-admins
  35. * but the guard is what keeps the page closed).
  36. *
  37. * Rows are newest-first: the goal brief's list convention. The
  38. * enable/disable action seals the row's NEXT state so the button
  39. * never races the table, and the edit expander is prefilled from the
  40. * same row (one round trip, no separate fetch).
  41. */
  42. public class UsersEntrypoint : StatumEntrypoint {
  43. private UserService users_service = Inversion.inject<UserService>();
  44. public override async DirectiveBuilder handle() throws GLib.Error {
  45. var guard = yield SpryAuth.require_permission(directives(), request, users_service,
  46. SpryAuth.ADMIN_PERMISSION, "/login");
  47. if (guard != null) {
  48. return (!)guard;
  49. }
  50. var me = SpryAuth.current(request);
  51. var rows = new Vector<ManagedUserRowView>();
  52. foreach (var user in yield users_service.list_users(0, 1000)) {
  53. bool is_admin = SpryAuth.permission_matches(user.permissions.to_array(),
  54. SpryAuth.ADMIN_PERMISSION);
  55. var sealed_row = new ManagedUserPrivate() {
  56. user_id = user.id,
  57. enabled = !user.enabled
  58. };
  59. var view = new ManagedUserRowView() {
  60. id = user.id,
  61. username = user.username,
  62. email = user.email,
  63. forename = user.forename,
  64. surname = user.surname,
  65. name_display = @"$(user.forename) $(user.surname)".strip(),
  66. date_of_birth = user.date_of_birth.format("%Y-%m-%d"),
  67. enabled = user.enabled,
  68. is_admin = is_admin,
  69. is_me = me.user_id == user.id,
  70. created_display = format_stamp(user.created),
  71. alter = action_registry.author_private<AlterManagedUserAction, ManagedUserPrivate>(
  72. new ManagedUserPrivate() { user_id = user.id }),
  73. set_enabled = action_registry.author_private<SetManagedUserEnabledAction, ManagedUserPrivate>(sealed_row),
  74. delete = action_registry.author_private<DeleteManagedUserAction, ManagedUserPrivate>(
  75. new ManagedUserPrivate() { user_id = user.id })
  76. };
  77. if (is_admin) {
  78. view.revoke_admin = action_registry.author_private<RevokeUserAdminAction, ManagedUserPrivate>(
  79. new ManagedUserPrivate() { user_id = user.id });
  80. } else {
  81. view.grant_admin = action_registry.author_private<GrantUserAdminAction, ManagedUserPrivate>(
  82. new ManagedUserPrivate() { user_id = user.id });
  83. }
  84. rows.add(view);
  85. }
  86. // Newest first. The ids are autoincrement, so id-descending
  87. // IS created-descending — including the same-minute ties the
  88. // display stamp (minute resolution) cannot distinguish.
  89. var sorted = rows.to_array();
  90. for (int i = 1; i < sorted.length; i++) {
  91. var row = sorted[i];
  92. int j = i - 1;
  93. while (j >= 0 && sorted[j].id < row.id) {
  94. sorted[j + 1] = sorted[j];
  95. j--;
  96. }
  97. sorted[j + 1] = row;
  98. }
  99. var pub = new UsersPublic();
  100. pub.create = action_registry.author<CreateUserAction>();
  101. var props = (PropertyDictionary) GObjectMapping.to_properties(pub);
  102. props["users"] = StateJson.list_array<ManagedUserRowView>(sorted);
  103. var slot = state_service.new_slot(Scope.PAGE, new State() {
  104. type_name = "users",
  105. public_data = props,
  106. private_data = new PropertyDictionary()
  107. });
  108. return directives().set(slot);
  109. }
  110. }
  111. }