Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d7a468c
Add customapi widget Dashy integration to docs
codebude Jun 12, 2026
68b2d37
Merge pull request #45 from codebude/feature/update-dashy-integration…
codebude Jun 12, 2026
3d700f6
Added integration docs for Glance
codebude Jun 12, 2026
179e4da
Merge pull request #46 from codebude/feature/glance-dashboard-integra…
codebude Jun 12, 2026
142e849
Pre-select field to be changed on data hygiene page in case if only o…
codebude Jun 12, 2026
1b53f1c
Fix width of field to change selector on data hygiene page
codebude Jun 12, 2026
f4a92f7
Added book details and cover action buttons to data hygiene page
codebude Jun 12, 2026
c4ff25b
Fix types in frontend tests
codebude Jun 12, 2026
f72f245
Merge pull request #47 from codebude/feature/improve-data-hygiene-ux
codebude Jun 12, 2026
207bcc2
Merge pull request #48 from codebude/bugfix/correct-type-for-frontend…
codebude Jun 12, 2026
3ac69a7
Testing multi-arch deployment
Jossey28 Jun 13, 2026
d1c380c
Name normalization needed because my username contains caps
Jossey28 Jun 13, 2026
0f9df60
Merge pull request #1 from Jossey28/feature/publish-arm64-image
Jossey28 Jun 13, 2026
d651627
Fixed minor mistake in repository name normilization
Jossey28 Jun 13, 2026
9003765
Merge branch 'feature/publish-arm64-image'
Jossey28 Jun 13, 2026
34d8e7f
Added /embed route to backend
codebude Jun 13, 2026
5f1dc02
Added embed api and Homarr to docs
codebude Jun 13, 2026
fbed313
Implemented embed token logic in frontend
codebude Jun 13, 2026
86f2d01
Handle rotation of expired embed tokens
codebude Jun 13, 2026
1be9f79
Set auto complete behaviour on profile page
codebude Jun 13, 2026
fc7cd29
Fix proxying of api docs via frontend
codebude Jun 13, 2026
6744622
Fix order of embed tokens and api keys in profile page view
codebude Jun 13, 2026
c6c5f9e
Updated embed api docs
codebude Jun 13, 2026
a9b675c
Updated embed api docs picture
codebude Jun 13, 2026
26a893f
Profile menu entry for docs
codebude Jun 13, 2026
daf2f73
Merge pull request #49 from Jossey28/main
codebude Jun 13, 2026
360b4c9
Fixed embed api e2e test
codebude Jun 13, 2026
4ec4f59
Merge pull request #50 from codebude/feature/html-iframe-embed-endpoint
codebude Jun 13, 2026
c811d7b
Auto-generate of DB layout docs
codebude Jun 14, 2026
8c8744b
Merge pull request #51 from codebude/feature/enhance-developer-docs-w…
codebude Jun 14, 2026
f363ece
Fix docs db generation
codebude Jun 14, 2026
db60fc3
Added zoom to mermaid diagram
codebude Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 97 additions & 20 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,11 @@ env:
REGISTRY: ghcr.io

jobs:
build-and-push:
prepare-tags-and-names:
runs-on: ubuntu-latest
strategy:
matrix:
service:
- name: frontend
image: librislog
dockerfile: ./frontend/Dockerfile
context: ./frontend
- name: backend
image: librislog-api
dockerfile: ./backend/Dockerfile
context: ./backend

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
Expand Down Expand Up @@ -59,6 +48,54 @@ jobs:
SANITIZED="$(echo "$SANITIZED" | sed 's/^[^a-zA-Z0-9_]\+//')"
echo "sanitized_tag=${SANITIZED:0:128}" >> "$GITHUB_OUTPUT"

- name: Normalize repository name
id: repo
uses: actions/github-script@v7
with:
result-encoding: string
script: return `${context.repo.owner}/${context.repo.repo}`.toLowerCase()

outputs:
version: ${{ steps.version.outputs.version }}
sha_short: ${{ steps.version.outputs.sha_short }}
sanitized_tag: ${{ steps.sanitize.outputs.sanitized_tag }}
repository: ${{ steps.repo.outputs.result }}

build-and-push:
runs-on: ${{ matrix.runner }}
needs: prepare-tags-and-names
strategy:
fail-fast: false
matrix:
service:
- name: frontend
image: librislog
dockerfile: ./frontend/Dockerfile
context: ./frontend
- name: backend
image: librislog-api
dockerfile: ./backend/Dockerfile
context: ./backend

arch: [amd64, arm64]

include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.inputs.branch || github.ref }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

Expand All @@ -72,14 +109,14 @@ jobs:
- name: Generate image tags
id: tags
run: |
IMAGE="${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.service.image }}"
SHA_SHORT="${{ steps.version.outputs.sha_short }}"
IMAGE="${{ env.REGISTRY }}/${{ needs.prepare-tags-and-names.outputs.repository }}/${{ matrix.service.image }}"
SHA_SHORT="${{ needs.prepare-tags-and-names.outputs.sha_short }}"

if [ "${{ github.event_name }}" = "release" ]; then
SANITIZED="${{ steps.sanitize.outputs.sanitized_tag }}"
TAGS="${IMAGE}:${SANITIZED},${IMAGE}:latest"
SANITIZED="${{ needs.prepare-tags-and-names.outputs.sanitized_tag }}"
TAGS="${IMAGE}:${SANITIZED}-${{ matrix.arch }},${IMAGE}:latest-${{ matrix.arch }}"
else
TAGS="${IMAGE}:develop,${IMAGE}:${SHA_SHORT}"
TAGS="${IMAGE}:develop-${{ matrix.arch }},${IMAGE}:${SHA_SHORT}-${{ matrix.arch }}"
fi

echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
Expand All @@ -90,11 +127,51 @@ jobs:
context: ${{ matrix.service.context }}
file: ${{ matrix.service.dockerfile }}
push: true
platforms: linux/amd64
platforms: linux/${{ matrix.arch }}
tags: ${{ steps.tags.outputs.tags }}
build-args: |
APP_VERSION=${{ steps.version.outputs.version }}
APP_VERSION=${{ needs.prepare-tags-and-names.outputs.version }}
GIT_SHA=${{ github.sha }}
${{ matrix.service.name == 'frontend' && 'PUBLIC_DEFAULT_LOCALE=en' || '' }}
cache-from: type=gha
cache-to: type=gha,mode=max

create-manifest:
needs: [prepare-tags-and-names, build-and-push]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service:
- name: frontend
image: librislog
- name: backend
image: librislog-api

permissions:
packages: write

steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create multi-arch manifest
run: |
IMAGE="${{ env.REGISTRY }}/${{ needs.prepare-tags-and-names.outputs.repository }}/${{ matrix.service.image }}"
SHA_SHORT="${{ needs.prepare-tags-and-names.outputs.sha_short }}"

if [ "${{ github.event_name }}" = "release" ]; then
SANITIZED="${{ needs.prepare-tags-and-names.outputs.sanitized_tag }}"
docker buildx imagetools create -t "${IMAGE}:${SANITIZED}" "${IMAGE}:${SANITIZED}-amd64" "${IMAGE}:${SANITIZED}-arm64"
docker buildx imagetools create -t "${IMAGE}:latest" "${IMAGE}:latest-amd64" "${IMAGE}:latest-arm64"
else
docker buildx imagetools create -t "${IMAGE}:develop" "${IMAGE}:develop-amd64" "${IMAGE}:develop-arm64"
docker buildx imagetools create -t "${IMAGE}:${SHA_SHORT}" "${IMAGE}:${SHA_SHORT}-amd64" "${IMAGE}:${SHA_SHORT}-arm64"
fi
15 changes: 15 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ concurrency:

env:
NODE_VERSION: "22"
PYTHON_VERSION: "3.14"

jobs:
deploy:
Expand Down Expand Up @@ -66,6 +67,16 @@ jobs:
release/docs/package-lock.json
develop/docs/package-lock.json

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Generate DB layout docs (release)
if: steps.check-release-docs.outputs.has_docs == 'true'
working-directory: release/docs
run: npm run docs:gen-db 2>/dev/null || echo "Skipped — docs:gen-db not in this release"

- name: Build release docs
if: steps.check-release-docs.outputs.has_docs == 'true'
working-directory: release/docs
Expand All @@ -74,6 +85,10 @@ jobs:
npx vitepress build
mv .vitepress/dist /tmp/dist-release

- name: Generate DB layout docs (nightly)
working-directory: develop/docs
run: npm run docs:gen-db

- name: Build nightly docs
working-directory: develop/docs
run: |
Expand Down
52 changes: 52 additions & 0 deletions backend/alembic/versions/1a2b3c4d5e6f_add_embed_token_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""add embed_token table for scoped embed tokens

Revision ID: 1a2b3c4d5e6f
Revises: f7e9d1c3b5a2
Create Date: 2026-06-13 23:25:00.000000

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.engine.reflection import Inspector


revision: str = "1a2b3c4d5e6f"
down_revision: Union[str, Sequence[str], None] = "f7e9d1c3b5a2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
conn = op.get_bind()
inspector = sa.inspect(conn)
tables = inspector.get_table_names()
if "embed_token" not in tables:
op.create_table(
"embed_token",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("token_prefix", sa.String(), nullable=False),
sa.Column("token_hash", sa.String(), nullable=False),
sa.Column("scopes", sa.String(), nullable=False, server_default="embed:stats:read"),
sa.Column("allowed_origins", sa.String(), nullable=True),
sa.Column("expires_at", sa.DateTime(), nullable=True),
sa.Column("last_used_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("revoked_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_embed_token_user_id"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("token_hash", name="uq_embed_token_token_hash"),
)
op.create_index(op.f("ix_embed_token_token_prefix"), "embed_token", ["token_prefix"])
op.create_index(op.f("ix_embed_token_user_id"), "embed_token", ["user_id"])


def downgrade() -> None:
conn = op.get_bind()
inspector = sa.inspect(conn)
tables = inspector.get_table_names()
if "embed_token" in tables:
op.drop_table("embed_token")
25 changes: 25 additions & 0 deletions backend/app/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,31 @@ def get_api_key_prefix(api_key: str) -> str:
return api_key[:12]


# --- Embed token utilities ---

EMBED_TOKEN_PREFIX = "le_"
EMBED_TOKEN_SCOPE_STATS_READ = "embed:stats:read"


def generate_embed_token() -> str:
"""Generate a new random embed token prefixed with 'le_'."""
return f"{EMBED_TOKEN_PREFIX}{secrets.token_urlsafe(32)}"


def hash_embed_token(value: str) -> str:
"""Return a HMAC-SHA256 hex digest of the embed token."""
return hmac.new(
settings.api_key_encryption_key.encode("utf-8"),
value.encode("utf-8"),
hashlib.sha256,
).hexdigest()


def get_embed_token_prefix(token: str) -> str:
"""Return the first 12 characters of the embed token (visible prefix)."""
return token[:12]


api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)


Expand Down
1 change: 1 addition & 0 deletions backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Settings(BaseSettings):
cover_import_timeout_seconds: int = 15
hardcover_app_api_token: str = ""
thalia_cover_search_enabled: bool = False
embed_enabled: bool = True
forwarded_allow_ips: str = "*"

@field_validator("api_key_encryption_key")
Expand Down
12 changes: 12 additions & 0 deletions backend/app/i18n/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"embed": {
"stats": {
"books": "Bücher",
"reading": "Lese ich gerade",
"read": "Gelesen",
"to_read": "Möchte ich lesen",
"pages": "Seiten",
"avg_pages": "Seiten/Buch"
}
}
}
12 changes: 12 additions & 0 deletions backend/app/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"embed": {
"stats": {
"books": "Books",
"reading": "Reading",
"read": "Read",
"to_read": "To Read",
"pages": "Pages",
"avg_pages": "Avg/Book"
}
}
}
12 changes: 12 additions & 0 deletions backend/app/i18n/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"embed": {
"stats": {
"books": "Libros",
"reading": "Leyendo",
"read": "Leído",
"to_read": "Quiero leer",
"pages": "Páginas",
"avg_pages": "Páginas/Libro"
}
}
}
12 changes: 12 additions & 0 deletions backend/app/i18n/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"embed": {
"stats": {
"books": "Livres",
"reading": "En cours",
"read": "Lu",
"to_read": "À lire",
"pages": "Pages",
"avg_pages": "Pages/Livre"
}
}
}
12 changes: 12 additions & 0 deletions backend/app/i18n/zh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"embed": {
"stats": {
"books": "图书",
"reading": "正在阅读",
"read": "已读",
"to_read": "待读",
"pages": "页数",
"avg_pages": "每本页数"
}
}
}
5 changes: 4 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from app._build_info import __git_sha__, __version__
from app.config import settings
from app.logging_config import configure_logging
from app.routers import admin, auth, books, cover_candidates, covers, data, docs, health, hygiene, import_, oidc, profile, progress, statistics, users
from app.routers import admin, auth, books, config, cover_candidates, covers, data, docs, embed, health, hygiene, import_, oidc, profile, progress, statistics, users
from app.services.cover_storage import cleanup_orphan_covers
from app.services.data_import import cleanup_temp_files

Expand Down Expand Up @@ -164,4 +164,7 @@ async def proxy_headers_middleware(request: Request, call_next) -> Response:
app.include_router(hygiene.router)
app.include_router(statistics.router)
app.include_router(data.router)
app.include_router(config.router)
if settings.embed_enabled:
app.include_router(embed.router)
app.include_router(admin.router)
Loading
Loading