| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- using Invercargill;
- using Invercargill.DataStructures;
- using InvercargillSql;
- namespace Spry.Authentication {
- /**
- * SQL implementation of UserRepository using InvercargillSql.
- */
- public class SqlUserRepository : Object, UserRepository {
- private Connection _connection;
- // =========================================================================
- // Constructor
- // =========================================================================
- public SqlUserRepository(Connection connection) {
- _connection = connection;
- }
- // =========================================================================
- // Retrieval Operations
- // =========================================================================
- public async User? get_by_id(string id) throws Error {
- var sql = "SELECT * FROM users WHERE id = :id";
- var results = yield _connection.create_command(sql)
- .with_parameter("id", id)
- .execute_query_async();
- var row = results.first_or_default();
- if (row == null) {
- return null;
- }
- var user = user_from_properties(row);
- // Load permissions
- var permissions = yield get_permissions(id);
- foreach (var perm in permissions) {
- user.add_permission(perm);
- }
- // Load app data
- var app_data = yield get_all_app_data(id);
- foreach (var key in app_data.keys) {
- user.app_data.set(key, app_data.get(key));
- }
- return user;
- }
- public async User? get_by_username(string username) throws Error {
- var sql = "SELECT * FROM users WHERE username = :username";
- var results = yield _connection.create_command(sql)
- .with_parameter("username", username)
- .execute_query_async();
- var row = results.first_or_default();
- if (row == null) {
- return null;
- }
- var user = user_from_properties(row);
- // Load permissions
- var permissions = yield get_permissions(user.id);
- foreach (var perm in permissions) {
- user.add_permission(perm);
- }
- // Load app data
- var app_data = yield get_all_app_data(user.id);
- foreach (var key in app_data.keys) {
- user.app_data.set(key, app_data.get(key));
- }
- return user;
- }
- public async User? get_by_email(string email) throws Error {
- var sql = "SELECT * FROM users WHERE email = :email";
- var results = yield _connection.create_command(sql)
- .with_parameter("email", email)
- .execute_query_async();
- var row = results.first_or_default();
- if (row == null) {
- return null;
- }
- var user = user_from_properties(row);
- // Load permissions
- var permissions = yield get_permissions(user.id);
- foreach (var perm in permissions) {
- user.add_permission(perm);
- }
- // Load app data
- var app_data = yield get_all_app_data(user.id);
- foreach (var key in app_data.keys) {
- user.app_data.set(key, app_data.get(key));
- }
- return user;
- }
- // =========================================================================
- // Mutation Operations
- // =========================================================================
- public async User create(string username, string email, string password_hash) throws Error {
- var id = generate_uuid();
- var now = new DateTime.now_utc();
- var sql = """
- INSERT INTO users (id, username, email, password_hash, created_at, updated_at)
- VALUES (:id, :username, :email, :password_hash, :created_at, :updated_at)
- """;
- yield _connection.create_command(sql)
- .with_parameter("id", id)
- .with_parameter("username", username)
- .with_parameter("email", email)
- .with_parameter("password_hash", password_hash)
- .with_parameter("created_at", now.format_iso8601())
- .with_parameter("updated_at", now.format_iso8601())
- .execute_non_query_async();
- var user = new User();
- user.set_id(id);
- user.set_username(username);
- user.email = email;
- user.password_hash = password_hash;
- user.created_at = now;
- user.updated_at = now;
- return user;
- }
- public async void update(User user) throws Error {
- var now = new DateTime.now_utc();
- var sql = """
- UPDATE users SET
- username = :username,
- email = :email,
- password_hash = :password_hash,
- updated_at = :updated_at
- WHERE id = :id
- """;
- yield _connection.create_command(sql)
- .with_parameter("id", user.id)
- .with_parameter("username", user.username)
- .with_parameter("email", user.email)
- .with_parameter("password_hash", user.password_hash)
- .with_parameter("updated_at", now.format_iso8601())
- .execute_non_query_async();
- user.updated_at = now;
- }
- public async void delete(string id) throws Error {
- var sql = "DELETE FROM users WHERE id = :id";
- yield _connection.create_command(sql)
- .with_parameter("id", id)
- .execute_non_query_async();
- }
- // =========================================================================
- // Query Operations
- // =========================================================================
- public async bool exists_by_username(string username) throws Error {
- var sql = "SELECT COUNT(*) FROM users WHERE username = :username";
- var scalar = yield _connection.create_command(sql)
- .with_parameter("username", username)
- .execute_scalar_async();
- if (scalar == null) {
- return false;
- }
- return scalar.as<int64?>() > 0;
- }
- public async bool exists_by_email(string email) throws Error {
- var sql = "SELECT COUNT(*) FROM users WHERE email = :email";
- var scalar = yield _connection.create_command(sql)
- .with_parameter("email", email)
- .execute_scalar_async();
- if (scalar == null) {
- return false;
- }
- return scalar.as<int64?>() > 0;
- }
- // =========================================================================
- // Permission Operations
- // =========================================================================
- public async void add_permission(string user_id, string permission) throws Error {
- var sql = """
- INSERT OR IGNORE INTO user_permissions (user_id, permission)
- VALUES (:user_id, :permission)
- """;
- yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .with_parameter("permission", permission)
- .execute_non_query_async();
- }
- public async void remove_permission(string user_id, string permission) throws Error {
- var sql = """
- DELETE FROM user_permissions
- WHERE user_id = :user_id AND permission = :permission
- """;
- yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .with_parameter("permission", permission)
- .execute_non_query_async();
- }
- public async bool has_permission(string user_id, string permission) throws Error {
- var sql = """
- SELECT COUNT(*) FROM user_permissions
- WHERE user_id = :user_id AND permission = :permission
- """;
- var scalar = yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .with_parameter("permission", permission)
- .execute_scalar_async();
- if (scalar == null) {
- return false;
- }
- return scalar.as<int64?>() > 0;
- }
- public async Vector<string> get_permissions(string user_id) throws Error {
- var sql = "SELECT permission FROM user_permissions WHERE user_id = :user_id";
- var results = yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .execute_query_async();
- var permissions = new Vector<string>();
- foreach (var row in results) {
- var perm_elem = row.get("permission");
- if (perm_elem != null) {
- var perm = perm_elem.as<string>();
- if (perm != null && perm.length > 0) {
- permissions.add(perm);
- }
- }
- }
- return permissions;
- }
- // =========================================================================
- // App Data Operations
- // =========================================================================
- public async void set_app_data(string user_id, string key, string value) throws Error {
- var sql = """
- INSERT OR REPLACE INTO user_app_data (user_id, key, value)
- VALUES (:user_id, :key, :value)
- """;
- yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .with_parameter("key", key)
- .with_parameter("value", value)
- .execute_non_query_async();
- }
- public async string? get_app_data(string user_id, string key) throws Error {
- var sql = """
- SELECT value FROM user_app_data
- WHERE user_id = :user_id AND key = :key
- """;
- var scalar = yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .with_parameter("key", key)
- .execute_scalar_async();
- if (scalar == null) {
- return null;
- }
- return scalar.as<string>();
- }
- // =========================================================================
- // Private Helpers
- // =========================================================================
- private User user_from_properties(Properties props) {
- var user = new User();
- // Required fields
- user.set_id(get_string_or_empty(props, "id"));
- user.set_username(get_string_or_empty(props, "username"));
- user.email = get_string_or_empty(props, "email");
- user.password_hash = get_string_or_empty(props, "password_hash");
- // created_at
- var created_str = get_string_or_empty(props, "created_at");
- if (created_str.length > 0) {
- user.created_at = new DateTime.from_iso8601(created_str, new TimeZone.utc());
- }
- // updated_at (nullable)
- var updated_str = get_string_or_null(props, "updated_at");
- if (updated_str != null && updated_str.length > 0) {
- user.updated_at = new DateTime.from_iso8601(updated_str, new TimeZone.utc());
- }
- return user;
- }
- private string get_string_or_empty(Properties props, string key) {
- if (!props.has(key)) {
- return "";
- }
- var elem = props.get(key);
- if (elem == null) {
- return "";
- }
- var str = elem.as<string>();
- return str ?? "";
- }
- private string? get_string_or_null(Properties props, string key) {
- if (!props.has(key)) {
- return null;
- }
- var elem = props.get(key);
- if (elem == null) {
- return null;
- }
- return elem.as<string>();
- }
- private async Dictionary<string, string> get_all_app_data(string user_id) throws Error {
- var sql = "SELECT key, value FROM user_app_data WHERE user_id = :user_id";
- var results = yield _connection.create_command(sql)
- .with_parameter("user_id", user_id)
- .execute_query_async();
- var app_data = new Dictionary<string, string>();
- foreach (var row in results) {
- var key_elem = row.get("key");
- var value_elem = row.get("value");
- if (key_elem != null) {
- var key = key_elem.as<string>();
- var value = value_elem != null ? value_elem.as<string>() ?? "" : "";
- if (key != null && key.length > 0) {
- app_data.set(key, value);
- }
- }
- }
- return app_data;
- }
- private string generate_uuid() {
- uint8[] bytes = new uint8[16];
- Sodium.Random.random_bytes(bytes);
- // Set version 4 (random UUID)
- bytes[6] = (bytes[6] & 0x0f) | 0x40;
- // Set variant RFC 4122
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
- return "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x".printf(
- bytes[0], bytes[1], bytes[2], bytes[3],
- bytes[4], bytes[5], bytes[6], bytes[7],
- bytes[8], bytes[9], bytes[10], bytes[11],
- bytes[12], bytes[13], bytes[14], bytes[15]
- );
- }
- }
- }
|