Implement portrait generation and retrieval for people in the school system. Added new API endpoints for generating and fetching portraits, including support for avatar and full-body images. Updated the client-side to handle portrait states and display options. Enhanced the person card UI to include tabs for overview and portrait, with appropriate localization strings. Updated solution files to include new test projects. Adjusted server configuration for SwarmUI integration.
ci / server (push) Failing after 3m46s
ci / client (push) Failing after 14s

This commit is contained in:
Leonid Pershin
2026-08-20 04:14:41 +03:00
parent 871178909e
commit dfa9a25dca
32 changed files with 1538 additions and 121 deletions
+51
View File
@@ -260,6 +260,55 @@ internal sealed class SchoolStore
{
File.Delete(timetable);
}
var portraits = PortraitsDirectory(id);
if (Directory.Exists(portraits))
{
Directory.Delete(portraits, recursive: true);
}
}
public bool HasPortrait(int schoolId, string personId, PortraitKind kind) =>
File.Exists(PortraitPath(schoolId, personId, kind));
public (bool HasAvatar, bool HasFullBody) PortraitFlags(int schoolId, string personId)
{
var directory = PortraitsDirectory(schoolId);
if (!Directory.Exists(directory))
{
return (false, false);
}
return (HasPortrait(schoolId, personId, PortraitKind.Avatar), HasPortrait(schoolId, personId, PortraitKind.Full));
}
public string PortraitPath(int schoolId, string personId, PortraitKind kind)
{
var safeId = SanitizePersonId(personId);
var suffix = kind == PortraitKind.Avatar ? "avatar" : "full";
return Path.Combine(PortraitsDirectory(schoolId), $"{safeId}.{suffix}.png");
}
public void SavePortrait(int schoolId, string personId, PortraitKind kind, ReadOnlySpan<byte> png)
{
var path = PortraitPath(schoolId, personId, kind);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var temp = path + ".tmp";
File.WriteAllBytes(temp, png);
File.Move(temp, path, overwrite: true);
}
private static string SanitizePersonId(string personId)
{
foreach (var ch in personId)
{
if (ch is not (>= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9' or '.' or '-' or '_'))
{
throw new ArgumentException("The person id contains invalid characters.", nameof(personId));
}
}
return personId;
}
public RosterDocument? TryReadPeople(int id)
@@ -319,6 +368,8 @@ internal sealed class SchoolStore
private string TimetablePath(int id) => Path.Combine(DirectoryPath, $"{id}.timetable.json");
private string PortraitsDirectory(int id) => Path.Combine(DirectoryPath, $"{id}.portraits");
private string IndexPath() => Path.Combine(DirectoryPath, IndexFileName);
private static void WriteAtomic<T>(string path, T value, JsonSerializerOptions? options = null)