using WebDrop.Core; namespace WebDrop.Storage; public sealed partial class SqliteAccountStore { public AccountProfile Profile(string username) { lock (_gate) { using var cmd = _connection.CreateCommand(); cmd.CommandText = "SELECT display_name,avatar,modified_at FROM account_profiles WHERE username=$u"; cmd.Parameters.AddWithValue("$u", username); using var reader = cmd.ExecuteReader(); return reader.Read() ? new AccountProfile { Username = username, DisplayName = reader.GetString(0), Avatar = reader.IsDBNull(1) ? null : reader.GetString(1), ModifiedAt = reader.GetInt64(2) } : new AccountProfile { Username = username, DisplayName = username, ModifiedAt = 0 }; } } public AccountProfile UpdateProfile(string username, string displayName, string? avatar) { if (displayName is null || displayName.Trim().Length > 48 || displayName.Any(char.IsControl)) throw new WebDropException(ErrorCode.BadRequest); if (avatar is not null) { string? prefix = new[] { "data:image/png;base64,", "data:image/jpeg;base64,", "data:image/webp;base64," }.FirstOrDefault(p => avatar.StartsWith(p, StringComparison.Ordinal)); if (prefix is null || avatar.Length > 350_000 || avatar.Length <= prefix.Length) throw new WebDropException(ErrorCode.BadRequest); try { _ = Convert.FromBase64String(avatar[prefix.Length..]); } catch (FormatException) { throw new WebDropException(ErrorCode.BadRequest); } } lock (_gate) { using var cmd = _connection.CreateCommand(); cmd.CommandText = "INSERT INTO account_profiles(username,display_name,avatar,modified_at) VALUES($u,$n,$a,$t) ON CONFLICT(username) DO UPDATE SET display_name=excluded.display_name,avatar=excluded.avatar,modified_at=MAX(account_profiles.modified_at+1,excluded.modified_at)"; cmd.Parameters.AddWithValue("$u", username); cmd.Parameters.AddWithValue("$n", displayName.Trim()); cmd.Parameters.AddWithValue("$a", (object?)avatar ?? DBNull.Value); cmd.Parameters.AddWithValue("$t", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); cmd.ExecuteNonQuery(); return Profile(username); } } }