Skip to content

Commit aeeb336

Browse files
authored
Merge pull request #46 from seanboyce/dev
New release
2 parents b932ee9 + a8abff8 commit aeeb336

33 files changed

Lines changed: 1859 additions & 124 deletions

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ RUN mkdir /app
1111

1212
WORKDIR /app
1313

14-
COPY . .
14+
COPY ./requirements.txt ./
1515

1616
RUN \
1717
cd /app && \
1818
pip install -r requirements.txt --break-system-packages
1919

20+
COPY . .
21+
2022
RUN chmod +x entrypoint.sh
2123

2224
ENV SECRET_KEY_FILE="/app/config/secret_key.txt"

README.md

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,41 @@
22

33
uBiblio is a small and fast web application to help you manage your personal library (and share it with friends, or form a book club). It provides a unified interface for both physical books and ebooks. It is free, open-source, designed for self-hosting, and will never contain ads or spy on you.
44

5-
You don't have to connect it to the Internet. It can run on your LAN. Or even on a single computer with no network access at all.
5+
You don't have to connect it to the Internet. It can run on your LAN. Or even on a single computer with no network access at all. If you do connect it to the Internet though, it supports federated inter-library search!
66

77
# Main Features:
88

9-
1. Add, remove, update books
10-
2. Search books and see their location (useful if stored in bins, because you don't have space for tons of bookshelves)
9+
1. Add, remove, update and search books. Physical and ebook management.
10+
2. User management! Admins can invite new users via one-time-use link.
1111
3. Autopopulate a book's fields by entering an ISBN, either manually or with your device's camera!
12-
4. Reading list management for each user
13-
5. Book wishlist (just set owned=False, then update to True when you buy it)
14-
6. Withdraw and return books. Display a list of withdrawn books (to help put them away or figure out who has them).
15-
7. Admin users can access all features. Non-admin can only search, manage their reading list, and withdraw/return books.
16-
8. Content discovery (browse by genre).
17-
9. Works on most phones and ebook readers.
18-
10. Really quite fast, low memory requirements for hosting (~100MB).
19-
11. No distractions -- it does what it needs to do and nothing else (by default).
20-
12. Docker, Docker Hub image, and no-container install options. A big thanks to m0ngr31 for helping with this!
21-
13. Backup management, for both the database and files!
22-
14. Ebook support, with the ability to store ebook files and download from your reader (as long as it has a web browser)
23-
15. You can search for only ebooks or only physical books (e.g. when browsing a physical library vs. from an e-reader)
24-
16. Optional support for cover / book images (up to 16 for each book), with thumbnail support. You can now judge books by their cover (but only if you want to)!
25-
17. A luxurious TWO optional custom fields, in case your library is structured differently than mine. Set them up in the admin menu.
26-
18. It even runs on RISCV64 with memory to spare (check out the cursed branch)!
27-
19. Aussi disponible en français! Démarrez ubiblio avec LANGUAGE='FR'
12+
4. Reading list management for each user, and a wishlist for the library (set owned=False)
13+
5. Withdraw and return books. Display a list of withdrawn books (to help put them away or figure out who has them).
14+
6. Admin users can access all features. Non-admin can only search, manage their reading list, and withdraw/return books.
15+
7. Content discovery (browse by genre).
16+
8. Works on most phones and ebook readers.
17+
9. Really quite fast, low memory requirements for hosting (~100MB). Simple, distraction-free UI.
18+
10. Docker, Docker Hub image, and no-container install options. A big thanks to m0ngr31 for helping with this!
19+
11. Backup management, for both the database and files!
20+
12. Optional support for cover / book images (up to 16 for each book), with thumbnail support. You can now judge books by their cover (but only if you want to)!
21+
13. A luxurious TWO optional custom fields, in case your library is structured differently than mine. Set them up in the admin menu.
22+
14. It even runs on RISCV64 with memory to spare (check out the cursed branch)!
23+
15. Aussi disponible en français! Démarrez ubiblio avec LANGUAGE='FR'
2824

2925
# Setup
3026

3127
![Setup has moved to it's own file to keep things organized](https://github.com/seanboyce/ubiblio/blob/main/SETUP.md)
3228

3329

3430
# Possible future features (in no particular order):
35-
1. Modify content discovery via 'browse by genre' to be more practical for high numbers of books.
36-
2. Search by a specific location would be nice -- can be another interface.
37-
3. Limited UI customization -- what fields are displayed on the search results interface. This can easily break mobile compatibility though, but not everyone is using this on their phone, so being able to take advantage of that extra space is fine.
31+
![Roadmap has moved to it's own file to keep things organized](https://github.com/seanboyce/ubiblio/blob/main/ROADMAP.md)
3832

33+
# Contributing
34+
35+
Raise an issue here on Github explaining the feature you want (or want to add yourself). Then make a PR! If you are using an AI to assist, please let me know (and test first!), and keep the PR size to a minimum so that I can manually review it.
36+
37+
For bug reports, the process is the same. Maybe also include the steps to reproduce it.
38+
39+
Things I especially need help with: testing, getting more scalable language support in, adding in some kind of integrated help or wiki.
3940

4041
# HTML Theme
4142
The HTML theme is modified from "forty" by HTML5UP (https://html5up.net/). Actually, check out their other themes too. They are excellent and provide the themes under CC Attribution 3.0 (https://html5up.net/license). It's a huge timesaver for building things like this. So please leave the attribution in the footer.

ROADMAP.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1+
This is a file where I list out things I plan to add to uBiblio. You can suggest new things by raising an issue!
2+
13
# In next release
24

35
1. Barcode Scanning with camera
46
2. French Language support
7+
3. User creation and management
8+
4. Some UI fixes
9+
5. Use local files for barcode scanner and JQuery instead of constantly downloading them
10+
6. Federated search and viewing book details
511

612
# User management
13+
1. Should there be a 'root' account level? The root account cannot be deleted, and can delete/promote/demote admin accounts.
14+
2. Still not going to add password recovery. However, might be nice to be able to change my password.
715

8-
This is long overdue, uBiblio is multi-user but has no user management, making it very hard to use for book clubs and so on.
9-
10-
1. Admins should be able to create a temporary link that lets a new user create a login. For example, they send the link to their friend, and their friend creates an account.
11-
2. Login links should expire in 24 hrs if unused
12-
3. There should be a 'root' account level. The owner should be able to set / unset admin rights on user accounts.
13-
4. Admin and Root are booleans.
14-
5. Still not going to add password recovery. However, might be nice to be able to change my password.
16+
# Privacy
17+
1. Should use local fonts instead of downloading them from google fonts.
1518

1619
# Federation
1720

18-
The goal here is twofold -- first, to allow something like the pre-search-engine inter-library search features of the early 1990s. Second, to make it easier for groups of people, who are perfectly happy running their own libraries, to have better options than merging their databases (although merges are already supported).
21+
The goal here is twofold -- first, to allow something like the pre-search-engine inter-library search features of the early 1990s. Second, to make it easier for groups of people, who are perfectly happy running their own libraries, to have better options than merging their databases (although merges are already supported). This is a list of unimplemented features related to federation.
22+
23+
1. Maybe offer some expedited way to get a user account -- e.g. request one, and admins get notified on login and can just activate it.
1924

20-
1. Instances of uBiblio should generate a public-private keypair on first launch, and store it in the DB.
21-
2. The public key will be exposed on an endpoint that requires no login, e.g. /key
22-
3. If an admin points a ubiblio instance to another (e.g. submits the URL), it will grab the key and store it.
23-
4. Users should be able to submit book searches to these other instances. This will be an endpoint on their local instance, which will sign the request and pass it to the target.
24-
5. If uBiblio receives a search request signed by a key that has been authorized, it will serve the search request without requiring a login.
25-
6. Maybe offer some expedited way to get a user account -- e.g. request one, and admins get notified on login.
26-
7. It should also be possible to manually add a public key (e.g. to let libraries without a public IP run federated searches)
2725

2826
# Quality of Life
29-
1. I think I can let people search book databases by title + author, and selecto ne to add. This will help with libraries of very old, pre-ISBN books.
30-
2. I think I could add a warning when you are adding something that is very probably a duplicate book.
27+
1. I think I can let people search book databases by title + author, and select one to add. This will help with libraries of very old, pre-ISBN books.
28+
2. I think I could add a warning when you are adding something that is very probably a duplicate book (but this might be hard).
3129
3. I need to eventually add a more scalable localization strategy. For just French a new set of templates is fine, but for more languages it will need to be something smarter.
30+
4. Books should have a 'date added' field.
31+
5. I should add a library stats page. It will be basic, just count/sum and group_by on different fields, including custom fields.
32+
6. There should be an 'about' page. By default, it includes some instructions for new users. However, it will also contain some sections designed to be filled out by users if they want, e.g. 'about this library', 'rules', or just 'how to apply for a user account'. This page requires login or federated access.

SETUP.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,12 @@ Note that this example above does not enable SSL. It will accept all incoming co
9090

9191
## Launching with Gunicorn and SSL / HTTPS
9292

93-
gunicorn will launch multiple instances of uvicorn as needed to support even more concurrent users. You need to add the paths to your cert files and choose a port. Certs from letsencrypt are just fine :)
93+
gunicorn will launch multiple instances of uvicorn as needed to support even more concurrent users. You need to add the paths to your cert files and choose a port. Certs from letsencrypt are just fine!
94+
95+
NEW: uBiblio can also use more than one worker. An example is below.
9496

9597
```bash
96-
python -m gunicorn ubiblio.main:app -b 127.0.0.1:8000 -k uvicorn.workers.UvicornWorker --certfile= --keyfile=
98+
python -m gunicorn ubiblio.main:app -b 127.0.0.1:8000 -k uvicorn.workers.UvicornWorker -t 2 -w 4 --certfile= --keyfile=
9799
```
98100

99101
# Password Recovery

static/assets/js/quagga.min.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/FR/base.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
<!DOCTYPE html>
1+
<!DOCTYPE html>
22
<!--
33
Forty by HTML5 UP
44
html5up.net | @ajlkn
55
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
66
-->
77
<html>
88
<head>
9-
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
10-
9+
<script src="{{ url_for('static', path='assets/js/jquery.min.js')}}"></script>
1110
<title>uBiblio</title>
1211
<meta charset="utf-8" />
1312
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
@@ -40,12 +39,15 @@
4039
<nav id="menu">
4140
<ul class="links">
4241
<li><a href="/">Recherche de livre</a></li>
42+
<li><a href="/fedsearch">Recherche avancée</a></li>
4343
<li><a href="/genre/">Parcourir par genres</a></li>
4444
<li><a href="/wishlist/">Liste de souhaits</a></li>
4545
<li><a href="/readingLists">Listes de lecture</a></li>
4646
<li><a href="/withdrawn">Livres empruntés</a></li>
4747
{% if user.isAdmin %}
48-
<li><a href="/config">Admin</a></li>
48+
<li><a href="/config">Configuration</a></li>
49+
<li><a href="/userManagement">Gestion des Utilisateurs</a></li>
50+
<li><a href="/manageVkeys">Fédération</a></li>
4951
{% endif %}
5052
</ul>
5153
<ul class="actions stacked">

templates/FR/bookDetails.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ <h3 style="margin-top:25px;"><strong><u>Couverture(s)</strong></u></h3>
5959
<div style= "display:block; clear:both;float:none;">
6060
<hr>
6161
<h3><strong><u>Emprunt</strong></u></h3>
62-
<a style="border:none; margin-right:25px;" href="/withdraw/{{book.id}}"><button>Emprunter</button></a><a style="border:none; margin-right:25px;" href="/return/{{book.id}}"><button>Rendre</button></a>
62+
{% if book.withdrawn %}
63+
<a style="border:none; margin-right:25px;" href="/return/{{book.id}}"><button>Rendre</button></a>
64+
{% else %}
65+
<a style="border:none; margin-right:25px;" href="/withdraw/{{book.id}}"><button>Emprunter</button></a>
66+
{% endif %}
67+
6368
</div>
6469
<div style= "display:block; clear:both;float:none;">
6570
<h3 style="margin-top:25px; margin-bottom:0px;"><strong><u>Liste de lecture</strong></u></h3>

templates/FR/booksearch.html

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% extends "base.html" %}
1+
{% extends "base.html" %}
22
{% block main %}
33

44
<!-- Banner -->
@@ -20,14 +20,16 @@
2020
<input type="checkbox" id="ebookSearch" name="ebookSearch" value="ebook">
2121
<label for="ebookSearch"> eBooks uniquement</label>
2222
<input type="checkbox" id="noEbookSearch" name="noEbookSearch" value="noebook">
23-
<label for="noEbookSearch"> Exclure les eBooks</label><br>
23+
<label for="noEbookSearch">Exclure les eBooks</label><br>
2424
<input style ="float:left;" type="submit" value="Rechercher">
2525
</form>
2626
{% if user.isAdmin %}
2727
<a style ='text-decoration:none !important; border:none;margin-left:25px;' href="/add_book"><button style="margin-bottom:25px;">Ajouter</button></a>
2828
<a style ='text-decoration:none !important; border:none;margin-left:25px;' href="/scan_isbn"><button style="margin-bottom:25px;">Scan</button></a>
29-
{% endif %}
30-
<h2 style="margin-top:25px; margin-bottom:15px;">Liste des Livres</h2>
29+
{% else %}
30+
<br><br>
31+
{% endif %}
32+
<h2 id="result_count" style="margin-top:25px; margin-bottom:15px;">Liste des Livres</h2>
3133

3234
<div class="container">
3335
<table>
@@ -75,9 +77,12 @@ <h2 style="margin-top:25px; margin-bottom:15px;">Liste des Livres</h2>
7577
var searchEndpoint = "/searchbooks?title="+searchTerm+"&author="+searchTerm+"&skip="+skip+"&onlyEbooks="+onlyEbooks+"&noEbooks="+noEbooks;
7678
}
7779
$.post(searchEndpoint,function(data, status){
78-
itemdata = jQuery.parseJSON(data);
80+
result = jQuery.parseJSON(data);
7981
$("#tablebody").empty();
8082
$("#pagination").empty();
83+
var count = result.count;
84+
document.getElementById("result_count").textContent="Liste des Livres (" + count + ")";
85+
var itemdata = jQuery.parseJSON(result.result);
8186
itemdata.forEach(function(item) {
8287
$("#tablebody").append('<tr><td><a style = "border:none;text-decoration: underline;" href="/bookDetails/'+item.id+'">'+item.title+"</a></td><td>"+item.author+"</td><td>"+item.shelf+'</td></tr>');
8388
});
@@ -86,11 +91,11 @@ <h2 style="margin-top:25px; margin-bottom:15px;">Liste des Livres</h2>
8691
$("#tablebody").append('<tr><td>' + "Aucune résultat" + '</td><td></td><td></td></tr>');
8792
}
8893
if (pagination > 0){
89-
$("#pagination").append("<button onclick='previous();'>Previous</button>");
94+
$("#pagination").append("<button onclick='previous();'>Précédente</button>");
9095
}
9196

9297
if(itemdata.length > 49){
93-
$("#pagination").append("<button onclick='next();'>Next</button>");
98+
$("#pagination").append("<button onclick='next();'>Suivante</button>");
9499
}
95100

96101
});}

templates/FR/createUser.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{% set hideNav = True %}
2+
{% extends "base.html" %}
3+
4+
{% block main %}
5+
<!-- Banner -->
6+
<!-- Two -->
7+
<section id="two">
8+
<div class="inner" style = "padding: 20px 0 1em 0;">
9+
<header class="major">
10+
<h2>Créer un Compte</h2>
11+
</header>
12+
{% for error in errors %}
13+
<p style="color: red">{{ error }}</p>
14+
{% endfor %}
15+
<form action="/auth/create" method="post">
16+
<p>
17+
<p><label for="">Nom d'utilisateur</label></p>
18+
<input type="text" name="username" value="" required>
19+
</p>
20+
<p>
21+
<p><label for="">Mot de passe</label></p>
22+
<input type="password" name="password" value="" required>
23+
<input type="hidden" name="accessCode" value="{{ accessCode }}" required>
24+
</p>
25+
<input type="submit" value="Submit">
26+
</form>
27+
</div>
28+
</section>
29+
{% endblock %}

templates/FR/fedBookDetails.html

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{% extends "base.html" %}
2+
{% block main %}
3+
4+
<!-- Banner -->
5+
<!-- Two -->
6+
<div class="inner" style = "padding: 20px 0 5em 5em;">
7+
<div style="display:block; clear:both; float:none;">
8+
<h3><strong><u>Détails du livre</strong></u></h3>
9+
<div style="display:block; clear:both; float:none;">
10+
<div id = "bookdetails" style="width:30%; min-width: 300px; float:left; margin-right:30px;">
11+
12+
13+
</div>
14+
<div id = "bookdetails2" style="width:30%; min-width: 280px; float:left;">
15+
16+
17+
</div>
18+
19+
<div style= "display:block; clear:both;float:none;">
20+
21+
<hr>
22+
23+
</div>
24+
25+
</div>
26+
<script>
27+
async function search(bookId) {
28+
var data = JSON.stringify({"bookId": bookId});
29+
var xhr = new XMLHttpRequest();
30+
var url = "/signsearch";
31+
xhr.open("POST", url, true);
32+
xhr.setRequestHeader("Content-Type", "application/json");
33+
xhr.onreadystatechange = function () {
34+
if (xhr.readyState === 4 && xhr.status === 200) {
35+
signedsearch(JSON.parse(xhr.responseText));
36+
37+
}
38+
};
39+
xhr.send(data);
40+
}
41+
42+
function signedsearch(mysearch){
43+
var xhr = new XMLHttpRequest()
44+
var url = '{{ vkey.url }}' + "fedBookDetails";
45+
xhr.open("POST", url, true);
46+
//xhr.setRequestHeader("Content-Type", "application/json");
47+
//xhr.setRequestHeader("Access-Control-Allow-Origins", "*");
48+
//xhr.setRequestHeader("Access-Control-Request-Method", "POST");
49+
xhr.onreadystatechange = function () {
50+
if (xhr.readyState === 4 && xhr.status === 200) {
51+
displayResults(xhr.responseText);
52+
};
53+
};
54+
var data = JSON.stringify(mysearch);
55+
xhr.send(data);
56+
}
57+
58+
function displayResults(results){
59+
60+
result = jQuery.parseJSON(results);
61+
$("#bookdetails").empty();
62+
$("#bookdetails2").empty();
63+
$("#bookdetails").append('<p><strong>Titre</strong>:'+result["title"]+'</p>');
64+
$("#bookdetails").append('<p><strong>Auteur</strong>:'+result["author"]+'</p>');
65+
$("#bookdetails").append('<p><strong>Synopsis</strong>:'+result["summary"]+'</p>');
66+
$("#bookdetails").append('<p><strong>Genre</strong>:'+result["genre"]+'</p>');
67+
$("#bookdetails").append('<p><strong>Bibliothèque</strong>:'+result["library"]+'</p>');
68+
$("#bookdetails").append('<p><strong>Étagère</strong>:'+result["shelf"]+'</p>');
69+
$("#bookdetails2").append('<p><strong>Collection</strong>:'+result["collection"]+'</p>');
70+
$("#bookdetails2").append('<p><strong>ISBN</strong>:'+result["ISBN"]+'</p>');
71+
$("#bookdetails2").append('<p><strong>Notes</strong>:'+result["notes"]+'</p>');
72+
$("#bookdetails2").append('<p><strong>Possédé</strong>:'+result["owned"]+'</p>');
73+
$("#bookdetails2").append('<p><strong>Emprunté</strong>:'+result["withdrawn"]+'</p>');
74+
}
75+
window.onload = () => search('{{ bookId }}' );
76+
</script>
77+
{% endblock %}

0 commit comments

Comments
 (0)