La in dapper, och routing. fixade upp lite exempelsidor
This commit is contained in:
parent
8c54a120d1
commit
01e341fce0
@ -17,6 +17,7 @@
|
||||
"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,32 +1,54 @@
|
||||
import { Router, Route } from "@solidjs/router";
|
||||
import type { Component } from "solid-js";
|
||||
import { createSignal, For, onMount } from "solid-js";
|
||||
import CalibreService from "./services/calibreservice";
|
||||
import { book } from "./types/calibretypes";
|
||||
import BookCard from "./components/BookCard";
|
||||
import Home from "./components/Home";
|
||||
import BookList from "./components/BookList";
|
||||
import AuthorList from "./components/AuthorList";
|
||||
|
||||
const App: Component = () => {
|
||||
const [books, setBooks] = createSignal<book[]>([]);
|
||||
onMount(() => {
|
||||
CalibreService.getBooks().then(setBooks);
|
||||
});
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="book-grid d-flex flex-wrap justify-content-between gap-3 p-3">
|
||||
<For each={books()}>{(item) => <BookCard book={item} />}</For>
|
||||
</div>
|
||||
<main class="container">
|
||||
<Router>
|
||||
<Route path="/books/author/:authorid" component={BookList} />
|
||||
<Route path="/books" component={BookList} />
|
||||
<Route path="/authors" component={AuthorList} />
|
||||
<Route path="/" component={Home} />
|
||||
</Router>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
27
Frontend/src/components/AuthorCard.tsx
Normal file
27
Frontend/src/components/AuthorCard.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
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">
|
||||
<Card.Img
|
||||
variant="top"
|
||||
class="padding-1"
|
||||
src={"/api/biiblan/authorcover/" + encodeURIComponent(props.author.id)}
|
||||
/>
|
||||
<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;
|
22
Frontend/src/components/AuthorList.tsx
Normal file
22
Frontend/src/components/AuthorList.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
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[]>([]);
|
||||
onMount(() => {
|
||||
BibblanService.getAuthors().then(setAuthors);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Books!</h1>
|
||||
<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;
|
@ -1,5 +1,5 @@
|
||||
import { Show, type Component } from "solid-js";
|
||||
import { book } from "../types/calibretypes";
|
||||
import { book } from "../types/types";
|
||||
import { Card } from "solid-bootstrap";
|
||||
|
||||
const BookCard: Component<{ book: book }> = (props: { book: book }) => {
|
||||
|
24
Frontend/src/components/BookList.tsx
Normal file
24
Frontend/src/components/BookList.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { createSignal, onMount, For, 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 params = useParams();
|
||||
onMount(() => {
|
||||
BibblanService.getBooks(params.authorid).then(setBooks);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Books!</h1>
|
||||
<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;
|
21
Frontend/src/services/bibblanservice.ts
Normal file
21
Frontend/src/services/bibblanservice.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { book, author } from "../types/types";
|
||||
|
||||
const BibblanService = {
|
||||
getBooks: async (
|
||||
authorid: string | undefined = undefined
|
||||
): Promise<book[]> => {
|
||||
let url = "/api/bibblan/books";
|
||||
if (authorid != undefined) {
|
||||
url += `/author/${authorid}`;
|
||||
}
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
getAuthors: async (): Promise<author[]> => {
|
||||
const response = await fetch("/api/bibblan/authors");
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
|
||||
export default BibblanService;
|
@ -1,4 +1,4 @@
|
||||
import { book } from "../types/calibretypes";
|
||||
import { book } from "../types/types";
|
||||
|
||||
const CalibreService = {
|
||||
getBooks: async (): Promise<book[]> => {
|
||||
|
@ -12,4 +12,10 @@ interface book {
|
||||
seriesNumber: number;
|
||||
}
|
||||
|
||||
export type { book };
|
||||
interface author {
|
||||
id: number;
|
||||
name: string;
|
||||
bookCount: number;
|
||||
}
|
||||
|
||||
export type { book, author };
|
@ -541,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"
|
||||
|
@ -10,6 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
||||
<Version>9.*-*</Version>
|
||||
|
45
Server/Business/Services/DatabaseService.cs
Normal file
45
Server/Business/Services/DatabaseService.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Bibblan.Models;
|
||||
using Bibblan.ViewModels;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
|
||||
namespace Bibblan.Business.Services
|
||||
{
|
||||
|
||||
public class BookFilter
|
||||
{
|
||||
public int? Author;
|
||||
}
|
||||
|
||||
public class DatabaseService
|
||||
{
|
||||
BibblanOptions settings;
|
||||
public DatabaseService(IOptions<BibblanOptions> options) {
|
||||
settings = options.Value;
|
||||
}
|
||||
|
||||
public IEnumerable<Books> GetBooks(int count, BookFilter filter = null)
|
||||
{
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
var query = "select * from books";
|
||||
if(filter != null)
|
||||
{
|
||||
query += " where ";
|
||||
if(filter.Author != null)
|
||||
{
|
||||
query += $"id in (select book from books_authors_link where author = {filter.Author})";
|
||||
}
|
||||
}
|
||||
return conn.Query<Books>(query).Take(count).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<AuthorVm> GetAuthors(int count)
|
||||
{
|
||||
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 ";
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
return conn.Query<AuthorVm>(query).Take(count).ToList();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
53
Server/Controllers/BibblanController.cs
Normal file
53
Server/Controllers/BibblanController.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Bibblan.Business.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bibblan.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class BibblanController : ControllerBase
|
||||
{
|
||||
DatabaseService _db;
|
||||
CalibreService _calibre;
|
||||
|
||||
public BibblanController(DatabaseService databaseService, CalibreService calibre)
|
||||
{
|
||||
_db = databaseService;
|
||||
_calibre = calibre;
|
||||
}
|
||||
|
||||
[HttpGet("cover")]
|
||||
public IActionResult GetCover(string path)
|
||||
{
|
||||
//TODO: Bör kanske inte gå direkt mot calibres filer..
|
||||
var bytes = _calibre.Cover(path);
|
||||
return File(bytes, "image/jpeg", true);
|
||||
}
|
||||
|
||||
[HttpGet("books")]
|
||||
public IActionResult GetBooks()
|
||||
{
|
||||
var authors = _db.GetBooks(100).ToList();
|
||||
return Ok(authors);
|
||||
}
|
||||
|
||||
[HttpGet("authors")]
|
||||
public IActionResult GetAuthors()
|
||||
{
|
||||
var authors = _db.GetAuthors(100).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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ configSection.Bind(config);
|
||||
builder.Services.Configure<BibblanOptions>(configSection);
|
||||
|
||||
builder.Services.AddScoped<CalibreService, CalibreService>();
|
||||
builder.Services.AddScoped<DatabaseService, DatabaseService>();
|
||||
|
||||
|
||||
builder.Services.AddControllers();
|
||||
@ -26,6 +27,8 @@ builder.Services.AddDbContext<PostgresCalibreContext>(options => options.UseNpgs
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user