From 312c1daa81bbb3d7cf36e3a87dfa883fc80b22d5 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Wed, 18 Mar 2026 18:37:17 +0100 Subject: [PATCH 1/7] Trigger deletion of orphaned TitleInstance objects when their only TIPP gets deleted --- .../domain/org/gokb/cred/Package.groovy | 84 +++++++++++++++++-- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/server/grails-app/domain/org/gokb/cred/Package.groovy b/server/grails-app/domain/org/gokb/cred/Package.groovy index b84d35e68..b98f37241 100644 --- a/server/grails-app/domain/org/gokb/cred/Package.groovy +++ b/server/grails-app/domain/org/gokb/cred/Package.groovy @@ -377,14 +377,15 @@ class Package extends KBComponent { RefdataValue expected_status = RefdataCategory.lookup('KBComponent.Status', 'Expected') RefdataValue rr_open = RefdataCategory.lookup('ReviewRequest.Status', 'Open') RefdataValue rr_closed = RefdataCategory.lookup('ReviewRequest.Status', 'Closed') - RefdataValue combo_type = RefdataCategory.lookup('Combo.Type', 'Package.Tipps') + RefdataValue combo_type_package = RefdataCategory.lookup('Combo.Type', 'Package.Tipps') + RefdataValue combo_type_title = RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps') def qry_params = [ ret: new_status, sce: [expected_status, current_status], comment: "Status set to ${new_status.value} due to package change!", pkg: this.id, - ctype: combo_type, + ctp: combo_type_package, now: new Date(), rdate: date ] @@ -403,9 +404,11 @@ class Package extends KBComponent { select 1 from Combo where fromComponent.id = :pkg and toComponent.id = t.id - and type = :ctype + and type = :ctp )''' + TitleInstancePackagePlatform.executeUpdate(qry, qry_params) + def rr_qry = '''update ReviewRequest as rr set rr.status = :closed, rr.lastUpdated = :now @@ -414,19 +417,80 @@ class Package extends KBComponent { select 1 from Combo where fromComponent.id = :pkg and toComponent.id = rr.componentToReview.id - and type = :ctype + and type = :ctp )''' def params_rr = [ closed: rr_closed, open: rr_open, pkg: this.id, - ctype: combo_type, + ctype: combo_type_package, now: new Date() ] - TitleInstancePackagePlatform.executeUpdate(qry, qry_params) ReviewRequest.executeUpdate(rr_qry, params_rr) + + def ti_qry = '''update TitleInstance as ti + set ti.status = :ns, + ti.lastUpdateComment = :comment, + ti.lastUpdated = :now + where ti.status in :sce + and exists ( + select 1 from Combo as ct + where fromComponent = ti + and type = :ctt + and exists ( + select 1 from Combo as cp + where fromComponent = :pkg + and type = :ctp + and toComponent = ct.toComponent + ) + ) + and not exists ( + select 1 from Combo as ct + where fromComponent = ti + and type = :ctt + and exists ( + select 1 from Combo as cp + where fromComponent != :pkg + and toComponent = ct.toComponent + ) + )''' + + Map params_ti = [ + ns: new_status, + sce: [expected_status, current_status], + comment: "Deleted due to remaing package deletion!", + pkg: this.id, + ctp: combo_type_package, + ctt: combo_type_title, + now: new Date() + ] + + TitleInstance.executeUpdate(ti_qry, params_ti) + + def ti_combo_qry = '''delete from Combo as ct + where type = :ctt + and exists ( + select 1 from Combo as cp + where type = :ctp + and fromComponent = :pkg + and toComponent = ct.toComponent + ) + and not exists ( + select 1 from Combo as ct2 + where type = :ctt + and fromComponent = ct.fromComponent + and toComponent != ct.toComponent + )''' + + Map combo_params = [ + pkg: this.id, + ctp: combo_type_package, + ctt: combo_type_title + ] + + Combo.executeUpdate(ti_combo_qry, combo_params) } @@ -668,7 +732,13 @@ class Package extends KBComponent { } } - def user = springSecurityService?.currentUser + User user + + try { + user = springSecurityService?.currentUser + } + catch (Exception e) {} + if (user != null) { this.lastUpdatedBy = user } From a5e75eede3b551d407b082fe532c6f6297fc8c0d Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Thu, 19 Mar 2026 16:53:30 +0100 Subject: [PATCH 2/7] add admin controller method for id cleanup --- .../org/gokb/AdminController.groovy | 41 +++++++++++++++--- .../org/gokb/PackageCleanupService.groovy | 42 +++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/server/grails-app/controllers/org/gokb/AdminController.groovy b/server/grails-app/controllers/org/gokb/AdminController.groovy index 78acdb55f..205c4e22a 100644 --- a/server/grails-app/controllers/org/gokb/AdminController.groovy +++ b/server/grails-app/controllers/org/gokb/AdminController.groovy @@ -9,7 +9,7 @@ import org.hibernate.criterion.CriteriaSpecification import org.springframework.security.access.annotation.Secured import org.springframework.security.acls.domain.BasePermission -import java.time.LocalDateTime +import java.time.* import java.util.concurrent.CancellationException import grails.gorm.transactions.* @@ -477,9 +477,9 @@ class AdminController { def cacheSinglePackage() { log.debug("Manual package caching for ID ${params.id}") - def result = [params: params, result: null] + Map result = [params: params, result: null] - def pkg = Package.findByUuid(params.id) + Package pkg = Package.findByUuid(params.id) if (!pkg && params.long('id')) { pkg = Package.get(params.long('id')) @@ -503,8 +503,8 @@ class AdminController { def deduplicatePackageTipps() { log.debug("Manual TIPP deduplication for ID ${params.id}") - def result = [params: params, result: null] - def pkgId = params.int('id') ?: null + Map result = [params: params, result: null] + Long pkgId = params.long('id') ?: null if (pkgId) { Job j = concurrencyManagerService.createJob { job -> @@ -519,6 +519,37 @@ class AdminController { render(view: "logViewer", model: logViewer()) } + def revertLinkedTiIds() { + log.debug("Manual TIPP deduplication for ID ${params.id}") + Map result = [params: params, result: null] + Long pkgId = params.long('id') ?: null + LocalDate linkDate + + try { + linkDate = LocalDate.from(params.date) + } + catch (Exception e) {} + + if (pkgId && linkDate) { + Job j = concurrencyManagerService.createJob { job -> + packageCleanupService.revertTitleIds(pkgId, linkDate, job) + }.startOrQueue() + + j.description = "Deduplicating package TIPPs for package ${pkgId}" + j.type = RefdataCategory.lookupOrCreate('Job.Type', 'Package TI Id Cleanup') + j.startTime = new Date() + + render(view: "logViewer", model: logViewer()) + } + else { + result.result = 'ERROR' + response.status = 400 + + render result as JSON + } + } + + def fetchEzbCollections() { log.debug("Triggering EZB open collections sync") diff --git a/server/grails-app/services/org/gokb/PackageCleanupService.groovy b/server/grails-app/services/org/gokb/PackageCleanupService.groovy index c0fb16830..bed730e86 100644 --- a/server/grails-app/services/org/gokb/PackageCleanupService.groovy +++ b/server/grails-app/services/org/gokb/PackageCleanupService.groovy @@ -6,12 +6,15 @@ import grails.gorm.transactions.Transactional import groovy.util.logging.Slf4j +import java.time.* + import org.gokb.cred.* @Slf4j class PackageCleanupService { def tippService + def titleAugmentService def reactivateReplacedTipps(pid, Job j = null) { def result = [result: 'OK', cases: 0, additionalDeletes: 0, total: 0] @@ -101,4 +104,43 @@ class PackageCleanupService { result } + + def revertTitleIds(Long pid, LocalDate date, Job job) { + Package.withNewSession { + def combos_qry = '''select id, fromComponent.id from Combo as cid + where type = :cti + and dateCreated between :dateStart and :dateEnd + and exists ( + select 1 from ct + where type = :ctt + and fromComponent = cid.fromComponent + and exists ( + select 1 from Combo as cp + where type = :ctp + and toComponent = ct.toComponent + and fromComponent.id = :pid + ) + ) + order by fromComponent.id''' + + Map pars = [ + cti: RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids'), + ctt: RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps'), + ctp: RefdataCategory.lookup('Combo.Type', 'Package.Tipps'), + dateStart: date, + dateEnd: date.plusDays(1) + ] + + List results = Combo.executeQuery(combos_qry, pars) + + Long last_id + + for (c in results) { + Combo ctd = Combo.get(c[0]).delete() + TitleInstance ti = TitleInstance.get(c[0]) + + titleAugmentService.touchTitleTipps(ti, false) + } + } + } } \ No newline at end of file From ec137d4c9f4e8925db9943c4758567e4aed7fb70 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Mon, 23 Mar 2026 18:47:20 +0100 Subject: [PATCH 3/7] add test --- .../org/gokb/PackageCleanupServiceSpec.groovy | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy diff --git a/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy new file mode 100644 index 000000000..226ae983e --- /dev/null +++ b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy @@ -0,0 +1,51 @@ +package org.gokb + +import grails.testing.mixin.integration.Integration +import org.gokb.PackageCleanupService +import org.gokb.cred.* +import org.hibernate.SessionFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.annotation.Rollback +import org.springframework.transaction.annotation.Transactional + +import spock.lang.Specification + +@Integration +@Transactional +@Rollback +class PackageCleanupServiceSpec extends Specification { + + @Autowired + PackageCleanupService packageCleanupService + + def autoTimestampEventListener + + def setup() { + RefdataValue status_deleted = RefdataCategory.lookup('KBComponent.Status', 'Deleted') + RefdataValue combo_type_id = RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids') + IdentifierNamespace ns_doi = IdentifierNamespace.findByValue('doi') + def old_id_date = java.sql.Date.valueOf(LocalDate.now().minusMonths(1)) + + BookInstance test_ti = BookInstance.findByName('PackageCleanupTitle') + Identifier new_id = Identifier.findByValue('10.2333/663633444') ?: new Identifier(value: '10.2333/663633444', namespace: ns_doi).save(flush: true, failOnError: true) + Identifier old_id = Identifier.findByValue('10.2333/345435535') ?: new Identifier(value: '10.2333/345435535', namespace: ns_doi).save(flush: true, failOnError: true) + + if (!test_ti) { + test_ti = new BookInstance(name: 'PackageCleanupTitle').save(flush: true, failOnError: true) + test_ti.ids << new_id + test_ti.save(flush: true) + + autoTimestampEventListener.withoutTimestamps { + new Combo(fromComponent: test_ti, toComponent: old_id, type: combo_type_id, dateCreated: old_id_date).save(flush: true) + } + } + } + + def cleanup() { + + } + + void "test selective ID removal by date"() { + + } +} From 6a485e77d26bb199a0e3ad91d366cabcff010299 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Thu, 16 Apr 2026 14:23:09 +0200 Subject: [PATCH 4/7] add cleanup method & test --- .../domain/org/gokb/cred/Package.groovy | 84 ++----------- .../org/gokb/PackageCleanupService.groovy | 111 +++++++++++++----- .../org/gokb/PackageCleanupServiceSpec.groovy | 58 ++++++++- 3 files changed, 145 insertions(+), 108 deletions(-) diff --git a/server/grails-app/domain/org/gokb/cred/Package.groovy b/server/grails-app/domain/org/gokb/cred/Package.groovy index b98f37241..b84d35e68 100644 --- a/server/grails-app/domain/org/gokb/cred/Package.groovy +++ b/server/grails-app/domain/org/gokb/cred/Package.groovy @@ -377,15 +377,14 @@ class Package extends KBComponent { RefdataValue expected_status = RefdataCategory.lookup('KBComponent.Status', 'Expected') RefdataValue rr_open = RefdataCategory.lookup('ReviewRequest.Status', 'Open') RefdataValue rr_closed = RefdataCategory.lookup('ReviewRequest.Status', 'Closed') - RefdataValue combo_type_package = RefdataCategory.lookup('Combo.Type', 'Package.Tipps') - RefdataValue combo_type_title = RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps') + RefdataValue combo_type = RefdataCategory.lookup('Combo.Type', 'Package.Tipps') def qry_params = [ ret: new_status, sce: [expected_status, current_status], comment: "Status set to ${new_status.value} due to package change!", pkg: this.id, - ctp: combo_type_package, + ctype: combo_type, now: new Date(), rdate: date ] @@ -404,11 +403,9 @@ class Package extends KBComponent { select 1 from Combo where fromComponent.id = :pkg and toComponent.id = t.id - and type = :ctp + and type = :ctype )''' - TitleInstancePackagePlatform.executeUpdate(qry, qry_params) - def rr_qry = '''update ReviewRequest as rr set rr.status = :closed, rr.lastUpdated = :now @@ -417,80 +414,19 @@ class Package extends KBComponent { select 1 from Combo where fromComponent.id = :pkg and toComponent.id = rr.componentToReview.id - and type = :ctp + and type = :ctype )''' def params_rr = [ closed: rr_closed, open: rr_open, pkg: this.id, - ctype: combo_type_package, + ctype: combo_type, now: new Date() ] + TitleInstancePackagePlatform.executeUpdate(qry, qry_params) ReviewRequest.executeUpdate(rr_qry, params_rr) - - def ti_qry = '''update TitleInstance as ti - set ti.status = :ns, - ti.lastUpdateComment = :comment, - ti.lastUpdated = :now - where ti.status in :sce - and exists ( - select 1 from Combo as ct - where fromComponent = ti - and type = :ctt - and exists ( - select 1 from Combo as cp - where fromComponent = :pkg - and type = :ctp - and toComponent = ct.toComponent - ) - ) - and not exists ( - select 1 from Combo as ct - where fromComponent = ti - and type = :ctt - and exists ( - select 1 from Combo as cp - where fromComponent != :pkg - and toComponent = ct.toComponent - ) - )''' - - Map params_ti = [ - ns: new_status, - sce: [expected_status, current_status], - comment: "Deleted due to remaing package deletion!", - pkg: this.id, - ctp: combo_type_package, - ctt: combo_type_title, - now: new Date() - ] - - TitleInstance.executeUpdate(ti_qry, params_ti) - - def ti_combo_qry = '''delete from Combo as ct - where type = :ctt - and exists ( - select 1 from Combo as cp - where type = :ctp - and fromComponent = :pkg - and toComponent = ct.toComponent - ) - and not exists ( - select 1 from Combo as ct2 - where type = :ctt - and fromComponent = ct.fromComponent - and toComponent != ct.toComponent - )''' - - Map combo_params = [ - pkg: this.id, - ctp: combo_type_package, - ctt: combo_type_title - ] - - Combo.executeUpdate(ti_combo_qry, combo_params) } @@ -732,13 +668,7 @@ class Package extends KBComponent { } } - User user - - try { - user = springSecurityService?.currentUser - } - catch (Exception e) {} - + def user = springSecurityService?.currentUser if (user != null) { this.lastUpdatedBy = user } diff --git a/server/grails-app/services/org/gokb/PackageCleanupService.groovy b/server/grails-app/services/org/gokb/PackageCleanupService.groovy index bed730e86..83345bf64 100644 --- a/server/grails-app/services/org/gokb/PackageCleanupService.groovy +++ b/server/grails-app/services/org/gokb/PackageCleanupService.groovy @@ -105,42 +105,97 @@ class PackageCleanupService { result } - def revertTitleIds(Long pid, LocalDate date, Job job) { - Package.withNewSession { - def combos_qry = '''select id, fromComponent.id from Combo as cid - where type = :cti - and dateCreated between :dateStart and :dateEnd - and exists ( - select 1 from ct - where type = :ctt - and fromComponent = cid.fromComponent + public Map revertTitleIds(Long pid, LocalDate date, Job j = null) { + Map result = [:] + + try { + def session = sessionFactory.currentSession + result = processTitleIdCleanup(session, pid, date, j) + } + catch (Exception e) { + Package.withNewSession { session -> + result = processTitleIdCleanup(session, pid, date, j) + } + } + + result + } + + private Map processTitleIdCleanup(session, Long pid, LocalDate date, Job job = null) { + Map result = [result: 'OK', cleanups: 0] + RefdataValue type_ids = RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids') + RefdataValue type_ti_tipps = RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps') + RefdataValue type_pkg_tipps = RefdataCategory.lookup('Combo.Type', 'Package.Tipps') + log.debug("revertTitleIds :: Processing id links for Package ${pid} between ${java.sql.Date.valueOf(date)} and ${java.sql.Date.valueOf(date.plusDays(1))} ..") + + def deletion_candidates_qry = '''select id, fromComponent.id from Combo as cid + where type = :cti + and dateCreated between :dateStart and :dateEnd + and exists ( + select 1 from Combo as ct + where type = :ctt + and fromComponent = cid.fromComponent + and exists ( + select 1 from Combo as cp + where type = :ctp + and toComponent = ct.toComponent + and fromComponent.id = :pid + ) + ) + order by fromComponent.id''' + + Map pars = [ + pid: pid, + cti: type_ids, + ctt: type_ti_tipps, + ctp: type_pkg_tipps, + dateStart: java.sql.Date.valueOf(date), + dateEnd: java.sql.Date.valueOf(date.plusDays(1)) + ] + + List delete_candidates = Combo.executeQuery(deletion_candidates_qry, pars) + + def total_ids_qry = '''select id, fromComponent.id from Combo as cid + where type = :cti and exists ( - select 1 from Combo as cp - where type = :ctp - and toComponent = ct.toComponent - and fromComponent.id = :pid + select 1 from Combo as ct + where type = :ctt + and fromComponent = cid.fromComponent + and exists ( + select 1 from Combo as cp + where type = :ctp + and toComponent = ct.toComponent + and fromComponent.id = :pid + ) ) - ) - order by fromComponent.id''' + order by fromComponent.id''' + + Map total_pars = [ + pid: pid, + cti: type_ids, + ctt: type_ti_tipps, + ctp: type_pkg_tipps + ] - Map pars = [ - cti: RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids'), - ctt: RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps'), - ctp: RefdataCategory.lookup('Combo.Type', 'Package.Tipps'), - dateStart: date, - dateEnd: date.plusDays(1) - ] + List total_ids = Combo.executeQuery(total_ids_qry, total_pars) - List results = Combo.executeQuery(combos_qry, pars) + log.debug("revertTitleIds :: Found ${delete_candidates.size()} of ${total_ids.size()} ids to remove ..") - Long last_id + for (c in delete_candidates) { + Combo ctd = Combo.get(c[0]).delete(flush: true) + result.cleanups++ + TitleInstance ti = TitleInstance.get(c[0]) - for (c in results) { - Combo ctd = Combo.get(c[0]).delete() - TitleInstance ti = TitleInstance.get(c[0]) + titleAugmentService.touchTitleTipps(ti, false) - titleAugmentService.touchTitleTipps(ti, false) + if (result.cleanups % 50 == 0) { + session.flush() + session.clear() } } + + job?.endTime = new Date() + + result } } \ No newline at end of file diff --git a/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy index 226ae983e..3c7db3bfa 100644 --- a/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy +++ b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy @@ -1,6 +1,9 @@ package org.gokb import grails.testing.mixin.integration.Integration + +import java.time.* + import org.gokb.PackageCleanupService import org.gokb.cred.* import org.hibernate.SessionFactory @@ -19,12 +22,14 @@ class PackageCleanupServiceSpec extends Specification { PackageCleanupService packageCleanupService def autoTimestampEventListener + LocalDate old_id_ld def setup() { RefdataValue status_deleted = RefdataCategory.lookup('KBComponent.Status', 'Deleted') RefdataValue combo_type_id = RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids') IdentifierNamespace ns_doi = IdentifierNamespace.findByValue('doi') - def old_id_date = java.sql.Date.valueOf(LocalDate.now().minusMonths(1)) + old_id_ld = LocalDate.now().minusMonths(1) + Date old_id_date = Date.from(LocalDateTime.now().minusMonths(1).atZone(ZoneId.systemDefault()).toInstant()) BookInstance test_ti = BookInstance.findByName('PackageCleanupTitle') Identifier new_id = Identifier.findByValue('10.2333/663633444') ?: new Identifier(value: '10.2333/663633444', namespace: ns_doi).save(flush: true, failOnError: true) @@ -36,16 +41,63 @@ class PackageCleanupServiceSpec extends Specification { test_ti.save(flush: true) autoTimestampEventListener.withoutTimestamps { - new Combo(fromComponent: test_ti, toComponent: old_id, type: combo_type_id, dateCreated: old_id_date).save(flush: true) + Combo nc = new Combo(fromComponent: test_ti, toComponent: old_id, type: combo_type_id).save(flush: true, failOnError: true) + nc.dateCreated = old_id_date + nc.save(flush: true) } } + + Package package_cleanup_pkg = Package.findByName('PackageCleanupPackage') + Org package_cleanup_provider = Org.findByName('PackageCleanupProvider') ?: new Org(name: 'PackageCleanupProvider').save(flush: true) + Platform package_cleanup_platform = Platform.findByName('PackageCleanupPlatform') ?: new Platform(name: 'PackageCleanupProvider', provider: package_cleanup_provider).save(flush:true) + + if (!package_cleanup_pkg) { + package_cleanup_pkg = new Package(name: 'PackageCleanupPackage', provider: package_cleanup_provider, nominalPlatform: package_cleanup_platform).save(flush: true) + } + + TitleInstancePackagePlatform package_cleanup_tipp = TitleInstancePackagePlatform.findByName('PackageCleanupTipp') + + if (!package_cleanup_tipp) { + package_cleanup_tipp = new TitleInstancePackagePlatform(name: 'PackageCleanupTipp', url: 'https://test.com/gokbcleanuptest', hostPlatform: package_cleanup_platform, pkg: package_cleanup_pkg, title: test_ti).save(flush: true, failOnError: true) + } } def cleanup() { - + TitleInstancePackagePlatform.findByName('PackageCleanupTipp')?.expunge() + BookInstance.findByName('PackageCleanupTitle')?.expunge() } void "test selective ID removal by date"() { + given: + Package package_cleanup_pkg = Package.findByName('PackageCleanupPackage') + BookInstance test_ti = BookInstance.findByName('PackageCleanupTitle') + + Map total_pars = [ + pid: package_cleanup_pkg.id, + cti: RefdataCategory.lookup('Combo.Type', 'KBComponent.Ids'), + ctt: RefdataCategory.lookup('Combo.Type', 'TitleInstance.Tipps'), + ctp: RefdataCategory.lookup('Combo.Type', 'Package.Tipps') + ] + when: + Map result = packageCleanupService.revertTitleIds(package_cleanup_pkg.id, old_id_ld) + then: + def combos = Combo.executeQuery('''select id, fromComponent.id from Combo as cid + where type = :cti + and exists ( + select 1 from Combo as ct + where type = :ctt + and fromComponent = cid.fromComponent + and exists ( + select 1 from Combo as cp + where type = :ctp + and toComponent = ct.toComponent + and fromComponent.id = :pid + ) + ) + order by fromComponent.id''', total_pars) + combos.size() == 1 || combos[1].dateCreated != null + combos.size() == 1 || combos[0].dateCreated != null + result.cleanups == 1 } } From 74c9c6a5563de9c580a63824a8fe660035b1cda2 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Mon, 20 Apr 2026 17:52:07 +0200 Subject: [PATCH 5/7] fix test --- .../services/org/gokb/PackageCleanupService.groovy | 4 ++++ .../groovy/org/gokb/PackageCleanupServiceSpec.groovy | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/server/grails-app/services/org/gokb/PackageCleanupService.groovy b/server/grails-app/services/org/gokb/PackageCleanupService.groovy index 83345bf64..889a17e1d 100644 --- a/server/grails-app/services/org/gokb/PackageCleanupService.groovy +++ b/server/grails-app/services/org/gokb/PackageCleanupService.groovy @@ -15,6 +15,7 @@ class PackageCleanupService { def tippService def titleAugmentService + def sessionFactory def reactivateReplacedTipps(pid, Job j = null) { def result = [result: 'OK', cases: 0, additionalDeletes: 0, total: 0] @@ -113,7 +114,10 @@ class PackageCleanupService { result = processTitleIdCleanup(session, pid, date, j) } catch (Exception e) { + log.debug("Failed session lookup", e) + Package.withNewSession { session -> + log.debug("revertTitleIds :: creating new session ..") result = processTitleIdCleanup(session, pid, date, j) } } diff --git a/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy index 3c7db3bfa..a6bfc978b 100644 --- a/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy +++ b/server/src/integration-test/groovy/org/gokb/PackageCleanupServiceSpec.groovy @@ -82,7 +82,7 @@ class PackageCleanupServiceSpec extends Specification { when: Map result = packageCleanupService.revertTitleIds(package_cleanup_pkg.id, old_id_ld) then: - def combos = Combo.executeQuery('''select id, fromComponent.id from Combo as cid + def combos = Combo.executeQuery('''from Combo as cid where type = :cti and exists ( select 1 from Combo as ct @@ -96,8 +96,7 @@ class PackageCleanupServiceSpec extends Specification { ) ) order by fromComponent.id''', total_pars) - combos.size() == 1 || combos[1].dateCreated != null - combos.size() == 1 || combos[0].dateCreated != null + combos.size() == 1 result.cleanups == 1 } } From 6b295ae76762500aef2037f8cd3c32fe5b3da124 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Mon, 20 Apr 2026 18:21:02 +0200 Subject: [PATCH 6/7] handle date parsing error --- .../grails-app/controllers/org/gokb/AdminController.groovy | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/grails-app/controllers/org/gokb/AdminController.groovy b/server/grails-app/controllers/org/gokb/AdminController.groovy index 205c4e22a..c9e7cf52f 100644 --- a/server/grails-app/controllers/org/gokb/AdminController.groovy +++ b/server/grails-app/controllers/org/gokb/AdminController.groovy @@ -521,14 +521,17 @@ class AdminController { def revertLinkedTiIds() { log.debug("Manual TIPP deduplication for ID ${params.id}") - Map result = [params: params, result: null] + Map result = [params: params, result: 'OK'] Long pkgId = params.long('id') ?: null LocalDate linkDate try { linkDate = LocalDate.from(params.date) } - catch (Exception e) {} + catch (Exception e) { + result.result = 'ERROR' + result.message = 'Unable to parse date parameter!' + } if (pkgId && linkDate) { Job j = concurrencyManagerService.createJob { job -> From 6514a1a32a1ac44c446234f1affc9b6156c38255 Mon Sep 17 00:00:00 2001 From: Moritz Horn Date: Tue, 21 Apr 2026 11:54:11 +0200 Subject: [PATCH 7/7] fix cleanup --- .../org/gokb/PackageCleanupService.groovy | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/server/grails-app/services/org/gokb/PackageCleanupService.groovy b/server/grails-app/services/org/gokb/PackageCleanupService.groovy index 889a17e1d..37024c46b 100644 --- a/server/grails-app/services/org/gokb/PackageCleanupService.groovy +++ b/server/grails-app/services/org/gokb/PackageCleanupService.groovy @@ -9,6 +9,7 @@ import groovy.util.logging.Slf4j import java.time.* import org.gokb.cred.* +import org.hibernate.HibernateException @Slf4j class PackageCleanupService { @@ -113,8 +114,8 @@ class PackageCleanupService { def session = sessionFactory.currentSession result = processTitleIdCleanup(session, pid, date, j) } - catch (Exception e) { - log.debug("Failed session lookup", e) + catch (HibernateException e) { + log.debug("Failed session lookup..") Package.withNewSession { session -> log.debug("revertTitleIds :: creating new session ..") @@ -159,36 +160,10 @@ class PackageCleanupService { List delete_candidates = Combo.executeQuery(deletion_candidates_qry, pars) - def total_ids_qry = '''select id, fromComponent.id from Combo as cid - where type = :cti - and exists ( - select 1 from Combo as ct - where type = :ctt - and fromComponent = cid.fromComponent - and exists ( - select 1 from Combo as cp - where type = :ctp - and toComponent = ct.toComponent - and fromComponent.id = :pid - ) - ) - order by fromComponent.id''' - - Map total_pars = [ - pid: pid, - cti: type_ids, - ctt: type_ti_tipps, - ctp: type_pkg_tipps - ] - - List total_ids = Combo.executeQuery(total_ids_qry, total_pars) - - log.debug("revertTitleIds :: Found ${delete_candidates.size()} of ${total_ids.size()} ids to remove ..") - for (c in delete_candidates) { Combo ctd = Combo.get(c[0]).delete(flush: true) result.cleanups++ - TitleInstance ti = TitleInstance.get(c[0]) + TitleInstance ti = TitleInstance.get(c[1]) titleAugmentService.touchTitleTipps(ti, false)