Preferred language for the user
This commit is contained in:
@@ -212,8 +212,9 @@ public class ProductionAuthTests(ProductionApiTestFactory factory) : IClassFixtu
|
||||
{
|
||||
username = "editor",
|
||||
password = "editorpass",
|
||||
isAdmin = false,
|
||||
displayName = "Editor User"
|
||||
displayName = "Editor User",
|
||||
preferredLanguage = "fi",
|
||||
roles = Array.Empty<string>()
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode);
|
||||
@@ -227,14 +228,16 @@ public class ProductionAuthTests(ProductionApiTestFactory factory) : IClassFixtu
|
||||
var updateResponse = await _client.PutAsJsonAsync("/users/editor", new
|
||||
{
|
||||
password = "editorpass2",
|
||||
isAdmin = true,
|
||||
displayName = "Editor Admin"
|
||||
displayName = "Editor Admin",
|
||||
preferredLanguage = "en",
|
||||
roles = new[] { "admin" }
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, updateResponse.StatusCode);
|
||||
var updatedUser = await updateResponse.Content.ReadFromJsonAsync<UserDto>();
|
||||
Assert.NotNull(updatedUser);
|
||||
Assert.True(updatedUser.IsAdmin);
|
||||
Assert.Contains("admin", updatedUser.Roles);
|
||||
Assert.Equal("en", updatedUser.PreferredLanguage);
|
||||
Assert.Equal("Editor Admin", updatedUser.DisplayName);
|
||||
|
||||
var deleteResponse = await _client.DeleteAsync("/users/editor");
|
||||
@@ -332,7 +335,9 @@ public class AuthTokenDto
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public bool IsAdmin { get; set; }
|
||||
public string PreferredLanguage { get; set; } = string.Empty;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
|
||||
public string TokenType { get; set; } = string.Empty;
|
||||
|
||||
@@ -347,7 +352,9 @@ public class UserDto
|
||||
|
||||
public DateTime LastUpdated { get; set; }
|
||||
|
||||
public bool IsAdmin { get; set; }
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string PreferredLanguage { get; set; } = string.Empty;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public static class AuthEndpoints
|
||||
new(ClaimTypes.Name, authenticatedUser.Username),
|
||||
new("username", authenticatedUser.Username),
|
||||
new("display_name", authenticatedUser.DisplayName),
|
||||
new("preferred_language", authenticatedUser.PreferredLanguage),
|
||||
};
|
||||
|
||||
foreach (var role in authenticatedUser.Roles)
|
||||
@@ -74,6 +75,7 @@ public static class AuthEndpoints
|
||||
AccessToken = tokenValue,
|
||||
Username = authenticatedUser.Username,
|
||||
DisplayName = authenticatedUser.DisplayName,
|
||||
PreferredLanguage = authenticatedUser.PreferredLanguage,
|
||||
Roles = authenticatedUser.Roles,
|
||||
TokenType = "Bearer",
|
||||
ExpiresIn = 43200
|
||||
|
||||
@@ -6,6 +6,23 @@ public static class AppRoles
|
||||
public static readonly IReadOnlyList<string> All = [Lok, Admin];
|
||||
}
|
||||
|
||||
public static class AppLanguages
|
||||
{
|
||||
public const string Finnish = "fi";
|
||||
public const string English = "en";
|
||||
public const string Slovak = "sk";
|
||||
|
||||
public static readonly IReadOnlyList<string> All = [Finnish, English, Slovak];
|
||||
|
||||
public static string NormalizeOrDefault(string? language)
|
||||
{
|
||||
var normalized = language?.Trim().ToLowerInvariant();
|
||||
return !string.IsNullOrWhiteSpace(normalized) && All.Contains(normalized)
|
||||
? normalized
|
||||
: Finnish;
|
||||
}
|
||||
}
|
||||
|
||||
public class AppUser
|
||||
{
|
||||
public long Id { get; set; }
|
||||
@@ -20,6 +37,8 @@ public class AppUser
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string PreferredLanguage { get; set; } = AppLanguages.Finnish;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -33,6 +52,8 @@ public class AppUserView
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string PreferredLanguage { get; set; } = AppLanguages.Finnish;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -44,6 +65,8 @@ public class AppUserCreateRequest
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string PreferredLanguage { get; set; } = AppLanguages.Finnish;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -53,5 +76,7 @@ public class AppUserUpdateRequest
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string PreferredLanguage { get; set; } = AppLanguages.Finnish;
|
||||
|
||||
public List<string> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -241,6 +241,22 @@ public class Program
|
||||
SELECT id, 'admin' FROM Users WHERE isAdmin = 1;";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Migration: add preferredLanguage to Users if missing and backfill with Finnish
|
||||
command.CommandText = "SELECT COUNT(*) FROM pragma_table_info('Users') WHERE name = 'preferredLanguage';";
|
||||
var usersHasPreferredLanguage = Convert.ToInt32(command.ExecuteScalar()) > 0;
|
||||
|
||||
if (!usersHasPreferredLanguage)
|
||||
{
|
||||
command.CommandText = "ALTER TABLE Users ADD COLUMN preferredLanguage TEXT NOT NULL DEFAULT 'fi';";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
command.CommandText = @"
|
||||
UPDATE Users
|
||||
SET preferredLanguage = 'fi'
|
||||
WHERE preferredLanguage IS NULL OR TRIM(preferredLanguage) = '';";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,13 @@ public class UserService
|
||||
|
||||
await using var upsertCommand = _connection.CreateCommand();
|
||||
upsertCommand.CommandText = @"
|
||||
INSERT INTO Users (username, password, added, lastUpdated, displayName)
|
||||
VALUES (@username, @password, @added, @lastUpdated, @displayName)
|
||||
INSERT INTO Users (username, password, added, lastUpdated, displayName, preferredLanguage)
|
||||
VALUES (@username, @password, @added, @lastUpdated, @displayName, @preferredLanguage)
|
||||
ON CONFLICT(username) DO UPDATE SET
|
||||
password = excluded.password,
|
||||
lastUpdated = excluded.lastUpdated,
|
||||
displayName = excluded.displayName;
|
||||
displayName = excluded.displayName,
|
||||
preferredLanguage = excluded.preferredLanguage;
|
||||
SELECT id FROM Users WHERE username = @username;";
|
||||
|
||||
upsertCommand.Parameters.AddWithValue("@username", normalizedUsername);
|
||||
@@ -32,6 +33,7 @@ public class UserService
|
||||
upsertCommand.Parameters.AddWithValue("@added", now.ToString("O"));
|
||||
upsertCommand.Parameters.AddWithValue("@lastUpdated", now.ToString("O"));
|
||||
upsertCommand.Parameters.AddWithValue("@displayName", admin.DisplayName.Trim());
|
||||
upsertCommand.Parameters.AddWithValue("@preferredLanguage", AppLanguages.Finnish);
|
||||
|
||||
var userId = Convert.ToInt64(await upsertCommand.ExecuteScalarAsync());
|
||||
|
||||
@@ -49,7 +51,7 @@ public class UserService
|
||||
|
||||
await using var command = _connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT u.id, u.username, u.password, u.added, u.lastUpdated, u.displayName,
|
||||
SELECT u.id, u.username, u.password, u.added, u.lastUpdated, u.displayName, u.preferredLanguage,
|
||||
GROUP_CONCAT(ur.roleName) AS roles
|
||||
FROM Users u
|
||||
LEFT JOIN UserRoles ur ON ur.userId = u.id
|
||||
@@ -81,7 +83,7 @@ public class UserService
|
||||
|
||||
await using var command = _connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT u.id, u.username, u.added, u.lastUpdated, u.displayName,
|
||||
SELECT u.id, u.username, u.added, u.lastUpdated, u.displayName, u.preferredLanguage,
|
||||
GROUP_CONCAT(ur.roleName) AS roles
|
||||
FROM Users u
|
||||
LEFT JOIN UserRoles ur ON ur.userId = u.id
|
||||
@@ -99,6 +101,7 @@ public class UserService
|
||||
Added = ParseDate(reader["added"]?.ToString()),
|
||||
LastUpdated = ParseDate(reader["lastUpdated"]?.ToString()),
|
||||
DisplayName = reader["displayName"]?.ToString() ?? string.Empty,
|
||||
PreferredLanguage = AppLanguages.NormalizeOrDefault(reader["preferredLanguage"]?.ToString()),
|
||||
Roles = ParseRoles(reader["roles"]?.ToString())
|
||||
});
|
||||
}
|
||||
@@ -112,11 +115,12 @@ public class UserService
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var normalizedUsername = request.Username.Trim().ToLowerInvariant();
|
||||
var preferredLanguage = AppLanguages.NormalizeOrDefault(request.PreferredLanguage);
|
||||
|
||||
await using var insertCommand = _connection.CreateCommand();
|
||||
insertCommand.CommandText = @"
|
||||
INSERT INTO Users (username, password, added, lastUpdated, displayName)
|
||||
VALUES (@username, @password, @added, @lastUpdated, @displayName);
|
||||
INSERT INTO Users (username, password, added, lastUpdated, displayName, preferredLanguage)
|
||||
VALUES (@username, @password, @added, @lastUpdated, @displayName, @preferredLanguage);
|
||||
SELECT last_insert_rowid();";
|
||||
|
||||
insertCommand.Parameters.AddWithValue("@username", normalizedUsername);
|
||||
@@ -124,6 +128,7 @@ public class UserService
|
||||
insertCommand.Parameters.AddWithValue("@added", now.ToString("O"));
|
||||
insertCommand.Parameters.AddWithValue("@lastUpdated", now.ToString("O"));
|
||||
insertCommand.Parameters.AddWithValue("@displayName", request.DisplayName.Trim());
|
||||
insertCommand.Parameters.AddWithValue("@preferredLanguage", preferredLanguage);
|
||||
|
||||
var insertedId = Convert.ToInt64(await insertCommand.ExecuteScalarAsync());
|
||||
|
||||
@@ -143,6 +148,7 @@ public class UserService
|
||||
Added = now,
|
||||
LastUpdated = now,
|
||||
DisplayName = request.DisplayName.Trim(),
|
||||
PreferredLanguage = preferredLanguage,
|
||||
Roles = validRoles
|
||||
};
|
||||
}
|
||||
@@ -153,6 +159,7 @@ public class UserService
|
||||
|
||||
var normalizedUsername = username.Trim().ToLowerInvariant();
|
||||
var now = DateTime.UtcNow;
|
||||
var preferredLanguage = AppLanguages.NormalizeOrDefault(request.PreferredLanguage);
|
||||
|
||||
var currentUser = await GetUserByUsername(normalizedUsername);
|
||||
if (currentUser is null)
|
||||
@@ -165,13 +172,15 @@ public class UserService
|
||||
UPDATE Users
|
||||
SET password = @password,
|
||||
lastUpdated = @lastUpdated,
|
||||
displayName = @displayName
|
||||
displayName = @displayName,
|
||||
preferredLanguage = @preferredLanguage
|
||||
WHERE username = @username;";
|
||||
|
||||
command.Parameters.AddWithValue("@username", normalizedUsername);
|
||||
command.Parameters.AddWithValue("@password", string.IsNullOrWhiteSpace(request.Password) ? currentUser.Password : request.Password);
|
||||
command.Parameters.AddWithValue("@lastUpdated", now.ToString("O"));
|
||||
command.Parameters.AddWithValue("@displayName", request.DisplayName.Trim());
|
||||
command.Parameters.AddWithValue("@preferredLanguage", preferredLanguage);
|
||||
|
||||
var affectedRows = await command.ExecuteNonQueryAsync();
|
||||
if (affectedRows == 0)
|
||||
@@ -192,6 +201,7 @@ public class UserService
|
||||
Added = currentUser.Added,
|
||||
LastUpdated = now,
|
||||
DisplayName = request.DisplayName.Trim(),
|
||||
PreferredLanguage = preferredLanguage,
|
||||
Roles = validRoles
|
||||
};
|
||||
}
|
||||
@@ -225,7 +235,7 @@ public class UserService
|
||||
|
||||
await using var command = _connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT u.id, u.username, u.added, u.lastUpdated, u.displayName,
|
||||
SELECT u.id, u.username, u.added, u.lastUpdated, u.displayName, u.preferredLanguage,
|
||||
GROUP_CONCAT(ur.roleName) AS roles
|
||||
FROM Users u
|
||||
LEFT JOIN UserRoles ur ON ur.userId = u.id
|
||||
@@ -247,6 +257,7 @@ public class UserService
|
||||
Added = ParseDate(reader["added"]?.ToString()),
|
||||
LastUpdated = ParseDate(reader["lastUpdated"]?.ToString()),
|
||||
DisplayName = reader["displayName"]?.ToString() ?? string.Empty,
|
||||
PreferredLanguage = AppLanguages.NormalizeOrDefault(reader["preferredLanguage"]?.ToString()),
|
||||
Roles = ParseRoles(reader["roles"]?.ToString())
|
||||
};
|
||||
}
|
||||
@@ -272,7 +283,7 @@ public class UserService
|
||||
{
|
||||
await using var command = _connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT u.id, u.username, u.password, u.added, u.lastUpdated, u.displayName,
|
||||
SELECT u.id, u.username, u.password, u.added, u.lastUpdated, u.displayName, u.preferredLanguage,
|
||||
GROUP_CONCAT(ur.roleName) AS roles
|
||||
FROM Users u
|
||||
LEFT JOIN UserRoles ur ON ur.userId = u.id
|
||||
@@ -301,6 +312,7 @@ public class UserService
|
||||
Added = ParseDate(reader["added"]?.ToString()),
|
||||
LastUpdated = ParseDate(reader["lastUpdated"]?.ToString()),
|
||||
DisplayName = reader["displayName"]?.ToString() ?? string.Empty,
|
||||
PreferredLanguage = AppLanguages.NormalizeOrDefault(reader["preferredLanguage"]?.ToString()),
|
||||
Roles = ParseRoles(reader["roles"]?.ToString())
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ CREATE TABLE IF NOT EXISTS Users (
|
||||
password TEXT NOT NULL,
|
||||
added TEXT NOT NULL,
|
||||
lastUpdated TEXT NOT NULL,
|
||||
displayName TEXT NOT NULL DEFAULT ''
|
||||
displayName TEXT NOT NULL DEFAULT '',
|
||||
preferredLanguage TEXT NOT NULL DEFAULT 'fi'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS UserRoles (
|
||||
|
||||
Reference in New Issue
Block a user