Compare commits
14 Commits
ecd0d5a3d2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0af47751cf | |||
| 1e90a68a6e | |||
| 910f500460 | |||
| 6efa8aba8a | |||
| f88c25f117 | |||
| e23822b30c | |||
| 01e341fce0 | |||
| 8c54a120d1 | |||
| 1744a5b7de | |||
| 5ae7c7f47b | |||
| 2ffeb53e32 | |||
| 42ac0a99e8 | |||
| ec787e80e4 | |||
|
596fe5159a
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ Server/.vs
|
||||
Server/bin
|
||||
Server/obj
|
||||
Server/Bibblan.csproj.user
|
||||
Server/appsettings.user.json
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||
<title>Solid App</title>
|
||||
<link href="/src/styles/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render } from "solid-js/web";
|
||||
import App from "./src/app";
|
||||
import App from "./src/App";
|
||||
|
||||
render(() => <App />, document.getElementById("root")!);
|
||||
|
||||
1876
Frontend/package-lock.json
generated
Normal file
1876
Frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,23 @@
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.1",
|
||||
"sass-embedded": "^1.92.0",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.4",
|
||||
"vite-plugin-solid": "^2.11.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"bootstrap": "^5.3.8",
|
||||
"solid-bootstrap": "^1.0.21",
|
||||
"solid-js": "^1.9.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,61 @@
|
||||
import { Router, Route } from "@solidjs/router";
|
||||
import type { Component } from "solid-js";
|
||||
import { createSignal, For, onMount } from "solid-js";
|
||||
|
||||
interface Weather {
|
||||
date: string;
|
||||
temperatureC: number;
|
||||
temperatureF: number;
|
||||
summary: string;
|
||||
}
|
||||
import Home from "./components/Home";
|
||||
import BookList from "./components/BookList";
|
||||
import AuthorList from "./components/AuthorList";
|
||||
import SeriesList from "./components/SeriesList";
|
||||
import BookDetail from "./components/BookDetail";
|
||||
|
||||
const App: Component = () => {
|
||||
const [weather, setWeather] = createSignal<Weather[]>([]);
|
||||
onMount(() => {
|
||||
fetch("/api/weatherforecast")
|
||||
.then((r) => r.json())
|
||||
.then(setWeather);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<header>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
href="https://github.com/solidjs/solid"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn Solid
|
||||
</a>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">
|
||||
Navbar
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNavAltMarkup"
|
||||
aria-controls="navbarNavAltMarkup"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link active" href="/">
|
||||
Home
|
||||
</a>
|
||||
<a class="nav-link" href="/books">
|
||||
Books
|
||||
</a>
|
||||
<a class="nav-link" href="/authors">
|
||||
Authors
|
||||
</a>
|
||||
<a class="nav-link" href="/series">
|
||||
Series
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<For each={weather()}>
|
||||
{(item) => (
|
||||
<li>
|
||||
{item.date} - {item.temperatureC}°C / {item.temperatureF}°F -{" "}
|
||||
{item.summary}
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
<main class="container">
|
||||
<Router>
|
||||
<Route path="/books/author/:authorid" component={BookList} />
|
||||
<Route path="/books/:id" component={BookDetail} />
|
||||
<Route path="/books" component={BookList} />
|
||||
<Route path="/authors" component={AuthorList} />
|
||||
<Route path="/series" component={SeriesList} />
|
||||
<Route path="/" component={Home} />
|
||||
</Router>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
32
Frontend/src/components/AuthorCard.tsx
Normal file
32
Frontend/src/components/AuthorCard.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Show, type Component } from "solid-js";
|
||||
import { Author } from "../types/types";
|
||||
import { Card } from "solid-bootstrap";
|
||||
|
||||
const AuthorCard: Component<{ author: Author }> = (props: {
|
||||
author: Author;
|
||||
}) => {
|
||||
return (
|
||||
<Card class="book-card col-lg-2 col-md-3 col-sm-4">
|
||||
<div class="card-img-top missing">?</div>
|
||||
<div
|
||||
class="card-img-top author"
|
||||
style={{
|
||||
"background-image":
|
||||
'url("/api/bibblan/authorcover/' +
|
||||
encodeURIComponent(props.author.id) +
|
||||
'")',
|
||||
}}
|
||||
></div>
|
||||
<Card.Body>
|
||||
<Card.Title>{props.author.name}</Card.Title>
|
||||
<Card.Subtitle>
|
||||
<a href={`/books/author/${props.author.id}`}>
|
||||
{props.author.bookCount} books
|
||||
</a>
|
||||
</Card.Subtitle>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthorCard;
|
||||
35
Frontend/src/components/AuthorList.tsx
Normal file
35
Frontend/src/components/AuthorList.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createSignal, onMount, For, type Component } from "solid-js";
|
||||
import AuthorCard from "./AuthorCard";
|
||||
import BibblanService from "../services/bibblanservice";
|
||||
import { Author } from "../types/types";
|
||||
|
||||
const BookList: Component = () => {
|
||||
const [authors, setAuthors] = createSignal<Author[]>([]);
|
||||
const [query, setQuery] = createSignal("");
|
||||
|
||||
const update = (query: string) => {
|
||||
setQuery(query);
|
||||
BibblanService.getAuthors(query).then(setAuthors);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
BibblanService.getAuthors().then(setAuthors);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Authors!</h1>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mb-3"
|
||||
placeholder="Search..."
|
||||
onInput={(e) => update(e.currentTarget.value)}
|
||||
/>
|
||||
<div class="book-grid d-flex flex-wrap justify-content-between gap-3 p-3">
|
||||
<For each={authors()}>{(item) => <AuthorCard author={item} />}</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookList;
|
||||
38
Frontend/src/components/BookCard.tsx
Normal file
38
Frontend/src/components/BookCard.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Show, createSignal, type Component } from "solid-js";
|
||||
import { Book } from "../types/types";
|
||||
import { Card, Modal } from "solid-bootstrap";
|
||||
|
||||
const BookCard: Component<{ book: Book }> = (props: { book: Book }) => {
|
||||
const [show, setShow] = createSignal(false);
|
||||
|
||||
function handleClick(): void {
|
||||
setShow(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card class="book-card col-lg-2 col-md-3 col-sm-4">
|
||||
<div class="card-img-top missing">?</div>
|
||||
<Show when={true || props.book.hasCover}>
|
||||
<div
|
||||
class="card-img-top"
|
||||
style={{
|
||||
"background-image":
|
||||
'url("/api/calibre/cover?path=' +
|
||||
encodeURIComponent(props.book.path) +
|
||||
'")',
|
||||
}}
|
||||
></div>
|
||||
</Show>
|
||||
<Card.Body>
|
||||
<Card.Title>{props.book.title}</Card.Title>
|
||||
<Card.Subtitle>{props.book.author}</Card.Subtitle>
|
||||
<Card.Text>
|
||||
Series: {props.book.seriesName} {props.book.seriesNumber}
|
||||
</Card.Text>
|
||||
<a href={`/books/${props.book.id}`} class="stretched-link"></a>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookCard;
|
||||
31
Frontend/src/components/BookDetail.tsx
Normal file
31
Frontend/src/components/BookDetail.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createSignal, onMount, For, Show, type Component } from "solid-js";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import BibblanService from "../services/bibblanservice";
|
||||
import { type BookDetail as BD } from "../types/types";
|
||||
|
||||
const BookDetail: Component = () => {
|
||||
const [detail, setDetail] = createSignal<BD>({} as BD);
|
||||
const params = useParams();
|
||||
|
||||
onMount(() => {
|
||||
BibblanService.getBook(params.id).then((b) => {
|
||||
console.log("detail", b);
|
||||
setDetail(b);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Book detail - {detail()?.book?.title}</h1>
|
||||
<Show when={detail()?.book?.id !== undefined}>
|
||||
<br />
|
||||
<pre>
|
||||
<code>{JSON.stringify(detail(), null, 2)}</code>
|
||||
</pre>
|
||||
<br />
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookDetail;
|
||||
39
Frontend/src/components/BookList.tsx
Normal file
39
Frontend/src/components/BookList.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createSignal, onMount, For, Show, type Component } from "solid-js";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import BookCard from "./BookCard";
|
||||
import BibblanService from "../services/bibblanservice";
|
||||
import { Book } from "../types/types";
|
||||
|
||||
const BookList: Component = () => {
|
||||
const [books, setBooks] = createSignal<Book[]>([]);
|
||||
const [query, setQuery] = createSignal("");
|
||||
const params = useParams();
|
||||
|
||||
const update = (query: string) => {
|
||||
setQuery(query);
|
||||
BibblanService.getBooks(params.authorid, query).then(setBooks);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
BibblanService.getBooks(params.authorid).then(setBooks);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Books!</h1>
|
||||
<Show when={!params.authorid}>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mb-3"
|
||||
placeholder="Search..."
|
||||
onInput={(e) => update(e.currentTarget.value)}
|
||||
/>
|
||||
</Show>
|
||||
<div class="book-grid d-flex flex-wrap justify-content-between gap-3 p-3">
|
||||
<For each={books()}>{(item) => <BookCard book={item} />}</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookList;
|
||||
6
Frontend/src/components/Home.tsx
Normal file
6
Frontend/src/components/Home.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type Component } from "solid-js";
|
||||
const Home: Component = () => {
|
||||
return <h1>Home!</h1>;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
139
Frontend/src/components/SeriesList.tsx
Normal file
139
Frontend/src/components/SeriesList.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { createSignal, onMount, For, type Component, Show } from "solid-js";
|
||||
import BookCard from "./BookCard";
|
||||
import BibblanService from "../services/bibblanservice";
|
||||
import { Series, ListBook, Book } from "../types/types";
|
||||
import { OverlayTrigger, Popover, Button } from "solid-bootstrap";
|
||||
const SeriesList: Component = () => {
|
||||
const [series, setSeries] = createSignal<Series[]>([]);
|
||||
const [query, setQuery] = createSignal("");
|
||||
|
||||
const update = (query: string) => {
|
||||
setQuery(query);
|
||||
BibblanService.getSeries(query).then(setSeries);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
update(query());
|
||||
});
|
||||
|
||||
const distinctAuthors = (
|
||||
books: ListBook[]
|
||||
): { id: number; name: string }[] => {
|
||||
return [
|
||||
...new Set(
|
||||
books.map((b) => JSON.stringify({ id: b.authorId, name: b.authorName }))
|
||||
),
|
||||
].map((x) => JSON.parse(x)) as { id: number; name: string }[];
|
||||
};
|
||||
|
||||
const minDate = (books: ListBook[]): string => {
|
||||
if (books.length === 0) return "";
|
||||
const darr = books.map((b) => new Date(b.pubDate));
|
||||
const d = darr.reduce((min, b) => (b < min ? b : min), darr[0]);
|
||||
return d?.toISOString().substr(0, 10) ?? "";
|
||||
};
|
||||
|
||||
const maxDate = (books: ListBook[]): string => {
|
||||
if (books.length === 0) return "";
|
||||
const darr = books.map((b) => new Date(b.pubDate));
|
||||
const d = darr.reduce((max, b) => (b > max ? b : max), darr[0]);
|
||||
return d?.toISOString().substr(0, 10) ?? "";
|
||||
};
|
||||
|
||||
function extendBook(item: ListBook, seriesName: string): Book {
|
||||
return {
|
||||
...item,
|
||||
|
||||
author: item.authorName,
|
||||
comments: "",
|
||||
|
||||
language: "",
|
||||
path: item.path,
|
||||
hasCover: item.hasCover,
|
||||
formats: [],
|
||||
seriesName,
|
||||
seriesNumber: item.seriesIndex,
|
||||
};
|
||||
}
|
||||
|
||||
function orderBySeriesIndex(books: ListBook[]): ListBook[] {
|
||||
return [...books].sort((a, b) => {
|
||||
const indexA = a.seriesIndex ?? 0;
|
||||
const indexB = b.seriesIndex ?? 0;
|
||||
return indexA - indexB;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Series!</h1>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mb-3"
|
||||
placeholder="Search..."
|
||||
onInput={(e) => update(e.currentTarget.value)}
|
||||
/>
|
||||
<table class="table p-3 series-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Author</th>
|
||||
<th>Book Count</th>
|
||||
<th>Published</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={series()}>
|
||||
{(item) => (
|
||||
<>
|
||||
<tr>
|
||||
<td>{item.id}</td>
|
||||
<td>{item.name}</td>
|
||||
<td>
|
||||
<For each={distinctAuthors(item.books)}>
|
||||
{(author) => (
|
||||
<a href={`/books/author/${author.id}`}>{author.name}</a>
|
||||
)}
|
||||
</For>
|
||||
</td>
|
||||
<td>{item.books.length}</td>
|
||||
<td>
|
||||
{minDate(item.books) ?? "N/A"} -{" "}
|
||||
{maxDate(item.books) ?? "N/A"}
|
||||
</td>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
offset={[0, 8]}
|
||||
placement="left"
|
||||
overlay={
|
||||
<Popover id="popover-series" class="shadow">
|
||||
<Popover.Header as="h3">{item.name}</Popover.Header>
|
||||
<Popover.Body>
|
||||
<div class="book-grid d-flex flex-wrap justify-content-between gap-3 p-3">
|
||||
<For each={orderBySeriesIndex(item.books)}>
|
||||
{(b) => (
|
||||
<BookCard book={extendBook(b, item.name)} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Button variant="secondary">Books</Button>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeriesList;
|
||||
47
Frontend/src/services/bibblanservice.ts
Normal file
47
Frontend/src/services/bibblanservice.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Book, Author, Series, BookDetail } from "../types/types";
|
||||
|
||||
const BibblanService = {
|
||||
getBooks: async (
|
||||
authorid: string | undefined = undefined,
|
||||
query: string | undefined = undefined
|
||||
): Promise<Book[]> => {
|
||||
let url = "/api/bibblan/books";
|
||||
if (authorid != undefined) {
|
||||
url += `/author/${authorid}`;
|
||||
} else if (query != undefined && query.length > 0) {
|
||||
url += `?query=${encodeURIComponent(query)}`;
|
||||
}
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
getBook: async (id: string): Promise<BookDetail> => {
|
||||
let url = "/api/bibblan/books/" + id;
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
getAuthors: async (
|
||||
query: string | undefined = undefined
|
||||
): Promise<Author[]> => {
|
||||
let url = "/api/bibblan/authors";
|
||||
if (query != undefined && query.length > 0) {
|
||||
url += `?query=${encodeURIComponent(query)}`;
|
||||
}
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
getSeries: async (
|
||||
query: string | undefined = undefined
|
||||
): Promise<Series[]> => {
|
||||
let url = "/api/bibblan/series";
|
||||
if (query != undefined && query.length > 0) {
|
||||
url += `?query=${encodeURIComponent(query)}`;
|
||||
}
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
|
||||
export default BibblanService;
|
||||
10
Frontend/src/services/calibreservice.ts
Normal file
10
Frontend/src/services/calibreservice.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Book } from "../types/types";
|
||||
|
||||
const CalibreService = {
|
||||
getBooks: async (): Promise<Book[]> => {
|
||||
const response = await fetch("/api/calibre/books");
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
|
||||
export default CalibreService;
|
||||
32
Frontend/src/styles/styles.css
Normal file
32
Frontend/src/styles/styles.css
Normal file
@@ -0,0 +1,32 @@
|
||||
@import "../../node_modules/bootstrap/scss/bootstrap.scss";
|
||||
|
||||
.book-card {
|
||||
.card-img-top {
|
||||
padding: 1rem;
|
||||
aspect-ratio: 30 / 47;
|
||||
background-size: contain;
|
||||
width: calc(100% - 1.6rem);
|
||||
margin-left: 0.8rem;
|
||||
margin-top: 0.8rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
z-index: 1;
|
||||
|
||||
&.missing {
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-size: 7rem;
|
||||
font-weight: 900;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* &.author { } */
|
||||
}
|
||||
}
|
||||
|
||||
#popover-series {
|
||||
--bs-popover-max-width: 80%;
|
||||
}
|
||||
86
Frontend/src/types/types.ts
Normal file
86
Frontend/src/types/types.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export interface Book {
|
||||
id: number;
|
||||
title: string;
|
||||
authorId: number;
|
||||
author: string;
|
||||
comments: string;
|
||||
language: string;
|
||||
path: string;
|
||||
hasCover: boolean;
|
||||
formats: Array<{ id: number; format: string; fileName: string }>;
|
||||
seriesName: string;
|
||||
seriesNumber: number;
|
||||
}
|
||||
|
||||
export interface Author {
|
||||
id: number;
|
||||
name: string;
|
||||
bookCount: number;
|
||||
}
|
||||
|
||||
export interface ListBook {
|
||||
id: number;
|
||||
title: string;
|
||||
authorId: number;
|
||||
authorName: string;
|
||||
path: string;
|
||||
hasCover: boolean;
|
||||
pubDate: Date;
|
||||
seriesIndex: number;
|
||||
}
|
||||
|
||||
export interface Series {
|
||||
id: number;
|
||||
name: string;
|
||||
books: Array<ListBook>;
|
||||
}
|
||||
|
||||
export interface Publisher {
|
||||
id: number;
|
||||
name: string;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Rating {
|
||||
id: number;
|
||||
book: number;
|
||||
rating: number;
|
||||
}
|
||||
|
||||
export interface Language {
|
||||
id: number;
|
||||
book: number;
|
||||
langCode: number;
|
||||
itemOrder: number;
|
||||
}
|
||||
|
||||
export interface BookComment {
|
||||
id: number;
|
||||
book: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface BookData {
|
||||
id: number;
|
||||
book: number;
|
||||
format: string;
|
||||
uncompressedSize: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface BookDetail {
|
||||
book: Book;
|
||||
authors: Author[];
|
||||
publishers: Publisher[];
|
||||
language: Language[];
|
||||
ratings: any[];
|
||||
series: Author[];
|
||||
tags: Tag[];
|
||||
comments: BookComment[];
|
||||
data: BookData[];
|
||||
}
|
||||
@@ -138,6 +138,11 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/runtime@^7.8.7":
|
||||
version "7.28.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
|
||||
integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
|
||||
|
||||
"@babel/template@^7.27.2":
|
||||
version "7.27.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
|
||||
@@ -168,6 +173,11 @@
|
||||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
|
||||
"@bufbuild/protobuf@^2.5.0":
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.7.0.tgz#8944a4575abdc222839f93e90c861a67f1981787"
|
||||
integrity sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA==
|
||||
|
||||
"@esbuild/aix-ppc64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9"
|
||||
@@ -332,6 +342,100 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@parcel/watcher-android-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1"
|
||||
integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==
|
||||
|
||||
"@parcel/watcher-darwin-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67"
|
||||
integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
|
||||
|
||||
"@parcel/watcher-darwin-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8"
|
||||
integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==
|
||||
|
||||
"@parcel/watcher-freebsd-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b"
|
||||
integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1"
|
||||
integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==
|
||||
|
||||
"@parcel/watcher-linux-arm-musl@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e"
|
||||
integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30"
|
||||
integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2"
|
||||
integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e"
|
||||
integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==
|
||||
|
||||
"@parcel/watcher-linux-x64-musl@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee"
|
||||
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==
|
||||
|
||||
"@parcel/watcher-win32-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243"
|
||||
integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==
|
||||
|
||||
"@parcel/watcher-win32-ia32@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6"
|
||||
integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==
|
||||
|
||||
"@parcel/watcher-win32-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947"
|
||||
integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==
|
||||
|
||||
"@parcel/watcher@^2.4.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200"
|
||||
integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
is-glob "^4.0.3"
|
||||
micromatch "^4.0.5"
|
||||
node-addon-api "^7.0.0"
|
||||
optionalDependencies:
|
||||
"@parcel/watcher-android-arm64" "2.5.1"
|
||||
"@parcel/watcher-darwin-arm64" "2.5.1"
|
||||
"@parcel/watcher-darwin-x64" "2.5.1"
|
||||
"@parcel/watcher-freebsd-x64" "2.5.1"
|
||||
"@parcel/watcher-linux-arm-glibc" "2.5.1"
|
||||
"@parcel/watcher-linux-arm-musl" "2.5.1"
|
||||
"@parcel/watcher-linux-arm64-glibc" "2.5.1"
|
||||
"@parcel/watcher-linux-arm64-musl" "2.5.1"
|
||||
"@parcel/watcher-linux-x64-glibc" "2.5.1"
|
||||
"@parcel/watcher-linux-x64-musl" "2.5.1"
|
||||
"@parcel/watcher-win32-arm64" "2.5.1"
|
||||
"@parcel/watcher-win32-ia32" "2.5.1"
|
||||
"@parcel/watcher-win32-x64" "2.5.1"
|
||||
|
||||
"@popperjs/core@^2.10.1":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.50.0":
|
||||
version "4.50.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz#939c1be9625d428d8513e4ab60d406fe8db23718"
|
||||
@@ -437,6 +541,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz#d81efe6a12060c7feddf9805e2a94c3ab0679f48"
|
||||
integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==
|
||||
|
||||
"@solidjs/router@^0.15.3":
|
||||
version "0.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@solidjs/router/-/router-0.15.3.tgz#2c5e7aa637980ab7fce956aedc8cd20614163f2a"
|
||||
integrity sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==
|
||||
|
||||
"@types/babel__core@^7.20.4":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
|
||||
@@ -506,6 +615,18 @@ babel-preset-solid@^1.8.4:
|
||||
dependencies:
|
||||
babel-plugin-jsx-dom-expressions "^0.40.1"
|
||||
|
||||
bootstrap@^5.3.8:
|
||||
version "5.3.8"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed"
|
||||
integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==
|
||||
|
||||
braces@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.24.0:
|
||||
version "4.25.4"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af"
|
||||
@@ -516,6 +637,11 @@ browserslist@^4.24.0:
|
||||
node-releases "^2.0.19"
|
||||
update-browserslist-db "^1.1.3"
|
||||
|
||||
buffer-builder@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-builder/-/buffer-builder-0.2.0.tgz#3322cd307d8296dab1f604618593b261a3fade8f"
|
||||
integrity sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@@ -526,6 +652,18 @@ caniuse-lite@^1.0.30001737:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4"
|
||||
integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==
|
||||
|
||||
chokidar@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
|
||||
integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
|
||||
dependencies:
|
||||
readdirp "^4.0.1"
|
||||
|
||||
colorjs.io@^0.5.0:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.5.2.tgz#63b20139b007591ebc3359932bef84628eb3fcef"
|
||||
integrity sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
@@ -536,7 +674,7 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
csstype@^3.1.0:
|
||||
csstype@^3.0.2, csstype@^3.1.0:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
@@ -548,6 +686,19 @@ debug@^4.1.0, debug@^4.3.1:
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
detect-libc@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
|
||||
|
||||
dom-helpers@^5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
|
||||
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.7"
|
||||
csstype "^3.0.2"
|
||||
|
||||
electron-to-chromium@^1.5.211:
|
||||
version "1.5.214"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz#f7bbdc0796124292d4b8a34a49e968c5e6430763"
|
||||
@@ -600,6 +751,13 @@ fdir@^6.4.4, fdir@^6.5.0:
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
@@ -610,11 +768,38 @@ gensync@^1.0.0-beta.2:
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
html-entities@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
|
||||
integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
|
||||
|
||||
immutable@^5.0.2:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.3.tgz#e6486694c8b76c37c063cca92399fa64098634d4"
|
||||
integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-glob@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
is-what@^4.1.8:
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f"
|
||||
@@ -649,6 +834,14 @@ merge-anything@^5.1.7:
|
||||
dependencies:
|
||||
is-what "^4.1.8"
|
||||
|
||||
micromatch@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
dependencies:
|
||||
braces "^3.0.3"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
@@ -659,6 +852,11 @@ nanoid@^3.3.11:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
node-addon-api@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
|
||||
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
||||
|
||||
node-releases@^2.0.19:
|
||||
version "2.0.19"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
|
||||
@@ -676,6 +874,11 @@ picocolors@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
picomatch@^4.0.2, picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
|
||||
@@ -690,6 +893,11 @@ postcss@^8.5.6:
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
readdirp@^4.0.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
|
||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.50.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.50.0.tgz#6f237f598b7163ede33ce827af8534c929aaa186"
|
||||
@@ -720,6 +928,151 @@ rollup@^4.43.0:
|
||||
"@rollup/rollup-win32-x64-msvc" "4.50.0"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
rxjs@^7.4.0:
|
||||
version "7.8.2"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
|
||||
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
sass-embedded-all-unknown@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.92.0.tgz#73b87468f0c3083c0a181e677730c1df891f3bce"
|
||||
integrity sha512-0VcRBilndf8Iot7zfKKEYH7Ig4JBRjltf7Ba9dNL6wtv0m1a36cm8FgZFofrXtDjUgVTV/aEH/Xw4zBUs6vFYA==
|
||||
dependencies:
|
||||
sass "1.92.0"
|
||||
|
||||
sass-embedded-android-arm64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.92.0.tgz#31bb0e18f9d00da2dbc4ed50625ad5cc8b33777f"
|
||||
integrity sha512-m0JY0QyskN77AFpA8FKxqXZNWSzrPvKOvZqOu1DwEEipyHuSdiAVFDHZ6EpI3aABxCXE3jgkP+Ij2mb4hiLxFw==
|
||||
|
||||
sass-embedded-android-arm@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.92.0.tgz#db635e9ca83b9b98402d7bf7f144484d59282a72"
|
||||
integrity sha512-0NH0zElKL5gLdNcWFzYX/bqtpoFq5ogcU+4vLdmpBXA9Zl5NFPXAPRA6K8pgjQNWpnV7bG05JSIVPuNKZ60Ptg==
|
||||
|
||||
sass-embedded-android-riscv64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.92.0.tgz#e61f1ba5373b41231ce728ab64901494f7ca8d9a"
|
||||
integrity sha512-wfVRf2PFR15vCgJE3SWLZQRo+98xm7vKvpHiaPU4satutwMKC8yXxDvsc7hFBqQYBtqHNK5ap5dZSXlgdGGZrA==
|
||||
|
||||
sass-embedded-android-x64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.92.0.tgz#5602f9dd439022c1257f156cdb589d9731400a85"
|
||||
integrity sha512-bc1c3OMdfrYoiIzVzsMI3KBnIa4mEumg7jHzonkDUIUWrfUNsbzlO+UXX1CcRyfaOIqyKNNZLVvBCz8EwmUsbQ==
|
||||
|
||||
sass-embedded-darwin-arm64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.92.0.tgz#f06c2550be25890fc1e9ead9fed908cff0709c36"
|
||||
integrity sha512-vZh1WCL2QlQyTlAD8snmC8W90XBZI/125o15bfKkGbUzV58dkZJf413hk6JVQS2+a0lZT4GxvrlGH1fSaSNTug==
|
||||
|
||||
sass-embedded-darwin-x64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.92.0.tgz#e2382ff4cd2a38aa13d9d7e0fd4aaa68d1df7433"
|
||||
integrity sha512-4vJsXpOQAgU+KrrW/3POvhnfkG9iJ4gU8ujeEDXqCSSY2M+5B/j0S6iXB7nu3Z9MfmCl8V4B6xyeB0EWE5Ul0g==
|
||||
|
||||
sass-embedded-linux-arm64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.92.0.tgz#17022911a8bcc4ede6c60a90e04fdb48a0fdce83"
|
||||
integrity sha512-HH4LNY1svM2Lv6NCxqOQca42hzG/o55ON9X3T0R18Rl9kVb3y5qiJpdrHh7sSlZWF4qhHYbRc9BIc+Tw142oog==
|
||||
|
||||
sass-embedded-linux-arm@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.92.0.tgz#96b2fbed0165f788a0973dca0d04145212549882"
|
||||
integrity sha512-HMjTDjIT8bHwAVd0c8r8QvGxGZwJg3H06/Y5ZiaiwVGiZtYS9jfef6LnrPw8LY5cOG8wm9RZ9AgVqvkL7E2jBQ==
|
||||
|
||||
sass-embedded-linux-musl-arm64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.92.0.tgz#714e6029f873dac5fce788e6ce0561dc82f25e95"
|
||||
integrity sha512-xYzZDmcPb3BsaD6qlRTqZqtyMOZfGCSKJBZYj2ZRJiKDDr1sqPSIqKx6G8jc1wJAVdvoNp5tzENnCfY7NRkxNA==
|
||||
|
||||
sass-embedded-linux-musl-arm@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.92.0.tgz#5bc5ff27414f5d8242fdf7681ba9ab73b5098c68"
|
||||
integrity sha512-qJDCXm379yRT9+8wKSi6nHFCOODTmD6XmE8rqmMozKo6kvCM+Y3sAMlHrT/0+pfzlGh1JSamkoYIo/ODn+LRVA==
|
||||
|
||||
sass-embedded-linux-musl-riscv64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.92.0.tgz#c4ce07f74b14c32a1f886a49b9e5b0e1fd6371ec"
|
||||
integrity sha512-ZD3a6c7YvAjp1lEkKyaQpHc5EuetQ0RU3YoTfjwHiyWwezsuJHZc4hkS7SXWbZNEvi7tc2U1bdt4nSdx9c5Qxw==
|
||||
|
||||
sass-embedded-linux-musl-x64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.92.0.tgz#967cbe4280edb2e50b1991c2b75f31143f9ce006"
|
||||
integrity sha512-ShivGoEKmpyL57hQB9K+EMEOWOo+LuwH5eIM2T0sRIHW5n28IS6h12R3WEJVf+PYtSi9FKWazy7kzeLefya6fQ==
|
||||
|
||||
sass-embedded-linux-riscv64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.92.0.tgz#3f584bf3038d5d5ef931ab124cca2e4ac51364fc"
|
||||
integrity sha512-CTZF8rMYBS4JsGGFMUwdPExq6DxhONXQv9omKpVmuleRw52Isx37GaMTQg5zSxunS6QfwqCyUysjWXTLe8khHA==
|
||||
|
||||
sass-embedded-linux-x64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.92.0.tgz#1a4b70ab5f71045c1ca77c59489f0371e6bc47c0"
|
||||
integrity sha512-jAY4tzhSUUDUYSl0m+GQub/ZpVk00Pn4ybHeUICAYSQj043A9rkag+LSKDGCvC/0MptMM+/HkIDAC06tRY4PeQ==
|
||||
|
||||
sass-embedded-unknown-all@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.92.0.tgz#3400c5ec2f6d7b4d488b09f6f7f4337736fb6cdb"
|
||||
integrity sha512-s0UF1jquqhrxg0dl/0E+L5tCH1zv1ueF+m3VgJukDkDSTW+nb7wpCGcm8csGoSXnP8+dq53jtUXVtt8sPLr8ZQ==
|
||||
dependencies:
|
||||
sass "1.92.0"
|
||||
|
||||
sass-embedded-win32-arm64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.92.0.tgz#92bef1e92023f6023bfcf87fbf126e354b91f317"
|
||||
integrity sha512-K8x+q2W0VyGPBtO3b0AlpecGOk47ce2FkEX0WD1gEexpbRCytQ+udDACHQGXpwWYPgSIT9ky0IASzDVT1fjMcw==
|
||||
|
||||
sass-embedded-win32-x64@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.92.0.tgz#71fb693e1e8f2a976f882dcf9fad3c7b6879a0ed"
|
||||
integrity sha512-b0051n7EwSvH580u8LjsCAj2US1F59FY6/GbWJWlE2bidzY86/f8ovl4LsGY/uM3lNzWQlA/0BGmdAm44d/qJg==
|
||||
|
||||
sass-embedded@^1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.92.0.tgz#6b1ba02343c253e66a7545e34076751a5f9d2f2c"
|
||||
integrity sha512-daqnoAA+AmXvcL1fvJRMd4RDPZM2s27qYxb51c5TYc1B1Zugu0gVGyA5leoXQJEzo6sDTQ95J8X0yFcdBNGNtw==
|
||||
dependencies:
|
||||
"@bufbuild/protobuf" "^2.5.0"
|
||||
buffer-builder "^0.2.0"
|
||||
colorjs.io "^0.5.0"
|
||||
immutable "^5.0.2"
|
||||
rxjs "^7.4.0"
|
||||
supports-color "^8.1.1"
|
||||
sync-child-process "^1.0.2"
|
||||
varint "^6.0.0"
|
||||
optionalDependencies:
|
||||
sass-embedded-all-unknown "1.92.0"
|
||||
sass-embedded-android-arm "1.92.0"
|
||||
sass-embedded-android-arm64 "1.92.0"
|
||||
sass-embedded-android-riscv64 "1.92.0"
|
||||
sass-embedded-android-x64 "1.92.0"
|
||||
sass-embedded-darwin-arm64 "1.92.0"
|
||||
sass-embedded-darwin-x64 "1.92.0"
|
||||
sass-embedded-linux-arm "1.92.0"
|
||||
sass-embedded-linux-arm64 "1.92.0"
|
||||
sass-embedded-linux-musl-arm "1.92.0"
|
||||
sass-embedded-linux-musl-arm64 "1.92.0"
|
||||
sass-embedded-linux-musl-riscv64 "1.92.0"
|
||||
sass-embedded-linux-musl-x64 "1.92.0"
|
||||
sass-embedded-linux-riscv64 "1.92.0"
|
||||
sass-embedded-linux-x64 "1.92.0"
|
||||
sass-embedded-unknown-all "1.92.0"
|
||||
sass-embedded-win32-arm64 "1.92.0"
|
||||
sass-embedded-win32-x64 "1.92.0"
|
||||
|
||||
sass@1.92.0:
|
||||
version "1.92.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.92.0.tgz#02d9ae21ce1763def2cd461449aac2eb56364796"
|
||||
integrity sha512-KDNI0BxgIRDAfJgzNm5wuy+4yOCIZyrUbjSpiU/JItfih+KGXAVefKL53MTml054MmBA3DDKIBMSI/7XLxZJ3A==
|
||||
dependencies:
|
||||
chokidar "^4.0.0"
|
||||
immutable "^5.0.2"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
optionalDependencies:
|
||||
"@parcel/watcher" "^2.4.1"
|
||||
|
||||
semver@^6.3.1:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
@@ -735,6 +1088,24 @@ seroval@~1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.3.2.tgz#7e5be0dc1a3de020800ef013ceae3a313f20eca7"
|
||||
integrity sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==
|
||||
|
||||
solid-bootstrap-core@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/solid-bootstrap-core/-/solid-bootstrap-core-2.0.0.tgz#5e9bd86bd1c21fe6d3b019542c324f50cfda12df"
|
||||
integrity sha512-tw1me1iEvI+UzYRL2Gs53MP51WVwx785CU+6KQVGhaLESw3OoayeFLhe1CvUYb7kuskjtNGyAorZMdMwBJQIBA==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.10.1"
|
||||
dom-helpers "^5.2.0"
|
||||
solid-react-transition "^1.0.8"
|
||||
|
||||
solid-bootstrap@^1.0.21:
|
||||
version "1.0.21"
|
||||
resolved "https://registry.yarnpkg.com/solid-bootstrap/-/solid-bootstrap-1.0.21.tgz#9183ba07e52f88db448710a1fa953320983d3d02"
|
||||
integrity sha512-B1+1qsdqrdQhl4xgmgrFRafxOM3xrnafxrC1scbOTYYJI2ido1XfoxtYyJ72W1eSHxYjm8eRGXdGTGdBJsGPpg==
|
||||
dependencies:
|
||||
dom-helpers "^5.2.0"
|
||||
solid-bootstrap-core "^2.0.0"
|
||||
solid-react-transition "^1.0.11"
|
||||
|
||||
solid-js@^1.9.9:
|
||||
version "1.9.9"
|
||||
resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.9.tgz#7c61402a969a246973ac55ff78a06210eabc097c"
|
||||
@@ -744,6 +1115,11 @@ solid-js@^1.9.9:
|
||||
seroval "~1.3.0"
|
||||
seroval-plugins "~1.3.0"
|
||||
|
||||
solid-react-transition@^1.0.11, solid-react-transition@^1.0.8:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/solid-react-transition/-/solid-react-transition-1.0.11.tgz#10c044b800674cadb2a3fb33af8a72f87bc8e9c3"
|
||||
integrity sha512-YMT7z6sOupCicDtX19156vbVOm3vCIgjVhPTybR9gLKiIPrDB2NDVqnQk4kpNCDZTOwSjLTOyUQw0xJnXgDg2A==
|
||||
|
||||
solid-refresh@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/solid-refresh/-/solid-refresh-0.6.3.tgz#d23ef80f04e177619c9234a809c573cb16360627"
|
||||
@@ -753,7 +1129,7 @@ solid-refresh@^0.6.3:
|
||||
"@babel/helper-module-imports" "^7.22.15"
|
||||
"@babel/types" "^7.23.6"
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
@@ -771,6 +1147,25 @@ source-map@^0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
supports-color@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
sync-child-process@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sync-child-process/-/sync-child-process-1.0.2.tgz#45e7c72e756d1243e80b547ea2e17957ab9e367f"
|
||||
integrity sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==
|
||||
dependencies:
|
||||
sync-message-port "^1.0.0"
|
||||
|
||||
sync-message-port@^1.0.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/sync-message-port/-/sync-message-port-1.1.3.tgz#6055c565ee8c81d2f9ee5aae7db757e6d9088c0c"
|
||||
integrity sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==
|
||||
|
||||
terser@^5.44.0:
|
||||
version "5.44.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.0.tgz#ebefb8e5b8579d93111bfdfc39d2cf63879f4a82"
|
||||
@@ -789,6 +1184,18 @@ tinyglobby@^0.2.14:
|
||||
fdir "^6.4.4"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
typescript@^5.9.2:
|
||||
version "5.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
|
||||
@@ -812,6 +1219,11 @@ validate-html-nesting@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/validate-html-nesting/-/validate-html-nesting-1.2.3.tgz#22edb5c77de247f9b2b20f6becee3d8c614f24cb"
|
||||
integrity sha512-kdkWdCl6eCeLlRShJKbjVOU2kFKxMF8Ghu50n+crEoyx+VKm3FxAxF9z4DCy6+bbTOqNW0+jcIYRnjoIRzigRw==
|
||||
|
||||
varint@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0"
|
||||
integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==
|
||||
|
||||
vite-plugin-solid@^2.11.8:
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-solid/-/vite-plugin-solid-2.11.8.tgz#db3d9a9f0b8c82f7fcacf8c5ebea4a3136bf107d"
|
||||
|
||||
@@ -10,10 +10,24 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="GraphQL.Client" Version="6.1.0" />
|
||||
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||
<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" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.user.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
26
Server/Business/Clients/HardcoverAuthenticationHandler.cs
Normal file
26
Server/Business/Clients/HardcoverAuthenticationHandler.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Bibblan.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Bibblan.Business.Clients
|
||||
{
|
||||
public class HardcoverAuthenticationHandler : DelegatingHandler
|
||||
{
|
||||
public BibblanOptions settings;
|
||||
|
||||
public HardcoverAuthenticationHandler(IOptions<BibblanOptions> options) : base()
|
||||
{
|
||||
settings = options.Value;
|
||||
InnerHandler = new HttpClientHandler();
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", settings.HardcoverApiToken);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Server/Business/Clients/HardcoverClient.cs
Normal file
154
Server/Business/Clients/HardcoverClient.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using GraphQL;
|
||||
using GraphQL.Client.Abstractions;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||
|
||||
namespace Bibblan.Business.Clients
|
||||
{
|
||||
public class Publisher
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
public class Image
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class Author
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Name_Personal { get; set; }
|
||||
public Image Image { get; set; }
|
||||
public string Bio { get; set; }
|
||||
public int Book_Count { get; set; }
|
||||
public DateTime? Born_Date { get; set; }
|
||||
public DateTime? Death_Date { get; set; }
|
||||
public int? Gender_Id{ get; set; }
|
||||
public Dictionary<string, List<string>> Identifiers { get; set; }
|
||||
public string State { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class Contributor
|
||||
{
|
||||
public Author Author { get; set; }
|
||||
}
|
||||
|
||||
public class Edition
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Edition_Format { get; set; }
|
||||
public int? Pages { get; set; }
|
||||
public DateTime? Release_Date { get; set; }
|
||||
public string Isbn_10 { get; set; }
|
||||
public string Isbn_13 { get; set; }
|
||||
public Publisher Publisher { get; set; }
|
||||
public List<Contributor> Contributions { get; set; }
|
||||
public Image Image { get; set; }
|
||||
}
|
||||
|
||||
public class EditionsCollectionType
|
||||
{
|
||||
public List<Edition> Editions { get; set; }
|
||||
}
|
||||
|
||||
public class AuthorsCollectionType
|
||||
{
|
||||
public List<Author> Authors { get; set; }
|
||||
}
|
||||
|
||||
public class HardcoverClient
|
||||
{
|
||||
private readonly IGraphQLClient _client;
|
||||
public HardcoverClient(IGraphQLClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<Edition>> LookupBook(string title, string? author = null)
|
||||
{
|
||||
var query = new GraphQLRequest
|
||||
{
|
||||
Query = @"
|
||||
query ($title: String!, $author: String) {
|
||||
editions(
|
||||
where: {title: {_eq: $title}, _and: {contributions: {author: {name: {_eq: $author}}}}}
|
||||
) {
|
||||
id
|
||||
title
|
||||
edition_format
|
||||
pages
|
||||
release_date
|
||||
isbn_10
|
||||
isbn_13
|
||||
publisher {
|
||||
name
|
||||
}
|
||||
contributions {
|
||||
author {
|
||||
id
|
||||
name
|
||||
name_personal
|
||||
image {
|
||||
url
|
||||
}
|
||||
bio
|
||||
books_count
|
||||
born_date
|
||||
death_date
|
||||
gender_id
|
||||
identifiers
|
||||
state
|
||||
}
|
||||
}
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
}",
|
||||
Variables = new
|
||||
{
|
||||
title,
|
||||
author
|
||||
}
|
||||
};
|
||||
var response = await _client.SendQueryAsync<EditionsCollectionType>(query);
|
||||
return response.Data.Editions;
|
||||
}
|
||||
|
||||
public async Task<List<Author>> LookupAuthor(string name)
|
||||
{
|
||||
//order_by: {books_count: desc}
|
||||
//limit: 1
|
||||
var query = new GraphQLRequest
|
||||
{
|
||||
Query = @"query ($name: String){
|
||||
authors(where: {name: {_eq: $name}}) {
|
||||
id
|
||||
name
|
||||
name_personal
|
||||
image {
|
||||
url
|
||||
}
|
||||
bio
|
||||
books_count
|
||||
born_date
|
||||
death_date
|
||||
gender_id
|
||||
identifiers
|
||||
state
|
||||
}
|
||||
}",
|
||||
Variables = new
|
||||
{
|
||||
name
|
||||
}
|
||||
};
|
||||
var response = await _client.SendQueryAsync<AuthorsCollectionType>(query);
|
||||
return response.Data.Authors;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
57
Server/Business/Services/CalibreService.cs
Normal file
57
Server/Business/Services/CalibreService.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Bibblan.Models;
|
||||
using Bibblan.ViewModels;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bibblan.Business.Services
|
||||
{
|
||||
public class CalibreService
|
||||
{
|
||||
private readonly BibblanOptions _options;
|
||||
SqliteCalibreContext _context;
|
||||
public CalibreService(SqliteCalibreContext context, IOptions<BibblanOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public byte[] Cover(string path)
|
||||
{
|
||||
var fullPath = Path.Combine(_options.CalibreRoot, path, "cover.jpg");
|
||||
if (!File.Exists(fullPath))
|
||||
return null;
|
||||
|
||||
return File.ReadAllBytes(fullPath);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
159
Server/Business/Services/DatabaseService.cs
Normal file
159
Server/Business/Services/DatabaseService.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Bibblan.Models;
|
||||
using Bibblan.ViewModels;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
|
||||
namespace Bibblan.Business.Services
|
||||
{
|
||||
|
||||
public class BookFilter
|
||||
{
|
||||
public int? Id;
|
||||
public int? Author;
|
||||
public string? Query;
|
||||
}
|
||||
|
||||
public class DatabaseService(IOptions<BibblanOptions> options)
|
||||
{
|
||||
readonly BibblanOptions settings = options.Value;
|
||||
|
||||
public List<Books> GetBooks(int count, BookFilter filter = null)
|
||||
{
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
var query = "select * from books";
|
||||
object parameters = null;
|
||||
if (filter != null)
|
||||
{
|
||||
query += " where ";
|
||||
if(filter.Author != null)
|
||||
{
|
||||
query += $"id in (select book from books_authors_link where author = {filter.Author})";
|
||||
}
|
||||
else if(filter.Id != null)
|
||||
{
|
||||
query += $"id = {filter.Id}";
|
||||
}
|
||||
else if(!String.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $"lower(title) like @query or lower(author_sort) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
}
|
||||
return conn.Query<Books>(query, parameters).Take(count).ToList();
|
||||
}
|
||||
|
||||
public BookDetailVm GetBookDetails(int id)
|
||||
{
|
||||
var query = @"select * from books where id = @book;
|
||||
select * from authors where id in (select author from books_authors_link where book = @book);
|
||||
select * from books_languages_link where book = @book;
|
||||
select * from publishers where id in (select publisher from books_publishers_link where book = @book);
|
||||
select * from books_ratings_link where book = @book;
|
||||
select * from series where id in (select series from books_series_link where book = @book);
|
||||
select * from tags where id in (select tag from books_tags_link where book = @book);
|
||||
select * from comments where book = @book;
|
||||
select * from data where book = @book;";
|
||||
|
||||
using (var conn = new NpgsqlConnection(settings.BibblanConnection))
|
||||
{
|
||||
var results = conn.QueryMultiple(query, new { book = id });
|
||||
var book = results.ReadFirst<Books>();
|
||||
var authors = results.Read<Authors>();
|
||||
var lang = results.Read<BooksLanguagesLink>();
|
||||
var publishers = results.Read<Publishers>();
|
||||
var ratings = results.Read<BooksRatingsLink>();
|
||||
var series = results.Read<Series>();
|
||||
var tags = results.Read<Tags>();
|
||||
var comments = results.Read<Comments>();
|
||||
var data = results.Read<Data>();
|
||||
|
||||
return new BookDetailVm
|
||||
{
|
||||
Book = book,
|
||||
Authors = authors,
|
||||
Language = lang,
|
||||
Publishers = publishers,
|
||||
Ratings = ratings,
|
||||
Series = series,
|
||||
Tags = tags,
|
||||
Comments = comments,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<AuthorVm> GetAuthors(int count, BookFilter filter = null)
|
||||
{
|
||||
var query = "select a.id, a.name, count(bal.*) as bookcount\r\nfrom authors a\r\nleft join books_authors_link bal on a.id = bal.author\r\ngroup by a.id , a.name ";
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $" having lower(a.name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
return conn.Query<AuthorVm>(query, parameters).Take(count).ToList();
|
||||
|
||||
}
|
||||
|
||||
internal List<SeriesVm> GetSeries(int count, BookFilter? filter)
|
||||
{
|
||||
var query = @"select s.id as seriesid, s.name as seriesname, b.*, a.*
|
||||
from series s
|
||||
inner join books_series_link bsl on s.id = bsl.series
|
||||
inner join books b on bsl.book = b.id
|
||||
inner join books_authors_link bal on b.id = bal.book
|
||||
inner join authors a on bal.author = a.id";
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query?.ToLowerInvariant().Trim() ?? "";
|
||||
query += $" where lower(s.name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
var lookup = new Dictionary<long, SeriesVm>();
|
||||
conn.Query<SeriesVm, Books, Authors, SeriesVm>(query, (s,b,a) =>
|
||||
{
|
||||
if (!lookup.TryGetValue(s.Id, out SeriesVm svm))
|
||||
{
|
||||
lookup.Add(s.Id, svm = s);
|
||||
}
|
||||
svm.Books.Add(new ListBook
|
||||
{
|
||||
AuthorId = a.Id,
|
||||
AuthorName = a.Name,
|
||||
Id = b.Id,
|
||||
PubDate = b.Pubdate,
|
||||
HasCover = b.HasCover,
|
||||
Path = b.Path,
|
||||
SeriesIndex = b.SeriesIndex,
|
||||
Title = b.Title
|
||||
});
|
||||
return svm;
|
||||
}, splitOn: "id", param: parameters);
|
||||
return lookup.Values.Take(count).ToList();
|
||||
}
|
||||
|
||||
internal List<Tag> GetTags(BookFilter? filter)
|
||||
{
|
||||
var query = @"select t.id, t.name, count(btl.*) as bookcount from tags t
|
||||
inner join books_tags_link btl on t.id = btl.tag
|
||||
group by t.id, t.name
|
||||
order by bookcount desc";
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $" having lower(name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
return conn.Query<Tag>(query,parameters).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Server/Controllers/BibblanController.cs
Normal file
126
Server/Controllers/BibblanController.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Bibblan.Business.Clients;
|
||||
using Bibblan.Business.Services;
|
||||
using GraphQL;
|
||||
using GraphQL.Client.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bibblan.Controllers
|
||||
{
|
||||
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class BibblanController : ControllerBase
|
||||
{
|
||||
DatabaseService _db;
|
||||
CalibreService _calibre;
|
||||
HardcoverClient _hardCover;
|
||||
|
||||
public BibblanController(DatabaseService databaseService, CalibreService calibre, HardcoverClient hcclient)
|
||||
{
|
||||
_db = databaseService;
|
||||
_calibre = calibre;
|
||||
_hardCover = hcclient;
|
||||
}
|
||||
|
||||
[HttpGet("cover")]
|
||||
public IActionResult GetCover(string path)
|
||||
{
|
||||
//TODO: Bör kanske inte gå direkt mot calibres filer..
|
||||
var bytes = _calibre.Cover(path);
|
||||
if (bytes == null)
|
||||
return NotFound();
|
||||
|
||||
return File(bytes, "image/jpeg", true);
|
||||
}
|
||||
|
||||
[HttpGet("books")]
|
||||
public IActionResult GetBooks(string query = null)
|
||||
{
|
||||
BookFilter? filter = query != null ? new BookFilter
|
||||
{
|
||||
Query = query
|
||||
} : null;
|
||||
|
||||
var books = _db.GetBooks(100,filter).ToList();
|
||||
return Ok(books);
|
||||
}
|
||||
|
||||
[HttpGet("books/{id}")]
|
||||
public IActionResult GetBook(int id)
|
||||
{
|
||||
var book = _db.GetBookDetails(id);
|
||||
return Ok(book);
|
||||
}
|
||||
|
||||
[HttpGet("authors")]
|
||||
public IActionResult GetAuthors(string query = null)
|
||||
{
|
||||
BookFilter? filter = query != null ? new BookFilter
|
||||
{
|
||||
Query = query
|
||||
} : null;
|
||||
|
||||
var authors = _db.GetAuthors(100, filter).ToList();
|
||||
return Ok(authors);
|
||||
}
|
||||
|
||||
[HttpGet("books/author/{authorid}")]
|
||||
public IActionResult GetBooksByAuthor(int authorid)
|
||||
{
|
||||
var authors = _db.GetBooks(100, new BookFilter
|
||||
{
|
||||
Author = authorid
|
||||
}).ToList();
|
||||
return Ok(authors);
|
||||
}
|
||||
|
||||
[HttpGet("authorcover/{authorid}")]
|
||||
public IActionResult GetAuthorCover(int authorid)
|
||||
{
|
||||
//TODO: fixa vid tillfälle
|
||||
return Ok(new { desc = "picture of banana goes here"});
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("series")]
|
||||
public IActionResult GetSeries(string query = null)
|
||||
{
|
||||
BookFilter? filter = query != null ? new BookFilter
|
||||
{
|
||||
Query = query
|
||||
} : null;
|
||||
|
||||
var series = _db.GetSeries(100, filter).ToList();
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
[HttpGet("tags")]
|
||||
public IActionResult GetTags(string query = null)
|
||||
{
|
||||
BookFilter? filter = query != null ? new BookFilter
|
||||
{
|
||||
Query = query
|
||||
} : null;
|
||||
|
||||
var tags = _db.GetTags(filter).ToList();
|
||||
return Ok(tags);
|
||||
}
|
||||
|
||||
//test
|
||||
[HttpGet("lookupbook")]
|
||||
public async Task<IActionResult> LookupBook(string title, string author)
|
||||
{
|
||||
var result = await _hardCover.LookupBook(title, author);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("lookupauthor")]
|
||||
public async Task<IActionResult> LookupAuthor(string name)
|
||||
{
|
||||
var result = await _hardCover.LookupAuthor(name);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
313
Server/Controllers/CalibreController.cs
Normal file
313
Server/Controllers/CalibreController.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using Bibblan.Business.Services;
|
||||
using Bibblan.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
|
||||
namespace Bibblan.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CalibreController : ControllerBase
|
||||
{
|
||||
CalibreService _service;
|
||||
PostgresCalibreContext _postgresContext;
|
||||
SqliteCalibreContext _sqliteContext;
|
||||
|
||||
public CalibreController(CalibreService service, SqliteCalibreContext sqliteContext, PostgresCalibreContext postgresContext) {
|
||||
_service = service;
|
||||
_postgresContext = postgresContext;
|
||||
_sqliteContext = sqliteContext;
|
||||
}
|
||||
|
||||
[HttpGet("books")]
|
||||
public IActionResult GetBooks()
|
||||
{
|
||||
var books = _service.GetAllBooks().Take(100).ToList();
|
||||
return Ok(books);
|
||||
}
|
||||
|
||||
[HttpGet("cover")]
|
||||
public IActionResult GetCover(string path)
|
||||
{
|
||||
var bytes = _service.Cover(path);
|
||||
if (bytes == null)
|
||||
return NotFound();
|
||||
return File(bytes, "image/jpeg", true);
|
||||
}
|
||||
|
||||
[HttpGet("dump")]
|
||||
public IActionResult Dump()
|
||||
{
|
||||
//var e = _postgresContext.Database.EnsureCreated();
|
||||
//var script = _postgresContext.Database.GenerateCreateScript();
|
||||
// _postgresContext.Database.ExecuteSqlRaw(script);
|
||||
|
||||
var books = 1;// DumpBooks();
|
||||
var auhtors = 1;// DumpAuthors();
|
||||
var comments = 1;// DumpComments();
|
||||
var data = 1;// DumpData();
|
||||
var identifiers = 1;// DumpIdentifiers();
|
||||
var languages = 1;// DumpLanguages();
|
||||
var publishers = 1;// DumpPublishers();
|
||||
var ratings = 1;// DumpRatings();
|
||||
var series = 1;// DumpSeries();
|
||||
var tags = 1;// DumpTags();
|
||||
|
||||
var ba = 1;// Dumpa(_postgresContext, _sqliteContext.BooksAuthorsLink.OrderBy(x => x.Id), _postgresContext.BooksAuthorsLink);
|
||||
var bl = 1;// Dumpa(_postgresContext, _sqliteContext.BooksLanguagesLink.OrderBy(x => x.Id), _postgresContext.BooksLanguagesLink);
|
||||
var bp = 1;// Dumpa(_postgresContext, _sqliteContext.BooksPublishersLink.OrderBy(x => x.Id), _postgresContext.BooksPublishersLink);
|
||||
var br = 1;// Dumpa(_postgresContext, _sqliteContext.BooksRatingsLink.OrderBy(x => x.Id), _postgresContext.BooksRatingsLink);
|
||||
var bs = 1;// Dumpa(_postgresContext, _sqliteContext.BooksSeriesLink.OrderBy(x => x.Id), _postgresContext.BooksSeriesLink);
|
||||
var bt = 1;// Dumpa(_postgresContext, _sqliteContext.BooksTagsLink.OrderBy(x => x.Id), _postgresContext.BooksTagsLink);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
books,
|
||||
auhtors,
|
||||
comments,
|
||||
data,
|
||||
identifiers,
|
||||
languages,
|
||||
publishers,
|
||||
ratings,
|
||||
series,
|
||||
tags,
|
||||
links = new
|
||||
{
|
||||
ba,
|
||||
bl,
|
||||
bp,
|
||||
br,
|
||||
bs,
|
||||
bt
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int Dumpa<T>(DbContext toContext, IQueryable<T> from, DbSet<T> to)
|
||||
where T : class
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var records = from.Skip(current).Take(step);
|
||||
done = records.Count() == 0;
|
||||
foreach (var record in records)
|
||||
{
|
||||
to.Add(record);
|
||||
}
|
||||
toContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
|
||||
}
|
||||
|
||||
private int DumpBooks() {
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var books = _sqliteContext.Books.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = books.Count() == 0;
|
||||
foreach (var book in books)
|
||||
{
|
||||
_postgresContext.Books.Add(book);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpAuthors()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var authors = _sqliteContext.Authors.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = authors.Count() == 0;
|
||||
foreach (var author in authors)
|
||||
{
|
||||
_postgresContext.Authors.Add(author);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpComments()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var comments = _sqliteContext.Comments.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = comments.Count() == 0;
|
||||
foreach (var comment in comments)
|
||||
{
|
||||
_postgresContext.Comments.Add(comment);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpData()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var data = _sqliteContext.Data.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = data.Count() == 0;
|
||||
foreach (var d in data)
|
||||
{
|
||||
_postgresContext.Data.Add(d);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpIdentifiers()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var identifiers = _sqliteContext.Identifiers.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = identifiers.Count() == 0;
|
||||
foreach (var id in identifiers)
|
||||
{
|
||||
_postgresContext.Identifiers.Add(id);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpLanguages()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var languages = _sqliteContext.Languages.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = languages.Count() == 0;
|
||||
foreach (var lang in languages)
|
||||
{
|
||||
_postgresContext.Languages.Add(lang);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpPublishers()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var publishers = _sqliteContext.Publishers.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = publishers.Count() == 0;
|
||||
foreach (var pub in publishers)
|
||||
{
|
||||
if(pub.Sort == null)
|
||||
{
|
||||
pub.Sort = pub.Name;
|
||||
}
|
||||
_postgresContext.Publishers.Add(pub);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpRatings()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var ratings = _sqliteContext.Ratings.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = ratings.Count() == 0;
|
||||
foreach (var rating in ratings)
|
||||
{
|
||||
_postgresContext.Ratings.Add(rating);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpSeries()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var series = _sqliteContext.Series.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = series.Count() == 0;
|
||||
foreach (var ser in series)
|
||||
{
|
||||
_postgresContext.Series.Add(ser);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private int DumpTags()
|
||||
{
|
||||
var current = 0;
|
||||
int step = 100;
|
||||
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
var tags = _sqliteContext.Tags.OrderBy(b => b.Id).Skip(current).Take(step);
|
||||
done = tags.Count() == 0;
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
_postgresContext.Tags.Add(tag);
|
||||
}
|
||||
_postgresContext.SaveChanges();
|
||||
current += step;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Server/Models/BibblanOptions.cs
Normal file
13
Server/Models/BibblanOptions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bibblan.Models
|
||||
{
|
||||
public class BibblanOptions
|
||||
{
|
||||
public const string Bibblan = "Bibblan";
|
||||
|
||||
public string CalibreDb { get; set; } = String.Empty;
|
||||
public string CalibreRoot { get; set; } = String.Empty;
|
||||
public string BibblanConnection { get; set; } = String.Empty;
|
||||
public string HardcoverApiToken { get; set; } = string.Empty;
|
||||
public string HardcoverApiUrl { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
781
Server/Models/SqliteCalibreContext.cs
Normal file
781
Server/Models/SqliteCalibreContext.cs
Normal file
@@ -0,0 +1,781 @@
|
||||
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 DateTime Timestamp { get; set; }
|
||||
public DateTime 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 bool HasCover { get; set; }
|
||||
public DateTime 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 bool MarkForDelete { get; set; }
|
||||
public bool Editable { get; set; }
|
||||
public string Display { get; set; }
|
||||
public bool IsMultiple { get; set; }
|
||||
public bool 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 BaseContext : 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 BaseContext(DbContextOptions 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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class SqliteCalibreContext : BaseContext
|
||||
{
|
||||
public SqliteCalibreContext(DbContextOptions<SqliteCalibreContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PostgresCalibreContext : BaseContext
|
||||
{
|
||||
public PostgresCalibreContext(DbContextOptions<PostgresCalibreContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,60 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
using Bibblan.Business.Clients;
|
||||
using Bibblan.Business.Services;
|
||||
using Bibblan.Models;
|
||||
using Bibblan.ViewModels;
|
||||
using Dapper;
|
||||
using GraphQL.Client.Abstractions;
|
||||
using GraphQL.Client.Http;
|
||||
using GraphQL.Client.Serializer.Newtonsoft;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Net.Http;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.AddJsonFile($"appsettings.user.json", true, true);
|
||||
|
||||
var configSection = builder.Configuration.GetSection(BibblanOptions.Bibblan);
|
||||
BibblanOptions config = new();
|
||||
configSection.Bind(config);
|
||||
// Add services to the container.
|
||||
builder.Services.Configure<BibblanOptions>(configSection);
|
||||
|
||||
builder.Services.AddScoped<CalibreService, CalibreService>();
|
||||
builder.Services.AddScoped<DatabaseService, DatabaseService>();
|
||||
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
//hardcover registration
|
||||
builder.Services.AddTransient<HardcoverAuthenticationHandler>();//m<>ste vara transient
|
||||
builder.Services.AddScoped<IGraphQLClient>(s => new GraphQLHttpClient(new GraphQLHttpClientOptions
|
||||
{
|
||||
HttpMessageHandler = s.GetRequiredService<HardcoverAuthenticationHandler>(),
|
||||
EndPoint = new Uri(config.HardcoverApiUrl),
|
||||
}, new NewtonsoftJsonSerializer()));
|
||||
builder.Services.AddScoped<HardcoverClient>();
|
||||
|
||||
//databases
|
||||
builder.Services.AddDbContext<SqliteCalibreContext>(options => options.UseSqlite($"Data Source={config.CalibreDb};Mode=ReadOnly;"));
|
||||
builder.Services.AddDbContext<PostgresCalibreContext>(options => options.UseNpgsql(config.BibblanConnection));
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||
Dapper.SqlMapper.SetTypeMap(
|
||||
typeof(SeriesVm),
|
||||
new CustomPropertyTypeMap(
|
||||
typeof(SeriesVm),
|
||||
(type, columnName) =>
|
||||
type.GetProperties().FirstOrDefault(prop =>
|
||||
prop.GetCustomAttributes(false)
|
||||
.OfType<ColumnAttribute>()
|
||||
.Any(attr => attr.Name == columnName))));
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
|
||||
9
Server/ViewModels/AuthorVm.cs
Normal file
9
Server/ViewModels/AuthorVm.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
40
Server/ViewModels/BookVm.cs
Normal file
40
Server/ViewModels/BookVm.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Bibblan.Models;
|
||||
|
||||
namespace Bibblan.ViewModels
|
||||
{
|
||||
public class BookDetailVm
|
||||
{
|
||||
public Books Book { get; internal set; }
|
||||
public IEnumerable<Authors> Authors { get; internal set; }
|
||||
public IEnumerable<Publishers> Publishers { get; internal set; }
|
||||
public IEnumerable<BooksLanguagesLink> Language { get; internal set; }
|
||||
public IEnumerable<BooksRatingsLink> Ratings { get; internal set; }
|
||||
public IEnumerable<Series> Series { get; internal set; }
|
||||
public IEnumerable<Tags> Tags { get; internal set; }
|
||||
public IEnumerable<Comments> Comments { get; internal set; }
|
||||
public IEnumerable<Data> Data { get; internal set; }
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Server/ViewModels/DataVm.cs
Normal file
9
Server/ViewModels/DataVm.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
34
Server/ViewModels/SeriesVm.cs
Normal file
34
Server/ViewModels/SeriesVm.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Bibblan.ViewModels
|
||||
{
|
||||
public class Tag
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int BookCount { get; set; }
|
||||
}
|
||||
|
||||
public class ListBook
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public DateTime PubDate { get; set; }
|
||||
public double SeriesIndex { get; set; }
|
||||
public long AuthorId { get; set; }
|
||||
public string AuthorName { get; set; }
|
||||
public bool HasCover { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class SeriesVm
|
||||
{
|
||||
[Column("seriesid")]
|
||||
public long Id { get; set; }
|
||||
[Column("seriesname")]
|
||||
public string Name { get; set; }
|
||||
public List<ListBook> Books { get; set; } = new();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,12 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"Bibblan": {
|
||||
"CalibreDb": "metadata.db",
|
||||
"CalibreRoot": "c:\\my_books\\",
|
||||
"BibblanConnection": "Server=localhost;Port=5432;Database=bibblan;User Id=bibblanuser;Password=1234567;",
|
||||
"HardcoverApiUrl": "https://api.hardcover.app/v1/graphql",
|
||||
"HardcoverApiToken": ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user