Forráskód Böngészése

feat(auth): add user creation form to UserManagementComponent

Add interactive user creation form with fields for username, email,
forename, surname, date of birth, and password. Include error and
success message display for user feedback.

Also handle null permissions gracefully in AuthorisationToken by
defaulting to empty buffer, and fix duplicate yield statement in
UserService insert_async method.
Billy Barrow 1 hónapja
szülő
commit
8f44279980

+ 73 - 4
src/Authentication/Components/UserManagementComponent.vala

@@ -9,19 +9,50 @@ namespace Spry.Authentication {
         public string authorisation_permission { get; set; default = "spry.admin"; }
         public bool authorised { get; set; }
         public const int64 users_per_page = 20;
+        public string? error_message { get; set; }
+        public string? success_message { get; set; }
 
         public override string markup { get { return """
         <spry-context property="page_number" />
         <spry-context property="authorisation_permission" />
+        <spry-context property="error_message" />
+        <spry-context property="success_message" />
         <div spry-if="!this.authorised">
             <strong>You must have the permission <code content-expr="this.authorisation_permission"></code> to access this content.</strong>
         </div>
-        <div spry-else sid="container" style="gap: 1em;">
+        <div spry-else sid="container" style="gap: 1em; padding: 1em;">
             <spry-outlet sid="outlet" />
+            <details>
+                <summary><strong>Create New User</strong></summary>
+                <form spry-action=":create" spry-target="container" spry-method="post" hx-disabled-elt="find input, find button" hx-indicator="find button" hx-swap="outerHTML">
+                    <div style="display: grid; grid-template-columns: max-content auto; column-gap: 3em; row-gap: 1em;">
+                        <label for="username">Username:</label>
+                        <input type="text" id="username" name="username" required />
+                        <label for="email">Email:</label>
+                        <input type="email" id="email" name="email" required />
+                        <label for="forename">Forename:</label>
+                        <input type="text" id="forename" name="forename" required />
+                        <label for="surname">Surname:</label>
+                        <input type="text" id="surname" name="surname" required />
+                        <label for="date_of_birth">Date of Birth:</label>
+                        <input type="date" id="date_of_birth" name="date_of_birth" required />
+                        <label for="password">Password:</label>
+                        <input type="password" id="password" name="password" required />
+                        <div></div>
+                        <button type="submit">Create User</button>
+                    </div>
+                </form>
+            </details>
+            <div spry-if="this.error_message != null" style="color: red;">
+                <strong content-expr="this.error_message"></strong>
+            </div>
+            <div spry-if="this.success_message != null" style="color: green;">
+                <strong content-expr="this.success_message"></strong>
+            </div>
             <div>
-                <button spry-action=":previous" spry-target="container">Previous</button>
+                <button spry-action=":previous" spry-target="container" hx-swap="outerHTML">Previous</button>
                 <span>Page <strong content-expr="stringify(this.page_number + 1)"></strong></span>
-                <button spry-action=":next" spry-target="container">Next</button>
+                <button spry-action=":next" spry-target="container" hx-swap="outerHTML">Next</button>
             </div>
         </div>
         """; }}
@@ -29,6 +60,7 @@ namespace Spry.Authentication {
         private UserService user_service = inject<UserService>();
         private AuthorisationContext authorisation_context = inject<AuthorisationContext>();
         private ComponentFactory component_factory = inject<ComponentFactory>();
+        private HttpContext http_context = inject<HttpContext>();
 
         public async override void prepare () throws Error {
             if(authorisation_context.is_anonymous() || !authorisation_context.has_permission (authorisation_permission)) {
@@ -56,8 +88,45 @@ namespace Spry.Authentication {
             if(action == "next") {
                 page_number ++;
             }
+            if(action == "create") {
+                try {
+                    var form = yield Astralis.FormDataParser.parse(http_context.request.request_body, http_context.request.content_type);
+                    
+                    var username = form.get_field("username");
+                    var email = form.get_field("email");
+                    var forename = form.get_field("forename");
+                    var surname = form.get_field("surname");
+                    var date_of_birth_str = form.get_field("date_of_birth");
+                    var password = form.get_field("password");
+                    
+                    if (username == null || email == null || forename == null || surname == null || date_of_birth_str == null || password == null) {
+                        error_message = "All fields are required.";
+                        return;
+                    }
+                    
+                    var date_parts = date_of_birth_str.split("-");
+                    if (date_parts.length != 3) {
+                        error_message = "Invalid date format. Use YYYY-MM-DD.";
+                        return;
+                    }
+                    
+                    var year = int.parse(date_parts[0]);
+                    var month = int.parse(date_parts[1]);
+                    var day = int.parse(date_parts[2]);
+                    
+                    var date_of_birth = new DateTime.utc(year, month, day, 0, 0, 0);
+                    
+                    yield user_service.register_user(username, email, forename, surname, date_of_birth, password);
+                    
+                    success_message = "User created successfully.";
+                    error_message = null;
+                } catch (Error e) {
+                    error_message = "Failed to create user: %s".printf(e.message);
+                    success_message = null;
+                }
+            }
         }
         
     }
 
-}
+}

+ 1 - 1
src/Authentication/UserService.vala

@@ -38,7 +38,7 @@ namespace Spry.Authentication {
                 enabled = enabled,
             };
 
-            yield yield db.insert_async<UserEntity>(user);
+            yield db.insert_async<UserEntity>(user);
             return user;
         }
 

+ 2 - 1
src/Authorisation/AuthorisationToken.vala

@@ -68,7 +68,8 @@ namespace Spry.Authorisation {
             return PropertyMapper.build_for<AuthorisationToken>(cfg => {
                 cfg.map<Element>("uid", o => o.user_identifier, (o, v) => o.user_identifier = v);
                 cfg.map<string>("unm", o => o.username, (o, v) => o.username = v);
-                cfg.map_many<string>("prm", o => o.permissions, (o, v) => o.permissions = v.to_immutable_buffer());
+                cfg.map_many<string>("prm", o => o.permissions, (o, v) => o.permissions = v.to_immutable_buffer())
+                    .when_null(o => o.permissions = Iterate.nothing<string>().to_immutable_buffer());
                 cfg.map<Properties>("dat", o => o.data, (o, v) => o.data = v);
                 cfg.map<string>("iat", o => o.issued_at.format_iso8601(), (o, v) => o.issued_at = new DateTime.from_iso8601(v, new TimeZone.utc()));
                 cfg.map<string?>("eat",