Skip to content

Commit 0546c5f

Browse files
authored
Merge pull request #1433 from booklore-app/develop
Merge develop into master for release
2 parents 8c4ee56 + ccf19c7 commit 0546c5f

File tree

204 files changed

+5210
-4580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

204 files changed

+5210
-4580
lines changed

.gitattributes

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
## Set Git attributes for paths including line ending
2+
## normalization, diff behavior, etc.
3+
##
4+
## Get latest from `dotnet new gitattributes`
5+
6+
# Auto detect text files and perform LF normalization
7+
* text=auto
8+
9+
#
10+
# The above will handle all files NOT found below
11+
#
12+
13+
*.cs text diff=csharp
14+
*.cshtml text diff=html
15+
*.csx text diff=csharp
16+
*.sln text eol=crlf
17+
18+
# Content below from: https://github.com/gitattributes/gitattributes/blob/master/Common.gitattributes
19+
20+
# Documents
21+
*.bibtex text diff=bibtex
22+
*.doc diff=astextplain
23+
*.DOC diff=astextplain
24+
*.docx diff=astextplain
25+
*.DOCX diff=astextplain
26+
*.dot diff=astextplain
27+
*.DOT diff=astextplain
28+
*.pdf diff=astextplain
29+
*.PDF diff=astextplain
30+
*.rtf diff=astextplain
31+
*.RTF diff=astextplain
32+
*.md text diff=markdown
33+
*.mdx text diff=markdown
34+
*.tex text diff=tex
35+
*.adoc text
36+
*.textile text
37+
*.mustache text
38+
# Per RFC 4180, .csv should be CRLF
39+
*.csv text eol=crlf
40+
*.tab text
41+
*.tsv text
42+
*.txt text
43+
*.sql text
44+
*.epub diff=astextplain
45+
46+
# Graphics
47+
*.png binary
48+
*.jpg binary
49+
*.jpeg binary
50+
*.gif binary
51+
*.tif binary
52+
*.tiff binary
53+
*.ico binary
54+
# SVG treated as text by default.
55+
*.svg text
56+
# If you want to treat it as binary,
57+
# use the following line instead.
58+
# *.svg binary
59+
*.eps binary
60+
61+
# Scripts
62+
# Force Unix scripts to always use lf line endings so that if a repo is accessed
63+
# in Unix via a file share from Windows, the scripts will work
64+
*.bash text eol=lf
65+
*.fish text eol=lf
66+
*.ksh text eol=lf
67+
*.sh text eol=lf
68+
*.zsh text eol=lf
69+
# Likewise, force cmd and batch scripts to always use crlf
70+
*.bat text eol=crlf
71+
*.cmd text eol=crlf
72+
73+
# Serialization
74+
*.json text
75+
*.toml text
76+
*.xml text
77+
*.yaml text
78+
*.yml text
79+
80+
# Archives
81+
*.7z binary
82+
*.bz binary
83+
*.bz2 binary
84+
*.bzip2 binary
85+
*.gz binary
86+
*.lz binary
87+
*.lzma binary
88+
*.rar binary
89+
*.tar binary
90+
*.taz binary
91+
*.tbz binary
92+
*.tbz2 binary
93+
*.tgz binary
94+
*.tlz binary
95+
*.txz binary
96+
*.xz binary
97+
*.Z binary
98+
*.zip binary
99+
*.zst binary
100+
101+
# Text files where line endings should be preserved
102+
*.patch -text
103+
104+
# Exclude files from exporting
105+
.gitattributes export-ignore
106+
.gitignore export-ignore
107+
.gitkeep export-ignore

README.md

Lines changed: 46 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -91,54 +91,70 @@ Ensure you have [Docker](https://docs.docker.com/get-docker/) and [Docker Compos
9191

9292
> **Note:** Legacy images under `https://ghcr.io/adityachandelgit/booklore-app` will remain available but will not receive new updates.
9393
94-
### 2️⃣ Create docker-compose.yml
94+
### 2️⃣ Set Up Your docker-compose.yml Configuration
9595

96-
> ⚠️ If you intend to run the container as a non-root user, you must manually create all of your `/your/local/path/to/booklore` directories with read and write permissions for your intended user **before first run**.
96+
**Step 1: Create a `.env` file** in the same directory as your `docker-compose.yml`:
9797

98-
Create a `docker-compose.yml` file with content:
98+
```ini
99+
# BookLore Application Settings
100+
APP_USER_ID=0
101+
APP_GROUP_ID=0
102+
TZ=Etc/UTC
103+
BOOKLORE_PORT=6060
104+
105+
# Database Connection (BookLore)
106+
DATABASE_URL=jdbc:mariadb://mariadb:3306/booklore
107+
DB_USER=booklore
108+
DB_PASSWORD=ChangeMe_BookLoreApp_2025!
109+
110+
# MariaDB Container Settings
111+
DB_USER_ID=1000
112+
DB_GROUP_ID=1000
113+
MYSQL_ROOT_PASSWORD=ChangeMe_MariaDBRoot_2025!
114+
MYSQL_DATABASE=booklore
115+
```
116+
117+
**Step 2: Create a `docker-compose.yml` file** that references the `.env` variables:
99118

100119
```yaml
101120
services:
102121
booklore:
103-
# Official Docker Hub image:
104122
image: booklore/booklore:latest
105-
# Or the GHCR image:
123+
# Alternative: Use GitHub Container Registry
106124
# image: ghcr.io/booklore-app/booklore:latest
107125
container_name: booklore
108126
environment:
109-
- USER_ID=0
110-
- GROUP_ID=0
111-
- TZ=Etc/UTC
112-
- DATABASE_URL=jdbc:mariadb://mariadb:3306/booklore
113-
- DATABASE_USERNAME=booklore # Must match MYSQL_USER defined in the mariadb container
114-
- DATABASE_PASSWORD=your_secure_password
115-
- BOOKLORE_PORT=6060 # Port BookLore listens on inside the container; must match container port below
127+
- USER_ID=${APP_USER_ID}
128+
- GROUP_ID=${APP_GROUP_ID}
129+
- TZ=${TZ}
130+
- DATABASE_URL=${DATABASE_URL}
131+
- DATABASE_USERNAME=${DB_USER}
132+
- DATABASE_PASSWORD=${DB_PASSWORD}
133+
- BOOKLORE_PORT=${BOOKLORE_PORT}
116134
depends_on:
117135
mariadb:
118136
condition: service_healthy
119137
ports:
120-
- "6060:6060" # HostPort:ContainerPort → Keep both numbers the same, and also ensure the container port matches BOOKLORE_PORT, no exceptions.
121-
# All three (host port, container port, BOOKLORE_PORT) must be identical for BookLore to function properly.
122-
# Example: To expose on host port 7070, set BOOKLORE_PORT=7070 and use "7070:7070".
138+
- "${BOOKLORE_PORT}:${BOOKLORE_PORT}"
123139
volumes:
124-
- /your/local/path/to/booklore/data:/app/data # Application data (settings, metadata, cache, etc.). Persist this folder to retain your library state across container restarts.
125-
- /your/local/path/to/booklore/books:/books # Primary book library folder. Mount your collection here so BookLore can access and organize your books.
126-
- /your/local/path/to/booklore/bookdrop:/bookdrop # BookDrop folder. Files placed here are automatically detected and prepared for import.
140+
- ./data:/app/data
141+
- ./books:/books
142+
- ./bookdrop:/bookdrop
127143
restart: unless-stopped
128144

129145
mariadb:
130146
image: lscr.io/linuxserver/mariadb:11.4.5
131147
container_name: mariadb
132148
environment:
133-
- PUID=1000
134-
- PGID=1000
135-
- TZ=Etc/UTC
136-
- MYSQL_ROOT_PASSWORD=super_secure_password # Use a strong password for the database's root user, should be different from MYSQL_PASSWORD
137-
- MYSQL_DATABASE=booklore
138-
- MYSQL_USER=booklore # Must match DATABASE_USERNAME defined in the booklore container
139-
- MYSQL_PASSWORD=your_secure_password # Use a strong password; must match DATABASE_PASSWORD defined in the booklore container
149+
- PUID=${DB_USER_ID}
150+
- PGID=${DB_GROUP_ID}
151+
- TZ=${TZ}
152+
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
153+
- MYSQL_DATABASE=${MYSQL_DATABASE}
154+
- MYSQL_USER=${DB_USER}
155+
- MYSQL_PASSWORD=${DB_PASSWORD}
140156
volumes:
141-
- /your/local/path/to/mariadb/config:/config
157+
- ./mariadb/config:/config
142158
restart: unless-stopped
143159
healthcheck:
144160
test: [ "CMD", "mariadb-admin", "ping", "-h", "localhost" ]
@@ -147,9 +163,6 @@ services:
147163
retries: 10
148164
```
149165
150-
Note: You can find the latest BookLore image tag `BOOKLORE_IMAGE_TAG` (e.g. v.0.x.x) from the Releases section:
151-
📦 [Latest Image Tag – GitHub Releases](https://github.com/adityachandelgit/BookLore/releases)
152-
153166
### 3️⃣ Start the Containers
154167
155168
Run the following command to start the services:
@@ -163,37 +176,9 @@ docker compose up -d
163176
Once the containers are up, access BookLore in your browser at:
164177

165178
```ini
166-
http : //localhost:6060
179+
http://localhost:6060
167180
```
168181

169-
## ⚙️ Supported Environment Variables
170-
171-
### BookLore Container
172-
173-
| Key | Default Value | Description |
174-
|----------------------|----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
175-
| `USER_ID` | `0` | User ID for file ownership within the container. Set this to match your host user's UID if running as non-root (e.g., `1000`). Use `id -u` on Linux to find your UID. |
176-
| `GROUP_ID` | `0` | Group ID for file ownership within the container. Set this to match your host user's GID if running as non-root (e.g., `1000`). Use `id -g` on Linux to find your GID. |
177-
| `TZ` | `Etc/UTC` | Timezone for the application. Controls timestamps in logs and UI. Use standard timezone identifiers (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`). See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). |
178-
| `DATABASE_URL` | `jdbc:mariadb://mariadb:3306/booklore` | JDBC connection string for MariaDB/MySQL database. Format: `jdbc:mariadb://hostname:port/database_name`. Only modify if using a custom database host, port, or external database server. |
179-
| `DATABASE_USERNAME` | `booklore` | Username for database authentication. Must match `MYSQL_USER` in your MariaDB container configuration. |
180-
| `DATABASE_PASSWORD` | - | Password for database authentication. **Required**. Use a strong, unique password. Must match `MYSQL_PASSWORD` in your MariaDB container configuration. Store securely and never commit to version control. |
181-
| `BOOKLORE_PORT` | `6060` | Internal port BookLore listens on. **Critical**: This must match both the host and container ports in your port mapping (e.g., `6060:6060`). Changing this requires updating all three values. Example: For port `7070`, use `BOOKLORE_PORT=7070` and `7070:7070`. |
182-
| `SWAGGER_ENABLED` | `false` | Controls access to Swagger UI for API documentation and testing. Set to `true` to enable at `/swagger-ui.html` (useful for development/testing). Keep `false` in production for security. |
183-
| `FORCE_DISABLE_OIDC` | `false` | Forces local username/password authentication only. Set to `true` to completely disable OIDC/OAuth2 providers (Authentik, Pocket ID, etc.). Useful for troubleshooting authentication issues or temporarily bypassing external identity providers. |
184-
185-
### MariaDB Container
186-
187-
| Key | Default Value | Description |
188-
|-----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
189-
| `PUID` | `1000` | Process User ID for the LinuxServer.io MariaDB container. Set this to match your host user's UID for proper file permissions. Use `id -u` on Linux to find your UID. |
190-
| `PGID` | `1000` | Process Group ID for the LinuxServer.io MariaDB container. Set this to match your host user's GID for proper file permissions. Use `id -g` on Linux to find your GID. |
191-
| `TZ` | `Etc/UTC` | Timezone for the MariaDB container. Should match the BookLore container's timezone for consistency in timestamps. |
192-
| `MYSQL_ROOT_PASSWORD` | - | Root password for the MariaDB database. **Required**. Use a strong, unique password different from `MYSQL_PASSWORD`. This provides administrative access to the database server. Never expose or commit to VCS. |
193-
| `MYSQL_DATABASE` | `booklore` | Name of the database to create on first run. This database will be used by BookLore to store all application data. Should not be changed after initial setup. |
194-
| `MYSQL_USER` | `booklore` | Username for the application database user. Must match `DATABASE_USERNAME` in the BookLore container configuration. This user will have full access to the `MYSQL_DATABASE`. |
195-
| `MYSQL_PASSWORD` | - | Password for the application database user. **Required**. Use a strong, unique password. Must match `DATABASE_PASSWORD` in the BookLore container configuration. Store securely and never commit to VCS. |
196-
197182
## 📥 Bookdrop Folder: Auto-Import Files
198183

199184
BookLore now supports a **Bookdrop folder**, a special directory where you can drop your book files (`.pdf`, `.epub`, `.cbz`, etc.), and BookLore will automatically detect, process, and prepare them for import. This makes it easy to bulk add new books without manually uploading each one.
@@ -214,9 +199,9 @@ services:
214199
booklore:
215200
...
216201
volumes:
217-
- /your/local/path/to/booklore/data:/app/data
218-
- /your/local/path/to/booklore/books:/books
219-
- /your/local/path/to/booklore/bookdrop:/bookdrop # 👈 Bookdrop directory
202+
- ./data:/app/data
203+
- ./books:/books
204+
- ./bookdrop:/bookdrop # 👈 Bookdrop directory
220205
```
221206
222207
## 🔑 OIDC/OAuth2 Authentication (Authentik, Pocket ID, etc.)

booklore-api/src/main/java/com/adityachandel/booklore/config/OpenApiConfig.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import io.swagger.v3.oas.models.Components;
44
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Contact;
56
import io.swagger.v3.oas.models.info.Info;
7+
import io.swagger.v3.oas.models.info.License;
68
import io.swagger.v3.oas.models.security.SecurityRequirement;
79
import io.swagger.v3.oas.models.security.SecurityScheme;
10+
import io.swagger.v3.oas.models.servers.Server;
811
import org.springframework.context.annotation.Bean;
912
import org.springframework.context.annotation.Configuration;
1013

@@ -16,7 +19,18 @@ public OpenAPI customOpenAPI() {
1619
final String securitySchemeName = "bearerAuth";
1720

1821
return new OpenAPI()
19-
.info(new Info().title("Booklore API").version("1.0"))
22+
.info(new Info()
23+
.title("Booklore API")
24+
.version("1.0")
25+
.description("Booklore is a personal library management system. This API allows you to manage books, libraries, users, shelves, and more.")
26+
.contact(new Contact()
27+
.name("Booklore")
28+
.url("https://github.com/booklore-app/booklore"))
29+
.license(new License()
30+
.name("GNU General Public License v3.0")
31+
.url("https://www.gnu.org/licenses/gpl-3.0.html"))
32+
)
33+
.addServersItem(new Server().url("/").description("Current server"))
2034
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
2135
.components(new Components().addSecuritySchemes(securitySchemeName,
2236
new SecurityScheme()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.adityachandel.booklore.config;
2+
3+
import com.adityachandel.booklore.service.task.TaskService;
4+
import lombok.AllArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.boot.context.event.ApplicationReadyEvent;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.event.EventListener;
9+
import org.springframework.scheduling.annotation.EnableScheduling;
10+
11+
@Configuration
12+
@EnableScheduling
13+
@AllArgsConstructor
14+
@Slf4j
15+
public class TaskSchedulerConfig {
16+
17+
private final TaskService taskService;
18+
19+
@EventListener(ApplicationReadyEvent.class)
20+
public void initializeScheduledTasks() {
21+
log.info("Application ready, initializing scheduled tasks");
22+
taskService.initializeScheduledTasks();
23+
}
24+
}

booklore-api/src/main/java/com/adityachandel/booklore/config/security/ImageCacheConfig.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
import org.springframework.boot.web.servlet.FilterRegistrationBean;
55
import org.springframework.context.annotation.Bean;
66
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.core.Ordered;
78

89
@Configuration
910
public class ImageCacheConfig {
1011

1112
@Bean
12-
public FilterRegistrationBean<ImageCachingFilter> imageCachingFilterRegistration(ImageCachingFilter filter) {
13+
public FilterRegistrationBean<ImageCachingFilter> imageCachingFilterRegistration() {
1314
FilterRegistrationBean<ImageCachingFilter> registrationBean = new FilterRegistrationBean<>();
14-
registrationBean.setFilter(filter);
15+
registrationBean.setFilter(new ImageCachingFilter());
1516
registrationBean.addUrlPatterns(
1617
"/api/v1/media/book/*/cover",
1718
"/api/v1/media/book/*/thumbnail",
1819
"/api/v1/media/book/*/backup-cover"
1920
);
20-
registrationBean.setOrder(1);
21+
registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);
2122
return registrationBean;
2223
}
2324
}

booklore-api/src/main/java/com/adityachandel/booklore/config/security/SecurityConfig.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
1818
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1919
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
20+
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
2021
import org.springframework.security.config.http.SessionCreationPolicy;
2122
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
2223
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -42,9 +43,9 @@ public class SecurityConfig {
4243
private final AppProperties appProperties;
4344

4445
private static final String[] SWAGGER_ENDPOINTS = {
45-
"/swagger-ui.html",
46-
"/swagger-ui/**",
47-
"/v3/api-docs/**"
46+
"/api/v1/swagger-ui.html",
47+
"/api/v1/swagger-ui/**",
48+
"/api/v1/api-docs/**"
4849
};
4950

5051
private static final String[] COMMON_PUBLIC_ENDPOINTS = {
@@ -120,6 +121,9 @@ public SecurityFilterChain coverJwtApiSecurityChain(HttpSecurity http, CoverJwtF
120121
.securityMatcher("/api/v1/media/**")
121122
.csrf(AbstractHttpConfigurer::disable)
122123
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
124+
.headers(headers -> headers
125+
.cacheControl(HeadersConfigurer.CacheControlConfig::disable)
126+
)
123127
.authorizeHttpRequests(auth -> auth
124128
.anyRequest().permitAll()
125129
)
@@ -148,7 +152,6 @@ public SecurityFilterChain jwtApiSecurityChain(HttpSecurity http) throws Excepti
148152
return http.build();
149153
}
150154

151-
152155
@Bean
153156
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
154157
return http.getSharedObject(AuthenticationManagerBuilder.class)

0 commit comments

Comments
 (0)