Reading Lists & More (#564)
* Added continous reading to the book reader. Clicking on the max pages to right of progress bar will now go to last page. * Forgot a file for continous book reading * Fixed up some code regarding transitioning between chapters. Arrows now show to represent a chapter transition. * Laid the foundation for reading lists * All foundation is laid out. Actions are wired in the UI. Backend repository is setup. Redid the migration to have ReadingList track modification so we can order them for the user. * Updated add modal to have basic skeleton * Hooked up ability to fetch reading lists from backend * Made a huge performance improvement to GetChapterIdsForSeriesAsync() by reducing a JOIN and an iteration loop. Improvement went from 2 seconds -> 200 ms. * Implemented the ability to add all chapters in a series to a reading list. * Fixed issue with adding new items to reading list not being in a logical order. Lots of work on getting all the information around the reading list view. Added some foreign keys back to chapter so delete should clean up after itself. * Added ability to open directly the series * Reading List Items now have progress attached * Hooked up list deletion and added a case where if doesn't exist on load, then redirect to library. * Lots of changes. Introduced a dashboard component for the main app. This will sit on libraries route for now and will have 3 tabs to show different sections. Moved libraries reel down to bottom as people are more likely to access recently added or in progress than explore their whole library. Note: Bundles are messed up, they need to be reoptimized and routes need to be updated. * Added pagination to the reading lists api and implemented a page to show all lists * Cleaned up old code from all-collections component so now it only handles all collections and doesn't have the old code for an individual collection * Hooked in actions and navigation on reading lists * When the user re-arranges items, they are now persisted * Implemented remove read, but performance is pretty poor. Needs to be optimized. * Lots of API fixes for adding items to a series, returning items, etc. Committing before fixing incorrect fetches of items for a readingListId. * Rewrote the joins for GetReadingListItemDtosByIdAsync() to not return extra records. * Remove bug marker now that it is fixed * Refactor update-by-series to move more of the code to a re-usable function for update-by-volume/chapter APIs * Implemented the ability to add via series, volume or chapter. * Added OPDS support for reading lists. This included adding VolumeId to the ReadingListDto. * Fixed a bug with deleting items * After we create a library inform user that a scan has started * Added some extra help information for users on directory picker, since linux users were getting confused. * Setup for the reading functionality * Fixed an issue where opening the edit series modal and pressing save without doing anything would empty collection tags. Would happen often when editing cover images. * Fixed get-next-chapter for reading list. Refactored all methods to use the new GetUserIdByUsernameAsync(), which is much faster and uses less memory. * Hooked in prev chapter for continuous reading with reading list * Hooked up the read code for manga reader and book reader to have list id passed * Manga reader now functions completely with reading lists * Implemented reading list and incognito mode into book reader * Refactored some common reading code into reader service * Added support for "Series - - Vol. 03 Ch. 023.5 - Volume 3 Extras.cbz" format that can occur with FMD2. * Implemented continuous reading with a reading list between different readers. This incurs a 3x performance hit on the book info api. * style changes. Don't emit an event if position of draggable item hasn't changed * Styling and added the edit reading list flow. * Cleaned up some extra spaces when actionables isn't shown. Lots of cleanup for promoted lists. * Refactored some filter code to a common service * Added an RBS check in getting Items for a given user. * Code smells * More smells
This commit is contained in:
parent
d65e49926a
commit
cf7a9aa71e
117 changed files with 7050 additions and 305 deletions
|
@ -1,7 +0,0 @@
|
|||
namespace API.Data
|
||||
{
|
||||
public class BookmarkRepository
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -35,6 +35,9 @@ namespace API.Data
|
|||
public DbSet<SeriesMetadata> SeriesMetadata { get; set; }
|
||||
public DbSet<CollectionTag> CollectionTag { get; set; }
|
||||
public DbSet<AppUserBookmark> AppUserBookmark { get; set; }
|
||||
public DbSet<ReadingList> ReadingList { get; set; }
|
||||
public DbSet<ReadingListItem> ReadingListItem { get; set; }
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
|
1018
API/Data/Migrations/20210901150310_ReadingLists.Designer.cs
generated
Normal file
1018
API/Data/Migrations/20210901150310_ReadingLists.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
84
API/Data/Migrations/20210901150310_ReadingLists.cs
Normal file
84
API/Data/Migrations/20210901150310_ReadingLists.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ReadingLists : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReadingList",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Title = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Summary = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Promoted = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
AppUserId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReadingList", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReadingList_AspNetUsers_AppUserId",
|
||||
column: x => x.AppUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReadingListItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
LibraryId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SeriesId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
VolumeId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChapterId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Order = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ReadingListId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReadingListItem", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReadingListItem_ReadingList_ReadingListId",
|
||||
column: x => x.ReadingListId,
|
||||
principalTable: "ReadingList",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingList_AppUserId",
|
||||
table: "ReadingList",
|
||||
column: "AppUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_ReadingListId",
|
||||
table: "ReadingListItem",
|
||||
column: "ReadingListId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_SeriesId_VolumeId_ChapterId_LibraryId",
|
||||
table: "ReadingListItem",
|
||||
columns: new[] { "SeriesId", "VolumeId", "ChapterId", "LibraryId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReadingList");
|
||||
}
|
||||
}
|
||||
}
|
1022
API/Data/Migrations/20210901200442_ReadingListsAdditions.Designer.cs
generated
Normal file
1022
API/Data/Migrations/20210901200442_ReadingListsAdditions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
55
API/Data/Migrations/20210901200442_ReadingListsAdditions.cs
Normal file
55
API/Data/Migrations/20210901200442_ReadingListsAdditions.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ReadingListsAdditions : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReadingListItem_ReadingList_ReadingListId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReadingListId",
|
||||
table: "ReadingListItem",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReadingListItem_ReadingList_ReadingListId",
|
||||
table: "ReadingListItem",
|
||||
column: "ReadingListId",
|
||||
principalTable: "ReadingList",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReadingListItem_ReadingList_ReadingListId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "ReadingListId",
|
||||
table: "ReadingListItem",
|
||||
type: "INTEGER",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReadingListItem_ReadingList_ReadingListId",
|
||||
table: "ReadingListItem",
|
||||
column: "ReadingListId",
|
||||
principalTable: "ReadingList",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
1050
API/Data/Migrations/20210902110705_ReadingListsExtraRealationships.Designer.cs
generated
Normal file
1050
API/Data/Migrations/20210902110705_ReadingListsExtraRealationships.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,67 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ReadingListsExtraRealationships : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_ChapterId",
|
||||
table: "ReadingListItem",
|
||||
column: "ChapterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_VolumeId",
|
||||
table: "ReadingListItem",
|
||||
column: "VolumeId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReadingListItem_Chapter_ChapterId",
|
||||
table: "ReadingListItem",
|
||||
column: "ChapterId",
|
||||
principalTable: "Chapter",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReadingListItem_Series_SeriesId",
|
||||
table: "ReadingListItem",
|
||||
column: "SeriesId",
|
||||
principalTable: "Series",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ReadingListItem_Volume_VolumeId",
|
||||
table: "ReadingListItem",
|
||||
column: "VolumeId",
|
||||
principalTable: "Volume",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReadingListItem_Chapter_ChapterId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReadingListItem_Series_SeriesId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ReadingListItem_Volume_VolumeId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ReadingListItem_ChapterId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ReadingListItem_VolumeId",
|
||||
table: "ReadingListItem");
|
||||
}
|
||||
}
|
||||
}
|
1046
API/Data/Migrations/20210906140845_ReadingListsChanges.Designer.cs
generated
Normal file
1046
API/Data/Migrations/20210906140845_ReadingListsChanges.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
43
API/Data/Migrations/20210906140845_ReadingListsChanges.cs
Normal file
43
API/Data/Migrations/20210906140845_ReadingListsChanges.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ReadingListsChanges : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ReadingListItem_SeriesId_VolumeId_ChapterId_LibraryId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LibraryId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_SeriesId",
|
||||
table: "ReadingListItem",
|
||||
column: "SeriesId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ReadingListItem_SeriesId",
|
||||
table: "ReadingListItem");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "LibraryId",
|
||||
table: "ReadingListItem",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReadingListItem_SeriesId_VolumeId_ChapterId_LibraryId",
|
||||
table: "ReadingListItem",
|
||||
columns: new[] { "SeriesId", "VolumeId", "ChapterId", "LibraryId" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -440,6 +440,71 @@ namespace API.Data.Migrations
|
|||
b.ToTable("MangaFile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingList", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Promoted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.ToTable("ReadingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingListItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingListId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VolumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasIndex("ReadingListId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.HasIndex("VolumeId");
|
||||
|
||||
b.ToTable("ReadingListItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -780,6 +845,52 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingList", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
.WithMany("ReadingLists")
|
||||
.HasForeignKey("AppUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AppUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingListItem", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.ReadingList", "ReadingList")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("ReadingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Series", "Series")
|
||||
.WithMany()
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Volume", "Volume")
|
||||
.WithMany()
|
||||
.HasForeignKey("VolumeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
|
||||
b.Navigation("ReadingList");
|
||||
|
||||
b.Navigation("Series");
|
||||
|
||||
b.Navigation("Volume");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Library", "Library")
|
||||
|
@ -892,6 +1003,8 @@ namespace API.Data.Migrations
|
|||
|
||||
b.Navigation("Ratings");
|
||||
|
||||
b.Navigation("ReadingLists");
|
||||
|
||||
b.Navigation("UserPreferences");
|
||||
|
||||
b.Navigation("UserRoles");
|
||||
|
@ -909,6 +1022,11 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ReadingList", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.Navigation("Metadata");
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace API.Data
|
|||
var rowsToRemove = await _context.AppUserProgresses
|
||||
.Where(progress => !chapterIds.Contains(progress.ChapterId))
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
_context.RemoveRange(rowsToRemove);
|
||||
return await _context.SaveChangesAsync() > 0 ? rowsToRemove.Count : 0;
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ namespace API.Data
|
|||
.ToListAsync();
|
||||
|
||||
if (seriesIds.Count == 0) return false;
|
||||
|
||||
|
||||
return await _context.Series
|
||||
.Include(s => s.Library)
|
||||
.Where(s => seriesIds.Contains(s.Id) && s.Library.Type == libraryType)
|
||||
|
@ -53,4 +54,4 @@ namespace API.Data
|
|||
.AnyAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
using API.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
|
@ -18,6 +21,14 @@ namespace API.Data
|
|||
_context.Entry(chapter).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Chapter>> GetChaptersByIdsAsync(IList<int> chapterIds)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.Include(c => c.Volume)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// TODO: Move over Chapter based queries here
|
||||
}
|
||||
}
|
|
@ -4,11 +4,12 @@ using System.Threading.Tasks;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class CollectionTagRepository : ICollectionTagRepository
|
||||
{
|
|
@ -3,9 +3,10 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class FileRepository : IFileRepository
|
||||
{
|
|
@ -5,11 +5,12 @@ using API.DTOs;
|
|||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
{
|
178
API/Data/Repositories/ReadingListRepository.cs
Normal file
178
API/Data/Repositories/ReadingListRepository.cs
Normal file
|
@ -0,0 +1,178 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities;
|
||||
using API.Helpers;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public ReadingListRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Update(ReadingList list)
|
||||
{
|
||||
_context.Entry(list).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
{
|
||||
_context.ReadingListItem.Remove(item);
|
||||
}
|
||||
|
||||
public void BulkRemove(IEnumerable<ReadingListItem> items)
|
||||
{
|
||||
_context.ReadingListItem.RemoveRange(items);
|
||||
}
|
||||
|
||||
|
||||
public async Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams)
|
||||
{
|
||||
var query = _context.ReadingList
|
||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||
.OrderBy(l => l.LastModified)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking();
|
||||
|
||||
return await PagedList<ReadingListDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
public async Task<ReadingList> GetReadingListByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId)
|
||||
.Include(r => r.Items)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
var userLibraries = _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(user => user.Id == userId))
|
||||
.AsNoTracking()
|
||||
.Select(library => library.Id)
|
||||
.ToList();
|
||||
|
||||
var items = await _context.ReadingListItem
|
||||
.Where(s => s.ReadingListId == readingListId)
|
||||
.Join(_context.Chapter, s => s.ChapterId, chapter => chapter.Id, (data, chapter) => new
|
||||
{
|
||||
TotalPages = chapter.Pages,
|
||||
ChapterNumber = chapter.Range,
|
||||
readingListItem = data
|
||||
})
|
||||
.Join(_context.Volume, s => s.readingListItem.VolumeId, volume => volume.Id, (data, volume) => new
|
||||
{
|
||||
data.readingListItem,
|
||||
data.TotalPages,
|
||||
data.ChapterNumber,
|
||||
VolumeId = volume.Id,
|
||||
VolumeNumber = volume.Name,
|
||||
})
|
||||
.Join(_context.Series, s => s.readingListItem.SeriesId, series => series.Id,
|
||||
(data, s) => new
|
||||
{
|
||||
SeriesName = s.Name,
|
||||
SeriesFormat = s.Format,
|
||||
s.LibraryId,
|
||||
data.readingListItem,
|
||||
data.TotalPages,
|
||||
data.ChapterNumber,
|
||||
data.VolumeNumber,
|
||||
data.VolumeId
|
||||
})
|
||||
.Select(data => new ReadingListItemDto()
|
||||
{
|
||||
Id = data.readingListItem.Id,
|
||||
ChapterId = data.readingListItem.ChapterId,
|
||||
Order = data.readingListItem.Order,
|
||||
SeriesId = data.readingListItem.SeriesId,
|
||||
SeriesName = data.SeriesName,
|
||||
SeriesFormat = data.SeriesFormat,
|
||||
PagesTotal = data.TotalPages,
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber,
|
||||
LibraryId = data.LibraryId,
|
||||
VolumeId = data.VolumeId,
|
||||
ReadingListId = data.readingListItem.ReadingListId
|
||||
})
|
||||
.Where(o => userLibraries.Contains(o.LibraryId))
|
||||
.OrderBy(rli => rli.Order)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
// Attach progress information
|
||||
var fetchedChapterIds = items.Select(i => i.ChapterId);
|
||||
var progresses = await _context.AppUserProgresses
|
||||
.Where(p => fetchedChapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var progress in progresses)
|
||||
{
|
||||
var progressItem = items.SingleOrDefault(i => i.ChapterId == progress.ChapterId && i.ReadingListId == readingListId);
|
||||
if (progressItem == null) continue;
|
||||
|
||||
progressItem.PagesRead = progress.PagesRead;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items)
|
||||
{
|
||||
var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId && chapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var progress = userProgress.Where(p => p.ChapterId == item.ChapterId);
|
||||
item.PagesRead = progress.Sum(p => p.PagesRead);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Title.Equals(title))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingListItem
|
||||
.Where(r => r.ReadingListId == readingListId)
|
||||
.OrderBy(r => r.Order)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -8,11 +8,12 @@ using API.Entities;
|
|||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
|
@ -221,21 +222,17 @@ namespace API.Data
|
|||
|
||||
public async Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds)
|
||||
{
|
||||
var series = await _context.Series
|
||||
.Where(s => seriesIds.Contains(s.Id))
|
||||
.Include(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
var volumes = await _context.Volume
|
||||
.Where(v => seriesIds.Contains(v.SeriesId))
|
||||
.Include(v => v.Chapters)
|
||||
.ToListAsync();
|
||||
|
||||
IList<int> chapterIds = new List<int>();
|
||||
foreach (var s in series)
|
||||
foreach (var v in volumes)
|
||||
{
|
||||
foreach (var v in s.Volumes)
|
||||
foreach (var c in v.Chapters)
|
||||
{
|
||||
foreach (var c in v.Chapters)
|
||||
{
|
||||
chapterIds.Add(c.Id);
|
||||
}
|
||||
chapterIds.Add(c.Id);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,11 @@ using API.DTOs;
|
|||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class SettingsRepository : ISettingsRepository
|
||||
{
|
||||
|
@ -45,4 +46,4 @@ namespace API.Data
|
|||
return await _context.ServerSetting.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,13 @@ using API.Constants;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class UserRepository : IUserRepository
|
||||
{
|
||||
|
@ -54,10 +55,36 @@ namespace API.Data
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AppUser by id. Returns back Progress information.
|
||||
/// This fetches the Id for a user. Use whenever you just need an ID.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetUserIdByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Where(x => x.UserName == username)
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AppUser by username. Returns back Reading List and their Items.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserWithReadingListsByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Include(u => u.ReadingLists)
|
||||
.ThenInclude(l => l.Items)
|
||||
.SingleOrDefaultAsync(x => x.UserName == username);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AppUser by id. Returns back Progress information.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserByIdAsync(int id)
|
||||
{
|
||||
return await _context.Users
|
|
@ -4,11 +4,12 @@ using System.Threading.Tasks;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
public class VolumeRepository : IVolumeRepository
|
||||
{
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Data.Repositories;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Repositories;
|
||||
|
@ -32,6 +33,7 @@ namespace API.Data
|
|||
public ICollectionTagRepository CollectionTagRepository => new CollectionTagRepository(_context, _mapper);
|
||||
public IFileRepository FileRepository => new FileRepository(_context);
|
||||
public IChapterRepository ChapterRepository => new ChapterRepository(_context);
|
||||
public IReadingListRepository ReadingListRepository => new ReadingListRepository(_context, _mapper);
|
||||
|
||||
/// <summary>
|
||||
/// Commits changes to the DB. Completes the open transaction.
|
||||
|
@ -39,7 +41,6 @@ namespace API.Data
|
|||
/// <returns></returns>
|
||||
public bool Commit()
|
||||
{
|
||||
|
||||
return _context.SaveChanges() > 0;
|
||||
}
|
||||
/// <summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue