├── .DS_Store
├── .ddev
├── config.yaml
├── docker-compose.typesense.yaml
└── web-build
│ └── Dockerfile
├── .dockerignore
├── .env
├── .env.test
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── doc.yml
│ ├── docker-publish.yml
│ └── tests.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── .phpactor.json
├── .phpunit.cache
└── test-results
├── Dockerfile
├── LICENSE
├── README.md
├── assets
├── ReadEbook.vue
├── app.js
├── bootstrap.js
├── controllers.json
├── controllers
│ ├── assistant-controller.js
│ ├── inline-edit-controller.js
│ └── search-controller.js
├── login.js
├── read-ebook.js
└── styles
│ ├── components
│ ├── assistant.css
│ ├── book.css
│ ├── bookDetails.css
│ ├── bookGrid.css
│ ├── bookReader.css
│ ├── bookWithDetails.css
│ ├── card.css
│ ├── facets.css
│ ├── heading.css
│ ├── hero.css
│ ├── heroProgress.css
│ ├── login.css
│ ├── menuBlock.css
│ ├── rating.css
│ ├── sidebar.css
│ └── table.css
│ ├── global.scss
│ ├── layout.css
│ ├── overrides.css
│ └── variables.css
├── backups
└── .gitkeep
├── bin
├── console
└── phpunit
├── composer.json
├── composer.lock
├── config
├── bundles.php
├── packages
│ ├── biblioverse_typesense.yaml
│ ├── cache.yaml
│ ├── csrf.yaml
│ ├── debug.yaml
│ ├── doctrine.yaml
│ ├── doctrine_migrations.yaml
│ ├── framework.yaml
│ ├── http_discovery.yaml
│ ├── knp_menu.yaml
│ ├── knp_paginator.yaml
│ ├── liip_imagine.yaml
│ ├── mailer.yaml
│ ├── messenger.yaml
│ ├── monolog.yaml
│ ├── notifier.yaml
│ ├── routing.yaml
│ ├── security.yaml
│ ├── translation.yaml
│ ├── twig.yaml
│ ├── twig_component.yaml
│ ├── validator.yaml
│ ├── web_profiler.yaml
│ └── webpack_encore.yaml
├── preload.php
├── routes.yaml
├── routes
│ ├── framework.yaml
│ ├── liip_imagine.yaml
│ ├── ux_autocomplete.yaml
│ ├── ux_live_component.yaml
│ └── web_profiler.yaml
└── services.yaml
├── doc
├── .gitignore
├── astro.config.mjs
├── package-lock.json
├── package.json
├── public
│ └── favicon.svg
├── src
│ ├── assets
│ │ ├── natural_language.gif
│ │ ├── summary.png
│ │ └── tags.png
│ ├── content
│ │ ├── config.ts
│ │ └── docs
│ │ │ ├── Troubleshooting
│ │ │ ├── documentation.md
│ │ │ └── github.md
│ │ │ ├── demo.mdx
│ │ │ ├── guides
│ │ │ ├── Administrator
│ │ │ │ ├── adding-books.mdx
│ │ │ │ ├── adding-users.mdx
│ │ │ │ ├── ai-features-configuration.mdx
│ │ │ │ ├── commands.md
│ │ │ │ ├── deleting-books.mdx
│ │ │ │ ├── edit-book.md
│ │ │ │ ├── filesystem.md
│ │ │ │ ├── instance-configuration.mdx
│ │ │ │ └── verified.md
│ │ │ ├── Developer
│ │ │ │ ├── themes.mdx
│ │ │ │ └── translations.mdx
│ │ │ └── User
│ │ │ │ ├── OPDS.mdx
│ │ │ │ ├── homepage.md
│ │ │ │ ├── interactions.md
│ │ │ │ ├── searching.md
│ │ │ │ ├── shelf.md
│ │ │ │ ├── sync-kobo.mdx
│ │ │ │ └── update-settings.md
│ │ │ ├── index.mdx
│ │ │ └── installing
│ │ │ ├── 1-helm.mdx
│ │ │ ├── 1-initial-setup.mdx
│ │ │ ├── 2-new-versions.mdx
│ │ │ ├── dotenv-config.md
│ │ │ └── unraid.md
│ ├── custom.css
│ ├── env.d.ts
│ └── files
│ │ ├── docker-compose.yml
│ │ └── helm-values.yml
└── tsconfig.json
├── docker-compose.test.yml
├── docker-compose.yml
├── justfile
├── migrations
├── .gitignore
├── AbstractMigration.php
├── Version20240101000000.php
├── Version20240112163633.php
├── Version20240121151215.php
├── Version20240121162659.php
├── Version20240219181604.php
├── Version20240505101909.php
├── Version20240510173645.php
├── Version20240512162606.php
├── Version20240513152414.php
├── Version20240531124659.php
├── Version20240531125758.php
├── Version20240714144826.php
├── Version20240721080644.php
├── Version20240812195147.php
├── Version20241121135658.php
├── Version20241121142239.php
├── Version20241129185216.php
├── Version20241208200901.php
├── Version20241210143123.php
├── Version20241218154423.php
├── Version20241218154713.php
├── Version20241223182304.php
├── Version20250101174217.php
├── Version20250106061005.php
├── Version20250106090320.php
├── Version20250106091638.php
├── Version20250106094820.php
├── Version20250123184640.php
├── Version20250128213230.php
├── Version20250128214238.php
├── Version20250130192538.php
├── Version20250131184632.php
├── Version20250201163836.php
├── Version20250202073734.php
├── Version20250202150749.php
└── Version20250213182540.php
├── package-lock.json
├── package.json
├── phpstan.neon
├── phpunit.xml.dist
├── public
├── .htaccess
├── images
│ ├── .gitkeep
│ └── blank.jpg
├── index.php
└── media
│ └── .gitkeep
├── rector.php
├── renovate.json
├── src
├── Ai
│ ├── Communicator
│ │ ├── AbstractCommunicator.php
│ │ ├── AiAction.php
│ │ ├── AiChatInterface.php
│ │ ├── AiCommunicatorInterface.php
│ │ ├── CommunicatorDefiner.php
│ │ ├── GeminiCommunicator.php
│ │ ├── OllamaCommunicator.php
│ │ ├── OpenAiCommunicator.php
│ │ └── PerplexicaCommunicator.php
│ ├── Context
│ │ ├── ContextBuilder.php
│ │ ├── ContextBuildingInterface.php
│ │ └── PerplexicaContextBuilder.php
│ ├── Message.php
│ └── Prompt
│ │ ├── AbstractBookPrompt.php
│ │ ├── BookPromptInterface.php
│ │ ├── PromptFactory.php
│ │ ├── SearchHintPrompt.php
│ │ ├── SummaryPrompt.php
│ │ └── TagPrompt.php
├── Command
│ ├── BackupDbCommand.php
│ ├── BooksAiCommand.php
│ ├── BooksCheckCommand.php
│ ├── BooksExtractCoverCommand.php
│ ├── BooksRelocateCommand.php
│ ├── BooksScanCommand.php
│ ├── CreateUserCommand.php
│ └── TranslationRetrieveFromProfilerCommand.php
├── Config
│ └── ConfigValue.php
├── Controller
│ ├── .gitignore
│ ├── AbstractController.php
│ ├── AiModelController.php
│ ├── AuthorController.php
│ ├── AutocompleteGroupController.php
│ ├── BookController.php
│ ├── DefaultController.php
│ ├── GroupController.php
│ ├── InstanceConfigurationController.php
│ ├── Kobo
│ │ ├── AbstractKoboController.php
│ │ ├── Api
│ │ │ ├── ApiEndpointController.php
│ │ │ ├── ImageController.php
│ │ │ ├── V1
│ │ │ │ ├── AnalyticsController.php
│ │ │ │ ├── DownloadController.php
│ │ │ │ ├── GenericController.php
│ │ │ │ ├── InitializationController.php
│ │ │ │ ├── Library
│ │ │ │ │ ├── LibraryController.php
│ │ │ │ │ ├── MetadataController.php
│ │ │ │ │ └── StateController.php
│ │ │ │ ├── ProductsController.php
│ │ │ │ └── TagController.php
│ │ │ └── V3
│ │ │ │ └── ContentController.php
│ │ ├── KoboDeviceController.php
│ │ └── KoboNextReadController.json
│ ├── LoginController.php
│ ├── OPDS
│ │ └── OpdsController.php
│ ├── OpdsAccessController.php
│ ├── SerieController.php
│ ├── ShelfController.php
│ ├── ShelfCrudController.php
│ └── UserController.php
├── DataFixtures
│ ├── BookFixture.php
│ ├── KoboFixture.php
│ ├── OpdsAccessFixture.php
│ ├── ShelfFixture.php
│ ├── ShelfKoboFixture.php
│ ├── UserFixture.php
│ └── books.yaml
├── Entity
│ ├── .gitignore
│ ├── AiModel.php
│ ├── Book.php
│ ├── BookInteraction.php
│ ├── BookmarkUser.php
│ ├── InstanceConfiguration.php
│ ├── KoboDevice.php
│ ├── KoboSyncedBook.php
│ ├── OpdsAccess.php
│ ├── RandomGeneratorTrait.php
│ ├── Shelf.php
│ ├── User.php
│ └── UuidGeneratorTrait.php
├── Enum
│ ├── AgeCategory.php
│ ├── AiMessageRole.php
│ ├── ReadStatus.php
│ └── ReadingList.php
├── EventListener
│ ├── LanguageListener.php
│ └── LoginListener.php
├── EventSubscriber
│ └── KoboLogRequestSubscriber.php
├── Exception
│ ├── BookExtractionException.php
│ └── BookFileNotFound.php
├── Form
│ ├── AiConfigurationType.php
│ ├── AiModelType.php
│ ├── BookType.php
│ ├── InlineInteractionType.php
│ ├── InstanceConfigurationType.php
│ ├── KoboLastSyncTokenType.php
│ ├── KoboType.php
│ ├── LabelTranslationFormExtension.php
│ ├── ProfileType.php
│ ├── ShelfType.php
│ └── UserType.php
├── Kernel.php
├── Kobo
│ ├── BookDownloadInfo.php
│ ├── DownloadHelper.php
│ ├── ImageProcessor
│ │ └── CoverTransformer.php
│ ├── Kepubify
│ │ ├── KebpubifyCachedData.php
│ │ ├── KepubifyConversionFailed.php
│ │ ├── KepubifyEnabler.php
│ │ ├── KepubifyMessage.php
│ │ └── KepubifyMessageHandler.php
│ ├── LogProcessor
│ │ └── KoboContextProcessor.php
│ ├── ParamConverter
│ │ ├── KoboParamConverter.php
│ │ └── SyncTokenParamConverter.php
│ ├── Proxy
│ │ ├── KoboHeaderFilterTrait.php
│ │ ├── KoboProxyConfiguration.json
│ │ ├── KoboProxyConfiguration.php
│ │ ├── KoboProxyListener.php
│ │ ├── KoboProxyLogger.php
│ │ ├── KoboProxyLoggerFactory.php
│ │ └── KoboStoreProxy.php
│ ├── Request
│ │ ├── Bookmark.php
│ │ ├── ReadingState.php
│ │ ├── ReadingStateLocation.php
│ │ ├── ReadingStateStatistics.php
│ │ ├── ReadingStateStatusInfo.php
│ │ ├── ReadingStates.php
│ │ ├── TagDeleteRequest.php
│ │ └── TagDeleteRequestItem.php
│ ├── Response
│ │ ├── MetadataResponseService.php
│ │ ├── ReadingStateResponse.php
│ │ ├── ReadingStateResponseFactory.php
│ │ ├── StateResponse.php
│ │ ├── SyncResponse.php
│ │ ├── SyncResponseFactory.php
│ │ └── SyncResponseHelper.php
│ ├── Serializer
│ │ └── KoboNameConverter.php
│ ├── SyncToken.php
│ ├── SyncTokenParser.php
│ └── UpstreamSyncMerger.php
├── Menu
│ └── MenuBuilder.php
├── OPDS
│ └── Opds.php
├── Repository
│ ├── .gitignore
│ ├── AiModelRepository.php
│ ├── BookInteractionRepository.php
│ ├── BookRepository.php
│ ├── BookmarkUserRepository.php
│ ├── InstanceConfigurationRepository.php
│ ├── KoboDeviceRepository.php
│ ├── KoboSyncedBookRepository.php
│ ├── OpdsAccessRepository.php
│ ├── ShelfRepository.php
│ └── UserRepository.php
├── Security
│ ├── Badge
│ │ └── KoboDeviceBadge.php
│ ├── KoboAccessTokenAuthenticator.php
│ ├── KoboAccessTokenHandlerInterface.php
│ ├── KoboTokenExtractor.php
│ ├── KoboTokenHandler.php
│ ├── OpdsTokenExtractor.php
│ ├── OpdsTokenHandler.php
│ ├── Token
│ │ └── PostAuthenticationTokenWithKoboDevice.php
│ └── Voter
│ │ ├── AiFeaturesVoter.php
│ │ ├── BookInteractionVoter.php
│ │ ├── BookVoter.php
│ │ ├── KoboDeviceVoter.php
│ │ └── RelocationVoter.php
├── Service
│ ├── AccessControlSubscriber.php
│ ├── BookArchiver.php
│ ├── BookFileSystemManager.php
│ ├── BookFileSystemManagerInterface.php
│ ├── BookInteractionService.php
│ ├── BookManager.php
│ ├── BookProgressionService.php
│ ├── ConnectionKeepAliveSubscriber.php
│ ├── FilteredBookUrlGenerator.php
│ ├── KoboSyncTokenExtractor.php
│ ├── Search
│ │ └── SearchHelper.php
│ ├── ShelfManager.php
│ └── ThemeSelector.php
└── Twig
│ ├── Components
│ ├── AddNewShelf.php
│ ├── Assistant.php
│ ├── BootstrapModal.php
│ ├── FieldGuesser.php
│ ├── InlineEditGroup.php
│ ├── InlineEditInteraction.php
│ ├── InlineEditMultiple.php
│ ├── InlineEditVerified.php
│ ├── Search.php
│ └── UploadBookPicture.php
│ ├── Extension
│ └── ThemeExtension.php
│ ├── FilteredBookUrl.php
│ ├── Runtime
│ └── ThemeExtensionRuntime.php
│ └── UniqueIdExtension.php
├── symfony.lock
├── templates
├── ai_model
│ ├── _form.html.twig
│ ├── edit.html.twig
│ ├── index.html.twig
│ ├── new.html.twig
│ └── show.html.twig
├── author
│ └── detail.html.twig
├── bare.html.twig
├── base.html.twig
├── book
│ ├── _book-row-empty.html.twig
│ ├── _book-row.html.twig
│ ├── _cover.html.twig
│ ├── _cover_empty.html.twig
│ ├── _empty.html.twig
│ ├── _knp_minimal_pagination.html.twig
│ ├── _teaser.html.twig
│ ├── consume.html.twig
│ ├── index.html.twig
│ ├── reader-files-epub.html.twig
│ ├── reader-files.html.twig
│ └── upload.html.twig
├── bundles
│ └── TwigBundle
│ │ └── Exception
│ │ └── error.html.twig
├── components
│ ├── AddNewShelf.html.twig
│ ├── Assistant.html.twig
│ ├── BootstrapModal.html.twig
│ ├── FieldGuesser.html.twig
│ ├── InlineEditGroup.html.twig
│ ├── InlineEditInteraction.html.twig
│ ├── InlineEditMultiple.html.twig
│ ├── InlineEditVerified.html.twig
│ ├── Search.html.twig
│ ├── UploadBookPicture.html.twig
│ ├── _facet.html.twig
│ └── _rating.html.twig
├── default
│ ├── dashboard.html.twig
│ ├── index.html.twig
│ ├── notverified.html.twig
│ ├── readingList.html.twig
│ └── timeline.html.twig
├── group
│ └── index.html.twig
├── instance_configuration
│ ├── _form.html.twig
│ ├── edit.html.twig
│ └── index.html.twig
├── kobodevice_user
│ ├── _delete_form.html.twig
│ ├── _form.html.twig
│ ├── _form_theme.html.twig
│ ├── edit.html.twig
│ ├── index.html.twig
│ ├── instructions.html.twig
│ ├── logs.html.twig
│ └── new.html.twig
├── login
│ └── login.html.twig
├── main_menu.html.twig
├── serie
│ └── detail.html.twig
├── shelf
│ └── index.html.twig
├── shelf_crud
│ ├── _form.html.twig
│ ├── edit.html.twig
│ └── index.html.twig
├── themes
│ └── dark
│ │ └── bare.html.twig
└── user
│ ├── _delete_form.html.twig
│ ├── _form.html.twig
│ ├── edit.html.twig
│ ├── index.html.twig
│ ├── new.html.twig
│ └── profile.html.twig
├── tests
├── Ai
│ └── AiModelControllerTest.php
├── Command
│ └── CreateUserCommandTest.php
├── Component
│ └── SearchComponentTest.php
├── Contraints
│ ├── ArrayHasNestedKey.php
│ ├── AssertHasDownloadWithFormat.php
│ ├── JSONContainKeys.php
│ └── JSONIsValidSyncResponse.php
├── Controller
│ ├── Kobo
│ │ ├── Api
│ │ │ ├── V1
│ │ │ │ ├── AnalyticsControllerTest.php
│ │ │ │ ├── DownloadControllerTest.php
│ │ │ │ ├── GenericControllerTest.php
│ │ │ │ ├── InitializationControllerTest.json
│ │ │ │ ├── InitializationControllerTest.php
│ │ │ │ ├── Library
│ │ │ │ │ ├── MetadataControllerTest.php
│ │ │ │ │ ├── StateControllerTest.php
│ │ │ │ │ └── SyncControllerTest.php
│ │ │ │ ├── ProductsControllerTest.php
│ │ │ │ └── TagControllerTest.php
│ │ │ └── V3
│ │ │ │ └── ContentControllerTest.php
│ │ ├── KoboControllerTestCase.php
│ │ └── KoboImageControllerTest.php
│ └── Opds
│ │ ├── AbstractOpdsTestController.php
│ │ └── OpdsAccessControllerTest.php
├── DynamicShelfTest.php
├── Entity
│ ├── BookTest.php
│ └── SyncedBookTest.php
├── FileSystemManagerForTests.php
├── GedmoTest.php
├── InstanceConfigurationControllerTest.php
├── Kobo
│ ├── DownloadHelperTest.php
│ ├── Proxy
│ │ └── KoboProxyConfigurationTest.php
│ ├── Request
│ │ ├── ReadingStateDeserializeTest.json
│ │ └── ReadingStateDeserializeTest.php
│ ├── Response
│ │ └── SyncResponseHelperTest.php
│ └── SyncTokenTest.php
├── LoginTest.php
├── Mock
│ └── AbstractApiMock.php
├── Resources
│ ├── books
│ │ ├── fake-AChristmasCarol.epub
│ │ ├── fake-AnnaKarenina.epub
│ │ ├── fake-BrothersKaramazov.epub
│ │ ├── fake-CountOfMonteCristo.epub
│ │ ├── fake-CrimeAndPunishment.epub
│ │ ├── fake-DivineComedy.epub
│ │ ├── fake-DonQuixote.epub
│ │ ├── fake-DorianGray.epub
│ │ ├── fake-Dracula.epub
│ │ ├── fake-Frankenstein.epub
│ │ ├── fake-GreatExpectations.epub
│ │ ├── fake-JaneEyre.epub
│ │ ├── fake-LesMiserables.epub
│ │ ├── fake-MobyDick.epub
│ │ ├── fake-PrideAndPrejudice.epub
│ │ ├── fake-SherlockHolmes.epub
│ │ ├── fake-TaleOfTwoCities.epub
│ │ ├── fake-TheJungleBook.epub
│ │ ├── fake-WarAndPeace.epub
│ │ ├── fake-WutheringHeights.epub
│ │ └── real-TheOdysses.epub
│ └── covers
│ │ └── TheOdysses.jpg
├── SampleTest.php
├── Service
│ ├── BookArchiverTest.php
│ └── BookProgressionServiceTest.php
├── SmokeTest.php
├── TestCaseHelperTrait.php
├── TestClock.php
├── Translations
│ └── TranslationExtractionTest.php
├── bootstrap.php
└── coverage
│ └── .gitkeep
├── translations
├── .gitignore
├── AutocompleteBundle.en.yaml
├── AutocompleteBundle.fr.yaml
├── KnpPaginatorBundle.en.yaml
├── KnpPaginatorBundle.fr.yaml
├── messages+intl-icu.en.yaml
├── messages+intl-icu.fr.yaml
├── security.en.yaml
├── security.fr.yaml
├── validators.en.yaml
└── validators.fr.yaml
├── upgrade.sh
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/.DS_Store
--------------------------------------------------------------------------------
/.ddev/docker-compose.typesense.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | typesense:
3 | image: typesense/typesense:0.26.0.rc46
4 | restart: on-failure
5 | ports:
6 | - 8983
7 | - "8108:8108"
8 | volumes:
9 | - searchdata:/data
10 | command: '--data-dir /data --api-key=xyz --enable-cors'
11 |
12 | volumes:
13 | searchdata:
--------------------------------------------------------------------------------
/.ddev/web-build/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | ENV COMPOSER_ALLOW_SUPERUSER 1
3 | ENV COMPOSER_HOME /home/.composer
4 | RUN mkdir -p /home/.composer
5 | RUN printf "deb http://http.us.debian.org/debian stable main contrib non-free" > /etc/apt/sources.list.d/nonfree.list
6 | RUN apt-get update
7 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \
8 | p7zip-full \
9 | build-essential \
10 | unrar && rm -rf /var/lib/apt/lists/*
11 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | # define your env variables for the test env here
2 | KERNEL_CLASS='App\Kernel'
3 | APP_SECRET='$ecretf0rt3st'
4 | SYMFONY_DEPRECATIONS_HELPER=999999
5 | PANTHER_APP_ENV=panther
6 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
7 |
8 | # Root access needed to create the new TEST database
9 |
10 | DATABASE_URL="mysql://root:biblioteca@db:3306/biblioteca?serverVersion=10.10.0-MariaDB&charset=utf8"
11 |
12 | TYPESENSE_URL=http://typesense:8108
13 | TYPESENSE_KEY=xyz
14 |
15 | # Disable the proxy for the test env
16 | KOBO_PROXY_USE_EVERYWHERE=0
17 | KOBO_PROXY_ENABLED=0
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Proposed changes
2 |
3 | Explain your PR here
4 |
5 |
6 | ## Checklist
7 | - [ ] Tests are passing
8 | - [ ] New tests have been written
9 | - [ ] Documentation has been updated
10 | - [ ] Translations have been updated
11 | - [ ] Breaking changes have been avoided or documented
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/doc.yml:
--------------------------------------------------------------------------------
1 | name: documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - migrate-documentation
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | matrix:
15 | os: [ubuntu-latest]
16 | node: [20]
17 |
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@master
21 |
22 | - name: Setup node env
23 | uses: actions/setup-node@v4.4.0
24 | with:
25 | node-version: ${{ matrix.node }}
26 |
27 | - name: Install dependencies
28 | run: npm ci
29 | working-directory: ./doc
30 |
31 | - name: Generate
32 | run: npm run build
33 | working-directory: ./doc
34 |
35 | - name: Deploy
36 | uses: peaceiris/actions-gh-pages@v4
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: ./doc/dist
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in(__DIR__.'/src')
8 | ->in(__DIR__.'/tests')
9 | ->in(__DIR__.'/migrations')
10 | ;
11 |
12 | $config = new PhpCsFixer\Config('Biblioteca');
13 | $config->setLineEnding(PHP_EOL);
14 | $config->setFinder($finder);
15 | $config->setRiskyAllowed(true);
16 | $config->setRules([
17 | '@Symfony' => true,
18 | 'increment_style' => false,
19 | 'logical_operators' => true,
20 | 'no_superfluous_phpdoc_tags' => false,
21 | 'phpdoc_align' => [
22 | 'align' => 'left',
23 | ],
24 | 'phpdoc_separation' => false,
25 | 'phpdoc_summary' => false,
26 | 'visibility_required' => [
27 | 'elements' => ['property', 'method', 'const'],
28 | ],
29 | 'method_argument_space' => [
30 | 'on_multiline' => 'ensure_fully_multiline',
31 | ],
32 | 'yoda_style' => false,
33 | ]);
34 |
35 | return $config;
36 |
--------------------------------------------------------------------------------
/.phpactor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "/phpactor.schema.json",
3 | "language_server_phpstan.enabled": true,
4 | "language_server_php_cs_fixer.enabled": true,
5 | "symfony.enabled": true,
6 | "indexer.exclude_patterns": [
7 | "/vendor/**/Tests/**/*",
8 | "/vendor/**/tests/**/*",
9 | "/var/cache/**/*",
10 | "/vendor/composer/**/*"
11 | ]
12 | }
--------------------------------------------------------------------------------
/.phpunit.cache/test-results:
--------------------------------------------------------------------------------
1 | {"version":1,"defects":{"App\\Tests\\GedmoTest::testTimestamp":8,"App\\Tests\\SampleTest::testSample":8},"times":{"App\\Tests\\SampleTest::testSample":0,"App\\Tests\\GedmoTest::testTimestamp":0.133}}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Biblioteca
2 |
3 | Biblioteca is a web application made to manage large ebook libraries and is developed aiming to help you to have consistent and well
4 | classified libraries.
5 |
6 |
7 | ## Documentation
8 | [Read the full documentation on the dedicated website](https://biblioverse.github.io/biblioteca/)
9 |
10 |
--------------------------------------------------------------------------------
/assets/app.js:
--------------------------------------------------------------------------------
1 | import './bootstrap.js';
2 | import './styles/global.scss';
3 | import * as bootstrap from 'bootstrap'
4 |
5 |
6 | window.addEventListener('manager:flush', () => {
7 | setTimeout(function (){location.reload()}, 500)
8 | });
9 |
10 | const toastElList = document.querySelectorAll('.toast')
11 | const toastList = [...toastElList].map(toastEl => new bootstrap.Toast(toastEl).show())
12 |
13 | const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
14 | const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
--------------------------------------------------------------------------------
/assets/bootstrap.js:
--------------------------------------------------------------------------------
1 | import { startStimulusApp } from '@symfony/stimulus-bridge';
2 |
3 | // Registers Stimulus controllers from controllers.json and in the controllers/ directory
4 | export const app = startStimulusApp(require.context(
5 | '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
6 | true,
7 | /\.[jt]sx?$/
8 | ));
9 | // register any custom, 3rd party controllers here
10 | // app.register('some_controller_name', SomeImportedController);
11 |
--------------------------------------------------------------------------------
/assets/controllers.json:
--------------------------------------------------------------------------------
1 | {
2 | "controllers": {
3 | "@symfony/ux-autocomplete": {
4 | "autocomplete": {
5 | "enabled": true,
6 | "fetch": "eager",
7 | "autoimport": {
8 | "tom-select/dist/css/tom-select.default.css": false,
9 | "tom-select/dist/css/tom-select.bootstrap4.css": false,
10 | "tom-select/dist/css/tom-select.bootstrap5.css": true
11 | }
12 | }
13 | },
14 | "@symfony/ux-live-component": {
15 | "live": {
16 | "enabled": true,
17 | "fetch": "eager",
18 | "autoimport": {
19 | "@symfony/ux-live-component/dist/live.min.css": true
20 | }
21 | }
22 | }
23 | },
24 | "entrypoints": []
25 | }
26 |
--------------------------------------------------------------------------------
/assets/controllers/assistant-controller.js:
--------------------------------------------------------------------------------
1 | import {Controller} from '@hotwired/stimulus';
2 | import {getComponent} from '@symfony/ux-live-component';
3 |
4 | export default class extends Controller {
5 | async initialize() {
6 | this.component = await getComponent(this.element);
7 |
8 | window.removeEventListener('select:clear', this.reinitializeTomSelect);
9 | window.addEventListener('select:clear', this.reinitializeTomSelect);
10 |
11 | }
12 |
13 | reinitializeTomSelect(event) {
14 | console.log(event.detail.field);
15 | const selects = document.querySelectorAll(`#book-assistant-${event.detail.book} .tomselected`);
16 | selects.forEach(select => {
17 | if (select.tomselect && select.name.includes(event.detail.field)) {
18 | select.tomselect.sync()
19 | }
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/assets/controllers/inline-edit-controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from '@hotwired/stimulus';
2 | import { getComponent } from '@symfony/ux-live-component';
3 |
4 | export default class extends Controller {
5 | async initialize() {
6 | this.component = await getComponent(this.element);
7 |
8 | this.component.on('render:finished', (component) => {
9 |
10 | if(document.querySelector('.alert-remove')) {
11 | setTimeout(function () {
12 | location.reload();
13 | }, 300);
14 | }
15 |
16 | });
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/assets/controllers/search-controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from '@hotwired/stimulus';
2 | import { getComponent } from '@symfony/ux-live-component';
3 |
4 | export default class extends Controller {
5 | static targets = [ "search", "query", "suggestions" ]
6 | async initialize() {
7 | this.component = await getComponent(this.element);
8 |
9 | }
10 |
11 | connect() {
12 | }
13 |
14 | redirect() {
15 | const path = window.location.pathname
16 | if(!path.includes('/all')) {
17 | document.getElementById('js-main').innerHTML = ''
18 |
19 | history.pushState({}, '', '/all')
20 | }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/assets/read-ebook.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import ReadEBook from './ReadEbook.vue'
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const mountId = 'vue-book-reader';
6 | const file = document.getElementById(mountId).getAttribute('data-file')
7 | const css = document.getElementById(mountId).getAttribute('data-css')
8 | const bgColor = document.getElementById(mountId).getAttribute('data-background-color')
9 | const percent = document.getElementById(mountId).getAttribute('data-percent')
10 | const progressionUrl = document.getElementById(mountId).getAttribute('data-progression-url')
11 | const backUrl = document.getElementById(mountId).getAttribute('data-back-url')
12 | createApp(ReadEBook, {
13 | file: file,
14 | css: css,
15 | bgColor: bgColor,
16 | percent: percent,
17 | progressionUrl: progressionUrl,
18 | backUrl: backUrl
19 | }).mount(`#${mountId}`);
20 | });
21 |
--------------------------------------------------------------------------------
/assets/styles/components/assistant.css:
--------------------------------------------------------------------------------
1 | @media (min-width: 768px) {
2 |
3 | .Assistant {
4 | display: grid;
5 | grid-template-columns: 15% auto 30%;
6 | }
7 |
8 | .Assistant__form {
9 | max-height: calc(75vh - 32px);
10 | overflow-y: auto;
11 | padding: 0 1rem;
12 | }
13 |
14 | .Assistant__chat {
15 | max-height: calc(75vh - 32px);
16 | overflow-y: auto;
17 | }
18 | }
--------------------------------------------------------------------------------
/assets/styles/components/bookDetails.css:
--------------------------------------------------------------------------------
1 | .BookDetails {
2 | display: grid;
3 | gap: var(--space--md);
4 | grid-template-areas: "actions" "summary";
5 | }
6 |
7 | .BookDetails--withInfo {
8 | grid-template-areas: "actions" "summary" "info";
9 | }
10 |
11 | @media (min-width: 768px) {
12 | .BookDetails--withInfo {
13 | grid-template-areas: "summary summary" "info actions";
14 | grid-template-columns: 1fr 1fr;
15 | }
16 | }
17 |
18 | @media (min-width: 1000px) {
19 | .BookDetails {
20 | grid-template-areas: "summary actions";
21 | grid-template-columns: 4fr 2fr;
22 | }
23 |
24 | .BookDetails--withInfo {
25 | grid-template-areas: "summary info actions";
26 | grid-template-columns: 3fr 2fr 2fr;
27 | }
28 | }
29 |
30 | .BookDetails__actions {
31 | grid-area: actions;
32 | }
33 |
34 | .BookDetails__summary {
35 | grid-area: summary;
36 | }
37 |
38 | .BookDetails__info {
39 | grid-area: info;
40 | }
41 |
42 |
43 | /*.BookDetails__item {}*/
44 |
45 | .BookDetails__item__title {
46 | margin-bottom: var(--space--sm) !important;
47 | }
48 |
49 | .BookDetails__item__content {
50 | list-style-type: none;
51 | padding-left: 1rem;
52 | margin: 0;
53 | }
54 |
55 | * + .BookDetails__item {
56 | margin-top: var(--space--md);
57 | }
--------------------------------------------------------------------------------
/assets/styles/components/bookGrid.css:
--------------------------------------------------------------------------------
1 | .bookGrid {
2 | display: grid;
3 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
4 | gap: var(--space--lg);
5 | }
6 |
7 | .bookGrid--large {
8 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
9 | gap: var(--space--xl);
10 | }
11 |
--------------------------------------------------------------------------------
/assets/styles/components/bookReader.css:
--------------------------------------------------------------------------------
1 | .vue-book-reader {
2 | width: 100%;
3 | }
4 |
5 | .vue-book-reader .flex {
6 | display: flex;
7 | }
8 |
9 | .vue-book-reader .flex button {
10 | display: block;
11 | }
12 |
--------------------------------------------------------------------------------
/assets/styles/components/bookWithDetails.css:
--------------------------------------------------------------------------------
1 | .BooksWithDetails {
2 | display: grid;
3 | grid-template-areas: "info" "content";
4 | gap: var(--space--md);
5 | }
6 |
7 | .BooksWithDetails__content {
8 | grid-area: content;
9 | }
10 |
11 | .BooksWithDetails__info {
12 | grid-area: info;
13 | }
14 |
15 |
16 | @media (min-width: 768px) {
17 | .BooksWithDetails {
18 | grid-template-columns: auto minmax(30%, 300px);
19 | grid-template-areas: "content info";
20 | }
21 | }
--------------------------------------------------------------------------------
/assets/styles/components/card.css:
--------------------------------------------------------------------------------
1 | .Card {
2 | background: var(--color-main-03);
3 | border-radius: var(--border-radius--sm);
4 | }
5 |
6 | .Card__title {
7 | padding: var(--space--xl);
8 | padding-bottom: var(--space--sm);
9 | }
10 |
11 | .Card__content {
12 | padding: var(--space--xl);
13 | }
14 |
15 | * + .Card__content {
16 | padding-top: var(--space--sm);
17 | }
18 |
--------------------------------------------------------------------------------
/assets/styles/components/facets.css:
--------------------------------------------------------------------------------
1 | .facets {
2 | display: grid;
3 | gap: var(--space--xs);
4 | margin-bottom: var(--space--lg);
5 | }
6 |
7 | .facets__item {
8 | width: 100%;
9 | display: flex;
10 | justify-content: space-between;
11 | gap: var(--space--sm);
12 | font-size: var(--font-size--sm);
13 |
14 | border-width: 0;
15 | background: transparent;
16 | border-radius: var(--border-radius--sm);
17 | padding: var(--space--xs);
18 | line-height: 1.5;
19 | text-align: left;
20 | }
21 |
22 | .facets__item--active {
23 | background: var(--bs-list-group-active-bg);
24 | color: var(--bs-list-group-active-color);
25 | }
26 |
--------------------------------------------------------------------------------
/assets/styles/components/heading.css:
--------------------------------------------------------------------------------
1 | .Heading {
2 | margin: 0;
3 | font-size: 1.25rem;
4 | font-weight: 500;
5 | line-height: 1.2em;
6 | }
7 |
8 | .Heading--sm {
9 | font-size: 1rem;
10 | }
11 |
12 | .Heading--lg {
13 | font-size: 1.5rem;
14 | }
15 |
16 | .Heading--xl {
17 | font-size: 1.75rem;
18 | }
19 |
20 | .Heading--2xl {
21 | font-size: 2.5rem;
22 | }
23 |
--------------------------------------------------------------------------------
/assets/styles/components/heroProgress.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --progress__track--background: var(--color-main-02);
3 | --progress__track--height: 1em;
4 | --progress__track--border-radius: 4px;
5 | --progress__bar--background: var(--color-main-01);
6 | }
7 |
8 | .heroProgress {
9 | position: absolute;
10 | bottom: -4px;
11 | left: 0;
12 | right: 0;
13 | }
14 |
15 | .heroProgress__bar {
16 | background: var(--progress__track--background);
17 | height: 4px;
18 | }
19 |
20 | .heroProgress__bar__progress {
21 | background: var(--progress__bar--background);
22 | height: 4px;
23 | }
24 |
--------------------------------------------------------------------------------
/assets/styles/components/login.css:
--------------------------------------------------------------------------------
1 | .LoginMasonry {
2 | position: absolute;
3 | inset: 0;
4 | overflow: hidden;
5 |
6 | justify-content: space-between;
7 | overflow: hidden;
8 | }
9 |
10 | .LoginMasonry__inner {
11 | width: 100%;
12 | height: 100%;
13 | transform: skew(-0.06turn, 18deg) scale(1.5); /**/
14 | display: flex;
15 | }
16 |
17 | .LoginMasonry__column {
18 | width: 100%;
19 | flex: 1 1 0%;
20 | }
21 |
22 | .LoginMasonry__column__content {
23 | transition-duration: 100ms;
24 | transition-timing-function: linear;
25 | transition-property: transform;
26 | width: 100%;
27 | }
28 |
29 | .LoginMasonry__item {
30 | width: 100%;
31 | aspect-ratio: 0.75;
32 | overflow: hidden;
33 | }
34 |
35 | .LoginMasonry__item canvas {
36 | width: 100%;
37 | height: 100%;
38 | }
39 |
40 | .LoginOverlay {
41 | position: absolute;
42 | inset: 0;
43 | background: rgba(255, 255, 255, 0.5);
44 | backdrop-filter: blur(12px);
45 | }
46 |
47 | .LoginCardContainer {
48 | position: absolute;
49 | inset: 0;
50 | display: flex;
51 | justify-content: center;
52 | align-items: center;
53 | }
54 |
55 | .LoginCard {
56 | background-color: rgb(255 255 255 / 0.9);
57 | border-radius: 0.5rem;
58 | box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
59 | }
60 |
--------------------------------------------------------------------------------
/assets/styles/components/menuBlock.css:
--------------------------------------------------------------------------------
1 | .Menu {
2 | padding-inline: var(--space--sm);
3 | margin-block-end: var(--space--md);
4 | }
5 |
6 | .MenuLink {
7 | display: grid;
8 | grid-template-columns: 1rem auto;
9 | gap: var(--space--sm);
10 | margin-left: 0.5rem;
11 |
12 | transition:
13 | color 0.3s ease-in-out,
14 | background-color 0.3s ease-in-out,
15 | border-color 0.3s ease-in-out;
16 |
17 | text-decoration: none;
18 | align-items: center;
19 | color: var(--bs-nav-link-color);
20 | font-size: var(--font-size--sm);
21 | padding: var(--space--xs) var(--space--sm);
22 | border-radius: var(--border-radius--sm);
23 | }
24 |
25 | .MenuLink i {
26 | justify-self: end;
27 | transition: inherit;
28 | }
29 |
30 | a.MenuLink:hover,
31 | .Menu--active > .MenuLink,
32 | .Menu--childActive > .MenuLink {
33 | background-color: var(--color-main-02);
34 | }
35 |
36 | a.MenuLink:hover i,
37 | .Menu--active > .MenuLink i,
38 | .Menu--childActive > .MenuLink i {
39 | color: var(--color-main-01);
40 | }
41 |
42 | .MenuLink--big {
43 | font-size: var(--font-size--lg);
44 | grid-template-columns: 1.5rem auto;
45 | margin-left: 0;
46 | }
47 |
48 | .MenuLink--big i {
49 | font-size: 1.1em;
50 | }
51 |
--------------------------------------------------------------------------------
/assets/styles/components/rating.css:
--------------------------------------------------------------------------------
1 | .Rating {
2 | color: rgba(var(--bs-secondary-rgb), 1);
3 | }
4 |
--------------------------------------------------------------------------------
/assets/styles/components/sidebar.css:
--------------------------------------------------------------------------------
1 | @media (min-width: 768px) {
2 | .sidebar .offcanvas-lg {
3 | position: sticky;
4 | top: 48px;
5 | }
6 | }
7 |
8 | @media (min-width: 1200px) {
9 | .modal-xl {
10 | --bs-modal-width: 90%;
11 | }
12 | }
13 |
14 | .sidebar .nav-link {
15 | font-size: var(--font-size--sm);
16 | font-weight: 500;
17 | }
18 |
19 | .sidebar .nav-item.active > .nav-link {
20 | font-weight: bold;
21 | color: brown;
22 | }
23 | .sidebar .icon-link .bi {
24 | height: auto;
25 | }
26 |
27 | .sidebar-heading {
28 | font-size: var(--font-size--xs);
29 | color: brown;
30 | }
31 |
--------------------------------------------------------------------------------
/assets/styles/components/table.css:
--------------------------------------------------------------------------------
1 | .Table {
2 | width: 100%;
3 | }
4 |
5 | .Table > :not(caption) > * > * {
6 | padding: 0.5rem;
7 | }
8 |
--------------------------------------------------------------------------------
/assets/styles/global.scss:
--------------------------------------------------------------------------------
1 | // the ~ allows you to reference things in node_modules
2 | @import "~bootstrap/scss/bootstrap";
3 | @import "~bootstrap-icons/font/bootstrap-icons.css";
4 | @import "./overrides.css";
5 |
6 | @import "./variables.css";
7 |
8 | @import "./layout.css";
9 |
10 | @import "./components/assistant.css";
11 | @import "./components/book.css";
12 | @import "./components/bookDetails.css";
13 | @import "./components/bookGrid.css";
14 | @import "./components/bookReader.css";
15 | @import "./components/bookWithDetails.css";
16 | @import "./components/card.css";
17 | @import "./components/facets.css";
18 | @import "./components/heading.css";
19 | @import "./components/hero.css";
20 | @import "./components/heroProgress.css";
21 | @import "./components/menuBlock.css";
22 | @import "./components/rating.css";
23 | @import "./components/sidebar.css";
24 | @import "./components/table.css";
25 | @import "./components/login.css";
26 |
--------------------------------------------------------------------------------
/assets/styles/overrides.css:
--------------------------------------------------------------------------------
1 | body.bg-darker {
2 | background-color: #000;
3 | color: #fff;
4 | }
5 |
6 | .bi {
7 | height: auto;
8 | }
9 |
10 | .navbar-brand {
11 | padding-top: 0.75rem;
12 | padding-bottom: 0.75rem;
13 | background-color: rgba(0, 0, 0, 0.25);
14 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25);
15 | }
16 |
17 | .navbar .form-control {
18 | padding: 0.75rem 1rem;
19 | }
20 |
--------------------------------------------------------------------------------
/assets/styles/variables.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --space--xs: 0.25rem;
3 | --space--sm: 0.5rem;
4 | --space--md: 1rem;
5 | --space--lg: 1.5rem;
6 | --space--xl: 2rem;
7 |
8 | --border-radius--sm: 0.25rem;
9 | --border-radius--md: 0.5rem;
10 | --border-radius--lg: 1rem;
11 |
12 | --font-size--xs: 0.75rem;
13 | --font-size--sm: 0.875rem;
14 | --font-size--md: 1rem;
15 | --font-size--lg: 1.25rem;
16 | --font-size--xl: 1.5rem;
17 |
18 | --body-background-color: #f2f2f2;
19 |
20 | --color-main-01: #de9779; /* cover background, icon color, progress bar progress */
21 | --color-main-02: #eddcd5; /* nav background, progress bar background, block emphasis */
22 | --color-main-03: #f1edeb; /* block background */
23 | }
24 |
25 | [data-bs-theme=dark] {
26 | --body-background-color: #0d0d0d;
27 | --color-main-01: #bf4f20;
28 | --color-main-02: #4f2311;
29 | --color-main-03: #241e1b;
30 | }
31 |
--------------------------------------------------------------------------------
/backups/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/backups/.gitkeep
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
--------------------------------------------------------------------------------
/doc/src/assets/natural_language.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/doc/src/assets/natural_language.gif
--------------------------------------------------------------------------------
/doc/src/assets/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/doc/src/assets/summary.png
--------------------------------------------------------------------------------
/doc/src/assets/tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/doc/src/assets/tags.png
--------------------------------------------------------------------------------
/doc/src/content/config.ts:
--------------------------------------------------------------------------------
1 | import { defineCollection } from 'astro:content';
2 | import { docsLoader } from "@astrojs/starlight/loaders";
3 | import { docsSchema } from '@astrojs/starlight/schema';
4 |
5 | export const collections = {
6 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
7 | };
8 |
--------------------------------------------------------------------------------
/doc/src/content/docs/Troubleshooting/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Updating this documentation
3 | ---
4 |
5 | This documentation is hosted on [GitHub](https://github.com/biblioverse/biblioteca-doc) and is open source. If you find an error or would like to contribute, please feel
6 | free to make a pull request or open an issue on the project.
--------------------------------------------------------------------------------
/doc/src/content/docs/Troubleshooting/github.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Ask on github
3 | ---
4 | Please feel free to open issues or start discussions on [GitHub](https://github.com/biblioverse/biblioteca) if you have
5 | any questions or need help. We are happy to help you.
6 |
7 | New features and merge requests are also welcome!
--------------------------------------------------------------------------------
/doc/src/content/docs/demo.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Demo
3 | description: Try it yourself
4 | ---
5 | import { LinkButton } from '@astrojs/starlight/components';
6 | import { Aside } from '@astrojs/starlight/components';
7 |
8 |
11 |
12 | You can try Biblioteca right now without installing it.
13 |
14 | You can login with the following accounts:
15 |
16 | - **Admin**:
17 | - Username: `admin`
18 | - **User**:
19 | - Username: `user`
20 |
21 | The password is in both cases the same value as the username.
22 |
23 | Try it now
24 |
25 |
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/adding-users.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Adding users
3 | ---
4 | import { Aside } from '@astrojs/starlight/components';
5 |
6 | ## First user
7 | You should have created your first admin user after installing the app. The first user is always an admin and you will need
8 | admin rights to add other users
9 |
10 | ## Adding new users
11 | If you are logged in with an administrator account, you will have a link to the user management page `/user`.
12 |
13 | Here you can add new users, change their roles, and set or change their passwords.
14 |
15 | You can select the maximum age category of books that the user can read. This is useful if you want to restrict access to children
16 |
17 |
20 |
21 | ## Roles
22 | User can have one of the following roles:
23 | - `ROLE_USER`: The default role for new users. They can read books, change their own settings, create their own shelves and sync their Kobo devices.
24 | - `ROLE_ADMIN`: Can do everything a user can do and manage users and books.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/deleting-books.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Deleting books
3 | ---
4 | import { Aside } from '@astrojs/starlight/components';
5 |
6 |
11 |
12 | On every book page, on the bottom of the page, there is a button to delete the book.
13 | This will remove the book from the database **and the filesystem**.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/edit-book.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Edit book metadata
3 | ---
4 |
5 | There are different ways to edit the metadata of a book in Biblioteca.
6 |
7 | ## On the book page
8 |
9 | On the book page, for each editable field you can click on the pencil icon to make the field editable.
10 |
11 | After you have made the changes, you can click on the checkmark icon to save the changes.
12 |
13 | ## On the "All books" page
14 | On the top right of the list of books, you can change the mode to list mode instead of the gallery view.
15 | In list mode, you can edit the title, author and serie of a book.
16 |
17 | ## Multiple book edition at once
18 | On the top right of the list of books, you can change the mode to list mode instead of the gallery view.
19 | On top of the list of books, you can find a button to edit all displayed books at once for serie, author and tags.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/filesystem.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Filesystem and relocation
3 | ---
4 |
5 | You can manage the file structure of your book by hand and run a command to have them in biblioteca, or you can ask biblioteca
6 | to relocate them to a path format of your choice.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/instance-configuration.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Instance Configuration
3 | ---
4 |
5 | Go to `/configuration` to configure or see the .env.local variables that can be configured.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/Administrator/verified.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Verified books
3 | ---
4 |
5 | The verification status is a flag on the books. When you add a book, it will be set to false.
6 |
7 | If you are an administrator, there is a link in the application menu listing all boks that have not been verified yet.
8 |
9 | This feature allows you to know which books have been added recently and for whom you need to verify and update the metadata.
10 |
11 | When the flag is checked, the edition controls are removed until you uncheck it.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/OPDS.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: OPDS Catalog
3 | ---
4 | import { Steps } from '@astrojs/starlight/components';
5 |
6 |
7 | It is possible to sync your Kobo devices with Biblioteca.
8 |
9 |
10 | 1. Go to the "My Profile" page on the OPDS tab and click on the "Create token" button.
11 | 2. An URL for your opds feed will be generated.
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/homepage.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Your homepage
3 | ---
4 |
5 | On your homepage, you will see the list of books that you have started reading and their progress.
6 |
7 | You can also see the books that you have marked as "In my reading List" and below, some suggestions based on tags.
8 |
9 | All the books series that you have started reading will also appear on your homepage, so you can see where you are in your
10 | progress. Completionists beware!
11 |
12 | When you read books in the app or on your kobo, the progress will be updated in the app.
13 |
14 |
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/interactions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Book interaction
3 | ---
4 |
5 | You can mark any book as "Read", "Don't want to read" or "In my reading List" by using the buttons below the book cover
6 | on the book's page.
7 |
8 | Books that you have marked as "Read" will appear in your reading timeline and bey greyed out in the lists.
9 | You can access your reading timeline by clicking on the "Reading timeline" link in the navigation bar.
10 |
11 | Books that you have marked as "Don't want to read" will be hidden from the lists and will not appear in your reading timeline.
12 |
13 | Books that you have marked as "In my reading List" will appear on your homepage.
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/searching.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Searching for books
3 | ---
4 |
5 | There are several ways to search for books in the library.
6 |
7 | ## Quick search
8 | On top of every page, you can find a search bar. You can search for books by title, author, series, or tags.
9 | The search is case-insensitive and will return all books that match the search query.
10 |
11 | This is mostly useful when you know what you are looking for and want to find it quickly.
12 |
13 |
14 | ## Filtering
15 | In the search bar, you can click on advanced filters, which will allow you to write a typesense filter query.
16 | The documentation for filtering typesense queries can be found here: https://typesense.org/docs/27.1/api/search.html#filter-parameters
17 |
18 | If AI features are enabled, you can also write a natural language query and ask AI to convert it to filters by clicking the magic wand button:
19 |
20 | 
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/shelf.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Shelves
3 | ---
4 |
5 | Shelves are groups of books, they can be static or dynamic.
6 |
7 | You can manually add books to static shelves and that's it.
8 |
9 | You can also create dynamic shelves, which are based on a query. For example, you can create a shelf that contains all
10 | books from your favorite authors that you haven't read and if you or an administrator adds books from these authors,
11 | they will automatically be listed in there.
12 |
13 | ## Create a dynamic Shelf
14 | To do this, head to the "All books" page, and update the filters in the top `Filters` sections. When you are satisfied
15 | with the result, click `Save current filter`, input the name of your shelf and you're good to go!
16 |
17 | ## Create a static shelf
18 | In your side menu click on "Edit Shelves". Here you can edit or delete all your existing shelves. At the bottom of the page,
19 | you can create a new Shelf.
20 | Once you have created a shelf, head on to the books you want to add in that shelves and below the cover, the list of the
21 | shelves will be displayed. Add them by clicking on the shelf's name and remove them by clicking again.
22 |
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/sync-kobo.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Sync Kobo Devices
3 | ---
4 | import { Steps } from '@astrojs/starlight/components';
5 |
6 |
7 | It is possible to sync your Kobo devices with Biblioteca.
8 |
9 |
10 | 1. Create a new [static shelf](/guides/user/shelf) on your profile. And add some books in it
11 | 2. Go to the "My Profile" page on the Kobo Settings tab and click on the "Create new Kobo device" button.
12 | 3. Follow the instructions on the kobo device page in biblioteca.
13 |
14 | You will need to plug your kobo device to your computer and edit a configuration file.
15 | The lines to edit and the values to enter are detailed in the app.
16 |
17 | 4. Once the configuration file is edited, unmount your kobo device and click on the "Sync" button on the kobo device.
18 | 5. Read your books on the Kobo device and sync it again to update your progress on Biblioteca.
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/doc/src/content/docs/guides/User/update-settings.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Updating your settings
3 | ---
4 |
5 | On the sidebar, you will see a panel with your username. Here you can click on the "My Profile link" to edit your profile.
6 |
7 | On this page, you can change your password, the display language and enable or disable some menu categories based on your
8 | preferences.
9 |
10 | You can also change your theme.
11 |
12 | If you are an administrator, you can also configure your [AI settings](../../administrator/ai-features-configuration)
--------------------------------------------------------------------------------
/doc/src/content/docs/installing/2-new-versions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Updating to new versions
3 | ---
4 | import { Steps } from '@astrojs/starlight/components';
5 | import { Aside } from '@astrojs/starlight/components';
6 |
7 |
10 |
11 |
12 | 1. Update the docker image to the latest version:
13 |
14 | ```bash
15 | docker-compose pull
16 | docker-compose up -d
17 | ```
18 |
19 | 2. Run the following command to update the database schema:
20 |
21 | ```bash
22 | docker compose exec biblioteca bin/console doctrine:migration:migrate --no-interaction
23 | ```
24 |
25 | 3. If needed, update the typesense schema:
26 |
27 | ```bash
28 | docker-compose exec biblioteca bin/console biblioverse:typesense:populate
29 | ```
30 |
31 | 4. Clear the cache:
32 |
33 | ```bash
34 | docker-compose exec biblioteca bin/console cache:clear
35 | ```
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/doc/src/content/docs/installing/unraid.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Unraid
3 | ---
4 | You can use and install Biblioteca on your Unraid system by using a docker-compose manager, like
5 | [Dockge](https://github.com/louislam/dockge)
--------------------------------------------------------------------------------
/doc/src/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --sl-content-width: 90%;
3 | --sl-text-5xl: 3.5rem;
4 | }
--------------------------------------------------------------------------------
/doc/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/doc/src/files/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | biblioteca:
3 | image: ghcr.io/biblioverse/biblioteca:main
4 | command: ["/bin/sh", "-c" , "apache2-foreground" ]
5 | ports:
6 | - 8080
7 | depends_on:
8 | - db
9 | stdin_open: true
10 | tty: true
11 | volumes:
12 | - :/var/www/html/public/covers
13 | - :/var/www/html/public/books
14 | - :/var/www/html/public/media
15 | - .env:/var/www/html/.env
16 | db:
17 | image: mariadb:11.7
18 | environment:
19 | - MYSQL_ROOT_PASSWORD=db
20 | - MYSQL_DATABASE=db
21 | - MYSQL_USER=db
22 | - MYSQL_PASSWORD=db
23 | volumes:
24 | - mariadb:/var/lib/mysql
25 |
26 | typesense:
27 | image: typesense/typesense:28.0
28 | restart: on-failure
29 | ports:
30 | - 8983
31 | - 8108
32 | volumes:
33 | - searchdata:/data
34 | command: '--data-dir /data --api-key=xyz --enable-cors'
35 |
36 | volumes:
37 | mariadb:
38 | searchdata:
--------------------------------------------------------------------------------
/doc/src/files/helm-values.yml:
--------------------------------------------------------------------------------
1 | biblioteca:
2 | appSecret:
3 | appSecret: zafmqUbgaMQbx4wCFbZSpwsQ34Dw7wUd
4 |
5 | persistence:
6 | enabled: true
7 |
--------------------------------------------------------------------------------
/doc/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict"
3 | }
4 |
--------------------------------------------------------------------------------
/docker-compose.test.yml:
--------------------------------------------------------------------------------
1 | services:
2 | biblioteca:
3 | build:
4 | target: dev
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | set shell := ["docker", "compose", "run", "--entrypoint", "/bin/sh", "-it", "--rm", "biblioteca", "-c"]
2 | composer *args="":
3 | /usr/local/bin/composer {{args}}
4 |
5 | sh *args="":
6 | sh {{args}}
7 |
8 | php *args="":
9 | php {{args}}
10 |
11 | console *args="":
12 | php bin/console {{args}}
13 |
14 | console-xdebug *args="":
15 | env PHP_IDE_CONFIG="serverName=biblioteca.docker.test" XDEBUG_TRIGGER=1 XDEBUG_MODE=debug php bin/console {{args}}
16 |
17 | install:
18 | composer install
19 |
20 | tests:
21 | composer run test
22 |
23 | update *args="":
24 | composer update {{args}}
25 |
26 | lint:
27 | composer run lint
28 |
29 | rector:
30 | composer rector
31 |
32 | test-phpcs:
33 | composer run test-phpcs
34 |
35 | phpcs:
36 | composer run phpcs
37 |
38 | phpunit *args="":
39 | env XDEBUG_MODE=coverage composer run test-phpunit -- {{args}}
40 |
41 | phpunit-xdebug *args="":
42 | composer test-phpunit-xdebug -- {{args}}
43 |
44 | phpstan *args="":
45 | composer test-phpstan -- {{args}}
46 |
47 | npm *args="":
48 | npm -- {{args}}
49 |
--------------------------------------------------------------------------------
/migrations/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/migrations/.gitignore
--------------------------------------------------------------------------------
/migrations/AbstractMigration.php:
--------------------------------------------------------------------------------
1 | getTable($table);
15 |
16 | return true;
17 | } catch (TableDoesNotExist|SchemaException) {
18 | return false;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/migrations/Version20240121162659.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE book ADD age_category INT DEFAULT NULL');
24 | $this->addSql('ALTER TABLE user ADD birthday DATE DEFAULT NULL, ADD max_age_category INT DEFAULT NULL');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE book DROP age_category');
31 | $this->addSql('ALTER TABLE `user` DROP birthday, DROP max_age_category');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/Version20240505101909.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE user ADD open_aikey VARCHAR(255) DEFAULT NULL, ADD book_summary_prompt LONGTEXT DEFAULT NULL, ADD book_keyword_prompt LONGTEXT DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE `user` DROP open_aikey, DROP book_summary_prompt, DROP book_keyword_prompt');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20240510173645.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE user ADD theme VARCHAR(255) DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE `user` DROP theme');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20240512162606.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE book ADD page_number INT DEFAULT NULL');
24 | $this->addSql('ALTER TABLE book_interaction ADD read_pages INT NOT NULL');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE book_interaction DROP read_pages');
31 | $this->addSql('ALTER TABLE book DROP page_number');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/Version20240513152414.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE book_interaction CHANGE read_pages read_pages INT DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE book_interaction CHANGE read_pages read_pages INT NOT NULL');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20240714144826.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE book_interaction ADD hidden TINYINT(1) NOT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE book_interaction DROP hidden');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20240721080644.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE user ADD last_login DATETIME DEFAULT NULL, ADD language VARCHAR(2) NULL DEFAULT \'en\', ADD use_kobo_devices TINYINT(1) NULL DEFAULT 1');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE `user` DROP last_login, DROP language, DROP use_kobo_devices');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20241121135658.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_device ADD device_id VARCHAR(255) DEFAULT NULL');
21 | $this->addSql('CREATE INDEX kobo_device_id ON kobo_device (device_id);');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->addSql('DROP INDEX kobo_device_id ON kobo_device;');
27 | $this->addSql('ALTER TABLE kobo_device DROP device_id;');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/migrations/Version20241121142239.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_device ADD model VARCHAR(255) DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE kobo_device DROP model');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20241129185216.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_device ADD upstream_sync TINYINT(1) DEFAULT 0 NOT NULL');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->addSql('ALTER TABLE kobo_device DROP upstream_sync');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/migrations/Version20241208200901.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE TABLE opds_access (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, token VARCHAR(255) NOT NULL, INDEX IDX_1F8403F8A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
24 | $this->addSql('ALTER TABLE opds_access ADD CONSTRAINT FK_1F8403F8A76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id)');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE opds_access DROP FOREIGN KEY FK_1F8403F8A76ED395');
31 | $this->addSql('DROP TABLE opds_access');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/Version20241210143123.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_device ADD sync_reading_list TINYINT(1) DEFAULT 1 NOT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE kobo_device DROP sync_reading_list');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20241218154713.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE book_interaction ADD rating INT DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE book_interaction DROP rating');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20241223182304.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE shelf CHANGE query_string query_string LONGTEXT DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE shelf CHANGE query_string query_string JSON DEFAULT NULL COMMENT \'(DC2Type:json)\'');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250101174217.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE shelf ADD query_filter LONGTEXT DEFAULT NULL, ADD query_order VARCHAR(255) DEFAULT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE shelf DROP query_filter, DROP query_order');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250106061005.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE TABLE instance_configuration (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, value LONGTEXT DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('DROP TABLE instance_configuration');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250106090320.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE TABLE ai_model (id INT AUTO_INCREMENT NOT NULL, type VARCHAR(255) NOT NULL, model VARCHAR(255) NOT NULL, token LONGTEXT DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('DROP TABLE ai_model');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250106091638.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE ai_model ADD url VARCHAR(255) NOT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | $this->addSql('ALTER TABLE ai_model DROP url');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/migrations/Version20250106094820.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE ai_model ADD system_prompt LONGTEXT DEFAULT NULL, ADD use_epub_context TINYINT(1) NOT NULL, ADD use_wikipedia_context TINYINT(1) NOT NULL, ADD use_amazon_context TINYINT(1) NOT NULL');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | // this down() migration is auto-generated, please modify it to your needs
29 | $this->addSql('ALTER TABLE ai_model DROP system_prompt, DROP use_epub_context, DROP use_wikipedia_context, DROP use_amazon_context');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/migrations/Version20250128213230.php:
--------------------------------------------------------------------------------
1 | addSql('
23 | DELETE FROM kobo_synced_book
24 | WHERE id NOT IN (
25 | SELECT id FROM (
26 | SELECT MAX(id) AS id
27 | FROM kobo_synced_book
28 | GROUP BY book_id, kobo_device_id
29 | ) AS subquery
30 | )
31 | ');
32 | }
33 |
34 | public function down(Schema $schema): void
35 | {
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/migrations/Version20250128214238.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE UNIQUE INDEX kobo_synced_book_unique ON kobo_synced_book (book_id, kobo_device_id)');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->addSql('DROP INDEX kobo_synced_book_unique ON kobo_synced_book');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/migrations/Version20250130192538.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_synced_book ADD archived DATETIME DEFAULT NULL');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->addSql('ALTER TABLE kobo_synced_book DROP archived');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/migrations/Version20250131184632.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE kobo_device ADD last_sync_token LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:json)\'');
24 | $this->addSql('ALTER TABLE kobo_device CHANGE last_sync_token last_sync_token JSON DEFAULT NULL');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE kobo_device DROP last_sync_token');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/migrations/Version20250202073734.php:
--------------------------------------------------------------------------------
1 | addSql('DELETE FROM book_interaction WHERE book_id is null or user_id is null');
24 | $this->addSql('ALTER TABLE book_interaction CHANGE user_id user_id INT NOT NULL, CHANGE book_id book_id INT NOT NULL');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE book_interaction CHANGE user_id user_id INT DEFAULT NULL, CHANGE book_id book_id INT DEFAULT NULL');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/migrations/Version20250202150749.php:
--------------------------------------------------------------------------------
1 | addSql('CREATE UNIQUE INDEX unique_user_book ON book_interaction (user_id, book_id)');
20 | }
21 |
22 | public function down(Schema $schema): void
23 | {
24 | $this->addSql('DROP INDEX unique_user_book ON book_interaction');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/migrations/Version20250213182540.php:
--------------------------------------------------------------------------------
1 | addSql('ALTER TABLE ai_model DROP use_epub_context, DROP use_wikipedia_context, DROP use_amazon_context');
24 | $this->addSql('ALTER TABLE user DROP book_summary_prompt, DROP book_keyword_prompt');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->addSql('ALTER TABLE ai_model ADD use_epub_context TINYINT(1) DEFAULT 0 NOT NULL, ADD use_wikipedia_context TINYINT(1) DEFAULT 0 NOT NULL, ADD use_amazon_context TINYINT(1) DEFAULT 0 NOT NULL');
31 | $this->addSql('ALTER TABLE `user` ADD book_summary_prompt LONGTEXT DEFAULT NULL, ADD book_keyword_prompt LONGTEXT DEFAULT NULL');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | tipsOfTheDay: false
3 | level: 9
4 | paths:
5 | - src/
6 | - tests/
7 | ignoreErrors:
8 | - identifier: missingType.generics
9 | - '#(.*)no value type specified in iterable type array#'
10 | # - '#Asserted type (.*) for (.*) with type (.*) does not narrow down the type.#'
11 | errorFormat: symplify
12 | typeAliases:
13 | ReadingStateCriteria: "array{'book':int, 'readPages': int|null, 'readStatus': App\\Enum\\ReadStatus}"
14 |
--------------------------------------------------------------------------------
/public/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/public/images/.gitkeep
--------------------------------------------------------------------------------
/public/images/blank.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/public/images/blank.jpg
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
8 |
--------------------------------------------------------------------------------
/public/media/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/public/media/.gitkeep
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | withPaths([
14 | __DIR__ . '/config',
15 | __DIR__ . '/public',
16 | __DIR__ . '/src',
17 | __DIR__ . '/tests',
18 | ])
19 | ->withRules([
20 | AddVoidReturnTypeWhereNoReturnRector::class,
21 | ])
22 | ->withPreparedSets(
23 | deadCode: true,
24 | codeQuality: true
25 | )
26 | ->withSets([
27 | LevelSetList::UP_TO_PHP_83,
28 | SymfonySetList::SYMFONY_64,
29 | SymfonySetList::SYMFONY_72,
30 | SymfonySetList::SYMFONY_CODE_QUALITY,
31 | SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
32 | ])
33 | ->withImportNames(true, true, false, true)
34 | ->withSkip([
35 | '**/config/bundles.php',
36 | InlineClassRoutePrefixRector::class
37 | ]);
38 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended",
5 | "schedule:weekends",
6 | ":dependencyDashboard",
7 | ":automergeMinor",
8 | "group:allNonMajor"
9 | ],
10 | "major": {
11 | "dependencyDashboardApproval": true
12 | },
13 | "lockFileMaintenance": {
14 | "enabled": true
15 | },
16 | "packageRules": [
17 | {
18 | "matchManagers": ["composer"],
19 | "rangeStrategy": "update-lockfile"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/Ai/Communicator/AbstractCommunicator.php:
--------------------------------------------------------------------------------
1 | aiModel = $model;
15 | }
16 |
17 | #[\Override]
18 | public function getAiModel(): AiModel
19 | {
20 | return $this->aiModel;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Ai/Communicator/AiAction.php:
--------------------------------------------------------------------------------
1 | 20])]
9 | interface AiChatInterface extends AiCommunicatorInterface
10 | {
11 | /**
12 | * @param Message[] $messages
13 | */
14 | public function chat(array $messages): string;
15 | }
16 |
--------------------------------------------------------------------------------
/src/Ai/Communicator/AiCommunicatorInterface.php:
--------------------------------------------------------------------------------
1 | 20])]
9 | interface AiCommunicatorInterface
10 | {
11 | public function initialise(AiModel $model): void;
12 |
13 | public function getAiModel(): AiModel;
14 |
15 | public function interrogate(string $prompt): string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Ai/Context/ContextBuildingInterface.php:
--------------------------------------------------------------------------------
1 | 20])]
11 | interface ContextBuildingInterface
12 | {
13 | public function isEnabled(AiModel $aiModel, ?Book $book = null): bool;
14 |
15 | public function getContextForPrompt(BookPromptInterface $prompt): string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Ai/Message.php:
--------------------------------------------------------------------------------
1 | date = new \DateTimeImmutable();
15 | }
16 |
17 | public function toOpenAI(): array
18 | {
19 | return [
20 | 'role' => $this->role->value,
21 | 'content' => $this->text,
22 | ];
23 | }
24 |
25 | public function getText(): string
26 | {
27 | return $this->text;
28 | }
29 |
30 | public function toPerplexica(): array
31 | {
32 | $roleValue = match ($this->role) {
33 | AiMessageRole::System => 'human',
34 | AiMessageRole::User => 'human',
35 | AiMessageRole::Assistant => 'assistant',
36 | };
37 |
38 | return [$roleValue, $this->text];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Ai/Prompt/AbstractBookPrompt.php:
--------------------------------------------------------------------------------
1 | prompt;
23 | }
24 |
25 | #[\Override]
26 | public function setPrompt(string $prompt): void
27 | {
28 | $this->prompt = $prompt;
29 | }
30 |
31 | #[\Override]
32 | public function getBook(): Book
33 | {
34 | return $this->book;
35 | }
36 |
37 | #[\Override]
38 | public function replaceBookOccurrence(string $prompt): string
39 | {
40 | $bookString = $this->book->getPromptString();
41 |
42 | $language = $this->book->getLanguage() ?? $this->language;
43 |
44 | return str_replace(['{book}', '{language}'], [$bookString, $this->getFullLanguageName($language)], $prompt);
45 | }
46 |
47 | private function getFullLanguageName(string $language): string
48 | {
49 | if (strlen($language) !== 2 || !class_exists('\Locale')) {
50 | return $language;
51 | }
52 |
53 | return \Locale::getDisplayLanguage($language);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Ai/Prompt/BookPromptInterface.php:
--------------------------------------------------------------------------------
1 | security->getUser();
21 | if ($language === null) {
22 | $language = $book->getLanguage();
23 | }
24 | if ($language === null && $user instanceof User) {
25 | $language = $user->getLanguage();
26 | }
27 | if ($language === null) {
28 | $language = 'en';
29 | }
30 |
31 | /** @var BookPromptInterface $object */
32 | $object = new $class($book, $this->config, $language);
33 | $object->initialisePrompt();
34 |
35 | return $object;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Controller/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biblioverse/biblioteca/ffd39a0a278e30ed1b918dffe805dce64b24c06b/src/Controller/.gitignore
--------------------------------------------------------------------------------
/src/Controller/Kobo/AbstractKoboController.php:
--------------------------------------------------------------------------------
1 | Hello Kobo