Fix Actions that can have related chapters and manga

This commit is contained in:
2025-10-16 20:28:43 +02:00
parent d029512354
commit ef907ee09d
15 changed files with 75 additions and 49 deletions

View File

@@ -69,7 +69,7 @@ public class ActionsController(ActionsContext context) : Controller
[ProducesResponseType(Status500InternalServerError)] [ProducesResponseType(Status500InternalServerError)]
public async Task<Results<Ok<IEnumerable<ActionRecord>>, InternalServerError>> GetActionsRelatedToManga(string MangaId) public async Task<Results<Ok<IEnumerable<ActionRecord>>, InternalServerError>> GetActionsRelatedToManga(string MangaId)
{ {
if(await context.Actions.FromSqlInterpolated($"""SELECT * FROM public."Actions" WHERE "MangaId" = {MangaId}""").ToListAsync() is not { } actions) if(await context.Actions.FromSqlInterpolated($"""SELECT * FROM public."Actions" WHERE "MangaId" = {MangaId}""").ToListAsync(HttpContext.RequestAborted) is not { } actions)
return TypedResults.InternalServerError(); return TypedResults.InternalServerError();
return TypedResults.Ok(actions.Select(a => new ActionRecord(a))); return TypedResults.Ok(actions.Select(a => new ActionRecord(a)));
@@ -85,7 +85,7 @@ public class ActionsController(ActionsContext context) : Controller
[ProducesResponseType(Status500InternalServerError)] [ProducesResponseType(Status500InternalServerError)]
public async Task<Results<Ok<IEnumerable<ActionRecord>>, InternalServerError>> GetActionsRelatedToChapter(string ChapterId) public async Task<Results<Ok<IEnumerable<ActionRecord>>, InternalServerError>> GetActionsRelatedToChapter(string ChapterId)
{ {
if(await context.Actions.FromSqlInterpolated($"""SELECT * FROM public."Actions" WHERE "ChapterId" = {ChapterId}""").ToListAsync() is not { } actions) if(await context.Actions.FromSqlInterpolated($"""SELECT * FROM public."Actions" WHERE "ChapterId" = {ChapterId}""").ToListAsync(HttpContext.RequestAborted) is not { } actions)
return TypedResults.InternalServerError(); return TypedResults.InternalServerError();
return TypedResults.Ok(actions.Select(a => new ActionRecord(a))); return TypedResults.Ok(actions.Select(a => new ActionRecord(a)));

View File

@@ -11,8 +11,8 @@ public sealed record ActionRecord : Identifiable
{ {
Action = actionRecord.Action; Action = actionRecord.Action;
PerformedAt = actionRecord.PerformedAt; PerformedAt = actionRecord.PerformedAt;
MangaId = actionRecord is ActionWithMangaRecord m ? m.MangaId : null; MangaId = actionRecord is IActionWithMangaRecord m ? m.MangaId : null;
ChapterId = actionRecord is ActionWithChapterRecord c ? c.ChapterId : null; ChapterId = actionRecord is IActionWithChapterRecord c ? c.ChapterId : null;
} }
/// <summary> /// <summary>
@@ -28,13 +28,13 @@ public sealed record ActionRecord : Identifiable
public DateTime PerformedAt { get; init; } public DateTime PerformedAt { get; init; }
/// <summary> /// <summary>
/// MangaId if Record is <see cref="ActionWithMangaRecord"/> /// MangaId if Record is <see cref="IActionWithMangaRecord"/>
/// </summary> /// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? MangaId { get; init; } public string? MangaId { get; init; }
/// <summary> /// <summary>
/// ChapterId if Record is <see cref="ActionWithMangaRecord"/> /// ChapterId if Record is <see cref="IActionWithMangaRecord"/>
/// </summary> /// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? ChapterId { get; init; } public string? ChapterId { get; init; }

View File

@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace API.Migrations.Actions namespace API.Migrations.Actions
{ {
[DbContext(typeof(ActionsContext))] [DbContext(typeof(ActionsContext))]
[Migration("20251016170924_Actions")] [Migration("20251016182526_Actions")]
partial class Actions partial class Actions
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -53,8 +53,14 @@ namespace API.Migrations.Actions
b.Property<string>("ChapterId") b.Property<string>("ChapterId")
.IsRequired() .IsRequired()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("ChapterId");
b.Property<string>("MangaId")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("text")
.HasColumnName("MangaId");
b.HasDiscriminator().HasValue(1); b.HasDiscriminator().HasValue(1);
}); });
@@ -66,8 +72,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(2); b.HasDiscriminator().HasValue(2);
}); });
@@ -84,8 +90,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(3); b.HasDiscriminator().HasValue(3);
}); });
@@ -119,8 +125,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(5); b.HasDiscriminator().HasValue(5);
}); });
@@ -132,8 +138,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.Property<string>("MetadataFetcher") b.Property<string>("MetadataFetcher")
.IsRequired() .IsRequired()

View File

@@ -18,8 +18,8 @@ namespace API.Migrations.Actions
Key = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false), Key = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Action = table.Column<int>(type: "integer", maxLength: 128, nullable: false), Action = table.Column<int>(type: "integer", maxLength: 128, nullable: false),
PerformedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), PerformedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ChapterId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true), ChapterId = table.Column<string>(type: "text", nullable: true),
MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true), MangaId = table.Column<string>(type: "text", nullable: true),
Filename = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true), Filename = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
From = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true), From = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
To = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true), To = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),

View File

@@ -50,8 +50,14 @@ namespace API.Migrations.Actions
b.Property<string>("ChapterId") b.Property<string>("ChapterId")
.IsRequired() .IsRequired()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("ChapterId");
b.Property<string>("MangaId")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("text")
.HasColumnName("MangaId");
b.HasDiscriminator().HasValue(1); b.HasDiscriminator().HasValue(1);
}); });
@@ -63,8 +69,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(2); b.HasDiscriminator().HasValue(2);
}); });
@@ -81,8 +87,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(3); b.HasDiscriminator().HasValue(3);
}); });
@@ -116,8 +122,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.HasDiscriminator().HasValue(5); b.HasDiscriminator().HasValue(5);
}); });
@@ -129,8 +135,8 @@ namespace API.Migrations.Actions
b.Property<string>("MangaId") b.Property<string>("MangaId")
.IsRequired() .IsRequired()
.ValueGeneratedOnUpdateSometimes() .ValueGeneratedOnUpdateSometimes()
.HasMaxLength(64) .HasColumnType("text")
.HasColumnType("character varying(64)"); .HasColumnName("MangaId");
b.Property<string>("MetadataFetcher") b.Property<string>("MetadataFetcher")
.IsRequired() .IsRequired()

View File

@@ -3,7 +3,9 @@ using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions; namespace API.Schema.ActionsContext.Actions;
public sealed class ChapterDownloadedActionRecord(ActionsEnum action, DateTime performedAt, string chapterId) : ActionWithChapterRecord(action, performedAt, chapterId) public sealed class ChapterDownloadedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string chapterId) : ActionRecord(action, performedAt), IActionWithChapterRecord, IActionWithMangaRecord
{ {
public ChapterDownloadedActionRecord(Chapter chapter) : this(ActionsEnum.ChapterDownloaded, DateTime.UtcNow, chapter.Key) { } public ChapterDownloadedActionRecord(Manga manga, Chapter chapter) : this(ActionsEnum.ChapterDownloaded, DateTime.UtcNow, manga.Key, chapter.Key) { }
public string ChapterId { get; init; } = chapterId;
public string MangaId { get; init; } = mangaId;
} }

View File

@@ -4,9 +4,9 @@ using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions; namespace API.Schema.ActionsContext.Actions;
public sealed class ChaptersRetrievedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId) public sealed class ChaptersRetrievedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId)
: ActionWithMangaRecord(action, performedAt, mangaId) : ActionRecord(action, performedAt), IActionWithMangaRecord
{ {
public ChaptersRetrievedActionRecord(Manga manga) : this(ActionsEnum.ChaptersRetrieved, DateTime.UtcNow, manga.Key) { } public ChaptersRetrievedActionRecord(Manga manga) : this(ActionsEnum.ChaptersRetrieved, DateTime.UtcNow, manga.Key) { }
public const string ChaptersRetrievedAction = "Manga.ChaptersRetrieved"; public string MangaId { get; init; } = mangaId;
} }

View File

@@ -5,7 +5,7 @@ using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions; namespace API.Schema.ActionsContext.Actions;
public sealed class CoverDownloadedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string filename) public sealed class CoverDownloadedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string filename)
: ActionWithMangaRecord(action, performedAt, mangaId) : ActionRecord(action, performedAt), IActionWithMangaRecord
{ {
public CoverDownloadedActionRecord(Manga manga, string filename) : this(ActionsEnum.CoverDownloaded, DateTime.UtcNow, manga.Key, filename) { } public CoverDownloadedActionRecord(Manga manga, string filename) : this(ActionsEnum.CoverDownloaded, DateTime.UtcNow, manga.Key, filename) { }
@@ -14,4 +14,6 @@ public sealed class CoverDownloadedActionRecord(ActionsEnum action, DateTime per
/// </summary> /// </summary>
[StringLength(1024)] [StringLength(1024)]
public string Filename { get; init; } = filename; public string Filename { get; init; } = filename;
public string MangaId { get; init; } = mangaId;
} }

View File

@@ -1,15 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions.Generic; namespace API.Schema.ActionsContext.Actions.Generic;
public abstract class ActionWithChapterRecord(ActionsEnum action, DateTime performedAt, string chapterId) : ActionRecord(action, performedAt) public interface IActionWithChapterRecord
{ {
protected ActionWithChapterRecord(ActionsEnum action, DateTime performedAt, Chapter chapter) : this(action, performedAt, chapter.Key) { }
/// <summary> /// <summary>
/// <see cref="Schema.MangaContext.Manga"/> for which the cover was downloaded /// <see cref="Schema.MangaContext.Manga"/> for which the cover was downloaded
/// </summary> /// </summary>
[StringLength(64)] [StringLength(64)]
public string ChapterId { get; init; } = chapterId; public string ChapterId { get; init; }
} }

View File

@@ -1,15 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions.Generic; namespace API.Schema.ActionsContext.Actions.Generic;
public abstract class ActionWithMangaRecord(ActionsEnum action, DateTime performedAt, string mangaId) : ActionRecord(action, performedAt) public interface IActionWithMangaRecord
{ {
protected ActionWithMangaRecord(ActionsEnum action, DateTime performedAt, Manga manga) : this(action, performedAt, manga.Key) { }
/// <summary> /// <summary>
/// <see cref="Schema.MangaContext.Manga"/> for which the cover was downloaded /// <see cref="Schema.MangaContext.Manga"/> for which the cover was downloaded
/// </summary> /// </summary>
[StringLength(64)] [StringLength(64)]
public string MangaId { get; init; } = mangaId; public string MangaId { get; init; }
} }

View File

@@ -4,7 +4,8 @@ using API.Schema.MangaContext;
namespace API.Schema.ActionsContext.Actions; namespace API.Schema.ActionsContext.Actions;
public sealed class LibraryMovedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string fileLibraryId) : ActionWithMangaRecord(action, performedAt, mangaId) public sealed class LibraryMovedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string fileLibraryId)
: ActionRecord(action, performedAt), IActionWithMangaRecord
{ {
public LibraryMovedActionRecord(Manga manga, FileLibrary library) : this(ActionsEnum.LibraryMoved, DateTime.UtcNow, manga.Key, library.Key) { } public LibraryMovedActionRecord(Manga manga, FileLibrary library) : this(ActionsEnum.LibraryMoved, DateTime.UtcNow, manga.Key, library.Key) { }
@@ -13,4 +14,6 @@ public sealed class LibraryMovedActionRecord(ActionsEnum action, DateTime perfor
/// </summary> /// </summary>
[StringLength(64)] [StringLength(64)]
public string FileLibraryId { get; init; } = fileLibraryId; public string FileLibraryId { get; init; } = fileLibraryId;
public string MangaId { get; init; } = mangaId;
} }

View File

@@ -6,7 +6,7 @@ using API.Schema.MangaContext.MetadataFetchers;
namespace API.Schema.ActionsContext.Actions; namespace API.Schema.ActionsContext.Actions;
public sealed class MetadataUpdatedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string metadataFetcher) public sealed class MetadataUpdatedActionRecord(ActionsEnum action, DateTime performedAt, string mangaId, string metadataFetcher)
: ActionWithMangaRecord(action, performedAt, mangaId) : ActionRecord(action, performedAt), IActionWithMangaRecord
{ {
public MetadataUpdatedActionRecord(Manga manga, MetadataFetcher fetcher) : this(ActionsEnum.MetadataUpdated, DateTime.UtcNow, manga.Key, fetcher.Name) { } public MetadataUpdatedActionRecord(Manga manga, MetadataFetcher fetcher) : this(ActionsEnum.MetadataUpdated, DateTime.UtcNow, manga.Key, fetcher.Name) { }
@@ -15,4 +15,6 @@ public sealed class MetadataUpdatedActionRecord(ActionsEnum action, DateTime per
/// </summary> /// </summary>
[StringLength(1024)] [StringLength(1024)]
public string MetadataFetcher { get; init; } = metadataFetcher; public string MetadataFetcher { get; init; } = metadataFetcher;
public string MangaId { get; init; } = mangaId;
} }

View File

@@ -19,5 +19,16 @@ public class ActionsContext(DbContextOptions<ActionsContext> options) : TrangaBa
.HasValue<DataMovedActionRecord>(ActionsEnum.DataMoved) .HasValue<DataMovedActionRecord>(ActionsEnum.DataMoved)
.HasValue<LibraryMovedActionRecord>(ActionsEnum.LibraryMoved) .HasValue<LibraryMovedActionRecord>(ActionsEnum.LibraryMoved)
.HasValue<StartupActionRecord>(ActionsEnum.Startup); .HasValue<StartupActionRecord>(ActionsEnum.Startup);
modelBuilder.Entity<ChapterDownloadedActionRecord>().Property(a => a.MangaId).HasColumnName("MangaId");
modelBuilder.Entity<ChapterDownloadedActionRecord>().Property(a => a.ChapterId).HasColumnName("ChapterId");
modelBuilder.Entity<CoverDownloadedActionRecord>().Property(a => a.MangaId).HasColumnName("MangaId");
modelBuilder.Entity<ChaptersRetrievedActionRecord>().Property(a => a.MangaId).HasColumnName("MangaId");
modelBuilder.Entity<MetadataUpdatedActionRecord>().Property(a => a.MangaId).HasColumnName("MangaId");
modelBuilder.Entity<LibraryMovedActionRecord>().Property(a => a.MangaId).HasColumnName("MangaId");
} }
} }

View File

@@ -192,7 +192,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
Log.Debug($"Downloaded chapter {chapter}."); Log.Debug($"Downloaded chapter {chapter}.");
ActionsContext.Actions.Add(new ChapterDownloadedActionRecord(chapter)); ActionsContext.Actions.Add(new ChapterDownloadedActionRecord(chapter.ParentManga, chapter));
if(await ActionsContext.Sync(CancellationToken, GetType(), "Download complete") is { success: false } actionsContextException) if(await ActionsContext.Sync(CancellationToken, GetType(), "Download complete") is { success: false } actionsContextException)
Log.Error($"Failed to save database changes: {actionsContextException.exceptionMessage}"); Log.Error($"Failed to save database changes: {actionsContextException.exceptionMessage}");

View File

@@ -3277,12 +3277,12 @@
}, },
"mangaId": { "mangaId": {
"type": "string", "type": "string",
"description": "MangaId if Record is API.Schema.ActionsContext.Actions.Generic.ActionWithMangaRecord", "description": "MangaId if Record is API.Schema.ActionsContext.Actions.Generic.IActionWithMangaRecord",
"nullable": true "nullable": true
}, },
"chapterId": { "chapterId": {
"type": "string", "type": "string",
"description": "ChapterId if Record is API.Schema.ActionsContext.Actions.Generic.ActionWithMangaRecord", "description": "ChapterId if Record is API.Schema.ActionsContext.Actions.Generic.IActionWithMangaRecord",
"nullable": true "nullable": true
}, },
"key": { "key": {