testade calibre

This commit is contained in:
florpan 2025-09-05 17:23:24 +02:00
parent ecd0d5a3d2
commit ec787e80e4
8 changed files with 886 additions and 0 deletions

View File

@ -14,6 +14,8 @@
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
<Version>9.*-*</Version>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,44 @@
using Bibblan.Models;
using Bibblan.ViewModels;
namespace Bibblan.Business.Services
{
public class CalibreService
{
CalibreContext _context;
public CalibreService(CalibreContext context)
{
_context = context;
}
public IQueryable<BookVm> GetAllBooks()
{
return from b in _context.Books
join ba in _context.BooksAuthorsLink on b.Id equals ba.Book
join a in _context.Authors on ba.Author equals a.Id
join comment in _context.Comments on b.Id equals comment.Book into comments
from comment in comments.DefaultIfEmpty()
join bl in _context.BooksLanguagesLink on b.Id equals bl.Book
join l in _context.Languages on bl.LangCode equals l.Id
join bs in _context.BooksSeriesLink on b.Id equals bs.Book into book_series_link
from bs in book_series_link.DefaultIfEmpty()
join s in _context.Series on bs.Series equals s.Id into series
from s in series.DefaultIfEmpty()
orderby b.Title
select new BookVm
{
Id = b.Id,
Title = b.Title,
AuthorId = a.Id,
Author = a.Sort,
Comments = comment.Text,
Language = l.LangCode,
Path = b.Path,
HasCover = b.HasCover == "1",
Formats = (from d in _context.Data where d.Book == b.Id orderby d.Format select new DataVm { Id = d.Id, Format = d.Format, FileName = d.Name + "." + d.Format.ToLower() }).ToList(),
SeriesName = s.Name,
SeriesNumber = b.SeriesIndex
};
}
}
}

View File

@ -0,0 +1,23 @@
using Bibblan.Business.Services;
using Bibblan.Models;
using Microsoft.AspNetCore.Mvc;
namespace Bibblan.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CalibreController : ControllerBase
{
CalibreService service;
public CalibreController(CalibreContext db) {
service = new CalibreService(db);
}
[HttpGet("books")]
public IActionResult GetBooks()
{
var books = service.GetAllBooks().Take(100).ToList();
return Ok(books);
}
}
}

View File

@ -0,0 +1,765 @@
using Microsoft.EntityFrameworkCore;
namespace Bibblan.Models
{
public partial class Authors
{
public long Id { get; set; }
public string Name { get; set; }
public string Sort { get; set; }
public string Link { get; set; }
}
public partial class Books
{
public long Id { get; set; }
public string Title { get; set; }
public string Sort { get; set; }
public string Timestamp { get; set; }
public string Pubdate { get; set; }
public double SeriesIndex { get; set; }
public string AuthorSort { get; set; }
public string Isbn { get; set; }
public string Lccn { get; set; }
public string Path { get; set; }
public long Flags { get; set; }
public string Uuid { get; set; }
public string HasCover { get; set; }
public string LastModified { get; set; }
}
public partial class BooksAuthorsLink
{
public long Id { get; set; }
public long Book { get; set; }
public long Author { get; set; }
}
public partial class BooksLanguagesLink
{
public long Id { get; set; }
public long Book { get; set; }
public long LangCode { get; set; }
public long ItemOrder { get; set; }
}
public partial class BooksPluginData
{
public long Id { get; set; }
public long Book { get; set; }
public string Name { get; set; }
public string Val { get; set; }
}
public partial class BooksPublishersLink
{
public long Id { get; set; }
public long Book { get; set; }
public long Publisher { get; set; }
}
public partial class BooksRatingsLink
{
public long Id { get; set; }
public long Book { get; set; }
public long Rating { get; set; }
}
public partial class BooksSeriesLink
{
public long Id { get; set; }
public long Book { get; set; }
public long Series { get; set; }
}
public partial class BooksTagsLink
{
public long Id { get; set; }
public long Book { get; set; }
public long Tag { get; set; }
}
public partial class Comments
{
public long Id { get; set; }
public long Book { get; set; }
public string Text { get; set; }
}
public partial class ConversionOptions
{
public long Id { get; set; }
public string Format { get; set; }
public long? Book { get; set; }
public byte[] Data { get; set; }
}
public partial class CustomColumns
{
public long Id { get; set; }
public string Label { get; set; }
public string Name { get; set; }
public string Datatype { get; set; }
public string MarkForDelete { get; set; }
public string Editable { get; set; }
public string Display { get; set; }
public string IsMultiple { get; set; }
public string Normalized { get; set; }
}
public partial class Data
{
public long Id { get; set; }
public long Book { get; set; }
public string Format { get; set; }
public long UncompressedSize { get; set; }
public string Name { get; set; }
}
public partial class Feeds
{
public long Id { get; set; }
public string Title { get; set; }
public string Script { get; set; }
}
public partial class Identifiers
{
public long Id { get; set; }
public long Book { get; set; }
public string Type { get; set; }
public string Val { get; set; }
}
public partial class Languages
{
public long Id { get; set; }
public string LangCode { get; set; }
}
public partial class LibraryId
{
public long Id { get; set; }
public string Uuid { get; set; }
}
public partial class MetadataDirtied
{
public long Id { get; set; }
public long Book { get; set; }
}
public partial class Preferences
{
public long Id { get; set; }
public string Key { get; set; }
public string Val { get; set; }
}
public partial class Publishers
{
public long Id { get; set; }
public string Name { get; set; }
public string Sort { get; set; }
}
public partial class Ratings
{
public long Id { get; set; }
public long? Rating { get; set; }
}
public partial class Series
{
public long Id { get; set; }
public string Name { get; set; }
public string Sort { get; set; }
}
public partial class Tags
{
public long Id { get; set; }
public string Name { get; set; }
}
public partial class CalibreContext : DbContext
{
public virtual DbSet<Authors> Authors { get; set; }
public virtual DbSet<Books> Books { get; set; }
public virtual DbSet<BooksAuthorsLink> BooksAuthorsLink { get; set; }
public virtual DbSet<BooksLanguagesLink> BooksLanguagesLink { get; set; }
public virtual DbSet<BooksPluginData> BooksPluginData { get; set; }
public virtual DbSet<BooksPublishersLink> BooksPublishersLink { get; set; }
public virtual DbSet<BooksRatingsLink> BooksRatingsLink { get; set; }
public virtual DbSet<BooksSeriesLink> BooksSeriesLink { get; set; }
public virtual DbSet<BooksTagsLink> BooksTagsLink { get; set; }
public virtual DbSet<Comments> Comments { get; set; }
public virtual DbSet<ConversionOptions> ConversionOptions { get; set; }
public virtual DbSet<CustomColumns> CustomColumns { get; set; }
public virtual DbSet<Data> Data { get; set; }
public virtual DbSet<Feeds> Feeds { get; set; }
public virtual DbSet<Identifiers> Identifiers { get; set; }
public virtual DbSet<Languages> Languages { get; set; }
public virtual DbSet<LibraryId> LibraryId { get; set; }
public virtual DbSet<MetadataDirtied> MetadataDirtied { get; set; }
public virtual DbSet<Preferences> Preferences { get; set; }
public virtual DbSet<Publishers> Publishers { get; set; }
public virtual DbSet<Ratings> Ratings { get; set; }
public virtual DbSet<Series> Series { get; set; }
public virtual DbSet<Tags> Tags { get; set; }
public CalibreContext(DbContextOptions<CalibreContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Authors>(entity =>
{
entity.ToTable("authors");
entity.HasIndex(e => e.Name)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Link)
.IsRequired()
.HasColumnName("link")
.HasDefaultValueSql("\"\"");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.Sort).HasColumnName("sort");
});
modelBuilder.Entity<Books>(entity =>
{
entity.ToTable("books");
entity.HasIndex(e => e.AuthorSort)
.HasDatabaseName("authors_idx");
entity.HasIndex(e => e.Sort)
.HasDatabaseName("books_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.AuthorSort).HasColumnName("author_sort");
entity.Property(e => e.Flags)
.HasColumnName("flags")
.HasDefaultValueSql("1");
entity.Property(e => e.HasCover)
.HasColumnName("has_cover")
.HasColumnType("BOOL")
.HasDefaultValueSql("0");
entity.Property(e => e.Isbn)
.HasColumnName("isbn")
.HasDefaultValueSql("\"\"");
entity.Property(e => e.LastModified)
.IsRequired()
.HasColumnName("last_modified")
.HasColumnType("TIMESTAMP")
.HasDefaultValueSql("\"2000-01-01 00:00:00+00:00\"");
entity.Property(e => e.Lccn)
.HasColumnName("lccn")
.HasDefaultValueSql("\"\"");
entity.Property(e => e.Path)
.IsRequired()
.HasColumnName("path")
.HasDefaultValueSql("\"\"");
entity.Property(e => e.Pubdate)
.HasColumnName("pubdate")
.HasColumnType("TIMESTAMP")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
entity.Property(e => e.SeriesIndex)
.HasColumnName("series_index")
.HasDefaultValueSql("1.0");
entity.Property(e => e.Sort).HasColumnName("sort");
entity.Property(e => e.Timestamp)
.HasColumnName("timestamp")
.HasColumnType("TIMESTAMP")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
entity.Property(e => e.Title)
.IsRequired()
.HasColumnName("title")
.HasDefaultValueSql("'Unknown'");
entity.Property(e => e.Uuid).HasColumnName("uuid");
});
modelBuilder.Entity<BooksAuthorsLink>(entity =>
{
entity.ToTable("books_authors_link");
entity.HasIndex(e => e.Author)
.HasDatabaseName("books_authors_link_aidx");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_authors_link_bidx");
entity.HasIndex(e => new { e.Book, e.Author })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Author).HasColumnName("author");
entity.Property(e => e.Book).HasColumnName("book");
});
modelBuilder.Entity<BooksLanguagesLink>(entity =>
{
entity.ToTable("books_languages_link");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_languages_link_bidx");
entity.HasIndex(e => e.LangCode)
.HasDatabaseName("books_languages_link_aidx");
entity.HasIndex(e => new { e.Book, e.LangCode })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.ItemOrder)
.HasColumnName("item_order")
.HasDefaultValueSql("0");
entity.Property(e => e.LangCode).HasColumnName("lang_code");
});
modelBuilder.Entity<BooksPluginData>(entity =>
{
entity.ToTable("books_plugin_data");
entity.HasIndex(e => new { e.Book, e.Name })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.Val)
.IsRequired()
.HasColumnName("val");
});
modelBuilder.Entity<BooksPublishersLink>(entity =>
{
entity.ToTable("books_publishers_link");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_publishers_link_bidx");
entity.HasIndex(e => e.Publisher)
.HasDatabaseName("books_publishers_link_aidx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Publisher).HasColumnName("publisher");
});
modelBuilder.Entity<BooksRatingsLink>(entity =>
{
entity.ToTable("books_ratings_link");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_ratings_link_bidx");
entity.HasIndex(e => e.Rating)
.HasDatabaseName("books_ratings_link_aidx");
entity.HasIndex(e => new { e.Book, e.Rating })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Rating).HasColumnName("rating");
});
modelBuilder.Entity<BooksSeriesLink>(entity =>
{
entity.ToTable("books_series_link");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_series_link_bidx");
entity.HasIndex(e => e.Series)
.HasDatabaseName("books_series_link_aidx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Series).HasColumnName("series");
});
modelBuilder.Entity<BooksTagsLink>(entity =>
{
entity.ToTable("books_tags_link");
entity.HasIndex(e => e.Book)
.HasDatabaseName("books_tags_link_bidx");
entity.HasIndex(e => e.Tag)
.HasDatabaseName("books_tags_link_aidx");
entity.HasIndex(e => new { e.Book, e.Tag })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Tag).HasColumnName("tag");
});
modelBuilder.Entity<Comments>(entity =>
{
entity.ToTable("comments");
entity.HasIndex(e => e.Book)
.HasDatabaseName("comments_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Text)
.IsRequired()
.HasColumnName("text");
});
modelBuilder.Entity<ConversionOptions>(entity =>
{
entity.ToTable("conversion_options");
entity.HasIndex(e => e.Book)
.HasDatabaseName("conversion_options_idx_b");
entity.HasIndex(e => e.Format)
.HasDatabaseName("conversion_options_idx_a");
entity.HasIndex(e => new { e.Format, e.Book })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Data)
.IsRequired()
.HasColumnName("data");
entity.Property(e => e.Format)
.IsRequired()
.HasColumnName("format");
});
modelBuilder.Entity<CustomColumns>(entity =>
{
entity.ToTable("custom_columns");
entity.HasIndex(e => e.Label)
.HasDatabaseName("custom_columns_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Datatype)
.IsRequired()
.HasColumnName("datatype");
entity.Property(e => e.Display)
.IsRequired()
.HasColumnName("display")
.HasDefaultValueSql("\"{}\"");
entity.Property(e => e.Editable)
.IsRequired()
.HasColumnName("editable")
.HasColumnType("BOOL")
.HasDefaultValueSql("1");
entity.Property(e => e.IsMultiple)
.IsRequired()
.HasColumnName("is_multiple")
.HasColumnType("BOOL")
.HasDefaultValueSql("0");
entity.Property(e => e.Label)
.IsRequired()
.HasColumnName("label");
entity.Property(e => e.MarkForDelete)
.IsRequired()
.HasColumnName("mark_for_delete")
.HasColumnType("BOOL")
.HasDefaultValueSql("0");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.Normalized)
.IsRequired()
.HasColumnName("normalized")
.HasColumnType("BOOL");
});
modelBuilder.Entity<Data>(entity =>
{
entity.ToTable("data");
entity.HasIndex(e => e.Book)
.HasDatabaseName("data_idx");
entity.HasIndex(e => e.Format)
.HasDatabaseName("formats_idx");
entity.HasIndex(e => new { e.Book, e.Format })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Format)
.IsRequired()
.HasColumnName("format");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.UncompressedSize).HasColumnName("uncompressed_size");
});
modelBuilder.Entity<Feeds>(entity =>
{
entity.ToTable("feeds");
entity.HasIndex(e => e.Title)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Script)
.IsRequired()
.HasColumnName("script");
entity.Property(e => e.Title)
.IsRequired()
.HasColumnName("title");
});
modelBuilder.Entity<Identifiers>(entity =>
{
entity.ToTable("identifiers");
entity.HasIndex(e => new { e.Book, e.Type })
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
entity.Property(e => e.Type)
.IsRequired()
.HasColumnName("type")
.HasDefaultValueSql("\"isbn\"");
entity.Property(e => e.Val)
.IsRequired()
.HasColumnName("val");
});
modelBuilder.Entity<Languages>(entity =>
{
entity.ToTable("languages");
entity.HasIndex(e => e.LangCode)
.HasDatabaseName("languages_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.LangCode)
.IsRequired()
.HasColumnName("lang_code");
});
modelBuilder.Entity<LibraryId>(entity =>
{
entity.ToTable("library_id");
entity.HasIndex(e => e.Uuid)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Uuid)
.IsRequired()
.HasColumnName("uuid");
});
modelBuilder.Entity<MetadataDirtied>(entity =>
{
entity.ToTable("metadata_dirtied");
entity.HasIndex(e => e.Book)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Book).HasColumnName("book");
});
modelBuilder.Entity<Preferences>(entity =>
{
entity.ToTable("preferences");
entity.HasIndex(e => e.Key)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Key)
.IsRequired()
.HasColumnName("key");
entity.Property(e => e.Val)
.IsRequired()
.HasColumnName("val");
});
modelBuilder.Entity<Publishers>(entity =>
{
entity.ToTable("publishers");
entity.HasIndex(e => e.Name)
.HasDatabaseName("publishers_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.Sort).HasColumnName("sort");
});
modelBuilder.Entity<Ratings>(entity =>
{
entity.ToTable("ratings");
entity.HasIndex(e => e.Rating)
.IsUnique();
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Rating).HasColumnName("rating");
});
modelBuilder.Entity<Series>(entity =>
{
entity.ToTable("series");
entity.HasIndex(e => e.Name)
.HasDatabaseName("series_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
entity.Property(e => e.Sort).HasColumnName("sort");
});
modelBuilder.Entity<Tags>(entity =>
{
entity.ToTable("tags");
entity.HasIndex(e => e.Name)
.HasDatabaseName("tags_idx");
entity.Property(e => e.Id)
.HasColumnName("id")
.ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name");
});
}
}
}

View File

@ -1,3 +1,6 @@
using Bibblan.Models;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@ -6,6 +9,10 @@ builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var db = "metadata.db";
var connection = $"Data Source={db};Mode=ReadOnly;";
builder.Services.AddDbContext<CalibreContext>(options => options.UseSqlite(connection));
var app = builder.Build();
app.UseDefaultFiles();

View File

@ -0,0 +1,9 @@
namespace Bibblan.ViewModels
{
public class AuthorVm
{
public long Id { get; set; }
public string Name { get; set; }
public int BookCount { get; set; }
}
}

View File

@ -0,0 +1,27 @@
using Bibblan.Models;
namespace Bibblan.ViewModels
{
public class BookVm
{
public long Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public long AuthorId { get; set; }
public string Comments { get; set; }
public string Language { get; set; }
public string Path { get; set; }
public List<DataVm> Formats { get; set; }
public bool HasCover { get; set; }
public string SeriesName { get; set; }
public double SeriesNumber { get; set; }
public string TitleAndSeries
{
get
{
return Title + (!string.IsNullOrWhiteSpace(SeriesName) ? $" ({SeriesName + (SeriesNumber > 0 ? $" {SeriesNumber:##}" : string.Empty)})" : string.Empty);
}
}
}
}

View File

@ -0,0 +1,9 @@
namespace Bibblan.ViewModels
{
public class DataVm
{
public long Id { get; set; }
public string Format { get; set; }
public string FileName { get; set; }
}
}