mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-22 23:10:34 -04:00
Missing Migration (#2790)
This commit is contained in:
parent
2b6fd1224f
commit
f443e513d1
1
.gitignore
vendored
1
.gitignore
vendored
@ -520,6 +520,7 @@ UI/Web/dist/
|
|||||||
/API/config/*.db
|
/API/config/*.db
|
||||||
/API/config/*.bak
|
/API/config/*.bak
|
||||||
/API/config/*.backup
|
/API/config/*.backup
|
||||||
|
/API/config/*.csv
|
||||||
/API/config/Hangfire.db
|
/API/config/Hangfire.db
|
||||||
/API/config/Hangfire-log.db
|
/API/config/Hangfire-log.db
|
||||||
API/config/covers/
|
API/config/covers/
|
||||||
|
159
API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs
Normal file
159
API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
|
using API.Helpers.Builders;
|
||||||
|
using API.Services;
|
||||||
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
|
using Kavita.Common.EnvironmentInfo;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace API.Data.ManualMigrations;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v0.8.0 migration to move loose leaf chapters into their own volume and retain user progress.
|
||||||
|
/// </summary>
|
||||||
|
public static class MigrateLooseLeafChapters
|
||||||
|
{
|
||||||
|
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, IDirectoryService directoryService, ILogger<Program> logger)
|
||||||
|
{
|
||||||
|
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateLooseLeafChapters"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogCritical(
|
||||||
|
"Running MigrateLooseLeafChapters migration - Please be patient, this may take some time. This is not an error");
|
||||||
|
|
||||||
|
var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||||
|
var extension = settings.EncodeMediaAs.GetExtension();
|
||||||
|
|
||||||
|
var progress = await dataContext.AppUserProgresses
|
||||||
|
.Join(dataContext.Chapter, p => p.ChapterId, c => c.Id, (p, c) => new UserProgressCsvRecord
|
||||||
|
{
|
||||||
|
IsSpecial = c.IsSpecial,
|
||||||
|
AppUserId = p.AppUserId,
|
||||||
|
PagesRead = p.PagesRead,
|
||||||
|
Range = c.Range,
|
||||||
|
Number = c.Number,
|
||||||
|
MinNumber = c.MinNumber,
|
||||||
|
SeriesId = p.SeriesId,
|
||||||
|
VolumeId = p.VolumeId,
|
||||||
|
ProgressId = p.Id
|
||||||
|
})
|
||||||
|
.Where(d => !d.IsSpecial)
|
||||||
|
.Join(dataContext.Volume, d => d.VolumeId, v => v.Id, (d, v) => new
|
||||||
|
{
|
||||||
|
ProgressRecord = d,
|
||||||
|
Volume = v
|
||||||
|
})
|
||||||
|
.Where(d => d.Volume.Name == "0")
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// First, group all the progresses into different series
|
||||||
|
logger.LogCritical("Migrating {Count} progress events to new Volume structure for Loose leafs - This may take over 10 minutes depending on size of DB. Please wait", progress.Count);
|
||||||
|
var progressesGroupedBySeries = progress
|
||||||
|
.GroupBy(p => p.ProgressRecord.SeriesId);
|
||||||
|
|
||||||
|
foreach (var seriesGroup in progressesGroupedBySeries)
|
||||||
|
{
|
||||||
|
// Get each series and move the loose leafs from the old volume to the new Volume
|
||||||
|
var seriesId = seriesGroup.Key;
|
||||||
|
|
||||||
|
// Handle All Loose Leafs
|
||||||
|
var looseLeafsInSeries = seriesGroup
|
||||||
|
.Where(p => !p.ProgressRecord.IsSpecial)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get distinct Volumes by Id. For each one, create it then create the progress events
|
||||||
|
var distinctVolumes = looseLeafsInSeries.DistinctBy(d => d.Volume.Id);
|
||||||
|
foreach (var distinctVolume in distinctVolumes)
|
||||||
|
{
|
||||||
|
// Create a new volume for each series with the appropriate number (-100000)
|
||||||
|
var chapters = await dataContext.Chapter
|
||||||
|
.Where(c => c.VolumeId == distinctVolume.Volume.Id && !c.IsSpecial).ToListAsync();
|
||||||
|
|
||||||
|
var newVolume = new VolumeBuilder(Parser.LooseLeafVolume)
|
||||||
|
.WithSeriesId(seriesId)
|
||||||
|
.WithCreated(distinctVolume.Volume.Created)
|
||||||
|
.WithLastModified(distinctVolume.Volume.LastModified)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
newVolume.Pages = chapters.Sum(c => c.Pages);
|
||||||
|
newVolume.WordCount = chapters.Sum(c => c.WordCount);
|
||||||
|
newVolume.MinHoursToRead = chapters.Sum(c => c.MinHoursToRead);
|
||||||
|
newVolume.MaxHoursToRead = chapters.Sum(c => c.MaxHoursToRead);
|
||||||
|
newVolume.AvgHoursToRead = chapters.Sum(c => c.AvgHoursToRead);
|
||||||
|
dataContext.Volume.Add(newVolume);
|
||||||
|
await dataContext.SaveChangesAsync(); // Save changes to generate the newVolumeId
|
||||||
|
|
||||||
|
// Migrate the progress event to the new volume
|
||||||
|
var oldVolumeProgresses = await dataContext.AppUserProgresses
|
||||||
|
.Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync();
|
||||||
|
foreach (var oldProgress in oldVolumeProgresses)
|
||||||
|
{
|
||||||
|
oldProgress.VolumeId = newVolume.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
logger.LogInformation("Moving {Count} chapters from Volume Id {OldVolumeId} to New Volume {NewVolumeId}",
|
||||||
|
chapters.Count, distinctVolume.Volume.Id, newVolume.Id);
|
||||||
|
|
||||||
|
// Move the loose leaf chapters from the old volume to the new Volume
|
||||||
|
foreach (var chapter in chapters)
|
||||||
|
{
|
||||||
|
// Update the VolumeId on the existing progress event
|
||||||
|
chapter.VolumeId = newVolume.Id;
|
||||||
|
|
||||||
|
// We need to migrate cover images as well
|
||||||
|
//UpdateCoverImage(directoryService, logger, chapter, extension, newVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the progress table with the new VolumeId
|
||||||
|
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save changes after processing all series
|
||||||
|
if (dataContext.ChangeTracker.HasChanges())
|
||||||
|
{
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||||
|
{
|
||||||
|
Name = "MigrateLooseLeafChapters",
|
||||||
|
ProductVersion = BuildInfo.Version.ToString(),
|
||||||
|
RanAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
logger.LogCritical(
|
||||||
|
"Running MigrateLooseLeafChapters migration - Completed. This is not an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateCoverImage(IDirectoryService directoryService, ILogger<Program> logger, Chapter chapter,
|
||||||
|
string extension, Volume newVolume)
|
||||||
|
{
|
||||||
|
var existingCover = ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId) + extension;
|
||||||
|
var newCover = ImageService.GetChapterFormat(chapter.Id, newVolume.Id) + extension;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!chapter.CoverImageLocked)
|
||||||
|
{
|
||||||
|
// First rename existing cover
|
||||||
|
File.Copy(Path.Join(directoryService.CoverImageDirectory, existingCover), Path.Join(directoryService.CoverImageDirectory, newCover));
|
||||||
|
chapter.CoverImage = newCover;
|
||||||
|
}
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Unable to rename {OldCover} to {NewCover}, this cover will need manual refresh", existingCover, newCover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,9 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using API.Helpers.Builders;
|
using API.Helpers.Builders;
|
||||||
|
using API.Services;
|
||||||
using API.Services.Tasks.Scanner.Parser;
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
using Kavita.Common.EnvironmentInfo;
|
using Kavita.Common.EnvironmentInfo;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -21,6 +23,7 @@ public class UserProgressCsvRecord
|
|||||||
public float MinNumber { get; set; }
|
public float MinNumber { get; set; }
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
public int VolumeId { get; set; }
|
public int VolumeId { get; set; }
|
||||||
|
public int ProgressId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,7 +31,7 @@ public class UserProgressCsvRecord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class MigrateMixedSpecials
|
public static class MigrateMixedSpecials
|
||||||
{
|
{
|
||||||
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, ILogger<Program> logger)
|
public static async Task Migrate(DataContext dataContext, IUnitOfWork unitOfWork, IDirectoryService directoryService, ILogger<Program> logger)
|
||||||
{
|
{
|
||||||
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateMixedSpecials"))
|
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateMixedSpecials"))
|
||||||
{
|
{
|
||||||
@ -39,13 +42,13 @@ public static class MigrateMixedSpecials
|
|||||||
"Running ManualMigrateMixedSpecials migration - Please be patient, this may take some time. This is not an error");
|
"Running ManualMigrateMixedSpecials migration - Please be patient, this may take some time. This is not an error");
|
||||||
|
|
||||||
// First, group all the progresses into different series
|
// First, group all the progresses into different series
|
||||||
|
|
||||||
// Get each series and move the specials from old volume to the new Volume()
|
// Get each series and move the specials from old volume to the new Volume()
|
||||||
|
|
||||||
// Create a new progress event from existing and store the Id of existing progress event to delete it
|
// Create a new progress event from existing and store the Id of existing progress event to delete it
|
||||||
|
|
||||||
// Save per series
|
// Save per series
|
||||||
|
|
||||||
|
var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||||
|
var extension = settings.EncodeMediaAs.GetExtension();
|
||||||
|
|
||||||
var progress = await dataContext.AppUserProgresses
|
var progress = await dataContext.AppUserProgresses
|
||||||
.Join(dataContext.Chapter, p => p.ChapterId, c => c.Id, (p, c) => new UserProgressCsvRecord
|
.Join(dataContext.Chapter, p => p.ChapterId, c => c.Id, (p, c) => new UserProgressCsvRecord
|
||||||
{
|
{
|
||||||
@ -56,10 +59,12 @@ public static class MigrateMixedSpecials
|
|||||||
Number = c.Number,
|
Number = c.Number,
|
||||||
MinNumber = c.MinNumber,
|
MinNumber = c.MinNumber,
|
||||||
SeriesId = p.SeriesId,
|
SeriesId = p.SeriesId,
|
||||||
VolumeId = p.VolumeId
|
VolumeId = p.VolumeId,
|
||||||
|
ProgressId = p.Id
|
||||||
})
|
})
|
||||||
.Where(d => d.IsSpecial || d.Number == "0")
|
.Where(d => d.IsSpecial || d.Number == "0")
|
||||||
.Join(dataContext.Volume, d => d.VolumeId, v => v.Id, (d, v) => new
|
.Join(dataContext.Volume, d => d.VolumeId, v => v.Id,
|
||||||
|
(d, v) => new
|
||||||
{
|
{
|
||||||
ProgressRecord = d,
|
ProgressRecord = d,
|
||||||
Volume = v
|
Volume = v
|
||||||
@ -68,18 +73,19 @@ public static class MigrateMixedSpecials
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// First, group all the progresses into different series
|
// First, group all the progresses into different series
|
||||||
logger.LogCritical("Migrating {Count} progress events to new Volume structure - This may take over 10 minutes depending on size of DB. Please wait", progress.Count);
|
logger.LogCritical("Migrating {Count} progress events to new Volume structure for Specials - This may take over 10 minutes depending on size of DB. Please wait", progress.Count);
|
||||||
var progressesGroupedBySeries = progress.GroupBy(p => p.ProgressRecord.SeriesId);
|
var progressesGroupedBySeries = progress.GroupBy(p => p.ProgressRecord.SeriesId);
|
||||||
|
|
||||||
foreach (var seriesGroup in progressesGroupedBySeries)
|
foreach (var seriesGroup in progressesGroupedBySeries)
|
||||||
{
|
{
|
||||||
// Get each series and move the specials from the old volume to the new Volume
|
// Get each series and move the specials from the old volume to the new Volume
|
||||||
var seriesId = seriesGroup.Key;
|
var seriesId = seriesGroup.Key;
|
||||||
|
|
||||||
|
// Handle All Specials
|
||||||
var specialsInSeries = seriesGroup
|
var specialsInSeries = seriesGroup
|
||||||
.Where(p => p.ProgressRecord.IsSpecial)
|
.Where(p => p.ProgressRecord.IsSpecial)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
// Get distinct Volumes by Id. For each one, create it then create the progress events
|
// Get distinct Volumes by Id. For each one, create it then create the progress events
|
||||||
var distinctVolumes = specialsInSeries.DistinctBy(d => d.Volume.Id);
|
var distinctVolumes = specialsInSeries.DistinctBy(d => d.Volume.Id);
|
||||||
foreach (var distinctVolume in distinctVolumes)
|
foreach (var distinctVolume in distinctVolumes)
|
||||||
@ -90,29 +96,43 @@ public static class MigrateMixedSpecials
|
|||||||
|
|
||||||
var newVolume = new VolumeBuilder(Parser.SpecialVolume)
|
var newVolume = new VolumeBuilder(Parser.SpecialVolume)
|
||||||
.WithSeriesId(seriesId)
|
.WithSeriesId(seriesId)
|
||||||
.WithChapters(chapters)
|
.WithCreated(distinctVolume.Volume.Created)
|
||||||
|
.WithLastModified(distinctVolume.Volume.LastModified)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
newVolume.Pages = chapters.Sum(c => c.Pages);
|
||||||
|
newVolume.WordCount = chapters.Sum(c => c.WordCount);
|
||||||
|
newVolume.MinHoursToRead = chapters.Sum(c => c.MinHoursToRead);
|
||||||
|
newVolume.MaxHoursToRead = chapters.Sum(c => c.MaxHoursToRead);
|
||||||
|
newVolume.AvgHoursToRead = chapters.Sum(c => c.AvgHoursToRead);
|
||||||
|
|
||||||
dataContext.Volume.Add(newVolume);
|
dataContext.Volume.Add(newVolume);
|
||||||
await dataContext.SaveChangesAsync(); // Save changes to generate the newVolumeId
|
await dataContext.SaveChangesAsync(); // Save changes to generate the newVolumeId
|
||||||
|
|
||||||
// Migrate the progress event to the new volume
|
// Migrate the progress event to the new volume
|
||||||
distinctVolume.ProgressRecord.VolumeId = newVolume.Id;
|
var oldVolumeProgresses = await dataContext.AppUserProgresses
|
||||||
|
.Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync();
|
||||||
|
foreach (var oldProgress in oldVolumeProgresses)
|
||||||
|
{
|
||||||
|
oldProgress.VolumeId = newVolume.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
logger.LogInformation("Moving {Count} chapters from Volume Id {OldVolumeId} to New Volume {NewVolumeId}",
|
logger.LogInformation("Moving {Count} chapters from Volume Id {OldVolumeId} to New Volume {NewVolumeId}",
|
||||||
chapters.Count, distinctVolume.Volume.Id, newVolume.Id);
|
chapters.Count, distinctVolume.Volume.Id, newVolume.Id);
|
||||||
// Move the special chapters from the old volume to the new Volume
|
|
||||||
var specialChapters = await dataContext.Chapter
|
|
||||||
.Where(c => c.VolumeId == distinctVolume.ProgressRecord.VolumeId && c.IsSpecial)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
foreach (var specialChapter in specialChapters)
|
// Move the special chapters from the old volume to the new Volume
|
||||||
|
foreach (var specialChapter in chapters)
|
||||||
{
|
{
|
||||||
// Update the VolumeId on the existing progress event
|
// Update the VolumeId on the existing progress event
|
||||||
specialChapter.VolumeId = newVolume.Id;
|
specialChapter.VolumeId = newVolume.Id;
|
||||||
|
|
||||||
|
//UpdateCoverImage(directoryService, logger, specialChapter, extension, newVolume);
|
||||||
}
|
}
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save changes after processing all series
|
// Save changes after processing all series
|
||||||
@ -121,10 +141,6 @@ public static class MigrateMixedSpecials
|
|||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all Volumes with Name as "0" -> Special
|
|
||||||
logger.LogCritical("Updating all Volumes with Name 0 to SpecialNumber");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||||
{
|
{
|
||||||
@ -137,4 +153,25 @@ public static class MigrateMixedSpecials
|
|||||||
logger.LogCritical(
|
logger.LogCritical(
|
||||||
"Running ManualMigrateMixedSpecials migration - Completed. This is not an error");
|
"Running ManualMigrateMixedSpecials migration - Completed. This is not an error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void UpdateCoverImage(IDirectoryService directoryService, ILogger<Program> logger, Chapter specialChapter,
|
||||||
|
string extension, Volume newVolume)
|
||||||
|
{
|
||||||
|
// We need to migrate cover images as well
|
||||||
|
var existingCover = ImageService.GetChapterFormat(specialChapter.Id, specialChapter.VolumeId) + extension;
|
||||||
|
var newCover = ImageService.GetChapterFormat(specialChapter.Id, newVolume.Id) + extension;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!specialChapter.CoverImageLocked)
|
||||||
|
{
|
||||||
|
// First rename existing cover
|
||||||
|
File.Copy(Path.Join(directoryService.CoverImageDirectory, existingCover), Path.Join(directoryService.CoverImageDirectory, newCover));
|
||||||
|
specialChapter.CoverImage = newCover;
|
||||||
|
}
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Unable to rename {OldCover} to {NewCover}, this cover will need manual refresh", existingCover, newCover);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
123
API/Data/ManualMigrations/MigrateProgressExport.cs
Normal file
123
API/Data/ManualMigrations/MigrateProgressExport.cs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Services;
|
||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration.Attributes;
|
||||||
|
using Kavita.Common.EnvironmentInfo;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace API.Data.ManualMigrations;
|
||||||
|
|
||||||
|
public class ProgressExport
|
||||||
|
{
|
||||||
|
[Name("Library Id")]
|
||||||
|
public int LibraryId { get; set; }
|
||||||
|
|
||||||
|
[Name("Library Name")]
|
||||||
|
public string LibraryName { get; set; }
|
||||||
|
|
||||||
|
[Name("Series Name")]
|
||||||
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
|
[Name("Volume Number")]
|
||||||
|
public string VolumeRange { get; set; }
|
||||||
|
|
||||||
|
[Name("Volume LookupName")]
|
||||||
|
public string VolumeLookupName { get; set; }
|
||||||
|
|
||||||
|
[Name("Chapter Number")]
|
||||||
|
public string ChapterRange { get; set; }
|
||||||
|
|
||||||
|
[Name("FileName")]
|
||||||
|
public string MangaFileName { get; set; }
|
||||||
|
|
||||||
|
[Name("FilePath")]
|
||||||
|
public string MangaFilePath { get; set; }
|
||||||
|
|
||||||
|
[Name("AppUser Name")]
|
||||||
|
public string AppUserName { get; set; }
|
||||||
|
|
||||||
|
[Name("AppUser Id")]
|
||||||
|
public int AppUserId { get; set; }
|
||||||
|
|
||||||
|
[Name("Pages Read")]
|
||||||
|
public int PagesRead { get; set; }
|
||||||
|
|
||||||
|
[Name("BookScrollId")]
|
||||||
|
public string BookScrollId { get; set; }
|
||||||
|
|
||||||
|
[Name("Progress Created")]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
[Name("Progress LastModified")]
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v0.8.0 - Progress is extracted and saved in a csv
|
||||||
|
/// </summary>
|
||||||
|
public static class MigrateProgressExport
|
||||||
|
{
|
||||||
|
public static async Task Migrate(DataContext dataContext, IDirectoryService directoryService, ILogger<Program> logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateProgressExport"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogCritical(
|
||||||
|
"Running MigrateProgressExport migration - Please be patient, this may take some time. This is not an error");
|
||||||
|
|
||||||
|
var data = await dataContext.AppUserProgresses
|
||||||
|
.Join(dataContext.Series, progress => progress.SeriesId, series => series.Id, (progress, series) => new { progress, series })
|
||||||
|
.Join(dataContext.Volume, ps => ps.progress.VolumeId, volume => volume.Id, (ps, volume) => new { ps.progress, ps.series, volume })
|
||||||
|
.Join(dataContext.Chapter, psv => psv.progress.ChapterId, chapter => chapter.Id, (psv, chapter) => new { psv.progress, psv.series, psv.volume, chapter })
|
||||||
|
.Join(dataContext.MangaFile, psvc => psvc.chapter.Id, mangaFile => mangaFile.ChapterId, (psvc, mangaFile) => new { psvc.progress, psvc.series, psvc.volume, psvc.chapter, mangaFile })
|
||||||
|
.Join(dataContext.AppUser, psvcm => psvcm.progress.AppUserId, appUser => appUser.Id, (psvcm, appUser) => new
|
||||||
|
{
|
||||||
|
LibraryId = psvcm.series.LibraryId,
|
||||||
|
LibraryName = psvcm.series.Library.Name,
|
||||||
|
SeriesName = psvcm.series.Name,
|
||||||
|
VolumeRange = psvcm.volume.MinNumber + "-" + psvcm.volume.MaxNumber,
|
||||||
|
VolumeLookupName = psvcm.volume.Name,
|
||||||
|
ChapterRange = psvcm.chapter.Range,
|
||||||
|
MangaFileName = psvcm.mangaFile.FileName,
|
||||||
|
MangaFilePath = psvcm.mangaFile.FilePath,
|
||||||
|
AppUserName = appUser.UserName,
|
||||||
|
AppUserId = appUser.Id,
|
||||||
|
PagesRead = psvcm.progress.PagesRead,
|
||||||
|
BookScrollId = psvcm.progress.BookScrollId,
|
||||||
|
ProgressCreated = psvcm.progress.Created,
|
||||||
|
ProgressLastModified = psvcm.progress.LastModified
|
||||||
|
}).ToListAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Write the mapped data to a CSV file
|
||||||
|
await using var writer = new StreamWriter(Path.Join(directoryService.ConfigDirectory, "progress_export.csv"));
|
||||||
|
await using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
|
||||||
|
await csv.WriteRecordsAsync(data);
|
||||||
|
|
||||||
|
logger.LogCritical(
|
||||||
|
"Running MigrateProgressExport migration - Completed. This is not an error");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// On new installs, the db isn't setup yet, so this has nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
|
||||||
|
{
|
||||||
|
Name = "MigrateProgressExport",
|
||||||
|
ProductVersion = BuildInfo.Version.ToString(),
|
||||||
|
RanAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ public class MangaFileBuilder : IEntityBuilder<MangaFile>
|
|||||||
{
|
{
|
||||||
_mangaFile = new MangaFile()
|
_mangaFile = new MangaFile()
|
||||||
{
|
{
|
||||||
FilePath = filePath,
|
FilePath = Parser.NormalizePath(filePath),
|
||||||
Format = format,
|
Format = format,
|
||||||
Pages = pages,
|
Pages = pages,
|
||||||
LastModified = File.GetLastWriteTime(filePath),
|
LastModified = File.GetLastWriteTime(filePath),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
@ -75,4 +76,18 @@ public class VolumeBuilder : IEntityBuilder<Volume>
|
|||||||
_volume.CoverImage = cover;
|
_volume.CoverImage = cover;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VolumeBuilder WithCreated(DateTime created)
|
||||||
|
{
|
||||||
|
_volume.Created = created;
|
||||||
|
_volume.CreatedUtc = created.ToUniversalTime();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeBuilder WithLastModified(DateTime lastModified)
|
||||||
|
{
|
||||||
|
_volume.LastModified = lastModified;
|
||||||
|
_volume.LastModifiedUtc = lastModified.ToUniversalTime();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,13 +166,17 @@ public class ParseScannedFiles
|
|||||||
}
|
}
|
||||||
|
|
||||||
normalizedPath = Parser.Parser.NormalizePath(folderPath);
|
normalizedPath = Parser.Parser.NormalizePath(folderPath);
|
||||||
|
var libraryRoot =
|
||||||
|
library.Folders.FirstOrDefault(f =>
|
||||||
|
Parser.Parser.NormalizePath(folderPath).Contains(Parser.Parser.NormalizePath(f.Path)))?.Path ??
|
||||||
|
folderPath;
|
||||||
if (HasSeriesFolderNotChangedSinceLastScan(seriesPaths, normalizedPath, forceCheck))
|
if (HasSeriesFolderNotChangedSinceLastScan(seriesPaths, normalizedPath, forceCheck))
|
||||||
{
|
{
|
||||||
result.Add(new ScanResult()
|
result.Add(new ScanResult()
|
||||||
{
|
{
|
||||||
Files = ArraySegment<string>.Empty,
|
Files = ArraySegment<string>.Empty,
|
||||||
Folder = folderPath,
|
Folder = folderPath,
|
||||||
LibraryRoot = folderPath,
|
LibraryRoot = libraryRoot,
|
||||||
HasChanged = false
|
HasChanged = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -181,7 +185,7 @@ public class ParseScannedFiles
|
|||||||
{
|
{
|
||||||
Files = _directoryService.ScanFiles(folderPath, fileExtensions),
|
Files = _directoryService.ScanFiles(folderPath, fileExtensions),
|
||||||
Folder = folderPath,
|
Folder = folderPath,
|
||||||
LibraryRoot = folderPath,
|
LibraryRoot = libraryRoot,
|
||||||
HasChanged = true
|
HasChanged = true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -724,6 +724,7 @@ public class ProcessSeries : IProcessSeries
|
|||||||
existingFile.Pages = _readingItemService.GetNumberOfPages(info.FullFilePath, info.Format);
|
existingFile.Pages = _readingItemService.GetNumberOfPages(info.FullFilePath, info.Format);
|
||||||
existingFile.Extension = fileInfo.Extension.ToLowerInvariant();
|
existingFile.Extension = fileInfo.Extension.ToLowerInvariant();
|
||||||
existingFile.FileName = Parser.Parser.RemoveExtensionIfSupported(existingFile.FilePath);
|
existingFile.FileName = Parser.Parser.RemoveExtensionIfSupported(existingFile.FilePath);
|
||||||
|
existingFile.FilePath = Parser.Parser.NormalizePath(existingFile.FilePath);
|
||||||
existingFile.Bytes = fileInfo.Length;
|
existingFile.Bytes = fileInfo.Length;
|
||||||
// We skip updating DB here with last modified time so that metadata refresh can do it
|
// We skip updating DB here with last modified time so that metadata refresh can do it
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,9 @@ public class Startup
|
|||||||
// v0.8.0
|
// v0.8.0
|
||||||
await MigrateVolumeLookupName.Migrate(dataContext, unitOfWork, logger);
|
await MigrateVolumeLookupName.Migrate(dataContext, unitOfWork, logger);
|
||||||
await MigrateChapterNumber.Migrate(dataContext, logger);
|
await MigrateChapterNumber.Migrate(dataContext, logger);
|
||||||
await MigrateMixedSpecials.Migrate(dataContext, unitOfWork, logger);
|
await MigrateProgressExport.Migrate(dataContext, directoryService, logger);
|
||||||
|
await MigrateMixedSpecials.Migrate(dataContext, unitOfWork, directoryService, logger);
|
||||||
|
await MigrateLooseLeafChapters.Migrate(dataContext, unitOfWork, directoryService, logger);
|
||||||
await MigrateChapterFields.Migrate(dataContext, unitOfWork, logger);
|
await MigrateChapterFields.Migrate(dataContext, unitOfWork, logger);
|
||||||
await MigrateChapterRange.Migrate(dataContext, unitOfWork, logger);
|
await MigrateChapterRange.Migrate(dataContext, unitOfWork, logger);
|
||||||
|
|
||||||
|
24
UI/Web/package-lock.json
generated
24
UI/Web/package-lock.json
generated
@ -688,6 +688,7 @@
|
|||||||
"version": "17.3.0",
|
"version": "17.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.0.tgz",
|
||||||
"integrity": "sha512-ewo+pb0QUC69Ey15z4vPteoBeO81HitqplysOoeXbyVBjMnKmZl3343wx7ukgcI97lmj4d38d1r4AnIoO5n/Vw==",
|
"integrity": "sha512-ewo+pb0QUC69Ey15z4vPteoBeO81HitqplysOoeXbyVBjMnKmZl3343wx7ukgcI97lmj4d38d1r4AnIoO5n/Vw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.23.9",
|
"@babel/core": "7.23.9",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||||
@ -5752,6 +5753,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"picomatch": "^2.0.4"
|
"picomatch": "^2.0.4"
|
||||||
@ -6013,6 +6015,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@ -6323,6 +6326,7 @@
|
|||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@ -6571,7 +6575,8 @@
|
|||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
|
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
@ -7473,6 +7478,7 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iconv-lite": "^0.6.2"
|
"iconv-lite": "^0.6.2"
|
||||||
@ -7482,6 +7488,7 @@
|
|||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
@ -8558,6 +8565,7 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -9332,6 +9340,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
},
|
},
|
||||||
@ -11115,6 +11124,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -12527,6 +12537,7 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
},
|
},
|
||||||
@ -12537,7 +12548,8 @@
|
|||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
|
||||||
"integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw=="
|
"integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
@ -12989,7 +13001,7 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.71.1",
|
"version": "1.71.1",
|
||||||
@ -13108,6 +13120,7 @@
|
|||||||
"version": "7.5.4",
|
"version": "7.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
},
|
},
|
||||||
@ -13122,6 +13135,7 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
},
|
},
|
||||||
@ -13132,7 +13146,8 @@
|
|||||||
"node_modules/semver/node_modules/yallist": {
|
"node_modules/semver/node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
@ -14244,6 +14259,7 @@
|
|||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||||
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.14.10"
|
"version": "0.7.14.5"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user