Compare commits
	
		
			16 Commits
		
	
	
		
			353d5ac13b
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0af47751cf | |||
| 1e90a68a6e | |||
| 910f500460 | |||
| 6efa8aba8a | |||
| f88c25f117 | |||
| e23822b30c | |||
| 01e341fce0 | |||
| 8c54a120d1 | |||
| 1744a5b7de | |||
| 5ae7c7f47b | |||
| 2ffeb53e32 | |||
| 42ac0a99e8 | |||
| ec787e80e4 | |||
| 
						
						
							
						
						596fe5159a
	
				 | 
					
					
						|||
| ecd0d5a3d2 | |||
| 48c1811c30 | 
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,7 @@
 | 
			
		||||
Frontend/node_modules
 | 
			
		||||
Frontend/dist
 | 
			
		||||
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,17 +3,23 @@
 | 
			
		||||
	"version": "1.0.0",
 | 
			
		||||
	"main": "index.ts",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"type": "module",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite",
 | 
			
		||||
		"build": "vite build"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"vite": "^7.1.4",
 | 
			
		||||
		"vite-plugin-solid": "^2.11.8",
 | 
			
		||||
		"@types/node": "^24.3.1",
 | 
			
		||||
		"sass-embedded": "^1.92.0",
 | 
			
		||||
		"terser": "^5.44.0",
 | 
			
		||||
		"typescript": "^5.9.2"
 | 
			
		||||
		"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,20 +1,61 @@
 | 
			
		||||
import { Router, Route } from "@solidjs/router";
 | 
			
		||||
import type { Component } from "solid-js";
 | 
			
		||||
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 = () => {
 | 
			
		||||
	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>
 | 
			
		||||
 | 
			
		||||
			<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[];
 | 
			
		||||
}
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
		"strict": true,
 | 
			
		||||
		"target": "ESNext",
 | 
			
		||||
		"module": "ESNext",
 | 
			
		||||
		"moduleResolution": "node",
 | 
			
		||||
		"moduleResolution": "bundler",
 | 
			
		||||
		"allowSyntheticDefaultImports": true,
 | 
			
		||||
		"esModuleInterop": true,
 | 
			
		||||
		"jsx": "preserve",
 | 
			
		||||
		"jsxImportSource": "solid-js",
 | 
			
		||||
		"types": ["vite/client"],
 | 
			
		||||
		"types": ["vite/client", "node"],
 | 
			
		||||
		"noEmit": true,
 | 
			
		||||
		"isolatedModules": true
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import { defineConfig } from "vite";
 | 
			
		||||
import solidPlugin from "vite-plugin-solid";
 | 
			
		||||
import { env } from "process";
 | 
			
		||||
//test
 | 
			
		||||
const target = env.BACKEND_URL ?? "https://localhost:5001";
 | 
			
		||||
 | 
			
		||||
const target = env.BACKEND_URL ?? "http://localhost:5084/"; //"https://localhost:7192/";
 | 
			
		||||
console.log("vite backend:", target);
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
	plugins: [solidPlugin()],
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -475,6 +584,13 @@
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
 | 
			
		||||
  integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
 | 
			
		||||
 | 
			
		||||
"@types/node@^24.3.1":
 | 
			
		||||
  version "24.3.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.1.tgz#b0a3fb2afed0ef98e8d7f06d46ef6349047709f3"
 | 
			
		||||
  integrity sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    undici-types "~7.10.0"
 | 
			
		||||
 | 
			
		||||
acorn@^8.15.0:
 | 
			
		||||
  version "8.15.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
 | 
			
		||||
@@ -499,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"
 | 
			
		||||
@@ -509,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"
 | 
			
		||||
@@ -519,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"
 | 
			
		||||
@@ -529,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==
 | 
			
		||||
@@ -541,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"
 | 
			
		||||
@@ -593,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"
 | 
			
		||||
@@ -603,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"
 | 
			
		||||
@@ -642,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"
 | 
			
		||||
@@ -652,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"
 | 
			
		||||
@@ -669,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"
 | 
			
		||||
@@ -683,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"
 | 
			
		||||
@@ -713,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"
 | 
			
		||||
@@ -728,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"
 | 
			
		||||
@@ -737,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"
 | 
			
		||||
@@ -746,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==
 | 
			
		||||
@@ -764,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"
 | 
			
		||||
@@ -782,11 +1184,28 @@ 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"
 | 
			
		||||
  integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
 | 
			
		||||
 | 
			
		||||
undici-types@~7.10.0:
 | 
			
		||||
  version "7.10.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350"
 | 
			
		||||
  integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
 | 
			
		||||
 | 
			
		||||
update-browserslist-db@^1.1.3:
 | 
			
		||||
  version "1.1.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
 | 
			
		||||
@@ -800,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"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								Server/Bibblan.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Server/Bibblan.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk.Web">
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>net9.0</TargetFramework>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <ImplicitUsings>enable</ImplicitUsings>
 | 
			
		||||
    <SpaRoot>../Frontend</SpaRoot>
 | 
			
		||||
    <SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
 | 
			
		||||
    <SpaProxyServerUrl>http://localhost:3000</SpaProxyServerUrl>
 | 
			
		||||
  </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>
 | 
			
		||||
							
								
								
									
										6
									
								
								Server/Bibblan.http
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Server/Bibblan.http
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
@Bibblan_HostAddress = http://localhost:5084
 | 
			
		||||
 | 
			
		||||
GET {{Bibblan_HostAddress}}/api/weatherforecast/
 | 
			
		||||
Accept: application/json
 | 
			
		||||
 | 
			
		||||
###
 | 
			
		||||
							
								
								
									
										22
									
								
								Server/Bibblan.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Server/Bibblan.sln
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
 | 
			
		||||
Microsoft Visual Studio Solution File, Format Version 12.00
 | 
			
		||||
# Visual Studio Version 17
 | 
			
		||||
VisualStudioVersion = 17.12.35728.132 d17.12
 | 
			
		||||
MinimumVisualStudioVersion = 10.0.40219.1
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bibblan", "Bibblan.csproj", "{760702AA-30CE-4B49-BF4F-D8A9E01D5B6C}"
 | 
			
		||||
EndProject
 | 
			
		||||
Global
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
		Debug|Any CPU = Debug|Any CPU
 | 
			
		||||
		Release|Any CPU = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 | 
			
		||||
		{760702AA-30CE-4B49-BF4F-D8A9E01D5B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{760702AA-30CE-4B49-BF4F-D8A9E01D5B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{760702AA-30CE-4B49-BF4F-D8A9E01D5B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{760702AA-30CE-4B49-BF4F-D8A9E01D5B6C}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
EndGlobal
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								Server/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Server/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
This file explains how Visual Studio created the project.
 | 
			
		||||
 | 
			
		||||
The following steps were used to generate this project:
 | 
			
		||||
- Create new ASP\.NET Core Web API project.
 | 
			
		||||
- Update `launchSettings.json` to register the SPA proxy as a startup assembly.
 | 
			
		||||
- Update project file to add a reference to the frontend project and set SPA properties.
 | 
			
		||||
- Add project to the startup projects list.
 | 
			
		||||
- Write this file.
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								Server/Controllers/WeatherForecastController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Server/Controllers/WeatherForecastController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
 | 
			
		||||
namespace Bibblan.Controllers
 | 
			
		||||
{
 | 
			
		||||
    [ApiController]
 | 
			
		||||
    [Route("api/[controller]")]
 | 
			
		||||
    public class WeatherForecastController : ControllerBase
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly string[] Summaries = new[]
 | 
			
		||||
        {
 | 
			
		||||
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private readonly ILogger<WeatherForecastController> _logger;
 | 
			
		||||
 | 
			
		||||
        public WeatherForecastController(ILogger<WeatherForecastController> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [HttpGet(Name = "GetWeatherForecast")]
 | 
			
		||||
        public IEnumerable<WeatherForecast> Get()
 | 
			
		||||
        {
 | 
			
		||||
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
 | 
			
		||||
            {
 | 
			
		||||
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
 | 
			
		||||
                TemperatureC = Random.Shared.Next(-20, 55),
 | 
			
		||||
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
 | 
			
		||||
            })
 | 
			
		||||
            .ToArray();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								Server/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Server/Program.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
// Configure the HTTP request pipeline.
 | 
			
		||||
if (app.Environment.IsDevelopment())
 | 
			
		||||
{
 | 
			
		||||
    app.MapOpenApi();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
app.UseHttpsRedirection();
 | 
			
		||||
 | 
			
		||||
app.UseAuthorization();
 | 
			
		||||
 | 
			
		||||
app.MapControllers();
 | 
			
		||||
 | 
			
		||||
app.MapFallbackToFile("/index.html");
 | 
			
		||||
 | 
			
		||||
app.Run();
 | 
			
		||||
							
								
								
									
										16
									
								
								Server/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Server/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://json.schemastore.org/launchsettings.json",
 | 
			
		||||
  "profiles": {
 | 
			
		||||
    "http": {
 | 
			
		||||
      "commandName": "Project",
 | 
			
		||||
      "dotnetRunMessages": true,
 | 
			
		||||
      "launchBrowser": false,
 | 
			
		||||
      "applicationUrl": "http://localhost:5084",
 | 
			
		||||
      "environmentVariables": {
 | 
			
		||||
        "ASPNETCORE_ENVIRONMENT": "Development",
 | 
			
		||||
        "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								Server/WeatherForecast.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Server/WeatherForecast.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
namespace Bibblan
 | 
			
		||||
{
 | 
			
		||||
    public class WeatherForecast
 | 
			
		||||
    {
 | 
			
		||||
        public DateOnly Date { get; set; }
 | 
			
		||||
 | 
			
		||||
        public int TemperatureC { get; set; }
 | 
			
		||||
 | 
			
		||||
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
 | 
			
		||||
 | 
			
		||||
        public string? Summary { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								Server/appsettings.Development.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Server/appsettings.Development.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Information",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								Server/appsettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Server/appsettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Information",
 | 
			
		||||
      "Microsoft.AspNetCore": "Warning"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "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