Merged develop in
This commit is contained in:
commit
7c692a1b46
580 changed files with 21233 additions and 9031 deletions
|
|
@ -11,6 +11,8 @@ using API.Entities.Enums.UserPreferences;
|
|||
using API.Entities.History;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Entities.Metadata;
|
||||
using API.Entities.MetadataMatching;
|
||||
using API.Entities.Person;
|
||||
using API.Entities.Scrobble;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
|
|
@ -131,6 +133,9 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
|||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.WantToReadSync)
|
||||
.HasDefaultValue(true);
|
||||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.AllowAutomaticWebtoonReaderDetection)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
builder.Entity<Library>()
|
||||
.Property(b => b.AllowScrobbling)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -35,7 +36,7 @@ public static class MigrateInitialInstallData
|
|||
{
|
||||
var fi = directoryService.FileSystem.FileInfo.New(dbFile);
|
||||
var setting = settings.First(s => s.Key == ServerSettingKey.FirstInstallDate);
|
||||
setting.Value = fi.CreationTimeUtc.ToString();
|
||||
setting.Value = fi.CreationTimeUtc.ToString(CultureInfo.InvariantCulture);
|
||||
await dataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities.History;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// v0.8.6 - Manually check when a user triggers scrobble event generation
|
||||
/// </summary>
|
||||
public static class ManualMigrateScrobbleEventGen
|
||||
{
|
||||
public static async Task Migrate(DataContext context, ILogger<Program> logger)
|
||||
{
|
||||
if (await context.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateScrobbleEventGen"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical("Running ManualMigrateScrobbleEventGen migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
var users = await context.Users
|
||||
.Where(u => u.AniListAccessToken != null)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (await context.ScrobbleEvent.AnyAsync(se => se.AppUserId == user.Id))
|
||||
{
|
||||
user.HasRunScrobbleEventGeneration = true;
|
||||
user.ScrobbleEventGenerationRan = DateTime.UtcNow;
|
||||
context.AppUser.Update(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ChangeTracker.HasChanges())
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await context.ManualMigrationHistory.AddAsync(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "ManualMigrateScrobbleEventGen",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
logger.LogCritical("Running ManualMigrateScrobbleEventGen migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities.History;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// v0.8.6 - Change to not scrobble specials as they will never process, this migration removes all existing scrobble events
|
||||
/// </summary>
|
||||
public static class ManualMigrateScrobbleSpecials
|
||||
{
|
||||
public static async Task Migrate(DataContext context, ILogger<Program> logger)
|
||||
{
|
||||
if (await context.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateScrobbleSpecials"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogCritical("Running ManualMigrateScrobbleSpecials migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
// Get all series in the Blacklist table and set their IsBlacklist = true
|
||||
var events = await context.ScrobbleEvent
|
||||
.Where(se => se.VolumeNumber == Parser.SpecialVolumeNumber)
|
||||
.ToListAsync();
|
||||
|
||||
context.ScrobbleEvent.RemoveRange(events);
|
||||
|
||||
if (context.ChangeTracker.HasChanges())
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
logger.LogInformation("Removed {Count} scrobble events that were specials", events.Count);
|
||||
}
|
||||
|
||||
await context.ManualMigrationHistory.AddAsync(new ManualMigrationHistory()
|
||||
{
|
||||
Name = "ManualMigrateScrobbleSpecials",
|
||||
ProductVersion = BuildInfo.Version.ToString(),
|
||||
RanAt = DateTime.UtcNow
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
logger.LogCritical("Running ManualMigrateScrobbleSpecials migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
||||
3403
API/Data/Migrations/20250328125012_AutomaticWebtoonReaderMode.Designer.cs
generated
Normal file
3403
API/Data/Migrations/20250328125012_AutomaticWebtoonReaderMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutomaticWebtoonReaderMode : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AllowAutomaticWebtoonReaderDetection",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AllowAutomaticWebtoonReaderDetection",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
||||
3409
API/Data/Migrations/20250408222330_ScrobbleGenerationDbCapture.Designer.cs
generated
Normal file
3409
API/Data/Migrations/20250408222330_ScrobbleGenerationDbCapture.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ScrobbleGenerationDbCapture : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "HasRunScrobbleEventGeneration",
|
||||
table: "AspNetUsers",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "ScrobbleEventGenerationRan",
|
||||
table: "AspNetUsers",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HasRunScrobbleEventGeneration",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ScrobbleEventGenerationRan",
|
||||
table: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
|
@ -85,6 +85,9 @@ namespace API.Data.Migrations
|
|||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HasRunScrobbleEventGeneration")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastActive")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
@ -124,6 +127,9 @@ namespace API.Data.Migrations
|
|||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("ScrobbleEventGenerationRan")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
@ -353,6 +359,11 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AllowAutomaticWebtoonReaderDetection")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("AniListScrobblingEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -911,24 +922,6 @@ namespace API.Data.Migrations
|
|||
b.ToTable("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ChapterPeople", b =>
|
||||
{
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ChapterId", "PersonId", "Role");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("ChapterPeople");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.CollectionTag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1640,7 +1633,7 @@ namespace API.Data.Migrations
|
|||
b.ToTable("MetadataFieldMapping");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.MetadataSettings", b =>
|
||||
modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
|
@ -1703,7 +1696,25 @@ namespace API.Data.Migrations
|
|||
b.ToTable("MetadataSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person", b =>
|
||||
modelBuilder.Entity("API.Entities.Person.ChapterPeople", b =>
|
||||
{
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ChapterId", "PersonId", "Role");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("ChapterPeople");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.Person", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
|
@ -1747,6 +1758,32 @@ namespace API.Data.Migrations
|
|||
b.ToTable("Person");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||
{
|
||||
b.Property<int>("SeriesMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KavitaPlusConnection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("OrderWeight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.HasKey("SeriesMetadataId", "PersonId", "Role");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("SeriesMetadataPeople");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingList", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -2111,32 +2148,6 @@ namespace API.Data.Migrations
|
|||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.SeriesMetadataPeople", b =>
|
||||
{
|
||||
b.Property<int>("SeriesMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KavitaPlusConnection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("OrderWeight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.HasKey("SeriesMetadataId", "PersonId", "Role");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("SeriesMetadataPeople");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ServerSetting", b =>
|
||||
{
|
||||
b.Property<int>("Key")
|
||||
|
|
@ -2804,25 +2815,6 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Volume");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ChapterPeople", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Person", "Person")
|
||||
.WithMany("ChapterPeople")
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
|
||||
b.Navigation("Person");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Device", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
|
|
@ -2943,7 +2935,7 @@ namespace API.Data.Migrations
|
|||
|
||||
modelBuilder.Entity("API.Entities.MetadataFieldMapping", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.MetadataSettings", "MetadataSettings")
|
||||
b.HasOne("API.Entities.MetadataMatching.MetadataSettings", "MetadataSettings")
|
||||
.WithMany("FieldMappings")
|
||||
.HasForeignKey("MetadataSettingsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
|
|
@ -2952,6 +2944,44 @@ namespace API.Data.Migrations
|
|||
b.Navigation("MetadataSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.ChapterPeople", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Person.Person", "Person")
|
||||
.WithMany("ChapterPeople")
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
|
||||
b.Navigation("Person");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Person.Person", "Person")
|
||||
.WithMany("SeriesMetadataPeople")
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Metadata.SeriesMetadata", "SeriesMetadata")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("SeriesMetadataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Person");
|
||||
|
||||
b.Navigation("SeriesMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingList", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
|
|
@ -3072,25 +3102,6 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Library");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.SeriesMetadataPeople", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Person", "Person")
|
||||
.WithMany("SeriesMetadataPeople")
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Metadata.SeriesMetadata", "SeriesMetadata")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("SeriesMetadataId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Person");
|
||||
|
||||
b.Navigation("SeriesMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Series", "Series")
|
||||
|
|
@ -3351,12 +3362,12 @@ namespace API.Data.Migrations
|
|||
b.Navigation("People");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.MetadataSettings", b =>
|
||||
modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b =>
|
||||
{
|
||||
b.Navigation("FieldMappings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person", b =>
|
||||
modelBuilder.Entity("API.Entities.Person.Person", b =>
|
||||
{
|
||||
b.Navigation("ChapterPeople");
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface IAppUserProgressRepository
|
||||
{
|
||||
void Update(AppUserProgress userProgress);
|
||||
|
|
@ -41,7 +42,7 @@ public interface IAppUserProgressRepository
|
|||
Task UpdateAllProgressThatAreMoreThanChapterPages();
|
||||
Task<IList<FullProgressDto>> GetUserProgressForChapter(int chapterId, int userId = 0);
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
|
@ -192,6 +193,7 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
|||
.Where(p => p.chapter.MaxNumber != Parser.SpecialVolumeNumber)
|
||||
.Select(p => p.chapter.Volume.MaxNumber)
|
||||
.ToListAsync();
|
||||
|
||||
return list.Count == 0 ? 0 : list.DefaultIfEmpty().Max();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using API.DTOs.CoverDb;
|
||||
using API.Entities;
|
||||
using API.Entities.Person;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface IGenreRepository
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,15 +9,18 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface IMediaErrorRepository
|
||||
{
|
||||
void Attach(MediaError error);
|
||||
void Remove(MediaError error);
|
||||
void Remove(IList<MediaError> errors);
|
||||
Task<MediaError> Find(string filename);
|
||||
IEnumerable<MediaErrorDto> GetAllErrorDtosAsync();
|
||||
Task<bool> ExistsAsync(MediaError error);
|
||||
Task DeleteAll();
|
||||
Task<List<MediaError>> GetAllErrorsAsync(IList<string> comments);
|
||||
}
|
||||
|
||||
public class MediaErrorRepository : IMediaErrorRepository
|
||||
|
|
@ -43,6 +46,11 @@ public class MediaErrorRepository : IMediaErrorRepository
|
|||
_context.MediaError.Remove(error);
|
||||
}
|
||||
|
||||
public void Remove(IList<MediaError> errors)
|
||||
{
|
||||
_context.MediaError.RemoveRange(errors);
|
||||
}
|
||||
|
||||
public Task<MediaError?> Find(string filename)
|
||||
{
|
||||
return _context.MediaError.Where(e => e.FilePath == filename).SingleOrDefaultAsync();
|
||||
|
|
@ -70,4 +78,11 @@ public class MediaErrorRepository : IMediaErrorRepository
|
|||
_context.MediaError.RemoveRange(await _context.MediaError.ToListAsync());
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task<List<MediaError>> GetAllErrorsAsync(IList<string> comments)
|
||||
{
|
||||
return _context.MediaError
|
||||
.Where(m => comments.Contains(m.Comment))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
using API.Extensions.QueryExtensions;
|
||||
using API.Helpers;
|
||||
|
|
@ -14,6 +12,7 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface IPersonRepository
|
||||
{
|
||||
|
|
@ -29,15 +28,13 @@ public interface IPersonRepository
|
|||
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
|
||||
Task RemoveAllPeopleNoLongerAssociated();
|
||||
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null);
|
||||
Task<int> GetCountAsync();
|
||||
|
||||
Task<string> GetCoverImageAsync(int personId);
|
||||
Task<string?> GetCoverImageAsync(int personId);
|
||||
Task<string?> GetCoverImageByNameAsync(string name);
|
||||
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
|
||||
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
|
||||
Task<Person?> GetPersonById(int personId);
|
||||
Task<PersonDto?> GetPersonDtoByName(string name, int userId);
|
||||
Task<Person> GetPersonByName(string name);
|
||||
Task<bool> IsNameUnique(string name);
|
||||
|
||||
Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId);
|
||||
|
|
@ -124,12 +121,8 @@ public class PersonRepository : IPersonRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetCountAsync()
|
||||
{
|
||||
return await _context.Person.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<string> GetCoverImageAsync(int personId)
|
||||
public async Task<string?> GetCoverImageAsync(int personId)
|
||||
{
|
||||
return await _context.Person
|
||||
.Where(c => c.Id == personId)
|
||||
|
|
@ -137,7 +130,7 @@ public class PersonRepository : IPersonRepository
|
|||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<string> GetCoverImageByNameAsync(string name)
|
||||
public async Task<string?> GetCoverImageByNameAsync(string name)
|
||||
{
|
||||
var normalized = name.ToNormalized();
|
||||
return await _context.Person
|
||||
|
|
@ -206,7 +199,7 @@ public class PersonRepository : IPersonRepository
|
|||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<PersonDto> GetPersonDtoByName(string name, int userId)
|
||||
public async Task<PersonDto?> GetPersonDtoByName(string name, int userId)
|
||||
{
|
||||
var normalized = name.ToNormalized();
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
|
|
@ -218,11 +211,6 @@ public class PersonRepository : IPersonRepository
|
|||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<Person> GetPersonByName(string name)
|
||||
{
|
||||
return await _context.Person.FirstOrDefaultAsync(p => p.NormalizedName == name.ToNormalized());
|
||||
}
|
||||
|
||||
public async Task<bool> IsNameUnique(string name)
|
||||
{
|
||||
return !(await _context.Person.AnyAsync(p => p.Name == name));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
[Flags]
|
||||
public enum ReadingListIncludes
|
||||
|
|
@ -48,11 +49,15 @@ public interface IReadingListRepository
|
|||
Task<IList<string>> GetRandomCoverImagesAsync(int readingListId);
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
Task<bool> ReadingListExists(string name);
|
||||
IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId);
|
||||
Task<bool> ReadingListExistsForUser(string name, int userId);
|
||||
IEnumerable<PersonDto> GetReadingListPeopleAsync(int readingListId, PersonRole role);
|
||||
Task<ReadingListCast> GetReadingListAllPeopleAsync(int readingListId);
|
||||
Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
|
||||
Task<int> RemoveReadingListsWithoutSeries();
|
||||
Task<ReadingList?> GetReadingListByTitleAsync(string name, int userId, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
Task<IEnumerable<ReadingList>> GetReadingListsByIds(IList<int> ids, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
Task<IEnumerable<ReadingList>> GetReadingListsBySeriesId(int seriesId, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
Task<ReadingListInfoDto?> GetReadingListInfoAsync(int readingListId);
|
||||
}
|
||||
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
|
|
@ -105,6 +110,7 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.SelectMany(r => r.Items.Select(ri => ri.Chapter.CoverImage))
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.ToListAsync();
|
||||
|
||||
return data
|
||||
.OrderBy(_ => random.Next())
|
||||
.Take(4)
|
||||
|
|
@ -119,12 +125,19 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.AnyAsync(x => x.NormalizedTitle != null && x.NormalizedTitle.Equals(normalized));
|
||||
}
|
||||
|
||||
public IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId)
|
||||
public async Task<bool> ReadingListExistsForUser(string name, int userId)
|
||||
{
|
||||
var normalized = name.ToNormalized();
|
||||
return await _context.ReadingList
|
||||
.AnyAsync(x => x.NormalizedTitle != null && x.NormalizedTitle.Equals(normalized) && x.AppUserId == userId);
|
||||
}
|
||||
|
||||
public IEnumerable<PersonDto> GetReadingListPeopleAsync(int readingListId, PersonRole role)
|
||||
{
|
||||
return _context.ReadingListItem
|
||||
.Where(item => item.ReadingListId == readingListId)
|
||||
.SelectMany(item => item.Chapter.People)
|
||||
.Where(p => p.Role == PersonRole.Character)
|
||||
.Where(p => p.Role == role)
|
||||
.OrderBy(p => p.Person.NormalizedName)
|
||||
.Select(p => p.Person)
|
||||
.Distinct()
|
||||
|
|
@ -132,6 +145,77 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.AsEnumerable();
|
||||
}
|
||||
|
||||
public async Task<ReadingListCast> GetReadingListAllPeopleAsync(int readingListId)
|
||||
{
|
||||
var allPeople = await _context.ReadingListItem
|
||||
.Where(item => item.ReadingListId == readingListId)
|
||||
.SelectMany(item => item.Chapter.People)
|
||||
.OrderBy(p => p.Person.NormalizedName)
|
||||
.Select(p => new
|
||||
{
|
||||
Role = p.Role,
|
||||
Person = _mapper.Map<PersonDto>(p.Person)
|
||||
})
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
// Create the ReadingListCast object
|
||||
var cast = new ReadingListCast();
|
||||
|
||||
// Group people by role and populate the appropriate collections
|
||||
foreach (var personGroup in allPeople.GroupBy(p => p.Role))
|
||||
{
|
||||
var people = personGroup.Select(pg => pg.Person).ToList();
|
||||
|
||||
switch (personGroup.Key)
|
||||
{
|
||||
case PersonRole.Writer:
|
||||
cast.Writers = people;
|
||||
break;
|
||||
case PersonRole.CoverArtist:
|
||||
cast.CoverArtists = people;
|
||||
break;
|
||||
case PersonRole.Publisher:
|
||||
cast.Publishers = people;
|
||||
break;
|
||||
case PersonRole.Character:
|
||||
cast.Characters = people;
|
||||
break;
|
||||
case PersonRole.Penciller:
|
||||
cast.Pencillers = people;
|
||||
break;
|
||||
case PersonRole.Inker:
|
||||
cast.Inkers = people;
|
||||
break;
|
||||
case PersonRole.Imprint:
|
||||
cast.Imprints = people;
|
||||
break;
|
||||
case PersonRole.Colorist:
|
||||
cast.Colorists = people;
|
||||
break;
|
||||
case PersonRole.Letterer:
|
||||
cast.Letterers = people;
|
||||
break;
|
||||
case PersonRole.Editor:
|
||||
cast.Editors = people;
|
||||
break;
|
||||
case PersonRole.Translator:
|
||||
cast.Translators = people;
|
||||
break;
|
||||
case PersonRole.Team:
|
||||
cast.Teams = people;
|
||||
break;
|
||||
case PersonRole.Location:
|
||||
cast.Locations = people;
|
||||
break;
|
||||
case PersonRole.Other:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cast;
|
||||
}
|
||||
|
||||
public async Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
|
||||
{
|
||||
var extension = encodeFormat.GetExtension();
|
||||
|
|
@ -170,7 +254,41 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<IEnumerable<ReadingList>> GetReadingListsBySeriesId(int seriesId, ReadingListIncludes includes = ReadingListIncludes.Items)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(rl => rl.Items.Any(rli => rli.SeriesId == seriesId))
|
||||
.Includes(includes)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Partial ReadingListInfoDto. The HourEstimate needs to be calculated outside the repo
|
||||
/// </summary>
|
||||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ReadingListInfoDto?> GetReadingListInfoAsync(int readingListId)
|
||||
{
|
||||
// Get sum of these across all ReadingListItems: long wordCount, int pageCount, bool isEpub (assume false if any ReadingListeItem.Series.Format is non-epub)
|
||||
var readingList = await _context.ReadingList
|
||||
.Where(rl => rl.Id == readingListId)
|
||||
.Include(rl => rl.Items)
|
||||
.ThenInclude(item => item.Series)
|
||||
.Include(rl => rl.Items)
|
||||
.ThenInclude(item => item.Volume)
|
||||
.Include(rl => rl.Items)
|
||||
.ThenInclude(item => item.Chapter)
|
||||
.Select(rl => new ReadingListInfoDto()
|
||||
{
|
||||
WordCount = rl.Items.Sum(item => item.Chapter.WordCount),
|
||||
Pages = rl.Items.Sum(item => item.Chapter.Pages),
|
||||
IsAllEpub = rl.Items.All(item => item.Series.Format == MangaFormat.Epub),
|
||||
})
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return readingList;
|
||||
}
|
||||
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
|
|
@ -343,8 +461,10 @@ public class ReadingListRepository : IReadingListRepository
|
|||
|
||||
public async Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
var user = await _context.AppUser.FirstAsync(u => u.Id == userId);
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
||||
.RestrictAgainstAgeRestriction(user.GetAgeRestriction())
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface IScrobbleRepository
|
||||
{
|
||||
|
|
@ -166,11 +167,11 @@ public class ScrobbleRepository : IScrobbleRepository
|
|||
var query = _context.ScrobbleEvent
|
||||
.Where(e => e.AppUserId == userId)
|
||||
.Include(e => e.Series)
|
||||
.SortBy(filter.Field, filter.IsDescending)
|
||||
.WhereIf(!string.IsNullOrEmpty(filter.Query), s =>
|
||||
EF.Functions.Like(s.Series.Name, $"%{filter.Query}%")
|
||||
)
|
||||
.WhereIf(!filter.IncludeReviews, e => e.ScrobbleEventType != ScrobbleEventType.Review)
|
||||
.SortBy(filter.Field, filter.IsDescending)
|
||||
.AsSplitQuery()
|
||||
.ProjectTo<ScrobbleEventDto>(_mapper.ConfigurationProvider);
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
[Flags]
|
||||
public enum SeriesIncludes
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ using API.DTOs.Settings;
|
|||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Metadata;
|
||||
using API.Entities.MetadataMatching;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface ISiteThemeRepository
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using AutoMapper.QueryableExtensions;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
public interface ITagRepository
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
[Flags]
|
||||
public enum AppUserIncludes
|
||||
|
|
@ -56,7 +57,9 @@ public interface IUserRepository
|
|||
void Delete(AppUser? user);
|
||||
void Delete(AppUserBookmark bookmark);
|
||||
void Delete(IEnumerable<AppUserDashboardStream> streams);
|
||||
void Delete(AppUserDashboardStream stream);
|
||||
void Delete(IEnumerable<AppUserSideNavStream> streams);
|
||||
void Delete(AppUserSideNavStream stream);
|
||||
Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync(bool emailConfirmed = true);
|
||||
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
||||
Task<bool> IsUserAdminAsync(AppUser? user);
|
||||
|
|
@ -94,6 +97,7 @@ public interface IUserRepository
|
|||
Task<IList<AppUserDashboardStream>> GetDashboardStreamWithFilter(int filterId);
|
||||
Task<IList<SideNavStreamDto>> GetSideNavStreams(int userId, bool visibleOnly = false);
|
||||
Task<AppUserSideNavStream?> GetSideNavStream(int streamId);
|
||||
Task<AppUserSideNavStream?> GetSideNavStreamWithUser(int streamId);
|
||||
Task<IList<AppUserSideNavStream>> GetSideNavStreamWithFilter(int filterId);
|
||||
Task<IList<AppUserSideNavStream>> GetSideNavStreamsByLibraryId(int libraryId);
|
||||
Task<IList<AppUserSideNavStream>> GetSideNavStreamWithExternalSource(int externalSourceId);
|
||||
|
|
@ -166,11 +170,21 @@ public class UserRepository : IUserRepository
|
|||
_context.AppUserDashboardStream.RemoveRange(streams);
|
||||
}
|
||||
|
||||
public void Delete(AppUserDashboardStream stream)
|
||||
{
|
||||
_context.AppUserDashboardStream.Remove(stream);
|
||||
}
|
||||
|
||||
public void Delete(IEnumerable<AppUserSideNavStream> streams)
|
||||
{
|
||||
_context.AppUserSideNavStream.RemoveRange(streams);
|
||||
}
|
||||
|
||||
public void Delete(AppUserSideNavStream stream)
|
||||
{
|
||||
_context.AppUserSideNavStream.Remove(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one stop shop to get a tracked AppUser instance with any number of JOINs generated by passing bitwise flags.
|
||||
/// </summary>
|
||||
|
|
@ -395,6 +409,7 @@ public class UserRepository : IUserRepository
|
|||
.FirstOrDefaultAsync(d => d.Id == streamId);
|
||||
}
|
||||
|
||||
|
||||
public async Task<IList<AppUserDashboardStream>> GetDashboardStreamWithFilter(int filterId)
|
||||
{
|
||||
return await _context.AppUserDashboardStream
|
||||
|
|
@ -431,10 +446,10 @@ public class UserRepository : IUserRepository
|
|||
.Select(d => d.LibraryId)
|
||||
.ToList();
|
||||
|
||||
var libraryDtos = _context.Library
|
||||
var libraryDtos = await _context.Library
|
||||
.Where(l => libraryIds.Contains(l.Id))
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.ToList();
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var dto in sideNavStreams.Where(dto => dto.StreamType == SideNavStreamType.Library))
|
||||
{
|
||||
|
|
@ -458,13 +473,21 @@ public class UserRepository : IUserRepository
|
|||
return sideNavStreams;
|
||||
}
|
||||
|
||||
public async Task<AppUserSideNavStream> GetSideNavStream(int streamId)
|
||||
public async Task<AppUserSideNavStream?> GetSideNavStream(int streamId)
|
||||
{
|
||||
return await _context.AppUserSideNavStream
|
||||
.Include(d => d.SmartFilter)
|
||||
.FirstOrDefaultAsync(d => d.Id == streamId);
|
||||
}
|
||||
|
||||
public async Task<AppUserSideNavStream?> GetSideNavStreamWithUser(int streamId)
|
||||
{
|
||||
return await _context.AppUserSideNavStream
|
||||
.Include(d => d.SmartFilter)
|
||||
.Include(d => d.AppUser)
|
||||
.FirstOrDefaultAsync(d => d.Id == streamId);
|
||||
}
|
||||
|
||||
public async Task<IList<AppUserSideNavStream>> GetSideNavStreamWithFilter(int filterId)
|
||||
{
|
||||
return await _context.AppUserSideNavStream
|
||||
|
|
@ -546,7 +569,16 @@ public class UserRepository : IUserRepository
|
|||
public async Task<IList<string>> GetRoles(int userId)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
if (user == null || _userManager == null) return ArraySegment<string>.Empty; // userManager is null on Unit Tests only
|
||||
if (user == null) return ArraySegment<string>.Empty;
|
||||
|
||||
if (_userManager == null)
|
||||
{
|
||||
// userManager is null on Unit Tests only
|
||||
return await _context.UserRoles
|
||||
.Where(ur => ur.UserId == userId)
|
||||
.Select(ur => ur.Role.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
return await _userManager.GetRolesAsync(user);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Kavita.Common;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
[Flags]
|
||||
public enum VolumeIncludes
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using API.Data.Repositories;
|
|||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Enums.Theme;
|
||||
using API.Entities.MetadataMatching;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue