Send To Device Support (#1557)

* Tweaked the logging output

* Started implementing some basic idea for devices

* Updated Email Service with new API routes

* Implemented basic DB structure and some APIs to prep for the UI and flows.

* Added an abstract class to make Unit testing easier.

* Removed dependency we don't need

* Updated the UI to be able to show devices and add new devices. Email field will update the platform if the user hasn't interacted with it already.

* Added ability to delete a device as well

* Basic ability to send files to devices works

* Refactored Action code to pass ActionItem back and allow for dynamic children based on an Observable (api).

Hooked in ability to send a chapter to a device. There is no logic in the FE to validate type.

* Fixed a broken unit test

* Implemented the ability to edit a device

* Code cleanup

* Fixed a bad success message

* Fixed broken unit test from updating mock layer
This commit is contained in:
Joseph Milazzo 2022-09-23 17:41:29 -05:00 committed by GitHub
parent ab0f13ef74
commit 9d7476a367
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 3026 additions and 157 deletions

View file

@ -44,6 +44,7 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
public DbSet<SiteTheme> SiteTheme { get; set; }
public DbSet<SeriesRelation> SeriesRelation { get; set; }
public DbSet<FolderPath> FolderPath { get; set; }
public DbSet<Device> Device { get; set; }
protected override void OnModelCreating(ModelBuilder builder)

View file

@ -162,7 +162,15 @@ public static class DbFactory
FilePath = filePath,
Format = format,
Pages = pages,
LastModified = File.GetLastWriteTime(filePath) // NOTE: Changed this from DateTime.Now
LastModified = File.GetLastWriteTime(filePath)
};
}
public static Device Device(string name)
{
return new Device()
{
Name = name,
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
public partial class DeviceSupport : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SeriesRelation_Series_TargetSeriesId",
table: "SeriesRelation");
migrationBuilder.CreateTable(
name: "Device",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
IpAddress = table.Column<string>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
EmailAddress = table.Column<string>(type: "TEXT", nullable: true),
Platform = table.Column<int>(type: "INTEGER", nullable: false),
AppUserId = table.Column<int>(type: "INTEGER", nullable: false),
LastUsed = table.Column<DateTime>(type: "TEXT", nullable: false),
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Device", x => x.Id);
table.ForeignKey(
name: "FK_Device_AspNetUsers_AppUserId",
column: x => x.AppUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Device_AppUserId",
table: "Device",
column: "AppUserId");
migrationBuilder.AddForeignKey(
name: "FK_SeriesRelation_Series_TargetSeriesId",
table: "SeriesRelation",
column: "TargetSeriesId",
principalTable: "Series",
principalColumn: "Id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SeriesRelation_Series_TargetSeriesId",
table: "SeriesRelation");
migrationBuilder.DropTable(
name: "Device");
migrationBuilder.AddForeignKey(
name: "FK_SeriesRelation_Series_TargetSeriesId",
table: "SeriesRelation",
column: "TargetSeriesId",
principalTable: "Series",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -15,7 +15,7 @@ namespace API.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("API.Entities.AppRole", b =>
{
@ -442,6 +442,43 @@ namespace API.Data.Migrations
b.ToTable("CollectionTag");
});
modelBuilder.Entity("API.Entities.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AppUserId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("EmailAddress")
.HasColumnType("TEXT");
b.Property<string>("IpAddress")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<DateTime>("LastUsed")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Platform")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("AppUserId");
b.ToTable("Device");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.Property<int>("Id")
@ -1262,6 +1299,17 @@ namespace API.Data.Migrations
b.Navigation("Volume");
});
modelBuilder.Entity("API.Entities.Device", b =>
{
b.HasOne("API.Entities.AppUser", "AppUser")
.WithMany("Devices")
.HasForeignKey("AppUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AppUser");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.HasOne("API.Entities.Library", "Library")
@ -1306,7 +1354,7 @@ namespace API.Data.Migrations
b.HasOne("API.Entities.Series", "TargetSeries")
.WithMany("RelationOf")
.HasForeignKey("TargetSeriesId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.ClientCascade)
.IsRequired();
b.Navigation("Series");
@ -1551,6 +1599,8 @@ namespace API.Data.Migrations
{
b.Navigation("Bookmarks");
b.Navigation("Devices");
b.Navigation("Progresses");
b.Navigation("Ratings");

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs.Device;
using API.Entities;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
public interface IDeviceRepository
{
void Update(Device device);
Task<IEnumerable<DeviceDto>> GetDevicesForUserAsync(int userId);
Task<Device> GetDeviceById(int deviceId);
}
public class DeviceRepository : IDeviceRepository
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public DeviceRepository(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public void Update(Device device)
{
_context.Entry(device).State = EntityState.Modified;
}
public async Task<IEnumerable<DeviceDto>> GetDevicesForUserAsync(int userId)
{
return await _context.Device
.Where(d => d.AppUserId == userId)
.OrderBy(d => d.LastUsed)
.ProjectTo<DeviceDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task<Device> GetDeviceById(int deviceId)
{
return await _context.Device
.Where(d => d.Id == deviceId)
.SingleOrDefaultAsync();
}
}

View file

@ -27,6 +27,7 @@ public enum AppUserIncludes
UserPreferences = 32,
WantToRead = 64,
ReadingListsWithItems = 128,
Devices = 256,
}
@ -194,6 +195,11 @@ public class UserRepository : IUserRepository
query = query.Include(u => u.WantToRead);
}
if (includeFlags.HasFlag(AppUserIncludes.Devices))
{
query = query.Include(u => u.Devices);
}
return query;

View file

@ -24,6 +24,7 @@ public interface IUnitOfWork
ITagRepository TagRepository { get; }
ISiteThemeRepository SiteThemeRepository { get; }
IMangaFileRepository MangaFileRepository { get; }
IDeviceRepository DeviceRepository { get; }
bool Commit();
Task<bool> CommitAsync();
bool HasChanges();
@ -60,6 +61,7 @@ public class UnitOfWork : IUnitOfWork
public ITagRepository TagRepository => new TagRepository(_context, _mapper);
public ISiteThemeRepository SiteThemeRepository => new SiteThemeRepository(_context, _mapper);
public IMangaFileRepository MangaFileRepository => new MangaFileRepository(_context, _mapper);
public IDeviceRepository DeviceRepository => new DeviceRepository(_context, _mapper);
/// <summary>
/// Commits changes to the DB. Completes the open transaction.