Update (Back-End): Mise à jour
continuous-integration/drone/push Build is failing Details

Back-End
Louis DUFOUR 2 years ago
parent bf92f31134
commit 1606a3a133

@ -1,5 +1,210 @@
# PocketBook
# TP2 : MVVM (partie 2)
_à terminer pour le 15 octobre 2023 - 23h59_
🚧 En construction : le **Modèle** est terminé à plus de 95%. Seules des modifications mineures peuvent être intégrées
## Modifications à prendre en compte :
- le **Stub** est modifié et _doit_ fonctionner sans avoir à utiliser la propriété ```BasePath```, aussi bien en Console qu'avec MAUI
- le **Model** prend en charge maintenant deux services : ```ILibraryManager``` doit permettre de gérer l'intégralité des livres, et ```IUserLibraryManager``` doit permettre de gérer la bibliothèque personnelle de l'utilisateur. Le **Stub** fournit de quoi simuler leur utilisation.
## Ajout dans l'énoncé
L'application doit maintenant permettre de naviguer sur toutes les pages et l'utilisateur doit pouvoir :
- changer le statut de lecture d'un livre,
- ajouter un livre aux favoris,
- filtrer les livres par **Auteur**, **Date de publication**, **Note**
- ajouter un livre à sa collection en saisissant l'ISBN (les vues seront à ajouter)
- supprimer un livre
- prêter un livre (et ajouter un contact si besoin)
- consulter la liste des livres prêtés.
Lors de la consultation des livres, il faudra gérer la pagination pour que l'on puisse limiter l'affichage à n (5, 10, 20,...) livres par page.
> # Note : les TP3 et TP4 peuvent être faits dans le désordre.
> ### Mais pour pouvoir réaliser les TP3 et TP4, vous devez au préalable faire valider les bases du TP2 à votre enseignant. Si celui-ci n'est pas exploitable à la date de rendu demandée, celle-ci est repoussée au 22 octobre 23h59. Le TP2 est prioritaire sur les TP3 et 4.
# TP3 : utilisation du MVVM Community Toolkit
_à terminer pour le 22 octobre 2023 - 23h59_
Modifiez l'intégralité de votre code pour que votre application utilise désormais le MVVM Community Toolkit à la place de votre toolkit maison.
# TP4 : utilisation de la caméra comme scanner d'ISBN
_à terminer pour le 22 octobre 2023 - 23h59_
Ajoutez les vues et les VM nécessaires pour permettre le scan de code-barres afin d'ajouter de nouveaux livres, et la recherche en ligne (via le web service).
-----------------------------------------------------------
-----------------------------------------------------------
# TP2 : MVVM
_à terminer pour le 15 octobre 2023 - 23h59_
L'objectif de cette partie est de relier les vues (_Views_) et le modèle (_Model_) en respectant le patron d'architecture **MVVM** (_Model-View-ViewModel_).
Vous devrez réaliser les étapes suivantes (je vous suggère de les réaliser dans cet ordre) :
1. page d'accueil : en utilisant une _ContentView_ et des _Commands_, faites en sorte que l'utilisateur puisse :
- naviguer vers la page affichant les livres de l'utilisateur (_BooksPage_) (sans liaison de données pour le moment) en tapant sur **Tous**,
- naviguer vers la page affichant les prêts et emprunts (_LoanPage_) (sans liaison de données pour le moment) en tapant sur **En prêt**,
- naviguer vers la page affichant des filtres par critères (_FilterPage_) (sans liaison de données pour le moment) en tapant sur **À lire plus tard**, **Statut de lecture**, **Favoris**, **Auteur**, **Date de publication**, **Note**
- afficher la vue permettant d'ajouter un livre lorsqu'on clique sur le **+** puis ajouter une vue modale lorsqu'on clique sur **Saisir l'ISBN**
> _**Note**_ : vous devez bien sûr limiter au maximum le code en _code-behind_ et utiliser des _VM_ et des _Commands_.
2. affichage des livres de l'utilisateur : afficher tous les livres de l'utilisateur dans la vue _BooksPage_ et permettre la sélection d'un livre et la navigation vers la page _BookPage_
> _**Note**_ : le modèle ne permet pas encore d'accéder aux livres de l'utilisateur, mais vous pouvez utiliser dans un premier temps la fonctionnalité qui permet d'atteindre tous les livres. Elle est basée sur un stub et il y en a donc peu. Cette partie manquante du modèle devrait être livrée dans la semaine.
> _**Conseils**_ : commencez par les données simples (titre du livre, auteur, statut de lecture), puis passez à la navigation vers la page d'un livre (_BookPage_) où vous pourrez afficher les données simples (titre du livre, l'auteur, la maison d'édition, le résumé, le nombre de pages, la langue, l'isbn et le statut de lecture). Enfin, vous pourrez vous occuper des données plus compliquées à afficher (la couverture et la note). Le bouton avec les deux flèches inversera l'ordre d'affichage.
> _**Difficile**_ : l'utilisation de la _scrollview_ par index est plus compliquée. Je la compterai en bonus.
3. filtrage par auteur et par date de publication : afficher dans la vue de filtrage (_FilterPage_)
- les auteurs (et le nombre de livres par auteur) lorsqu'on tape sur _Auteurs_, puis navigation vers la page des livres _BooksPage_ mais seulement avec les livres de l'auteur sélectionné,
- les années de publication (et le nombre de livres par année) lorsqu'on tape sur _Date de publication_, puis navigation vers la page des livres _BooksPage_ mais seulement avec les livres publiés dans l'année sélectionné.
## Modèle
```mermaid
classDiagram
direction LR
class Book {
+Id : string
+Title : string
+Publishers : List~string~
+PublishDate : DateTime
+ISBN13 : string
+Series : List~string~
+NbPages : int
+Format : string
+ImageSmall : string
+ImageMedium : string
+ImageLarge : string
}
class Languages {
<<enum>>
Unknown,
French,
}
class Work {
+Id : string
+Description : string
+Title : string
+Subjects : List~string~
}
class Contributor {
+Name : string
+Role : string
}
class Author {
+Id : string
+Name : string
+ImageSmall : string
+ImageMedium : string
+ImageLarge : string
+Bio : string
+AlternateNames : List~string~
+BirthDate : DateTime?
+DeathDate : DateTime?
}
class Link {
+Title : string
+Url : string
}
class Ratings {
+Average : float
+Count : int
}
Book --> "1" Languages : Language
Book --> "*" Contributor : Contributors
Author --> "*" Link : Links
Work --> "*" Author : Authors
Work --> "1" Ratings : Ratings
Book --> "*" Author : Authors
Book --> "*" Work : Works
class Status {
<<enum>>
Unknown,
Finished,
Reading,
NotRead,
ToBeRead
}
class Contact{
+string Id;
+string FirstName;
+string LastName;
}
```
## Services & Interfaces
```mermaid
classDiagram
direction LR
Book --> "1" Languages : Language
Book --> "*" Contributor : Contributors
Author --> "*" Link : Links
Work --> "1" Ratings : Ratings
Work --> "*" Author : Authors
Book --> "*" Work : Works
Book --> "*" Author : Authors
class IUserLibraryManager {
<<interface>>
+AddBook(Book book) : Task<Book>
+AddBook(string id) : Task<Book>
+AddBookByIsbn(string isbn) : Task<Book>
+RemoveBook(Book book) : Task<bool>
+RemoveBook(string id) : Task<bool>
+RemoveBook(string id) : Task<bool>
+AddToFavorites(Book book) : Task<bool>
+AddToFavorites(string bookId) : Task<bool>
+RemoveFromFavorites(Book book) : Task<bool>
+RemoveFromFavorites(string bookId) : Task<bool>
+UpdateBook(Book updatedBook) : Task<Book>
+AddContact(Contact contact) : Task<Contact>
+RemoveContact(Contact contact) : Task<bool>
+LendBook(Book book, Contact contact, DateTime? loanDate) : Task<bool>
+GetBackBook(Book book, DateTime? returnedDate) : Task<bool>
+BorrowBook(Book book, Contact owner, DateTime? borrowedDate) : Task<bool>
+GiveBackBook(Book book, DateTime? returnedDate) : Task<bool>
+GetCurrentLoans(int index, int count)
+GetPastLoans(int index, int count)
+GetCurrentBorrowings(int index, int count)
+GetPastBorrowings(int index, int count)
+GetContacts(int index, int count)
}
class ILibraryManager {
<<interface>>
+GetBookById(string id)
+GetBookByIsbn(string isbn)
+GetBooksByTitle(string title, int index, int count, string sort)
+GetBooksByAuthorId(string authorId, int index, int count, string sort)
+GetBooksByAuthor(string author, int index, int count, string sort)
+GetAuthorById(string id)
+GetAuthorsByName(string substring, int index, int count, string sort)
}
class Status {
<<enum>>
}
IUserLibraryManager ..|> ILibraryManager
IUserLibraryManager ..> Status
IUserLibraryManager ..> Contact
IUserLibraryManager ..> Book
ILibraryManager ..> Book
ILibraryManager ..> Author
```
# TO BE CONTINUED
# TP1 : Pages & Views
_à terminer pour le 17 septembre 2023 - 23h59_
@ -71,6 +276,11 @@ Lorsqu'on clique sur un le filtre "Date de publication" de la page "Mes Livres",
Un clic sur une de ces années permet d'aller sur la même page que celle qui affiche la liste des livres, mais seuls les livres publiée cette année apparaitront.
### Emprunts / Prêts
Lorsqu'on clique sur le menu "En prêt" sur la page d'accueil, on peut voir la liste de nos emprunts et de nos prêts répartis par contacts. Par exemple :
<img src="screens/emprunts.png" width="300"/>
Le switch permet de voir les livres qu'on a prêtés regroupés par contacts, ou les livres qu'on a empruntés regroupés par prêteurs.
### Le fameux bouton "+"
Un clic sur le bouton "+" fait apparaître un sous-menu permettant d'ajouter un livre à la bibliothèque :
- en scannant un code-barres (cf. plus bas)

@ -4,6 +4,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:composants="clr-namespace:BookApp.Composants"
xmlns:vm="clr-namespace:BookApp.ViewModel"
xmlns:model="clr-namespace:Model;assembly=Model"
x:DataType="vm:TousViewModel"
x:Class="BookApp.Pages.Filtrage">
<Shell.BackButtonBehavior>
<BackButtonBehavior IsVisible="False" IsEnabled="False"/>
@ -64,7 +67,37 @@
<Rectangle HeightRequest="1" BackgroundColor="LightGray"/>
<SearchBar Placeholder="Search items..."/>
<Rectangle HeightRequest="1" BackgroundColor="LightGray"/>
<composants:CollectionFiltrage VerticalOptions="FillAndExpand"/>
<CollectionView ItemsSource="{Binding Authors}" VerticalOptions="FillAndExpand">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Author">
<StackLayout Padding="5">
<Grid RowDefinitions="auto" Margin="5,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" HeightRequest="30"
WidthRequest="30" Source="Book.svg">
<Image.Behaviors>
<toolkit:IconTintColorBehavior TintColor="Red" />
</Image.Behaviors>
</Image>
<Label Grid.Column="1" Margin="10,0,0,0" VerticalTextAlignment="Center" Text="{Binding Name}"/>
<!-- <Label Grid.Column="2" VerticalTextAlignment="Center" Margin="0,0,40,0" HorizontalTextAlignment="End" Text="{Binding NbLivre}"/> -->
<Button Grid.Column="2"
ImageSource="chevron_right.svg"
HeightRequest="35"
WidthRequest="35"
VerticalOptions="Center"
BackgroundColor="White"
HorizontalOptions="End"/>
</Grid>
<Rectangle Margin="45,0,0,0" HeightRequest="1" BackgroundColor="LightGray" VerticalOptions="End" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
</ContentPage>

@ -6,9 +6,10 @@ namespace BookApp.Pages
{
SearchBar searchBar = new SearchBar { Placeholder = "Search items..." };
public Filtrage()
public Filtrage(TousViewModel data)
{
InitializeComponent();
BindingContext= data;
}
async void BackButton(object sender, EventArgs args)

@ -6,10 +6,6 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibraryDTO\LibraryDTO.csproj" />
</ItemGroup>

@ -8,7 +8,6 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibraryDTO\LibraryDTO.csproj" />

@ -15,7 +15,4 @@
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\net7.0\LibraryDTO.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
</Project>

@ -1,7 +1,7 @@
using System;
namespace Model
{
public class Author
public class Author : IEquatable<Author>
{
public string Id { get; set; }
public string Name { get; set; }
@ -13,6 +13,20 @@ namespace Model
public List<Link> Links { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime? DeathDate { get; set; }
public bool Equals(Author? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Author);
}
public override int GetHashCode()
=> Id.GetHashCode();
}
}

@ -2,7 +2,7 @@
namespace Model;
public class Book
public class Book : IEquatable<Book>
{
public string Id { get; set; }
public string Title { get; set; }
@ -19,5 +19,23 @@ public class Book
public string ImageLarge => $"https://covers.openlibrary.org/b/isbn/{ISBN13}-L.jpg";
public List<Work> Works { get; set; } = new List<Work>();
public List<Author> Authors { get; set; } = new List<Author>();
public Status Status { get; set; }
public List<string> UserTags { get; set; } = new List<string>();
public float? UserRating { get; set; }
public string UserNote { get; set; }
public bool Equals(Book? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Book);
}
public override int GetHashCode()
=> Id.GetHashCode();
}

@ -3,12 +3,27 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Model
{
public class Borrowing //emprunt
public class Borrowing : IEquatable<Borrowing> //emprunt
{
public string Id { get; set; }
public Person Owner { get; set; }
public Book Book { get; set; }
public Contact Owner { get; set; }
public DateTime BorrowedAt { get; set; }
public DateTime? ReturnedAt { get; set; }
public bool Equals(Borrowing? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Borrowing);
}
public override int GetHashCode()
=> Id.GetHashCode();
}
}

@ -0,0 +1,25 @@
using System;
namespace Model
{
public class Contact : IEquatable<Contact>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Contact? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Contact);
}
public override int GetHashCode()
=> Id.GetHashCode();
}
}

@ -3,7 +3,38 @@ namespace Model
{
public interface IUserLibraryManager : ILibraryManager
{
Task<Tuple<long, IEnumerable<Book>>> GetAllBooks(int index, int count, string sort = "");
Task<Tuple<long, IEnumerable<Book>>> GetBooksFromCollection(int index, int count, string sort = "");
Task<Book> AddBook(Book book);
Task<Book> AddBook(string id);
Task<Book> AddBookByIsbn(string isbn);
Task<bool> RemoveBook(Book book);
Task<bool> RemoveBook(string id);
Task<bool> RemoveBookByIsbn(string isbn);
Task<bool> AddToFavorites(Book book);
Task<bool> AddToFavorites(string bookId);
Task<bool> RemoveFromFavorites(Book book);
Task<bool> RemoveFromFavorites(string bookId);
Task<Book> UpdateBook(Book updatedBook);
Task<Contact> AddContact(Contact contact);
Task<bool> RemoveContact(Contact contact);
Task<bool> LendBook(Book book, Contact contact, DateTime? loanDate);
Task<bool> GetBackBook(Book book, DateTime? returnedDate);
Task<bool> BorrowBook(Book book, Contact owner, DateTime? borrowedDate);
Task<bool> GiveBackBook(Book book, DateTime? returnedDate);
Task<Tuple<long, IEnumerable<Loan>>> GetCurrentLoans(int index, int count);
Task<Tuple<long, IEnumerable<Loan>>> GetPastLoans(int index, int count);
Task<Tuple<long, IEnumerable<Borrowing>>> GetCurrentBorrowings(int index, int count);
Task<Tuple<long, IEnumerable<Borrowing>>> GetPastBorrowings(int index, int count);
Task<Tuple<long, IEnumerable<Contact>>> GetContacts(int index, int count);
}
}

@ -1,12 +1,27 @@
using System;
namespace Model
{
public class Loan //prêt
public class Loan : IEquatable<Loan> //prêt
{
public string Id { get; set; }
public Person Loaner { get; set; }
public Book Book { get; set; }
public Contact Loaner { get; set; }
public DateTime LoanedAt { get; set; }
public DateTime? ReturnedAt { get; set; }
public bool Equals(Loan? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Loan);
}
public override int GetHashCode()
=> Id.GetHashCode();
}
}

@ -7,13 +7,15 @@ namespace Model
public class Manager
{
private ILibraryManager LibraryManager { get; set; }
private IUserLibraryManager UserLibraryManager { get; set; }
public ReadOnlyCollection<Book> Books { get; private set; }
private List<Book> books = new();
public Manager(ILibraryManager libMgr)
public Manager(ILibraryManager libMgr, IUserLibraryManager userLibMgr)
{
LibraryManager = libMgr;
UserLibraryManager = userLibMgr;
Books = new ReadOnlyCollection<Book>(books);
}
@ -49,6 +51,56 @@ namespace Model
var result = await LibraryManager.GetAuthorsByName(substring, index, count, sort);
return (result.Item1, result.Item2);
}
public Task<Book> AddBookToCollection(string id)
{
return UserLibraryManager.AddBook(id);
}
public async Task<Book> GetBookByIdFromCollection(string id)
=> await UserLibraryManager.GetBookById(id);
public Task<Book> UpdateBook(Book book)
{
return UserLibraryManager.UpdateBook(book);
}
public Task<(long count, IEnumerable<Book> books)> GetBooksFromCollection(int index, int count, string sort = "")
{
var result = UserLibraryManager.GetBooksFromCollection(index, count, sort).Result;
return Task.FromResult((result.Item1, result.Item2));
}
public Task<(long count, IEnumerable<Contact> contacts)> GetContacts(int index, int count)
{
var result = UserLibraryManager.GetContacts(index, count).Result;
return Task.FromResult((result.Item1, result.Item2));
}
public Task<(long count, IEnumerable<Loan> loans)> GetCurrentLoans(int index, int count)
{
var result = UserLibraryManager.GetCurrentLoans(index, count).Result;
return Task.FromResult((result.Item1, result.Item2));
}
public Task<(long count, IEnumerable<Loan> loans)> GetPastLoans(int index, int count)
{
var result = UserLibraryManager.GetPastLoans(index, count).Result;
return Task.FromResult((result.Item1, result.Item2));
}
public Task<(long count, IEnumerable<Borrowing> borrowings)> GetCurrentBorrowings(int index, int count)
{
var result = UserLibraryManager.GetCurrentBorrowings(index, count).Result;
return Task.FromResult((result.Item1, result.Item2));
}
public Task<(long count, IEnumerable<Borrowing> borrowings)> GetPastBorrowings(int index, int count)
{
var result = UserLibraryManager.GetPastBorrowings(index, count).Result;
return Task.FromResult((result.Item1, result.Item2));
}
}
}

@ -6,8 +6,4 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
</Project>

@ -0,0 +1,13 @@
using System;
namespace Model
{
public enum Status
{
Unknown,
Finished,
Reading,
NotRead,
ToBeRead
}
}

@ -1,7 +1,7 @@
using System;
namespace Model
{
public class Work
public class Work : IEquatable<Work>
{
public string Id { get; set; }
public string Description { get; set; }
@ -9,6 +9,20 @@ namespace Model
public List<string> Subjects { get; set; } = new List<string>();
public List<Author> Authors { get; set; } = new List<Author>();
public Ratings Ratings { get; set; }
public bool Equals(Work? other)
=> Id == other.Id;
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(this, obj)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Work);
}
public override int GetHashCode()
=> Id.GetHashCode();
}
}

@ -6,10 +6,6 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\StubbedDTO\StubbedDTO.csproj" />

@ -0,0 +1,421 @@
using System;
using System.Collections.ObjectModel;
using Model;
using System.Linq;
using LibraryDTO;
using System.Xml.Linq;
namespace StubLib
{
public class UserLibraryStub : IUserLibraryManager
{
public ReadOnlyCollection<Book> Favorites { get; private set; }
private List<Book> favorites = new List<Book>();
public ReadOnlyCollection<Book> Books { get; private set; }
private List<Book> books = new List<Book>();
public ReadOnlyCollection<Contact> Contacts { get; private set; }
private List<Contact> contacts = new List<Contact>();
public ReadOnlyCollection<Loan> Loans { get; private set; }
private List<Loan> loans = new List<Loan>();
public ReadOnlyCollection<Borrowing> Borrowings { get; private set; }
private List<Borrowing> borrowings = new List<Borrowing>();
public ILibraryManager LibraryMgr { get; private set; }
public UserLibraryStub(ILibraryManager libraryMgr)
{
LibraryMgr = libraryMgr;
Favorites = new ReadOnlyCollection<Book>(favorites);
Books = new ReadOnlyCollection<Book>(books);
Borrowings = new ReadOnlyCollection<Borrowing>(borrowings);
Loans = new ReadOnlyCollection<Loan>(loans);
Contacts = new ReadOnlyCollection<Contact>(contacts);
contacts.AddRange(new Contact[]
{
new Contact { Id = "/contacts/01", FirstName = "Audrey", LastName = "Pouclet" },
new Contact { Id = "/contacts/02", FirstName = "Malika", LastName = "More" },
new Contact { Id = "/contacts/03", FirstName = "Antoine" },
});
books.AddRange(new Book[]
{
LibraryMgr.GetBookById("/books/OL25910297M").Result,
LibraryMgr.GetBookById("/books/OL26210208M").Result,
LibraryMgr.GetBookById("/books/OL27258011M").Result,
LibraryMgr.GetBookById("/books/OL28294024M").Result,
LibraryMgr.GetBookById("/books/OL28639494M").Result,
LibraryMgr.GetBookById("/books/OL35699439M").Result,
LibraryMgr.GetBookById("/books/OL37758347M").Result,
LibraryMgr.GetBookById("/books/OL38218739M").Result,
LibraryMgr.GetBookById("/books/OL38586212M").Result,
LibraryMgr.GetBookById("/books/OL8839071M").Result,
LibraryMgr.GetBookById("/books/OL8198056M").Result,
});
books[0].Status = Status.Finished;
books[0].UserNote = "Super bouquin de SF !";
books[0].UserRating = 4.5f;
loans.Add(new Loan { Id = "/loans/01", Book = books[0], Loaner = contacts[0], LoanedAt = new DateTime(2022, 7, 12), ReturnedAt = new DateTime(2023, 9, 1) });
books[1].Status = Status.ToBeRead;
books[2].Status = Status.Finished;
books[2].UserNote = "Des nouvelles de SF. Super auteur à découvrir !";
books[2].UserRating = 4.8f;
books[3].Status = Status.Finished;
books[3].UserRating = 4.0f;
loans.Add(new Loan { Id = "/loans/02", Book = books[3], Loaner = contacts[2], LoanedAt = new DateTime(2020, 12, 23), ReturnedAt = new DateTime(2021, 8, 13) } );
books[4].Status = Status.Finished;
books[4].UserNote = "Déjà moins connu que le premier, et pourtant...";
books[4].UserRating = 4.2f;
books[5].Status = Status.Finished;
books[5].UserNote = "Coup de coeur. Poétique, anarchique, philosophique... + SF. Du Deleuze et du Foucault chez Damasio";
books[5].UserRating = 4.9f;
books[6].Status = Status.NotRead;
books[7].Status = Status.Finished;
books[7].UserRating = 4.9f;
books[7].UserNote = "Chef d'oeuvre";
books[8].Status = Status.Finished;
books[8].UserRating = 4.2f;
books[8].UserNote = "Des nouvelles très réussies dont Rapport Minoritaire";
books[9].Status = Status.ToBeRead;
books[9].Status = Status.Reading;
borrowings.Add(new Borrowing
{
Id = "/borrowing/01", Owner = contacts[0],
Book = LibraryMgr.GetBookById("/books/OL27328194M").Result,
BorrowedAt = new DateTime(2023, 9, 7)
});
borrowings.Add(new Borrowing
{
Id = "/borrowing/02", Owner = contacts[1],
Book = LibraryMgr.GetBookById("/books/OL27989051M").Result,
BorrowedAt = new DateTime(2022, 7, 7),
ReturnedAt = new DateTime(2023, 3, 1)
});
borrowings.Add(new Borrowing
{
Id = "/borrowing/03", Owner = contacts[1],
Book = LibraryMgr.GetBookById("/books/OL35698073M").Result,
BorrowedAt = new DateTime(2022, 7, 7),
ReturnedAt = new DateTime(2022, 9, 1)
});
borrowings.Add(new Borrowing
{
Id = "/borrowing/04", Owner = contacts[1],
Book = LibraryMgr.GetBookById("/books/OL35698083M").Result,
BorrowedAt = new DateTime(2022, 7, 7),
ReturnedAt = new DateTime(2023, 8, 30)
});
}
public Task<Book> AddBook(Book book)
{
if(Books.Contains(book))
{
return Task.FromResult<Book>(null);
}
books.Add(book);
return Task.FromResult(book);
}
public async Task<Book> AddBook(string id)
{
if(Books.SingleOrDefault(b => b.Id == id) != null)
{
return null;
}
var book = await LibraryMgr.GetBookById(id);
books.Add(book);
return book;
}
public async Task<Book> AddBookByIsbn(string isbn)
{
if(Books.SingleOrDefault(b => b.ISBN13 == isbn) != null)
{
return null;
}
var book = await LibraryMgr.GetBookByISBN(isbn);
books.Add(book);
return book;
}
public Task<bool> RemoveBook(Book book)
{
return Task.FromResult(books.Remove(book));
}
public Task<bool> RemoveBook(string id)
{
return Task.FromResult(books.RemoveAll(b => b.Id == id) >= 0);
}
public Task<bool> RemoveBookByIsbn(string isbn)
{
return Task.FromResult(books.RemoveAll(b => b.ISBN13 == isbn) >= 0);
}
public Task<bool> AddToFavorites(Book book)
{
if(Favorites.Contains(book))
{
return Task.FromResult(false);
}
var bookToAdd = Books.SingleOrDefault(b => b.Id == book.Id);
if(bookToAdd == null)
{
return Task.FromResult(false);
}
favorites.Add(bookToAdd);
return Task.FromResult(true);
}
public Task<bool> AddToFavorites(string bookId)
{
if(Favorites.SingleOrDefault(b => b.Id == bookId) != null)
{
return Task.FromResult(false);
}
var book = Books.SingleOrDefault(b => b.Id == bookId);
if(book == null)
{
return Task.FromResult(false);
}
favorites.Add(book);
return Task.FromResult(true);
}
public Task<bool> RemoveFromFavorites(Book book)
{
return Task.FromResult(favorites.Remove(book));
}
public Task<bool> RemoveFromFavorites(string bookId)
{
return Task.FromResult(favorites.RemoveAll(b => b.Id == bookId) >= 0);
}
public Task<Contact> AddContact(Contact contact)
{
if(Contacts.Contains(contact))
{
return Task.FromResult<Contact>(null);
}
contacts.Add(contact);
return Task.FromResult(contact);
}
public Task<bool> RemoveContact(Contact contact)
{
return Task.FromResult(contacts.Remove(contact));
}
public Task<bool> LendBook(Book book, Contact contact, DateTime? loanDate = null)
{
if(!Books.Contains(book))
return Task.FromResult(false);
if(!Contacts.Contains(contact))
AddContact(contact);
Loan loan = new Loan { Book = book, Loaner = contact, LoanedAt = loanDate.GetValueOrDefault(DateTime.Now) };
if(Loans.Contains(loan))
return Task.FromResult(false);
loans.Add(loan);
return Task.FromResult(true);
}
public Task<bool> GetBackBook(Book book, DateTime? returnedDate = null)
{
if(!Books.Contains(book))
return Task.FromResult(false);
var loan = loans.SingleOrDefault(l => l.Book == book);
if(loan == null)
return Task.FromResult(false);
loan.ReturnedAt = returnedDate.GetValueOrDefault(DateTime.Now);
return Task.FromResult(true);
}
public Task<bool> BorrowBook(Book book, Contact owner, DateTime? borrowedDate = null)
{
if(!Books.Contains(book))
return Task.FromResult(false);
if(!Contacts.Contains(owner))
AddContact(owner);
Borrowing borrow = new Borrowing { Book = book, Owner = owner, BorrowedAt = borrowedDate.GetValueOrDefault(DateTime.Now) };
if(Borrowings.Contains(borrow))
return Task.FromResult(false);
borrowings.Add(borrow);
return Task.FromResult(true);
}
public Task<bool> GiveBackBook(Book book, DateTime? returnedDate = null)
{
if(!Books.Contains(book))
return Task.FromResult(false);
var borrow = borrowings.SingleOrDefault(b => b.Book == book);
if(borrow == null)
return Task.FromResult(false);
borrow.ReturnedAt = returnedDate.GetValueOrDefault(DateTime.Now);
return Task.FromResult(true);
}
public Task<Book> GetBookById(string id)
{
return Task.FromResult(Books.SingleOrDefault(b => b.Id == id));
}
public Task<Book> GetBookByISBN(string isbn)
{
return Task.FromResult(Books.SingleOrDefault(b => b.ISBN13 == isbn));
}
public Task<Tuple<long, IEnumerable<Book>>> GetBooksByTitle(string title, int index, int count, string sort = "")
{
var foundBooks = Books.Where(b => b.Title.Contains(title, StringComparison.InvariantCultureIgnoreCase));
return OrderBooks(foundBooks, index, count, sort);
}
public Task<Tuple<long, IEnumerable<Book>>> GetBooksByAuthorId(string authorId, int index, int count, string sort = "")
{
var foundBooks = Books.Where(b => b.Authors.Exists(a => a.Id.Contains(authorId))
|| b.Works.Exists(w => w.Authors.Exists(a => a.Id.Contains(authorId))));
return OrderBooks(books, index, count, sort);
}
public Task<Tuple<long, IEnumerable<Book>>> GetBooksByAuthor(string author, int index, int count, string sort = "")
{
var foundBooks = Books.Where(b => ContainsAuthorName(b, author));
return OrderBooks(books, index, count, sort);
}
public IEnumerable<Author> Authors
{
get
{
var bookAuthors = Books.SelectMany(b => b.Authors);
var workAuthors = Books.SelectMany(b => b.Works).SelectMany(w => w.Authors);
return bookAuthors.Union(workAuthors).Distinct();
}
}
public Task<Author> GetAuthorById(string id)
{
return Task.FromResult(Authors.SingleOrDefault(a => a.Id == id));
}
private Task<Tuple<long, IEnumerable<Author>>> OrderAuthors(IEnumerable<Author> authors, int index, int count, string sort = "")
{
switch(sort)
{
case "name":
authors = authors.OrderBy(a => a.Name);
break;
case "name_reverse":
authors = authors.OrderByDescending(a => a.Name);
break;
}
return Task.FromResult(Tuple.Create((long)authors.Count(), authors.Skip(index*count).Take(count)));
}
public Task<Tuple<long, IEnumerable<Author>>> GetAuthorsByName(string substring, int index, int count, string sort = "")
{
var foundAuthors = Authors.Where(a => a.Name.Contains(substring, StringComparison.InvariantCultureIgnoreCase)
|| a.AlternateNames.Exists(alt => alt.Contains(substring, StringComparison.InvariantCultureIgnoreCase)));
return OrderAuthors(foundAuthors, index, count, sort);
}
public Task<Book> UpdateBook(Book updatedBook)
{
if(!books.Contains(updatedBook))
{
return Task.FromResult<Book>(null);
}
books.Remove(updatedBook);
books.Add(updatedBook);
return Task.FromResult(updatedBook);
}
private Task<Tuple<long, IEnumerable<Book>>> OrderBooks(IEnumerable<Book> books, int index, int count, string sort = "")
{
switch(sort)
{
case "title":
books = books.OrderBy(b => b.Title);
break;
case "title_reverse":
books = books.OrderByDescending(b => b.Title);
break;
case "new":
books = books.OrderByDescending(b => b.PublishDate);
break;
case "old":
books = books.OrderBy(b => b.PublishDate);
break;
}
return Task.FromResult(Tuple.Create(books.LongCount(), books.Skip(index*count).Take(count)));
}
private bool ContainsAuthorName(Book book, string name)
{
IEnumerable<Author> authors = new List<Author>();
if(book.Authors != null && book.Authors.Count > 0)
{
authors = authors.Union(book.Authors);
}
if(book.Works != null)
{
var worksAuthors = book.Works.SelectMany(w => w.Authors).ToList();
if(worksAuthors.Count > 0)
authors = authors.Union(worksAuthors);
}
foreach(var author in authors)
{
if(author.Name.Contains(name, StringComparison.OrdinalIgnoreCase)
|| author.AlternateNames.Exists(alt => alt.Contains(name, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
}
return false;
}
public Task<Tuple<long, IEnumerable<Book>>> GetBooksFromCollection(int index, int count, string sort = "")
{
return OrderBooks(Books, index, count, sort);
}
public Task<Tuple<long, IEnumerable<Loan>>> GetCurrentLoans(int index, int count)
{
var currentLoans = Loans.Where(l => !l.ReturnedAt.HasValue);
return Task.FromResult(Tuple.Create(currentLoans.LongCount(), currentLoans.Skip(index*count).Take(count)));
}
public Task<Tuple<long, IEnumerable<Loan>>> GetPastLoans(int index, int count)
{
var currentLoans = Loans.Where(l => l.ReturnedAt.HasValue);
return Task.FromResult(Tuple.Create(currentLoans.LongCount(), currentLoans.Skip(index*count).Take(count)));
}
public Task<Tuple<long, IEnumerable<Borrowing>>> GetCurrentBorrowings(int index, int count)
{
var currentBorrowings = Borrowings.Where(l => !l.ReturnedAt.HasValue);
return Task.FromResult(Tuple.Create(currentBorrowings.LongCount(), currentBorrowings.Skip(index*count).Take(count)));
}
public Task<Tuple<long, IEnumerable<Borrowing>>> GetPastBorrowings(int index, int count)
{
var currentBorrowings = Borrowings.Where(l => l.ReturnedAt.HasValue);
return Task.FromResult(Tuple.Create(currentBorrowings.LongCount(), currentBorrowings.Skip(index*count).Take(count)));
}
public Task<Tuple<long, IEnumerable<Contact>>> GetContacts(int index, int count)
{
return Task.FromResult(Tuple.Create(Contacts.LongCount(), Contacts.Skip(index*count).Take(count)));
}
}
}

@ -1,4 +1,5 @@
using System.Data.SqlTypes;
using System.Reflection;
using DtoAbstractLayer;
using JsonReader;
using LibraryDTO;
@ -14,23 +15,27 @@ public class Stub : IDtoManager
public static List<WorkDTO> Works { get; set; } = new List<WorkDTO>();
public static string BasePath { get; set; } = "";
public static Assembly Assembly => typeof(Stub).Assembly;
static Stub()
{
foreach(var fileAuthor in new DirectoryInfo($"{BasePath}authors/").GetFiles())
{
using(StreamReader reader = File.OpenText(fileAuthor.FullName))
foreach(var resource in Assembly.GetManifestResourceNames().Where(n => n.Contains("authors")))
{
using(Stream stream = Assembly.GetManifestResourceStream(resource))
using(StreamReader reader = new StreamReader(stream))
{
Authors.Add(AuthorJsonReader.ReadAuthor(reader.ReadToEnd()));
}
}
foreach(var fileWork in new DirectoryInfo($"{BasePath}works/").GetFiles())
foreach(var resource in Assembly.GetManifestResourceNames().Where(n => n.Contains("works")))
{
var ratingsFile = $"{BasePath}ratings/{fileWork.Name.Insert((int)(fileWork.Name.Length - fileWork.Extension.Length), ".ratings")}";
using(StreamReader reader = File.OpenText(fileWork.FullName))
using(StreamReader readerRatings = File.OpenText(ratingsFile))
var ratingsResource = resource.Insert(resource.LastIndexOf('.'), ".ratings").Replace("works", "ratings");
using(Stream stream = Assembly.GetManifestResourceStream(resource))
using(StreamReader reader = new StreamReader(stream))
using(Stream streamRatings = Assembly.GetManifestResourceStream(ratingsResource))
using(StreamReader readerRatings = new StreamReader(streamRatings))
{
var work = WorkJsonReader.ReadWork(reader.ReadToEnd(), readerRatings.ReadToEnd());
if(work.Authors != null)
@ -44,9 +49,10 @@ public class Stub : IDtoManager
}
}
foreach(var fileBook in new DirectoryInfo($"{BasePath}books/").GetFiles())
{
using(StreamReader reader = File.OpenText(fileBook.FullName))
foreach(var resource in Assembly.GetManifestResourceNames().Where(n => n.Contains("books")))
{
using(Stream stream = Assembly.GetManifestResourceStream(resource))
using(StreamReader reader = new StreamReader(stream))
{
var book = BookJsonReader.ReadBook(reader.ReadToEnd());
foreach(var author in book.Authors.ToList())
@ -64,7 +70,6 @@ public class Stub : IDtoManager
Books.Add(book);
}
}
}
public Task<AuthorDTO> GetAuthorById(string id)
@ -124,7 +129,7 @@ public class Stub : IDtoManager
break;
}
return Task.FromResult(Tuple.Create((long)books.Count(), books.Skip(index*count).Take(count)));
return Task.FromResult(Tuple.Create(books.LongCount(), books.Skip(index*count).Take(count)));
}
public async Task<Tuple<long, IEnumerable<BookDTO>>> GetBooks(int index, int count, string sort = "")

@ -6,30 +6,31 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!--<ItemGroup>
<None Remove="books\" />
<None Remove="authors\" />
<None Remove="works\" />
<None Remove="ratings\" />
</ItemGroup>
<None Remove="authors\OL13066A.json" />
</ItemGroup>-->
<ItemGroup>
<Folder Include="books\" />
<Folder Include="authors\" />
<Folder Include="works\" />
<Folder Include="ratings\" />
</ItemGroup>
<ItemGroup>
<!--<ItemGroup>
<None Include="*\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
</ItemGroup>-->
<ItemGroup>
<ProjectReference Include="..\DtoAbstractLayer\DtoAbstractLayer.csproj" />
<ProjectReference Include="..\JsonReader\JsonReader.csproj" />
<ProjectReference Include="..\LibraryDTO\LibraryDTO.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="*\*.json" />
</ItemGroup>
</Project>

@ -0,0 +1 @@
{"alternate_names": ["Herbert George Wells", "H. Wells"], "type": {"key": "/type/author"}, "key": "/authors/OL13066A", "photos": [14364506, 6255164, 6255163, 5542282], "birth_date": "21 September 1866", "wikipedia": "http://en.wikipedia.org/wiki/H._G._Wells", "title": "(Wells, H. G.(Herbert George))", "fuller_name": "Herbert George Wells", "remote_ids": {"project_gutenberg": "30", "librarything": "wellshg", "goodreads": "880695", "wikidata": "Q42511", "librivox": "146", "isni": "0000000122832703", "viaf": "97006424", "amazon": "B08JKH6BRG"}, "personal_name": "H. G. Wells", "source_records": ["amazon:0353051969", "amazon:1545593752", "amazon:0353360031", "amazon:0343204800", "amazon:0900948442", "amazon:0359695256", "amazon:1541399528", "bwb:9798748338851", "bwb:9798711942399", "amazon:154230038X", "bwb:9798515967789", "amazon:1548545791", "amazon:167576655X", "amazon:1096406985", "amazon:0342716778", "amazon:0343312492", "amazon:1642262722", "amazon:1539515087", "bwb:9798515810764", "bwb:9798594591745", "amazon:2329219377", "bwb:9798588040884", "amazon:151971002X", "amazon:2070500179", "amazon:1535132841", "ia:warofworlds0000well_p4j3", "bwb:9798589404036", "amazon:8380321359", "bwb:9798549302624", "amazon:1530609798", "amazon:1420938495", "amazon:1006147551", "bwb:9798458602198", "bwb:9798520694045", "bwb:9780342740260", "bwb:9798541851458", "bwb:9798653436154", "amazon:9385018116", "amazon:1014334756", "bwb:9798459326819", "bwb:9798796783801", "amazon:1006147721", "amazon:1536901911", "bwb:9798442116618", "bwb:9798446719273", "bwb:9798715872197", "bwb:9798411853216", "bwb:9781398812130", "bwb:9798537902386", "bwb:9798537873945", "bwb:9798578578908", "bwb:9798760273000", "bwb:9798708376572", "bwb:9780342996100", "promise:bwb_daily_pallets_2022-03-17", "bwb:9798833824375", "ia:warofworldsillus0000well", "bwb:9781958437681", "amazon:1298631807", "bwb:9798709836068", "promise:bwb_daily_pallets_2022-08-23", "bwb:9798685274663", "promise:bwb_daily_pallets_2022-08-01", "bwb:9781728974033", "bwb:9781731472441", "bwb:9781790605743", "marc:marc_columbia/Columbia-extract-20221130-030.mrc:206198520:1318", "bwb:9798840336311", "promise:bwb_daily_pallets_2022-12-19", "amazon:8461947932", "bwb:9798364688729", "amazon:201998198X", "bwb:9781549506130"], "name": "H. G. Wells", "death_date": "13 August 1946", "bio": "Herbert George Wells was an English author, best known for his work in the science fiction genre. He was also a prolific writer in many genres, including contemporary novels, history, politics and social commentary.", "latest_revision": 44, "revision": 44, "created": {"type": "/type/datetime", "value": "2008-04-01T03:28:50.625462"}, "last_modified": {"type": "/type/datetime", "value": "2023-08-31T06:46:36.893522"}}

@ -0,0 +1 @@
{"location": "Paris, France", "photos": [5548027], "type": {"key": "/type/author"}, "death_date": "24 November 1985", "alternate_names": ["Rene\u0301 Barjavel", "Barjavel Ren\u00e9", "Rene Barjavel", "Barjavel Rene"], "key": "/authors/OL152472A", "source_records": ["amazon:8498724929", "promise:bwb_daily_pallets_2022-03-17", "promise:bwb_daily_pallets_2021-02-03", "amazon:2070361691", "promise:bwb_daily_pallets_2021-02-17"], "birth_date": "1911-01-24", "name": "Ren\u00e9 Barjavel", "bio": "Ren\u00e9 Barjavel born in Nyons, France, the son of a baker. In 1914, while his father served in World War I, his mother ran the bakery, and he was left alone much of the time to discover the world through exploration and reading. His his mother died of sleeping sickness in 1922, when he was just 11 years old, and he was sent to boarding school in Nyons. In 1923, when his father was unable to pay the school's fees to continue his studies, he became the proteg\u00e9 of the school's director, Abel Boisselier, and accompanied him to the college in Cusset. During his stay there, which lasted until he ran out of funds in 1927, he continued to study literature. After leaving school, he worked worked several jobs, including as a real estate agent and a bank employee, until 1929 when he became a journalist in Progress Allier in Moulins.\r\n\r\nIn 1935, he met the publisher Robert Deno\u00ebl, and he moved to Paris to work at \u00c9ditions Deno\u00ebl. In 1936 he married Madeleine de Wattripont. While working at Deno\u00ebl, he continued to work as a journalist for the weekly Le Merle Blanc, where he wrote film reviews. In 1939, he joined the war with Germany and was sent to the Pyrenees, but returned to Paris when the armistice was declared and Deno\u00ebl re-opened his publishing house. During this time, his first novel, Roland, le chevalier plus fort que le lion (Roland, the Knight More Proud than the Lion) (1942), was published, with help from Deno\u00ebl. He wrote Le Voyageur imprudent (Future Times Three) in 1943, and became the first writer to present the famous grandfather paradox of time travel. In 1944 he became literary director at \u00c9ditions Deno\u00ebl. In 1945, Deno\u00ebl was killed.\r\n\r\nAfter the war, and the failure of his latest novel, Le diable l'emporte (The Devil Wins) (1948), he left novel-writing for the cinema. However, he contracted tuberculosis and ran out of money before completing his first project, \"Barabbas, pour qui Dieu ne fut qu\u2019un temps\". He spent some time recovering in the south of France, returning to Paris in 1951.\r\n\r\nHe worked as a screenwriter in Paris, and in 1962 he became involved in science fiction, at that time a growing fiction genre in France. He published the novel Colomb de la lune (Columbus of the Moon) in 1962. In 1968 he published La Nuit des temps (The Ice People), which was very successful and popular, and won the Prix des libraires. In 1969, he began a weekly column in the Sunday newspaper Les Libres Propos. In 1972, he was a co-founder of the Prix de science-fiction Apollo, and was on the jury. In 1981, at age 70, he stopped writing his columns in the Journal du Dimanche and resumed writing novels. He died in 1985, having written over 25 novels and several screenplays.", "wikipedia": "http://en.wikipedia.org/wiki/Ren%C3%A9_Barjavel", "personal_name": "Ren\u00e9 Barjavel", "remote_ids": {"viaf": "99967531", "wikidata": "Q562556", "isni": "0000000109285020"}, "latest_revision": 7, "revision": 7, "created": {"type": "/type/datetime", "value": "2008-04-01T03:28:50.625462"}, "last_modified": {"type": "/type/datetime", "value": "2023-02-20T12:24:20.987187"}}

@ -0,0 +1 @@
{"photos": [8567902], "created": {"type": "/type/datetime", "value": "2008-04-01T03:28:50.625462"}, "latest_revision": 5, "name": "Ted Chiang", "key": "/authors/OL1604887A", "personal_name": "Ted Chiang", "revision": 5, "type": {"key": "/type/author"}, "last_modified": {"type": "/type/datetime", "value": "2020-09-30T12:42:07.389520"}, "remote_ids": {"viaf": "85807457", "wikidata": "Q503095", "isni": "0000000109218979"}}

@ -0,0 +1 @@
{"name": "Michael A. Hiltzik", "personal_name": "Michael A. Hiltzik", "last_modified": {"type": "/type/datetime", "value": "2008-09-07 09:06:34.939911"}, "key": "/authors/OL239209A", "type": {"key": "/type/author"}, "id": 623786, "revision": 2}

@ -0,0 +1 @@
{"name": "Pennac", "last_modified": {"type": "/type/datetime", "value": "2008-04-30 08:14:56.482104"}, "key": "/authors/OL3113661A", "type": {"key": "/type/author"}, "id": 11969949, "revision": 1}

@ -0,0 +1 @@
{"created": {"type": "/type/datetime", "value": "2008-04-01T03:28:50.625462"}, "latest_revision": 6, "name": "Daniel Pennac", "key": "/authors/OL55888A", "personal_name": "Daniel Pennac", "birth_date": "1944", "revision": 6, "type": {"key": "/type/author"}, "last_modified": {"type": "/type/datetime", "value": "2020-09-30T12:22:32.446485"}, "remote_ids": {"viaf": "29539018", "wikidata": "Q332689", "isni": "0000000121258295"}}

@ -0,0 +1 @@
{"birth_date": "1802", "key": "/authors/OL5989984A", "personal_name": "Alexandre Dumas", "remote_ids": {"wikidata": "Q38337", "isni": "0000000121012885", "viaf": "51688902"}, "source_records": ["ia:lacomtessedechar0000duma_c0h3", "amazon:0343253933", "ia:sanjianke0008dazh", "amazon:1087918545", "amazon:1361580682", "amazon:1374919551", "amazon:2091870757", "amazon:2363583477", "bwb:9798725078565", "amazon:1374990906", "amazon:1374997609", "amazon:1374828378", "ia:lestroismousquet0000duma_h9z1", "amazon:1080726861", "amazon:1376310082", "amazon:1530054605", "amazon:1548458821", "amazon:2743400463", "amazon:1535180129"], "alternate_names": ["Da Zhong Ma", "Alexander Dumas", "Alejandro Dumas"], "type": {"key": "/type/author"}, "death_date": "1870", "name": "Alexandre Dumas", "title": "(Dumas, Alexandre, 1802-1870)", "photos": [12919454], "latest_revision": 30, "revision": 30, "created": {"type": "/type/datetime", "value": "2008-10-27T00:19:50.151000"}, "last_modified": {"type": "/type/datetime", "value": "2022-09-28T00:27:01.465804"}}

@ -0,0 +1 @@
{"source_records": ["bwb:9781801108454"], "type": {"key": "/type/author"}, "personal_name": "Adrian Tchaikovsky", "links": [{"title": "Wikipedia", "url": "https://en.wikipedia.org/wiki/Adrian_Tchaikovsky", "type": {"key": "/type/link"}}], "birth_date": "1972", "name": "Adrian Tchaikovsky", "key": "/authors/OL7468980A", "remote_ids": {"storygraph": "5a684299-8db8-4c82-8f0c-26f2c6c8ad7b", "viaf": "102929550", "wikidata": "Q4685389"}, "latest_revision": 8, "revision": 8, "created": {"type": "/type/datetime", "value": "2019-03-06T04:53:32.329596"}, "last_modified": {"type": "/type/datetime", "value": "2023-08-07T23:24:42.671051"}}

@ -0,0 +1,35 @@
{
"publishers": [ "Denoe\u0308l" ],
"title": "Le voyageur imprudent.",
"notes": {
"type": "/type/text",
"value": "French text."
},
"identifiers": {
"librarything": [ "182578" ],
"goodreads": [ "106786" ]
},
"covers": [ 7267304 ],
"languages": [ { "key": "/languages/fre" } ],
"isbn_10": [ "2070364852" ],
"isbn_13": [ "9782070364852" ],
"publish_date": "1958",
"publish_country": "fr ",
"key": "/books/OL18547803M",
"authors": [ { "key": "/authors/OL152472A" } ],
"publish_places": [ "Paris" ],
"works": [ { "key": "/works/OL11466820W" } ],
"type": { "key": "/type/edition" },
"local_id": [ "urn:bwbsku:O6-CHO-999" ],
"source_records": [ "promise:bwb_daily_pallets_2022-05-23" ],
"latest_revision": 7,
"revision": 7,
"created": {
"type": "/type/datetime",
"value": "2008-10-17T19:42:31.933490"
},
"last_modified": {
"type": "/type/datetime",
"value": "2023-01-14T06:54:32.286139"
}
}

@ -0,0 +1,30 @@
{
"publishers": [ "Gallimard" ],
"number_of_pages": 736,
"covers": [ 7891413 ],
"physical_format": "Paperback",
"key": "/books/OL26210208M",
"isbn_13": [ "9782070464234" ],
"pagination": "700",
"classifications": {},
"title": "La Horde du Contrevent",
"identifiers": { "goodreads": [ "25135826" ] },
"languages": [ { "key": "/languages/fre" } ],
"isbn_10": [ "2070464237" ],
"publish_date": "2015",
"works": [ { "key": "/works/OL15505243W" } ],
"type": { "key": "/type/edition" },
"physical_dimensions": "17.8 x 10.8 x centimeters",
"local_id": [ "urn:bwbsku:W7-DET-916" ],
"source_records": [ "promise:bwb_daily_pallets_2022-09-01" ],
"latest_revision": 5,
"revision": 5,
"created": {
"type": "/type/datetime",
"value": "2017-01-01T23:14:47.314119"
},
"last_modified": {
"type": "/type/datetime",
"value": "2022-12-04T13:20:06.799989"
}
}

@ -0,0 +1,26 @@
{
"publishers": [ "Folio", "FOLIO", "GALLIMARD" ],
"source_records": [ "amazon:207285329X" ],
"title": "Dans la toile du temps",
"identifiers": { "amazon": [ "207285329X" ] },
"isbn_13": [ "9782072853296" ],
"covers": [ 8792712 ],
"physical_format": "mass market paperback",
"isbn_10": [ "207285329X" ],
"publish_date": "2019",
"key": "/books/OL27328194M",
"authors": [ { "key": "/authors/OL7468980A" } ],
"works": [ { "key": "/works/OL20148560W" } ],
"type": { "key": "/type/edition" },
"number_of_pages": 704,
"latest_revision": 2,
"revision": 2,
"created": {
"type": "/type/datetime",
"value": "2019-10-02T21:11:02.625762"
},
"last_modified": {
"type": "/type/datetime",
"value": "2023-04-07T18:08:17.514602"
}
}

@ -1 +1,32 @@
{"publishers": ["GALLIMARD"], "last_modified": {"type": "/type/datetime", "value": "2020-06-28T05:17:49.626242"}, "source_records": ["amazon:2070469875"], "title": "La trilogie Spin", "notes": {"type": "/type/text", "value": "Source title: La trilogie Spin (Folio SF - XL) (French Edition)"}, "number_of_pages": 1120, "isbn_13": ["9782070469871"], "covers": [10218906], "created": {"type": "/type/datetime", "value": "2020-06-28T05:17:49.626242"}, "physical_format": "paperback", "isbn_10": ["2070469875"], "publish_date": "Jun 02, 2016", "key": "/books/OL28294024M", "authors": [{"key": "/authors/OL7876839A"}, {"key": "/authors/OL3113900A"}], "latest_revision": 1, "works": [{"key": "/works/OL20889233W"}], "type": {"key": "/type/edition"}, "revision": 1}
{
"publishers": [ "GALLIMARD" ],
"last_modified": {
"type": "/type/datetime",
"value": "2020-06-28T05:17:49.626242"
},
"source_records": [ "amazon:2070469875" ],
"title": "La trilogie Spin",
"notes": {
"type": "/type/text",
"value": "Source title: La trilogie Spin (Folio SF - XL) (French Edition)"
},
"number_of_pages": 1120,
"isbn_13": [ "9782070469871" ],
"covers": [ 10218906 ],
"created": {
"type": "/type/datetime",
"value": "2020-06-28T05:17:49.626242"
},
"physical_format": "paperback",
"isbn_10": [ "2070469875" ],
"publish_date": "Jun 02, 2016",
"key": "/books/OL28294024M",
"authors": [
{ "key": "/authors/OL7876839A" },
{ "key": "/authors/OL3113900A" }
],
"latest_revision": 1,
"works": [ { "key": "/works/OL20889233W" } ],
"type": { "key": "/type/edition" },
"revision": 1
}

@ -0,0 +1,29 @@
{
"type": { "key": "/type/edition" },
"title": "Tour de Babylone",
"authors": [ { "key": "/authors/OL1604887A" } ],
"publish_date": "Apr 01, 2010",
"source_records": [ "amazon:2070406881" ],
"number_of_pages": 416,
"publishers": [ "Gallimard Education" ],
"isbn_10": [ "2070406881" ],
"isbn_13": [ "9782070406883" ],
"physical_format": "pocket book",
"notes": {
"type": "/type/text",
"value": "Source title: Tour de Babylone (Folio Science Fiction) (French Edition)"
},
"works": [ { "key": "/works/OL27687397W" } ],
"key": "/books/OL37758347M",
"covers": [ 14058074 ],
"latest_revision": 2,
"revision": 2,
"created": {
"type": "/type/datetime",
"value": "2022-03-15T04:11:29.461242"
},
"last_modified": {
"type": "/type/datetime",
"value": "2023-04-16T15:24:11.712871"
}
}

@ -0,0 +1,38 @@
{
"publishers": [ "Collins" ],
"identifiers": {
"goodreads": [ "1101290" ],
"librarything": [ "68418" ]
},
"subtitle": "Xerox PARC and the Dawn of the Computer Age",
"weight": "13.9 ounces",
"covers": [ 684348 ],
"physical_format": "Paperback",
"key": "/books/OL8198056M",
"authors": [ { "key": "/authors/OL239209A" } ],
"subjects": [ "Industries - General", "Research", "Corporate & Business History - General", "California", "Business & Economics", "Business / Economics / Finance", "Palo Alto Research Center", "Business/Economics", "Palo Alto", "History", "Computer Industry", "Business & Economics / Industries", "Xerox Corporation.", "Computer Science" ],
"languages": [ { "key": "/languages/eng" } ],
"first_sentence": {
"type": "/type/text",
"value": "The photograph shows a handsome man in a checked sport shirt, his boyish face half-obscured by a cloud of pipe smoke."
},
"title": "Dealers of Lightning",
"number_of_pages": 448,
"isbn_13": [ "9780887309892" ],
"isbn_10": [ "0887309895" ],
"publish_date": "April 4, 2000",
"works": [ { "key": "/works/OL1987733W" } ],
"type": { "key": "/type/edition" },
"physical_dimensions": "8.1 x 5.3 x 1.2 inches",
"source_records": [ "bwb:9780887309892" ],
"latest_revision": 7,
"revision": 7,
"created": {
"type": "/type/datetime",
"value": "2008-04-29T15:03:11.581851"
},
"last_modified": {
"type": "/type/datetime",
"value": "2021-12-27T03:34:56.026678"
}
}

@ -0,0 +1,34 @@
{
"publishers": [ "Editions Flammarion" ],
"number_of_pages": 420,
"weight": "7 ounces",
"covers": [ 968374 ],
"physical_format": "Mass Market Paperback",
"last_modified": {
"type": "/type/datetime",
"value": "2020-07-15T08:55:39.381795"
},
"latest_revision": 7,
"key": "/books/OL8839071M",
"authors": [ { "key": "/authors/OL3113661A" } ],
"subjects": [ "Language readers" ],
"edition_name": "1 edition",
"languages": [ { "key": "/languages/fre" } ],
"classifications": {},
"title": "La Petite Marchande De Prose",
"identifiers": {
"librarything": [ "586212" ],
"goodreads": [ "355884" ]
},
"created": {
"type": "/type/datetime",
"value": "2008-04-30T08:14:56.482104"
},
"isbn_13": [ "9782070403684" ],
"isbn_10": [ "2070403688" ],
"publish_date": "October 3, 1997",
"works": [ { "key": "/works/OL705036W" } ],
"type": { "key": "/type/edition" },
"physical_dimensions": "7 x 4.2 x 0.9 inches",
"revision": 7
}

@ -0,0 +1,29 @@
{
"publishers": [ "Hachette" ],
"physical_format": "Unknown Binding",
"source_records": [ "amazon:2010144236:2-au:1680493645:142556", "promise:bwb_daily_pallets_2020-11-19", "promise:bwb_daily_pallets_2021-03-30" ],
"title": "L'homme Invisible",
"identifiers": {
"librarything": [ "21214" ],
"goodreads": [ "1337072" ]
},
"isbn_13": [ "9782010144233" ],
"isbn_10": [ "2010144236" ],
"key": "/books/OL9797565M",
"authors": [ { "key": "/authors/OL13066A" } ],
"oclc_numbers": [ "417795539" ],
"works": [ { "key": "/works/OL52266W" } ],
"type": { "key": "/type/edition" },
"subjects": [ "Language readers" ],
"local_id": [ "urn:bwbsku:KO-352-184", "urn:bwbsku:UE-795-266" ],
"latest_revision": 9,
"revision": 9,
"created": {
"type": "/type/datetime",
"value": "2008-04-30T09:38:13.731961"
},
"last_modified": {
"type": "/type/datetime",
"value": "2023-01-14T23:55:47.329525"
}
}

@ -0,0 +1 @@
{"summary": {"average": 4.5, "count": 2, "sortable": 2.61203319071753}, "counts": {"1": 0, "2": 0, "3": 0, "4": 1, "5": 1}}

@ -0,0 +1 @@
{"summary": {"average": 4.2, "count": 5, "sortable": 2.8894368430603796}, "counts": {"1": 0, "2": 1, "3": 0, "4": 1, "5": 3}}

@ -0,0 +1 @@
{"summary": {"average": 4.0, "count": 7, "sortable": 3.0399841220661994}, "counts": {"1": 0, "2": 0, "3": 2, "4": 3, "5": 2}}

@ -0,0 +1 @@
{"summary": {"average": null, "count": 0}, "counts": {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0}}

@ -0,0 +1 @@
{"summary": {"average": 4.5, "count": 2, "sortable": 2.61203319071753}, "counts": {"1": 0, "2": 0, "3": 0, "4": 1, "5": 1}}

@ -0,0 +1 @@
{"summary": {"average": 3.8266666666666667, "count": 75, "sortable": 3.5620352304827025}, "counts": {"1": 4, "2": 5, "3": 16, "4": 25, "5": 25}}

@ -0,0 +1 @@
{"summary": {"average": 3.5, "count": 2, "sortable": 2.4163369210786563}, "counts": {"1": 0, "2": 0, "3": 1, "4": 1, "5": 0}}

@ -0,0 +1 @@
{"title": "Le voyageur imprudent", "key": "/works/OL11466820W", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL152472A"}}], "type": {"key": "/type/work"}, "covers": [7267304], "first_publish_date": "1958", "latest_revision": 4, "revision": 4, "created": {"type": "/type/datetime", "value": "2009-12-11T04:42:14.137010"}, "last_modified": {"type": "/type/datetime", "value": "2023-02-20T15:43:39.602393"}}

@ -0,0 +1 @@
{"title": "La Horde du Contrevent", "key": "/works/OL15505243W", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL3980331A"}}], "type": {"key": "/type/work"}, "covers": [6670450], "latest_revision": 2, "revision": 2, "created": {"type": "/type/datetime", "value": "2010-11-25T19:33:55.977790"}, "last_modified": {"type": "/type/datetime", "value": "2022-11-15T14:44:49.779753"}}

@ -0,0 +1 @@
{"subtitle": "Xerox PARC and the dawn of the computer age", "description": {"type": "/type/text", "value": "In the bestselling tradition of The Soul of a New Machine, Dealers of Lightning is a fascinating journey of intellectual creation. In the 1970s and '80s, Xerox Corporation brought together a brain-trust of engineering geniuses, a group of computer eccentrics dubbed PARC. This brilliant group created several monumental innovations that triggered a technological revolution, including the first personal computer, the laser printer, and the graphical interface (one of the main precursors of the Internet), only to see these breakthroughs rejected by the corporation. Yet, instead of giving up, these determined inventors turned their ideas into empires that radically altered contemporary life and changed the world.Based on extensive interviews with the scientists, engineers, administrators, and executives who lived the story, this riveting chronicle details PARC's humble beginnings through its triumph as a hothouse for ideas, and shows why Xerox was never able to grasp, and ultimately exploit, the cutting-edge innovations PARC delivered. Dealers of Lightning offers an unprecedented look at the ideas, the inventions, and the individuals that propelled Xerox PARC to the frontier of technohistory--and the corporate machinations that almost prevented it from achieving greatness."}, "title": "Dealers of Lightning", "lc_classifications": ["QA76.27 .H55 1999"], "covers": [684348], "first_sentence": {"type": "/type/text", "value": "The photograph shows a handsome man in a checked sport shirt, his boyish face half-obscured by a cloud of pipe smoke."}, "subject_places": ["California", "Palo Alto"], "first_publish_date": "1999", "key": "/works/OL1987733W", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL239209A"}}], "dewey_number": ["004/.0720794/73"], "type": {"key": "/type/work"}, "subjects": ["History", "Research", "Xerox Corporation. Palo Alto Research Center", "Computer science", "Xerox Corporation", "Microcomputers", "Grafische gebruikersinterfaces", "Computer scientists", "Computers, history", "Xerox corporation", "Computer industry"], "latest_revision": 13, "revision": 13, "created": {"type": "/type/datetime", "value": "2009-12-09T22:40:23.717604"}, "last_modified": {"type": "/type/datetime", "value": "2021-12-25T12:06:42.488356"}}

@ -0,0 +1 @@
{"title": "Dans la toile du temps", "created": {"type": "/type/datetime", "value": "2019-10-02T21:11:02.625762"}, "covers": [8792712], "last_modified": {"type": "/type/datetime", "value": "2019-10-02T21:11:02.625762"}, "latest_revision": 1, "key": "/works/OL20148560W", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL7468980A"}}], "type": {"key": "/type/work"}, "revision": 1}

@ -0,0 +1 @@
{"type": {"key": "/type/work"}, "title": "Tour de Babylone", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL1604887A"}}], "key": "/works/OL27687397W", "covers": [14058074], "latest_revision": 2, "revision": 2, "created": {"type": "/type/datetime", "value": "2022-03-15T04:11:29.461242"}, "last_modified": {"type": "/type/datetime", "value": "2023-04-16T15:24:11.712871"}}

@ -0,0 +1 @@
{"description": "This book is the story of Griffin, a scientist who creates a serum to render himself invisible, and his descent into madness that follows.", "covers": [6419199, 8237014, 368526, 2324378, 911204, 278563, 1260699, 1964114, 1288569, 11876542, 6160924, 2972211, 6930045, 8443536, 8460593, 8586633, 8597931, 8665880, 8665881, 8782856, 8783092, 8890102, 9071724, 9195165, 10187589, 10767011, 8785301, 8915576, 9177146, 10079814, 10105791, 10455175, 11194264, 5268879, 11515689, 11652598, 12029238, 8571040, 11475787, 10508513, 12106436, 10562269, 8757822, 11312159, 8423448, 10630463, 406825, 11642139, 2132527, 8389242, 10849644, 10850558, 12119680, 8775825, 11514004, 10690885], "key": "/works/OL52266W", "authors": [{"author": {"key": "/authors/OL13066A"}, "type": {"key": "/type/author_role"}}], "title": "The Invisible Man", "subjects": ["Ciencia-ficci\u00f3n", "Classic Literature", "Fiction", "Mentally ill", "Science Fiction & Fantasy", "Science fiction", "Scientists", "English Science fiction", "Experiments", "Adaptations", "Time travel", "British and irish fiction (fictional works by one author)", "England, fiction", "Scientists, fiction", "Children's fiction", "Mystery and detective stories", "Literature and fiction, science fiction", "Fiction, science fiction, general", "English literature", "English fiction, history and criticism, 20th century", "Wells, h. g. (herbert george), 1866-1946", "FICTION / Classics", "FICTION / Science Fiction / General", "FICTION / Horror", "Juvenile fiction", "Science", "Roman pour la jeunesse", "Comic and Graphic Books", "Cartoons and comics", "Comic books, strips", "Animal experimentation", "English language", "French", "Korean", "German", "Fiction, general", "Fiction, historical, general", "Elementary education of adults", "Reading, remedial teaching", "Spanish language", "Readers", "Horror tales", "Psychological fiction"], "type": {"key": "/type/work"}, "subject_times": ["19th century"], "first_publish_date": "1897", "subject_people": ["H. G. Wells (1866-1946)"], "latest_revision": 63, "revision": 63, "created": {"type": "/type/datetime", "value": "2009-10-15T16:30:47.596577"}, "last_modified": {"type": "/type/datetime", "value": "2023-03-10T13:21:44.920976"}}

@ -0,0 +1 @@
{"title": "La petite marchande de prose", "covers": [969779], "key": "/works/OL705036W", "authors": [{"type": {"key": "/type/author_role"}, "author": {"key": "/authors/OL55888A"}}], "type": {"key": "/type/work"}, "subjects": ["Brothers and sisters, fiction", "Fiction, mystery & detective, general"], "latest_revision": 4, "revision": 4, "created": {"type": "/type/datetime", "value": "2009-12-09T01:56:15.425517"}, "last_modified": {"type": "/type/datetime", "value": "2022-05-25T03:04:40.343020"}}

@ -0,0 +1,100 @@
// See https://aka.ms/new-console-template for more information
using Model;
using static System.Console;
WriteLine("Test LibraryStub.GetBookByISBN");
StubLib.LibraryStub libStub = new StubLib.LibraryStub();
var book = await libStub.GetBookByISBN("9782330033118");
WriteLine(book.Title);
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetBookByISBN");
Manager manager = new Manager(libStub, new StubLib.UserLibraryStub(libStub));
var book2 = await manager.GetBookByISBN("9782330033118");
WriteLine(book2.Title);
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetBooksByAuthor");
var booksFromHerbert = await manager.GetBooksByAuthor("herbert", 0, 10);
foreach(var b in booksFromHerbert.books)
{
WriteLine($"\t{b.Title}");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetBooks");
var books = await manager.GetBooksByTitle("", 0, 100);
foreach(var b in books.books)
{
WriteLine($"\t{b.Title}");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.AddBook");
var book1 = await manager.AddBookToCollection("/books/OL25910297M");
if(book1 == null) book1 = await manager.GetBookByIdFromCollection("/books/OL25910297M");
book1.Status = Status.Finished;
book1.UserRating = 5;
book1.UserNote = "Trop bien !";
manager.UpdateBook(book1);
manager.AddBookToCollection("/books/OL26210208M");
manager.AddBookToCollection("/books/OL27258011M");
var mybooks = await manager.GetBooksFromCollection(0, 100);
foreach(var b in mybooks.books)
{
WriteLine($"\t{b.Title} {b.UserRating ?? -1}");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetContacts");
var contacts = await manager.GetContacts(0, 100);
foreach(var c in contacts.contacts)
{
WriteLine($"\t{c.FirstName} {c.LastName}");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetCurrentLoans");
var loans = await manager.GetCurrentLoans(0, 100);
foreach(var l in loans.loans)
{
WriteLine($"\t{l.Book.Title} -> {l.Loaner.FirstName} {l.Loaner.LastName} ({l.LoanedAt.ToShortDateString()})");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetPastLoans");
var loans2 = await manager.GetPastLoans(0, 100);
foreach(var l in loans2.loans)
{
WriteLine($"\t{l.Book.Title} -> {l.Loaner.FirstName} {l.Loaner.LastName} ({l.LoanedAt.ToShortDateString()})");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetCurrentBorrowings");
var borrowings = await manager.GetCurrentBorrowings(0, 100);
foreach(var b in borrowings.borrowings)
{
WriteLine($"\t{b.Book.Title} -> {b.Owner.FirstName} {b.Owner.LastName} ({b.BorrowedAt.ToShortDateString()})");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
WriteLine("Test Manager.GetPastBorrowings");
var borrowings2 = await manager.GetPastBorrowings(0, 100);
foreach(var b in borrowings2.borrowings)
{
WriteLine($"\t{b.Book.Title} -> {b.Owner.FirstName} {b.Owner.LastName} ({b.BorrowedAt.ToShortDateString()})");
}
WriteLine(new string('*', WindowWidth));
WriteLine();
ReadLine();

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Model\Model.csproj" />
<ProjectReference Include="..\..\Stub\Stub.csproj" />
</ItemGroup>
</Project>

@ -4,8 +4,4 @@
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq" Version="4.3.0" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

@ -0,0 +1,10 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2124_88346)">
<path d="M1 20.8125C1 23.2383 2.23047 24.4453 4.67969 24.4453H24.2852C26.3828 24.4453 27.6133 23.2266 27.6133 20.8125V11.2031H1V20.8125ZM1 9.66797H27.6133V8.77734C27.6133 6.36328 26.3711 5.14453 23.9336 5.14453H12.6836C11.8867 5.14453 11.4062 4.95703 10.8203 4.45312L10.1055 3.86719C9.33203 3.21094 8.73438 3 7.57422 3H4.23438C2.18359 3 1 4.17188 1 6.52734V9.66797Z" fill="black" fill-opacity="0.85"/>
</g>
<defs>
<clipPath id="clip0_2124_88346">
<rect width="26.6133" height="21.5977" fill="white" transform="translate(1 3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 690 B

@ -0,0 +1,10 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2124_88345)">
<path d="M4.67969 24.4453H24.2852C26.3828 24.4453 27.6133 23.2266 27.6133 20.8125V8.77734C27.6133 6.36328 26.3711 5.14453 23.9336 5.14453H12.6836C11.8867 5.14453 11.4062 4.95703 10.8203 4.45312L10.1055 3.86719C9.33203 3.21094 8.73438 3 7.57422 3H4.23438C2.18359 3 1 4.17188 1 6.52734V20.8125C1 23.2383 2.23047 24.4453 4.67969 24.4453ZM4.70312 22.5586C3.53125 22.5586 2.88672 21.9375 2.88672 20.7188V6.63281C2.88672 5.47266 3.49609 4.875 4.62109 4.875H7.09375C7.86719 4.875 8.33594 5.07422 8.93359 5.57812L9.64844 6.17578C10.4102 6.80859 11.0312 7.03125 12.1914 7.03125H23.8984C25.0586 7.03125 25.7266 7.66406 25.7266 8.88281V20.7305C25.7266 21.9375 25.0586 22.5586 23.8984 22.5586H4.70312ZM2.14844 11.3203H26.4531V9.55078H2.14844V11.3203Z" fill="black" fill-opacity="0.85"/>
</g>
<defs>
<clipPath id="clip0_2124_88345">
<rect width="26.6133" height="21.5977" fill="white" transform="translate(1 3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Loading…
Cancel
Save