diff --git a/DEVELOPING.md b/DEVELOPING.md index 02172a2c6..c8a1eab46 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -27,10 +27,10 @@ Komga is composed of 2 projects: Komga uses Spring Profiles extensively: - `dev`: add more logging, disable periodic scanning, in-memory database -- `localdb`: a dev profile that stores the database in `./testdb`. +- `localdb`: a dev profile that stores the database in `./localdb`. - `noclaim`: will create initial users at startup if none exist and output users and passwords in the standard output - if `dev` is active, will create `admin@example.org` with password `admin`, and `user@example.org` with password `user` - - if `dev` is not active, will create `admin@example.org` with a random password + - if `dev` is not active, will create `admin@example.org` with a random password that will be shown in the logs ### Gradle tasks diff --git a/komga-webui/src/components/SearchBox.vue b/komga-webui/src/components/SearchBox.vue index 52c9489c7..bd17fa7b5 100644 --- a/komga-webui/src/components/SearchBox.vue +++ b/komga-webui/src/components/SearchBox.vue @@ -133,13 +133,13 @@ export default Vue.extend({ this.$router.push({ name: 'search', query: { q: s } }).catch(e => { }) }, - seriesThumbnailUrl (seriesId: number): string { + seriesThumbnailUrl (seriesId: string): string { return seriesThumbnailUrl(seriesId) }, - bookThumbnailUrl (bookId: number): string { + bookThumbnailUrl (bookId: string): string { return bookThumbnailUrl(bookId) }, - collectionThumbnailUrl (collectionId: number): string { + collectionThumbnailUrl (collectionId: string): string { return collectionThumbnailUrl(collectionId) }, }, diff --git a/komga-webui/src/components/dialogs/CollectionAddToDialog.vue b/komga-webui/src/components/dialogs/CollectionAddToDialog.vue index f6b112a54..21a898bea 100644 --- a/komga-webui/src/components/dialogs/CollectionAddToDialog.vue +++ b/komga-webui/src/components/dialogs/CollectionAddToDialog.vue @@ -117,7 +117,7 @@ export default Vue.extend({ }, computed: { - seriesIds (): number[] { + seriesIds (): string[] { if (Array.isArray(this.series)) return this.series.map(s => s.id) else return [this.series.id] }, diff --git a/komga-webui/src/components/dialogs/ThumbnailExplorerDialog.vue b/komga-webui/src/components/dialogs/ThumbnailExplorerDialog.vue index ed278c93e..5b2a10daa 100644 --- a/komga-webui/src/components/dialogs/ThumbnailExplorerDialog.vue +++ b/komga-webui/src/components/dialogs/ThumbnailExplorerDialog.vue @@ -54,7 +54,7 @@ export default Vue.extend({ type: Boolean, }, bookId: { - type: Number, + type: String, }, }, data: () => { diff --git a/komga-webui/src/components/dialogs/UserSharedLibrariesEditDialog.vue b/komga-webui/src/components/dialogs/UserSharedLibrariesEditDialog.vue index 401fe221d..f2594d23f 100644 --- a/komga-webui/src/components/dialogs/UserSharedLibrariesEditDialog.vue +++ b/komga-webui/src/components/dialogs/UserSharedLibrariesEditDialog.vue @@ -78,7 +78,7 @@ export default Vue.extend({ snackText: '', modal: false, allLibraries: true, - selectedLibraries: [] as number[], + selectedLibraries: [] as string[], } }, props: { diff --git a/komga-webui/src/functions/urls.ts b/komga-webui/src/functions/urls.ts index c95f352fa..840dc7d8e 100644 --- a/komga-webui/src/functions/urls.ts +++ b/komga-webui/src/functions/urls.ts @@ -12,15 +12,15 @@ const urls = { export default urls -export function bookThumbnailUrl (bookId: number): string { +export function bookThumbnailUrl (bookId: string): string { return `${urls.originNoSlash}/api/v1/books/${bookId}/thumbnail` } -export function bookFileUrl (bookId: number): string { +export function bookFileUrl (bookId: string): string { return `${urls.originNoSlash}/api/v1/books/${bookId}/file` } -export function bookPageUrl (bookId: number, page: number, convertTo?: string): string { +export function bookPageUrl (bookId: string, page: number, convertTo?: string): string { let url = `${urls.originNoSlash}/api/v1/books/${bookId}/pages/${page}` if (convertTo) { url += `?convert=${convertTo}` @@ -28,14 +28,14 @@ export function bookPageUrl (bookId: number, page: number, convertTo?: string): return url } -export function bookPageThumbnailUrl (bookId: number, page: number): string { +export function bookPageThumbnailUrl (bookId: string, page: number): string { return `${urls.originNoSlash}/api/v1/books/${bookId}/pages/${page}/thumbnail` } -export function seriesThumbnailUrl (seriesId: number): string { +export function seriesThumbnailUrl (seriesId: string): string { return `${urls.originNoSlash}/api/v1/series/${seriesId}/thumbnail` } -export function collectionThumbnailUrl (collectionId: number): string { +export function collectionThumbnailUrl (collectionId: string): string { return `${urls.originNoSlash}/api/v1/collections/${collectionId}/thumbnail` } diff --git a/komga-webui/src/plugins/komga-users.plugin.ts b/komga-webui/src/plugins/komga-users.plugin.ts index de0db809c..212222935 100644 --- a/komga-webui/src/plugins/komga-users.plugin.ts +++ b/komga-webui/src/plugins/komga-users.plugin.ts @@ -49,7 +49,7 @@ const vuexModule: Module = { await service.postUser(user) dispatch('getAllUsers') }, - async updateUserRoles ({ dispatch }, { userId, roles }: { userId: number, roles: RolesUpdateDto }) { + async updateUserRoles ({ dispatch }, { userId, roles }: { userId: string, roles: RolesUpdateDto }) { await service.patchUserRoles(userId, roles) dispatch('getAllUsers') }, diff --git a/komga-webui/src/router.ts b/komga-webui/src/router.ts index 8abbf56e5..aa79ba6b9 100644 --- a/komga-webui/src/router.ts +++ b/komga-webui/src/router.ts @@ -76,32 +76,32 @@ const router = new Router({ name: 'browse-libraries', beforeEnter: noLibraryGuard, component: () => import(/* webpackChunkName: "browse-libraries" */ './views/BrowseLibraries.vue'), - props: (route) => ({ libraryId: Number(route.params.libraryId) }), + props: (route) => ({ libraryId: route.params.libraryId }), }, { path: '/libraries/:libraryId/collections', name: 'browse-collections', beforeEnter: noLibraryGuard, component: () => import(/* webpackChunkName: "browse-collections" */ './views/BrowseCollections.vue'), - props: (route) => ({ libraryId: Number(route.params.libraryId) }), + props: (route) => ({ libraryId: route.params.libraryId }), }, { path: '/collections/:collectionId', name: 'browse-collection', component: () => import(/* webpackChunkName: "browse-collection" */ './views/BrowseCollection.vue'), - props: (route) => ({ collectionId: Number(route.params.collectionId) }), + props: (route) => ({ collectionId: route.params.collectionId }), }, { path: '/series/:seriesId', name: 'browse-series', component: () => import(/* webpackChunkName: "browse-series" */ './views/BrowseSeries.vue'), - props: (route) => ({ seriesId: Number(route.params.seriesId) }), + props: (route) => ({ seriesId: route.params.seriesId }), }, { path: '/book/:bookId', name: 'browse-book', component: () => import(/* webpackChunkName: "browse-book" */ './views/BrowseBook.vue'), - props: (route) => ({ bookId: Number(route.params.bookId) }), + props: (route) => ({ bookId: route.params.bookId }), }, { path: '/search', @@ -124,7 +124,7 @@ const router = new Router({ path: '/book/:bookId/read', name: 'read-book', component: () => import(/* webpackChunkName: "read-book" */ './views/BookReader.vue'), - props: (route) => ({ bookId: Number(route.params.bookId) }), + props: (route) => ({ bookId: route.params.bookId }), }, { path: '*', diff --git a/komga-webui/src/services/komga-books.service.ts b/komga-webui/src/services/komga-books.service.ts index aa8920fd9..2ccc70890 100644 --- a/komga-webui/src/services/komga-books.service.ts +++ b/komga-webui/src/services/komga-books.service.ts @@ -11,7 +11,7 @@ export default class KomgaBooksService { this.http = http } - async getBooks (libraryId?: number, pageRequest?: PageRequest, search?: string, mediaStatus?: string[], readStatus?: string[]): Promise> { + async getBooks (libraryId?: string, pageRequest?: PageRequest, search?: string, mediaStatus?: string[], readStatus?: string[]): Promise> { try { const params = { ...pageRequest } as any if (libraryId) { @@ -53,7 +53,7 @@ export default class KomgaBooksService { } } - async getBook (bookId: number): Promise { + async getBook (bookId: string): Promise { try { return (await this.http.get(`${API_BOOKS}/${bookId}`)).data } catch (e) { @@ -65,7 +65,7 @@ export default class KomgaBooksService { } } - async getBookSiblingNext (bookId: number): Promise { + async getBookSiblingNext (bookId: string): Promise { try { return (await this.http.get(`${API_BOOKS}/${bookId}/next`)).data } catch (e) { @@ -77,7 +77,7 @@ export default class KomgaBooksService { } } - async getBookSiblingPrevious (bookId: number): Promise { + async getBookSiblingPrevious (bookId: string): Promise { try { return (await this.http.get(`${API_BOOKS}/${bookId}/previous`)).data } catch (e) { @@ -89,7 +89,7 @@ export default class KomgaBooksService { } } - async getBookPages (bookId: number): Promise { + async getBookPages (bookId: string): Promise { try { return (await this.http.get(`${API_BOOKS}/${bookId}/pages`)).data } catch (e) { @@ -125,7 +125,7 @@ export default class KomgaBooksService { } } - async updateMetadata (bookId: number, metadata: BookMetadataUpdateDto) { + async updateMetadata (bookId: string, metadata: BookMetadataUpdateDto) { try { await this.http.patch(`${API_BOOKS}/${bookId}/metadata`, metadata) } catch (e) { @@ -137,7 +137,7 @@ export default class KomgaBooksService { } } - async updateReadProgress (bookId: number, readProgress: ReadProgressUpdateDto) { + async updateReadProgress (bookId: string, readProgress: ReadProgressUpdateDto) { try { await this.http.patch(`${API_BOOKS}/${bookId}/read-progress`, readProgress) } catch (e) { @@ -149,7 +149,7 @@ export default class KomgaBooksService { } } - async deleteReadProgress (bookId: number) { + async deleteReadProgress (bookId: string) { try { await this.http.delete(`${API_BOOKS}/${bookId}/read-progress`) } catch (e) { diff --git a/komga-webui/src/services/komga-collections.service.ts b/komga-webui/src/services/komga-collections.service.ts index f5e07a536..baba576a3 100644 --- a/komga-webui/src/services/komga-collections.service.ts +++ b/komga-webui/src/services/komga-collections.service.ts @@ -11,7 +11,7 @@ export default class KomgaCollectionsService { this.http = http } - async getCollections (libraryIds?: number[], pageRequest?: PageRequest, search?: string): Promise> { + async getCollections (libraryIds?: string[], pageRequest?: PageRequest, search?: string): Promise> { try { const params = { ...pageRequest } as any if (libraryIds) params.library_id = libraryIds @@ -30,7 +30,7 @@ export default class KomgaCollectionsService { } } - async getOneCollection (collectionId: number): Promise { + async getOneCollection (collectionId: string): Promise { try { return (await this.http.get(`${API_COLLECTIONS}/${collectionId}`)).data } catch (e) { @@ -54,7 +54,7 @@ export default class KomgaCollectionsService { } } - async patchCollection (collectionId: number, collection: CollectionUpdateDto) { + async patchCollection (collectionId: string, collection: CollectionUpdateDto) { try { await this.http.patch(`${API_COLLECTIONS}/${collectionId}`, collection) } catch (e) { @@ -66,7 +66,7 @@ export default class KomgaCollectionsService { } } - async deleteCollection (collectionId: number) { + async deleteCollection (collectionId: string) { try { await this.http.delete(`${API_COLLECTIONS}/${collectionId}`) } catch (e) { @@ -78,7 +78,7 @@ export default class KomgaCollectionsService { } } - async getSeries (collectionId: number, pageRequest?: PageRequest): Promise> { + async getSeries (collectionId: string, pageRequest?: PageRequest): Promise> { try { const params = { ...pageRequest } return (await this.http.get(`${API_COLLECTIONS}/${collectionId}/series`, { diff --git a/komga-webui/src/services/komga-libraries.service.ts b/komga-webui/src/services/komga-libraries.service.ts index eec2d03af..25d965e3a 100644 --- a/komga-webui/src/services/komga-libraries.service.ts +++ b/komga-webui/src/services/komga-libraries.service.ts @@ -21,7 +21,7 @@ export default class KomgaLibrariesService { } } - async getLibrary (libraryId: number): Promise { + async getLibrary (libraryId: string): Promise { try { return (await this.http.get(`${API_LIBRARIES}/${libraryId}`)).data } catch (e) { @@ -45,7 +45,7 @@ export default class KomgaLibrariesService { } } - async updateLibrary (libraryId: number, library: LibraryUpdateDto) { + async updateLibrary (libraryId: string, library: LibraryUpdateDto) { try { await this.http.put(`${API_LIBRARIES}/${libraryId}`, library) } catch (e) { diff --git a/komga-webui/src/services/komga-series.service.ts b/komga-webui/src/services/komga-series.service.ts index b3cb694a7..b6e0a30fd 100644 --- a/komga-webui/src/services/komga-series.service.ts +++ b/komga-webui/src/services/komga-series.service.ts @@ -11,7 +11,7 @@ export default class KomgaSeriesService { this.http = http } - async getSeries (libraryId?: number, pageRequest?: PageRequest, search?: string, status?: string[], readStatus?: string[]): Promise> { + async getSeries (libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[], readStatus?: string[]): Promise> { try { const params = { ...pageRequest } as any if (libraryId) { @@ -69,7 +69,7 @@ export default class KomgaSeriesService { } } - async getOneSeries (seriesId: number): Promise { + async getOneSeries (seriesId: string): Promise { try { return (await this.http.get(`${API_SERIES}/${seriesId}`)).data } catch (e) { @@ -81,7 +81,7 @@ export default class KomgaSeriesService { } } - async getBooks (seriesId: number, pageRequest?: PageRequest, readStatus?: string[]): Promise> { + async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[]): Promise> { try { const params = { ...pageRequest } as any if (readStatus) { @@ -100,7 +100,7 @@ export default class KomgaSeriesService { } } - async getCollections (seriesId: number): Promise { + async getCollections (seriesId: string): Promise { try { return (await this.http.get(`${API_SERIES}/${seriesId}/collections`)).data } catch (e) { @@ -136,7 +136,7 @@ export default class KomgaSeriesService { } } - async updateMetadata (seriesId: number, metadata: SeriesMetadataUpdateDto) { + async updateMetadata (seriesId: string, metadata: SeriesMetadataUpdateDto) { try { await this.http.patch(`${API_SERIES}/${seriesId}/metadata`, metadata) } catch (e) { @@ -148,7 +148,7 @@ export default class KomgaSeriesService { } } - async markAsRead (seriesId: number) { + async markAsRead (seriesId: string) { try { await this.http.post(`${API_SERIES}/${seriesId}/read-progress`) } catch (e) { @@ -160,7 +160,7 @@ export default class KomgaSeriesService { } } - async markAsUnread (seriesId: number) { + async markAsUnread (seriesId: string) { try { await this.http.delete(`${API_SERIES}/${seriesId}/read-progress`) } catch (e) { diff --git a/komga-webui/src/services/komga-users.service.ts b/komga-webui/src/services/komga-users.service.ts index 7d7756e82..a76dac55e 100644 --- a/komga-webui/src/services/komga-users.service.ts +++ b/komga-webui/src/services/komga-users.service.ts @@ -70,7 +70,7 @@ export default class KomgaUsersService { } } - async patchUserRoles (userId: number, roles: RolesUpdateDto): Promise { + async patchUserRoles (userId: string, roles: RolesUpdateDto): Promise { try { return (await this.http.patch(`${API_USERS}/${userId}`, roles)).data } catch (e) { diff --git a/komga-webui/src/types/events-payloads.ts b/komga-webui/src/types/events-payloads.ts index b32131359..72f937e07 100644 --- a/komga-webui/src/types/events-payloads.ts +++ b/komga-webui/src/types/events-payloads.ts @@ -1,29 +1,29 @@ interface EventBookChanged { - id: number, - seriesId: number + id: string, + seriesId: string } interface EventSeriesChanged { - id: number, - libraryId: number + id: string, + libraryId: string } interface EventCollectionChanged { - id: number + id: string } interface EventCollectionDeleted { - id: number + id: string } interface EventLibraryAdded { - id: number + id: string } interface EventLibraryChanged { - id: number + id: string } interface EventLibraryDeleted { - id: number + id: string } diff --git a/komga-webui/src/types/komga-books.ts b/komga-webui/src/types/komga-books.ts index 0d44711b3..5cc200dd8 100644 --- a/komga-webui/src/types/komga-books.ts +++ b/komga-webui/src/types/komga-books.ts @@ -1,7 +1,7 @@ interface BookDto { - id: number, - seriesId: number, - libraryId: number, + id: string, + seriesId: string, + libraryId: string, name: string, url: string, number: number, diff --git a/komga-webui/src/types/komga-collections.ts b/komga-webui/src/types/komga-collections.ts index c388d1082..0e47826fc 100644 --- a/komga-webui/src/types/komga-collections.ts +++ b/komga-webui/src/types/komga-collections.ts @@ -1,9 +1,9 @@ interface CollectionDto { - id: number, + id: string, name: string, ordered: boolean, filtered: boolean, - seriesIds: number[], + seriesIds: string[], createdDate: string, lastModifiedDate: string } @@ -11,11 +11,11 @@ interface CollectionDto { interface CollectionCreationDto { name: string, ordered: boolean, - seriesIds: number[] + seriesIds: string[] } interface CollectionUpdateDto { name?: string, ordered?: boolean, - seriesIds?: number[] + seriesIds?: string[] } diff --git a/komga-webui/src/types/komga-libraries.ts b/komga-webui/src/types/komga-libraries.ts index 6acd3e48d..9035c387e 100644 --- a/komga-webui/src/types/komga-libraries.ts +++ b/komga-webui/src/types/komga-libraries.ts @@ -19,7 +19,7 @@ interface LibraryUpdateDto { } interface LibraryDto { - id: number, + id: string, name: string, root: string, importComicInfoBook: boolean, diff --git a/komga-webui/src/types/komga-series.ts b/komga-webui/src/types/komga-series.ts index 78fc02f47..3b8ed108f 100644 --- a/komga-webui/src/types/komga-series.ts +++ b/komga-webui/src/types/komga-series.ts @@ -1,6 +1,6 @@ interface SeriesDto { - id: number, - libraryId: number, + id: string, + libraryId: string, name: string, url: string, lastModified: string, diff --git a/komga-webui/src/types/komga-users.ts b/komga-webui/src/types/komga-users.ts index 7e50e2bfd..48e842988 100644 --- a/komga-webui/src/types/komga-users.ts +++ b/komga-webui/src/types/komga-users.ts @@ -1,11 +1,11 @@ interface UserDto { - id: number, + id: string, email: string, roles: string[] } interface UserWithSharedLibrariesDto { - id: number, + id: string, email: string, roles: string[], sharedAllLibraries: boolean, @@ -13,7 +13,7 @@ interface UserWithSharedLibrariesDto { } interface SharedLibraryDto { - id: number + id: string } interface UserCreationDto { @@ -27,7 +27,7 @@ interface PasswordUpdateDto { interface SharedLibrariesUpdateDto { all: boolean, - libraryIds: number[] + libraryIds: string[] } interface RolesUpdateDto { diff --git a/komga-webui/src/views/BookReader.vue b/komga-webui/src/views/BookReader.vue index 93d21ac9e..76950cb56 100644 --- a/komga-webui/src/views/BookReader.vue +++ b/komga-webui/src/views/BookReader.vue @@ -384,14 +384,14 @@ export default Vue.extend({ }, props: { bookId: { - type: Number, + type: String, required: true, }, }, async beforeRouteUpdate (to, from, next) { if (to.params.bookId !== from.params.bookId) { // route update means going to previous/next book, in this case we start from first page - this.setup(Number(to.params.bookId), 1) + this.setup(to.params.bookId, 1) } next() }, @@ -517,7 +517,7 @@ export default Vue.extend({ keyPressed (e: KeyboardEvent) { executeShortcut(this, e) }, - async setup (bookId: number, page: number) { + async setup (bookId: string, page: number) { this.book = await this.$komgaBooks.getBook(bookId) this.pages = await this.$komgaBooks.getBookPages(bookId) if (page >= 1 && page <= this.pagesCount) { diff --git a/komga-webui/src/views/BrowseBook.vue b/komga-webui/src/views/BrowseBook.vue index fbf7c74ca..0672a0792 100644 --- a/komga-webui/src/views/BrowseBook.vue +++ b/komga-webui/src/views/BrowseBook.vue @@ -184,13 +184,13 @@ export default Vue.extend({ }, props: { bookId: { - type: Number, + type: String, required: true, }, }, async beforeRouteUpdate (to, from, next) { if (to.params.bookId !== from.params.bookId) { - this.loadBook(Number(to.params.bookId)) + this.loadBook(to.params.bookId) } next() @@ -239,7 +239,7 @@ export default Vue.extend({ reloadBook (event: EventBookChanged) { if (event.id === this.bookId) this.loadBook(this.bookId) }, - async loadBook (bookId: number) { + async loadBook (bookId: string) { this.book = await this.$komgaBooks.getBook(bookId) }, analyze () { diff --git a/komga-webui/src/views/BrowseCollection.vue b/komga-webui/src/views/BrowseCollection.vue index 914aef6c2..84ddb62fe 100644 --- a/komga-webui/src/views/BrowseCollection.vue +++ b/komga-webui/src/views/BrowseCollection.vue @@ -103,7 +103,7 @@ export default Vue.extend({ }, props: { collectionId: { - type: Number, + type: String, required: true, }, }, @@ -140,7 +140,7 @@ export default Vue.extend({ this.series = [] this.editElements = false - this.loadCollection(Number(to.params.collectionId)) + this.loadCollection(to.params.collectionId) } next() @@ -156,7 +156,7 @@ export default Vue.extend({ this.loadCollection(this.collectionId) } }, - async loadCollection (collectionId: number) { + async loadCollection (collectionId: string) { this.collection = await this.$komgaCollections.getOneCollection(collectionId) this.series = (await this.$komgaCollections.getSeries(collectionId, { unpaged: true } as PageRequest)).content this.seriesCopy = [...this.series] diff --git a/komga-webui/src/views/BrowseCollections.vue b/komga-webui/src/views/BrowseCollections.vue index f76c8ef28..e782ca01c 100644 --- a/komga-webui/src/views/BrowseCollections.vue +++ b/komga-webui/src/views/BrowseCollections.vue @@ -47,6 +47,7 @@ import { COLLECTION_CHANGED, LIBRARY_CHANGED } from '@/types/events' import Vue from 'vue' const cookiePageSize = 'pagesize' +const all = 'all' export default Vue.extend({ name: 'BrowseCollections', @@ -72,8 +73,8 @@ export default Vue.extend({ }, props: { libraryId: { - type: Number, - default: 0, + type: String, + default: all, }, }, created () { @@ -105,7 +106,7 @@ export default Vue.extend({ this.totalElements = null this.collections = [] - this.loadLibrary(Number(to.params.libraryId)) + this.loadLibrary(to.params.libraryId) } next() @@ -173,7 +174,7 @@ export default Vue.extend({ this.loadLibrary(this.libraryId) } }, - async loadLibrary (libraryId: number) { + async loadLibrary (libraryId: string) { this.library = this.getLibraryLazy(libraryId) await this.loadPage(libraryId, this.page) @@ -181,21 +182,21 @@ export default Vue.extend({ await this.$router.push({ name: 'browse-libraries', params: { libraryId: libraryId.toString() } }) } }, - async loadPage (libraryId: number, page: number) { + async loadPage (libraryId: string, page: number) { const pageRequest = { page: page - 1, size: this.pageSize, } as PageRequest - const lib = libraryId !== 0 ? [libraryId] : undefined + const lib = libraryId !== all ? [libraryId] : undefined const collectionsPage = await this.$komgaCollections.getCollections(lib, pageRequest) this.totalPages = collectionsPage.totalPages this.totalElements = collectionsPage.totalElements this.collections = collectionsPage.content }, - getLibraryLazy (libraryId: any): LibraryDto | undefined { - if (libraryId !== 0) { + getLibraryLazy (libraryId: string): LibraryDto | undefined { + if (libraryId !== all) { return this.$store.getters.getLibraryById(libraryId) } else { return undefined diff --git a/komga-webui/src/views/BrowseLibraries.vue b/komga-webui/src/views/BrowseLibraries.vue index 452e1f8db..98d9989d9 100644 --- a/komga-webui/src/views/BrowseLibraries.vue +++ b/komga-webui/src/views/BrowseLibraries.vue @@ -86,6 +86,7 @@ import { COLLECTION_CHANGED, LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } import Vue from 'vue' const cookiePageSize = 'pagesize' +const all = 'all' export default Vue.extend({ name: 'BrowseLibraries', @@ -136,8 +137,8 @@ export default Vue.extend({ }, props: { libraryId: { - type: Number, - default: 0, + type: String, + default: all, }, }, watch: { @@ -188,7 +189,7 @@ export default Vue.extend({ this.series = [] this.collectionsCount = 0 - this.loadLibrary(Number(to.params.libraryId)) + this.loadLibrary(to.params.libraryId) this.setWatches() } @@ -214,10 +215,10 @@ export default Vue.extend({ }, }, methods: { - cookieSort (libraryId: number): string { + cookieSort (libraryId: string): string { return `library.sort.${libraryId}` }, - cookieFilter (libraryId: number): string { + cookieFilter (libraryId: string): string { return `library.filter.${libraryId}` }, resetParams (route: any) { @@ -236,7 +237,7 @@ export default Vue.extend({ libraryDeleted (event: EventLibraryDeleted) { if (event.id === this.libraryId) { this.$router.push({ name: 'home' }) - } else if (this.libraryId === 0) { + } else if (this.libraryId === all) { this.loadLibrary(this.libraryId) } }, @@ -280,19 +281,19 @@ export default Vue.extend({ this.loadLibrary(this.libraryId) }, reloadSeries (event: EventSeriesChanged) { - if (this.libraryId === 0 || event.libraryId === this.libraryId) { + if (this.libraryId === all || event.libraryId === this.libraryId) { this.loadPage(this.libraryId, this.page, this.sortActive) } }, reloadLibrary (event: EventLibraryChanged) { - if (this.libraryId === 0 || event.id === this.libraryId) { + if (this.libraryId === all || event.id === this.libraryId) { this.loadLibrary(this.libraryId) } }, - async loadLibrary (libraryId: number) { + async loadLibrary (libraryId: string) { this.library = this.getLibraryLazy(libraryId) - const lib = libraryId !== 0 ? [libraryId] : undefined + const lib = libraryId !== all ? [libraryId] : undefined this.collectionsCount = (await this.$komgaCollections.getCollections(lib, { size: 1 })).totalElements await this.loadPage(libraryId, this.page, this.sortActive) @@ -311,7 +312,7 @@ export default Vue.extend({ }).catch(_ => { }) }, - async loadPage (libraryId: number, page: number, sort: SortActive) { + async loadPage (libraryId: string, page: number, sort: SortActive) { const pageRequest = { page: page - 1, size: this.pageSize, @@ -321,7 +322,7 @@ export default Vue.extend({ pageRequest.sort = [`${sort.key},${sort.order}`] } - const requestLibraryId = libraryId !== 0 ? libraryId : undefined + const requestLibraryId = libraryId !== all ? libraryId : undefined const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, this.filters.readStatus) this.totalPages = seriesPage.totalPages diff --git a/komga-webui/src/views/BrowseSeries.vue b/komga-webui/src/views/BrowseSeries.vue index bd6e1e85d..584f651db 100644 --- a/komga-webui/src/views/BrowseSeries.vue +++ b/komga-webui/src/views/BrowseSeries.vue @@ -204,7 +204,7 @@ export default Vue.extend({ }, props: { seriesId: { - type: Number, + type: String, required: true, }, }, @@ -253,7 +253,7 @@ export default Vue.extend({ this.books = [] this.collections = [] - this.loadSeries(Number(to.params.seriesId)) + this.loadSeries(to.params.seriesId) this.setWatches() } @@ -302,7 +302,7 @@ export default Vue.extend({ reloadBooks (event: EventBookChanged) { if (event.seriesId === this.seriesId) this.loadSeries(this.seriesId) }, - async loadSeries (seriesId: number) { + async loadSeries (seriesId: string) { this.series = await this.$komgaSeries.getOneSeries(seriesId) this.collections = await this.$komgaSeries.getCollections(seriesId) await this.loadPage(seriesId, this.page, this.sortActive) @@ -326,7 +326,7 @@ export default Vue.extend({ }).catch(_ => { }) }, - async loadPage (seriesId: number, page: number, sort: SortActive) { + async loadPage (seriesId: string, page: number, sort: SortActive) { const pageRequest = { page: page - 1, size: this.pageSize, diff --git a/komga-webui/src/views/Home.vue b/komga-webui/src/views/Home.vue index 67100f486..9e4513212 100644 --- a/komga-webui/src/views/Home.vue +++ b/komga-webui/src/views/Home.vue @@ -34,7 +34,7 @@ - + mdi-book-multiple diff --git a/komga-webui/src/views/Login.vue b/komga-webui/src/views/Login.vue index 67a75c446..f3626eb9a 100644 --- a/komga-webui/src/views/Login.vue +++ b/komga-webui/src/views/Login.vue @@ -130,7 +130,7 @@ export default Vue.extend({ await this.$router.push({ name: 'home' }) } } catch (e) { - this.showSnack(e.message) + this.showSnack(e?.message) } }, showSnack (message: string) { diff --git a/komga/Dockerfile b/komga/Dockerfile index a52a58793..14af02275 100644 --- a/komga/Dockerfile +++ b/komga/Dockerfile @@ -5,6 +5,7 @@ COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY ${DEPENDENCY}/META-INF /app/META-INF COPY ${DEPENDENCY}/BOOT-INF/classes /app ENV KOMGA_DATABASE_BACKUP_PATH="/config/database-backup.zip" +ENV KOMGA_DATABASE_FILE="/config/database.sqlite" ENV SPRING_DATASOURCE_URL="jdbc:h2:/config/database.h2" ENV SPRING_ARTEMIS_EMBEDDED_DATA_DIRECTORY="/config/artemis" ENV LOGGING_FILE_NAME="/config/logs/komga.log" diff --git a/komga/build.gradle.kts b/komga/build.gradle.kts index 675d59df9..7e305296d 100644 --- a/komga/build.gradle.kts +++ b/komga/build.gradle.kts @@ -92,8 +92,11 @@ dependencies { implementation("com.jakewharton.byteunits:byteunits:0.9.1") + implementation("com.github.f4b6a3:tsid-creator:2.2.4") + runtimeOnly("com.h2database:h2:1.4.200") - jooqGeneratorRuntime("com.h2database:h2:1.4.200") + runtimeOnly("org.xerial:sqlite-jdbc:3.32.3") + jooqGeneratorRuntime("org.xerial:sqlite-jdbc:3.32.3") testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(module = "mockito-core") @@ -197,42 +200,38 @@ sourceSets { } } -val jooqDb = mapOf( - "url" to "jdbc:h2:${project.buildDir}/generated/flyway/h2", - "schema" to "PUBLIC", - "user" to "sa", - "password" to "" + +val dbSqlite = mapOf( + "url" to "jdbc:sqlite:${project.buildDir}/generated/flyway/database.sqlite" ) -val migrationDirs = listOf( - "$projectDir/src/flyway/resources/db/migration", - "$projectDir/src/flyway/kotlin/db/migration" +val migrationDirsSqlite = listOf( + "$projectDir/src/flyway/resources/db/migration/sqlite" +// "$projectDir/src/flyway/kotlin/db/migration/sqlite" ) flyway { - url = jooqDb["url"] - user = jooqDb["user"] - password = jooqDb["password"] - schemas = arrayOf(jooqDb["schema"]) - locations = arrayOf("classpath:db/migration") + url = dbSqlite["url"] + locations = arrayOf("classpath:db/migration/sqlite") } -//in order to include the Java migrations, flywayClasses must be run before flywayMigrate tasks.flywayMigrate { + //in order to include the Java migrations, flywayClasses must be run before flywayMigrate dependsOn("flywayClasses") - migrationDirs.forEach { inputs.dir(it) } + migrationDirsSqlite.forEach { inputs.dir(it) } outputs.dir("${project.buildDir}/generated/flyway") - doFirst { delete(outputs.files) } + doFirst { + delete(outputs.files) + mkdir("${project.buildDir}/generated/flyway") + } } jooqGenerator { jooqVersion = "3.13.1" configuration("primary", project.sourceSets.getByName("main")) { - databaseSources = migrationDirs + databaseSources = migrationDirsSqlite configuration = jooqCodegenConfiguration { jdbc { - username = jooqDb["user"] - password = jooqDb["password"] - driver = "org.h2.Driver" - url = jooqDb["url"] + driver = "org.sqlite.JDBC" + url = dbSqlite["url"] } generator { @@ -242,8 +241,7 @@ jooqGenerator { } database { - name = "org.jooq.meta.h2.H2Database" - inputSchema = jooqDb["schema"] + name = "org.jooq.meta.sqlite.SQLiteDatabase" } } } diff --git a/komga/src/flyway/kotlin/db/migration/V20190926114415__create_library_from_series.kt b/komga/src/flyway/kotlin/db/migration/h2/V20190926114415__create_library_from_series.kt similarity index 98% rename from komga/src/flyway/kotlin/db/migration/V20190926114415__create_library_from_series.kt rename to komga/src/flyway/kotlin/db/migration/h2/V20190926114415__create_library_from_series.kt index 376d12f51..3848448ae 100644 --- a/komga/src/flyway/kotlin/db/migration/V20190926114415__create_library_from_series.kt +++ b/komga/src/flyway/kotlin/db/migration/h2/V20190926114415__create_library_from_series.kt @@ -1,4 +1,4 @@ -package db.migration +package db.migration.h2 import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context @@ -44,4 +44,4 @@ fun findCommonDirPath(paths: List, separator: Char): String { if (!paths2.all { it.startsWith(common + separator) || it == common }) return prevCommon if (++k == n) return common } -} \ No newline at end of file +} diff --git a/komga/src/flyway/kotlin/db/migration/V20200121154334__create_series_metadata_from_series.kt b/komga/src/flyway/kotlin/db/migration/h2/V20200121154334__create_series_metadata_from_series.kt similarity index 97% rename from komga/src/flyway/kotlin/db/migration/V20200121154334__create_series_metadata_from_series.kt rename to komga/src/flyway/kotlin/db/migration/h2/V20200121154334__create_series_metadata_from_series.kt index f34f1765a..202121e61 100644 --- a/komga/src/flyway/kotlin/db/migration/V20200121154334__create_series_metadata_from_series.kt +++ b/komga/src/flyway/kotlin/db/migration/h2/V20200121154334__create_series_metadata_from_series.kt @@ -1,4 +1,4 @@ -package db.migration +package db.migration.h2 import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context diff --git a/komga/src/flyway/kotlin/db/migration/V20200306175848__create_book_metadata_from_book.kt b/komga/src/flyway/kotlin/db/migration/h2/V20200306175848__create_book_metadata_from_book.kt similarity index 98% rename from komga/src/flyway/kotlin/db/migration/V20200306175848__create_book_metadata_from_book.kt rename to komga/src/flyway/kotlin/db/migration/h2/V20200306175848__create_book_metadata_from_book.kt index ac5dcc592..7fde201fd 100644 --- a/komga/src/flyway/kotlin/db/migration/V20200306175848__create_book_metadata_from_book.kt +++ b/komga/src/flyway/kotlin/db/migration/h2/V20200306175848__create_book_metadata_from_book.kt @@ -1,4 +1,4 @@ -package db.migration +package db.migration.h2 import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context diff --git a/komga/src/flyway/resources/db/migration/V20190819161603__First_Version.sql b/komga/src/flyway/resources/db/migration/h2/V20190819161603__First_Version.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190819161603__First_Version.sql rename to komga/src/flyway/resources/db/migration/h2/V20190819161603__First_Version.sql diff --git a/komga/src/flyway/resources/db/migration/V20190906100609__regenerate_webp_covers.sql b/komga/src/flyway/resources/db/migration/h2/V20190906100609__regenerate_webp_covers.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190906100609__regenerate_webp_covers.sql rename to komga/src/flyway/resources/db/migration/h2/V20190906100609__regenerate_webp_covers.sql diff --git a/komga/src/flyway/resources/db/migration/V20190906152334__reparse_pdf_files.sql b/komga/src/flyway/resources/db/migration/h2/V20190906152334__reparse_pdf_files.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190906152334__reparse_pdf_files.sql rename to komga/src/flyway/resources/db/migration/h2/V20190906152334__reparse_pdf_files.sql diff --git a/komga/src/flyway/resources/db/migration/V20190907104615__reparse_pdf_files.sql b/komga/src/flyway/resources/db/migration/h2/V20190907104615__reparse_pdf_files.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190907104615__reparse_pdf_files.sql rename to komga/src/flyway/resources/db/migration/h2/V20190907104615__reparse_pdf_files.sql diff --git a/komga/src/flyway/resources/db/migration/V20190907143337__reparse_pdf_files.sql b/komga/src/flyway/resources/db/migration/h2/V20190907143337__reparse_pdf_files.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190907143337__reparse_pdf_files.sql rename to komga/src/flyway/resources/db/migration/h2/V20190907143337__reparse_pdf_files.sql diff --git a/komga/src/flyway/resources/db/migration/V20190910082852__rescan_series.sql b/komga/src/flyway/resources/db/migration/h2/V20190910082852__rescan_series.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190910082852__rescan_series.sql rename to komga/src/flyway/resources/db/migration/h2/V20190910082852__rescan_series.sql diff --git a/komga/src/flyway/resources/db/migration/V20190926112211__library_entity.sql b/komga/src/flyway/resources/db/migration/h2/V20190926112211__library_entity.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190926112211__library_entity.sql rename to komga/src/flyway/resources/db/migration/h2/V20190926112211__library_entity.sql diff --git a/komga/src/flyway/resources/db/migration/V20190927132225__serie_library_id_not_null.sql b/komga/src/flyway/resources/db/migration/h2/V20190927132225__serie_library_id_not_null.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20190927132225__serie_library_id_not_null.sql rename to komga/src/flyway/resources/db/migration/h2/V20190927132225__serie_library_id_not_null.sql diff --git a/komga/src/flyway/resources/db/migration/V20191008135338__rename_serie_to_series.sql b/komga/src/flyway/resources/db/migration/h2/V20191008135338__rename_serie_to_series.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191008135338__rename_serie_to_series.sql rename to komga/src/flyway/resources/db/migration/h2/V20191008135338__rename_serie_to_series.sql diff --git a/komga/src/flyway/resources/db/migration/V20191010153833__delete_thumbnails_to_force_regeneration_in_jpeg.sql b/komga/src/flyway/resources/db/migration/h2/V20191010153833__delete_thumbnails_to_force_regeneration_in_jpeg.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191010153833__delete_thumbnails_to_force_regeneration_in_jpeg.sql rename to komga/src/flyway/resources/db/migration/h2/V20191010153833__delete_thumbnails_to_force_regeneration_in_jpeg.sql diff --git a/komga/src/flyway/resources/db/migration/V20191014143703__user_entity.sql b/komga/src/flyway/resources/db/migration/h2/V20191014143703__user_entity.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191014143703__user_entity.sql rename to komga/src/flyway/resources/db/migration/h2/V20191014143703__user_entity.sql diff --git a/komga/src/flyway/resources/db/migration/V20191021101906__user_library_sharing.sql b/komga/src/flyway/resources/db/migration/h2/V20191021101906__user_library_sharing.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191021101906__user_library_sharing.sql rename to komga/src/flyway/resources/db/migration/h2/V20191021101906__user_library_sharing.sql diff --git a/komga/src/flyway/resources/db/migration/V20191029153618__book_filesize.sql b/komga/src/flyway/resources/db/migration/h2/V20191029153618__book_filesize.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191029153618__book_filesize.sql rename to komga/src/flyway/resources/db/migration/h2/V20191029153618__book_filesize.sql diff --git a/komga/src/flyway/resources/db/migration/V20191122164159__book_number.sql b/komga/src/flyway/resources/db/migration/h2/V20191122164159__book_number.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191122164159__book_number.sql rename to komga/src/flyway/resources/db/migration/h2/V20191122164159__book_number.sql diff --git a/komga/src/flyway/resources/db/migration/V20191230131239__rename_metadata_to_media.sql b/komga/src/flyway/resources/db/migration/h2/V20191230131239__rename_metadata_to_media.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20191230131239__rename_metadata_to_media.sql rename to komga/src/flyway/resources/db/migration/h2/V20191230131239__rename_metadata_to_media.sql diff --git a/komga/src/flyway/resources/db/migration/V20200103170613__media_as_auditable_entity.sql b/komga/src/flyway/resources/db/migration/h2/V20200103170613__media_as_auditable_entity.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200103170613__media_as_auditable_entity.sql rename to komga/src/flyway/resources/db/migration/h2/V20200103170613__media_as_auditable_entity.sql diff --git a/komga/src/flyway/resources/db/migration/V20200108103325__media_comment.sql b/komga/src/flyway/resources/db/migration/h2/V20200108103325__media_comment.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200108103325__media_comment.sql rename to komga/src/flyway/resources/db/migration/h2/V20200108103325__media_comment.sql diff --git a/komga/src/flyway/resources/db/migration/V20200121153351__series_metadata.sql b/komga/src/flyway/resources/db/migration/h2/V20200121153351__series_metadata.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200121153351__series_metadata.sql rename to komga/src/flyway/resources/db/migration/h2/V20200121153351__series_metadata.sql diff --git a/komga/src/flyway/resources/db/migration/V20200121154845__series_metadata_id_not_null.sql b/komga/src/flyway/resources/db/migration/h2/V20200121154845__series_metadata_id_not_null.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200121154845__series_metadata_id_not_null.sql rename to komga/src/flyway/resources/db/migration/h2/V20200121154845__series_metadata_id_not_null.sql diff --git a/komga/src/flyway/resources/db/migration/V20200131101624__series_metadata_title.sql b/komga/src/flyway/resources/db/migration/h2/V20200131101624__series_metadata_title.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200131101624__series_metadata_title.sql rename to komga/src/flyway/resources/db/migration/h2/V20200131101624__series_metadata_title.sql diff --git a/komga/src/flyway/resources/db/migration/V20200306174948__book_number_to_int.sql b/komga/src/flyway/resources/db/migration/h2/V20200306174948__book_number_to_int.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200306174948__book_number_to_int.sql rename to komga/src/flyway/resources/db/migration/h2/V20200306174948__book_number_to_int.sql diff --git a/komga/src/flyway/resources/db/migration/V20200306175821__book_metadata.sql b/komga/src/flyway/resources/db/migration/h2/V20200306175821__book_metadata.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200306175821__book_metadata.sql rename to komga/src/flyway/resources/db/migration/h2/V20200306175821__book_metadata.sql diff --git a/komga/src/flyway/resources/db/migration/V20200402103754__media_files.sql b/komga/src/flyway/resources/db/migration/h2/V20200402103754__media_files.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200402103754__media_files.sql rename to komga/src/flyway/resources/db/migration/h2/V20200402103754__media_files.sql diff --git a/komga/src/flyway/resources/db/migration/V20200410115036__rescan_series_for_epub.sql b/komga/src/flyway/resources/db/migration/h2/V20200410115036__rescan_series_for_epub.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200410115036__rescan_series_for_epub.sql rename to komga/src/flyway/resources/db/migration/h2/V20200410115036__rescan_series_for_epub.sql diff --git a/komga/src/flyway/resources/db/migration/V20200429230748__jooq_migration.sql b/komga/src/flyway/resources/db/migration/h2/V20200429230748__jooq_migration.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200429230748__jooq_migration.sql rename to komga/src/flyway/resources/db/migration/h2/V20200429230748__jooq_migration.sql diff --git a/komga/src/flyway/resources/db/migration/V20200601173217__read_progress.sql b/komga/src/flyway/resources/db/migration/h2/V20200601173217__read_progress.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200601173217__read_progress.sql rename to komga/src/flyway/resources/db/migration/h2/V20200601173217__read_progress.sql diff --git a/komga/src/flyway/resources/db/migration/V20200609173933__user_roles.sql b/komga/src/flyway/resources/db/migration/h2/V20200609173933__user_roles.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200609173933__user_roles.sql rename to komga/src/flyway/resources/db/migration/h2/V20200609173933__user_roles.sql diff --git a/komga/src/flyway/resources/db/migration/V20200610172313__collections.sql b/komga/src/flyway/resources/db/migration/h2/V20200610172313__collections.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200610172313__collections.sql rename to komga/src/flyway/resources/db/migration/h2/V20200610172313__collections.sql diff --git a/komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql b/komga/src/flyway/resources/db/migration/h2/V20200702111235__library_metadata_import_flags.sql similarity index 100% rename from komga/src/flyway/resources/db/migration/V20200702111235__library_metadata_import_flags.sql rename to komga/src/flyway/resources/db/migration/h2/V20200702111235__library_metadata_import_flags.sql diff --git a/komga/src/flyway/resources/db/migration/sqlite/V20200706141854__initial_migration.sql b/komga/src/flyway/resources/db/migration/sqlite/V20200706141854__initial_migration.sql new file mode 100644 index 000000000..fc35ef97f --- /dev/null +++ b/komga/src/flyway/resources/db/migration/sqlite/V20200706141854__initial_migration.sql @@ -0,0 +1,160 @@ +CREATE TABLE LIBRARY +( + ID varchar NOT NULL PRIMARY KEY, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + NAME varchar NOT NULL, + ROOT varchar NOT NULL, + IMPORT_COMICINFO_BOOK boolean NOT NULL DEFAULT 1, + IMPORT_COMICINFO_SERIES boolean NOT NULL DEFAULT 1, + IMPORT_COMICINFO_COLLECTION boolean NOT NULL DEFAULT 1, + IMPORT_EPUB_BOOK boolean NOT NULL DEFAULT 1, + IMPORT_EPUB_SERIES boolean NOT NULL DEFAULT 1 +); +CREATE TABLE USER +( + ID varchar NOT NULL PRIMARY KEY, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + EMAIL varchar NOT NULL UNIQUE, + PASSWORD varchar NOT NULL, + SHARED_ALL_LIBRARIES boolean NOT NULL DEFAULT 1, + ROLE_ADMIN boolean NOT NULL DEFAULT 0, + ROLE_FILE_DOWNLOAD boolean NOT NULL DEFAULT 1, + ROLE_PAGE_STREAMING boolean NOT NULL DEFAULT 1 +); +CREATE TABLE USER_LIBRARY_SHARING +( + USER_ID varchar NOT NULL, + LIBRARY_ID varchar NOT NULL, + PRIMARY KEY (USER_ID, LIBRARY_ID), + FOREIGN KEY (USER_ID) REFERENCES USER (ID), + FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY (ID) +); +CREATE TABLE SERIES +( + ID varchar NOT NULL PRIMARY KEY, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + FILE_LAST_MODIFIED datetime NOT NULL, + NAME varchar NOT NULL, + URL varchar NOT NULL, + LIBRARY_ID varchar NOT NULL, + FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY (ID) +); +CREATE TABLE SERIES_METADATA +( + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + STATUS varchar NOT NULL, + STATUS_LOCK boolean NOT NULL DEFAULT 0, + TITLE varchar NOT NULL, + TITLE_LOCK boolean NOT NULL DEFAULT 0, + TITLE_SORT varchar NOT NULL, + TITLE_SORT_LOCK boolean NOT NULL DEFAULT 0, + SERIES_ID varchar NOT NULL PRIMARY KEY, + FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID) +); +CREATE TABLE BOOK +( + ID varchar NOT NULL PRIMARY KEY, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + FILE_LAST_MODIFIED datetime NOT NULL, + NAME varchar NOT NULL, + URL varchar NOT NULL, + SERIES_ID varchar NOT NULL, + FILE_SIZE int8 NOT NULL DEFAULT 0, + NUMBER int NOT NULL DEFAULT 0, + LIBRARY_ID varchar NOT NULL, + FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY (ID), + FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID) +); +CREATE TABLE MEDIA +( + MEDIA_TYPE varchar NULL, + STATUS varchar NOT NULL, + THUMBNAIL blob NULL, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + COMMENT varchar NULL, + BOOK_ID varchar NOT NULL PRIMARY KEY, + PAGE_COUNT int NOT NULL DEFAULT 0, + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) +); +CREATE TABLE MEDIA_PAGE +( + FILE_NAME varchar NOT NULL, + MEDIA_TYPE varchar NOT NULL, + NUMBER int NOT NULL, + BOOK_ID varchar NOT NULL, + PRIMARY KEY (BOOK_ID, NUMBER), + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) +); +CREATE TABLE MEDIA_FILE +( + FILE_NAME varchar NOT NULL, + BOOK_ID varchar NOT NULL, + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) +); +CREATE TABLE BOOK_METADATA +( + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + AGE_RATING int NULL, + AGE_RATING_LOCK boolean NOT NULL DEFAULT 0, + NUMBER varchar NOT NULL, + NUMBER_LOCK boolean NOT NULL DEFAULT 0, + NUMBER_SORT real NOT NULL, + NUMBER_SORT_LOCK boolean NOT NULL DEFAULT 0, + PUBLISHER varchar NOT NULL DEFAULT '', + PUBLISHER_LOCK boolean NOT NULL DEFAULT 0, + READING_DIRECTION varchar NULL, + READING_DIRECTION_LOCK boolean NOT NULL DEFAULT 0, + RELEASE_DATE date NULL, + RELEASE_DATE_LOCK boolean NOT NULL DEFAULT 0, + SUMMARY varchar NOT NULL DEFAULT '', + SUMMARY_LOCK boolean NOT NULL DEFAULT 0, + TITLE varchar NOT NULL, + TITLE_LOCK boolean NOT NULL DEFAULT 0, + AUTHORS_LOCK boolean NOT NULL DEFAULT 0, + BOOK_ID varchar NOT NULL PRIMARY KEY, + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) +); +CREATE TABLE BOOK_METADATA_AUTHOR +( + NAME varchar NOT NULL, + ROLE varchar NOT NULL, + BOOK_ID varchar NOT NULL, + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) +); +CREATE TABLE READ_PROGRESS +( + BOOK_ID varchar NOT NULL, + USER_ID varchar NOT NULL, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PAGE int NOT NULL, + COMPLETED boolean NOT NULL, + PRIMARY KEY (BOOK_ID, USER_ID), + FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID), + FOREIGN KEY (USER_ID) REFERENCES USER (ID) +); +CREATE TABLE COLLECTION +( + ID varchar NOT NULL PRIMARY KEY, + NAME varchar NOT NULL, + ORDERED boolean NOT NULL DEFAULT 0, + SERIES_COUNT int NOT NULL, + CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE COLLECTION_SERIES +( + COLLECTION_ID varchar NOT NULL, + SERIES_ID varchar NOT NULL, + NUMBER int NOT NULL, + PRIMARY KEY (COLLECTION_ID, SERIES_ID), + FOREIGN KEY (COLLECTION_ID) REFERENCES COLLECTION (ID), + FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID) +); diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt index 265bdcb4b..7d244ee44 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt @@ -5,28 +5,23 @@ import java.io.Serializable sealed class Task : Serializable { abstract fun uniqueId(): String - data class ScanLibrary(val libraryId: Long) : Task() { + data class ScanLibrary(val libraryId: String) : Task() { override fun uniqueId() = "SCAN_LIBRARY_$libraryId" } - data class AnalyzeBook(val bookId: Long) : Task() { + data class AnalyzeBook(val bookId: String) : Task() { override fun uniqueId() = "ANALYZE_BOOK_$bookId" } - data class GenerateBookThumbnail(val bookId: Long) : Task() { + data class GenerateBookThumbnail(val bookId: String) : Task() { override fun uniqueId() = "GENERATE_BOOK_THUMBNAIL_$bookId" } - data class RefreshBookMetadata(val bookId: Long) : Task() { + data class RefreshBookMetadata(val bookId: String) : Task() { override fun uniqueId() = "REFRESH_BOOK_METADATA_$bookId" } - data class RefreshSeriesMetadata(val seriesId: Long) : Task() { + data class RefreshSeriesMetadata(val seriesId: String) : Task() { override fun uniqueId() = "REFRESH_SERIES_METADATA_$seriesId" } - - object BackupDatabase : Task() { - override fun uniqueId(): String = "BACKUP_DATABASE" - override fun toString(): String = "BackupDatabase" - } } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt index b6868d1cf..30641d6c7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt @@ -7,7 +7,6 @@ import org.gotson.komga.domain.persistence.SeriesRepository import org.gotson.komga.domain.service.BookLifecycle import org.gotson.komga.domain.service.LibraryScanner import org.gotson.komga.domain.service.MetadataLifecycle -import org.gotson.komga.infrastructure.h2.DatabaseBackuper import org.gotson.komga.infrastructure.jms.QUEUE_TASKS import org.gotson.komga.infrastructure.jms.QUEUE_TASKS_SELECTOR import org.springframework.jms.annotation.JmsListener @@ -24,8 +23,7 @@ class TaskHandler( private val seriesRepository: SeriesRepository, private val libraryScanner: LibraryScanner, private val bookLifecycle: BookLifecycle, - private val metadataLifecycle: MetadataLifecycle, - private val databaseBackuper: DatabaseBackuper + private val metadataLifecycle: MetadataLifecycle ) { @JmsListener(destination = QUEUE_TASKS, selector = QUEUE_TASKS_SELECTOR) @@ -63,10 +61,6 @@ class TaskHandler( metadataLifecycle.refreshMetadata(it) } ?: logger.warn { "Cannot execute task $task: Series does not exist" } - is Task.BackupDatabase -> { - databaseBackuper.backupDatabase() - } - } }.also { logger.info { "Task $task executed in $it" } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt index 49bdeaa3a..c80a4e541 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt @@ -27,7 +27,7 @@ class TaskReceiver( libraryRepository.findAll().forEach { scanLibrary(it.id) } } - fun scanLibrary(libraryId: Long) { + fun scanLibrary(libraryId: String) { submitTask(Task.ScanLibrary(libraryId)) } @@ -40,7 +40,7 @@ class TaskReceiver( } } - fun analyzeBook(bookId: Long) { + fun analyzeBook(bookId: String) { submitTask(Task.AnalyzeBook(bookId)) } @@ -48,11 +48,11 @@ class TaskReceiver( submitTask(Task.AnalyzeBook(book.id)) } - fun generateBookThumbnail(bookId: Long) { + fun generateBookThumbnail(bookId: String) { submitTask(Task.GenerateBookThumbnail(bookId)) } - fun refreshBookMetadata(bookId: Long) { + fun refreshBookMetadata(bookId: String) { submitTask(Task.RefreshBookMetadata(bookId)) } @@ -60,14 +60,10 @@ class TaskReceiver( submitTask(Task.RefreshBookMetadata(book.id)) } - fun refreshSeriesMetadata(seriesId: Long) { + fun refreshSeriesMetadata(seriesId: String) { submitTask(Task.RefreshSeriesMetadata(seriesId)) } - fun databaseBackup() { - submitTask(Task.BackupDatabase) - } - private fun submitTask(task: Task) { logger.info { "Sending task: $task" } jmsTemplate.convertAndSend(QUEUE_TASKS, task) { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt index 3ace55703..d677540da 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt @@ -1,5 +1,6 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import com.jakewharton.byteunits.BinaryByteUnit import org.apache.commons.io.FilenameUtils import java.net.URL @@ -14,9 +15,9 @@ data class Book( val fileSize: Long = 0, val number: Int = 0, - val id: Long = 0, - val seriesId: Long = 0, - val libraryId: Long = 0, + val id: String = TsidCreator.getTsidString256(), + val seriesId: String = "", + val libraryId: String = "", override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt index eca0ced7f..198a2418f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt @@ -24,7 +24,7 @@ class BookMetadata( val releaseDateLock: Boolean = false, val authorsLock: Boolean = false, - val bookId: Long = 0, + val bookId: String = "", override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() @@ -54,7 +54,7 @@ class BookMetadata( ageRatingLock: Boolean = this.ageRatingLock, releaseDateLock: Boolean = this.releaseDateLock, authorsLock: Boolean = this.authorsLock, - bookId: Long = this.bookId, + bookId: String = this.bookId, createdDate: LocalDateTime = this.createdDate, lastModifiedDate: LocalDateTime = this.lastModifiedDate ) = diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt index 6ad14986d..3bdcb47c6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt @@ -1,15 +1,15 @@ package org.gotson.komga.domain.model open class BookSearch( - val libraryIds: Collection? = null, - val seriesIds: Collection? = null, + val libraryIds: Collection? = null, + val seriesIds: Collection? = null, val searchTerm: String? = null, val mediaStatus: Collection? = null ) class BookSearchWithReadProgress( - libraryIds: Collection? = null, - seriesIds: Collection? = null, + libraryIds: Collection? = null, + seriesIds: Collection? = null, searchTerm: String? = null, mediaStatus: Collection? = null, val readStatus: Collection? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt index 5de6bb59e..1861536a4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt @@ -1,5 +1,6 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import java.time.LocalDateTime import javax.validation.constraints.Email import javax.validation.constraints.NotBlank @@ -18,9 +19,9 @@ data class KomgaUser( val roleAdmin: Boolean, val roleFileDownload: Boolean = true, val rolePageStreaming: Boolean = true, - val sharedLibrariesIds: Set = emptySet(), + val sharedLibrariesIds: Set = emptySet(), val sharedAllLibraries: Boolean = true, - val id: Long = 0, + val id: String = TsidCreator.getTsidString256(), override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() ) : Auditable() { @@ -38,7 +39,7 @@ data class KomgaUser( * * @return a list of authorised LibraryIds, or null if the user is authorized to see all libraries */ - fun getAuthorizedLibraryIds(libraryIds: Collection?): Collection? = + fun getAuthorizedLibraryIds(libraryIds: Collection?): Collection? = when { // limited user & libraryIds are specified: filter on provided libraries intersecting user's authorized libraries !sharedAllLibraries && libraryIds != null -> libraryIds.intersect(sharedLibrariesIds) @@ -61,7 +62,7 @@ data class KomgaUser( return sharedAllLibraries || sharedLibrariesIds.any { it == series.libraryId } } - fun canAccessLibrary(libraryId: Long): Boolean = + fun canAccessLibrary(libraryId: String): Boolean = sharedAllLibraries || sharedLibrariesIds.any { it == libraryId } fun canAccessLibrary(library: Library): Boolean { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt index b7ca555b6..43afaec3e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt @@ -1,5 +1,6 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import java.net.URL import java.nio.file.Path import java.nio.file.Paths @@ -14,7 +15,7 @@ data class Library( val importEpubBook: Boolean = true, val importEpubSeries: Boolean = true, - val id: Long = 0, + val id: String = TsidCreator.getTsidString256(), override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt index b4fae1a5f..c16f546d4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt @@ -9,7 +9,7 @@ class Media( val pages: List = emptyList(), val files: List = emptyList(), val comment: String? = null, - val bookId: Long = 0, + val bookId: String = "", override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() ) : Auditable() { @@ -21,7 +21,7 @@ class Media( pages: List = this.pages.toList(), files: List = this.files.toList(), comment: String? = this.comment, - bookId: Long = this.bookId, + bookId: String = this.bookId, createdDate: LocalDateTime = this.createdDate, lastModifiedDate: LocalDateTime = this.lastModifiedDate ) = diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt index 8fc0d4790..9ff3abd65 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt @@ -3,8 +3,8 @@ package org.gotson.komga.domain.model import java.time.LocalDateTime data class ReadProgress( - val bookId: Long, - val userId: Long, + val bookId: String, + val userId: String, val page: Int, val completed: Boolean, diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt index 5626bfb96..46fc4ff95 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt @@ -1,15 +1,16 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import java.net.URL import java.time.LocalDateTime data class Series( val name: String, val url: URL, - var fileLastModified: LocalDateTime, + val fileLastModified: LocalDateTime, - val id: Long = 0, - val libraryId: Long = 0, + val id: String = TsidCreator.getTsidString256(), + val libraryId: String = "", override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt index dbd7632ec..4b613f4d8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt @@ -1,14 +1,15 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import java.time.LocalDateTime data class SeriesCollection( val name: String, val ordered: Boolean = false, - val seriesIds: List = emptyList(), + val seriesIds: List = emptyList(), - val id: Long = 0, + val id: String = TsidCreator.getTsidString256(), override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now(), diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt index 96e700631..fd6095ecd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt @@ -11,7 +11,7 @@ class SeriesMetadata( val titleLock: Boolean = false, val titleSortLock: Boolean = false, - val seriesId: Long = 0, + val seriesId: String = "", override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = LocalDateTime.now() @@ -26,7 +26,7 @@ class SeriesMetadata( statusLock: Boolean = this.statusLock, titleLock: Boolean = this.titleLock, titleSortLock: Boolean = this.titleSortLock, - seriesId: Long = this.seriesId, + seriesId: String = this.seriesId, createdDate: LocalDateTime = this.createdDate, lastModifiedDate: LocalDateTime = this.lastModifiedDate ) = diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt index c99256df3..d9345e952 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt @@ -1,14 +1,14 @@ package org.gotson.komga.domain.model open class SeriesSearch( - val libraryIds: Collection? = null, + val libraryIds: Collection? = null, val collectionIds: Collection? = null, val searchTerm: String? = null, val metadataStatus: Collection? = null ) class SeriesSearchWithReadProgress( - libraryIds: Collection? = null, + libraryIds: Collection? = null, collectionIds: Collection? = null, searchTerm: String? = null, metadataStatus: Collection? = null, diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt index 2819e5d1f..a71d31438 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt @@ -3,13 +3,17 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.BookMetadata interface BookMetadataRepository { - fun findById(bookId: Long): BookMetadata - fun findByIdOrNull(bookId: Long): BookMetadata? + fun findById(bookId: String): BookMetadata + fun findByIdOrNull(bookId: String): BookMetadata? + fun findByIds(bookIds: Collection): Collection fun findAuthorsByName(search: String): List - fun insert(metadata: BookMetadata): BookMetadata + fun insert(metadata: BookMetadata) + fun insertMany(metadatas: Collection) fun update(metadata: BookMetadata) + fun updateMany(metadatas: Collection) - fun delete(bookId: Long) + fun delete(bookId: String) + fun deleteByBookIds(bookIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt index 9ecdf0d9c..a09df5ac2 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt @@ -4,22 +4,25 @@ import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookSearch interface BookRepository { - fun findByIdOrNull(bookId: Long): Book? - fun findBySeriesId(seriesId: Long): Collection + fun findByIdOrNull(bookId: String): Book? + fun findBySeriesId(seriesId: String): Collection fun findAll(): Collection fun findAll(bookSearch: BookSearch): Collection - fun getLibraryId(bookId: Long): Long? - fun findFirstIdInSeries(seriesId: Long): Long? - fun findAllIdBySeriesId(seriesId: Long): Collection - fun findAllIdByLibraryId(libraryId: Long): Collection - fun findAllId(bookSearch: BookSearch): Collection + fun getLibraryId(bookId: String): String? + fun findFirstIdInSeries(seriesId: String): String? + fun findAllIdBySeriesId(seriesId: String): Collection + fun findAllIdBySeriesIds(seriesIds: Collection): Collection + fun findAllIdByLibraryId(libraryId: String): Collection + fun findAllId(bookSearch: BookSearch): Collection - fun insert(book: Book): Book + fun insert(book: Book) + fun insertMany(books: Collection) fun update(book: Book) + fun updateMany(books: Collection) - fun delete(bookId: Long) - fun deleteAll(bookIds: List) + fun delete(bookId: String) + fun deleteByBookIds(bookIds: Collection) fun deleteAll() fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt index 99827a226..3ff0ecf98 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt @@ -6,12 +6,12 @@ interface KomgaUserRepository { fun count(): Long fun findAll(): Collection - fun findByIdOrNull(id: Long): KomgaUser? + fun findByIdOrNull(id: String): KomgaUser? - fun save(user: KomgaUser): KomgaUser - fun saveAll(users: Iterable): Collection + fun insert(user: KomgaUser) + fun update(user: KomgaUser) - fun delete(user: KomgaUser) + fun delete(userId: String) fun deleteAll() fun existsByEmailIgnoreCase(email: String): Boolean diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt index 0ebe8a8e0..8b6eb63d1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt @@ -3,15 +3,15 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.Library interface LibraryRepository { - fun findByIdOrNull(libraryId: Long): Library? - fun findById(libraryId: Long): Library + fun findByIdOrNull(libraryId: String): Library? + fun findById(libraryId: String): Library fun findAll(): Collection - fun findAllById(libraryIds: Collection): Collection + fun findAllById(libraryIds: Collection): Collection - fun delete(libraryId: Long) + fun delete(libraryId: String) fun deleteAll() - fun insert(library: Library): Library + fun insert(library: Library) fun update(library: Library) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt index e5a25a152..7dcd252f4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt @@ -3,12 +3,14 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.Media interface MediaRepository { - fun findById(bookId: Long): Media + fun findById(bookId: String): Media - fun getThumbnail(bookId: Long): ByteArray? + fun getThumbnail(bookId: String): ByteArray? - fun insert(media: Media): Media + fun insert(media: Media) + fun insertMany(medias: Collection) fun update(media: Media) - fun delete(bookId: Long) + fun delete(bookId: String) + fun deleteByBookIds(bookIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt index e835574f5..27c59b553 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt @@ -5,14 +5,14 @@ import org.gotson.komga.domain.model.ReadProgress interface ReadProgressRepository { fun findAll(): Collection - fun findByBookIdAndUserId(bookId: Long, userId: Long): ReadProgress? - fun findByUserId(userId: Long): Collection + fun findByBookIdAndUserId(bookId: String, userId: String): ReadProgress? + fun findByUserId(userId: String): Collection fun save(readProgress: ReadProgress) - fun delete(bookId: Long, userId: Long) - fun deleteByUserId(userId: Long) - fun deleteByBookId(bookId: Long) - fun deleteByBookIds(bookIds: Collection) + fun delete(bookId: String, userId: String) + fun deleteByUserId(userId: String) + fun deleteByBookId(bookId: String) + fun deleteByBookIds(bookIds: Collection) fun deleteAll() } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt index 1d884a454..2d4da958f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt @@ -5,36 +5,37 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable interface SeriesCollectionRepository { - fun findByIdOrNull(collectionId: Long): SeriesCollection? + fun findByIdOrNull(collectionId: String): SeriesCollection? fun findAll(search: String? = null, pageable: Pageable): Page /** * Find one SeriesCollection by collectionId, * optionally with only seriesId filtered by the provided filterOnLibraryIds. */ - fun findByIdOrNull(collectionId: Long, filterOnLibraryIds: Collection?): SeriesCollection? + fun findByIdOrNull(collectionId: String, filterOnLibraryIds: Collection?): SeriesCollection? /** * Find all SeriesCollection with at least one Series belonging to the provided belongsToLibraryIds, * optionally with only seriesId filtered by the provided filterOnLibraryIds. */ - fun findAllByLibraries(belongsToLibraryIds: Collection, filterOnLibraryIds: Collection?, search: String? = null, pageable: Pageable): Page + fun findAllByLibraries(belongsToLibraryIds: Collection, filterOnLibraryIds: Collection?, search: String? = null, pageable: Pageable): Page /** * Find all SeriesCollection that contains the provided containsSeriesId, * optionally with only seriesId filtered by the provided filterOnLibraryIds. */ - fun findAllBySeries(containsSeriesId: Long, filterOnLibraryIds: Collection?): Collection + fun findAllBySeries(containsSeriesId: String, filterOnLibraryIds: Collection?): Collection fun findByNameOrNull(name: String): SeriesCollection? - fun insert(collection: SeriesCollection): SeriesCollection + fun insert(collection: SeriesCollection) fun update(collection: SeriesCollection) - fun removeSeriesFromAll(seriesId: Long) + fun removeSeriesFromAll(seriesId: String) + fun removeSeriesFromAll(seriesIds: Collection) + + fun delete(collectionId: String) - fun delete(collectionId: Long) fun deleteAll() - fun existsByName(name: String): Boolean } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt index ed00970af..1870807a6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt @@ -3,13 +3,13 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.SeriesMetadata interface SeriesMetadataRepository { - fun findById(seriesId: Long): SeriesMetadata - fun findByIdOrNull(seriesId: Long): SeriesMetadata? + fun findById(seriesId: String): SeriesMetadata + fun findByIdOrNull(seriesId: String): SeriesMetadata? fun insert(metadata: SeriesMetadata): SeriesMetadata fun update(metadata: SeriesMetadata) - fun delete(seriesId: Long) + fun delete(seriesId: String) fun count(): Long } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt index 4d4292e3d..8af1cbe54 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt @@ -6,20 +6,20 @@ import java.net.URL interface SeriesRepository { fun findAll(): Collection - fun findByIdOrNull(seriesId: Long): Series? - fun findByLibraryId(libraryId: Long): Collection - fun findByLibraryIdAndUrlNotIn(libraryId: Long, urls: Collection): Collection - fun findByLibraryIdAndUrl(libraryId: Long, url: URL): Series? + fun findByIdOrNull(seriesId: String): Series? + fun findByLibraryId(libraryId: String): Collection + fun findByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): Collection + fun findByLibraryIdAndUrl(libraryId: String, url: URL): Series? fun findAll(search: SeriesSearch): Collection - fun getLibraryId(seriesId: Long): Long? + fun getLibraryId(seriesId: String): String? - fun insert(series: Series): Series + fun insert(series: Series) fun update(series: Series) - fun delete(seriesId: Long) + fun delete(seriesId: String) fun deleteAll() - fun deleteAll(seriesIds: Collection) + fun deleteAll(seriesIds: Collection) fun count(): Long } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt index 7722fa92c..ae70db9b6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt @@ -104,7 +104,7 @@ class BookLifecycle( } } - fun delete(bookId: Long) { + fun deleteOne(bookId: String) { logger.info { "Delete book id: $bookId" } readProgressRepository.deleteByBookId(bookId) @@ -114,6 +114,16 @@ class BookLifecycle( bookRepository.delete(bookId) } + fun deleteMany(bookIds: Collection) { + logger.info { "Delete all books: $bookIds" } + + readProgressRepository.deleteByBookIds(bookIds) + mediaRepository.deleteByBookIds(bookIds) + bookMetadataRepository.deleteByBookIds(bookIds) + + bookRepository.deleteByBookIds(bookIds) + } + fun markReadProgress(book: Book, user: KomgaUser, page: Int) { val media = mediaRepository.findById(book.id) require(page >= 1 && page <= media.pages.size) { "Page argument ($page) must be within 1 and book page count (${media.pages.size})" } @@ -121,13 +131,13 @@ class BookLifecycle( readProgressRepository.save(ReadProgress(book.id, user.id, page, page == media.pages.size)) } - fun markReadProgressCompleted(bookId: Long, user: KomgaUser) { + fun markReadProgressCompleted(bookId: String, user: KomgaUser) { val media = mediaRepository.findById(bookId) readProgressRepository.save(ReadProgress(bookId, user.id, media.pages.size, true)) } - fun deleteReadProgress(bookId: Long, user: KomgaUser) { + fun deleteReadProgress(bookId: String, user: KomgaUser) { readProgressRepository.delete(bookId, user.id) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt index f106a02ba..456087111 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt @@ -33,7 +33,7 @@ class KomgaUserLifecycle( userRepository.findByEmailIgnoreCase(user.username)?.let { komgaUser -> logger.info { "Changing password for user ${user.username}" } val updatedUser = komgaUser.copy(password = passwordEncoder.encode(newPassword)) - userRepository.save(updatedUser) + userRepository.update(updatedUser) if (expireSessions) expireSessions(updatedUser) @@ -47,7 +47,9 @@ class KomgaUserLifecycle( fun createUser(komgaUser: KomgaUser): KomgaUser { if (userRepository.existsByEmailIgnoreCase(komgaUser.email)) throw UserEmailAlreadyExistsException("A user with the same email already exists: ${komgaUser.email}") - val createdUser = userRepository.save(komgaUser.copy(password = passwordEncoder.encode(komgaUser.password))) + userRepository.insert(komgaUser.copy(password = passwordEncoder.encode(komgaUser.password))) + + val createdUser = userRepository.findByIdOrNull(komgaUser.id)!! logger.info { "User created: $createdUser" } return createdUser } @@ -55,7 +57,7 @@ class KomgaUserLifecycle( fun deleteUser(user: KomgaUser) { logger.info { "Deleting user: $user" } readProgressRepository.deleteByUserId(user.id) - userRepository.delete(user) + userRepository.delete(user.id) expireSessions(user) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt index 483412347..bf2f5958c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt @@ -34,9 +34,10 @@ class LibraryLifecycle( val existing = libraryRepository.findAll() checkLibraryValidity(library, existing) - return libraryRepository.insert(library).also { - taskReceiver.scanLibrary(it.id) - } + libraryRepository.insert(library) + taskReceiver.scanLibrary(library.id) + + return libraryRepository.findById(library.id) } fun updateLibrary(toUpdate: Library) { @@ -70,9 +71,8 @@ class LibraryLifecycle( fun deleteLibrary(library: Library) { logger.info { "Deleting library: $library" } - seriesRepository.findByLibraryId(library.id).forEach { - seriesLifecycle.deleteSeries(it.id) - } + val seriesIds = seriesRepository.findByLibraryId(library.id).map { it.id } + seriesLifecycle.deleteMany(seriesIds) libraryRepository.delete(library.id) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryScanner.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryScanner.kt index 4fd69c8a9..fc6fca7bc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryScanner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryScanner.kt @@ -9,6 +9,7 @@ import org.gotson.komga.domain.persistence.SeriesRepository import org.springframework.stereotype.Service import java.nio.file.Paths import java.time.temporal.ChronoUnit +import kotlin.time.measureTime private val logger = KotlinLogging.logger {} @@ -24,78 +25,81 @@ class LibraryScanner( fun scanRootFolder(library: Library) { logger.info { "Updating library: $library" } - val scannedSeries = - fileSystemScanner.scanRootFolder(Paths.get(library.root.toURI())) - .map { (series, books) -> - series.copy(libraryId = library.id) to books.map { it.copy(libraryId = library.id) } - }.toMap() + measureTime { + val scannedSeries = + fileSystemScanner.scanRootFolder(Paths.get(library.root.toURI())) + .map { (series, books) -> + series.copy(libraryId = library.id) to books.map { it.copy(libraryId = library.id) } + }.toMap() - // delete series that don't exist anymore - if (scannedSeries.isEmpty()) { - logger.info { "Scan returned no series, deleting all existing series" } - seriesRepository.findByLibraryId(library.id).forEach { - seriesLifecycle.deleteSeries(it.id) - } - } else { - scannedSeries.keys.map { it.url }.let { urls -> - seriesRepository.findByLibraryIdAndUrlNotIn(library.id, urls).forEach { - logger.info { "Deleting series not on disk anymore: $it" } - seriesLifecycle.deleteSeries(it.id) + // delete series that don't exist anymore + if (scannedSeries.isEmpty()) { + logger.info { "Scan returned no series, deleting all existing series" } + val seriesIds = seriesRepository.findByLibraryId(library.id).map { it.id } + seriesLifecycle.deleteMany(seriesIds) + } else { + scannedSeries.keys.map { it.url }.let { urls -> + val series = seriesRepository.findByLibraryIdAndUrlNotIn(library.id, urls) + if (series.isNotEmpty()) { + logger.info { "Deleting series not on disk anymore: $series" } + seriesLifecycle.deleteMany(series.map { it.id }) + } } } - } - scannedSeries.forEach { (newSeries, newBooks) -> - val existingSeries = seriesRepository.findByLibraryIdAndUrl(library.id, newSeries.url) + scannedSeries.forEach { (newSeries, newBooks) -> + val existingSeries = seriesRepository.findByLibraryIdAndUrl(library.id, newSeries.url) - // if series does not exist, save it - if (existingSeries == null) { - logger.info { "Adding new series: $newSeries" } - val createdSeries = seriesLifecycle.createSeries(newSeries) - seriesLifecycle.addBooks(createdSeries, newBooks) - seriesLifecycle.sortBooks(createdSeries) - } else { - // if series already exists, update it - if (newSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) { - logger.info { "Series changed on disk, updating: $existingSeries" } - existingSeries.fileLastModified = newSeries.fileLastModified + // if series does not exist, save it + if (existingSeries == null) { + logger.info { "Adding new series: $newSeries" } + val createdSeries = seriesLifecycle.createSeries(newSeries) + seriesLifecycle.addBooks(createdSeries, newBooks) + seriesLifecycle.sortBooks(createdSeries) + } else { + // if series already exists, update it + if (newSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingSeries.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) { + logger.info { "Series changed on disk, updating: $existingSeries" } - seriesRepository.update(existingSeries) + seriesRepository.update(existingSeries.copy(fileLastModified = newSeries.fileLastModified)) - // update list of books with existing entities if they exist - val existingBooks = bookRepository.findBySeriesId(existingSeries.id) + // update list of books with existing entities if they exist + val existingBooks = bookRepository.findBySeriesId(existingSeries.id) - // update existing books - newBooks.forEach { newBook -> - existingBooks.find { it.url == newBook.url }?.let { existingBook -> - if (newBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) { - logger.info { "Book changed on disk, update and reset media status: $existingBook" } - val updatedBook = existingBook.copy( - fileLastModified = newBook.fileLastModified, - fileSize = newBook.fileSize - ) - mediaRepository.findById(existingBook.id).let { - mediaRepository.update(it.copy(status = Media.Status.OUTDATED)) + // update existing books + newBooks.forEach { newBook -> + existingBooks.find { it.url == newBook.url }?.let { existingBook -> + if (newBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS) != existingBook.fileLastModified.truncatedTo(ChronoUnit.MILLIS)) { + logger.info { "Book changed on disk, update and reset media status: $existingBook" } + val updatedBook = existingBook.copy( + fileLastModified = newBook.fileLastModified, + fileSize = newBook.fileSize + ) + mediaRepository.findById(existingBook.id).let { + mediaRepository.update(it.copy(status = Media.Status.OUTDATED)) + } + bookRepository.update(updatedBook) } - bookRepository.update(updatedBook) } } + + // remove books not present anymore + val newBooksUrls = newBooks.map { it.url } + existingBooks + .filterNot { existingBook -> newBooksUrls.contains(existingBook.url) } + .let { books -> bookLifecycle.deleteMany(books.map { it.id }) } + + // add new books + val existingBooksUrls = existingBooks.map { it.url } + val booksToAdd = newBooks.filterNot { newBook -> existingBooksUrls.contains(newBook.url) } + seriesLifecycle.addBooks(existingSeries, booksToAdd) + + // sort all books + seriesLifecycle.sortBooks(existingSeries) } - - // remove books not present anymore - existingBooks - .filterNot { existingBook -> newBooks.map { it.url }.contains(existingBook.url) } - .forEach { bookLifecycle.delete(it.id) } - - // add new books - val booksToAdd = newBooks.filterNot { newBook -> existingBooks.map { it.url }.contains(newBook.url) } - seriesLifecycle.addBooks(existingSeries, booksToAdd) - - // sort all books - seriesLifecycle.sortBooks(existingSeries) } } - } + }.also { logger.info { "Library updated in $it" } } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt index 301d72070..9712ff535 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt @@ -22,7 +22,9 @@ class SeriesCollectionLifecycle( if (collectionRepository.existsByName(collection.name)) throw DuplicateNameException("Collection name already exists") - return collectionRepository.insert(collection) + collectionRepository.insert(collection) + + return collectionRepository.findByIdOrNull(collection.id)!! } fun updateCollection(toUpdate: SeriesCollection) { @@ -35,7 +37,7 @@ class SeriesCollectionLifecycle( collectionRepository.update(toUpdate) } - fun deleteCollection(collectionId: Long) { + fun deleteCollection(collectionId: String) { collectionRepository.delete(collectionId) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt index b4556674a..3042716eb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt @@ -15,7 +15,7 @@ import org.gotson.komga.domain.persistence.SeriesCollectionRepository import org.gotson.komga.domain.persistence.SeriesMetadataRepository import org.gotson.komga.domain.persistence.SeriesRepository import org.springframework.stereotype.Service -import java.util.Comparator +import java.util.* private val logger = KotlinLogging.logger {} private val natSortComparator: Comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() @@ -33,21 +33,23 @@ class SeriesLifecycle( fun sortBooks(series: Series) { val books = bookRepository.findBySeriesId(series.id) + val metadatas = bookMetadataRepository.findByIds(books.map { it.id }) - val sorted = books.sortedWith(compareBy(natSortComparator) { it.name }) - sorted.forEachIndexed { index, book -> - val number = index + 1 - bookRepository.update(book.copy(number = number)) + val sorted = books + .sortedWith(compareBy(natSortComparator) { it.name }) + .map { book -> book to metadatas.first { it.bookId == book.id } } - bookMetadataRepository.findById(book.id).let { metadata -> - val renumbered = metadata.copy( - number = if (!metadata.numberLock) number.toString() else metadata.number, - numberSort = if (!metadata.numberSortLock) number.toFloat() else metadata.numberSort - ) - if (!metadata.numberLock || !metadata.numberSortLock) - bookMetadataRepository.update(renumbered) - } - } + bookRepository.updateMany( + sorted.mapIndexed { index, (book, _) -> book.copy(number = index + 1) } + ) + + sorted.mapIndexedNotNull { index, (_, metadata) -> + if (metadata.numberLock && metadata.numberSortLock) null + else metadata.copy( + number = if (!metadata.numberLock) (index + 1).toString() else metadata.number, + numberSort = if (!metadata.numberSortLock) (index + 1).toFloat() else metadata.numberSort + ) + }.let { bookMetadataRepository.updateMany(it) } } fun addBooks(series: Series, booksToAdd: Collection) { @@ -55,45 +57,57 @@ class SeriesLifecycle( check(it.libraryId == series.libraryId) { "Cannot add book to series if they don't share the same libraryId" } } - booksToAdd.forEach { book -> - val createdBook = bookRepository.insert(book.copy(seriesId = series.id)) + bookRepository.insertMany( + booksToAdd.map { it.copy(seriesId = series.id) } + ) - // create associated media - mediaRepository.insert(Media(bookId = createdBook.id)) + // create associated media + mediaRepository.insertMany(booksToAdd.map { Media(bookId = it.id) }) - // create associated metadata - bookMetadataRepository.insert(BookMetadata( - title = createdBook.name, - number = createdBook.number.toString(), - numberSort = createdBook.number.toFloat(), - bookId = createdBook.id - )) - } + // create associated metadata + booksToAdd.map { + BookMetadata( + title = it.name, + number = it.number.toString(), + numberSort = it.number.toFloat(), + bookId = it.id + ) + }.let { bookMetadataRepository.insertMany(it) } } fun createSeries(series: Series): Series { - val createdSeries = seriesRepository.insert(series) + seriesRepository.insert(series) seriesMetadataRepository.insert( SeriesMetadata( - title = createdSeries.name, - titleSort = StringUtils.stripAccents(createdSeries.name), - seriesId = createdSeries.id + title = series.name, + titleSort = StringUtils.stripAccents(series.name), + seriesId = series.id ) ) - return createdSeries + return seriesRepository.findByIdOrNull(series.id)!! } - fun deleteSeries(seriesId: Long) { + fun deleteOne(seriesId: String) { logger.info { "Delete series id: $seriesId" } - bookRepository.findBySeriesId(seriesId).forEach { - bookLifecycle.delete(it.id) - } + val bookIds = bookRepository.findAllIdBySeriesId(seriesId) + bookLifecycle.deleteMany(bookIds) collectionRepository.removeSeriesFromAll(seriesId) seriesRepository.delete(seriesId) } + + fun deleteMany(seriesIds: Collection) { + logger.info { "Delete series ids: $seriesIds" } + + val bookIds = bookRepository.findAllIdBySeriesIds(seriesIds) + bookLifecycle.deleteMany(bookIds) + + collectionRepository.removeSeriesFromAll(seriesIds) + + seriesRepository.deleteAll(seriesIds) + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt index 89b2705aa..142cd69d5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt @@ -20,20 +20,20 @@ class KomgaProperties { var rememberMe = RememberMe() + var database = Database() + class RememberMe { - @NotBlank + @get:NotBlank var key: String? = null - @Positive + @get:Positive var validity: Int = 1209600 // 2 weeks } - var databaseBackup = DatabaseBackup() + class Database { + @get:NotBlank + var file: String = "" - class DatabaseBackup { - var enabled: Boolean = true - var path: String = "" - var schedule: String = "" - var startup: Boolean = true + var batchSize: Int = 500 } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt new file mode 100644 index 000000000..c12f53f5f --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt @@ -0,0 +1,38 @@ +package org.gotson.komga.infrastructure.datasource + +import com.zaxxer.hikari.HikariDataSource +import org.gotson.komga.infrastructure.configuration.KomgaProperties +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.jdbc.DataSourceBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary +import javax.sql.DataSource + +@Configuration +class DataSourcesConfiguration( + private val komgaProperties: KomgaProperties +) { + + @Bean("sqliteDataSource") + @Primary + fun sqliteDataSource(): DataSource = + (DataSourceBuilder.create() + .apply { + driverClassName("org.sqlite.JDBC") + url("jdbc:sqlite:${komgaProperties.database.file}?foreign_keys=on;") + }.type(HikariDataSource::class.java) + .build() as HikariDataSource) + .apply { maximumPoolSize = 1 } + + @Bean + @Primary + @ConfigurationProperties(prefix = "spring.datasource") + fun h2DataSourceProperties() = DataSourceProperties() + + @Bean("h2DataSource") + fun h2DataSource(): DataSource = + h2DataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource::class.java).build() + +} diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigration.kt new file mode 100644 index 000000000..9a243aefd --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigration.kt @@ -0,0 +1,247 @@ +package org.gotson.komga.infrastructure.datasource + +import mu.KotlinLogging +import org.flywaydb.core.Flyway +import org.flywaydb.core.api.configuration.FluentConfiguration +import org.gotson.komga.infrastructure.configuration.KomgaProperties +import org.springframework.beans.factory.BeanInitializationException +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Profile +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.jdbc.support.JdbcUtils +import org.springframework.jms.config.JmsListenerEndpointRegistry +import org.springframework.stereotype.Component +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.ResultSetMetaData +import java.sql.Types +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import javax.annotation.PostConstruct +import javax.sql.DataSource +import kotlin.time.measureTime + +private val logger = KotlinLogging.logger {} + +@Component +@Profile("!test") +class DatabaseMigration( + @Qualifier("h2DataSource") private val h2DataSource: DataSource, + @Qualifier("sqliteDataSource") private val sqliteDataSource: DataSource, + private val jmsListenerEndpointRegistry: JmsListenerEndpointRegistry, + @Value("\${spring.datasource.url}") private val h2Url: String, + private val komgaProperties: KomgaProperties +) { + + // tables in order of creation, to ensure there is no missing foreign key + private val tables = listOf( + "LIBRARY", + "USER", + "USER_LIBRARY_SHARING", + "SERIES", + "SERIES_METADATA", + "BOOK", + "MEDIA", + "MEDIA_PAGE", + "MEDIA_FILE", + "BOOK_METADATA", + "BOOK_METADATA_AUTHOR", + "READ_PROGRESS", + "COLLECTION", + "COLLECTION_SERIES" + ) + + lateinit var h2MigratedFilePath: Path + lateinit var sqlitePath: Path + + @PostConstruct + fun init() { + try { + logger.info { "Initiating database migration from H2 to SQLite" } + + logger.info { "H2 url: $h2Url" } + var h2Filename = extractH2Path(h2Url)?.plus(".mv.db") + if (h2Filename == null) { + logger.warn { "The H2 URL ($h2Url) does not refer to a file database, skipping migration" } + return + } + + val h2Path = convertHomeDir(h2Filename) + h2Filename = h2Path.toString() + logger.info { "H2 database file: $h2Filename" } + + if (Files.notExists(h2Path)) { + logger.warn { "The H2 database file does not exists: $h2Path, skipping migration" } + return + } + + h2MigratedFilePath = Paths.get("$h2Filename.migrated") + if (Files.exists(h2MigratedFilePath)) { + logger.info { "The H2 database has already been migrated, skipping migration" } + return + } + + h2Backup(h2Filename) + + // make sure H2 database is at the latest migration + flywayMigrateH2() + + sqlitePath = convertHomeDir(komgaProperties.database.file) + // flyway Migrate must perform exactly one migration (target of one) + // if it performs 0, the database has already been migrated and probably has data in it + // it should never perform more than one with a target of 1 migration + if (flywayMigrateSqlite() != 1) + throw BeanInitializationException("The SQLite database ($sqlitePath) is not newly minted") + + logger.info { "Stopping all JMS listeners" } + jmsListenerEndpointRegistry.stop() + + var rows: Int + measureTime { + rows = transferH2DataToSqlite() + }.also { + val insertsPerSecond = rows / it.inSeconds + logger.info { "Migration performed in $it ($rows rows). $insertsPerSecond inserts per second." } + } + + logger.info { "Creating H2 migrated file: $h2MigratedFilePath" } + Files.createFile(h2MigratedFilePath) + + logger.info { "Starting all JMS listeners" } + jmsListenerEndpointRegistry.start() + + logger.info { "Migration finished" } + + } catch (e: Exception) { + logger.error(e) { "Migration failed" } + + if (this::sqlitePath.isInitialized) { + logger.info { "Deleting Sqlite database if exists" } + Files.deleteIfExists(sqlitePath) + } + + if (this::h2MigratedFilePath.isInitialized) { + logger.info { "Deleting H2 migrated file if exists" } + Files.deleteIfExists(h2MigratedFilePath) + } + + throw BeanInitializationException("Migration failed") + } + } + + private fun flywayMigrateSqlite(): Int { + logger.info { "Initialize SQLite database with initial migration: 20200706141854" } + return Flyway(FluentConfiguration() + .dataSource(sqliteDataSource) + .locations("classpath:db/migration/sqlite") + .target("20200706141854") + ).migrate() + } + + private fun flywayMigrateH2(): Int { + logger.info { "Migrating H2 database to the latest migration" } + return Flyway(FluentConfiguration() + .dataSource(h2DataSource) + .locations("classpath:db/migration/h2") + ).migrate() + } + + private fun h2Backup(h2Filename: String) { + val jdbcTemplate = JdbcTemplate(h2DataSource) + val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd.HH-mm-ss")) + val backup = "$h2Filename.backup.$timestamp.zip" + logger.info { "Perform a specific backup of the H2 database to: $backup" } + jdbcTemplate.execute("BACKUP TO '$backup'") + logger.info { "Backup finished" } + } + + private fun transferH2DataToSqlite(): Int { + val maxBatchSize = komgaProperties.database.batchSize + + val sourceConnection = h2DataSource.connection + val destinationConnection = sqliteDataSource.connection + var resultSet: ResultSet? = null + var selectStatement: PreparedStatement? = null + var insertStatement: PreparedStatement? = null + + var totalRows = 0 + + destinationConnection.autoCommit = false + destinationConnection.transactionIsolation = 1 + + try { + tables.forEach { table -> + logger.info { "Migrate table: $table" } + selectStatement = sourceConnection.prepareStatement("select * from $table") + resultSet = selectStatement!!.executeQuery() + insertStatement = destinationConnection.prepareStatement(createInsert(resultSet!!.metaData, table)) + + var batchSize = 0 + var batchCount = 1 + while (resultSet!!.next()) { + for (i in 1..resultSet!!.metaData.columnCount) { + if (resultSet!!.metaData.getColumnType(i) == Types.BLOB) { + val blob = resultSet!!.getBlob(i) + val byteArray = blob?.binaryStream?.readBytes() + insertStatement!!.setObject(i, byteArray) + } else + insertStatement!!.setObject(i, resultSet!!.getObject(i)) + } + insertStatement!!.addBatch() + batchSize++ + totalRows++ + + if (batchSize >= maxBatchSize) { + insertStatement!!.executeBatch() + logger.info { "Insert batch #$batchCount ($batchSize rows)" } + batchSize = 0 + batchCount++ + } + } + insertStatement!!.executeBatch() + logger.info { "Insert batch #$batchCount ($batchSize rows)" } + } + } catch (e: Exception) { + destinationConnection.rollback() + throw e + } finally { + destinationConnection.commit() + JdbcUtils.closeResultSet(resultSet) + JdbcUtils.closeStatement(selectStatement) + JdbcUtils.closeStatement(insertStatement) + JdbcUtils.closeConnection(sourceConnection) + JdbcUtils.closeConnection(destinationConnection) + } + + return totalRows + } + + private fun createInsert(metadata: ResultSetMetaData, table: String): String { + val columns = (1..metadata.columnCount).map { metadata.getColumnName(it) } + val quids = MutableList(columns.size) { "?" } + + return "insert into $table (${columns.joinToString()}) values (${quids.joinToString()})" + } + +} + +val excludeH2Url = listOf(":mem:", ":ssl:", ":tcp:", ":zip:") + +fun extractH2Path(url: String): String? { + if (!url.startsWith("jdbc:h2:")) return null + if (excludeH2Url.any { url.contains(it, ignoreCase = true) }) return null + return url.split(":").last().split(";").first() +} + +fun convertHomeDir(path: String): Path { + val aPath = Paths.get(path) + val components = aPath.toList() + + return if (components.first().toString() == "~") { + Paths.get(System.getProperty("user.home"), *components.drop(1).map { it.toString() }.toTypedArray()) + } else aPath +} diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt deleted file mode 100644 index 101793bf0..000000000 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.gotson.komga.infrastructure.h2 - -import mu.KotlinLogging -import org.gotson.komga.infrastructure.configuration.KomgaProperties -import org.springframework.jdbc.core.JdbcTemplate -import org.springframework.stereotype.Component -import java.nio.file.Files -import java.nio.file.Paths - -private val logger = KotlinLogging.logger {} - -@Component -class DatabaseBackuper( - private val jdbcTemplate: JdbcTemplate, - private val komgaProperties: KomgaProperties -) { - - fun backupDatabase() { - val path = Paths.get(komgaProperties.databaseBackup.path) - - Files.deleteIfExists(path) - - val command = "BACKUP TO '$path'" - - logger.info { "Executing command: $command" } - jdbcTemplate.execute(command) - } -} diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt index d6b01cdf8..2e2623385 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt @@ -3,7 +3,6 @@ package org.gotson.komga.infrastructure.jooq import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookSearch import org.gotson.komga.domain.persistence.BookRepository -import org.gotson.komga.jooq.Sequences import org.gotson.komga.jooq.Tables import org.gotson.komga.jooq.tables.records.BookRecord import org.jooq.Condition @@ -12,6 +11,7 @@ import org.jooq.impl.DSL import org.springframework.stereotype.Component import java.net.URL import java.time.LocalDateTime +import java.time.ZoneId @Component class BookDao( @@ -21,15 +21,17 @@ class BookDao( private val b = Tables.BOOK private val m = Tables.MEDIA private val d = Tables.BOOK_METADATA - private val a = Tables.BOOK_METADATA_AUTHOR - override fun findByIdOrNull(bookId: Long): Book? = + override fun findByIdOrNull(bookId: String): Book? = + findByIdOrNull(dsl, bookId) + + private fun findByIdOrNull(dsl: DSLContext, bookId: String): Book? = dsl.selectFrom(b) .where(b.ID.eq(bookId)) .fetchOneInto(b) ?.toDomain() - override fun findBySeriesId(seriesId: Long): Collection = + override fun findBySeriesId(seriesId: String): Collection = dsl.selectFrom(b) .where(b.SERIES_ID.eq(seriesId)) .fetchInto(b) @@ -51,34 +53,40 @@ class BookDao( .map { it.toDomain() } - override fun getLibraryId(bookId: Long): Long? = + override fun getLibraryId(bookId: String): String? = dsl.select(b.LIBRARY_ID) .from(b) .where(b.ID.eq(bookId)) - .fetchOne(0, Long::class.java) + .fetchOne(0, String::class.java) - override fun findFirstIdInSeries(seriesId: Long): Long? = + override fun findFirstIdInSeries(seriesId: String): String? = dsl.select(b.ID) .from(b) .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) .where(b.SERIES_ID.eq(seriesId)) .orderBy(d.NUMBER_SORT) .limit(1) - .fetchOne(0, Long::class.java) + .fetchOne(0, String::class.java) - override fun findAllIdBySeriesId(seriesId: Long): Collection = + override fun findAllIdBySeriesId(seriesId: String): Collection = dsl.select(b.ID) .from(b) .where(b.SERIES_ID.eq(seriesId)) - .fetch(0, Long::class.java) + .fetch(0, String::class.java) - override fun findAllIdByLibraryId(libraryId: Long): Collection = + override fun findAllIdBySeriesIds(seriesIds: Collection): Collection = + dsl.select(b.ID) + .from(b) + .where(b.SERIES_ID.`in`(seriesIds)) + .fetch(0, String::class.java) + + override fun findAllIdByLibraryId(libraryId: String): Collection = dsl.select(b.ID) .from(b) .where(b.LIBRARY_ID.eq(libraryId)) - .fetch(0, Long::class.java) + .fetch(0, String::class.java) - override fun findAllId(bookSearch: BookSearch): Collection { + override fun findAllId(bookSearch: BookSearch): Collection { val conditions = bookSearch.toCondition() return dsl.select(b.ID) @@ -86,27 +94,58 @@ class BookDao( .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) .where(conditions) - .fetch(0, Long::class.java) + .fetch(0, String::class.java) } - override fun insert(book: Book): Book { - val id = dsl.nextval(Sequences.HIBERNATE_SEQUENCE) - dsl.insertInto(b) - .set(b.ID, id) - .set(b.NAME, book.name) - .set(b.URL, book.url.toString()) - .set(b.NUMBER, book.number) - .set(b.FILE_LAST_MODIFIED, book.fileLastModified) - .set(b.FILE_SIZE, book.fileSize) - .set(b.LIBRARY_ID, book.libraryId) - .set(b.SERIES_ID, book.seriesId) - .execute() + override fun insert(book: Book) { + insertMany(listOf(book)) + } - return findByIdOrNull(id)!! + override fun insertMany(books: Collection) { + if (books.isNotEmpty()) { + dsl.transaction { config -> + config.dsl().batch( + config.dsl().insertInto( + b, + b.ID, + b.NAME, + b.URL, + b.NUMBER, + b.FILE_LAST_MODIFIED, + b.FILE_SIZE, + b.LIBRARY_ID, + b.SERIES_ID + ).values(null as String?, null, null, null, null, null, null, null) + ).also { step -> + books.forEach { + step.bind( + it.id, + it.name, + it.url, + it.number, + it.fileLastModified, + it.fileSize, + it.libraryId, + it.seriesId + ) + } + }.execute() + } + } } override fun update(book: Book) { + update(dsl, book) + } + + override fun updateMany(books: Collection) { + dsl.transaction { config -> + books.map { update(config.dsl(), it) } + } + } + + private fun update(dsl: DSLContext, book: Book) { dsl.update(b) .set(b.NAME, book.name) .set(b.URL, book.url.toString()) @@ -115,24 +154,22 @@ class BookDao( .set(b.FILE_SIZE, book.fileSize) .set(b.LIBRARY_ID, book.libraryId) .set(b.SERIES_ID, book.seriesId) - .set(b.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(b.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(b.ID.eq(book.id)) .execute() } - override fun delete(bookId: Long) { + override fun delete(bookId: String) { dsl.transaction { config -> - with(config.dsl()) - { + with(config.dsl()) { deleteFrom(b).where(b.ID.eq(bookId)).execute() } } } - override fun deleteAll(bookIds: List) { + override fun deleteByBookIds(bookIds: Collection) { dsl.transaction { config -> - with(config.dsl()) - { + with(config.dsl()) { deleteFrom(b).where(b.ID.`in`(bookIds)).execute() } } @@ -140,8 +177,7 @@ class BookDao( override fun deleteAll() { dsl.transaction { config -> - with(config.dsl()) - { + with(config.dsl()) { deleteFrom(b).execute() } } @@ -170,8 +206,8 @@ class BookDao( id = id, libraryId = libraryId, seriesId = seriesId, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate, + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), number = number ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt index fa5e352ff..f0c88ef96 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDao.kt @@ -18,6 +18,7 @@ import org.jooq.DSLContext import org.jooq.Record import org.jooq.ResultQuery import org.jooq.impl.DSL +import org.jooq.impl.DSL.inline import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest @@ -55,7 +56,7 @@ class BookDtoDao( "readProgress.lastModified" to r.LAST_MODIFIED_DATE ) - override fun findAll(search: BookSearchWithReadProgress, userId: Long, pageable: Pageable): Page { + override fun findAll(search: BookSearchWithReadProgress, userId: String, pageable: Pageable): Page { val conditions = search.toCondition() val count = dsl.selectCount() @@ -83,18 +84,18 @@ class BookDtoDao( ) } - override fun findByIdOrNull(bookId: Long, userId: Long): BookDto? = + override fun findByIdOrNull(bookId: String, userId: String): BookDto? = selectBase(userId) .where(b.ID.eq(bookId)) .fetchAndMap() .firstOrNull() - override fun findPreviousInSeries(bookId: Long, userId: Long): BookDto? = findSibling(bookId, userId, next = false) + override fun findPreviousInSeries(bookId: String, userId: String): BookDto? = findSibling(bookId, userId, next = false) - override fun findNextInSeries(bookId: Long, userId: Long): BookDto? = findSibling(bookId, userId, next = true) + override fun findNextInSeries(bookId: String, userId: String): BookDto? = findSibling(bookId, userId, next = true) - override fun findOnDeck(libraryIds: Collection, userId: Long, pageable: Pageable): Page { + override fun findOnDeck(libraryIds: Collection, userId: String, pageable: Pageable): Page { val conditions = if (libraryIds.isEmpty()) DSL.trueCondition() else s.LIBRARY_ID.`in`(libraryIds) val seriesIds = dsl.select(s.ID) @@ -104,11 +105,11 @@ class BookDtoDao( .and(readProgressCondition(userId)) .where(conditions) .groupBy(s.ID) - .having(SeriesDtoDao.countUnread.ge(1.toBigDecimal())) - .and(SeriesDtoDao.countRead.ge(1.toBigDecimal())) - .and(SeriesDtoDao.countInProgress.eq(0.toBigDecimal())) + .having(SeriesDtoDao.countUnread.ge(inline(1.toBigDecimal()))) + .and(SeriesDtoDao.countRead.ge(inline(1.toBigDecimal()))) + .and(SeriesDtoDao.countInProgress.eq(inline(0.toBigDecimal()))) .orderBy(DSL.max(r.LAST_MODIFIED_DATE).desc()) - .fetchInto(Long::class.java) + .fetchInto(String::class.java) val dtos = seriesIds .drop(pageable.pageNumber * pageable.pageSize) @@ -130,15 +131,15 @@ class BookDtoDao( ) } - private fun readProgressCondition(userId: Long): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull) + private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull) - private fun findSibling(bookId: Long, userId: Long, next: Boolean): BookDto? { + private fun findSibling(bookId: String, userId: String, next: Boolean): BookDto? { val record = dsl.select(b.SERIES_ID, d.NUMBER_SORT) .from(b) .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) .where(b.ID.eq(bookId)) .fetchOne() - val seriesId = record.get(0, Long::class.java) + val seriesId = record.get(0, String::class.java) val numberSort = record.get(1, Float::class.java) return selectBase(userId) @@ -150,7 +151,7 @@ class BookDtoDao( .firstOrNull() } - private fun selectBase(userId: Long) = + private fun selectBase(userId: String) = dsl.select( *b.fields(), *mediaFields, @@ -210,9 +211,9 @@ class BookDtoDao( name = name, url = URL(url).toFilePath(), number = number, - created = createdDate.toUTC(), - lastModified = lastModifiedDate.toUTC(), - fileLastModified = fileLastModified.toUTC(), + created = createdDate, + lastModified = lastModifiedDate, + fileLastModified = fileLastModified, sizeBytes = fileSize, media = media, metadata = metadata, @@ -253,7 +254,7 @@ class BookDtoDao( ReadProgressDto( page = page, completed = completed, - created = createdDate.toUTC(), - lastModified = lastModifiedDate.toUTC() + created = createdDate, + lastModified = lastModifiedDate ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDao.kt index f0b1b86c6..46d7699b4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDao.kt @@ -9,6 +9,7 @@ import org.gotson.komga.jooq.tables.records.BookMetadataRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class BookMetadataDao( @@ -20,17 +21,20 @@ class BookMetadataDao( private val groupFields = arrayOf(*d.fields(), *a.fields()) - override fun findById(bookId: Long): BookMetadata = - findOne(bookId).first() + override fun findById(bookId: String): BookMetadata = + find(dsl, listOf(bookId)).first() - override fun findByIdOrNull(bookId: Long): BookMetadata? = - findOne(bookId).firstOrNull() + override fun findByIdOrNull(bookId: String): BookMetadata? = + find(dsl, listOf(bookId)).firstOrNull() - private fun findOne(bookId: Long) = + override fun findByIds(bookIds: Collection): Collection = + find(dsl, bookIds) + + private fun find(dsl: DSLContext, bookIds: Collection) = dsl.select(*groupFields) .from(d) .leftJoin(a).on(d.BOOK_ID.eq(a.BOOK_ID)) - .where(d.BOOK_ID.eq(bookId)) + .where(d.BOOK_ID.`in`(bookIds)) .groupBy(*groupFields) .fetchGroups( { it.into(d) }, { it.into(a) } @@ -46,89 +50,136 @@ class BookMetadataDao( .fetch(a.NAME) } - override fun insert(metadata: BookMetadata): BookMetadata { - dsl.transaction { config -> - with(config.dsl()) - { - insertInto(d) - .set(d.BOOK_ID, metadata.bookId) - .set(d.TITLE, metadata.title) - .set(d.TITLE_LOCK, metadata.titleLock) - .set(d.SUMMARY, metadata.summary) - .set(d.SUMMARY_LOCK, metadata.summaryLock) - .set(d.NUMBER, metadata.number) - .set(d.NUMBER_LOCK, metadata.numberLock) - .set(d.NUMBER_SORT, metadata.numberSort) - .set(d.NUMBER_SORT_LOCK, metadata.numberSortLock) - .set(d.READING_DIRECTION, metadata.readingDirection?.toString()) - .set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock) - .set(d.PUBLISHER, metadata.publisher) - .set(d.PUBLISHER_LOCK, metadata.publisherLock) - .set(d.AGE_RATING, metadata.ageRating) - .set(d.AGE_RATING_LOCK, metadata.ageRatingLock) - .set(d.RELEASE_DATE, metadata.releaseDate) - .set(d.RELEASE_DATE_LOCK, metadata.releaseDateLock) - .set(d.AUTHORS_LOCK, metadata.authorsLock) - .execute() + override fun insert(metadata: BookMetadata) { + insertMany(listOf(metadata)) + } - insertAuthors(this, metadata) + override fun insertMany(metadatas: Collection) { + if (metadatas.isNotEmpty()) { + dsl.transaction { config -> + config.dsl().batch( + config.dsl().insertInto( + d, + d.BOOK_ID, + d.TITLE, + d.TITLE_LOCK, + d.SUMMARY, + d.SUMMARY_LOCK, + d.NUMBER, + d.NUMBER_LOCK, + d.NUMBER_SORT, + d.NUMBER_SORT_LOCK, + d.READING_DIRECTION, + d.READING_DIRECTION_LOCK, + d.PUBLISHER, + d.PUBLISHER_LOCK, + d.AGE_RATING, + d.AGE_RATING_LOCK, + d.RELEASE_DATE, + d.RELEASE_DATE_LOCK, + d.AUTHORS_LOCK + ).values(null as String?, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null) + ).also { step -> + metadatas.forEach { + step.bind( + it.bookId, + it.title, + it.titleLock, + it.summary, + it.summaryLock, + it.number, + it.numberLock, + it.numberSort, + it.numberSortLock, + it.readingDirection?.toString(), + it.readingDirectionLock, + it.publisher, + it.publisherLock, + it.ageRating, + it.ageRatingLock, + it.releaseDate, + it.releaseDateLock, + it.authorsLock + ) + } + }.execute() + + insertAuthors(config.dsl(), metadatas) } } - - return findById(metadata.bookId) } override fun update(metadata: BookMetadata) { dsl.transaction { config -> - with(config.dsl()) - { - update(d) - .set(d.TITLE, metadata.title) - .set(d.TITLE_LOCK, metadata.titleLock) - .set(d.SUMMARY, metadata.summary) - .set(d.SUMMARY_LOCK, metadata.summaryLock) - .set(d.NUMBER, metadata.number) - .set(d.NUMBER_LOCK, metadata.numberLock) - .set(d.NUMBER_SORT, metadata.numberSort) - .set(d.NUMBER_SORT_LOCK, metadata.numberSortLock) - .set(d.READING_DIRECTION, metadata.readingDirection?.toString()) - .set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock) - .set(d.PUBLISHER, metadata.publisher) - .set(d.PUBLISHER_LOCK, metadata.publisherLock) - .set(d.AGE_RATING, metadata.ageRating) - .set(d.AGE_RATING_LOCK, metadata.ageRatingLock) - .set(d.RELEASE_DATE, metadata.releaseDate) - .set(d.RELEASE_DATE_LOCK, metadata.releaseDateLock) - .set(d.AUTHORS_LOCK, metadata.authorsLock) - .set(d.LAST_MODIFIED_DATE, LocalDateTime.now()) - .where(d.BOOK_ID.eq(metadata.bookId)) - .execute() + updateMetadata(config.dsl(), metadata) + } + } - deleteFrom(a) - .where(a.BOOK_ID.eq(metadata.bookId)) - .execute() + override fun updateMany(metadatas: Collection) { + dsl.transaction { config -> + metadatas.forEach { updateMetadata(config.dsl(), it) } + } + } - insertAuthors(this, metadata) + private fun updateMetadata(dsl: DSLContext, metadata: BookMetadata) { + dsl.update(d) + .set(d.TITLE, metadata.title) + .set(d.TITLE_LOCK, metadata.titleLock) + .set(d.SUMMARY, metadata.summary) + .set(d.SUMMARY_LOCK, metadata.summaryLock) + .set(d.NUMBER, metadata.number) + .set(d.NUMBER_LOCK, metadata.numberLock) + .set(d.NUMBER_SORT, metadata.numberSort) + .set(d.NUMBER_SORT_LOCK, metadata.numberSortLock) + .set(d.READING_DIRECTION, metadata.readingDirection?.toString()) + .set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock) + .set(d.PUBLISHER, metadata.publisher) + .set(d.PUBLISHER_LOCK, metadata.publisherLock) + .set(d.AGE_RATING, metadata.ageRating) + .set(d.AGE_RATING_LOCK, metadata.ageRatingLock) + .set(d.RELEASE_DATE, metadata.releaseDate) + .set(d.RELEASE_DATE_LOCK, metadata.releaseDateLock) + .set(d.AUTHORS_LOCK, metadata.authorsLock) + .set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) + .where(d.BOOK_ID.eq(metadata.bookId)) + .execute() + + dsl.deleteFrom(a) + .where(a.BOOK_ID.eq(metadata.bookId)) + .execute() + + insertAuthors(dsl, listOf(metadata)) + } + + private fun insertAuthors(dsl: DSLContext, metadatas: Collection) { + if (metadatas.any { it.authors.isNotEmpty() }) { + dsl.batch( + dsl.insertInto(a, a.BOOK_ID, a.NAME, a.ROLE) + .values(null as String?, null, null) + ).also { step -> + metadatas.forEach { metadata -> + metadata.authors.forEach { + step.bind(metadata.bookId, it.name, it.role) + } + } + }.execute() + } + } + + override fun delete(bookId: String) { + dsl.transaction { config -> + with(config.dsl()) { + deleteFrom(a).where(a.BOOK_ID.eq(bookId)).execute() + deleteFrom(d).where(d.BOOK_ID.eq(bookId)).execute() } } } - private fun insertAuthors(dsl: DSLContext, metadata: BookMetadata) { - metadata.authors.forEach { - dsl.insertInto(a) - .set(a.BOOK_ID, metadata.bookId) - .set(a.NAME, it.name) - .set(a.ROLE, it.role) - .execute() - } - } - - override fun delete(bookId: Long) { + override fun deleteByBookIds(bookIds: Collection) { dsl.transaction { config -> - with(config.dsl()) - { - deleteFrom(a).where(a.BOOK_ID.eq(bookId)).execute() - deleteFrom(d).where(d.BOOK_ID.eq(bookId)).execute() + with(config.dsl()) { + deleteFrom(a).where(a.BOOK_ID.`in`(bookIds)).execute() + deleteFrom(d).where(d.BOOK_ID.`in`(bookIds)).execute() } } } @@ -149,8 +200,8 @@ class BookMetadataDao( bookId = bookId, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate, + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), titleLock = titleLock, summaryLock = summaryLock, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt index 7a8a6998e..cbe884af5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt @@ -2,13 +2,13 @@ package org.gotson.komga.infrastructure.jooq import org.gotson.komga.domain.model.KomgaUser import org.gotson.komga.domain.persistence.KomgaUserRepository -import org.gotson.komga.jooq.Sequences.HIBERNATE_SEQUENCE import org.gotson.komga.jooq.Tables import org.jooq.DSLContext import org.jooq.Record import org.jooq.ResultQuery import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class KomgaUserDao( @@ -24,7 +24,7 @@ class KomgaUserDao( selectBase() .fetchAndMap() - override fun findByIdOrNull(id: Long): KomgaUser? = + override fun findByIdOrNull(id: String): KomgaUser? = selectBase() .where(u.ID.equal(id)) .fetchAndMap() @@ -49,57 +49,70 @@ class KomgaUserDao( sharedLibrariesIds = ulr.mapNotNull { it.libraryId }.toSet(), sharedAllLibraries = ur.sharedAllLibraries, id = ur.id, - createdDate = ur.createdDate, - lastModifiedDate = ur.lastModifiedDate + createdDate = ur.createdDate.toCurrentTimeZone(), + lastModifiedDate = ur.lastModifiedDate.toCurrentTimeZone() ) } - override fun save(user: KomgaUser): KomgaUser { - val id = if (user.id == 0L) dsl.nextval(HIBERNATE_SEQUENCE) else user.id - + override fun insert(user: KomgaUser) { dsl.transaction { config -> with(config.dsl()) { - mergeInto(u) - .using(dsl.selectOne()) - .on(u.ID.eq(id)) - .whenMatchedThenUpdate() + insertInto(u) + .set(u.ID, user.id) .set(u.EMAIL, user.email) .set(u.PASSWORD, user.password) .set(u.ROLE_ADMIN, user.roleAdmin) .set(u.ROLE_FILE_DOWNLOAD, user.roleFileDownload) .set(u.ROLE_PAGE_STREAMING, user.rolePageStreaming) .set(u.SHARED_ALL_LIBRARIES, user.sharedAllLibraries) - .set(u.LAST_MODIFIED_DATE, LocalDateTime.now()) - .whenNotMatchedThenInsert(u.ID, u.EMAIL, u.PASSWORD, u.ROLE_ADMIN, u.ROLE_FILE_DOWNLOAD, u.ROLE_PAGE_STREAMING, u.SHARED_ALL_LIBRARIES) - .values(id, user.email, user.password, user.roleAdmin, user.roleFileDownload, user.rolePageStreaming, user.sharedAllLibraries) - .execute() - - deleteFrom(ul) - .where(ul.USER_ID.eq(id)) .execute() user.sharedLibrariesIds.forEach { insertInto(ul) .columns(ul.USER_ID, ul.LIBRARY_ID) - .values(id, it) + .values(user.id, it) .execute() } } } - - return findByIdOrNull(id)!! } - override fun saveAll(users: Iterable): Collection = - users.map { save(it) } - - override fun delete(user: KomgaUser) { + override fun update(user: KomgaUser) { dsl.transaction { config -> with(config.dsl()) { - deleteFrom(ul).where(ul.USER_ID.equal(user.id)).execute() - deleteFrom(u).where(u.ID.equal(user.id)).execute() + update(u) + .set(u.EMAIL, user.email) + .set(u.PASSWORD, user.password) + .set(u.ROLE_ADMIN, user.roleAdmin) + .set(u.ROLE_FILE_DOWNLOAD, user.roleFileDownload) + .set(u.ROLE_PAGE_STREAMING, user.rolePageStreaming) + .set(u.SHARED_ALL_LIBRARIES, user.sharedAllLibraries) + .set(u.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) + .where(u.ID.eq(user.id)) + .execute() + + deleteFrom(ul) + .where(ul.USER_ID.eq(user.id)) + .execute() + + user.sharedLibrariesIds.forEach { + insertInto(ul) + .columns(ul.USER_ID, ul.LIBRARY_ID) + .values(user.id, it) + .execute() + } + } + } + } + + override fun delete(userId: String) { + dsl.transaction { config -> + with(config.dsl()) + { + deleteFrom(ul).where(ul.USER_ID.equal(userId)).execute() + deleteFrom(u).where(u.ID.equal(userId)).execute() } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt index de1696b52..b95369d97 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDao.kt @@ -2,13 +2,13 @@ package org.gotson.komga.infrastructure.jooq import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.persistence.LibraryRepository -import org.gotson.komga.jooq.Sequences.HIBERNATE_SEQUENCE import org.gotson.komga.jooq.Tables import org.gotson.komga.jooq.tables.records.LibraryRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.net.URL import java.time.LocalDateTime +import java.time.ZoneId @Component class LibraryDao( @@ -18,15 +18,15 @@ class LibraryDao( private val l = Tables.LIBRARY private val ul = Tables.USER_LIBRARY_SHARING - override fun findByIdOrNull(libraryId: Long): Library? = + override fun findByIdOrNull(libraryId: String): Library? = findOne(libraryId) ?.toDomain() - override fun findById(libraryId: Long): Library = + override fun findById(libraryId: String): Library = findOne(libraryId) .toDomain() - private fun findOne(libraryId: Long) = + private fun findOne(libraryId: String) = dsl.selectFrom(l) .where(l.ID.eq(libraryId)) .fetchOneInto(l) @@ -36,13 +36,13 @@ class LibraryDao( .fetchInto(l) .map { it.toDomain() } - override fun findAllById(libraryIds: Collection): Collection = + override fun findAllById(libraryIds: Collection): Collection = dsl.selectFrom(l) .where(l.ID.`in`(libraryIds)) .fetchInto(l) .map { it.toDomain() } - override fun delete(libraryId: Long) { + override fun delete(libraryId: String) { dsl.transaction { config -> with(config.dsl()) { @@ -62,11 +62,9 @@ class LibraryDao( } } - override fun insert(library: Library): Library { - val id = dsl.nextval(HIBERNATE_SEQUENCE) - + override fun insert(library: Library) { dsl.insertInto(l) - .set(l.ID, id) + .set(l.ID, library.id) .set(l.NAME, library.name) .set(l.ROOT, library.root.toString()) .set(l.IMPORT_COMICINFO_BOOK, library.importComicInfoBook) @@ -75,8 +73,6 @@ class LibraryDao( .set(l.IMPORT_EPUB_BOOK, library.importEpubBook) .set(l.IMPORT_EPUB_SERIES, library.importEpubSeries) .execute() - - return findById(id) } override fun update(library: Library) { @@ -88,7 +84,7 @@ class LibraryDao( .set(l.IMPORT_COMICINFO_COLLECTION, library.importComicInfoCollection) .set(l.IMPORT_EPUB_BOOK, library.importEpubBook) .set(l.IMPORT_EPUB_SERIES, library.importEpubSeries) - .set(l.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(l.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(l.ID.eq(library.id)) .execute() } @@ -106,7 +102,7 @@ class LibraryDao( importEpubBook = importEpubBook, importEpubSeries = importEpubSeries, id = id, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone() ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt index 13015e01f..31fa29bfd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt @@ -9,6 +9,7 @@ import org.gotson.komga.jooq.tables.records.MediaRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class MediaDao( @@ -21,7 +22,10 @@ class MediaDao( private val groupFields = arrayOf(*m.fields(), *p.fields()) - override fun findById(bookId: Long): Media = + override fun findById(bookId: String): Media = + find(dsl, bookId) + + private fun find(dsl: DSLContext, bookId: String): Media = dsl.select(*groupFields) .from(m) .leftJoin(p).on(m.BOOK_ID.eq(p.BOOK_ID)) @@ -39,52 +43,92 @@ class MediaDao( mr.toDomain(pr.filterNot { it.bookId == null }.map { it.toDomain() }, files) }.first() - - override fun getThumbnail(bookId: Long): ByteArray? = + override fun getThumbnail(bookId: String): ByteArray? = dsl.select(m.THUMBNAIL) .from(m) .where(m.BOOK_ID.eq(bookId)) .fetchOne(0, ByteArray::class.java) - override fun insert(media: Media): Media { - dsl.transaction { config -> - with(config.dsl()) - { - insertInto(m) - .set(m.BOOK_ID, media.bookId) - .set(m.STATUS, media.status.toString()) - .set(m.MEDIA_TYPE, media.mediaType) - .set(m.THUMBNAIL, media.thumbnail) - .set(m.COMMENT, media.comment) - .set(m.PAGE_COUNT, media.pages.size.toLong()) - .execute() + override fun insert(media: Media) { + insertMany(listOf(media)) + } - insertPages(this, media) - insertFiles(this, media) + override fun insertMany(medias: Collection) { + if (medias.isNotEmpty()) { + dsl.transaction { config -> + config.dsl().batch( + config.dsl().insertInto( + m, + m.BOOK_ID, + m.STATUS, + m.MEDIA_TYPE, + m.THUMBNAIL, + m.COMMENT, + m.PAGE_COUNT + ).values(null as String?, null, null, null, null, null) + ).also { step -> + medias.forEach { + step.bind( + it.bookId, + it.status, + it.mediaType, + it.thumbnail, + it.comment, + it.pages.size + ) + } + }.execute() + + insertPages(config.dsl(), medias) + insertFiles(config.dsl(), medias) } } - - return findById(media.bookId) } - private fun insertPages(dsl: DSLContext, media: Media) { - media.pages.forEachIndexed { index, page -> - dsl.insertInto(p) - .set(p.BOOK_ID, media.bookId) - .set(p.FILE_NAME, page.fileName) - .set(p.MEDIA_TYPE, page.mediaType) - .set(p.NUMBER, index) - .execute() + private fun insertPages(dsl: DSLContext, medias: Collection) { + if (medias.any { it.pages.isNotEmpty() }) { + dsl.batch( + dsl.insertInto( + p, + p.BOOK_ID, + p.FILE_NAME, + p.MEDIA_TYPE, + p.NUMBER + ).values(null as String?, null, null, null) + ).also { + medias.forEach { media -> + media.pages.forEachIndexed { index, page -> + it.bind( + media.bookId, + page.fileName, + page.mediaType, + index + ) + } + } + }.execute() } } - private fun insertFiles(dsl: DSLContext, media: Media) { - media.files.forEach { - dsl.insertInto(f) - .set(f.BOOK_ID, media.bookId) - .set(f.FILE_NAME, it) - .execute() + private fun insertFiles(dsl: DSLContext, medias: Collection) { + if (medias.any { it.files.isNotEmpty() }) { + dsl.batch( + dsl.insertInto( + f, + f.BOOK_ID, + f.FILE_NAME + ).values(null as String?, null) + ).also { step -> + medias.forEach { media -> + media.files.forEach { + step.bind( + media.bookId, + it + ) + } + } + }.execute() } } @@ -97,8 +141,8 @@ class MediaDao( .set(m.MEDIA_TYPE, media.mediaType) .set(m.THUMBNAIL, media.thumbnail) .set(m.COMMENT, media.comment) - .set(m.PAGE_COUNT, media.pages.size.toLong()) - .set(m.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(m.PAGE_COUNT, media.pages.size) + .set(m.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(m.BOOK_ID.eq(media.bookId)) .execute() @@ -110,13 +154,13 @@ class MediaDao( .where(f.BOOK_ID.eq(media.bookId)) .execute() - insertPages(this, media) - insertFiles(this, media) + insertPages(this, listOf(media)) + insertFiles(this, listOf(media)) } } } - override fun delete(bookId: Long) { + override fun delete(bookId: String) { dsl.transaction { config -> with(config.dsl()) { @@ -127,6 +171,17 @@ class MediaDao( } } + override fun deleteByBookIds(bookIds: Collection) { + dsl.transaction { config -> + with(config.dsl()) + { + deleteFrom(p).where(p.BOOK_ID.`in`(bookIds)).execute() + deleteFrom(f).where(f.BOOK_ID.`in`(bookIds)).execute() + deleteFrom(m).where(m.BOOK_ID.`in`(bookIds)).execute() + } + } + } + private fun MediaRecord.toDomain(pages: List, files: List) = Media( status = Media.Status.valueOf(status), @@ -136,8 +191,8 @@ class MediaDao( files = files, comment = comment, bookId = bookId, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone() ) private fun MediaPageRecord.toDomain() = diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDao.kt index 520a6f8df..51737cc62 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDao.kt @@ -7,6 +7,7 @@ import org.gotson.komga.jooq.tables.records.ReadProgressRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class ReadProgressDao( @@ -20,13 +21,13 @@ class ReadProgressDao( .fetchInto(r) .map { it.toDomain() } - override fun findByBookIdAndUserId(bookId: Long, userId: Long): ReadProgress? = + override fun findByBookIdAndUserId(bookId: String, userId: String): ReadProgress? = dsl.selectFrom(r) .where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))) .fetchOneInto(r) ?.toDomain() - override fun findByUserId(userId: Long): Collection = + override fun findByUserId(userId: String): Collection = dsl.selectFrom(r) .where(r.USER_ID.eq(userId)) .fetchInto(r) @@ -34,41 +35,35 @@ class ReadProgressDao( override fun save(readProgress: ReadProgress) { - dsl.mergeInto(r) - .using(dsl.selectOne()) - .on(r.BOOK_ID.eq(readProgress.bookId).and(r.USER_ID.eq(readProgress.userId))) - .whenMatchedThenUpdate() - .set(r.PAGE, readProgress.page) - .set(r.COMPLETED, readProgress.completed) - .set(r.LAST_MODIFIED_DATE, LocalDateTime.now()) - .whenNotMatchedThenInsert() - .set(r.BOOK_ID, readProgress.bookId) - .set(r.USER_ID, readProgress.userId) + dsl.insertInto(r, r.BOOK_ID, r.USER_ID, r.PAGE, r.COMPLETED) + .values(readProgress.bookId, readProgress.userId, readProgress.page, readProgress.completed) + .onDuplicateKeyUpdate() .set(r.PAGE, readProgress.page) .set(r.COMPLETED, readProgress.completed) + .set(r.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .execute() } - override fun delete(bookId: Long, userId: Long) { + override fun delete(bookId: String, userId: String) { dsl.deleteFrom(r) .where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))) .execute() } - override fun deleteByUserId(userId: Long) { + override fun deleteByUserId(userId: String) { dsl.deleteFrom(r) .where(r.USER_ID.eq(userId)) .execute() } - override fun deleteByBookId(bookId: Long) { + override fun deleteByBookId(bookId: String) { dsl.deleteFrom(r) .where(r.BOOK_ID.eq(bookId)) .execute() } - override fun deleteByBookIds(bookIds: Collection) { + override fun deleteByBookIds(bookIds: Collection) { dsl.deleteFrom(r) .where(r.BOOK_ID.`in`(bookIds)) .execute() @@ -85,7 +80,7 @@ class ReadProgressDao( userId = userId, page = page, completed = completed, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone() ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt index 17155360c..29fcd3d87 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt @@ -2,7 +2,6 @@ package org.gotson.komga.infrastructure.jooq import org.gotson.komga.domain.model.SeriesCollection import org.gotson.komga.domain.persistence.SeriesCollectionRepository -import org.gotson.komga.jooq.Sequences import org.gotson.komga.jooq.Tables import org.gotson.komga.jooq.tables.records.CollectionRecord import org.jooq.DSLContext @@ -16,6 +15,7 @@ import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class SeriesCollectionDao( @@ -31,13 +31,13 @@ class SeriesCollectionDao( ) - override fun findByIdOrNull(collectionId: Long): SeriesCollection? = + override fun findByIdOrNull(collectionId: String): SeriesCollection? = selectBase() .where(c.ID.eq(collectionId)) .fetchAndMap(null) .firstOrNull() - override fun findByIdOrNull(collectionId: Long, filterOnLibraryIds: Collection?): SeriesCollection? = + override fun findByIdOrNull(collectionId: String, filterOnLibraryIds: Collection?): SeriesCollection? = selectBase() .where(c.ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } @@ -70,14 +70,14 @@ class SeriesCollectionDao( ) } - override fun findAllByLibraries(belongsToLibraryIds: Collection, filterOnLibraryIds: Collection?, search: String?, pageable: Pageable): Page { + override fun findAllByLibraries(belongsToLibraryIds: Collection, filterOnLibraryIds: Collection?, search: String?, pageable: Pageable): Page { val ids = dsl.selectDistinct(c.ID) .from(c) .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) .where(s.LIBRARY_ID.`in`(belongsToLibraryIds)) .apply { search?.let { and(c.NAME.containsIgnoreCase(it)) } } - .fetch(0, Long::class.java) + .fetch(0, String::class.java) val count = ids.size @@ -100,12 +100,12 @@ class SeriesCollectionDao( ) } - override fun findAllBySeries(containsSeriesId: Long, filterOnLibraryIds: Collection?): Collection { + override fun findAllBySeries(containsSeriesId: String, filterOnLibraryIds: Collection?): Collection { val ids = dsl.select(c.ID) .from(c) .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) .where(cs.SERIES_ID.eq(containsSeriesId)) - .fetch(0, Long::class.java) + .fetch(0, String::class.java) return selectBase() .where(c.ID.`in`(ids)) @@ -125,7 +125,7 @@ class SeriesCollectionDao( .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) - private fun ResultQuery.fetchAndMap(filterOnLibraryIds: Collection?): List = + private fun ResultQuery.fetchAndMap(filterOnLibraryIds: Collection?): List = fetchInto(c) .map { cr -> val seriesIds = dsl.select(*cs.fields()) @@ -139,24 +139,21 @@ class SeriesCollectionDao( cr.toDomain(seriesIds) } - override fun insert(collection: SeriesCollection): SeriesCollection { - val id = dsl.nextval(Sequences.HIBERNATE_SEQUENCE) - val insert = collection.copy(id = id) + override fun insert(collection: SeriesCollection) { + dsl.transaction { config -> + config.dsl().insertInto(c) + .set(c.ID, collection.id) + .set(c.NAME, collection.name) + .set(c.ORDERED, collection.ordered) + .set(c.SERIES_COUNT, collection.seriesIds.size) + .execute() - dsl.insertInto(c) - .set(c.ID, insert.id) - .set(c.NAME, insert.name) - .set(c.ORDERED, insert.ordered) - .set(c.SERIES_COUNT, collection.seriesIds.size) - .execute() - - insertSeries(insert) - - return findByIdOrNull(id)!! + insertSeries(config.dsl(), collection) + } } - private fun insertSeries(collection: SeriesCollection) { + private fun insertSeries(dsl: DSLContext, collection: SeriesCollection) { collection.seriesIds.forEachIndexed { index, id -> dsl.insertInto(cs) .set(cs.COLLECTION_ID, collection.id) @@ -168,30 +165,35 @@ class SeriesCollectionDao( override fun update(collection: SeriesCollection) { dsl.transaction { config -> - with(config.dsl()) - { + with(config.dsl()) { update(c) .set(c.NAME, collection.name) .set(c.ORDERED, collection.ordered) .set(c.SERIES_COUNT, collection.seriesIds.size) - .set(c.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(c.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(c.ID.eq(collection.id)) .execute() deleteFrom(cs).where(cs.COLLECTION_ID.eq(collection.id)).execute() - insertSeries(collection) + insertSeries(config.dsl(), collection) } } } - override fun removeSeriesFromAll(seriesId: Long) { + override fun removeSeriesFromAll(seriesId: String) { dsl.deleteFrom(cs) .where(cs.SERIES_ID.eq(seriesId)) .execute() } - override fun delete(collectionId: Long) { + override fun removeSeriesFromAll(seriesIds: Collection) { + dsl.deleteFrom(cs) + .where(cs.SERIES_ID.`in`(seriesIds)) + .execute() + } + + override fun delete(collectionId: String) { dsl.transaction { config -> with(config.dsl()) { @@ -218,14 +220,14 @@ class SeriesCollectionDao( ) - private fun CollectionRecord.toDomain(seriesIds: List) = + private fun CollectionRecord.toDomain(seriesIds: List) = SeriesCollection( name = name, ordered = ordered, seriesIds = seriesIds, id = id, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate, + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), filtered = seriesCount != seriesIds.size ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDao.kt index 6d31d2411..c189ce7c1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDao.kt @@ -3,7 +3,6 @@ package org.gotson.komga.infrastructure.jooq import org.gotson.komga.domain.model.Series import org.gotson.komga.domain.model.SeriesSearch import org.gotson.komga.domain.persistence.SeriesRepository -import org.gotson.komga.jooq.Sequences import org.gotson.komga.jooq.Tables import org.gotson.komga.jooq.tables.records.SeriesRecord import org.jooq.Condition @@ -12,6 +11,7 @@ import org.jooq.impl.DSL import org.springframework.stereotype.Component import java.net.URL import java.time.LocalDateTime +import java.time.ZoneId @Component class SeriesDao( @@ -20,7 +20,6 @@ class SeriesDao( private val s = Tables.SERIES private val d = Tables.SERIES_METADATA - private val b = Tables.BOOK private val cs = Tables.COLLECTION_SERIES @@ -29,36 +28,36 @@ class SeriesDao( .fetchInto(s) .map { it.toDomain() } - override fun findByIdOrNull(seriesId: Long): Series? = + override fun findByIdOrNull(seriesId: String): Series? = dsl.selectFrom(s) .where(s.ID.eq(seriesId)) .fetchOneInto(s) ?.toDomain() - override fun findByLibraryId(libraryId: Long): List = + override fun findByLibraryId(libraryId: String): List = dsl.selectFrom(s) .where(s.LIBRARY_ID.eq(libraryId)) .fetchInto(s) .map { it.toDomain() } - override fun findByLibraryIdAndUrlNotIn(libraryId: Long, urls: Collection): List = + override fun findByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): List = dsl.selectFrom(s) .where(s.LIBRARY_ID.eq(libraryId).and(s.URL.notIn(urls.map { it.toString() }))) .fetchInto(s) .map { it.toDomain() } - override fun findByLibraryIdAndUrl(libraryId: Long, url: URL): Series? = + override fun findByLibraryIdAndUrl(libraryId: String, url: URL): Series? = dsl.selectFrom(s) .where(s.LIBRARY_ID.eq(libraryId).and(s.URL.eq(url.toString()))) .fetchOneInto(s) ?.toDomain() - override fun getLibraryId(seriesId: Long): Long? = + override fun getLibraryId(seriesId: String): String? = dsl.select(s.LIBRARY_ID) .from(s) .where(s.ID.eq(seriesId)) - .fetchOne(0, Long::class.java) + .fetchOne(0, String::class.java) override fun findAll(search: SeriesSearch): Collection { @@ -74,18 +73,14 @@ class SeriesDao( } - override fun insert(series: Series): Series { - val id = dsl.nextval(Sequences.HIBERNATE_SEQUENCE) - + override fun insert(series: Series) { dsl.insertInto(s) - .set(s.ID, id) + .set(s.ID, series.id) .set(s.NAME, series.name) .set(s.URL, series.url.toString()) .set(s.FILE_LAST_MODIFIED, series.fileLastModified) .set(s.LIBRARY_ID, series.libraryId) .execute() - - return findByIdOrNull(id)!! } override fun update(series: Series) { @@ -94,12 +89,12 @@ class SeriesDao( .set(s.URL, series.url.toString()) .set(s.FILE_LAST_MODIFIED, series.fileLastModified) .set(s.LIBRARY_ID, series.libraryId) - .set(s.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(s.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(s.ID.eq(series.id)) .execute() } - override fun delete(seriesId: Long) { + override fun delete(seriesId: String) { dsl.transaction { config -> with(config.dsl()) { @@ -119,7 +114,7 @@ class SeriesDao( } } - override fun deleteAll(seriesIds: Collection) { + override fun deleteAll(seriesIds: Collection) { dsl.transaction { config -> with(config.dsl()) { @@ -150,7 +145,7 @@ class SeriesDao( fileLastModified = fileLastModified, id = id, libraryId = libraryId, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone() ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt index 8f230f11f..e3b138c7a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt @@ -4,7 +4,6 @@ import org.gotson.komga.domain.model.ReadStatus import org.gotson.komga.domain.model.SeriesSearchWithReadProgress import org.gotson.komga.interfaces.rest.dto.SeriesDto import org.gotson.komga.interfaces.rest.dto.SeriesMetadataDto -import org.gotson.komga.interfaces.rest.dto.toUTC import org.gotson.komga.interfaces.rest.persistence.SeriesDtoRepository import org.gotson.komga.jooq.Tables import org.gotson.komga.jooq.tables.records.SeriesMetadataRecord @@ -16,6 +15,7 @@ import org.jooq.Record import org.jooq.ResultQuery import org.jooq.SelectOnConditionStep import org.jooq.impl.DSL +import org.jooq.impl.DSL.inline import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest @@ -62,7 +62,7 @@ class SeriesDtoDao( "collection.number" to cs.NUMBER ) - override fun findAll(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page { + override fun findAll(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable): Page { val conditions = search.toCondition() val having = search.readStatus?.toCondition() ?: DSL.trueCondition() @@ -70,14 +70,14 @@ class SeriesDtoDao( return findAll(conditions, having, userId, pageable) } - override fun findByCollectionId(collectionId: Long, userId: Long, pageable: Pageable): Page { + override fun findByCollectionId(collectionId: String, userId: String, pageable: Pageable): Page { val conditions = cs.COLLECTION_ID.eq(collectionId) val having = DSL.trueCondition() return findAll(conditions, having, userId, pageable, true) } - override fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page { + override fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable): Page { val conditions = search.toCondition() .and(s.CREATED_DATE.ne(s.LAST_MODIFIED_DATE)) @@ -86,7 +86,7 @@ class SeriesDtoDao( return findAll(conditions, having, userId, pageable) } - override fun findByIdOrNull(seriesId: Long, userId: Long): SeriesDto? = + override fun findByIdOrNull(seriesId: String, userId: String): SeriesDto? = selectBase(userId) .where(s.ID.eq(seriesId)) .groupBy(*groupFields) @@ -94,7 +94,7 @@ class SeriesDtoDao( .firstOrNull() - private fun selectBase(userId: Long, selectCollectionNumber: Boolean = false): SelectOnConditionStep = + private fun selectBase(userId: String, selectCollectionNumber: Boolean = false): SelectOnConditionStep = dsl.selectDistinct(*groupFields) .select(DSL.countDistinct(b.ID).`as`(BOOKS_COUNT)) .apply { if (selectCollectionNumber) select(cs.NUMBER) } @@ -105,7 +105,7 @@ class SeriesDtoDao( .and(readProgressCondition(userId)) .leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) - private fun findAll(conditions: Condition, having: Condition, userId: Long, pageable: Pageable, selectCollectionNumber: Boolean = false): Page { + private fun findAll(conditions: Condition, having: Condition, userId: String, pageable: Pageable, selectCollectionNumber: Boolean = false): Page { val count = dsl.selectDistinct(s.ID) .from(s) .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) @@ -138,9 +138,9 @@ class SeriesDtoDao( ) } - private fun readProgressCondition(userId: Long): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull) + private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull) - private fun ResultQuery.fetchAndMap(userId: Long) = + private fun ResultQuery.fetchAndMap(userId: String) = fetch() .map { rec -> val sr = rec.into(s) @@ -178,9 +178,9 @@ class SeriesDtoDao( private fun Collection.toCondition(): Condition = map { when (it) { - ReadStatus.UNREAD -> countUnread.ge(1.toBigDecimal()) - ReadStatus.READ -> countRead.ge(1.toBigDecimal()) - ReadStatus.IN_PROGRESS -> countInProgress.ge(1.toBigDecimal()) + ReadStatus.UNREAD -> countUnread.ge(inline(1.toBigDecimal())) + ReadStatus.READ -> countRead.ge(inline(1.toBigDecimal())) + ReadStatus.IN_PROGRESS -> countInProgress.ge(inline(1.toBigDecimal())) } }.reduce { acc, condition -> acc.or(condition) } @@ -190,9 +190,9 @@ class SeriesDtoDao( libraryId = libraryId, name = name, url = URL(url).toFilePath(), - created = createdDate.toUTC(), - lastModified = lastModifiedDate.toUTC(), - fileLastModified = fileLastModified.toUTC(), + created = createdDate, + lastModified = lastModifiedDate, + fileLastModified = fileLastModified, booksCount = booksCount, booksReadCount = booksReadCount, booksUnreadCount = booksUnreadCount, @@ -204,8 +204,8 @@ class SeriesDtoDao( SeriesMetadataDto( status = status, statusLock = statusLock, - created = createdDate.toUTC(), - lastModified = lastModifiedDate.toUTC(), + created = createdDate, + lastModified = lastModifiedDate, title = title, titleLock = titleLock, titleSort = titleSort, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt index 163f14362..691be1ebc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt @@ -7,6 +7,7 @@ import org.gotson.komga.jooq.tables.records.SeriesMetadataRecord import org.jooq.DSLContext import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.time.ZoneId @Component class SeriesMetadataDao( @@ -15,13 +16,13 @@ class SeriesMetadataDao( private val d = Tables.SERIES_METADATA - override fun findById(seriesId: Long): SeriesMetadata = + override fun findById(seriesId: String): SeriesMetadata = findOne(seriesId).toDomain() - override fun findByIdOrNull(seriesId: Long): SeriesMetadata? = + override fun findByIdOrNull(seriesId: String): SeriesMetadata? = findOne(seriesId)?.toDomain() - private fun findOne(seriesId: Long) = + private fun findOne(seriesId: String) = dsl.selectFrom(d) .where(d.SERIES_ID.eq(seriesId)) .fetchOneInto(d) @@ -48,12 +49,12 @@ class SeriesMetadataDao( .set(d.STATUS_LOCK, metadata.statusLock) .set(d.TITLE_LOCK, metadata.titleLock) .set(d.TITLE_SORT_LOCK, metadata.titleSortLock) - .set(d.LAST_MODIFIED_DATE, LocalDateTime.now()) + .set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(d.SERIES_ID.eq(metadata.seriesId)) .execute() } - override fun delete(seriesId: Long) { + override fun delete(seriesId: String) { dsl.deleteFrom(d) .where(d.SERIES_ID.eq(seriesId)) .execute() @@ -71,7 +72,7 @@ class SeriesMetadataDao( statusLock = statusLock, titleLock = titleLock, titleSortLock = titleSortLock, - createdDate = createdDate, - lastModifiedDate = lastModifiedDate + createdDate = createdDate.toCurrentTimeZone(), + lastModifiedDate = lastModifiedDate.toCurrentTimeZone() ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt index e27f58824..ddd180989 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt @@ -15,3 +15,6 @@ fun Sort.toOrderBy(sorts: Map>): List> val f = sorts[it.property] if (it.isAscending) f?.asc() else f?.desc() } + +fun LocalDateTime.toCurrentTimeZone(): LocalDateTime = + this.atZone(ZoneId.of("Z")).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime() diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/opds/OpdsController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/opds/OpdsController.kt index 7cf708479..0e3821124 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/opds/OpdsController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/opds/OpdsController.kt @@ -249,7 +249,7 @@ class OpdsController( fun getOneSeries( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestHeader(name = HttpHeaders.USER_AGENT, required = false, defaultValue = "") userAgent: String, - @PathVariable id: Long + @PathVariable id: String ): OpdsFeed = seriesRepository.findByIdOrNull(id)?.let { series -> if (!principal.user.canAccessSeries(series)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -281,7 +281,7 @@ class OpdsController( @GetMapping("libraries/{id}") fun getOneLibrary( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable id: Long + @PathVariable id: String ): OpdsFeed = libraryRepository.findByIdOrNull(id)?.let { library -> if (!principal.user.canAccessLibrary(library)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -309,7 +309,7 @@ class OpdsController( @GetMapping("collections/{id}") fun getOneCollection( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable id: Long + @PathVariable id: String ): OpdsFeed { return collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { collection -> val series = collection.seriesIds.mapNotNull { seriesRepository.findByIdOrNull(it) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt index 1ee60cee8..7208273fb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/BookController.kt @@ -79,7 +79,7 @@ class BookController( fun getAllBooks( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "search", required = false) searchTerm: String?, - @RequestParam(name = "library_id", required = false) libraryIds: List?, + @RequestParam(name = "library_id", required = false) libraryIds: List?, @RequestParam(name = "media_status", required = false) mediaStatus: List?, @RequestParam(name = "read_status", required = false) readStatus: List?, @Parameter(hidden = true) page: Pageable @@ -132,7 +132,7 @@ class BookController( @AuthenticationPrincipal principal: KomgaPrincipal, @Parameter(hidden = true) page: Pageable ): Page { - val libraryIds = if (principal.user.sharedAllLibraries) emptyList() else principal.user.sharedLibrariesIds + val libraryIds = if (principal.user.sharedAllLibraries) emptySet() else principal.user.sharedLibrariesIds return bookDtoRepository.findOnDeck( libraryIds, @@ -145,7 +145,7 @@ class BookController( @GetMapping("api/v1/books/{bookId}") fun getOneBook( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): BookDto = bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let { if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -155,7 +155,7 @@ class BookController( @GetMapping("api/v1/books/{bookId}/previous") fun getBookSiblingPrevious( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): BookDto { bookRepository.getLibraryId(bookId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -169,7 +169,7 @@ class BookController( @GetMapping("api/v1/books/{bookId}/next") fun getBookSiblingNext( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): BookDto { bookRepository.getLibraryId(bookId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -188,7 +188,7 @@ class BookController( ], produces = [MediaType.IMAGE_JPEG_VALUE]) fun getBookThumbnail( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): ResponseEntity { bookRepository.getLibraryId(bookId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -210,7 +210,7 @@ class BookController( @PreAuthorize("hasRole('$ROLE_FILE_DOWNLOAD')") fun getBookFile( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): ResponseEntity = bookRepository.findByIdOrNull(bookId)?.let { book -> if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -237,9 +237,9 @@ class BookController( @GetMapping("api/v1/books/{bookId}/pages") fun getBookPages( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable bookId: Long + @PathVariable bookId: String ): List = - bookRepository.findByIdOrNull((bookId))?.let { book -> + bookRepository.findByIdOrNull(bookId)?.let { book -> if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN) val media = mediaRepository.findById(book.id) @@ -264,7 +264,7 @@ class BookController( fun getBookPage( @AuthenticationPrincipal principal: KomgaPrincipal, request: WebRequest, - @PathVariable bookId: Long, + @PathVariable bookId: String, @PathVariable pageNumber: Int, @Parameter(description = "Convert the image to the provided format.", schema = Schema(allowableValues = ["jpeg", "png"])) @RequestParam(value = "convert", required = false) convertTo: String?, @@ -316,10 +316,10 @@ class BookController( fun getBookPageThumbnail( @AuthenticationPrincipal principal: KomgaPrincipal, request: WebRequest, - @PathVariable bookId: Long, + @PathVariable bookId: String, @PathVariable pageNumber: Int ): ResponseEntity = - bookRepository.findByIdOrNull((bookId))?.let { book -> + bookRepository.findByIdOrNull(bookId)?.let { book -> val media = mediaRepository.findById(bookId) if (request.checkNotModified(getBookLastModified(media))) { return@let ResponseEntity @@ -350,7 +350,7 @@ class BookController( @PostMapping("api/v1/books/{bookId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable bookId: Long) { + fun analyze(@PathVariable bookId: String) { bookRepository.findByIdOrNull(bookId)?.let { book -> taskReceiver.analyzeBook(book) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -359,7 +359,7 @@ class BookController( @PostMapping("api/v1/books/{bookId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable bookId: Long) { + fun refreshMetadata(@PathVariable bookId: String) { bookRepository.findByIdOrNull(bookId)?.let { book -> taskReceiver.refreshBookMetadata(book) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -369,7 +369,7 @@ class BookController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) fun updateMetadata( - @PathVariable bookId: Long, + @PathVariable bookId: String, @Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.") @Valid @RequestBody newMetadata: BookMetadataUpdateDto, @AuthenticationPrincipal principal: KomgaPrincipal @@ -405,7 +405,7 @@ class BookController( @PatchMapping("api/v1/books/{bookId}/read-progress") @ResponseStatus(HttpStatus.NO_CONTENT) fun markReadProgress( - @PathVariable bookId: Long, + @PathVariable bookId: String, @Parameter(description = "page can be omitted if completed is set to true. completed can be omitted, and will be set accordingly depending on the page passed and the total number of pages in the book.") @Valid @RequestBody readProgress: ReadProgressUpdateDto, @AuthenticationPrincipal principal: KomgaPrincipal @@ -427,7 +427,7 @@ class BookController( @DeleteMapping("api/v1/books/{bookId}/read-progress") @ResponseStatus(HttpStatus.NO_CONTENT) fun deleteReadProgress( - @PathVariable bookId: Long, + @PathVariable bookId: String, @AuthenticationPrincipal principal: KomgaPrincipal ) { bookRepository.findByIdOrNull(bookId)?.let { book -> diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt index ee7b36657..91c79c520 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/LibraryController.kt @@ -52,12 +52,12 @@ class LibraryController( libraryRepository.findAllById(principal.user.sharedLibrariesIds) }.sortedBy { it.name.toLowerCase() }.map { it.toDto(includeRoot = principal.user.roleAdmin) } - @GetMapping("{id}") + @GetMapping("{libraryId}") fun getOne( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable id: Long + @PathVariable libraryId: String ): LibraryDto = - libraryRepository.findByIdOrNull(id)?.let { + libraryRepository.findByIdOrNull(libraryId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) it.toDto(includeRoot = principal.user.roleAdmin) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -91,16 +91,16 @@ class LibraryController( } } - @PutMapping("/{id}") + @PutMapping("/{libraryId}") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) fun updateOne( - @PathVariable id: Long, + @PathVariable libraryId: String, @Valid @RequestBody library: LibraryUpdateDto ) { - libraryRepository.findByIdOrNull(id)?.let { + libraryRepository.findByIdOrNull(libraryId)?.let { val toUpdate = Library( - id = id, + id = libraryId, name = library.name, root = filePathToUrl(library.root), importComicInfoBook = library.importComicInfoBook, @@ -113,11 +113,11 @@ class LibraryController( } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } - @DeleteMapping("/{id}") + @DeleteMapping("/{libraryId}") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) - fun deleteOne(@PathVariable id: Long) { - libraryRepository.findByIdOrNull(id)?.let { + fun deleteOne(@PathVariable libraryId: String) { + libraryRepository.findByIdOrNull(libraryId)?.let { libraryLifecycle.deleteLibrary(it) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } @@ -125,7 +125,7 @@ class LibraryController( @PostMapping("{libraryId}/scan") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun scan(@PathVariable libraryId: Long) { + fun scan(@PathVariable libraryId: String) { libraryRepository.findByIdOrNull(libraryId)?.let { library -> taskReceiver.scanLibrary(library.id) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -134,7 +134,7 @@ class LibraryController( @PostMapping("{libraryId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable libraryId: Long) { + fun analyze(@PathVariable libraryId: String) { bookRepository.findAllIdByLibraryId(libraryId).forEach { taskReceiver.analyzeBook(it) } @@ -143,7 +143,7 @@ class LibraryController( @PostMapping("{libraryId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable libraryId: Long) { + fun refreshMetadata(@PathVariable libraryId: String) { bookRepository.findAllIdByLibraryId(libraryId).forEach { taskReceiver.refreshBookMetadata(it) } @@ -161,7 +161,7 @@ data class LibraryCreationDto( ) data class LibraryDto( - val id: Long, + val id: String, val name: String, val root: String, val importComicInfoBook: Boolean, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt index c067094df..1e3d83a12 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt @@ -62,7 +62,7 @@ class SeriesCollectionController( fun getAll( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "search", required = false) searchTerm: String?, - @RequestParam(name = "library_id", required = false) libraryIds: List?, + @RequestParam(name = "library_id", required = false) libraryIds: List?, @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable ): Page { @@ -85,7 +85,7 @@ class SeriesCollectionController( @GetMapping("{id}") fun getOne( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable id: Long + @PathVariable id: String ): CollectionDto = collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null)) ?.toDto() @@ -95,10 +95,10 @@ class SeriesCollectionController( @GetMapping(value = ["{id}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE]) fun getCollectionThumbnail( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable id: Long + @PathVariable id: String ): ResponseEntity { collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { - val ids = with(mutableListOf()) { + val ids = with(mutableListOf()) { while (size < 4) { this += it.seriesIds.take(4) } @@ -133,7 +133,7 @@ class SeriesCollectionController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) fun updateOne( - @PathVariable id: Long, + @PathVariable id: String, @Valid @RequestBody collection: CollectionUpdateDto ) { collectionRepository.findByIdOrNull(id)?.let { existing -> @@ -154,7 +154,7 @@ class SeriesCollectionController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) fun deleteOne( - @PathVariable id: Long + @PathVariable id: String ) { collectionRepository.findByIdOrNull(id)?.let { collectionLifecycle.deleteCollection(it.id) @@ -164,7 +164,7 @@ class SeriesCollectionController( @PageableWithoutSortAsQueryParam @GetMapping("{id}/series") fun getSeriesForCollection( - @PathVariable id: Long, + @PathVariable id: String, @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt index 1963a9ade..52e4d95e3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt @@ -73,7 +73,7 @@ class SeriesController( fun getAllSeries( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "search", required = false) searchTerm: String?, - @RequestParam(name = "library_id", required = false) libraryIds: List?, + @RequestParam(name = "library_id", required = false) libraryIds: List?, @RequestParam(name = "collection_id", required = false) collectionIds: List?, @RequestParam(name = "status", required = false) metadataStatus: List?, @RequestParam(name = "read_status", required = false) readStatus: List?, @@ -182,7 +182,7 @@ class SeriesController( @GetMapping("{seriesId}") fun getOneSeries( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable(name = "seriesId") id: Long + @PathVariable(name = "seriesId") id: String ): SeriesDto = seriesDtoRepository.findByIdOrNull(id, principal.user.id)?.let { if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -193,7 +193,7 @@ class SeriesController( @GetMapping(value = ["{seriesId}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE]) fun getSeriesThumbnail( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable(name = "seriesId") seriesId: Long + @PathVariable(name = "seriesId") seriesId: String ): ResponseEntity { seriesRepository.getLibraryId(seriesId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -208,7 +208,7 @@ class SeriesController( @GetMapping("{seriesId}/books") fun getAllBooksBySeries( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable(name = "seriesId") seriesId: Long, + @PathVariable(name = "seriesId") seriesId: String, @RequestParam(name = "media_status", required = false) mediaStatus: List?, @RequestParam(name = "read_status", required = false) readStatus: List?, @Parameter(hidden = true) page: Pageable @@ -238,7 +238,7 @@ class SeriesController( @GetMapping("{seriesId}/collections") fun getAllCollectionsBySeries( @AuthenticationPrincipal principal: KomgaPrincipal, - @PathVariable(name = "seriesId") seriesId: Long + @PathVariable(name = "seriesId") seriesId: String ): List { seriesRepository.getLibraryId(seriesId)?.let { if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) @@ -251,7 +251,7 @@ class SeriesController( @PostMapping("{seriesId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable seriesId: Long) { + fun analyze(@PathVariable seriesId: String) { bookRepository.findAllIdBySeriesId(seriesId).forEach { taskReceiver.analyzeBook(it) } @@ -260,7 +260,7 @@ class SeriesController( @PostMapping("{seriesId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable seriesId: Long) { + fun refreshMetadata(@PathVariable seriesId: String) { bookRepository.findAllIdBySeriesId(seriesId).forEach { taskReceiver.refreshBookMetadata(it) } @@ -270,7 +270,7 @@ class SeriesController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) fun updateMetadata( - @PathVariable seriesId: Long, + @PathVariable seriesId: String, @Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.") @Valid @RequestBody newMetadata: SeriesMetadataUpdateDto, @AuthenticationPrincipal principal: KomgaPrincipal @@ -292,7 +292,7 @@ class SeriesController( @PostMapping("{seriesId}/read-progress") @ResponseStatus(HttpStatus.NO_CONTENT) fun markAsRead( - @PathVariable seriesId: Long, + @PathVariable seriesId: String, @AuthenticationPrincipal principal: KomgaPrincipal ) { seriesRepository.getLibraryId(seriesId)?.let { @@ -307,7 +307,7 @@ class SeriesController( @DeleteMapping("{seriesId}/read-progress") @ResponseStatus(HttpStatus.NO_CONTENT) fun markAsUnread( - @PathVariable seriesId: Long, + @PathVariable seriesId: String, @AuthenticationPrincipal principal: KomgaPrincipal ) { seriesRepository.getLibraryId(seriesId)?.let { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/UserController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/UserController.kt index 82f32d679..d93d37684 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/UserController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/UserController.kt @@ -80,7 +80,7 @@ class UserController( @ResponseStatus(HttpStatus.NO_CONTENT) @PreAuthorize("hasRole('$ROLE_ADMIN') and #principal.user.id != #id") fun delete( - @PathVariable id: Long, + @PathVariable id: String, @AuthenticationPrincipal principal: KomgaPrincipal ) { userRepository.findByIdOrNull(id)?.let { @@ -92,7 +92,7 @@ class UserController( @ResponseStatus(HttpStatus.NO_CONTENT) @PreAuthorize("hasRole('$ROLE_ADMIN') and #principal.user.id != #id") fun updateUserRoles( - @PathVariable id: Long, + @PathVariable id: String, @Valid @RequestBody patch: RolesUpdateDto, @AuthenticationPrincipal principal: KomgaPrincipal ) { @@ -102,9 +102,8 @@ class UserController( roleFileDownload = patch.roles.contains(ROLE_FILE_DOWNLOAD), rolePageStreaming = patch.roles.contains(ROLE_PAGE_STREAMING) ) - userRepository.save(updatedUser).also { - logger.info { "Updated user roles: $it" } - } + userRepository.update(updatedUser) + logger.info { "Updated user roles: $updatedUser" } } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } @@ -112,7 +111,7 @@ class UserController( @ResponseStatus(HttpStatus.NO_CONTENT) @PreAuthorize("hasRole('$ROLE_ADMIN')") fun updateSharesLibraries( - @PathVariable id: Long, + @PathVariable id: String, @Valid @RequestBody sharedLibrariesUpdateDto: SharedLibrariesUpdateDto ) { userRepository.findByIdOrNull(id)?.let { user -> @@ -123,9 +122,8 @@ class UserController( .map { it.id } .toSet() ) - userRepository.save(updatedUser).also { - logger.info { "Updated user shared libraries: $it" } - } + userRepository.update(updatedUser) + logger.info { "Updated user shared libraries: $updatedUser" } } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/BookDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/BookDto.kt index e50662720..b09c46596 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/BookDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/BookDto.kt @@ -7,9 +7,9 @@ import java.time.LocalDate import java.time.LocalDateTime data class BookDto( - val id: Long, - val seriesId: Long, - val libraryId: Long, + val id: String, + val seriesId: String, + val libraryId: String, val name: String, val url: String, val number: Int, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionCreationDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionCreationDto.kt index 8adcac529..5417a0ff0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionCreationDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionCreationDto.kt @@ -7,5 +7,5 @@ import javax.validation.constraints.NotEmpty data class CollectionCreationDto( @get:NotBlank val name: String, val ordered: Boolean, - @get:NotEmpty @get:Unique val seriesIds: List + @get:NotEmpty @get:Unique val seriesIds: List ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionDto.kt index e25838b12..378a0a04c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionDto.kt @@ -5,11 +5,11 @@ import org.gotson.komga.domain.model.SeriesCollection import java.time.LocalDateTime data class CollectionDto( - val id: Long, + val id: String, val name: String, val ordered: Boolean, - val seriesIds: List, + val seriesIds: List, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") val createdDate: LocalDateTime, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionUpdateDto.kt index 918adacc1..6330bb5cf 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/CollectionUpdateDto.kt @@ -7,5 +7,5 @@ import org.gotson.komga.infrastructure.validation.Unique data class CollectionUpdateDto( @get:NullOrNotBlank val name: String?, val ordered: Boolean?, - @get:NullOrNotEmpty @get:Unique val seriesIds: List? + @get:NullOrNotEmpty @get:Unique val seriesIds: List? ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/SeriesDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/SeriesDto.kt index 17a0506dc..d8c760c73 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/SeriesDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/SeriesDto.kt @@ -4,8 +4,8 @@ import com.fasterxml.jackson.annotation.JsonFormat import java.time.LocalDateTime data class SeriesDto( - val id: Long, - val libraryId: Long, + val id: String, + val libraryId: String, val name: String, val url: String, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt index 81c42e453..44759904b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt @@ -9,7 +9,7 @@ import javax.validation.constraints.Email import javax.validation.constraints.NotBlank data class UserDto( - val id: Long, + val id: String, val email: String, val roles: List ) @@ -24,7 +24,7 @@ fun KomgaUser.toDto() = fun KomgaPrincipal.toDto() = user.toDto() data class UserWithSharedLibrariesDto( - val id: Long, + val id: String, val email: String, val roles: List, val sharedAllLibraries: Boolean, @@ -32,7 +32,7 @@ data class UserWithSharedLibrariesDto( ) data class SharedLibraryDto( - val id: Long + val id: String ) fun KomgaUser.toWithSharedLibrariesDto() = @@ -65,7 +65,7 @@ data class PasswordUpdateDto( data class SharedLibrariesUpdateDto( val all: Boolean, - val libraryIds: Set + val libraryIds: Set ) data class RolesUpdateDto( diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/BookDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/BookDtoRepository.kt index bed695a43..4b1b54fbc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/BookDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/BookDtoRepository.kt @@ -6,9 +6,9 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable interface BookDtoRepository { - fun findAll(search: BookSearchWithReadProgress, userId: Long, pageable: Pageable): Page - fun findByIdOrNull(bookId: Long, userId: Long): BookDto? - fun findPreviousInSeries(bookId: Long, userId: Long): BookDto? - fun findNextInSeries(bookId: Long, userId: Long): BookDto? - fun findOnDeck(libraryIds: Collection, userId: Long, pageable: Pageable): Page + fun findAll(search: BookSearchWithReadProgress, userId: String, pageable: Pageable): Page + fun findByIdOrNull(bookId: String, userId: String): BookDto? + fun findPreviousInSeries(bookId: String, userId: String): BookDto? + fun findNextInSeries(bookId: String, userId: String): BookDto? + fun findOnDeck(libraryIds: Collection, userId: String, pageable: Pageable): Page } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt index 381e7d640..6dbcd110b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt @@ -6,8 +6,8 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable interface SeriesDtoRepository { - fun findAll(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page - fun findByCollectionId(collectionId: Long, userId: Long, pageable: Pageable): Page - fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page - fun findByIdOrNull(seriesId: Long, userId: Long): SeriesDto? + fun findAll(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable): Page + fun findByCollectionId(collectionId: String, userId: String, pageable: Pageable): Page + fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable): Page + fun findByIdOrNull(seriesId: String, userId: String): SeriesDto? } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt deleted file mode 100644 index 39cdc9e2e..000000000 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.gotson.komga.interfaces.scheduler - -import mu.KotlinLogging -import org.gotson.komga.application.tasks.TaskReceiver -import org.gotson.komga.infrastructure.configuration.KomgaProperties -import org.springframework.boot.context.event.ApplicationReadyEvent -import org.springframework.context.annotation.Profile -import org.springframework.context.event.EventListener -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Component - -private val logger = KotlinLogging.logger {} - -@Profile("!test") -@Component -class PeriodicDatabaseBackupController( - private val taskReceiver: TaskReceiver, - private val komgaProperties: KomgaProperties -) { - - @EventListener(classes = [ApplicationReadyEvent::class], condition = "@komgaProperties.databaseBackup.startup") - @Scheduled(cron = "#{@komgaProperties.databaseBackup.schedule ?: '-'}") - fun scanAllLibraries() { - if (komgaProperties.databaseBackup.enabled) { - logger.info { "Periodic database backup" } - taskReceiver.databaseBackup() - } - } -} diff --git a/komga/src/main/resources/application-dev.yml b/komga/src/main/resources/application-dev.yml index 06188496b..32e8d8c84 100644 --- a/komga/src/main/resources/application-dev.yml +++ b/komga/src/main/resources/application-dev.yml @@ -6,10 +6,9 @@ komga: # libraries-scan-cron: "*/5 * * * * ?" #every 5 seconds libraries-scan-cron: "-" #disable libraries-scan-startup: true - database-backup: - enabled: false - path: ./backup.zip - schedule: "*/15 * * * * ?" + database: + file: ":memory:" + spring: datasource: url: jdbc:h2:mem:testdb diff --git a/komga/src/main/resources/application-localdb.yml b/komga/src/main/resources/application-localdb.yml index c1547c0c7..153aa6a21 100644 --- a/komga/src/main/resources/application-localdb.yml +++ b/komga/src/main/resources/application-localdb.yml @@ -1,3 +1,7 @@ +komga: + database: + file: ./localdb.sqlite + spring: datasource: url: jdbc:h2:./testdb diff --git a/komga/src/main/resources/application.yml b/komga/src/main/resources/application.yml index a73174641..424fb95ef 100644 --- a/komga/src/main/resources/application.yml +++ b/komga/src/main/resources/application.yml @@ -12,15 +12,15 @@ komga: libraries-scan-directory-exclusions: - "#recycle" - "@eaDir" - database-backup: - path: ~/.komga/database-backup.zip - schedule: "0 0 */6 * * ?" + database: + file: \${user.home}/.komga/database.sqlite spring: # cache: # caffeine-spec: maximumSize=500,expireAfterWrite=300s datasource: url: jdbc:h2:~/.komga/database.h2 + username: sa h2: console: enabled: true @@ -28,6 +28,7 @@ spring: add-mappings: false flyway: enabled: true + locations: classpath:db/migration/{vendor} thymeleaf: prefix: classpath:/public/ mvc: diff --git a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt index 99ba78797..b8881b536 100644 --- a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt @@ -23,7 +23,6 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.jms.core.JmsTemplate import org.springframework.jms.support.destination.JmsDestinationAccessor @@ -33,7 +32,6 @@ private val logger = KotlinLogging.logger {} @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class TaskHandlerTest( @Autowired private val taskReceiver: TaskReceiver, @Autowired private val jmsTemplate: JmsTemplate, @@ -46,7 +44,7 @@ class TaskHandlerTest( @MockkBean private lateinit var mockMetadataLifecycle: MetadataLifecycle - private var library = makeLibrary() + private val library = makeLibrary() init { jmsTemplate.receiveTimeout = JmsDestinationAccessor.RECEIVE_TIMEOUT_NO_WAIT @@ -54,7 +52,7 @@ class TaskHandlerTest( @BeforeAll fun `setup library`() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) } @AfterAll diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt index 53d8df02e..ef3aaa469 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt @@ -1,9 +1,10 @@ package org.gotson.komga.domain.model +import com.github.f4b6a3.tsid.TsidCreator import java.net.URL import java.time.LocalDateTime -fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now(), libraryId: Long = 0, seriesId: Long = 0): Book { +fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now(), libraryId: String = "", seriesId: String = ""): Book { Thread.sleep(5) return Book( name = name, @@ -14,7 +15,7 @@ fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now() ) } -fun makeSeries(name: String, libraryId: Long = 0): Series { +fun makeSeries(name: String, libraryId: String = ""): Series { Thread.sleep(5) return Series( name = name, @@ -24,8 +25,8 @@ fun makeSeries(name: String, libraryId: Long = 0): Series { ) } -fun makeLibrary(name: String = "default", url: String = "file:/$name"): Library { - return Library(name, URL(url)) +fun makeLibrary(name: String = "default", url: String = "file:/$name", id: String = TsidCreator.getTsidString256()): Library { + return Library(name, URL(url), id = id) } fun makeBookPage(name: String) = diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt index f47afc696..4eddbb23b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt @@ -8,7 +8,6 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.ClassPathResource import org.springframework.test.context.junit.jupiter.SpringExtension @@ -16,7 +15,6 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class BookAnalyzerTest( @Autowired private val bookAnalyzer: BookAnalyzer ) { diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt index 395c0c934..6f8e54fd2 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt @@ -22,13 +22,11 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class BookLifecycleTest( @Autowired private val bookLifecycle: BookLifecycle, @Autowired private val bookRepository: BookRepository, @@ -43,16 +41,16 @@ class BookLifecycleTest( @MockkBean private lateinit var mockAnalyzer: BookAnalyzer - private var library = makeLibrary() - private var user1 = KomgaUser("user1@example.org", "", false) - private var user2 = KomgaUser("user2@example.org", "", false) + private val library = makeLibrary() + private val user1 = KomgaUser("user1@example.org", "", false) + private val user2 = KomgaUser("user2@example.org", "", false) @BeforeAll fun `setup library`() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) - user1 = userRepository.save(user1) - user2 = userRepository.save(user2) + userRepository.insert(user1) + userRepository.insert(user2) } @AfterAll @@ -63,9 +61,7 @@ class BookLifecycleTest( @AfterEach fun `clear repository`() { - seriesRepository.findAll().forEach { - seriesLifecycle.deleteSeries(it.id) - } + seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id }) } @Test diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt index c33078252..e50039a62 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.io.FileNotFoundException @@ -21,7 +20,6 @@ import java.nio.file.Files @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class LibraryLifecycleTest( @Autowired private val libraryRepository: LibraryRepository, @Autowired private val libraryLifecycle: LibraryLifecycle diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt index 3f9623bbb..9fa8740c4 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt @@ -17,14 +17,12 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.nio.file.Paths @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class LibraryScannerTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, @@ -51,7 +49,8 @@ class LibraryScannerTest( @Test fun `given existing series when adding files and scanning then only updated Books are persisted`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) val books = listOf(makeBook("book1")) val moreBooks = listOf(makeBook("book1"), makeBook("book2")) @@ -79,7 +78,8 @@ class LibraryScannerTest( @Test fun `given existing series when removing files and scanning then only updated Books are persisted`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) val books = listOf(makeBook("book1"), makeBook("book2")) val lessBooks = listOf(makeBook("book1")) @@ -109,7 +109,8 @@ class LibraryScannerTest( @Test fun `given existing series when updating files and scanning then Books are updated`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) val books = listOf(makeBook("book1")) val updatedBooks = listOf(makeBook("book1")) @@ -139,7 +140,8 @@ class LibraryScannerTest( @Test fun `given existing series when deleting all books and scanning then Series and Books are removed`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) every { mockScanner.scanRootFolder(any()) } .returnsMany( @@ -161,7 +163,8 @@ class LibraryScannerTest( @Test fun `given existing Series when deleting all books of one series and scanning then series and its Books are removed`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) every { mockScanner.scanRootFolder(any()) } .returnsMany( @@ -186,7 +189,8 @@ class LibraryScannerTest( @Test fun `given existing Book with media when rescanning then media is kept intact`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) val book1 = makeBook("book1") every { mockScanner.scanRootFolder(any()) } @@ -222,7 +226,8 @@ class LibraryScannerTest( @Test fun `given existing Book with different last modified date when rescanning then media is marked as outdated`() { // given - val library = libraryRepository.insert(makeLibrary()) + val library = makeLibrary() + libraryRepository.insert(library) val book1 = makeBook("book1") every { mockScanner.scanRootFolder(any()) } @@ -258,8 +263,10 @@ class LibraryScannerTest( @Test fun `given 2 libraries when deleting all books of one and scanning then the other library is kept intact`() { // given - val library1 = libraryRepository.insert(makeLibrary(name = "library1")) - val library2 = libraryRepository.insert(makeLibrary(name = "library2")) + val library1 = makeLibrary(name = "library1") + libraryRepository.insert(library1) + val library2 = makeLibrary(name = "library2") + libraryRepository.insert(library2) every { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) } returns mapOf(makeSeries(name = "series1") to listOf(makeBook("book1"))) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt index 4b11d5931..575767ff0 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt @@ -14,13 +14,11 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class SeriesLifecycleTest( @Autowired private val seriesLifecycle: SeriesLifecycle, @Autowired private val bookLifecycle: BookLifecycle, @@ -30,11 +28,11 @@ class SeriesLifecycleTest( @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() + private val library = makeLibrary() @BeforeAll fun `setup library`() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) } @AfterAll @@ -44,9 +42,7 @@ class SeriesLifecycleTest( @AfterEach fun `clear repository`() { - seriesRepository.findAll().forEach { - seriesLifecycle.deleteSeries(it.id) - } + seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id }) } @Test @@ -92,7 +88,7 @@ class SeriesLifecycleTest( // when val book = bookRepository.findBySeriesId(createdSeries.id).first { it.name == "book 2" } - bookLifecycle.delete(book.id) + bookLifecycle.deleteOne(book.id) seriesLifecycle.sortBooks(createdSeries) // then diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigrationTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigrationTest.kt new file mode 100644 index 000000000..eedffcce3 --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/datasource/DatabaseMigrationTest.kt @@ -0,0 +1,31 @@ +package org.gotson.komga.infrastructure.datasource + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +class DatabaseMigrationTest { + + companion object { + @JvmStatic + fun h2Urls() = + listOf( + "not a jdbc url" to null, + "jdbc:h2:./testdb" to "./testdb", + "jdbc:h2:file:./testdb" to "./testdb", + "jdbc:h2:~/.komga/database.h2" to "~/.komga/database.h2", + "jdbc:h2:mem:testdb" to null, + "jdbc:h2:tcp://localhost/~/test" to null, + "jdbc:h2:ssl://localhost:8085/~/sample" to null, + "jdbc:h2:file:~/private;CIPHER=AES;FILE_LOCK=SOCKET" to "~/private", + "jdbc:h2:zip:~/db.zip!/test" to null + ) + } + + @ParameterizedTest + @MethodSource("h2Urls") + fun `given h2 url when extracting file name then file name is returned`(pair: Pair) { + val fileName = extractH2Path(pair.first) + assertThat(fileName).isEqualTo(pair.second) + } +} diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDaoTest.kt index 2c3de4b7e..232c42fb2 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDaoTest.kt @@ -1,5 +1,6 @@ package org.gotson.komga.infrastructure.jooq +import mu.KotlinLogging import org.assertj.core.api.Assertions.assertThat import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookSearch @@ -14,7 +15,6 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.net.URL @@ -22,20 +22,19 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class BookDaoTest( @Autowired private val bookDao: BookDao, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() - private var series = makeSeries("Series") + private val library = makeLibrary() + private val series = makeSeries("Series") @BeforeAll fun setup() { - library = libraryRepository.insert(library) - series = seriesRepository.insert(series.copy(libraryId = library.id)) + libraryRepository.insert(library) + seriesRepository.insert(series.copy(libraryId = library.id)) } @AfterEach @@ -63,16 +62,15 @@ class BookDaoTest( libraryId = library.id ) - Thread.sleep(5) - - val created = bookDao.insert(book) + bookDao.insert(book) + val created = bookDao.findByIdOrNull(book.id)!! assertThat(created.id).isNotEqualTo(0) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.name).isEqualTo(book.name) assertThat(created.url).isEqualTo(book.url) - assertThat(created.fileLastModified).isEqualTo(book.fileLastModified) + assertThat(created.fileLastModified).isEqualToIgnoringNanos(book.fileLastModified) assertThat(created.fileSize).isEqualTo(book.fileSize) } @@ -86,13 +84,11 @@ class BookDaoTest( seriesId = series.id, libraryId = library.id ) - val created = bookDao.insert(book) - - Thread.sleep(5) + bookDao.insert(book) val modificationDate = LocalDateTime.now() - val updated = with(created) { + val updated = with(bookDao.findByIdOrNull(book.id)!!) { copy( name = "Updated", url = URL("file://updated"), @@ -107,11 +103,11 @@ class BookDaoTest( assertThat(modified.id).isEqualTo(updated.id) assertThat(modified.createdDate).isEqualTo(updated.createdDate) assertThat(modified.lastModifiedDate) - .isAfterOrEqualTo(modificationDate) + .isCloseTo(modificationDate, offset) .isNotEqualTo(updated.lastModifiedDate) assertThat(modified.name).isEqualTo("Updated") assertThat(modified.url).isEqualTo(URL("file://updated")) - assertThat(modified.fileLastModified).isEqualTo(modificationDate) + assertThat(modified.fileLastModified).isEqualToIgnoringNanos(modificationDate) assertThat(modified.fileSize).isEqualTo(5) } @@ -125,9 +121,9 @@ class BookDaoTest( seriesId = series.id, libraryId = library.id ) - val created = bookDao.insert(book) + bookDao.insert(book) - val found = bookDao.findByIdOrNull(created.id) + val found = bookDao.findByIdOrNull(book.id) assertThat(found).isNotNull assertThat(found?.name).isEqualTo("Book") @@ -135,7 +131,7 @@ class BookDaoTest( @Test fun `given non-existing book when finding by id then null is returned`() { - val found = bookDao.findByIdOrNull(128742) + val found = bookDao.findByIdOrNull("128742") assertThat(found).isNull() } @@ -193,4 +189,35 @@ class BookDaoTest( assertThat(bookDao.count()).isEqualTo(0) } + + + private val logger = KotlinLogging.logger {} + +// @Test +// fun benchmark() { +// val books = (1..10000).map { +// makeBook(it.toString(), libraryId = library.id, seriesId = series.id) +// } +// +// val single = measureTime { +// books.map { bookDao.insert(it) } +// } +// bookDao.deleteAll() +// +// val singleBatch = measureTime { +// books.map { bookDao.insertBatch(it) } +// } +// bookDao.deleteAll() +// +// val transaction = measureTime { +// bookDao.insertMany(books) +// } +// bookDao.deleteAll() +// +// logger.info { "Single: $single" } +// logger.info { "SingleBatch: $singleBatch" } +// logger.info { "Transaction: $transaction" } +// +// assertThat(single).isEqualTo(transaction) +// } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDaoTest.kt index 28444c73a..4b59e157a 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookDtoDaoTest.kt @@ -12,7 +12,6 @@ import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.KomgaUserRepository import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.persistence.ReadProgressRepository -import org.gotson.komga.domain.persistence.SeriesRepository import org.gotson.komga.domain.service.BookLifecycle import org.gotson.komga.domain.service.KomgaUserLifecycle import org.gotson.komga.domain.service.LibraryLifecycle @@ -24,19 +23,16 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.domain.PageRequest import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class BookDtoDaoTest( @Autowired private val bookDtoDao: BookDtoDao, @Autowired private val bookRepository: BookRepository, @Autowired private val bookLifecycle: BookLifecycle, - @Autowired private val seriesRepository: SeriesRepository, @Autowired private val seriesLifecycle: SeriesLifecycle, @Autowired private val libraryRepository: LibraryRepository, @Autowired private val libraryLifecycle: LibraryLifecycle, @@ -45,22 +41,20 @@ class BookDtoDaoTest( @Autowired private val userLifecycle: KomgaUserLifecycle ) { - private var library = makeLibrary() + private val library = makeLibrary() private var series = makeSeries("Series") - private var user = KomgaUser("user@example.org", "", false) + private val user = KomgaUser("user@example.org", "", false) @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) series = seriesLifecycle.createSeries(series.copy(libraryId = library.id)) - user = userRepository.save(user) + userRepository.insert(user) } @AfterEach fun deleteBooks() { - bookRepository.findAll().forEach { - bookLifecycle.delete(it.id) - } + bookLifecycle.deleteMany(bookRepository.findAll().map { it.id }) } @AfterAll diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDaoTest.kt index 564306948..63fbf0fc5 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/BookMetadataDaoTest.kt @@ -16,7 +16,6 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.LocalDate @@ -24,24 +23,23 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class BookMetadataDaoTest( @Autowired private val bookMetadataDao: BookMetadataDao, @Autowired private val bookRepository: BookRepository, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() - private var series = makeSeries("Series") - private var book = makeBook("Book") + private val library = makeLibrary() + private val series = makeSeries("Series") + private val book = makeBook("Book") @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) - series = seriesRepository.insert(series.copy(libraryId = library.id)) + seriesRepository.insert(series.copy(libraryId = library.id)) - book = bookRepository.insert(book.copy(libraryId = library.id, seriesId = series.id)) + bookRepository.insert(book.copy(libraryId = library.id, seriesId = series.id)) } @AfterEach @@ -83,13 +81,12 @@ class BookMetadataDaoTest( authorsLock = true ) - Thread.sleep(5) - - val created = bookMetadataDao.insert(metadata) + bookMetadataDao.insert(metadata) + val created = bookMetadataDao.findById(metadata.bookId) assertThat(created.bookId).isEqualTo(book.id) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.title).isEqualTo(metadata.title) assertThat(created.summary).isEqualTo(metadata.summary) @@ -125,7 +122,8 @@ class BookMetadataDaoTest( bookId = book.id ) - val created = bookMetadataDao.insert(metadata) + bookMetadataDao.insert(metadata) + val created = bookMetadataDao.findById(metadata.bookId) assertThat(created.bookId).isEqualTo(book.id) @@ -164,13 +162,10 @@ class BookMetadataDaoTest( authors = mutableListOf(Author("author", "role")), bookId = book.id ) - val created = bookMetadataDao.insert(metadata) - - Thread.sleep(5) + bookMetadataDao.insert(metadata) val modificationDate = LocalDateTime.now() - - val updated = with(created) { + val updated = with(bookMetadataDao.findById(metadata.bookId)) { copy( title = "BookUpdated", summary = "SummaryUpdated", @@ -199,7 +194,7 @@ class BookMetadataDaoTest( assertThat(modified.bookId).isEqualTo(updated.bookId) assertThat(modified.createdDate).isEqualTo(updated.createdDate) assertThat(modified.lastModifiedDate) - .isAfterOrEqualTo(modificationDate) + .isCloseTo(modificationDate, offset) .isNotEqualTo(updated.lastModifiedDate) assertThat(modified.title).isEqualTo(updated.title) @@ -238,16 +233,16 @@ class BookMetadataDaoTest( authors = mutableListOf(Author("author", "role")), bookId = book.id ) - val created = bookMetadataDao.insert(metadata) + bookMetadataDao.insert(metadata) - val found = catchThrowable { bookMetadataDao.findById(created.bookId) } + val found = catchThrowable { bookMetadataDao.findById(metadata.bookId) } assertThat(found).doesNotThrowAnyException() } @Test fun `given non-existing metadata when finding by id then exception is thrown`() { - val found = catchThrowable { bookMetadataDao.findById(128742) } + val found = catchThrowable { bookMetadataDao.findById("128742") } assertThat(found).isInstanceOf(Exception::class.java) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt index a712cd618..54e618999 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt @@ -10,24 +10,22 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class KomgaUserDaoTest( @Autowired private val komgaUserDao: KomgaUserDao, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() + private val library = makeLibrary() @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) } @AfterEach @@ -52,14 +50,13 @@ class KomgaUserDaoTest( sharedAllLibraries = false ) - Thread.sleep(5) - - val created = komgaUserDao.save(user) + komgaUserDao.insert(user) + val created = komgaUserDao.findByIdOrNull(user.id)!! with(created) { assertThat(id).isNotEqualTo(0) - assertThat(createdDate).isAfter(now) - assertThat(lastModifiedDate).isAfter(now) + assertThat(createdDate).isCloseTo(now, offset) + assertThat(lastModifiedDate).isCloseTo(now, offset) assertThat(email).isEqualTo("user@example.org") assertThat(password).isEqualTo("password") assertThat(roleAdmin).isFalse() @@ -78,11 +75,8 @@ class KomgaUserDaoTest( sharedAllLibraries = false ) - Thread.sleep(5) - - val created = komgaUserDao.save(user) - - Thread.sleep(5) + komgaUserDao.insert(user) + val created = komgaUserDao.findByIdOrNull(user.id)!! val modified = created.copy( email = "user2@example.org", @@ -92,13 +86,14 @@ class KomgaUserDaoTest( sharedAllLibraries = true ) val modifiedDate = LocalDateTime.now() - val modifiedSaved = komgaUserDao.save(modified) + komgaUserDao.update(modified) + val modifiedSaved = komgaUserDao.findByIdOrNull(modified.id)!! with(modifiedSaved) { assertThat(id).isEqualTo(created.id) assertThat(createdDate).isEqualTo(created.createdDate) assertThat(lastModifiedDate) - .isAfterOrEqualTo(modifiedDate) + .isCloseTo(modifiedDate, offset) .isNotEqualTo(modified.createdDate) assertThat(email).isEqualTo("user2@example.org") assertThat(password).isEqualTo("password2") @@ -110,10 +105,8 @@ class KomgaUserDaoTest( @Test fun `given multiple users when saving then they are persisted`() { - komgaUserDao.saveAll(listOf( - KomgaUser("user1@example.org", "p", false), - KomgaUser("user2@example.org", "p", true) - )) + komgaUserDao.insert(KomgaUser("user1@example.org", "p", false)) + komgaUserDao.insert(KomgaUser("user2@example.org", "p", true)) val users = komgaUserDao.findAll() @@ -126,10 +119,8 @@ class KomgaUserDaoTest( @Test fun `given some users when counting then proper count is returned`() { - komgaUserDao.saveAll(listOf( - KomgaUser("user1@example.org", "p", false), - KomgaUser("user2@example.org", "p", true) - )) + komgaUserDao.insert(KomgaUser("user1@example.org", "p", false)) + komgaUserDao.insert(KomgaUser("user2@example.org", "p", true)) val count = komgaUserDao.count() @@ -138,9 +129,8 @@ class KomgaUserDaoTest( @Test fun `given existing user when finding by id then user is returned`() { - val existing = komgaUserDao.save( - KomgaUser("user1@example.org", "p", false) - ) + val existing = KomgaUser("user1@example.org", "p", false) + komgaUserDao.insert(existing) val user = komgaUserDao.findByIdOrNull(existing.id) @@ -149,25 +139,24 @@ class KomgaUserDaoTest( @Test fun `given non-existent user when finding by id then null is returned`() { - val user = komgaUserDao.findByIdOrNull(38473) + val user = komgaUserDao.findByIdOrNull("38473") assertThat(user).isNull() } @Test fun `given existing user when deleting then user is deleted`() { - val existing = komgaUserDao.save( - KomgaUser("user1@example.org", "p", false) - ) + val existing = KomgaUser("user1@example.org", "p", false) + komgaUserDao.insert(existing) - komgaUserDao.delete(existing) + komgaUserDao.delete(existing.id) assertThat(komgaUserDao.count()).isEqualTo(0) } @Test fun `given users when checking if exists by email then return true or false`() { - komgaUserDao.save( + komgaUserDao.insert( KomgaUser("user1@example.org", "p", false) ) @@ -180,7 +169,7 @@ class KomgaUserDaoTest( @Test fun `given users when finding by email then return user`() { - komgaUserDao.save( + komgaUserDao.insert( KomgaUser("user1@example.org", "p", false) ) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt index c5b7342b0..5439bae5b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/LibraryDaoTest.kt @@ -3,11 +3,9 @@ package org.gotson.komga.infrastructure.jooq import org.assertj.core.api.Assertions.assertThat import org.gotson.komga.domain.model.Library import org.junit.jupiter.api.AfterEach - import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.net.URL @@ -15,7 +13,6 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class LibraryDaoTest( @Autowired private val libraryDao: LibraryDao ) { @@ -34,13 +31,12 @@ class LibraryDaoTest( root = URL("file://library") ) - Thread.sleep(5) - - val created = libraryDao.insert(library) + libraryDao.insert(library) + val created = libraryDao.findById(library.id) assertThat(created.id).isNotEqualTo(0) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.name).isEqualTo(library.name) assertThat(created.root).isEqualTo(library.root) } @@ -51,21 +47,21 @@ class LibraryDaoTest( name = "Library", root = URL("file://library") ) - val created = libraryDao.insert(library) - - Thread.sleep(5) + libraryDao.insert(library) val modificationDate = LocalDateTime.now() - val updated = created.copy( - name = "LibraryUpdated", - root = URL("file://library2"), - importEpubSeries = false, - importEpubBook = false, - importComicInfoCollection = false, - importComicInfoSeries = false, - importComicInfoBook = false - ) + val updated = with(libraryDao.findById(library.id)) { + copy( + name = "LibraryUpdated", + root = URL("file://library2"), + importEpubSeries = false, + importEpubBook = false, + importComicInfoCollection = false, + importComicInfoSeries = false, + importComicInfoBook = false + ) + } libraryDao.update(updated) val modified = libraryDao.findById(updated.id) @@ -73,7 +69,7 @@ class LibraryDaoTest( assertThat(modified.id).isEqualTo(updated.id) assertThat(modified.createdDate).isEqualTo(updated.createdDate) assertThat(modified.lastModifiedDate) - .isAfterOrEqualTo(modificationDate) + .isCloseTo(modificationDate, offset) .isNotEqualTo(updated.lastModifiedDate) assertThat(modified.name).isEqualTo(updated.name) @@ -92,10 +88,10 @@ class LibraryDaoTest( root = URL("file://library") ) - val created = libraryDao.insert(library) + libraryDao.insert(library) assertThat(libraryDao.count()).isEqualTo(1) - libraryDao.delete(created.id) + libraryDao.delete(library.id) assertThat(libraryDao.count()).isEqualTo(0) } @@ -151,10 +147,10 @@ class LibraryDaoTest( root = URL("file://library2") ) - val created1 = libraryDao.insert(library) - val created2 = libraryDao.insert(library2) + libraryDao.insert(library) + libraryDao.insert(library2) - val all = libraryDao.findAllById(listOf(created1.id, created2.id)) + val all = libraryDao.findAllById(listOf(library.id, library2.id)) assertThat(all).hasSize(2) assertThat(all.map { it.name }).containsExactlyInAnyOrder("Library", "Library2") @@ -167,9 +163,9 @@ class LibraryDaoTest( root = URL("file://library") ) - val created = libraryDao.insert(library) + libraryDao.insert(library) - val found = libraryDao.findByIdOrNull(created.id) + val found = libraryDao.findByIdOrNull(library.id) assertThat(found).isNotNull assertThat(found?.name).isEqualTo("Library") @@ -177,7 +173,7 @@ class LibraryDaoTest( @Test fun `given non-existing library when finding by id then null is returned`() { - val found = libraryDao.findByIdOrNull(1287386) + val found = libraryDao.findByIdOrNull("1287386") assertThat(found).isNull() } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt index 18dc56cd0..6ad945ff0 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt @@ -16,7 +16,6 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.LocalDateTime @@ -24,24 +23,23 @@ import kotlin.random.Random @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class MediaDaoTest( @Autowired private val mediaDao: MediaDao, @Autowired private val bookRepository: BookRepository, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() - private var series = makeSeries("Series") - private var book = makeBook("Book") + private val library = makeLibrary() + private val series = makeSeries("Series") + private val book = makeBook("Book") @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) - series = seriesRepository.insert(series.copy(libraryId = library.id)) + seriesRepository.insert(series.copy(libraryId = library.id)) - book = bookRepository.insert(book.copy(libraryId = library.id, seriesId = series.id)) + bookRepository.insert(book.copy(libraryId = library.id, seriesId = series.id)) } @AfterEach @@ -74,13 +72,12 @@ class MediaDaoTest( bookId = book.id ) - Thread.sleep(5) - - val created = mediaDao.insert(media) + mediaDao.insert(media) + val created = mediaDao.findById(media.bookId) assertThat(created.bookId).isEqualTo(book.id) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.status).isEqualTo(media.status) assertThat(created.mediaType).isEqualTo(media.mediaType) assertThat(created.thumbnail).isEqualTo(media.thumbnail) @@ -98,7 +95,8 @@ class MediaDaoTest( fun `given a minimum media when inserting then it is persisted`() { val media = Media(bookId = book.id) - val created = mediaDao.insert(media) + mediaDao.insert(media) + val created = mediaDao.findById(media.bookId) assertThat(created.bookId).isEqualTo(book.id) assertThat(created.status).isEqualTo(Media.Status.UNKNOWN) @@ -123,23 +121,23 @@ class MediaDaoTest( comment = "comment", bookId = book.id ) - val created = mediaDao.insert(media) - - Thread.sleep(5) + mediaDao.insert(media) val modificationDate = LocalDateTime.now() - val updated = created.copy( - status = Media.Status.ERROR, - mediaType = "application/rar", - thumbnail = Random.nextBytes(1), - pages = listOf(BookPage( - fileName = "2.png", - mediaType = "image/png" - )), - files = listOf("id.txt"), - comment = "comment2" - ) + val updated = with(mediaDao.findById(media.bookId)) { + copy( + status = Media.Status.ERROR, + mediaType = "application/rar", + thumbnail = Random.nextBytes(1), + pages = listOf(BookPage( + fileName = "2.png", + mediaType = "image/png" + )), + files = listOf("id.txt"), + comment = "comment2" + ) + } mediaDao.update(updated) val modified = mediaDao.findById(updated.bookId) @@ -147,7 +145,7 @@ class MediaDaoTest( assertThat(modified.bookId).isEqualTo(updated.bookId) assertThat(modified.createdDate).isEqualTo(updated.createdDate) assertThat(modified.lastModifiedDate) - .isAfterOrEqualTo(modificationDate) + .isCloseTo(modificationDate, offset) .isNotEqualTo(updated.lastModifiedDate) assertThat(modified.status).isEqualTo(updated.status) assertThat(modified.mediaType).isEqualTo(updated.mediaType) @@ -172,16 +170,16 @@ class MediaDaoTest( comment = "comment", bookId = book.id ) - val created = mediaDao.insert(media) + mediaDao.insert(media) - val found = catchThrowable { mediaDao.findById(created.bookId) } + val found = catchThrowable { mediaDao.findById(media.bookId) } assertThat(found).doesNotThrowAnyException() } @Test fun `given non-existing media when finding by id then exception is thrown`() { - val found = catchThrowable { mediaDao.findById(128742) } + val found = catchThrowable { mediaDao.findById("128742") } assertThat(found).isInstanceOf(Exception::class.java) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDaoTest.kt index c544e3564..3dfe3c302 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/ReadProgressDaoTest.kt @@ -16,14 +16,12 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class ReadProgressDaoTest( @Autowired private val readProgressDao: ReadProgressDao, @Autowired private val userRepository: KomgaUserRepository, @@ -31,23 +29,23 @@ class ReadProgressDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() - private var series = makeSeries("Series") + private val library = makeLibrary() + private val series = makeSeries("Series") - private var user1 = KomgaUser("user1@example.org", "", false) - private var user2 = KomgaUser("user2@example.org", "", false) + private val user1 = KomgaUser("user1@example.org", "", false) + private val user2 = KomgaUser("user2@example.org", "", false) - private var book1 = makeBook("Book1") - private var book2 = makeBook("Book2") + private val book1 = makeBook("Book1") + private val book2 = makeBook("Book2") @BeforeAll fun setup() { - library = libraryRepository.insert(library) - series = seriesRepository.insert(series.copy(libraryId = library.id)) - user1 = userRepository.save(user1) - user2 = userRepository.save(user2) - book1 = bookRepository.insert(book1.copy(libraryId = library.id, seriesId = series.id)) - book2 = bookRepository.insert(book2.copy(libraryId = library.id, seriesId = series.id)) + libraryRepository.insert(library) + seriesRepository.insert(series.copy(libraryId = library.id)) + userRepository.insert(user1) + userRepository.insert(user2) + bookRepository.insert(book1.copy(libraryId = library.id, seriesId = series.id)) + bookRepository.insert(book2.copy(libraryId = library.id, seriesId = series.id)) } @AfterEach @@ -82,7 +80,7 @@ class ReadProgressDaoTest( assertThat(completed).isEqualTo(false) assertThat(bookId).isEqualTo(book1.id) assertThat(createdDate) - .isAfterOrEqualTo(now) + .isCloseTo(now, offset) .isEqualTo(lastModifiedDate) } } @@ -96,7 +94,6 @@ class ReadProgressDaoTest( false )) - Thread.sleep(5) val modificationDate = LocalDateTime.now() readProgressDao.save(ReadProgress( @@ -116,7 +113,7 @@ class ReadProgressDaoTest( assertThat(createdDate) .isBefore(modificationDate) .isNotEqualTo(lastModifiedDate) - assertThat(lastModifiedDate).isAfterOrEqualTo(modificationDate) + assertThat(lastModifiedDate).isCloseTo(modificationDate, offset) } } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDaoTest.kt index f998d95e8..7764b2287 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDaoTest.kt @@ -12,7 +12,6 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.domain.Pageable import org.springframework.test.context.junit.jupiter.SpringExtension @@ -20,20 +19,19 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class SeriesCollectionDaoTest( @Autowired private val collectionDao: SeriesCollectionDao, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() - private var library2 = makeLibrary("library2") + private val library = makeLibrary() + private val library2 = makeLibrary("library2") @BeforeAll fun setup() { - library = libraryRepository.insert(library) - library2 = libraryRepository.insert(library2) + libraryRepository.insert(library) + libraryRepository.insert(library2) } @AfterEach @@ -50,9 +48,8 @@ class SeriesCollectionDaoTest( @Test fun `given collection with series when inserting then it is persisted`() { // given - val series = (1..10) - .map { makeSeries("Series $it", library.id) } - .map { seriesRepository.insert(it) } + val series = (1..10).map { makeSeries("Series $it", library.id) } + series.forEach { seriesRepository.insert(it) } val collection = SeriesCollection( name = "MyCollection", @@ -61,36 +58,37 @@ class SeriesCollectionDaoTest( // when val now = LocalDateTime.now() - val created = collectionDao.insert(collection) + + collectionDao.insert(collection) + val created = collectionDao.findByIdOrNull(collection.id)!! // then assertThat(created.name).isEqualTo(collection.name) assertThat(created.ordered).isEqualTo(collection.ordered) assertThat(created.createdDate) .isEqualTo(created.lastModifiedDate) - .isAfterOrEqualTo(now) + .isCloseTo(now, offset) assertThat(created.seriesIds).containsExactlyElementsOf(series.map { it.id }) } @Test fun `given collection with updated series when updating then it is persisted`() { // given - val series = (1..10) - .map { makeSeries("Series $it", library.id) } - .map { seriesRepository.insert(it) } + val series = (1..10).map { makeSeries("Series $it", library.id) } + series.forEach { seriesRepository.insert(it) } val collection = SeriesCollection( name = "MyCollection", seriesIds = series.map { it.id } ) - val created = collectionDao.insert(collection) + collectionDao.insert(collection) // when - val updatedCollection = created.copy( + val updatedCollection = collection.copy( name = "UpdatedCollection", ordered = true, - seriesIds = created.seriesIds.take(5) + seriesIds = collection.seriesIds.take(5) ) val now = LocalDateTime.now() @@ -101,7 +99,7 @@ class SeriesCollectionDaoTest( assertThat(updated.name).isEqualTo(updatedCollection.name) assertThat(updated.ordered).isEqualTo(updatedCollection.ordered) assertThat(updated.createdDate).isNotEqualTo(updated.lastModifiedDate) - assertThat(updated.lastModifiedDate).isAfterOrEqualTo(now) + assertThat(updated.lastModifiedDate).isCloseTo(now, offset) assertThat(updated.seriesIds) .hasSize(5) .containsExactlyElementsOf(series.map { it.id }.take(5)) @@ -110,23 +108,20 @@ class SeriesCollectionDaoTest( @Test fun `given collections with series when removing one series from all then it is removed from all`() { // given - val series = (1..10) - .map { makeSeries("Series $it", library.id) } - .map { seriesRepository.insert(it) } + val series = (1..10).map { makeSeries("Series $it", library.id) } + series.forEach { seriesRepository.insert(it) } - val collection1 = collectionDao.insert( - SeriesCollection( - name = "MyCollection", - seriesIds = series.map { it.id } - ) + val collection1 = SeriesCollection( + name = "MyCollection", + seriesIds = series.map { it.id } ) + collectionDao.insert(collection1) - val collection2 = collectionDao.insert( - SeriesCollection( - name = "MyCollection2", - seriesIds = series.map { it.id }.take(5) - ) + val collection2 = SeriesCollection( + name = "MyCollection2", + seriesIds = series.map { it.id }.take(5) ) + collectionDao.insert(collection2) // when collectionDao.removeSeriesFromAll(series.first().id) @@ -146,8 +141,8 @@ class SeriesCollectionDaoTest( @Test fun `given collections spanning different libraries when finding by library then only matching collections are returned`() { // given - val seriesLibrary1 = seriesRepository.insert(makeSeries("Series1", library.id)) - val seriesLibrary2 = seriesRepository.insert(makeSeries("Series2", library2.id)) + val seriesLibrary1 = makeSeries("Series1", library.id).also { seriesRepository.insert(it) } + val seriesLibrary2 = makeSeries("Series2", library2.id).also { seriesRepository.insert(it) } collectionDao.insert(SeriesCollection( name = "collectionLibrary1", diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDaoTest.kt index a6a2ec837..cb824b67d 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDaoTest.kt @@ -7,12 +7,10 @@ import org.gotson.komga.domain.model.makeLibrary import org.gotson.komga.domain.persistence.LibraryRepository import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach - import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.net.URL @@ -20,17 +18,16 @@ import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class SeriesDaoTest( @Autowired private val seriesDao: SeriesDao, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() + private val library = makeLibrary() @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) } @AfterEach @@ -55,16 +52,15 @@ class SeriesDaoTest( libraryId = library.id ) - Thread.sleep(5) - - val created = seriesDao.insert(series) + seriesDao.insert(series) + val created = seriesDao.findByIdOrNull(series.id)!! assertThat(created.id).isNotEqualTo(0) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.name).isEqualTo(series.name) assertThat(created.url).isEqualTo(series.url) - assertThat(created.fileLastModified).isEqualTo(series.fileLastModified) + assertThat(created.fileLastModified).isEqualToIgnoringNanos(series.fileLastModified) } @Test @@ -76,10 +72,10 @@ class SeriesDaoTest( libraryId = library.id ) - val created = seriesDao.insert(series) + seriesDao.insert(series) assertThat(seriesDao.count()).isEqualTo(1) - seriesDao.delete(created.id) + seriesDao.delete(series.id) assertThat(seriesDao.count()).isEqualTo(0) } @@ -145,9 +141,9 @@ class SeriesDaoTest( libraryId = library.id ) - val created = seriesDao.insert(series) + seriesDao.insert(series) - val found = seriesDao.findByIdOrNull(created.id) + val found = seriesDao.findByIdOrNull(series.id) assertThat(found).isNotNull assertThat(found?.name).isEqualTo("Series") @@ -155,7 +151,7 @@ class SeriesDaoTest( @Test fun `given non-existing series when finding by id then null is returned`() { - val found = seriesDao.findByIdOrNull(1287746) + val found = seriesDao.findByIdOrNull("1287746") assertThat(found).isNull() } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDaoTest.kt index 94676042a..128c512d8 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDaoTest.kt @@ -13,7 +13,6 @@ import org.gotson.komga.domain.persistence.KomgaUserRepository import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.persistence.ReadProgressRepository import org.gotson.komga.domain.persistence.SeriesRepository -import org.gotson.komga.domain.service.BookLifecycle import org.gotson.komga.domain.service.KomgaUserLifecycle import org.gotson.komga.domain.service.LibraryLifecycle import org.gotson.komga.domain.service.SeriesLifecycle @@ -23,18 +22,15 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.domain.PageRequest import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class SeriesDtoDaoTest( @Autowired private val seriesDtoDao: SeriesDtoDao, @Autowired private val bookRepository: BookRepository, - @Autowired private val bookLifecycle: BookLifecycle, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val seriesLifecycle: SeriesLifecycle, @Autowired private val libraryRepository: LibraryRepository, @@ -44,20 +40,18 @@ class SeriesDtoDaoTest( @Autowired private val userLifecycle: KomgaUserLifecycle ) { - private var library = makeLibrary() - private var user = KomgaUser("user@example.org", "", false) + private val library = makeLibrary() + private val user = KomgaUser("user@example.org", "", false) @BeforeAll fun setup() { - library = libraryRepository.insert(library) - user = userRepository.save(user) + libraryRepository.insert(library) + userRepository.insert(user) } @AfterEach fun deleteSeries() { - seriesRepository.findAll().forEach { - seriesLifecycle.deleteSeries(it.id) - } + seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id }) } @AfterAll diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt index 28ea0cc7a..7a69932f1 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt @@ -13,25 +13,23 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.LocalDateTime @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase class SeriesMetadataDaoTest( @Autowired private val seriesMetadataDao: SeriesMetadataDao, @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository ) { - private var library = makeLibrary() + private val library = makeLibrary() @BeforeAll fun setup() { - library = libraryRepository.insert(library) + libraryRepository.insert(library) } @AfterEach @@ -47,9 +45,7 @@ class SeriesMetadataDaoTest( @Test fun `given a seriesMetadata when inserting then it is persisted`() { - val series = seriesRepository.insert( - makeSeries("Series", libraryId = library.id) - ) + val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val now = LocalDateTime.now() val metadata = SeriesMetadata( @@ -59,13 +55,11 @@ class SeriesMetadataDaoTest( seriesId = series.id ) - Thread.sleep(5) - val created = seriesMetadataDao.insert(metadata) assertThat(created.seriesId).isEqualTo(series.id) - assertThat(created.createdDate).isAfter(now) - assertThat(created.lastModifiedDate).isAfter(now) + assertThat(created.createdDate).isCloseTo(now, offset) + assertThat(created.lastModifiedDate).isCloseTo(now, offset) assertThat(created.title).isEqualTo("Series") assertThat(created.titleSort).isEqualTo("Series, The") assertThat(created.status).isEqualTo(SeriesMetadata.Status.ENDED) @@ -76,9 +70,8 @@ class SeriesMetadataDaoTest( @Test fun `given existing seriesMetadata when finding by id then metadata is returned`() { - val series = seriesRepository.insert( - makeSeries("Series", libraryId = library.id) - ) + val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } + val metadata = SeriesMetadata( status = SeriesMetadata.Status.ENDED, title = "Series", @@ -96,23 +89,21 @@ class SeriesMetadataDaoTest( @Test fun `given non-existing seriesMetadata when finding by id then exception is thrown`() { - val found = catchThrowable { seriesMetadataDao.findById(128742) } + val found = catchThrowable { seriesMetadataDao.findById("128742") } assertThat(found).isInstanceOf(Exception::class.java) } @Test fun `given non-existing seriesMetadata when findByIdOrNull then null is returned`() { - val found = seriesMetadataDao.findByIdOrNull(128742) + val found = seriesMetadataDao.findByIdOrNull("128742") assertThat(found).isNull() } @Test fun `given a seriesMetadata when updating then it is persisted`() { - val series = seriesRepository.insert( - makeSeries("Series", libraryId = library.id) - ) + val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val metadata = SeriesMetadata( status = SeriesMetadata.Status.ENDED, @@ -122,7 +113,6 @@ class SeriesMetadataDaoTest( ) val created = seriesMetadataDao.insert(metadata) - Thread.sleep(5) val modificationDate = LocalDateTime.now() @@ -140,12 +130,10 @@ class SeriesMetadataDaoTest( seriesMetadataDao.update(updated) val modified = seriesMetadataDao.findById(updated.seriesId) - Thread.sleep(5) - assertThat(modified.seriesId).isEqualTo(series.id) assertThat(modified.createdDate).isEqualTo(updated.createdDate) assertThat(modified.lastModifiedDate) - .isAfterOrEqualTo(modificationDate) + .isCloseTo(modificationDate, offset) .isNotEqualTo(modified.createdDate) assertThat(modified.title).isEqualTo("Changed") assertThat(modified.titleSort).isEqualTo("Changed, The") diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/TestUtils.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/TestUtils.kt new file mode 100644 index 000000000..16e79cdf5 --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/TestUtils.kt @@ -0,0 +1,7 @@ +package org.gotson.komga.infrastructure.jooq + +import org.assertj.core.api.Assertions.within +import org.assertj.core.data.TemporalUnitOffset +import java.time.temporal.ChronoUnit + +val offset: TemporalUnitOffset = within(3, ChronoUnit.SECONDS) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt index 91f44b763..d450f2d7f 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/BookControllerTest.kt @@ -34,12 +34,10 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpHeaders import org.springframework.http.MediaType -import org.springframework.jdbc.core.JdbcTemplate import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc @@ -50,12 +48,10 @@ import org.springframework.test.web.servlet.patch import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import java.time.LocalDate -import javax.sql.DataSource import kotlin.random.Random @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase @AutoConfigureMockMvc(printOnlyOnFailure = false) class BookControllerTest( @Autowired private val seriesRepository: SeriesRepository, @@ -70,24 +66,15 @@ class BookControllerTest( @Autowired private val mockMvc: MockMvc ) { - lateinit var jdbcTemplate: JdbcTemplate - - @Autowired - fun setDataSource(dataSource: DataSource) { - this.jdbcTemplate = JdbcTemplate(dataSource) - } - - private var library = makeLibrary() - private lateinit var user2: KomgaUser - private lateinit var user3: KomgaUser + private val library = makeLibrary(id = "1") + private val user = KomgaUser("user@example.org", "", false, id = "1") + private val user2 = KomgaUser("user2@example.org", "", false, id = "2") @BeforeAll fun `setup library`() { - jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1") - - library = libraryRepository.insert(library) // id = 1 - user2 = userRepository.save(KomgaUser("user@example.org", "", false)) // id = 2 - user3 = userRepository.save(KomgaUser("user2@example.org", "", false)) // id = 3 + libraryRepository.insert(library) + userRepository.insert(user) + userRepository.insert(user2) } @AfterAll @@ -102,15 +89,13 @@ class BookControllerTest( @AfterEach fun `clear repository`() { - seriesRepository.findAll().forEach { - seriesLifecycle.deleteSeries(it.id) - } + seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id }) } @Nested inner class LimitedUser { @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting books then only gets books from this library`() { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).let { created -> @@ -119,7 +104,8 @@ class BookControllerTest( } } - val otherLibrary = libraryRepository.insert(makeLibrary("other")) + val otherLibrary = makeLibrary("other") + libraryRepository.insert(otherLibrary) makeSeries(name = "otherSeries", libraryId = otherLibrary.id).let { series -> seriesLifecycle.createSeries(series).let { created -> val otherBooks = listOf(makeBook("2", libraryId = otherLibrary.id)) @@ -812,7 +798,7 @@ class BookControllerTest( } @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when marking book in progress with page read then progress is marked accordingly`() { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -851,7 +837,7 @@ class BookControllerTest( } @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when marking book completed then progress is marked accordingly`() { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -890,7 +876,7 @@ class BookControllerTest( } @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when deleting read progress then progress is removed`() { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -961,14 +947,14 @@ class BookControllerTest( mockMvc.perform(MockMvcRequestBuilders .patch("/api/v1/books/${book.id}/read-progress") - .with(user(KomgaPrincipal(user2))) + .with(user(KomgaPrincipal(user))) .contentType(MediaType.APPLICATION_JSON) .content(jsonString) ) mockMvc.perform(MockMvcRequestBuilders .get("/api/v1/books") - .with(user(KomgaPrincipal(user2))) + .with(user(KomgaPrincipal(user))) .contentType(MediaType.APPLICATION_JSON) ).andExpect( jsonPath("$.totalElements").value(2) @@ -976,7 +962,7 @@ class BookControllerTest( mockMvc.perform(MockMvcRequestBuilders .get("/api/v1/books") - .with(user(KomgaPrincipal(user3))) + .with(user(KomgaPrincipal(user2))) .contentType(MediaType.APPLICATION_JSON) ).andExpect( jsonPath("$.totalElements").value(2) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt index 5f280eb64..6022f2b3a 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/LibraryControllerTest.kt @@ -11,45 +11,31 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.MediaType -import org.springframework.jdbc.core.JdbcTemplate import org.springframework.security.test.context.support.WithAnonymousUser import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.post -import javax.sql.DataSource @ExtendWith(SpringExtension::class) @SpringBootTest @AutoConfigureMockMvc(printOnlyOnFailure = false) -@AutoConfigureTestDatabase class LibraryControllerTest( @Autowired private val mockMvc: MockMvc, @Autowired private val libraryRepository: LibraryRepository ) { - lateinit var jdbcTemplate: JdbcTemplate - - @Autowired - fun setDataSource(dataSource: DataSource) { - jdbcTemplate = JdbcTemplate(dataSource) - } - - private val route = "/api/v1/libraries" - private var library = makeLibrary(url = "file:/library1") + private val library = makeLibrary(url = "file:/library1", id = "1") @BeforeAll fun `setup library`() { - jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1") - - library = libraryRepository.insert(library) // id = 1 + libraryRepository.insert(library) } @AfterAll @@ -102,7 +88,7 @@ class LibraryControllerTest( @Nested inner class LimitedUser { @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getAll then only gets this library`() { mockMvc.get(route) .andExpect { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/MockSpringSecurity.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/MockSpringSecurity.kt index a7afed6a1..4aa49f49e 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/MockSpringSecurity.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/MockSpringSecurity.kt @@ -18,8 +18,8 @@ annotation class WithMockCustomUser( val email: String = "user@example.org", val roles: Array = [ROLE_FILE_DOWNLOAD, ROLE_PAGE_STREAMING], val sharedAllLibraries: Boolean = true, - val sharedLibraries: LongArray = [], - val id: Long = 0 + val sharedLibraries: Array = [], + val id: String = "0" ) class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionControllerTest.kt index dc3bfcd1a..5eef6cb5b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionControllerTest.kt @@ -17,23 +17,19 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.MediaType -import org.springframework.jdbc.core.JdbcTemplate import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.patch import org.springframework.test.web.servlet.post -import javax.sql.DataSource @ExtendWith(SpringExtension::class) @SpringBootTest @AutoConfigureMockMvc(printOnlyOnFailure = false) -@AutoConfigureTestDatabase class SeriesCollectionControllerTest( @Autowired private val mockMvc: MockMvc, @Autowired private val collectionLifecycle: SeriesCollectionLifecycle, @@ -41,18 +37,10 @@ class SeriesCollectionControllerTest( @Autowired private val libraryLifecycle: LibraryLifecycle, @Autowired private val libraryRepository: LibraryRepository, @Autowired private val seriesLifecycle: SeriesLifecycle - ) { - lateinit var jdbcTemplate: JdbcTemplate - - @Autowired - fun setDataSource(dataSource: DataSource) { - jdbcTemplate = JdbcTemplate(dataSource) - } - - private var library1 = makeLibrary("Library1") - private var library2 = makeLibrary("Library2") + private val library1 = makeLibrary("Library1", id = "1") + private val library2 = makeLibrary("Library2", id = "2") private lateinit var seriesLibrary1: List private lateinit var seriesLibrary2: List private lateinit var colLib1: SeriesCollection @@ -61,10 +49,8 @@ class SeriesCollectionControllerTest( @BeforeAll fun setup() { - jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1") - - library1 = libraryRepository.insert(library1) // id = 1 - library2 = libraryRepository.insert(library2) // id = 2 + libraryRepository.insert(library1) + libraryRepository.insert(library2) seriesLibrary1 = (1..5) .map { makeSeries("Series_$it", library1.id) } @@ -122,7 +108,7 @@ class SeriesCollectionControllerTest( } @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting collections then only get collections from this library`() { makeCollections() @@ -149,7 +135,7 @@ class SeriesCollectionControllerTest( } @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting single collection with items from 2 libraries then it is filtered`() { makeCollections() @@ -162,7 +148,7 @@ class SeriesCollectionControllerTest( } @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting single collection from another library then return not found`() { makeCollections() @@ -194,7 +180,7 @@ class SeriesCollectionControllerTest( @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given admin user when creating collection then return ok`() { val jsonString = """ - {"name":"collection","ordered":false,"seriesIds":[${seriesLibrary1.first().id}]} + {"name":"collection","ordered":false,"seriesIds":["${seriesLibrary1.first().id}"]} """.trimIndent() mockMvc.post("/api/v1/collections") { @@ -266,7 +252,7 @@ class SeriesCollectionControllerTest( makeCollections() val jsonString = """ - {"name":"updated","ordered":true,"seriesIds":[${seriesLibrary1.first().id}]} + {"name":"updated","ordered":true,"seriesIds":["${seriesLibrary1.first().id}"]} """.trimIndent() mockMvc.patch("/api/v1/collections/${colLib1.id}") { @@ -351,7 +337,7 @@ class SeriesCollectionControllerTest( mockMvc.patch("/api/v1/collections/${colLibBoth.id}") { contentType = MediaType.APPLICATION_JSON - content = """{"seriesIds":[${seriesLibrary1.first().id}]}""" + content = """{"seriesIds":["${seriesLibrary1.first().id}"]}""" } mockMvc.get("/api/v1/collections/${colLibBoth.id}") diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt index 5df4ddbc4..259c7cb5b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/SeriesControllerTest.kt @@ -30,12 +30,10 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpHeaders import org.springframework.http.MediaType -import org.springframework.jdbc.core.JdbcTemplate import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvcResultMatchersDsl @@ -43,12 +41,10 @@ import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.patch import org.springframework.test.web.servlet.post -import javax.sql.DataSource import kotlin.random.Random @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase @AutoConfigureMockMvc(printOnlyOnFailure = false) class SeriesControllerTest( @Autowired private val seriesRepository: SeriesRepository, @@ -64,25 +60,16 @@ class SeriesControllerTest( @Autowired private val mockMvc: MockMvc ) { - lateinit var jdbcTemplate: JdbcTemplate - - @Autowired - fun setDataSource(dataSource: DataSource) { - this.jdbcTemplate = JdbcTemplate(dataSource) - } - - private var library = makeLibrary() + private val library = makeLibrary(id = "1") @BeforeAll fun `setup library`() { - jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1") - - library = libraryRepository.insert(library) // id = 1 - userRepository.save(KomgaUser("user@example.org", "", false)) // id = 2 + libraryRepository.insert(library) + userRepository.insert(KomgaUser("user@example.org", "", false, id = "1")) } @AfterAll - fun `teardown`() { + fun teardown() { userRepository.findAll().forEach { userLifecycle.deleteUser(it) } @@ -93,9 +80,7 @@ class SeriesControllerTest( @AfterEach fun `clear repository`() { - seriesRepository.findAll().forEach { - seriesLifecycle.deleteSeries(it.id) - } + seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id }) } @Nested @@ -195,7 +180,7 @@ class SeriesControllerTest( @Nested inner class LimitedUser { @Test - @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1]) + @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting series then only gets series from this library`() { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -204,7 +189,8 @@ class SeriesControllerTest( } } - val otherLibrary = libraryRepository.insert(makeLibrary("other")) + val otherLibrary = makeLibrary("other") + libraryRepository.insert(otherLibrary) makeSeries(name = "otherSeries", libraryId = otherLibrary.id).let { series -> seriesLifecycle.createSeries(series).also { created -> val books = listOf(makeBook("2", libraryId = otherLibrary.id)) @@ -487,7 +473,7 @@ class SeriesControllerTest( @Nested inner class ReadProgress { @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when marking series as read then progress is marked for all books`() { val series = makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -527,7 +513,7 @@ class SeriesControllerTest( } @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when marking series as unread then progress is removed for all books`() { val series = makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> @@ -572,7 +558,7 @@ class SeriesControllerTest( } @Test - @WithMockCustomUser(id = 2) + @WithMockCustomUser(id = "1") fun `given user when marking book as in progress then progress series return books count accordingly`() { val series = makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).also { created -> diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt index 38b81bd99..12ce88db5 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt @@ -3,7 +3,6 @@ package org.gotson.komga.interfaces.rest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.MediaType @@ -14,9 +13,8 @@ import org.springframework.test.web.servlet.patch @ExtendWith(SpringExtension::class) @SpringBootTest -@AutoConfigureTestDatabase @AutoConfigureMockMvc(printOnlyOnFailure = false) -@ActiveProfiles("demo") +@ActiveProfiles("demo", "test") class UserControllerTest( @Autowired private val mockMvc: MockMvc diff --git a/komga/src/test/resources/application-test.yml b/komga/src/test/resources/application-test.yml index 7493c0d41..39f889c84 100644 --- a/komga/src/test/resources/application-test.yml +++ b/komga/src/test/resources/application-test.yml @@ -1,8 +1,8 @@ application.version: TESTING komga: - database-backup: - enabled: false + database: + file: ":memory:" spring: datasource: