using Invercargill; using Invercargill.DataStructures; using Json; namespace Spry.Users { public class User : GLib.Object { // Identity public string id { get; set; } public string username { get; set; } public string email { get; set; } public string password_hash { get; set; } // Metadata public DateTime created_at { get; set; } public DateTime? updated_at { get; set; } // Permissions - stored as JSON array of strings public Vector permissions { get; set; default = new Vector(); } // Application-specific data - stored as JSON object public Dictionary app_data { get; set; default = new Dictionary(); } public User() { id = ""; username = ""; email = ""; password_hash = ""; created_at = new DateTime.now_utc(); } public static User from_json(Json.Object obj) { var user = new User(); // Required string fields - use has_member and null coalescing for safety user.id = obj.has_member("id") ? (obj.get_string_member("id") ?? "") : ""; user.username = obj.has_member("username") ? (obj.get_string_member("username") ?? "") : ""; user.email = obj.has_member("email") ? (obj.get_string_member("email") ?? "") : ""; user.password_hash = obj.has_member("password_hash") ? (obj.get_string_member("password_hash") ?? "") : ""; // created_at - check member exists and value is not null/empty if (obj.has_member("created_at")) { var created_str = obj.get_string_member("created_at"); if (created_str != null && created_str.length > 0) { user.created_at = new DateTime.from_iso8601(created_str, new TimeZone.utc()); } } // updated_at (optional) - check member exists, is not null, and has value if (obj.has_member("updated_at")) { var member = obj.get_member("updated_at"); if (member != null && member.get_node_type() == Json.NodeType.VALUE) { var updated_str = obj.get_string_member("updated_at"); if (updated_str != null && updated_str.length > 0) { user.updated_at = new DateTime.from_iso8601(updated_str, new TimeZone.utc()); } } } // permissions (array of strings) - check member exists and is array if (obj.has_member("permissions")) { var member = obj.get_member("permissions"); if (member != null && member.get_node_type() == Json.NodeType.ARRAY) { var perms_array = obj.get_array_member("permissions"); if (perms_array != null) { foreach (var perm in perms_array.get_elements()) { var perm_str = perm.get_string(); if (perm_str != null) { user.permissions.add(perm_str); } } } } } // app_data (object with string values) - check member exists and is object if (obj.has_member("app_data")) { var member = obj.get_member("app_data"); if (member != null && member.get_node_type() == Json.NodeType.OBJECT) { var app_data_obj = obj.get_object_member("app_data"); if (app_data_obj != null) { foreach (var key in app_data_obj.get_members()) { var value = app_data_obj.get_string_member(key); user.app_data.set(key, value ?? ""); } } } } return user; } public Json.Object to_json() { var obj = new Json.Object(); // Use null coalescing to ensure we never pass null to set_string_member obj.set_string_member("id", id ?? ""); obj.set_string_member("username", username ?? ""); obj.set_string_member("email", email ?? ""); obj.set_string_member("password_hash", password_hash ?? ""); obj.set_string_member("created_at", created_at != null ? created_at.format_iso8601() : new DateTime.now_utc().format_iso8601()); // updated_at (optional) if (updated_at != null) { obj.set_string_member("updated_at", ((!)updated_at).format_iso8601()); } else { obj.set_null_member("updated_at"); } // permissions array - always create array even if empty var perms_array = new Json.Array(); if (permissions != null) { foreach (var perm in permissions) { if (perm != null) { perms_array.add_string_element(perm); } } } obj.set_array_member("permissions", perms_array); // app_data object - always create object even if empty var app_data_obj = new Json.Object(); if (app_data != null) { var iter = app_data.iterator(); while (iter.next()) { var pair = iter.get(); app_data_obj.set_string_member(pair.key ?? "", pair.value ?? ""); } } obj.set_object_member("app_data", app_data_obj); return obj; } } }