diff --git a/.github/workflows/reportIntermittentTests.yml b/.github/workflows/reportIntermittentTests.yml index b4b2e02c33..6554a7ec73 100644 --- a/.github/workflows/reportIntermittentTests.yml +++ b/.github/workflows/reportIntermittentTests.yml @@ -29,8 +29,11 @@ jobs: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Compile kotlin + run: ./gradlew compileTestKotlin --parallel + - name: Build backend - run: ./gradlew bootJar + run: ./gradlew bootJar --parallel - name: Tar App build run: tar -czf ~/backend-app.tgz ./backend/app/build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95e4df73e9..072a931f3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,9 @@ jobs: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Compile kotlin + run: ./gradlew compileTestKotlin --parallel + - name: Build backend run: ./gradlew bootJar --parallel diff --git a/backend/api/build.gradle b/backend/api/build.gradle index 8ef28addf3..8a2e1767b0 100644 --- a/backend/api/build.gradle +++ b/backend/api/build.gradle @@ -12,9 +12,8 @@ plugins { id 'java' id 'io.spring.dependency-management' id 'org.jetbrains.kotlin.jvm' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id "kotlin-allopen" - } group = 'io.tolgee' @@ -22,7 +21,6 @@ group = 'io.tolgee' apply plugin: 'java' apply plugin: 'idea' apply plugin: "org.jetbrains.kotlin.plugin.spring" -apply plugin: 'org.springframework.boot' apply plugin: "kotlin-allopen" apply plugin: 'io.spring.dependency-management' @@ -31,7 +29,7 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } allOpen { @@ -54,15 +52,9 @@ dependencies { /** * SPRING DOC */ - implementation libs.springDocOpenApiWebMvcCore + implementation libs.springDocWebmvcApi implementation libs.springDocOpenApiUi - implementation libs.springDocOpenApiKotlin - implementation libs.springDocOpenApiDataRest - implementation libs.springDocOpenApiHateoas - implementation dependencies.create(libs.redissonSpringBootStarter.get()) { - exclude group: 'org.redisson', module: 'redisson-spring-data-31' - } - implementation libs.redissonSpringData + implementation libs.redissonSpringBootStarter /** * Misc @@ -74,6 +66,8 @@ dependencies { implementation libs.jjwtJackson implementation("com.github.ben-manes.caffeine:caffeine:3.0.5") api libs.postHog + implementation libs.kotlinReflect + implementation libs.jacksonModuleKotlin } sourceSets { @@ -81,8 +75,11 @@ sourceSets { test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AdministrationController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AdministrationController.kt index d3a6be0684..4cfec5a21b 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AdministrationController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AdministrationController.kt @@ -14,7 +14,7 @@ import io.tolgee.security.authentication.JwtService import io.tolgee.security.authentication.RequiresSuperAuthentication import io.tolgee.service.organization.OrganizationService import io.tolgee.service.security.UserAccountService -import org.springdoc.api.annotations.ParameterObject +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt index 75903ceb68..f677a2ad35 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt @@ -33,6 +33,7 @@ import io.tolgee.service.project.ProjectService import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PermissionService import io.tolgee.service.security.SecurityService +import jakarta.validation.Valid import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -46,7 +47,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/BigMetaController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/BigMetaController.kt index cbb70d8d82..7ca945f437 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/BigMetaController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/BigMetaController.kt @@ -10,6 +10,7 @@ import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.service.bigMeta.BigMetaService +import jakarta.validation.Valid import org.springframework.hateoas.CollectionModel import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -17,7 +18,6 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/KeyController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/KeyController.kt index 8a3bea8ccf..c334595a5f 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/KeyController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/KeyController.kt @@ -26,10 +26,12 @@ import io.tolgee.hateoas.key.KeyWithScreenshotsModelAssembler import io.tolgee.hateoas.language.LanguageModel import io.tolgee.hateoas.language.LanguageModelAssembler import io.tolgee.hateoas.screenshot.ScreenshotModelAssembler +import io.tolgee.model.Language import io.tolgee.model.Project import io.tolgee.model.enums.AssignableTranslationState import io.tolgee.model.enums.Scope import io.tolgee.model.key.Key +import io.tolgee.model.views.LanguageViewImpl import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions @@ -37,7 +39,8 @@ import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.key.KeySearchResultView import io.tolgee.service.key.KeyService import io.tolgee.service.security.SecurityService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.context.ApplicationContext import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler @@ -58,7 +61,6 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController @@ -251,7 +253,7 @@ class KeyController( @Operation(summary = "Returns languages, in which key is disabled") fun getDisabledLanguages(@PathVariable id: Long): CollectionModel { val languages = keyService.getDisabledLanguages(projectHolder.project.id, id) - return languageModelAssembler.toCollectionModel(languages) + return languageModelAssembler.toCollectionModel(languages.toViews()) } @PutMapping("/{id}/disabled-languages") @@ -263,7 +265,12 @@ class KeyController( @RequestBody @Valid dto: SetDisabledLanguagesRequest ): CollectionModel { val languages = keyService.setDisabledLanguages(projectHolder.project.id, id, dto.languageIds) - return languageModelAssembler.toCollectionModel(languages) + return languageModelAssembler.toCollectionModel(languages.toViews()) + } + + private fun List.toViews(): List { + val baseLanguage = projectHolder.projectEntity.baseLanguage + return this.map { LanguageViewImpl(it, it.id == baseLanguage?.id) } } private fun Key.checkInProject() { diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NamespaceController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NamespaceController.kt index 372c45637b..32f60e6a93 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NamespaceController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NamespaceController.kt @@ -16,7 +16,8 @@ import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.key.NamespaceService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault @@ -29,7 +30,6 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/PatController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/PatController.kt index 322125003e..36331392be 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/PatController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/PatController.kt @@ -20,7 +20,8 @@ import io.tolgee.security.authentication.AuthTokenType import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.security.authentication.RequiresSuperAuthentication import io.tolgee.service.security.PatService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -34,7 +35,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @RequestMapping("/v2/pats") diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ProjectActivityController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ProjectActivityController.kt index da4b573dbc..357674368f 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ProjectActivityController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ProjectActivityController.kt @@ -14,7 +14,7 @@ import io.tolgee.model.views.activity.ProjectActivityView import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions -import org.springdoc.api.annotations.ParameterObject +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.MediaTypes diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/SlugController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/SlugController.kt index 09542411a7..7e88bfa302 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/SlugController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/SlugController.kt @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.tags.Tag import io.tolgee.dtos.request.GenerateSlugDto import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.ProjectService +import jakarta.validation.Valid import org.springframework.http.MediaType import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping @@ -17,7 +18,6 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/TagsController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/TagsController.kt index 47eaf55576..776aa2d53c 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/TagsController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/TagsController.kt @@ -17,7 +17,8 @@ import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.key.KeyService import io.tolgee.service.key.TagService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault @@ -31,7 +32,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid import io.swagger.v3.oas.annotations.tags.Tag as OpenApiTag @Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection") diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/UserMfaController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/UserMfaController.kt index 9ff07c1b4e..ae789b9394 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/UserMfaController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/UserMfaController.kt @@ -9,12 +9,12 @@ import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.security.authentication.JwtService import io.tolgee.security.payload.JwtAuthenticationResponse import io.tolgee.service.security.MfaService +import jakarta.validation.Valid import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @RequestMapping("/v2/user/mfa") diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt index 9509b1e8e8..a0500e1ab8 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt @@ -16,6 +16,8 @@ import io.tolgee.exceptions.BadRequestException import io.tolgee.exceptions.ErrorResponseBody import io.tolgee.exceptions.NotFoundException import io.tolgee.hateoas.dataImport.ImportAddFilesResultModel +import io.tolgee.hateoas.dataImport.ImportFileIssueModel +import io.tolgee.hateoas.dataImport.ImportFileIssueModelAssembler import io.tolgee.hateoas.dataImport.ImportLanguageModel import io.tolgee.hateoas.dataImport.ImportLanguageModelAssembler import io.tolgee.hateoas.dataImport.ImportNamespaceModel @@ -37,14 +39,13 @@ import io.tolgee.service.LanguageService import io.tolgee.service.dataImport.ForceMode import io.tolgee.service.dataImport.ImportService import io.tolgee.service.key.NamespaceService -import io.tolgee.service.security.SecurityService -import org.springdoc.api.annotations.ParameterObject +import jakarta.servlet.http.HttpServletRequest +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault import org.springframework.hateoas.CollectionModel -import org.springframework.hateoas.EntityModel import org.springframework.hateoas.PagedModel import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport import org.springframework.http.MediaType @@ -60,7 +61,6 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile -import javax.servlet.http.HttpServletRequest @Suppress("MVCPathVariableInspection") @RestController @@ -86,11 +86,11 @@ class V2ImportController( private val projectHolder: ProjectHolder, private val languageService: LanguageService, private val namespaceService: NamespaceService, - private val securityService: SecurityService + private val importFileIssueModelAssembler: ImportFileIssueModelAssembler ) { @PostMapping("", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @Operation(description = "Prepares provided files to import.", summary = "Add files") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun addFiles( @RequestPart("files") files: Array, @@ -120,7 +120,7 @@ class V2ImportController( @PutMapping("/apply") @Operation(description = "Imports the data prepared in previous step", summary = "Apply") @RequestActivity(ActivityType.IMPORT) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun applyImport( @Parameter(description = "Whether override or keep all translations with unresolved conflicts") @@ -133,7 +133,7 @@ class V2ImportController( @GetMapping("/result") @Operation(description = "Returns the result of preparation.", summary = "Get result") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun getImportResult( @ParameterObject pageable: Pageable @@ -146,7 +146,7 @@ class V2ImportController( @GetMapping("/result/languages/{languageId}") @Operation(description = "Returns language prepared to import.", summary = "Get import language") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun getImportLanguage( @PathVariable("languageId") languageId: Long, @@ -158,7 +158,7 @@ class V2ImportController( @GetMapping("/result/languages/{languageId}/translations") @Operation(description = "Returns translations prepared to import.", summary = "Get translations") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun getImportTranslations( @PathVariable("projectId") projectId: Long, @@ -184,7 +184,7 @@ class V2ImportController( @DeleteMapping("") @Operation(description = "Deletes prepared import data.", summary = "Delete") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun cancelImport() { this.importService.deleteImport(projectHolder.project.id, authenticationFacade.authenticatedUser.id) @@ -192,7 +192,7 @@ class V2ImportController( @DeleteMapping("/result/languages/{languageId}") @Operation(description = "Deletes language prepared to import.", summary = "Delete language") - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun deleteLanguage(@PathVariable("languageId") languageId: Long) { val language = checkImportLanguageInProject(languageId) @@ -204,7 +204,7 @@ class V2ImportController( description = "Resolves translation conflict. The old translation will be overridden.", summary = "Resolve conflict (override)" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun resolveTranslationSetOverride( @PathVariable("languageId") languageId: Long, @@ -218,7 +218,7 @@ class V2ImportController( description = "Resolves translation conflict. The old translation will be kept.", summary = "Resolve conflict (keep existing)" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun resolveTranslationSetKeepExisting( @PathVariable("languageId") languageId: Long, @@ -232,7 +232,7 @@ class V2ImportController( description = "Resolves all translation conflicts for provided language. The old translations will be overridden.", summary = "Resolve all translation conflicts (override)" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun resolveTranslationSetOverride( @PathVariable("languageId") languageId: Long @@ -245,7 +245,7 @@ class V2ImportController( description = "Resolves all translation conflicts for provided language. The old translations will be kept.", summary = "Resolve all translation conflicts (keep existing)" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun resolveTranslationSetKeepExisting( @PathVariable("languageId") languageId: Long, @@ -258,7 +258,7 @@ class V2ImportController( description = "Sets namespace for file to import.", summary = "Select namespace" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun selectNamespace( @PathVariable fileId: Long, @@ -275,7 +275,7 @@ class V2ImportController( "Data will be imported to selected existing language when applied.", summary = "Pair existing language" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun selectExistingLanguage( @PathVariable("importLanguageId") importLanguageId: Long, @@ -291,7 +291,7 @@ class V2ImportController( description = "Resets existing language paired with language to import.", summary = "Reset existing language pairing" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun resetExistingLanguage( @PathVariable("importLanguageId") importLanguageId: Long, @@ -305,15 +305,15 @@ class V2ImportController( description = "Returns issues for uploaded file.", summary = "Get file issues" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun getImportFileIssues( @PathVariable("importFileId") importFileId: Long, @ParameterObject pageable: Pageable - ): PagedModel> { + ): PagedModel { checkFileFromProject(importFileId) val page = importService.getFileIssues(importFileId, pageable) - return pagedImportFileIssueResourcesAssembler.toModel(page) + return pagedImportFileIssueResourcesAssembler.toModel(page, importFileIssueModelAssembler) } @GetMapping("/all-namespaces") @@ -321,7 +321,7 @@ class V2ImportController( description = "Returns all existing and imported namespaces", summary = "Get namespaces" ) - @RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ]) + @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess fun getAllNamespaces(): CollectionModel { val import = importService.get( diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2LanguagesController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2LanguagesController.kt index 46620c3e97..3df42eaf36 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2LanguagesController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2LanguagesController.kt @@ -15,8 +15,9 @@ import io.tolgee.dtos.request.LanguageDto import io.tolgee.exceptions.BadRequestException import io.tolgee.hateoas.language.LanguageModel import io.tolgee.hateoas.language.LanguageModelAssembler -import io.tolgee.model.Language import io.tolgee.model.enums.Scope +import io.tolgee.model.views.LanguageView +import io.tolgee.model.views.LanguageViewImpl import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions @@ -24,7 +25,8 @@ import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.LanguageService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.SecurityService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -37,7 +39,6 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection") @RestController @@ -59,13 +60,13 @@ class V2LanguagesController( private val languageValidator: LanguageValidator, private val securityService: SecurityService, private val languageModelAssembler: LanguageModelAssembler, - private val pagedAssembler: PagedResourcesAssembler, + private val pagedAssembler: PagedResourcesAssembler, private val projectHolder: ProjectHolder, ) : IController { @PostMapping(value = [""]) @Operation(summary = "Creates language") @RequestActivity(ActivityType.CREATE_LANGUAGE) - @RequiresProjectPermissions([ Scope.LANGUAGES_EDIT ]) + @RequiresProjectPermissions([Scope.LANGUAGES_EDIT]) @AllowApiAccess fun createLanguage( @PathVariable("projectId") projectId: Long, @@ -74,21 +75,23 @@ class V2LanguagesController( val project = projectService.get(projectId) languageValidator.validateCreate(dto, project) val language = languageService.createLanguage(dto, project) - return languageModelAssembler.toModel(language) + return languageModelAssembler.toModel(LanguageViewImpl(language, false)) } @Operation(summary = "Edits language") @PutMapping(value = ["/{languageId}"]) @RequestActivity(ActivityType.EDIT_LANGUAGE) - @RequiresProjectPermissions([ Scope.LANGUAGES_EDIT ]) + @RequiresProjectPermissions([Scope.LANGUAGES_EDIT]) @AllowApiAccess fun editLanguage( @RequestBody @Valid dto: LanguageDto, @PathVariable("languageId") languageId: Long ): LanguageModel { languageValidator.validateEdit(languageId, dto) - val language = languageService.get(languageId) - return languageModelAssembler.toModel(languageService.editLanguage(language, dto)) + val view = languageService.getView(languageId) + languageService.editLanguage(view.language, dto) + val languageView = languageService.getView(languageId) + return languageModelAssembler.toModel(languageView) } @GetMapping(value = [""]) @@ -108,14 +111,14 @@ class V2LanguagesController( @UseDefaultPermissions @AllowApiAccess fun get(@PathVariable("languageId") id: Long): LanguageModel { - val language = languageService.get(id) - return languageModelAssembler.toModel(language) + val languageView = languageService.getView(id) + return languageModelAssembler.toModel(languageView) } @Operation(summary = "Deletes specific language") @DeleteMapping(value = ["/{languageId}"]) @RequestActivity(ActivityType.DELETE_LANGUAGE) - @RequiresProjectPermissions([ Scope.LANGUAGES_EDIT ]) + @RequiresProjectPermissions([Scope.LANGUAGES_EDIT]) @AllowApiAccess fun deleteLanguage(@PathVariable languageId: Long) { val language = languageService.get(languageId) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsController.kt index feb5c220c6..f7cb0cacaf 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsController.kt @@ -46,7 +46,8 @@ import io.tolgee.service.project.ProjectService import io.tolgee.service.security.PermissionService import io.tolgee.service.security.UserAccountService import io.tolgee.service.translation.AutoTranslationService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler @@ -67,7 +68,6 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile -import javax.validation.Valid @Suppress(names = ["MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection"]) @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsInvitationController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsInvitationController.kt index af5981727a..df14c911dc 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsInvitationController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ProjectsInvitationController.kt @@ -19,12 +19,12 @@ import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.RequiresSuperAuthentication import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.service.InvitationService +import jakarta.validation.Valid import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress(names = ["MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection"]) @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2UserController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2UserController.kt index 624d73fbd1..ee6c745eac 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2UserController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2UserController.kt @@ -20,6 +20,7 @@ import io.tolgee.service.ImageUploadService import io.tolgee.service.organization.OrganizationService import io.tolgee.service.security.MfaService import io.tolgee.service.security.UserAccountService +import jakarta.validation.Valid import org.springframework.hateoas.CollectionModel import org.springframework.http.HttpStatus import org.springframework.http.MediaType @@ -27,7 +28,6 @@ import org.springframework.http.ResponseEntity import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -import javax.validation.Valid @RestController @RequestMapping("/v2/user") diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementController.kt index ab4856f983..48c5e3d09b 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementController.kt @@ -16,7 +16,8 @@ import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.security.SecurityService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault @@ -28,7 +29,6 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/StartBatchJobController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/StartBatchJobController.kt index 4c71f87539..7b58d8d8b2 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/StartBatchJobController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/StartBatchJobController.kt @@ -24,12 +24,12 @@ import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.service.security.SecurityService +import jakarta.validation.Valid import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/V2ExportController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/V2ExportController.kt index 7665b20735..3cd30a5400 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/V2ExportController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/batch/V2ExportController.kt @@ -14,7 +14,7 @@ import io.tolgee.service.LanguageService import io.tolgee.service.export.ExportService import io.tolgee.util.StreamingResponseBodyProvider import org.apache.tomcat.util.http.fileupload.IOUtils -import org.springdoc.api.annotations.ParameterObject +import org.springdoc.core.annotations.ParameterObject import org.springframework.http.ContentDisposition import org.springframework.http.HttpHeaders import org.springframework.http.MediaType diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/contentDelivery/ContentDeliveryConfigController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/contentDelivery/ContentDeliveryConfigController.kt index a85f61636c..b2d03fa352 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/contentDelivery/ContentDeliveryConfigController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/contentDelivery/ContentDeliveryConfigController.kt @@ -13,7 +13,8 @@ import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.service.contentDelivery.ContentDeliveryConfigService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -26,7 +27,6 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt index d1f8d98f9f..8a2f4d7368 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt @@ -47,7 +47,8 @@ import io.tolgee.service.organization.OrganizationService import io.tolgee.service.organization.OrganizationStatsService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.UserAccountService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort @@ -72,7 +73,6 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile -import javax.validation.Valid @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationProjectController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationProjectController.kt index e3b846e564..4c9076a9e4 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationProjectController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationProjectController.kt @@ -15,7 +15,7 @@ import io.tolgee.model.views.ProjectWithLanguagesView import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.ProjectService -import org.springdoc.api.annotations.ParameterObject +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.data.web.SortDefault diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/suggestion/TranslationSuggestionController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/suggestion/TranslationSuggestionController.kt index d6702c1b9c..51950cfcaa 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/suggestion/TranslationSuggestionController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/suggestion/TranslationSuggestionController.kt @@ -18,7 +18,8 @@ import io.tolgee.service.LanguageService import io.tolgee.service.key.KeyService import io.tolgee.service.security.SecurityService import io.tolgee.service.translation.TranslationMemoryService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -29,7 +30,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody -import javax.validation.Valid @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationCommentController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationCommentController.kt index cc57b0ed86..95ee2aff3d 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationCommentController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationCommentController.kt @@ -31,7 +31,8 @@ import io.tolgee.security.authorization.UseDefaultPermissions import io.tolgee.service.security.SecurityService import io.tolgee.service.translation.TranslationCommentService import io.tolgee.service.translation.TranslationService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -47,7 +48,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection") @RestController diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt index 9e96464510..783a3c74cf 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt @@ -48,7 +48,8 @@ import io.tolgee.service.key.ScreenshotService import io.tolgee.service.query_builders.CursorUtil import io.tolgee.service.security.SecurityService import io.tolgee.service.translation.TranslationService -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.beans.propertyeditors.CustomCollectionEditor import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable @@ -58,6 +59,7 @@ import org.springframework.data.web.SortDefault import org.springframework.hateoas.PagedModel import org.springframework.http.CacheControl import org.springframework.http.ResponseEntity +import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.WebDataBinder import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping @@ -72,7 +74,6 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import org.springframework.web.context.request.WebRequest import java.util.concurrent.TimeUnit -import javax.validation.Valid @Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection") @RestController @@ -235,10 +236,13 @@ When null, resulting file will be a flat key-value object. @Operation(summary = "Returns translations in project") @UseDefaultPermissions // Security: check done internally @AllowApiAccess + @Transactional fun getTranslations( @ParameterObject @ModelAttribute("translationFilters") params: GetTranslationsParams, @ParameterObject pageable: Pageable ): KeysWithTranslationsPageModel { + val baseLanguage = projectHolder.projectEntity.baseLanguage + val languages: Set = languageService.getLanguagesForTranslationsView( params.languages, projectHolder.project.id, @@ -257,7 +261,7 @@ When null, resulting file will be a flat key-value object. } val cursor = if (data.content.isNotEmpty()) CursorUtil.getCursor(data.content.last(), data.sort) else null - return pagedAssembler.toTranslationModel(data, languages, cursor) + return pagedAssembler.toTranslationModel(data, languages, cursor, baseLanguage) } @GetMapping(value = ["select-all"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/component/TolgeeCacheErrorHandler.kt b/backend/api/src/main/kotlin/io/tolgee/component/TolgeeCacheErrorHandler.kt index 8d21e09de2..b3d6355222 100644 --- a/backend/api/src/main/kotlin/io/tolgee/component/TolgeeCacheErrorHandler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/component/TolgeeCacheErrorHandler.kt @@ -20,7 +20,6 @@ import org.redisson.client.RedisException import org.slf4j.LoggerFactory import org.springframework.cache.Cache import org.springframework.cache.interceptor.SimpleCacheErrorHandler -import java.lang.RuntimeException class TolgeeCacheErrorHandler : SimpleCacheErrorHandler() { private val logger = LoggerFactory.getLogger(TolgeeCacheErrorHandler::class.java) diff --git a/backend/api/src/main/kotlin/io/tolgee/component/TransferEncodingHeaderDebugFilter.kt b/backend/api/src/main/kotlin/io/tolgee/component/TransferEncodingHeaderDebugFilter.kt index 0d57e387f5..a57290c61c 100644 --- a/backend/api/src/main/kotlin/io/tolgee/component/TransferEncodingHeaderDebugFilter.kt +++ b/backend/api/src/main/kotlin/io/tolgee/component/TransferEncodingHeaderDebugFilter.kt @@ -1,10 +1,10 @@ package io.tolgee.component import io.tolgee.util.Logging +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse class TransferEncodingHeaderDebugFilter : OncePerRequestFilter(), Logging { init { diff --git a/backend/api/src/main/kotlin/io/tolgee/component/VersionFilter.kt b/backend/api/src/main/kotlin/io/tolgee/component/VersionFilter.kt index c58df45375..165b214b79 100644 --- a/backend/api/src/main/kotlin/io/tolgee/component/VersionFilter.kt +++ b/backend/api/src/main/kotlin/io/tolgee/component/VersionFilter.kt @@ -1,11 +1,11 @@ package io.tolgee.component import io.tolgee.util.VersionProvider +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class VersionFilter( diff --git a/backend/api/src/main/kotlin/io/tolgee/controllers/ImageStorageController.kt b/backend/api/src/main/kotlin/io/tolgee/controllers/ImageStorageController.kt index f581da2cb8..29cc430daf 100644 --- a/backend/api/src/main/kotlin/io/tolgee/controllers/ImageStorageController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/controllers/ImageStorageController.kt @@ -14,13 +14,13 @@ import io.tolgee.security.authentication.JwtService import io.tolgee.service.ImageUploadService.Companion.UPLOADED_IMAGES_STORAGE_FOLDER_NAME import io.tolgee.service.key.ScreenshotService.Companion.SCREENSHOTS_STORAGE_FOLDER_NAME import io.tolgee.service.security.SecurityService +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/api/src/main/kotlin/io/tolgee/controllers/PublicController.kt b/backend/api/src/main/kotlin/io/tolgee/controllers/PublicController.kt index 97d9ebe514..ff2b19e7f7 100644 --- a/backend/api/src/main/kotlin/io/tolgee/controllers/PublicController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/controllers/PublicController.kt @@ -26,6 +26,9 @@ import io.tolgee.service.security.ReCaptchaValidationService import io.tolgee.service.security.SignUpService import io.tolgee.service.security.UserAccountService import io.tolgee.service.security.UserCredentialsService +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.apache.commons.lang3.RandomStringUtils import org.springframework.http.MediaType import org.springframework.transaction.annotation.Transactional @@ -37,9 +40,6 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.util.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull @RestController @RequestMapping("/api/public") diff --git a/backend/api/src/main/kotlin/io/tolgee/facade/ProjectWithStatsFacade.kt b/backend/api/src/main/kotlin/io/tolgee/facade/ProjectWithStatsFacade.kt index 5cf2cc98e2..f1c6980810 100644 --- a/backend/api/src/main/kotlin/io/tolgee/facade/ProjectWithStatsFacade.kt +++ b/backend/api/src/main/kotlin/io/tolgee/facade/ProjectWithStatsFacade.kt @@ -7,8 +7,8 @@ import io.tolgee.hateoas.project.ProjectWithStatsModelAssembler import io.tolgee.model.enums.TranslationState import io.tolgee.model.views.ProjectWithLanguagesView import io.tolgee.model.views.ProjectWithStatsView +import io.tolgee.service.LanguageService import io.tolgee.service.project.LanguageStatsService -import io.tolgee.service.project.ProjectService import io.tolgee.service.project.ProjectStatsService import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl @@ -24,16 +24,16 @@ class ProjectWithStatsFacade( private val projectStatsService: ProjectStatsService, private val pagedWithStatsResourcesAssembler: PagedResourcesAssembler, private val projectWithStatsModelAssembler: ProjectWithStatsModelAssembler, - private val projectService: ProjectService, - private val languageStatsService: LanguageStatsService + private val languageStatsService: LanguageStatsService, + private val languageService: LanguageService ) { fun getPagedModelWithStats( projects: Page, ): PagedModel { val projectIds = projects.content.map { it.id } val totals = projectStatsService.getProjectsTotals(projectIds) - val languages = projectService.getProjectsWithFetchedLanguages(projectIds) - .associate { it.id to it.languages.toList() } + val languages = languageService.getViewsOfProjects(projectIds) + .groupBy { it.language.project.id } val languageStats = languageStatsService.getLanguageStats(projectIds) @@ -69,7 +69,11 @@ class ProjectWithStatsFacade( TranslationState.UNTRANSLATED to untranslatedPercent ) ) - ProjectWithStatsView(projectWithLanguagesView, projectStatistics, languages[projectWithLanguagesView.id]!!) + ProjectWithStatsView( + view = projectWithLanguagesView, + stats = projectStatistics, + languages = languages[projectWithLanguagesView.id] ?: listOf() + ) } val page = PageImpl(projectsWithStatsContent, projects.pageable, projects.totalElements) return pagedWithStatsResourcesAssembler.toModel(page, projectWithStatsModelAssembler) diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/dataImport/ImportFileIssueModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/dataImport/ImportFileIssueModel.kt index b37f116a48..c88827270b 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/dataImport/ImportFileIssueModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/dataImport/ImportFileIssueModel.kt @@ -5,7 +5,7 @@ import io.tolgee.model.views.ImportFileIssueView import org.springframework.hateoas.RepresentationModel import org.springframework.hateoas.server.core.Relation -@Relation(collectionRelation = "fileIssues", itemRelation = "fileIssue") +@Relation(collectionRelation = "importFileIssues", itemRelation = "importFileIssue") open class ImportFileIssueModel( override val id: Long, override val type: FileIssueType, diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/language/LanguageModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/language/LanguageModelAssembler.kt index 1ce3862426..a796b4e4a3 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/language/LanguageModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/language/LanguageModelAssembler.kt @@ -1,22 +1,22 @@ package io.tolgee.hateoas.language import io.tolgee.api.v2.controllers.V2LanguagesController -import io.tolgee.model.Language +import io.tolgee.model.views.LanguageView import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport import org.springframework.stereotype.Component @Component -class LanguageModelAssembler : RepresentationModelAssemblerSupport( +class LanguageModelAssembler : RepresentationModelAssemblerSupport( V2LanguagesController::class.java, LanguageModel::class.java ) { - override fun toModel(entity: Language): LanguageModel { + override fun toModel(view: LanguageView): LanguageModel { return LanguageModel( - id = entity.id, - name = entity.name ?: "", - originalName = entity.originalName, - tag = entity.tag, - flagEmoji = entity.flagEmoji, - base = entity.project.baseLanguage?.id == entity.id + id = view.language.id, + name = view.language.name ?: "", + originalName = view.language.originalName, + tag = view.language.tag, + flagEmoji = view.language.flagEmoji, + base = view.base ) } } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectModelAssembler.kt index 23755f7974..3747287a7a 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectModelAssembler.kt @@ -8,6 +8,7 @@ import io.tolgee.hateoas.organization.SimpleOrganizationModelAssembler import io.tolgee.hateoas.permission.ComputedPermissionModelAssembler import io.tolgee.hateoas.permission.PermissionModelAssembler import io.tolgee.model.UserAccount +import io.tolgee.model.views.LanguageViewImpl import io.tolgee.model.views.ProjectWithLanguagesView import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.AvatarService @@ -46,7 +47,7 @@ class ProjectModelAssembler( avatar = avatarService.getAvatarLinks(view.avatarHash), organizationRole = view.organizationRole, organizationOwner = view.organizationOwner.let { simpleOrganizationModelAssembler.toModel(it) }, - baseLanguage = baseLanguage?.let { languageModelAssembler.toModel(baseLanguage) }, + baseLanguage = baseLanguage?.let { languageModelAssembler.toModel(LanguageViewImpl(baseLanguage, true)) }, directPermission = view.directPermission?.let { permissionModelAssembler.toModel(it) }, computedPermission = computedPermissionModelAssembler.toModel(computedPermissions), ).add(link).also { model -> diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectWithStatsModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectWithStatsModelAssembler.kt index 1e3928b29f..2bf0714f0d 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectWithStatsModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/ProjectWithStatsModelAssembler.kt @@ -7,6 +7,7 @@ import io.tolgee.hateoas.organization.SimpleOrganizationModelAssembler import io.tolgee.hateoas.permission.ComputedPermissionModelAssembler import io.tolgee.hateoas.permission.PermissionModelAssembler import io.tolgee.model.UserAccount +import io.tolgee.model.views.LanguageViewImpl import io.tolgee.model.views.ProjectWithStatsView import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.AvatarService @@ -48,7 +49,7 @@ class ProjectWithStatsModelAssembler( slug = view.slug, avatar = avatarService.getAvatarLinks(view.avatarHash), organizationRole = view.organizationRole, - baseLanguage = baseLanguage?.let { languageModelAssembler.toModel(baseLanguage) }, + baseLanguage = baseLanguage?.let { languageModelAssembler.toModel(LanguageViewImpl(baseLanguage, true)) }, organizationOwner = view.organizationOwner.let { simpleOrganizationModelAssembler.toModel(it) }, directPermission = view.directPermission?.let { permissionModelAssembler.toModel(it) }, computedPermission = computedPermissionModelAssembler.toModel(computedPermissions), diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/SimpleProjectModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/SimpleProjectModelAssembler.kt index ff87aad550..cdaf96ccb0 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/project/SimpleProjectModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/project/SimpleProjectModelAssembler.kt @@ -3,6 +3,7 @@ package io.tolgee.hateoas.project import io.tolgee.api.v2.controllers.V2ProjectsController import io.tolgee.hateoas.language.LanguageModelAssembler import io.tolgee.model.Project +import io.tolgee.model.views.LanguageViewImpl import io.tolgee.service.AvatarService import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport import org.springframework.stereotype.Component @@ -21,7 +22,11 @@ class SimpleProjectModelAssembler( description = entity.description, slug = entity.slug, avatar = avatarService.getAvatarLinks(entity.avatarHash), - baseLanguage = entity.baseLanguage?.let { languageModelAssembler.toModel(it) }, + baseLanguage = entity.baseLanguage?.let { + languageModelAssembler.toModel( + LanguageViewImpl(it, true) + ) + }, ) } } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/quickStart/QuickStartModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/quickStart/QuickStartModelAssembler.kt index 375f7d14a7..ca60f5666c 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/quickStart/QuickStartModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/quickStart/QuickStartModelAssembler.kt @@ -11,7 +11,7 @@ class QuickStartModelAssembler() : RepresentationModelAssemblerSupport, selectedLanguages: Collection, - nextCursor: String? + nextCursor: String?, + baseLanguage: Language? ): KeysWithTranslationsPageModel { val pageModel = toModel(entities, keyWithTranslationsModelAssembler) return KeysWithTranslationsPageModel( content = pageModel.content, metadata = pageModel.metadata, links = pageModel.links.toList().toTypedArray(), - selectedLanguages = selectedLanguages.map { languageModelAssembler.toModel(it) }, + selectedLanguages = selectedLanguages.map { + languageModelAssembler.toModel( + LanguageViewImpl( + it, + it.id == baseLanguage?.id + ) + ) + }, nextCursor = nextCursor, ) } diff --git a/backend/api/src/main/kotlin/io/tolgee/websocket/WebSocketConfig.kt b/backend/api/src/main/kotlin/io/tolgee/websocket/WebSocketConfig.kt index 363967e14c..04955b9ede 100644 --- a/backend/api/src/main/kotlin/io/tolgee/websocket/WebSocketConfig.kt +++ b/backend/api/src/main/kotlin/io/tolgee/websocket/WebSocketConfig.kt @@ -62,10 +62,6 @@ class WebSocketConfig( return message } - - override fun postReceive(message: Message<*>, channel: MessageChannel): Message<*>? { - return super.postReceive(message, channel) - } }) } } diff --git a/backend/app/build.gradle b/backend/app/build.gradle index 28078b1793..244894663d 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -47,9 +47,9 @@ repositories { allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Service") annotation("org.springframework.stereotype.Component") annotation("org.springframework.stereotype.Service") @@ -59,7 +59,7 @@ allOpen { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } dependencies { @@ -104,11 +104,11 @@ dependencies { implementation libs.icu4j implementation libs.jacksonModuleKotlin implementation libs.jacksonDataFormatXml + api 'org.apache.httpcomponents.client5:httpclient5:5.2.1' - testApi dependencies.create(libs.redissonSpringBootStarter.get()) { - exclude group: 'org.redisson', module: 'redisson-spring-data-31' - } - testApi libs.redissonSpringData + implementation "org.springframework.boot:spring-boot-properties-migrator" + + testApi libs.redissonSpringBootStarter /** * KOTLIN @@ -174,7 +174,7 @@ dependencies { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" testLogging { events = ["passed", "failed", "skipped"] } @@ -184,21 +184,21 @@ task runContextRecreatingTests(type: Test, group: 'verification') { useJUnitPlatform { includeTags "contextRecreating" } - maxHeapSize = "8000m" + maxHeapSize = "4096m" } task runStandardTests(type: Test, group: 'verification') { useJUnitPlatform { excludeTags "contextRecreating", "websocket" } - maxHeapSize = "8000m" + maxHeapSize = "4096m" } task runWebsocketTests(type: Test, group: 'verification') { useJUnitPlatform { includeTags "websocket" } - maxHeapSize = "8000m" + maxHeapSize = "4096m" } springBoot { @@ -238,7 +238,7 @@ task addVersionFile(type: Task) { project.tasks.findByName("compileKotlin").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } project.tasks.findByName("bootBuildInfo").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } project.tasks.findByName("compileJava").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } -project.tasks.findByName("bootJarMainClassName").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } +project.tasks.findByName("bootJarMainClassName")?.onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } sourceSets { main.kotlin.srcDirs = ['src/main/kotlin', 'src/main/java'] diff --git a/backend/app/src/main/java/io/tolgee/security/JwtAuthenticationEntryPoint.java b/backend/app/src/main/java/io/tolgee/security/JwtAuthenticationEntryPoint.java index 1a19172131..cbb99f82e9 100644 --- a/backend/app/src/main/java/io/tolgee/security/JwtAuthenticationEntryPoint.java +++ b/backend/app/src/main/java/io/tolgee/security/JwtAuthenticationEntryPoint.java @@ -1,14 +1,14 @@ package io.tolgee.security; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component diff --git a/backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt b/backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt index 0218710bdb..969cd6b3f1 100644 --- a/backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt +++ b/backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt @@ -13,6 +13,8 @@ import io.tolgee.exceptions.ErrorResponseBody import io.tolgee.exceptions.NotFoundException import io.tolgee.security.ratelimit.RateLimitResponseBody import io.tolgee.security.ratelimit.RateLimitedException +import jakarta.persistence.EntityNotFoundException +import jakarta.servlet.http.HttpServletRequest import org.apache.catalina.connector.ClientAbortException import org.apache.commons.lang3.exception.ExceptionUtils import org.hibernate.QueryException @@ -36,8 +38,6 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept import java.io.Serializable import java.util.* import java.util.function.Consumer -import javax.persistence.EntityNotFoundException -import javax.servlet.http.HttpServletRequest @RestControllerAdvice class ExceptionHandlers { diff --git a/backend/app/src/main/kotlin/io/tolgee/commandLineRunners/InitialUserCreatorCommandLineRunner.kt b/backend/app/src/main/kotlin/io/tolgee/commandLineRunners/InitialUserCreatorCommandLineRunner.kt index f2b6acb71a..3aa2f0979e 100644 --- a/backend/app/src/main/kotlin/io/tolgee/commandLineRunners/InitialUserCreatorCommandLineRunner.kt +++ b/backend/app/src/main/kotlin/io/tolgee/commandLineRunners/InitialUserCreatorCommandLineRunner.kt @@ -1,5 +1,6 @@ package io.tolgee.commandLineRunners +import io.tolgee.configuration.tolgee.InternalProperties import io.tolgee.configuration.tolgee.TolgeeProperties import io.tolgee.dtos.request.auth.SignUpDto import io.tolgee.dtos.request.organization.OrganizationDto @@ -24,11 +25,15 @@ class InitialUserCreatorCommandLineRunner( private val initialPasswordManager: InitialPasswordManager, private val organizationService: OrganizationService, private val passwordEncoder: PasswordEncoder, + private val internalProperties: InternalProperties ) : CommandLineRunner, ApplicationListener { private val logger = LoggerFactory.getLogger(this::class.java) @Transactional override fun run(vararg args: String) { + if (internalProperties.disableInitialUserCreation) { + return + } val initialUser = userAccountService.findInitialUser() if (initialUser == null) { createInitialUser() @@ -39,7 +44,6 @@ class InitialUserCreatorCommandLineRunner( fun createInitialUser() { logger.info("Creating initial user...") - val initialUsername = properties.authentication.initialUsername val initialPassword = initialPasswordManager.initialPassword val user = userAccountService.createInitialUser( diff --git a/backend/app/src/main/kotlin/io/tolgee/component/ExceptionHandlerFilter.kt b/backend/app/src/main/kotlin/io/tolgee/component/ExceptionHandlerFilter.kt index 1ea56862c6..6ef00ba15c 100644 --- a/backend/app/src/main/kotlin/io/tolgee/component/ExceptionHandlerFilter.kt +++ b/backend/app/src/main/kotlin/io/tolgee/component/ExceptionHandlerFilter.kt @@ -16,13 +16,13 @@ package io.tolgee.component +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter import org.springframework.web.servlet.HandlerExceptionResolver -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class ExceptionHandlerFilter( diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/AsyncWebMvcConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/AsyncWebMvcConfiguration.kt index 98cddb642d..ce56d98aa1 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/AsyncWebMvcConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/AsyncWebMvcConfiguration.kt @@ -4,7 +4,7 @@ import io.sentry.spring.SentryTaskDecorator import org.springframework.context.annotation.Configuration import org.springframework.core.task.AsyncTaskExecutor import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor -import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @@ -15,25 +15,9 @@ internal class AsyncWebMvcConfiguration : WebMvcConfigurer { } private fun asyncExecutor(): AsyncTaskExecutor { - val executor = SecurityContextAwareThreadPoolTaskExecutor() - executor.setTaskDecorator(SentryTaskDecorator()) - executor.initialize() - return executor - } - - class SecurityContextAwareThreadPoolTaskExecutor : ThreadPoolTaskExecutor() { - override fun execute(task: Runnable) { - val currentAuthentication = SecurityContextHolder.getContext().authentication - super.execute { - try { - val ctx = SecurityContextHolder.createEmptyContext() - ctx.authentication = currentAuthentication - SecurityContextHolder.setContext(ctx) - task.run() - } finally { - SecurityContextHolder.clearContext() - } - } - } + val asyncExecutor = ThreadPoolTaskExecutor() + asyncExecutor.setTaskDecorator(SentryTaskDecorator()) + asyncExecutor.initialize() + return DelegatingSecurityContextAsyncTaskExecutor(asyncExecutor) } } diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/BatchConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/BatchConfiguration.kt index 1b39bd271e..e77311c973 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/BatchConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/BatchConfiguration.kt @@ -1,8 +1,6 @@ package io.tolgee.configuration -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing import org.springframework.context.annotation.Configuration -@EnableBatchProcessing @Configuration class BatchConfiguration diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/OpenApiConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/OpenApiConfiguration.kt index d19582fa66..57ec880356 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/OpenApiConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/OpenApiConfiguration.kt @@ -10,7 +10,7 @@ import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.parameters.Parameter import io.tolgee.API_KEY_HEADER_NAME import io.tolgee.security.authentication.AllowApiAccess -import org.springdoc.core.GroupedOpenApi +import org.springdoc.core.models.GroupedOpenApi import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.method.HandlerMethod @@ -119,7 +119,7 @@ class OpenApiConfiguration { } .pathsToExclude(*excludedPaths, "/api/project/{$PROJECT_ID_PARAMETER}/sources/**") .pathsToMatch(*paths) - .addOpenApiCustomiser { openApi -> + .addOpenApiCustomizer { openApi -> openApi.paths.forEach { (path, value) -> value.readOperations().forEach { operation -> operationHandlers[operation.operationId]?.method?.let { method -> @@ -133,7 +133,7 @@ class OpenApiConfiguration { } } } - .addOpenApiCustomiser { openApi -> + .addOpenApiCustomizer { openApi -> val newPaths = Paths() openApi.paths.forEach { pathEntry -> val operations = ArrayList() @@ -200,7 +200,7 @@ class OpenApiConfiguration { return GroupedOpenApi.builder().group(name) .pathsToExclude(*excludedPaths) .pathsToMatch(*paths) - .addOpenApiCustomiser { openApi -> + .addOpenApiCustomizer { openApi -> val newPaths = Paths() openApi.paths.forEach { pathEntry -> val operations = ArrayList() @@ -243,7 +243,7 @@ class OpenApiConfiguration { } private fun GroupedOpenApi.Builder.handleLinks(): GroupedOpenApi.Builder { - this.addOpenApiCustomiser { + this.addOpenApiCustomizer { it.components?.schemas?.values?.forEach { it?.properties?.remove("_links") } diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/PostgresAutoStartConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/PostgresAutoStartConfiguration.kt index 1bcfd59d53..b1d3f8a5c3 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/PostgresAutoStartConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/PostgresAutoStartConfiguration.kt @@ -1,8 +1,7 @@ package io.tolgee.configuration +import io.tolgee.PostgresRunner import io.tolgee.configuration.tolgee.PostgresAutostartProperties -import io.tolgee.postgresRunners.PostgresRunner -import io.tolgee.postgresRunners.PostgresRunnerFactory import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.jdbc.DataSourceBuilder @@ -14,28 +13,24 @@ import javax.sql.DataSource @ConditionalOnProperty(name = ["tolgee.postgres-autostart.enabled"], havingValue = "true") class PostgresAutoStartConfiguration( val postgresAutostartProperties: PostgresAutostartProperties, - val postgresRunnerFactory: PostgresRunnerFactory, ) { - private var _dataSource: DataSource? = null - @Bean + @Bean("dataSource") @ConfigurationProperties(prefix = "spring.datasource") - fun getDataSource(): DataSource { + fun getDataSource(postgresRunner: PostgresRunner?): DataSource { + postgresRunner ?: throw IllegalStateException("Postgres runner is not initialized") _dataSource?.let { return it } postgresRunner.run() - _dataSource = buildDataSource() + _dataSource = buildDataSource(postgresRunner) return _dataSource!! } - private fun buildDataSource(): DataSource { + private fun buildDataSource(postgresRunner: PostgresRunner): DataSource { val dataSourceBuilder = DataSourceBuilder.create() dataSourceBuilder.url(postgresRunner.datasourceUrl) dataSourceBuilder.username(postgresAutostartProperties.user) dataSourceBuilder.password(postgresAutostartProperties.password) return dataSourceBuilder.build() } - - private val postgresRunner: PostgresRunner - get() = postgresRunnerFactory.runner } diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt similarity index 91% rename from backend/data/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt rename to backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt index 0005bdc590..1d047305d0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt @@ -1,7 +1,8 @@ package io.tolgee.configuration -import org.apache.http.impl.client.HttpClientBuilder +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Lazy import org.springframework.context.annotation.Primary import org.springframework.http.client.HttpComponentsClientHttpRequestFactory import org.springframework.http.client.SimpleClientHttpRequestFactory @@ -13,6 +14,7 @@ import org.springframework.web.client.RestTemplate class RestTemplateConfiguration { @Bean + @Lazy @Primary fun restTemplate(): RestTemplate { return RestTemplate( diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/WebConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/WebConfiguration.kt index 992acc61ae..5d4b21b63b 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/WebConfiguration.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/WebConfiguration.kt @@ -10,6 +10,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.tolgee.activity.ActivityHandlerInterceptor import io.tolgee.component.VersionFilter import io.tolgee.configuration.tolgee.TolgeeProperties +import jakarta.servlet.MultipartConfigElement import org.springframework.boot.web.servlet.MultipartConfigFactory import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -24,7 +25,6 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer import java.security.SecureRandom import java.util.concurrent.TimeUnit -import javax.servlet.MultipartConfigElement @Configuration @EnableScheduling diff --git a/backend/app/src/main/kotlin/io/tolgee/configuration/WebSecurityConfig.kt b/backend/app/src/main/kotlin/io/tolgee/configuration/WebSecurityConfig.kt index 4c6be32cc0..3025d029fb 100644 --- a/backend/app/src/main/kotlin/io/tolgee/configuration/WebSecurityConfig.kt +++ b/backend/app/src/main/kotlin/io/tolgee/configuration/WebSecurityConfig.kt @@ -32,10 +32,13 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.Ordered import org.springframework.core.annotation.Order +import org.springframework.security.config.Customizer +import org.springframework.security.config.annotation.ObjectPostProcessor import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.access.intercept.AuthorizationFilter import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy import org.springframework.web.servlet.config.annotation.InterceptorRegistry @@ -58,26 +61,36 @@ class WebSecurityConfig( fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain { return httpSecurity // -- Global configuration - .csrf().disable() - .cors().and() - .headers() - .referrerPolicy().policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN).and() - .xssProtection().block(true).and() - .contentTypeOptions().and() - .frameOptions().deny() - .and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .csrf { it.disable() } + .cors(Customizer.withDefaults()) + .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } .addFilterBefore(exceptionHandlerFilter, UsernamePasswordAuthenticationFilter::class.java) .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter::class.java) .addFilterBefore(authenticationDisabledFilter, UsernamePasswordAuthenticationFilter::class.java) .addFilterBefore(globalUserRateLimitFilter, UsernamePasswordAuthenticationFilter::class.java) .addFilterBefore(globalIpRateLimitFilter, UsernamePasswordAuthenticationFilter::class.java) - .authorizeRequests() - .mvcMatchers("/api/public/**", "/v2/public/**").permitAll() - .mvcMatchers("/v2/administration/**", "/v2/ee-license/**").hasRole("ADMIN") - .mvcMatchers("/api/**", "/v2/**").authenticated() - .anyRequest().permitAll() - .and().build() + .authorizeHttpRequests { + it.withObjectPostProcessor(object : ObjectPostProcessor { + override fun postProcess(filter: O): O { + // otherwise it throws error when using StreamingResponseBody + filter?.setFilterAsyncDispatch(false) + return filter + } + }) + it.requestMatchers("/api/public/**", "/v2/public/**").permitAll() + it.requestMatchers("/v2/administration/**", "/v2/ee-license/**").hasRole("ADMIN") + it.requestMatchers("/api/**", "/v2/**") + it.anyRequest().permitAll() + }.headers { headers -> + headers.xssProtection(Customizer.withDefaults()) + headers.contentTypeOptions(Customizer.withDefaults()) + headers.frameOptions { + it.deny() + } + headers.referrerPolicy { + it.policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) + } + }.build() } @Bean @@ -85,7 +98,7 @@ class WebSecurityConfig( @ConditionalOnProperty(value = ["tolgee.internal.controller-enabled"], havingValue = "false", matchIfMissing = true) fun internalSecurityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain { return httpSecurity - .antMatcher("/internal/**") + .securityMatcher("/internal/**") .authorizeRequests() .anyRequest() .denyAll() diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresDockerRunner.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresDockerRunner.kt index be842e981e..bd24805fa8 100644 --- a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresDockerRunner.kt +++ b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresDockerRunner.kt @@ -1,17 +1,12 @@ package io.tolgee.postgresRunners +import io.tolgee.PostgresRunner import io.tolgee.configuration.tolgee.PostgresAutostartProperties import io.tolgee.misc.dockerRunner.DockerContainerRunner import org.slf4j.LoggerFactory -import org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_SINGLETON -import org.springframework.context.annotation.Scope -import org.springframework.stereotype.Component -import javax.annotation.PreDestroy -@Component -@Scope(SCOPE_SINGLETON) class PostgresDockerRunner( - protected val postgresAutostartProperties: PostgresAutostartProperties, + private val postgresAutostartProperties: PostgresAutostartProperties, ) : PostgresRunner { private var instance: DockerContainerRunner? = null private val logger = LoggerFactory.getLogger(javaClass) @@ -40,15 +35,21 @@ class PostgresDockerRunner( } } - override val datasourceUrl by lazy { - "jdbc:postgresql://localhost:${postgresAutostartProperties.port}/${postgresAutostartProperties.databaseName}" + override fun stop() { + if (postgresAutostartProperties.stop) { + instance?.let { + logger.info("Stopping Postgres container") + it.stop() + } + } } - @PreDestroy - fun preDestroy() { - instance?.let { - logger.info("Stopping Postgres container") - it.stop() - } + override val shouldRunMigrations: Boolean + // we don't want to run migrations when the container existed, and we are not stopping it, + // this happens only for tests and there we can delete the database and start again with migrations + get() = instance?.containerExisted != true || postgresAutostartProperties.stop + + override val datasourceUrl by lazy { + "jdbc:postgresql://localhost:${postgresAutostartProperties.port}/${postgresAutostartProperties.databaseName}" } } diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresEmbeddedRunner.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresEmbeddedRunner.kt index dccc52c8f4..0eae6fd2e0 100644 --- a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresEmbeddedRunner.kt +++ b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresEmbeddedRunner.kt @@ -1,21 +1,16 @@ package io.tolgee.postgresRunners +import io.tolgee.PostgresRunner import io.tolgee.configuration.tolgee.FileStorageProperties import io.tolgee.configuration.tolgee.PostgresAutostartProperties import io.tolgee.fixtures.waitFor import org.slf4j.LoggerFactory -import org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_SINGLETON -import org.springframework.context.annotation.Scope -import org.springframework.stereotype.Component import java.io.IOException import java.io.InputStream import java.net.Socket import java.util.concurrent.atomic.AtomicBoolean -import javax.annotation.PreDestroy import kotlin.concurrent.thread -@Component -@Scope(SCOPE_SINGLETON) class PostgresEmbeddedRunner( private val postgresAutostartProperties: PostgresAutostartProperties, private val storageProperties: FileStorageProperties @@ -32,6 +27,14 @@ class PostgresEmbeddedRunner( } } + override fun stop() { + proc.destroy() + proc.waitFor() + running.set(false) + } + + override val shouldRunMigrations: Boolean = true + private fun startPostgresProcess() { val processBuilder = buildProcess() logger.info("Starting embedded Postgres DB...") @@ -101,13 +104,6 @@ class PostgresEmbeddedRunner( } } - @PreDestroy - fun preDestroy() { - proc.destroy() - proc.waitFor() - running.set(false) - } - private fun logOutput(loggerMap: Map Unit>) { while (running.get()) { try { diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunner.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunner.kt deleted file mode 100644 index 92ddc98f3c..0000000000 --- a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunner.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.tolgee.postgresRunners - -interface PostgresRunner { - fun run() - val datasourceUrl: String -} diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerConfiguration.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerConfiguration.kt new file mode 100644 index 0000000000..bd4e00ab23 --- /dev/null +++ b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerConfiguration.kt @@ -0,0 +1,27 @@ +package io.tolgee.postgresRunners + +import io.tolgee.PostgresRunner +import io.tolgee.configuration.tolgee.FileStorageProperties +import io.tolgee.configuration.tolgee.PostgresAutostartProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class PostgresRunnerConfiguration { + @Bean + fun postgresRunner( + postgresAutostartProperties: PostgresAutostartProperties, + storageProperties: FileStorageProperties + ): PostgresRunner? { + if (!postgresAutostartProperties.enabled) { + return null + } + if (postgresAutostartProperties.mode == PostgresAutostartProperties.PostgresAutostartMode.DOCKER) { + return PostgresDockerRunner(postgresAutostartProperties) + } + if (postgresAutostartProperties.mode == PostgresAutostartProperties.PostgresAutostartMode.EMBEDDED) { + return PostgresEmbeddedRunner(postgresAutostartProperties, storageProperties) + } + return null + } +} diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerFactory.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerFactory.kt deleted file mode 100644 index aae0296d4a..0000000000 --- a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresRunnerFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.tolgee.postgresRunners - -import io.tolgee.configuration.tolgee.PostgresAutostartProperties -import org.springframework.context.ApplicationContext -import org.springframework.stereotype.Component - -@Component -class PostgresRunnerFactory( - private val postgresAutostartProperties: PostgresAutostartProperties, - private val applicationContext: ApplicationContext -) { - - val runner: PostgresRunner by lazy { - if (postgresAutostartProperties.mode == PostgresAutostartProperties.PostgresAutostartMode.DOCKER) { - return@lazy applicationContext.getBean(PostgresDockerRunner::class.java) - } - if (postgresAutostartProperties.mode == PostgresAutostartProperties.PostgresAutostartMode.EMBEDDED) { - return@lazy applicationContext.getBean(PostgresEmbeddedRunner::class.java) - } - - throw IllegalStateException("Postgres autostart mode: '${postgresAutostartProperties.mode}' not recognized.") - } -} diff --git a/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresStopper.kt b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresStopper.kt new file mode 100644 index 0000000000..65476454d2 --- /dev/null +++ b/backend/app/src/main/kotlin/io/tolgee/postgresRunners/PostgresStopper.kt @@ -0,0 +1,22 @@ +package io.tolgee.postgresRunners + +import io.tolgee.PostgresRunner +import io.tolgee.configuration.tolgee.PostgresAutostartProperties +import org.springframework.context.event.ContextClosedEvent +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Component + +@Component +class PostgresStopper( + private val postgresAutostartProperties: PostgresAutostartProperties, + private val postgresRunner: PostgresRunner? +) { + @EventListener(ContextClosedEvent::class) + fun onAppStop() { + val itHasToStop = postgresAutostartProperties.mode != PostgresAutostartProperties.PostgresAutostartMode.DOCKER + val itShouldStop = postgresAutostartProperties.stop + if (itHasToStop || itShouldStop) { + postgresRunner?.stop() + } + } +} diff --git a/backend/app/src/main/resources/META-INF/spring-devtools.properties b/backend/app/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000000..87cfbe4640 --- /dev/null +++ b/backend/app/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1,7 @@ +restart.include.tolgee-security=/security[\\w\\d-\\.]*\\.jar +restart.include.tolgee-data=/data[\\w\\d-\\.]*\\.jar +restart.include.tolgee-api=/api[\\w\\d-\\.]*\\.jar +restart.include.tolgee-development=/development[\\w\\d-\\.]*\\.jar +restart.include.tolgee-misc=/misc[\\w\\d-\\.]*\\.jar +restart.include.tolgee-ee=/ee[\\w\\d-\\.]*\\.jar +restart.include.tolgee-billing=/billing[\\w\\d-\\.]*\\.jar diff --git a/backend/app/src/main/resources/application-e2e.yaml b/backend/app/src/main/resources/application-e2e.yaml index 2ea6b6f80c..a11fff3249 100644 --- a/backend/app/src/main/resources/application-e2e.yaml +++ b/backend/app/src/main/resources/application-e2e.yaml @@ -66,3 +66,8 @@ server: error: include-exception: true include-stacktrace: always +logging: + level: + io.tolgee.service: TRACE + io.tolgee.controllers.internal.e2e_data.OrganizationE2eDataController: TRACE + io.tolgee.controllers.internal.e2e_data.ProjectsE2eDataController: TRACE diff --git a/backend/app/src/main/resources/application.yaml b/backend/app/src/main/resources/application.yaml index 780713c1b5..280c38e0ba 100644 --- a/backend/app/src/main/resources/application.yaml +++ b/backend/app/src/main/resources/application.yaml @@ -12,6 +12,8 @@ spring: # open-in-view: false properties: hibernate: + order_by: + default_null_ordering: first jdbc: batch_size: 1000 order_inserts: true @@ -20,13 +22,15 @@ spring: types: print: banner: false + enhancer: + enableLazyInitialization: true + enableDirtyTracking: true + # open-in-view: false batch: job: enabled: false jdbc: initialize-schema: always -# main: -# lazy-initialization: true tolgee: authentication: enabled: false diff --git a/backend/app/src/test/kotlin/io/tolgee/PatAuthTest.kt b/backend/app/src/test/kotlin/io/tolgee/PatAuthTest.kt index 7e7038993b..d60bf7296c 100644 --- a/backend/app/src/test/kotlin/io/tolgee/PatAuthTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/PatAuthTest.kt @@ -69,7 +69,7 @@ class PatAuthTest : AbstractControllerTest() { @Test fun `user doesnt authorize with expired PAT`() { - val pat = createUserWithPat(Date(Date().time - 10000)) + val pat = createUserWithPat(expiresAt = Date(Date().time - 10000)) performGet( "/v2/user", HttpHeaders().apply { @@ -80,10 +80,11 @@ class PatAuthTest : AbstractControllerTest() { @Test fun `pat doesnt work on restricted endpoint`() { - val pat = createUserWithPat(Date(Date().time - 10000)) + val pat = createUserWithPat(expiresAt = Date(Date().time + 100000)) performDelete( "/v2/pats/${pat.id}", - HttpHeaders().apply { + content = null, + httpHeaders = HttpHeaders().apply { add("X-API-Key", "tgpat_${pat.token}") } ).andIsForbidden diff --git a/backend/app/src/test/kotlin/io/tolgee/StreamingBodyDatabasePoolHealthTest.kt b/backend/app/src/test/kotlin/io/tolgee/StreamingBodyDatabasePoolHealthTest.kt index 52c307a4b0..9cce136896 100644 --- a/backend/app/src/test/kotlin/io/tolgee/StreamingBodyDatabasePoolHealthTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/StreamingBodyDatabasePoolHealthTest.kt @@ -19,6 +19,7 @@ package io.tolgee import com.zaxxer.hikari.HikariDataSource import io.tolgee.development.testDataBuilder.data.TranslationsTestData import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.retry import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod import io.tolgee.testing.assert import org.junit.jupiter.api.BeforeEach @@ -52,11 +53,30 @@ class StreamingBodyDatabasePoolHealthTest : ProjectAuthControllerTest("/v2/proje @Test @ProjectJWTAuthTestMethod fun `streaming responses do not cause a database connection pool exhaustion`() { - val hikariDataSource = dataSource as HikariDataSource - val pool = hikariDataSource.hikariPoolMXBean + // there is the bug in spring, co it throws the concurrent modification exception + // to avoid this, we will retry the test until it passes + // but we will also increase the sleep time between requests to make it more probable to pass + // I know, it's ugly. Sorry. If you have time to spare, remove the repeats and the sleep, maybe it will pass + // in future spring versions + // https://github.com/spring-projects/spring-security/issues/9175 + var sleepBetweenMs = 0L + retry( + retries = 100, + exceptionMatcher = { it is ConcurrentModificationException || it is IllegalStateException } + ) { + try { + val hikariDataSource = dataSource as HikariDataSource + val pool = hikariDataSource.hikariPoolMXBean - pool.idleConnections.assert.isGreaterThan(90) - repeat(50) { performProjectAuthGet("export").andIsOk } - pool.idleConnections.assert.isGreaterThan(90) + pool.idleConnections.assert.isGreaterThan(90) + repeat(50) { + performProjectAuthGet("export").andIsOk + Thread.sleep(sleepBetweenMs) + } + pool.idleConnections.assert.isGreaterThan(90) + } finally { + sleepBetweenMs += 10 + } + } } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/PatControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/PatControllerTest.kt index 1b097ec134..9bc2892839 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/PatControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/PatControllerTest.kt @@ -1,7 +1,13 @@ package io.tolgee.api.v2.controllers import io.tolgee.development.testDataBuilder.data.PatTestData -import io.tolgee.fixtures.* +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andIsBadRequest +import io.tolgee.fixtures.andIsCreated +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.andPrettyPrint +import io.tolgee.fixtures.isValidId +import io.tolgee.fixtures.node import io.tolgee.testing.AuthorizedControllerTest import io.tolgee.testing.assert import io.tolgee.testing.assertions.Assertions.assertThat diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2ExportControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2ExportControllerTest.kt index a5d3b05556..3a6437a96d 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2ExportControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2ExportControllerTest.kt @@ -13,6 +13,7 @@ import io.tolgee.fixtures.andGetContentAsString import io.tolgee.fixtures.andIsOk import io.tolgee.fixtures.andPrettyPrint import io.tolgee.fixtures.retry +import io.tolgee.fixtures.waitForNotThrowing import io.tolgee.testing.ContextRecreatingTest import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod import io.tolgee.testing.assert @@ -30,6 +31,7 @@ import org.mockito.kotlin.verify import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.dao.DataIntegrityViolationException import org.springframework.test.web.servlet.MvcResult import org.springframework.transaction.annotation.Transactional import java.io.ByteArrayInputStream @@ -77,19 +79,29 @@ class V2ExportControllerTest : ProjectAuthControllerTest("/v2/projects/") { @Test @ProjectJWTAuthTestMethod fun `it reports business event once in a day`() { - executeInNewTransaction { + retry( + retries = 10, + exceptionMatcher = { it is ConcurrentModificationException || it is DataIntegrityViolationException } + ) { initBaseData() + try { + executeInNewTransaction { + } + performExport() + performExport() + waitForNotThrowing(pollTime = 50, timeout = 3000) { + verify(postHog, times(1)).capture(any(), eq("EXPORT"), any()) + } + setForcedDate(currentDateProvider.date.addDays(1)) + performExport() + waitForNotThrowing(pollTime = 50, timeout = 3000) { + verify(postHog, times(2)).capture(any(), eq("EXPORT"), any()) + } + } finally { + Mockito.reset(postHog) + testDataService.cleanTestData(testData.root) + } } - performExport() - performExport() - performExport() - performExport() - Thread.sleep(2000) - verify(postHog, times(1)).capture(any(), eq("EXPORT"), any()) - setForcedDate(currentDateProvider.date.addDays(1)) - performExport() - performExport() - verify(postHog, times(2)).capture(any(), eq("EXPORT"), any()) } @Test diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2InvitationControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2InvitationControllerTest.kt index 8b34fdc48a..5829df38c9 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2InvitationControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/V2InvitationControllerTest.kt @@ -2,7 +2,11 @@ package io.tolgee.api.v2.controllers import io.tolgee.dtos.misc.CreateProjectInvitationParams import io.tolgee.dtos.request.project.LanguagePermissions -import io.tolgee.fixtures.* +import io.tolgee.fixtures.EmailTestUtil +import io.tolgee.fixtures.andIsNotFound +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.equalsPermissionType +import io.tolgee.fixtures.generateUniqueString import io.tolgee.model.Invitation import io.tolgee.model.Project import io.tolgee.model.UserAccount diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementControllerTest.kt index 588e8da00b..d3355680aa 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/batch/BatchJobManagementControllerTest.kt @@ -25,9 +25,9 @@ import io.tolgee.model.batch.BatchJobChunkExecutionStatus import io.tolgee.model.batch.BatchJobStatus import io.tolgee.service.machineTranslation.MtCreditBucketService import io.tolgee.service.translation.AutoTranslationService -import io.tolgee.testing.ContextRecreatingTest import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod import io.tolgee.testing.assert +import io.tolgee.util.BatchDumper import io.tolgee.util.Logging import io.tolgee.util.addMinutes import kotlinx.coroutines.ensureActive @@ -41,18 +41,14 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.mock.mockito.SpyBean import org.springframework.stereotype.Service -import org.springframework.test.annotation.DirtiesContext import org.springframework.transaction.annotation.Transactional +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.CoroutineContext -@AutoConfigureMockMvc -@ContextRecreatingTest -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class BatchJobManagementControllerTest : ProjectAuthControllerTest("/v2/projects/"), Logging { lateinit var testData: BatchJobsTestData @@ -93,6 +89,9 @@ class BatchJobManagementControllerTest : ProjectAuthControllerTest("/v2/projects @Autowired lateinit var batchJobActivityFinalizer: BatchJobActivityFinalizer + @Autowired + lateinit var batchDumper: BatchDumper + @BeforeEach fun setup() { batchJobChunkExecutionQueue.clear() @@ -166,38 +165,48 @@ class BatchJobManagementControllerTest : ProjectAuthControllerTest("/v2/projects @Test @ProjectJWTAuthTestMethod fun `exception from inner transaction doesn't break it`() { - val keys = testData.addTranslationOperationData(100) - saveAndPrepare() - - val keyIds = keys.map { it.id }.toList() - - // although it passes once, there should be no successful targets, because the whole transaction is rolled back - doAnswer { it.callRealMethod() } - .doAnswer { throwingService.throwExceptionInTransaction() } - .whenever(autoTranslationService).autoTranslateSync(any(), any(), any(), any(), any()) - - performProjectAuthPost( - "start-batch-job/machine-translate", - mapOf( - "keyIds" to keyIds, - "targetLanguageIds" to listOf( - testData.projectBuilder.getLanguageByTag("cs")!!.self.id + finallyDump { + val keys = testData.addTranslationOperationData(100) + saveAndPrepare() + + val keyIds = keys.map { it.id }.toList() + + // although it passes once, there should be no successful targets, because the whole transaction is rolled back + doAnswer { it.callRealMethod() } + .doAnswer { throwingService.throwExceptionInTransaction() } + .whenever(autoTranslationService).autoTranslateSync(any(), any(), any(), any(), any()) + + performProjectAuthPost( + "start-batch-job/machine-translate", + mapOf( + "keyIds" to keyIds, + "targetLanguageIds" to listOf( + testData.projectBuilder.getLanguageByTag("cs")!!.self.id + ) ) - ) - ).andIsOk + ).andIsOk - waitForNotThrowing(pollTime = 100) { - // lets move time fast - setForcedDate(currentDateProvider.date.addMinutes(1)) - getSingleJob().status.assert.isEqualTo(BatchJobStatus.FAILED) + waitForNotThrowing(pollTime = 100) { + // lets move time fast + setForcedDate(currentDateProvider.date.addMinutes(1)) + getSingleJob().status.assert.isEqualTo(BatchJobStatus.FAILED) + } + + val executions = batchJobService.getExecutions(getSingleJob().id) + executions.assert.hasSize(80) + executions.forEach { + it.status.assert.isEqualTo(BatchJobChunkExecutionStatus.FAILED) + // no successful targets, since all was rolled back + it.successTargets.assert.isEmpty() + } } + } - val executions = batchJobService.getExecutions(getSingleJob().id) - executions.assert.hasSize(80) - executions.forEach { - it.status.assert.isEqualTo(BatchJobChunkExecutionStatus.FAILED) - // no successful targets, since all was rolled back - it.successTargets.assert.isEmpty() + fun finallyDump(fn: () -> T): T { + return try { + fn() + } finally { + batchDumper.dump(getSingleJob().id) } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationControllerMembersTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationControllerMembersTest.kt index 61e404ff0c..73bf064d71 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationControllerMembersTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationControllerMembersTest.kt @@ -4,7 +4,14 @@ import io.tolgee.constants.Message import io.tolgee.development.testDataBuilder.data.OrganizationTestData import io.tolgee.development.testDataBuilder.data.PermissionsTestData import io.tolgee.dtos.request.organization.SetOrganizationRoleDto -import io.tolgee.fixtures.* +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andHasErrorMessage +import io.tolgee.fixtures.andIsBadRequest +import io.tolgee.fixtures.andIsForbidden +import io.tolgee.fixtures.andIsNotFound +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.andPrettyPrint +import io.tolgee.fixtures.node import io.tolgee.model.enums.OrganizationRoleType import io.tolgee.model.enums.ProjectPermissionType import io.tolgee.testing.assert diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt index 804bd46f76..fc1b97238e 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt @@ -3,7 +3,12 @@ package io.tolgee.api.v2.controllers.translations.v2TranslationsController import io.tolgee.ProjectAuthControllerTest import io.tolgee.development.testDataBuilder.data.NamespacesTestData import io.tolgee.development.testDataBuilder.data.TranslationsTestData -import io.tolgee.fixtures.* +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andIsNotFound +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.andPrettyPrint +import io.tolgee.fixtures.isValidId +import io.tolgee.fixtures.node import io.tolgee.model.enums.Scope import io.tolgee.testing.annotations.ApiKeyPresentMode import io.tolgee.testing.annotations.ProjectApiKeyAuthTestMethod diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/SecuredV2ImageUploadControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/SecuredV2ImageUploadControllerTest.kt index 69e068c4fb..fcb9aae653 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/SecuredV2ImageUploadControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/SecuredV2ImageUploadControllerTest.kt @@ -78,7 +78,7 @@ class SecuredV2ImageUploadControllerTest : AbstractV2ImageUploadControllerTest() node("filename").isString.satisfies { val file = File(tolgeeProperties.fileStorage.fsDataPath + "/uploadedImages/" + it + ".png") assertThat(file).exists() - assertThat(file.readBytes().size).isCloseTo(7365, Offset.offset(200)) + assertThat(file.readBytes().size).isCloseTo(5538, Offset.offset(500)) } node("requestFilename").isString.satisfies { val parts = it.split("?token=") diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/V2ImageUploadControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/V2ImageUploadControllerTest.kt index b0dce3b9e6..69b3d7e6f4 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/V2ImageUploadControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/V2ImageUploadControllerTest.kt @@ -12,6 +12,7 @@ import io.tolgee.fixtures.andIsForbidden import io.tolgee.fixtures.andIsOk import io.tolgee.fixtures.andPrettyPrint import io.tolgee.testing.assertions.Assertions.assertThat +import org.assertj.core.data.Offset import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @@ -52,9 +53,7 @@ class V2ImageUploadControllerTest : AbstractV2ImageUploadControllerTest() { node("requestFilename").isString.satisfies { val file = File(tolgeeProperties.fileStorage.fsDataPath + "/uploadedImages/" + it) assertThat(file).exists() - assertThat(file.readBytes().size) - .isGreaterThan(7200) - .isLessThan(7500) + assertThat(file.readBytes().size).isCloseTo(5538, Offset.offset(500)) } } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerAddFilesTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerAddFilesTest.kt index 78b72d784c..f79885bf01 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerAddFilesTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerAddFilesTest.kt @@ -204,7 +204,7 @@ class V2ImportControllerAddFilesTest : AuthorizedControllerTest() { null ).andIsBadRequest.andAssertThatJson { node("STANDARD_VALIDATION") { - node("files").isEqualTo("Required request part 'files' is not present") + node("files").isEqualTo("Required part 'files' is not present.") } } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt index 8fca80134d..5a961a6f6a 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt @@ -86,7 +86,6 @@ class KeyControllerKeySearchTest : ProjectAuthControllerTest("/v2/projects/"), L executeInNewTransaction { val time = measureTimeMillis { performProjectAuthGet("keys/search?search=dol&languageTag=de").andAssertThatJson { - node("page.totalElements").isNumber.isGreaterThan(2000.toBigDecimal()) node("page.totalElements").isNumber.isGreaterThan(4000.toBigDecimal()) }.andPrettyPrint } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/V2ProjectsControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/V2ProjectsControllerTest.kt index d4c0efacc2..85e8b36892 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/V2ProjectsControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/V2ProjectsControllerTest.kt @@ -3,7 +3,14 @@ package io.tolgee.api.v2.controllers.v2ProjectsController import io.tolgee.ProjectAuthControllerTest import io.tolgee.development.testDataBuilder.data.BaseTestData import io.tolgee.development.testDataBuilder.data.ProjectsTestData -import io.tolgee.fixtures.* +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andIsBadRequest +import io.tolgee.fixtures.andIsNotFound +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.andPrettyPrint +import io.tolgee.fixtures.generateUniqueString +import io.tolgee.fixtures.isPermissionScopes +import io.tolgee.fixtures.node import io.tolgee.model.Permission import io.tolgee.model.UserAccount import io.tolgee.model.enums.ProjectPermissionType diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/KeyScreenshotControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/KeyScreenshotControllerTest.kt index f13b2f04c5..566a362d72 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/KeyScreenshotControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/KeyScreenshotControllerTest.kt @@ -21,7 +21,6 @@ import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import java.io.File -import java.util.stream.Collectors @TestInstance(TestInstance.Lifecycle.PER_CLASS) class KeyScreenshotControllerTest : AbstractV2ScreenshotControllerTest() { @@ -76,7 +75,6 @@ class KeyScreenshotControllerTest : AbstractV2ScreenshotControllerTest() { fun `uploads without metadata`() { val key = keyService.create(project, CreateKeyDto("test")) - val text = "I am key" performStoreScreenshot( project, key, @@ -166,12 +164,16 @@ class KeyScreenshotControllerTest : AbstractV2ScreenshotControllerTest() { }.toCollection(mutableListOf()) key to list } - val idsToDelete = list.stream().limit(10).map { it.id.toString() }.collect(Collectors.joining(",")) + val chunked = list.chunked(10) + val toDelete = chunked[0] + val notToDelete = chunked[1] + + val idsToDelete = toDelete.map { it.id.toString() }.joinToString(",") performProjectAuthDelete("/keys/${key.id}/screenshots/$idsToDelete", null).andExpect(status().isOk) val rest = screenshotService.findAll(key) - assertThat(rest).isEqualTo(list.stream().skip(10).collect(Collectors.toList())) + assertThat(rest).hasSize(10).containsExactlyInAnyOrder(*notToDelete.toTypedArray()) } @Test diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/SecuredKeyScreenshotControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/SecuredKeyScreenshotControllerTest.kt index eba534a367..25fa0bf65e 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/SecuredKeyScreenshotControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/SecuredKeyScreenshotControllerTest.kt @@ -103,7 +103,7 @@ class SecuredKeyScreenshotControllerTest : AbstractV2ScreenshotControllerTest() assertThat(screenshots).hasSize(1) val file = File(tolgeeProperties.fileStorage.fsDataPath + "/screenshots/" + screenshots[0].filename) assertThat(file).exists() - assertThat(file.readBytes().size).isCloseTo(1524, Offset.offset(200)) + assertThat(file.readBytes().size).isCloseTo(1070, Offset.offset(500)) node("filename").isString.startsWith(screenshots[0].filename).satisfies { val parts = it.split("?token=") val auth = jwtService.validateTicket(parts[1], JwtService.TicketType.IMG_ACCESS) diff --git a/backend/app/src/test/kotlin/io/tolgee/autoTranslating/AutoTranslatingTest.kt b/backend/app/src/test/kotlin/io/tolgee/autoTranslating/AutoTranslatingTest.kt index f859360e7b..4e91ce411b 100644 --- a/backend/app/src/test/kotlin/io/tolgee/autoTranslating/AutoTranslatingTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/autoTranslating/AutoTranslatingTest.kt @@ -168,7 +168,7 @@ class AutoTranslatingTest : MachineTranslationTest() { ) waitForSpanishTranslationSet("jaj") - verify(googleTranslate, times(2)).translate(any(), any()) + verify(googleTranslate, times(2)).translate(any(), any(), any(), any()) val balance = mtCreditBucketService.getCreditBalances(testData.project) balance.creditBalance.assert.isEqualTo(0) diff --git a/backend/app/src/test/kotlin/io/tolgee/automation/AutomationCachingTest.kt b/backend/app/src/test/kotlin/io/tolgee/automation/AutomationCachingTest.kt index 3273346252..3df1f316e4 100644 --- a/backend/app/src/test/kotlin/io/tolgee/automation/AutomationCachingTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/automation/AutomationCachingTest.kt @@ -8,6 +8,7 @@ import io.tolgee.service.automations.AutomationService import io.tolgee.testing.ContextRecreatingTest import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod import io.tolgee.testing.assert +import jakarta.persistence.EntityManager import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mockito @@ -16,7 +17,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.SpyBean import org.springframework.cache.Cache -import javax.persistence.EntityManager @AutoConfigureMockMvc @ContextRecreatingTest diff --git a/backend/app/src/test/kotlin/io/tolgee/automation/AutomationIntegrationTest.kt b/backend/app/src/test/kotlin/io/tolgee/automation/AutomationIntegrationTest.kt index 9898c4b5cb..f59a6f253a 100644 --- a/backend/app/src/test/kotlin/io/tolgee/automation/AutomationIntegrationTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/automation/AutomationIntegrationTest.kt @@ -98,10 +98,14 @@ class AutomationIntegrationTest : ProjectAuthControllerTest("/v2/projects/") { purgingMock = mock() doReturn(purgingMock).whenever(contentDeliveryCachePurgingProvider).defaultPurging + // wait for the first invocation happening because of test data saving, then clear invocations + Thread.sleep(1000) + Mockito.clearInvocations(fileStorageMock) + modifyTranslationData() verifyContentDeliveryPublish() - waitForNotThrowing { + waitForNotThrowing(pollTime = 200) { contentDeliveryConfigService .get(testData.defaultServerContentDeliveryConfig.self.id) .lastPublished!!.time diff --git a/backend/app/src/test/kotlin/io/tolgee/batch/AbstractBatchJobsGeneralTest.kt b/backend/app/src/test/kotlin/io/tolgee/batch/AbstractBatchJobsGeneralTest.kt index 4f306a9ae1..c246277e0d 100644 --- a/backend/app/src/test/kotlin/io/tolgee/batch/AbstractBatchJobsGeneralTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/batch/AbstractBatchJobsGeneralTest.kt @@ -266,13 +266,18 @@ abstract class AbstractBatchJobsGeneralTest : AbstractSpringTest(), Logging { @Test fun `debounces job`() { currentDateProvider.forcedDate = currentDateProvider.date + val startTie = currentDateProvider.date + util.makeAutomationChunkProcessorPass() val firstJobId = util.runDebouncedJob().id repeat(2) { + Thread.sleep(500) util.runDebouncedJob().id.assert.isEqualTo(firstJobId) } currentDateProvider.move(Duration.ofSeconds(5)) + + Thread.sleep(500) repeat(2) { util.runDebouncedJob().id.assert.isEqualTo(firstJobId) } @@ -284,7 +289,11 @@ abstract class AbstractBatchJobsGeneralTest : AbstractSpringTest(), Logging { // test it debounces for max time (10 sec * 4 = 40) repeat(7) { - currentDateProvider.move(Duration.ofSeconds(5)) + currentDateProvider.move(Duration.ofSeconds(2)) + Thread.sleep(20) + util.runDebouncedJob().id.assert.isEqualTo(anotherJobId) + currentDateProvider.move(Duration.ofSeconds(3)) + Thread.sleep(20) util.runDebouncedJob().id.assert.isEqualTo(anotherJobId) } diff --git a/backend/app/src/test/kotlin/io/tolgee/batch/BatchJobTestUtil.kt b/backend/app/src/test/kotlin/io/tolgee/batch/BatchJobTestUtil.kt index 4b6565e0e4..e02b62289c 100644 --- a/backend/app/src/test/kotlin/io/tolgee/batch/BatchJobTestUtil.kt +++ b/backend/app/src/test/kotlin/io/tolgee/batch/BatchJobTestUtil.kt @@ -27,6 +27,7 @@ import io.tolgee.util.addMinutes import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger import io.tolgee.websocket.WebsocketTestHelper +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.mockito.ArgumentMatchers import org.mockito.kotlin.any @@ -41,7 +42,6 @@ import org.springframework.context.ApplicationContext import org.springframework.transaction.PlatformTransactionManager import java.time.Duration import java.util.* -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext class BatchJobTestUtil( diff --git a/backend/app/src/test/kotlin/io/tolgee/cache/AbstractCacheTest.kt b/backend/app/src/test/kotlin/io/tolgee/cache/AbstractCacheTest.kt index f0c2601a33..d3d8dadef5 100644 --- a/backend/app/src/test/kotlin/io/tolgee/cache/AbstractCacheTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/cache/AbstractCacheTest.kt @@ -109,9 +109,9 @@ abstract class AbstractCacheTest : AbstractSpringTest() { project.organizationOwner = Organization() whenever(projectRepository.findById(project.id)).then { Optional.of(project) } projectService.findDto(project.id) - Mockito.verify(projectRepository, times(1)).findById(project.id) + Mockito.verify(projectRepository, times(1)).find(project.id) projectService.findDto(project.id) - Mockito.verify(projectRepository, times(1)).findById(project.id) + Mockito.verify(projectRepository, times(1)).find(project.id) } @Test diff --git a/backend/app/src/test/kotlin/io/tolgee/controllers/AbstractApiKeyTest.kt b/backend/app/src/test/kotlin/io/tolgee/controllers/AbstractApiKeyTest.kt index 7fecd6ab75..3631804777 100644 --- a/backend/app/src/test/kotlin/io/tolgee/controllers/AbstractApiKeyTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/controllers/AbstractApiKeyTest.kt @@ -4,13 +4,13 @@ import io.tolgee.dtos.response.ApiKeyDTO.ApiKeyDTO import io.tolgee.fixtures.generateUniqueString import io.tolgee.model.enums.Scope import io.tolgee.testing.AbstractControllerTest -import io.tolgee.testing.assertions.UserApiAppAction +import io.tolgee.testing.assertions.PakAction import org.springframework.test.web.servlet.MvcResult import org.springframework.test.web.servlet.result.MockMvcResultMatchers @Deprecated("This is too complicated") abstract class AbstractApiKeyTest : AbstractControllerTest() { - fun performAction(action: UserApiAppAction): MvcResult { + fun performAction(action: PakAction): MvcResult { return try { var resultActions = mvc.perform(action.requestBuilder) if (action.expectedStatus != null) { diff --git a/backend/app/src/test/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialectTest.kt b/backend/app/src/test/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialectTest.kt index 85f6c29eb6..e21846cb29 100644 --- a/backend/app/src/test/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialectTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialectTest.kt @@ -2,12 +2,12 @@ package io.tolgee.dialects.postgres import io.tolgee.model.UserAccount import io.tolgee.testing.assertions.Assertions.assertThat +import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @SpringBootTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) diff --git a/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/CreateEnabledTest.kt b/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/CreateEnabledTest.kt index 39b79f6d00..c79a2cfea1 100644 --- a/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/CreateEnabledTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/CreateEnabledTest.kt @@ -29,7 +29,8 @@ import java.io.File classes = [Application::class], properties = [ "tolgee.file-storage.fs-data-path=./build/create-enabled-test-data/", - "tolgee.authentication.initial-username=johny" + "tolgee.authentication.initial-username=johny", + "tolgee.internal.disable-initial-user-creation=false" ] ) @TestInstance(TestInstance.Lifecycle.PER_CLASS) diff --git a/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/LegacyMigrationTest.kt b/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/LegacyMigrationTest.kt index f56386dced..d3df61a01d 100644 --- a/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/LegacyMigrationTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/initial_user_creation/LegacyMigrationTest.kt @@ -25,7 +25,8 @@ import org.springframework.transaction.annotation.Transactional @SpringBootTest( classes = [Application::class], properties = [ - "tolgee.authentication.initial-username=johny" + "tolgee.authentication.initial-username=johny", + "tolgee.internal.disable-initial-user-creation=false" ] ) @TestInstance(TestInstance.Lifecycle.PER_CLASS) diff --git a/backend/app/src/test/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobTest.kt b/backend/app/src/test/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobTest.kt index 7e611a9ef7..3f7843926b 100644 --- a/backend/app/src/test/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobTest.kt @@ -1,96 +1,96 @@ -package io.tolgee.jobs.migration.translationStats - -import io.tolgee.AbstractSpringTest -import io.tolgee.development.testDataBuilder.data.TranslationsTestData -import io.tolgee.repository.TranslationRepository -import io.tolgee.testing.assertions.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.batch.core.BatchStatus -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.transaction.TestTransaction -import org.springframework.transaction.annotation.Transactional - -@SpringBootTest -class TranslationStatsJobTest : AbstractSpringTest() { - - @Autowired - lateinit var translationsStatsUpdateJobRunner: TranslationsStatsUpdateJobRunner - - @Autowired - lateinit var translationRepository: TranslationRepository - - @Test - @Transactional - fun `it adds the stats`() { - prepareData(10) - - val instance = translationsStatsUpdateJobRunner.run() - - assertStatsAdded() - assertThat(instance?.status).isEqualTo(BatchStatus.COMPLETED) - } - - @Test - @Transactional - fun `it does not run multiple times for same params`() { - prepareData() - - // first - it really runs - val instance = translationsStatsUpdateJobRunner.run() - // nothing to migrate, no run - val instance2 = translationsStatsUpdateJobRunner.run() - - assertThat(instance).isNotNull - assertThat(instance2).isNull() - } - - @Test - @Transactional - fun `it runs again when new translation without stats is created`() { - val testData = prepareData() - - val instance = translationsStatsUpdateJobRunner.run() - - TestTransaction.start() - val newTranslationId = translationService.setForKey(testData.aKey, mapOf("en" to "Hellooooo!"))["en"]!!.id - entityManager - .createNativeQuery( - "update translation set word_count = null, character_count = null where id = $newTranslationId" - ).executeUpdate() - TestTransaction.flagForCommit() - TestTransaction.end() - - val instance2 = translationsStatsUpdateJobRunner.run() - assertThat(instance2?.id).isNotEqualTo(instance?.id) - } - - private fun assertStatsAdded() { - val translations = translationRepository.findAll().toMutableList() - translations.sortBy { it.id } - - assertThat(translations) - .allMatch { it.wordCount != null } - .allMatch { it.characterCount != null } - } - - private fun prepareData(keysToCreateCount: Long = 10): TranslationsTestData { - val testData = TranslationsTestData() - testData.generateLotOfData(keysToCreateCount) - testDataService.saveTestData(testData.root) - - commitTransaction() - - entityManager - .createNativeQuery("update translation set word_count = null, character_count = null") - .executeUpdate() - - commitTransaction() - - val translations = translationRepository.findAll() - assertThat(translations).allMatch { it.wordCount == null }.allMatch { it.characterCount == null } - - TestTransaction.end() - return testData - } -} +// package io.tolgee.jobs.migration.translationStats +// +// import io.tolgee.AbstractSpringTest +// import io.tolgee.development.testDataBuilder.data.TranslationsTestData +// import io.tolgee.repository.TranslationRepository +// import io.tolgee.testing.assertions.Assertions.assertThat +// import org.junit.jupiter.api.Test +// import org.springframework.batch.core.BatchStatus +// import org.springframework.beans.factory.annotation.Autowired +// import org.springframework.boot.test.context.SpringBootTest +// import org.springframework.test.context.transaction.TestTransaction +// import org.springframework.transaction.annotation.Transactional +// +// @SpringBootTest +// class TranslationStatsJobTest : AbstractSpringTest() { +// +// @Autowired +// lateinit var translationsStatsUpdateJobRunner: TranslationsStatsUpdateJobRunner +// +// @Autowired +// lateinit var translationRepository: TranslationRepository +// +// @Test +// @Transactional +// fun `it adds the stats`() { +// prepareData(10) +// +// val instance = translationsStatsUpdateJobRunner.run() +// +// assertStatsAdded() +// assertThat(instance?.status).isEqualTo(BatchStatus.COMPLETED) +// } +// +// @Test +// @Transactional +// fun `it does not run multiple times for same params`() { +// prepareData() +// +// // first - it really runs +// val instance = translationsStatsUpdateJobRunner.run() +// // nothing to migrate, no run +// val instance2 = translationsStatsUpdateJobRunner.run() +// +// assertThat(instance).isNotNull +// assertThat(instance2).isNull() +// } +// +// @Test +// @Transactional +// fun `it runs again when new translation without stats is created`() { +// val testData = prepareData() +// +// val instance = translationsStatsUpdateJobRunner.run() +// +// TestTransaction.start() +// val newTranslationId = translationService.setForKey(testData.aKey, mapOf("en" to "Hellooooo!"))["en"]!!.id +// entityManager +// .createNativeQuery( +// "update translation set word_count = null, character_count = null where id = $newTranslationId" +// ).executeUpdate() +// TestTransaction.flagForCommit() +// TestTransaction.end() +// +// val instance2 = translationsStatsUpdateJobRunner.run() +// assertThat(instance2?.id).isNotEqualTo(instance?.id) +// } +// +// private fun assertStatsAdded() { +// val translations = translationRepository.findAll().toMutableList() +// translations.sortBy { it.id } +// +// assertThat(translations) +// .allMatch { it.wordCount != null } +// .allMatch { it.characterCount != null } +// } +// +// private fun prepareData(keysToCreateCount: Long = 10): TranslationsTestData { +// val testData = TranslationsTestData() +// testData.generateLotOfData(keysToCreateCount) +// testDataService.saveTestData(testData.root) +// +// commitTransaction() +// +// entityManager +// .createNativeQuery("update translation set word_count = null, character_count = null") +// .executeUpdate() +// +// commitTransaction() +// +// val translations = translationRepository.findAll() +// assertThat(translations).allMatch { it.wordCount == null }.allMatch { it.characterCount == null } +// +// TestTransaction.end() +// return testData +// } +// } diff --git a/backend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportFileRepositoryTest.kt b/backend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportFileRepositoryTest.kt index fa4966731f..68b6f22ab9 100644 --- a/backend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportFileRepositoryTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportFileRepositoryTest.kt @@ -5,11 +5,11 @@ import io.tolgee.model.dataImport.Import import io.tolgee.model.dataImport.ImportFile import io.tolgee.testing.assertions.Assertions.assertThat import io.tolgee.testing.assertions.Assertions.assertThatExceptionOfType +import jakarta.validation.ConstraintViolationException import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional -import javax.validation.ConstraintViolationException @Transactional @SpringBootTest diff --git a/backend/app/src/test/kotlin/io/tolgee/security/ProjectApiKeyAuthenticationTest.kt b/backend/app/src/test/kotlin/io/tolgee/security/ProjectApiKeyAuthenticationTest.kt index be25884071..5f16548a3a 100644 --- a/backend/app/src/test/kotlin/io/tolgee/security/ProjectApiKeyAuthenticationTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/security/ProjectApiKeyAuthenticationTest.kt @@ -3,12 +3,17 @@ package io.tolgee.security import io.tolgee.API_KEY_HEADER_NAME import io.tolgee.controllers.AbstractApiKeyTest import io.tolgee.development.testDataBuilder.data.ApiKeysTestData -import io.tolgee.fixtures.* +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andIsForbidden +import io.tolgee.fixtures.andIsOk +import io.tolgee.fixtures.andIsUnauthorized +import io.tolgee.fixtures.generateUniqueString +import io.tolgee.fixtures.waitForNotThrowing import io.tolgee.model.enums.Scope import io.tolgee.security.authentication.JwtService import io.tolgee.testing.assert import io.tolgee.testing.assertions.Assertions -import io.tolgee.testing.assertions.UserApiAppAction +import io.tolgee.testing.assertions.PakAction import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc @@ -30,7 +35,7 @@ class ProjectApiKeyAuthenticationTest : AbstractApiKeyTest() { @Test fun accessWithApiKey_failure() { val mvcResult = mvc.perform(MockMvcRequestBuilders.get("/v2/projects/translations")) - .andExpect(MockMvcResultMatchers.status().isForbidden).andReturn() + .andExpect(MockMvcResultMatchers.status().isUnauthorized).andReturn() Assertions.assertThat(mvcResult).error() } @@ -52,14 +57,14 @@ class ProjectApiKeyAuthenticationTest : AbstractApiKeyTest() { val base = dbPopulator.createBase(generateUniqueString()) val apiKey = apiKeyService.create(base.userAccount, setOf(*Scope.values()), base.project) performAction( - UserApiAppAction( + PakAction( apiKey = apiKey.key, url = "/v2/projects", expectedStatus = HttpStatus.FORBIDDEN ) ) mvc.perform(MockMvcRequestBuilders.get("/v2/projects")) - .andIsForbidden + .andIsUnauthorized } @Test diff --git a/backend/app/src/test/kotlin/io/tolgee/service/SoftDeleteTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/SoftDeleteTest.kt new file mode 100644 index 0000000000..c8c2368ec9 --- /dev/null +++ b/backend/app/src/test/kotlin/io/tolgee/service/SoftDeleteTest.kt @@ -0,0 +1,48 @@ +package io.tolgee.service + +import io.tolgee.AbstractSpringTest +import io.tolgee.development.testDataBuilder.data.BaseTestData +import io.tolgee.testing.assert +import org.junit.jupiter.api.Test +import org.springframework.data.domain.Pageable + +class SoftDeleteTest : AbstractSpringTest() { + + @Test + fun `project is soft deleted`() { + val testData = BaseTestData() + executeInNewTransaction { + testDataService.saveTestData(testData.root) + } + executeInNewTransaction { + projectService.deleteProject(testData.projectBuilder.self.id) + } + val result = entityManager.createNativeQuery("select deleted_at from project where id = :id") + .setParameter("id", testData.projectBuilder.self.id) + .singleResult + + result.assert.isNotNull + } + + @Test + fun `queries don't return deleted projects`() { + val testData = BaseTestData() + executeInNewTransaction { + testDataService.saveTestData(testData.root) + } + + executeInNewTransaction { + projectService.deleteProject(testData.projectBuilder.self.id) + } + + executeInNewTransaction { + projectService.findAllPermitted(testData.user).assert.isEmpty() + projectService.findPermittedInOrganizationPaged( + pageable = Pageable.ofSize(100), + search = null, + organizationId = testData.projectBuilder.self.organizationOwner.id, + userAccountId = testData.user.id + ).assert.isEmpty() + } + } +} diff --git a/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt index 7c4af4aa25..b385217f7d 100644 --- a/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt @@ -137,7 +137,7 @@ class ProjectServiceTest : AbstractSpringTest() { testData.generateVeryLotOfData() testDataService.saveTestData(testData.root) val start = System.currentTimeMillis() - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) entityManager.flush() entityManager.clear() val time = System.currentTimeMillis() - start @@ -155,7 +155,7 @@ class ProjectServiceTest : AbstractSpringTest() { return@executeInNewTransaction testData } executeInNewTransaction(platformTransactionManager) { - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) } } @@ -181,7 +181,7 @@ class ProjectServiceTest : AbstractSpringTest() { } executeInNewTransaction(platformTransactionManager) { - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) } executeInNewTransaction { @@ -209,7 +209,7 @@ class ProjectServiceTest : AbstractSpringTest() { ) } executeInNewTransaction(platformTransactionManager) { - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) } } @@ -218,7 +218,7 @@ class ProjectServiceTest : AbstractSpringTest() { val testData = ContentDeliveryConfigTestData() testDataService.saveTestData(testData.root) executeInNewRepeatableTransaction(platformTransactionManager) { - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) } } @@ -227,7 +227,7 @@ class ProjectServiceTest : AbstractSpringTest() { val testData = WebhooksTestData() testDataService.saveTestData(testData.root) executeInNewTransaction(platformTransactionManager) { - projectService.deleteProject(testData.projectBuilder.self.id) + projectService.hardDeleteProject(testData.projectBuilder.self.id) } } } diff --git a/backend/app/src/test/kotlin/io/tolgee/util/BatchDumper.kt b/backend/app/src/test/kotlin/io/tolgee/util/BatchDumper.kt new file mode 100644 index 0000000000..1672bbdcbb --- /dev/null +++ b/backend/app/src/test/kotlin/io/tolgee/util/BatchDumper.kt @@ -0,0 +1,73 @@ +package io.tolgee.util + +import io.tolgee.batch.BatchJobChunkExecutionQueue +import io.tolgee.batch.BatchJobService +import io.tolgee.batch.state.BatchJobStateProvider +import io.tolgee.component.CurrentDateProvider +import org.springframework.stereotype.Component + +@Component +class BatchDumper( + private val batchJobService: BatchJobService, + private val batchJobStateProvider: BatchJobStateProvider, + private val currentDateProvider: CurrentDateProvider, + private val batchJobChunkExecutionQueue: BatchJobChunkExecutionQueue +) : Logging { + fun dump(jobId: Long) { + val stringBuilder = StringBuilder() + stringBuilder.append("Dumping job $jobId:") + dumpQueuedItems(jobId, stringBuilder) + dumpCachedState(stringBuilder, jobId) + dumpDbExecutions(jobId, stringBuilder) + logger.info(stringBuilder.toString()) + } + + private fun dumpDbExecutions(jobId: Long, stringBuilder: StringBuilder) { + val dbExecutions = batchJobService.getExecutions(jobId) + stringBuilder.append("\n\nDatabase state:") + stringBuilder.append(" (${dbExecutions.size} executions)") + stringBuilder.append("\n\n${listOf("Execution ID", "Status", "Completed", "ExecuteAfter offset").toTable()}") + val dbExecutionsString = dbExecutions + .joinToString(separator = "\n") { + listOf( + it.id, it.status.name, it.status.completed, it.executeAfter.offset + ).toTable() + } + stringBuilder.append("\n$dbExecutionsString") + } + + private fun dumpCachedState(stringBuilder: StringBuilder, jobId: Long) { + stringBuilder.append("\n\nCached state:") + val cachedState = batchJobStateProvider.getCached(jobId)?.entries + if (cachedState == null) { + stringBuilder.append("\nNo cached state") + } else { + stringBuilder.append(" (${cachedState.size} executions)") + val headers = listOf("Execution ID", "Status", "Completed", "Transaction committed").toTable() + val cachedStateString = + cachedState.joinToString(separator = "\n") { + listOf( + it.key, + it.value.status.name, + it.value.status.completed, + it.value.transactionCommitted, + ).toTable() + } + stringBuilder.append("\n\n$headers\n$cachedStateString") + } + } + + private fun dumpQueuedItems(jobId: Long, stringBuilder: StringBuilder) { + val queuedItems = batchJobChunkExecutionQueue.getQueuedJobItems(jobId) + val queuedItemsString = queuedItems.joinToString(", ") + stringBuilder.append("\n\nQueued items: (${queuedItems.size} executions) $queuedItemsString") + } + + private fun Collection.toTable() = this.joinToString("\t\t|") + + private val java.util.Date?.offset: Long? + get() { + this ?: return null + return this.time - currentDateProvider.date.time + } +} diff --git a/backend/app/src/test/kotlin/io/tolgee/websocket/WebsocketTestHelper.kt b/backend/app/src/test/kotlin/io/tolgee/websocket/WebsocketTestHelper.kt index fe210b2a34..2d7e99a472 100644 --- a/backend/app/src/test/kotlin/io/tolgee/websocket/WebsocketTestHelper.kt +++ b/backend/app/src/test/kotlin/io/tolgee/websocket/WebsocketTestHelper.kt @@ -43,7 +43,7 @@ class WebsocketTestHelper(val port: Int?, val jwtToken: String, val projectId: L webSocketStompClient.messageConverter = SimpleMessageConverter() sessionHandler = MySessionHandler(path, receivedMessages) - connection = webSocketStompClient.connect( + connection = webSocketStompClient.connectAsync( "http://localhost:$port/websocket", WebSocketHttpHeaders(), StompHeaders().apply { add("jwtToken", jwtToken) }, sessionHandler!! @@ -70,6 +70,8 @@ class WebsocketTestHelper(val port: Int?, val jwtToken: String, val projectId: L var subscription: StompSession.Subscription? = null override fun afterConnected(session: StompSession, connectedHeaders: StompHeaders) { + logger.info("Connected to websocket") + logger.info("Subscribing to $dest") subscription = session.subscribe(dest, this) } diff --git a/backend/app/src/test/resources/application.yaml b/backend/app/src/test/resources/application.yaml index 5e3ad5196a..4404906561 100644 --- a/backend/app/src/test/resources/application.yaml +++ b/backend/app/src/test/resources/application.yaml @@ -4,14 +4,19 @@ spring: - org.redisson.spring.starter.RedissonAutoConfiguration - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration jpa: - show-sql: true +# show-sql: true properties: hibernate: + order_by: + default_null_ordering: first jdbc: batch_size: 1000 order_inserts: true order_updates: true dialect: io.tolgee.dialects.postgres.CustomPostgreSQLDialect + enhancer: + enableLazyInitialization: true + enableDirtyTracking: true mvc: pathmatch: matching-strategy: ant_path_matcher @@ -38,8 +43,9 @@ spring: tolgee: postgres-autostart: enabled: true - container-name: tolgee_backend_tests_postgres + container-name: tolgee_backend_tests_postgres_main port: 55433 + stop: false data-path: ./build/test_data authentication: native-enabled: true @@ -57,6 +63,7 @@ tolgee: internal: fake-mt-providers: true mock-free-plan: true + disable-initial-user-creation: true cache: caffeine-max-size: 1000 machine-translation: @@ -79,3 +86,4 @@ logging: io.tolgee.batch.BatchJobActionService: DEBUG io.tolgee.component.atomicLong.AtomicLongProvider: DEBUG io.tolgee: DEBUG + io.tolgee.component.CurrentDateProvider: DEBUG diff --git a/backend/data/build.gradle b/backend/data/build.gradle index ac104e5e92..e96ad2146b 100644 --- a/backend/data/build.gradle +++ b/backend/data/build.gradle @@ -7,19 +7,19 @@ buildscript { mavenCentral() } dependencies { - classpath "org.hibernate:hibernate-gradle-plugin:5.6.10.Final" } } plugins { id 'io.spring.dependency-management' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id 'java' id 'org.liquibase.gradle' id 'org.jetbrains.kotlin.jvm' id "kotlin-jpa" id "org.jetbrains.kotlin.kapt" id "kotlin-allopen" + id "org.hibernate.orm" } group = 'io.tolgee' @@ -41,7 +41,6 @@ configurations { apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' -apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: "org.jetbrains.kotlin.plugin.jpa" apply plugin: "org.jetbrains.kotlin.plugin.spring" @@ -61,19 +60,10 @@ idea { } } -hibernate { - enhance { - enableLazyInitialization = true - enableDirtyTracking = true - enableAssociationManagement = false - enableExtendedEnhancement = false - } -} - allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Component") annotation("org.springframework.transaction.annotation.Transactional") annotation("org.springframework.stereotype.Service") @@ -88,7 +78,14 @@ diff.dependsOn compileKotlin diffChangeLog.dependsOn compileKotlin kotlin { - jvmToolchain(11) + jvmToolchain(17) +} + +hibernate { + enhancement { + lazyInitialization = true + dirtyTracking = true + } } dependencies { @@ -113,22 +110,21 @@ dependencies { * DB */ runtimeOnly 'org.postgresql:postgresql' - implementation 'org.hibernate:hibernate-jpamodelgen' - kapt "org.hibernate:hibernate-jpamodelgen" + implementation "org.hibernate:hibernate-jpamodelgen:$hibernateVersion" + kapt "org.hibernate:hibernate-jpamodelgen:$hibernateVersion" /** * Redisson */ - implementation dependencies.create(libs.redissonSpringBootStarter.get()) { - exclude group: 'org.redisson', module: 'redisson-spring-data-31' - } - implementation libs.redissonSpringData + implementation libs.redissonSpringBootStarter /** * Liquibase */ implementation libs.liquibaseCore liquibaseRuntime libs.liquibaseCore + liquibaseRuntime libs.jacksonModuleKotlin + liquibaseRuntime libs.liquibasePicoli liquibaseRuntime 'org.postgresql:postgresql' liquibaseRuntime('org.liquibase:liquibase-groovy-dsl:3.0.2') liquibaseRuntime libs.liquibaseHibernate @@ -155,8 +151,7 @@ dependencies { /** * SPRING DOC */ - implementation libs.springDocOpenApiWebMvcCore - implementation libs.springDocOpenApiKotlin + implementation libs.springDocOpenApiCommon /** * MISC @@ -177,6 +172,8 @@ dependencies { implementation 'com.eatthepath:java-otp:0.4.0' implementation libs.postHog implementation libs.micrometerPrometheus + implementation 'org.dom4j:dom4j:2.1.4' + implementation libs.jacksonKotlin /** * Google translation API @@ -205,20 +202,23 @@ dependencies { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" } project.tasks.findByName("compileKotlin").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } project.tasks.findByName("compileJava").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } -project.tasks.findByName("bootJarMainClassName").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } +project.tasks.findByName("bootJarMainClassName")?.onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } sourceSets { main.kotlin.srcDirs = ['src/main/kotlin', 'src/main/java'] test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/backend/data/src/main/java/io/tolgee/dtos/TelemetryReportRequest.kt b/backend/data/src/main/java/io/tolgee/dtos/TelemetryReportRequest.kt index 06c2dd471b..d796cd441b 100644 --- a/backend/data/src/main/java/io/tolgee/dtos/TelemetryReportRequest.kt +++ b/backend/data/src/main/java/io/tolgee/dtos/TelemetryReportRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class TelemetryReportRequest { @NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/PostgresRunner.kt b/backend/data/src/main/kotlin/io/tolgee/PostgresRunner.kt new file mode 100644 index 0000000000..0da1798733 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/PostgresRunner.kt @@ -0,0 +1,13 @@ +package io.tolgee + +interface PostgresRunner { + fun run() + fun stop() + + /** + * Whether migrations should be run or not. + * Usefull for tests, where we don't want to wait for liquibase every time context starts + */ + val shouldRunMigrations: Boolean + val datasourceUrl: String +} diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/ActivityHandlerInterceptor.kt b/backend/data/src/main/kotlin/io/tolgee/activity/ActivityHandlerInterceptor.kt index e6e27fafc7..f835f783eb 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/ActivityHandlerInterceptor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/ActivityHandlerInterceptor.kt @@ -3,6 +3,8 @@ package io.tolgee.activity import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.sentry.Sentry import io.tolgee.component.reporting.SdkInfoProvider +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.beans.factory.support.ScopeNotActiveException import org.springframework.core.annotation.AnnotationUtils @@ -12,8 +14,6 @@ import org.springframework.web.servlet.HandlerInterceptor import java.net.URLDecoder import java.nio.charset.StandardCharsets import java.util.* -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class ActivityHandlerInterceptor( diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt b/backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt index 6d8bd31c2d..1d1b85a040 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt @@ -10,13 +10,13 @@ import io.tolgee.model.views.activity.ProjectActivityView import io.tolgee.repository.activity.ActivityModifiedEntityRepository import io.tolgee.util.Logging import io.tolgee.util.logger +import jakarta.persistence.EntityExistsException +import jakarta.persistence.EntityManager import org.springframework.context.ApplicationContext import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityExistsException -import javax.persistence.EntityManager @Service class ActivityService( diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt index 8121e511c8..9921c7cb3e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt @@ -2,16 +2,15 @@ package io.tolgee.activity.iterceptor import io.tolgee.activity.data.RevisionType import io.tolgee.util.Logging -import org.hibernate.EmptyInterceptor +import org.hibernate.Interceptor import org.hibernate.Transaction import org.hibernate.type.Type import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext import org.springframework.stereotype.Component -import java.io.Serializable @Component -class ActivityDatabaseInterceptor : EmptyInterceptor(), Logging { +class ActivityDatabaseInterceptor : Interceptor, Logging { @Autowired lateinit var applicationContext: ApplicationContext @@ -22,7 +21,7 @@ class ActivityDatabaseInterceptor : EmptyInterceptor(), Logging { override fun onSave( entity: Any?, - id: Serializable?, + id: Any?, state: Array?, propertyNames: Array?, types: Array? @@ -36,7 +35,7 @@ class ActivityDatabaseInterceptor : EmptyInterceptor(), Logging { override fun onDelete( entity: Any?, - id: Serializable?, + id: Any?, state: Array?, propertyNames: Array?, types: Array? @@ -49,7 +48,7 @@ class ActivityDatabaseInterceptor : EmptyInterceptor(), Logging { override fun onFlushDirty( entity: Any?, - id: Serializable?, + id: Any?, currentState: Array?, previousState: Array?, propertyNames: Array?, @@ -66,15 +65,15 @@ class ActivityDatabaseInterceptor : EmptyInterceptor(), Logging { return true } - override fun onCollectionRemove(collection: Any?, key: Serializable?) { + override fun onCollectionRemove(collection: Any?, key: Any?) { interceptedEventsManager.onCollectionModification(collection, key) } - override fun onCollectionRecreate(collection: Any?, key: Serializable?) { + override fun onCollectionRecreate(collection: Any?, key: Any?) { interceptedEventsManager.onCollectionModification(collection, key) } - override fun onCollectionUpdate(collection: Any?, key: Serializable?) { + override fun onCollectionUpdate(collection: Any?, key: Any?) { interceptedEventsManager.onCollectionModification(collection, key) } diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt index cac6512368..fb2b17d610 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt @@ -19,17 +19,16 @@ import io.tolgee.model.activity.ActivityRevision import io.tolgee.security.ProjectHolder import io.tolgee.security.ProjectNotSelectedException import io.tolgee.security.authentication.AuthenticationFacade +import jakarta.persistence.EntityManager import org.hibernate.Transaction import org.hibernate.action.spi.BeforeTransactionCompletionProcess -import org.hibernate.collection.internal.AbstractPersistentCollection +import org.hibernate.collection.spi.AbstractPersistentCollection import org.hibernate.event.spi.EventSource import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Scope import org.springframework.stereotype.Component -import java.io.Serializable -import javax.persistence.EntityManager import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.hasAnnotation @@ -44,8 +43,8 @@ class InterceptedEventsManager( activityHolder.transactionRollbackOnly = tx.rollbackOnly } - fun onCollectionModification(collection: Any?, key: Serializable?) { - if (collection !is AbstractPersistentCollection || collection !is Collection<*> || key !is Long) { + fun onCollectionModification(collection: Any?, key: Any?) { + if (collection !is AbstractPersistentCollection<*> || collection !is Collection<*> || key !is Long) { return } diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/projectActivityView/ActivityViewByRevisionsProvider.kt b/backend/data/src/main/kotlin/io/tolgee/activity/projectActivityView/ActivityViewByRevisionsProvider.kt index 721d75e4df..5c385e50c5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/projectActivityView/ActivityViewByRevisionsProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/projectActivityView/ActivityViewByRevisionsProvider.kt @@ -16,9 +16,9 @@ import io.tolgee.model.views.activity.ProjectActivityView import io.tolgee.repository.activity.ActivityRevisionRepository import io.tolgee.service.security.UserAccountService import io.tolgee.util.EntityUtil +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.Predicate import org.springframework.context.ApplicationContext -import javax.persistence.EntityManager -import javax.persistence.criteria.Predicate class ActivityViewByRevisionsProvider( private val applicationContext: ApplicationContext, diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchActivityParamsProvider.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchActivityParamsProvider.kt index 7234d1eaca..8b946bfa83 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchActivityParamsProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchActivityParamsProvider.kt @@ -1,8 +1,8 @@ package io.tolgee.batch import io.tolgee.activity.PublicParamsProvider +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class BatchActivityParamsProvider( diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.kt index 317becdd8c..a0b1cf55c1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.kt @@ -18,6 +18,8 @@ import io.tolgee.util.Logging import io.tolgee.util.addSeconds import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger +import jakarta.persistence.EntityManager +import jakarta.persistence.LockModeType import org.hibernate.LockOptions import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationContext @@ -27,8 +29,6 @@ import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.UnexpectedRollbackException -import javax.persistence.EntityManager -import javax.persistence.LockModeType @Service class BatchJobActionService( @@ -84,15 +84,17 @@ class BatchJobActionService( savePointManager.rollbackSavepoint(savepoint) // we have rolled back the transaction, so no targets were actually successfull lockedExecution.successTargets = listOf() + entityManager.clear() rollbackActivity() } progressManager.handleProgress(lockedExecution) - entityManager.persist(lockedExecution) + entityManager.persist(entityManager.merge(lockedExecution)) if (lockedExecution.retry) { retryExecution = util.retryExecution entityManager.persist(util.retryExecution) + entityManager.flush() } logger.debug("Job ${batchJobDto.id}: ✅ Processed chunk ${lockedExecution.id}") @@ -211,7 +213,7 @@ class BatchJobActionService( .setParameter("id", id) .setLockMode(LockModeType.PESSIMISTIC_WRITE) .setHint( - "javax.persistence.lock.timeout", + "jakarta.persistence.lock.timeout", LockOptions.SKIP_LOCKED ).resultList.singleOrNull() } diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActivityFinalizer.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActivityFinalizer.kt index 55443130b5..669ff6dd81 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActivityFinalizer.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActivityFinalizer.kt @@ -12,11 +12,11 @@ import io.tolgee.fixtures.waitFor import io.tolgee.fixtures.waitForNotThrowing import io.tolgee.util.Logging import io.tolgee.util.logger -import org.hibernate.jpa.TypedParameterValue +import jakarta.persistence.EntityManager +import org.hibernate.query.TypedParameterValue import org.hibernate.type.StandardBasicTypes import org.springframework.context.event.EventListener import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class BatchJobActivityFinalizer( @@ -41,6 +41,7 @@ class BatchJobActivityFinalizer( fun finalizeActivityWhenJobCompleted(job: BatchJobDto) { activityHolder.afterActivityFlushed = afterFlush@{ + entityManager.clear() try { logger.debug("Finalizing activity for job ${job.id} (after flush)") waitForOtherChunksToComplete(job) diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobCancellationManager.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobCancellationManager.kt index 645275cd75..0f0ee630de 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobCancellationManager.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobCancellationManager.kt @@ -10,6 +10,8 @@ import io.tolgee.pubSub.RedisPubSubReceiverConfiguration import io.tolgee.util.Logging import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger +import jakarta.persistence.EntityManager +import jakarta.persistence.LockModeType import org.hibernate.LockOptions import org.springframework.context.annotation.Lazy import org.springframework.context.event.EventListener @@ -17,8 +19,6 @@ import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.stereotype.Component import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager -import javax.persistence.LockModeType @Component class BatchJobCancellationManager( @@ -72,7 +72,7 @@ class BatchJobCancellationManager( ) .setLockMode(LockModeType.PESSIMISTIC_WRITE) .setHint( - "javax.persistence.lock.timeout", + "jakarta.persistence.lock.timeout", LockOptions.SKIP_LOCKED ) .setParameter("id", jobId) diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobChunkExecutionQueue.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobChunkExecutionQueue.kt index 6269ffc23d..008cae89c6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobChunkExecutionQueue.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobChunkExecutionQueue.kt @@ -11,6 +11,7 @@ import io.tolgee.model.batch.BatchJobChunkExecutionStatus import io.tolgee.pubSub.RedisPubSubReceiverConfiguration import io.tolgee.util.Logging import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.hibernate.LockOptions import org.springframework.beans.factory.InitializingBean import org.springframework.context.annotation.Lazy @@ -20,7 +21,6 @@ import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component import java.util.* import java.util.concurrent.ConcurrentLinkedQueue -import javax.persistence.EntityManager @Component class BatchJobChunkExecutionQueue( @@ -58,7 +58,7 @@ class BatchJobChunkExecutionQueue( BatchJobChunkExecution::class.java ).setParameter("executionStatus", BatchJobChunkExecutionStatus.PENDING) .setHint( - "javax.persistence.lock.timeout", + "jakarta.persistence.lock.timeout", LockOptions.SKIP_LOCKED ).resultList if (data.size > 0) { @@ -149,4 +149,8 @@ class BatchJobChunkExecutionQueue( override fun afterPropertiesSet() { metrics.registerJobQueue(queue) } + + fun getQueuedJobItems(jobId: Long): List { + return queue.filter { it.jobId == jobId } + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobConcurrentLauncher.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobConcurrentLauncher.kt index a7a86334e9..46693be253 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobConcurrentLauncher.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobConcurrentLauncher.kt @@ -9,6 +9,8 @@ import io.tolgee.fixtures.waitFor import io.tolgee.model.batch.BatchJobChunkExecutionStatus import io.tolgee.util.Logging import io.tolgee.util.logger +import io.tolgee.util.trace +import jakarta.annotation.PreDestroy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -17,7 +19,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.springframework.stereotype.Component import java.util.concurrent.ConcurrentHashMap -import javax.annotation.PreDestroy import kotlin.coroutines.CoroutineContext import kotlin.math.ceil @@ -147,10 +148,10 @@ class BatchJobConcurrentLauncher( ): Boolean { logger.trace("Trying to run execution ${executionItem.chunkExecutionId}") if (!executionItem.isTimeToExecute()) { - logger.trace( - """Execution ${executionItem.chunkExecutionId} not ready to execute, adding back to queue: - | Difference ${executionItem.executeAfter!! - currentDateProvider.date.time}""".trimMargin() - ) + logger.trace { + "Execution ${executionItem.chunkExecutionId} not ready to execute, adding back to queue:" + + " Difference ${executionItem.executeAfter!! - currentDateProvider.date.time}" + } addBackToQueue(executionItem) return false } @@ -214,7 +215,7 @@ class BatchJobConcurrentLauncher( } private fun addBackToQueue(executionItem: ExecutionQueueItem) { - logger.trace("Adding execution $executionItem back to queue") + logger.trace { "Adding execution $executionItem back to queue" } batchJobChunkExecutionQueue.addItemsToLocalQueue(listOf(executionItem)) } @@ -237,11 +238,20 @@ class BatchJobConcurrentLauncher( val debounceDuration = dto.debounceDurationInMs ?: return true val executeAfter = lastEventTime + debounceDuration if (executeAfter <= currentDateProvider.date.time) { + logger.debug( + "Debouncing duration reached for job ${dto.id}, " + + "execute after $executeAfter, " + + "now ${currentDateProvider.date.time}" + ) return true } val createdAt = dto.createdAt ?: return true val debounceMaxWaitTimeInMs = dto.debounceMaxWaitTimeInMs ?: return true - return createdAt + debounceMaxWaitTimeInMs <= currentDateProvider.date.time + val maxTimeReached = createdAt + debounceMaxWaitTimeInMs <= currentDateProvider.date.time + if (maxTimeReached) { + logger.debug("Debouncing max wait time reached for job ${dto.id}") + } + return maxTimeReached } private fun canRunJobWithCharacter(character: JobCharacter): Boolean { @@ -257,7 +267,8 @@ class BatchJobConcurrentLauncher( private fun ExecutionQueueItem.trySetRunningState(): Boolean { return progressManager.trySetExecutionRunning(this.chunkExecutionId, this.jobId) { - val count = it.values.count { executionState -> executionState.status == BatchJobChunkExecutionStatus.RUNNING } + val count = + it.values.count { executionState -> executionState.status == BatchJobChunkExecutionStatus.RUNNING } if (count == 0) { return@trySetExecutionRunning true } diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt index 9fb5967f5d..6ba8ab59a9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt @@ -23,6 +23,7 @@ import io.tolgee.service.security.SecurityService import io.tolgee.util.Logging import io.tolgee.util.addMinutes import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.apache.commons.codec.digest.DigestUtils.sha256Hex import org.hibernate.LockOptions import org.springframework.context.ApplicationContext @@ -32,10 +33,8 @@ import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.event.TransactionalEventListener -import java.math.BigInteger import java.time.Duration import java.util.* -import javax.persistence.EntityManager @Service class BatchJobService( @@ -225,9 +224,9 @@ class BatchJobService( } val needsProgress = cachedProgresses.filter { it.value == null }.map { it.key }.toList() val progresses = batchJobRepository.getProgresses(needsProgress) - .associate { (it[0] as BigInteger).toLong() to it[1] as BigInteger } + .associate { it[0] as Long to it[1] as Long } - return jobs.associate { it.id to (cachedProgresses[it.id] ?: progresses[it.id]?.toLong() ?: 0).toInt() } + return jobs.associate { it.id to (cachedProgresses[it.id] ?: progresses[it.id] ?: 0).toInt() } } fun getView(jobId: Long): BatchJobView { @@ -252,7 +251,7 @@ class BatchJobService( ) .setParameter("jobIds", jobIds) .setHint( - "javax.persistence.lock.timeout", + "jakarta.persistence.lock.timeout", LockOptions.SKIP_LOCKED ).resultList } diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/CachingBatchJobService.kt b/backend/data/src/main/kotlin/io/tolgee/batch/CachingBatchJobService.kt index e59ffa1edb..588832759f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/CachingBatchJobService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/CachingBatchJobService.kt @@ -7,12 +7,12 @@ import io.tolgee.model.batch.BatchJobStatus import io.tolgee.repository.BatchJobRepository import io.tolgee.util.Logging import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.springframework.cache.annotation.CacheEvict import org.springframework.cache.annotation.Cacheable import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @Service class CachingBatchJobService( diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/ChunkProcessingUtil.kt b/backend/data/src/main/kotlin/io/tolgee/batch/ChunkProcessingUtil.kt index 2371e6aac2..916851c7fe 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/ChunkProcessingUtil.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/ChunkProcessingUtil.kt @@ -12,11 +12,11 @@ import io.tolgee.model.batch.BatchJobChunkExecution import io.tolgee.model.batch.BatchJobChunkExecutionStatus import io.tolgee.util.Logging import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.apache.commons.lang3.exception.ExceptionUtils import org.hibernate.LockOptions import org.springframework.context.ApplicationContext import java.util.* -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext import kotlin.math.pow import kotlin.system.measureTimeMillis @@ -212,7 +212,7 @@ open class ChunkProcessingUtil( .setParameter("batchJobId", job.id) .setParameter("status", BatchJobChunkExecutionStatus.FAILED) .setHint( - "javax.persistence.lock.timeout", + "jakarta.persistence.lock.timeout", LockOptions.NO_WAIT ) .resultList as List diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/ProgressManager.kt b/backend/data/src/main/kotlin/io/tolgee/batch/ProgressManager.kt index 516d1a2ae7..b31a0c8f1c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/ProgressManager.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/ProgressManager.kt @@ -13,6 +13,7 @@ import io.tolgee.model.batch.BatchJobChunkExecution import io.tolgee.model.batch.BatchJobChunkExecutionStatus import io.tolgee.model.batch.BatchJobStatus import io.tolgee.util.Logging +import io.tolgee.util.debug import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger import org.springframework.context.ApplicationEventPublisher @@ -106,7 +107,16 @@ class ProgressManager( it } val isJobCompleted = state.all { it.value.transactionCommitted && it.value.status.completed } - logger.debug("Is job ${execution.batchJob.id} completed: $isJobCompleted (execution: ${execution.id})") + logger.debug { + val incompleteExecutions = + state.filter { !(it.value.transactionCommitted && it.value.status.completed) } + .map { it.key } + .joinToString(", ") + "Is job ${execution.batchJob.id} " + + "completed: $isJobCompleted " + + "(current execution: ${execution.id}), " + + "incomplete executions: $incompleteExecutions" + } if (isJobCompleted) { onJobCompletedCommitted(execution) } diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/AutoTranslateChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/AutoTranslateChunkProcessor.kt index 6686607935..e73cc6de3b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/AutoTranslateChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/AutoTranslateChunkProcessor.kt @@ -10,8 +10,8 @@ import io.tolgee.model.Project import io.tolgee.model.batch.params.AutoTranslationJobParams import io.tolgee.service.machineTranslation.MtServiceConfigService import io.tolgee.service.translation.AutoTranslationService +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/ClearTranslationsChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/ClearTranslationsChunkProcessor.kt index 475ac9a7d8..7cf3bfa1d3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/ClearTranslationsChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/ClearTranslationsChunkProcessor.kt @@ -5,9 +5,9 @@ import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.ClearTranslationsRequest import io.tolgee.model.batch.params.ClearTranslationsJobParams import io.tolgee.service.translation.TranslationService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/CopyTranslationsChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/CopyTranslationsChunkProcessor.kt index d61f5291cb..51e88dfe4f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/CopyTranslationsChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/CopyTranslationsChunkProcessor.kt @@ -5,9 +5,9 @@ import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.CopyTranslationRequest import io.tolgee.model.batch.params.CopyTranslationJobParams import io.tolgee.service.translation.TranslationService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/DeleteKeysChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/DeleteKeysChunkProcessor.kt index 097966cde6..eb5cb7e3c2 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/DeleteKeysChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/DeleteKeysChunkProcessor.kt @@ -4,9 +4,9 @@ import io.tolgee.batch.ChunkProcessor import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.DeleteKeysRequest import io.tolgee.service.key.KeyService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component @@ -26,7 +26,7 @@ class DeleteKeysChunkProcessor( subChunked.forEach { subChunk -> coroutineContext.ensureActive() @Suppress("UNCHECKED_CAST") - keyService.deleteMultiple(subChunk as List) + keyService.deleteMultiple(subChunk) entityManager.flush() progress += subChunk.size onProgress.invoke(progress) diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/MachineTranslationChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/MachineTranslationChunkProcessor.kt index 24d0fccb25..50e5ab8dc5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/MachineTranslationChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/MachineTranslationChunkProcessor.kt @@ -9,8 +9,8 @@ import io.tolgee.constants.MtServiceType import io.tolgee.model.Project import io.tolgee.model.batch.params.MachineTranslationJobParams import io.tolgee.service.machineTranslation.MtServiceConfigService +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetKeysNamespaceChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetKeysNamespaceChunkProcessor.kt index 517616032a..d90951b27d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetKeysNamespaceChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetKeysNamespaceChunkProcessor.kt @@ -7,11 +7,11 @@ import io.tolgee.batch.request.SetKeysNamespaceRequest import io.tolgee.constants.Message import io.tolgee.model.batch.params.SetKeysNamespaceParams import io.tolgee.service.key.KeyService +import jakarta.persistence.EntityManager +import jakarta.persistence.PersistenceException import kotlinx.coroutines.ensureActive import org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage import org.springframework.stereotype.Component -import javax.persistence.EntityManager -import javax.persistence.PersistenceException import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetTranslationsStateChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetTranslationsStateChunkProcessor.kt index ff2bc64899..65ee90bc27 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetTranslationsStateChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/SetTranslationsStateChunkProcessor.kt @@ -5,9 +5,9 @@ import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.SetTranslationsStateStateRequest import io.tolgee.model.batch.params.SetTranslationStateJobParams import io.tolgee.service.translation.TranslationService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/TagKeysChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/TagKeysChunkProcessor.kt index 00f51287f9..70a73cddac 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/TagKeysChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/TagKeysChunkProcessor.kt @@ -5,9 +5,9 @@ import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.TagKeysRequest import io.tolgee.model.batch.params.TagKeysParams import io.tolgee.service.key.TagService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/processors/UntagKeysChunkProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/batch/processors/UntagKeysChunkProcessor.kt index d1102d4ff6..c293717434 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/processors/UntagKeysChunkProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/processors/UntagKeysChunkProcessor.kt @@ -5,9 +5,9 @@ import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.request.UntagKeysRequest import io.tolgee.model.batch.params.UntagKeysParams import io.tolgee.service.key.TagService +import jakarta.persistence.EntityManager import kotlinx.coroutines.ensureActive import org.springframework.stereotype.Component -import javax.persistence.EntityManager import kotlin.coroutines.CoroutineContext @Component diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/ClearTranslationsRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/ClearTranslationsRequest.kt index 863cc5bcd7..0b19836da3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/ClearTranslationsRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/ClearTranslationsRequest.kt @@ -1,7 +1,7 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size class ClearTranslationsRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/CopyTranslationRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/CopyTranslationRequest.kt index 970d3cc0e3..34994203f3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/CopyTranslationRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/CopyTranslationRequest.kt @@ -1,8 +1,8 @@ package io.tolgee.batch.request -import javax.validation.constraints.Min -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size class CopyTranslationRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/DeleteKeysRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/DeleteKeysRequest.kt index 1126079c2e..c58b0d100e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/DeleteKeysRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/DeleteKeysRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty class DeleteKeysRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/MachineTranslationRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/MachineTranslationRequest.kt index a0b47efe68..ee2961a0cc 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/MachineTranslationRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/MachineTranslationRequest.kt @@ -1,7 +1,7 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size class MachineTranslationRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/PreTranslationByTmRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/PreTranslationByTmRequest.kt index dc9c6d7ccf..693e2ff52d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/PreTranslationByTmRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/PreTranslationByTmRequest.kt @@ -1,7 +1,7 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size class PreTranslationByTmRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/SetKeysNamespaceRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/SetKeysNamespaceRequest.kt index 535fef4fa2..9b19de6342 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/SetKeysNamespaceRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/SetKeysNamespaceRequest.kt @@ -1,8 +1,8 @@ package io.tolgee.batch.request import io.tolgee.constants.ValidationConstants -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size class SetKeysNamespaceRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/SetTranslationsStateStateRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/SetTranslationsStateStateRequest.kt index 20fa96c750..694e90844d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/SetTranslationsStateStateRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/SetTranslationsStateStateRequest.kt @@ -1,9 +1,9 @@ package io.tolgee.batch.request import io.tolgee.model.enums.TranslationState -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size class SetTranslationsStateStateRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/TagKeysRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/TagKeysRequest.kt index b3caa01abb..977a375bb7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/TagKeysRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/TagKeysRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty class TagKeysRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/request/UntagKeysRequest.kt b/backend/data/src/main/kotlin/io/tolgee/batch/request/UntagKeysRequest.kt index d21fca93d8..a530137a18 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/request/UntagKeysRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/request/UntagKeysRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.batch.request -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty class UntagKeysRequest { @NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/batch/state/BatchJobStateProvider.kt b/backend/data/src/main/kotlin/io/tolgee/batch/state/BatchJobStateProvider.kt index 47c1c1b0f5..4ce2107f7a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/batch/state/BatchJobStateProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/batch/state/BatchJobStateProvider.kt @@ -6,6 +6,7 @@ import io.tolgee.model.batch.BatchJobChunkExecution import io.tolgee.util.Logging import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.redisson.api.RMap import org.redisson.api.RedissonClient import org.springframework.context.annotation.Lazy @@ -15,7 +16,6 @@ import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.TransactionDefinition import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap -import javax.persistence.EntityManager @Component class BatchJobStateProvider( diff --git a/backend/data/src/main/kotlin/io/tolgee/component/AutoTranslationListener.kt b/backend/data/src/main/kotlin/io/tolgee/component/AutoTranslationListener.kt index a34e727d2d..f4f314721a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/AutoTranslationListener.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/AutoTranslationListener.kt @@ -10,10 +10,10 @@ import io.tolgee.model.key.Key import io.tolgee.service.project.ProjectService import io.tolgee.service.translation.AutoTranslationService import io.tolgee.util.Logging +import jakarta.persistence.EntityManager import org.springframework.context.event.EventListener import org.springframework.core.annotation.Order import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class AutoTranslationListener( diff --git a/backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt b/backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt index 5d9d42202d..4df1fe681c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt @@ -2,7 +2,10 @@ package io.tolgee.component import io.tolgee.development.OnDateForced import io.tolgee.model.ForcedServerDateTime +import io.tolgee.util.Logging import io.tolgee.util.executeInNewTransaction +import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.springframework.beans.factory.config.ConfigurableBeanFactory import org.springframework.context.ApplicationEventPublisher import org.springframework.context.annotation.Lazy @@ -11,33 +14,30 @@ import org.springframework.data.auditing.AuditingHandler import org.springframework.data.auditing.DateTimeProvider import org.springframework.stereotype.Component import org.springframework.transaction.PlatformTransactionManager +import java.sql.Timestamp import java.time.Duration import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.temporal.TemporalAccessor import java.util.* -import javax.persistence.EntityManager @Component @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) class CurrentDateProvider( - @Lazy + @Suppress("SpringJavaInjectionPointsAutowiringInspection") @Lazy auditingHandler: AuditingHandler, private val entityManager: EntityManager, private val applicationEventPublisher: ApplicationEventPublisher, private val transactionManager: PlatformTransactionManager -) : DateTimeProvider { - private fun getServerTimeEntity(): ForcedServerDateTime? = - entityManager.createQuery( - "select st from ForcedServerDateTime st where st.id = 1", - ForcedServerDateTime::class.java - ).resultList.singleOrNull() - +) : Logging, DateTimeProvider { var forcedDate: Date? = null set(value) { - field = value - updateEntity(field) - applicationEventPublisher.publishEvent(OnDateForced(this, value)) + if (field != value) { + logger.debug("Forcing date to: {} (old value: {})", value, field) + field = value + updateEntity(field) + applicationEventPublisher.publishEvent(OnDateForced(this, value)) + } } private fun updateEntity(forcedDate: Date?) { @@ -62,8 +62,7 @@ class CurrentDateProvider( } init { - val forcedServerDateTime: ForcedServerDateTime? = getServerTimeEntity() - forcedDate = forcedServerDateTime?.time + forcedDate = getForcedTime() auditingHandler.setDateTimeProvider(this) } @@ -85,4 +84,16 @@ class CurrentDateProvider( override fun getNow(): Optional { return Optional.of(date.toInstant()) } + + private fun getServerTimeEntity(): ForcedServerDateTime? = + entityManager.createQuery( + "select st from ForcedServerDateTime st where st.id = 1", + ForcedServerDateTime::class.java + ).resultList.singleOrNull() + + private fun getForcedTime(): Timestamp? = + entityManager.createNativeQuery( + "select st.time from public.forced_server_date_time st where st.id = 1", + Timestamp::class.java + ).resultList.singleOrNull() as Timestamp? } diff --git a/backend/data/src/main/kotlin/io/tolgee/component/SavePointManager.kt b/backend/data/src/main/kotlin/io/tolgee/component/SavePointManager.kt index d7299cb668..2711496814 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/SavePointManager.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/SavePointManager.kt @@ -1,12 +1,11 @@ package io.tolgee.component -import org.hibernate.Session +import jakarta.persistence.EntityManager import org.hibernate.internal.SessionImpl import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl import org.springframework.stereotype.Component import java.sql.Savepoint import java.util.* -import javax.persistence.EntityManager @Component class SavePointManager( @@ -23,7 +22,7 @@ class SavePointManager( connection.rollback(savepoint) } - val session = (getSession().session as? SessionImpl) ?: throw IllegalStateException("Session is not SessionImpl") + val session = getSession() val coordinatorGetter = session::class.java.getMethod("getTransactionCoordinator") coordinatorGetter.isAccessible = true val coordinator = coordinatorGetter.invoke(session) as? JdbcResourceLocalTransactionCoordinatorImpl @@ -40,8 +39,9 @@ class SavePointManager( field.isAccessible = false } - fun getSession(): Session { - return entityManager.unwrap(Session::class.java) + fun getSession(): SessionImpl { + return entityManager.unwrap(SessionImpl::class.java) + ?.let { it as? SessionImpl ?: throw IllegalStateException("Session is not SessionImpl") } ?: throw IllegalStateException("Session is null") } } diff --git a/backend/data/src/main/kotlin/io/tolgee/component/automations/AutomationsBatchJobCreator.kt b/backend/data/src/main/kotlin/io/tolgee/component/automations/AutomationsBatchJobCreator.kt index 7376ee3b39..85e3f2190d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/automations/AutomationsBatchJobCreator.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/automations/AutomationsBatchJobCreator.kt @@ -10,9 +10,9 @@ import io.tolgee.dtos.cacheable.automations.AutomationTriggerDto import io.tolgee.model.Project import io.tolgee.model.automations.AutomationTriggerType import io.tolgee.service.automations.AutomationService +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component import java.time.Duration -import javax.persistence.EntityManager @Component class AutomationsBatchJobCreator( diff --git a/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/WebhookProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/WebhookProcessor.kt index e6650cb605..496df046b9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/WebhookProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/WebhookProcessor.kt @@ -9,8 +9,8 @@ import io.tolgee.constants.Message import io.tolgee.model.automations.AutomationAction import io.tolgee.model.webhook.WebhookConfig import io.tolgee.security.ProjectHolder +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class WebhookProcessor( diff --git a/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/webhookExceptions.kt b/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/webhookExceptions.kt index 8585cc30f0..019beb0305 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/webhookExceptions.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/automations/processors/webhookExceptions.kt @@ -1,8 +1,8 @@ package io.tolgee.component.automations.processors -import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode -class WebhookRespondedWithNon200Status(statusCode: HttpStatus, body: Any?) : WebhookException() +class WebhookRespondedWithNon200Status(statusCode: HttpStatusCode, body: Any?) : WebhookException() class WebhookExecutionFailed(e: Throwable) : WebhookException(e) open class WebhookException(cause: Throwable? = null) : RuntimeException("Webhook execution failed", cause) diff --git a/backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt b/backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt index dd84130a1c..ebd8188e57 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt @@ -15,9 +15,13 @@ import org.springframework.transaction.event.TransactionalEventListener class LanguageStatsListener( private var languageStatsService: LanguageStatsService ) { + + var bypass = false + @TransactionalEventListener @Async fun onActivity(event: OnProjectActivityEvent) { + if (bypass) return runSentryCatching { val projectId = event.activityRevision.projectId ?: return diff --git a/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/MtEventListener.kt b/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/MtEventListener.kt index 09bd8c0837..f278857186 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/MtEventListener.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/MtEventListener.kt @@ -6,9 +6,10 @@ import io.tolgee.events.OnAfterMachineTranslationEvent import io.tolgee.events.OnBeforeMachineTranslationEvent import io.tolgee.model.MtCreditBucket import io.tolgee.service.machineTranslation.MtCreditBucketService +import jakarta.persistence.EntityManager import org.springframework.context.event.EventListener import org.springframework.stereotype.Component -import javax.persistence.EntityManager +import kotlin.time.ExperimentalTime @Component class MtEventListener( @@ -18,6 +19,7 @@ class MtEventListener( private val entityManager: EntityManager ) { @EventListener(OnBeforeMachineTranslationEvent::class) + @ExperimentalTime fun onBeforeMtEvent(event: OnBeforeMachineTranslationEvent) { if (shouldConsumeCredits()) { mtCreditBucketService.checkPositiveBalance(event.project) diff --git a/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/providers/AzureCognitiveApiService.kt b/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/providers/AzureCognitiveApiService.kt index 1744639717..4ee73ee762 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/providers/AzureCognitiveApiService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/machineTranslation/providers/AzureCognitiveApiService.kt @@ -12,7 +12,7 @@ import org.springframework.http.ResponseEntity import org.springframework.stereotype.Component import org.springframework.web.client.RestTemplate import org.springframework.web.client.exchange -import java.util.LinkedList +import java.util.* @Component @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) diff --git a/backend/data/src/main/kotlin/io/tolgee/component/reporting/SdkInfoProvider.kt b/backend/data/src/main/kotlin/io/tolgee/component/reporting/SdkInfoProvider.kt index 2ffe281db8..930b50a056 100644 --- a/backend/data/src/main/kotlin/io/tolgee/component/reporting/SdkInfoProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/component/reporting/SdkInfoProvider.kt @@ -1,9 +1,9 @@ package io.tolgee.component.reporting +import jakarta.servlet.http.HttpServletRequest import org.springframework.stereotype.Component import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.context.request.ServletRequestAttributes -import javax.servlet.http.HttpServletRequest @Component class SdkInfoProvider() { diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/HibernateSession.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/HibernateSession.kt new file mode 100644 index 0000000000..16607834e3 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/HibernateSession.kt @@ -0,0 +1,16 @@ +package io.tolgee.configuration + +// +// @Configuration +// class HibernateSessionConfiguration( +// private val dataSource: DataSource +// ) { +// @Bean +// fun sessionFactory(emf: EntityManagerFactory?): LocalSessionFactoryBean { +// val sessionFactory = LocalSessionFactoryBean() +// sessionFactory.setDataSource(dataSource) +// sessionFactory.setPackagesToScan("io.tolgee.model") +// sessionFactory.hibernateProperties = hibernateProperties() +// return sessionFactory +// } +// } diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt index 2bf3bcdf82..afb90ef102 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt @@ -1,5 +1,6 @@ package io.tolgee.configuration +import io.tolgee.PostgresRunner import liquibase.integration.spring.SpringLiquibase import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -10,9 +11,9 @@ import javax.sql.DataSource class LiquibaseConfiguration { @Bean @Primary - fun liquibase(dataSource: DataSource): SpringLiquibase { + fun liquibase(dataSource: DataSource, postgresRunner: PostgresRunner?): SpringLiquibase { val liquibase = SpringLiquibase() - + liquibase.setShouldRun(postgresRunner?.shouldRunMigrations != false) liquibase.dataSource = dataSource liquibase.changeLog = "classpath:db/changelog/schema.xml" liquibase.defaultSchema = "public" diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/AuthenticationProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/AuthenticationProperties.kt index 2a8df29bf3..0cfa212f4d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/AuthenticationProperties.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/AuthenticationProperties.kt @@ -7,11 +7,9 @@ package io.tolgee.configuration.tolgee import io.tolgee.configuration.annotations.AdditionalDocsProperties import io.tolgee.configuration.annotations.DocProperty import io.tolgee.exceptions.BadRequestException +import jakarta.validation.constraints.Size import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.ConstructorBinding -import javax.validation.constraints.Size -@ConstructorBinding @ConfigurationProperties(prefix = "tolgee.authentication") @AdditionalDocsProperties( properties = [ diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/HibernateConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/HibernateConfiguration.kt index e0838debff..78f8c3e708 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/HibernateConfiguration.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/HibernateConfiguration.kt @@ -12,6 +12,6 @@ class HibernateConfiguration : HibernatePropertiesCustomizer { lateinit var activityInterceptor: ActivityDatabaseInterceptor override fun customize(vendorProperties: MutableMap) { - vendorProperties["hibernate.ejb.interceptor"] = activityInterceptor + vendorProperties["hibernate.session_factory.interceptor"] = activityInterceptor } } diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/InternalProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/InternalProperties.kt index d1b14d8da1..5b61ac9e13 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/InternalProperties.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/InternalProperties.kt @@ -26,4 +26,6 @@ class InternalProperties { */ @E2eRuntimeMutable var e3eContentStorageBypassOk: Boolean? = null + + var disableInitialUserCreation: Boolean = false } diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/PostgresAutostartProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/PostgresAutostartProperties.kt index 5f72cbaa4b..832bd499f6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/PostgresAutostartProperties.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/PostgresAutostartProperties.kt @@ -57,4 +57,10 @@ class PostgresAutostartProperties { */ EMBEDDED } + + @DocProperty( + description = "When true, Tolgee will stop the Postgres container on Tolgee shutdown." + + "This setting is applicable only for `DOCKER` mode." + ) + var stop: Boolean = true } diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt index 678a45cd35..44911475f0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt +++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt @@ -8,9 +8,7 @@ import io.tolgee.configuration.annotations.AdditionalDocsProperties import io.tolgee.configuration.annotations.DocProperty import io.tolgee.configuration.tolgee.machineTranslation.MachineTranslationProperties import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.ConstructorBinding -@ConstructorBinding @AdditionalDocsProperties( [ DocProperty( diff --git a/backend/data/src/main/kotlin/io/tolgee/constants/MtServiceType.kt b/backend/data/src/main/kotlin/io/tolgee/constants/MtServiceType.kt index bc39541e6d..061b334af7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/constants/MtServiceType.kt +++ b/backend/data/src/main/kotlin/io/tolgee/constants/MtServiceType.kt @@ -1,8 +1,19 @@ package io.tolgee.constants import io.tolgee.component.machineTranslation.MtValueProvider -import io.tolgee.component.machineTranslation.providers.* -import io.tolgee.configuration.tolgee.machineTranslation.* +import io.tolgee.component.machineTranslation.providers.AwsMtValueProvider +import io.tolgee.component.machineTranslation.providers.AzureCognitiveTranslationProvider +import io.tolgee.component.machineTranslation.providers.BaiduTranslationProvider +import io.tolgee.component.machineTranslation.providers.DeeplTranslationProvider +import io.tolgee.component.machineTranslation.providers.GoogleTranslationProvider +import io.tolgee.component.machineTranslation.providers.TolgeeTranslationProvider +import io.tolgee.configuration.tolgee.machineTranslation.AwsMachineTranslationProperties +import io.tolgee.configuration.tolgee.machineTranslation.AzureCognitiveTranslationProperties +import io.tolgee.configuration.tolgee.machineTranslation.BaiduMachineTranslationProperties +import io.tolgee.configuration.tolgee.machineTranslation.DeeplMachineTranslationProperties +import io.tolgee.configuration.tolgee.machineTranslation.GoogleMachineTranslationProperties +import io.tolgee.configuration.tolgee.machineTranslation.MachineTranslationServiceProperties +import io.tolgee.configuration.tolgee.machineTranslation.TolgeeMachineTranslationProperties enum class MtServiceType( val propertyClass: Class, diff --git a/backend/data/src/main/kotlin/io/tolgee/development/DbPopulatorReal.kt b/backend/data/src/main/kotlin/io/tolgee/development/DbPopulatorReal.kt index efb4499fa1..86ce178bca 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/DbPopulatorReal.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/DbPopulatorReal.kt @@ -24,11 +24,11 @@ import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.UserAccountService import io.tolgee.util.SlugGenerator import io.tolgee.util.executeInNewTransaction +import jakarta.persistence.EntityManager import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional import java.util.* -import javax.persistence.EntityManager @Service class DbPopulatorReal( @@ -103,6 +103,7 @@ class DbPopulatorReal( project.organizationOwner = organization project.slug = slugGenerator.generate(projectName, 3, 60) { true } en = createLanguage("en", project) + project.baseLanguage = en de = createLanguage("de", project) organization.projects.add(project) projectService.save(project) diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt index dfe1952f60..a8bb59f0f1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt @@ -1,6 +1,7 @@ package io.tolgee.development.testDataBuilder import io.tolgee.activity.ActivityHolder +import io.tolgee.component.eventListeners.LanguageStatsListener import io.tolgee.development.testDataBuilder.builders.ImportBuilder import io.tolgee.development.testDataBuilder.builders.KeyBuilder import io.tolgee.development.testDataBuilder.builders.PatBuilder @@ -37,12 +38,12 @@ import io.tolgee.util.Logging import io.tolgee.util.executeInNewTransaction import io.tolgee.util.logger import io.tolgee.util.tryUntilItDoesntBreakConstraint +import jakarta.persistence.EntityManager import org.springframework.dao.DataIntegrityViolationException import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @Service class TestDataService( @@ -74,7 +75,8 @@ class TestDataService( private val bigMetaService: BigMetaService, private val activityHolder: ActivityHolder, private val automationService: AutomationService, - private val contentDeliveryConfigService: ContentDeliveryConfigService + private val contentDeliveryConfigService: ContentDeliveryConfigService, + private val languageStatsListener: LanguageStatsListener ) : Logging { @Transactional @@ -88,6 +90,7 @@ class TestDataService( @Transactional fun saveTestData(builder: TestDataBuilder) { activityHolder.enableAutoCompletion = false + languageStatsListener.bypass = true prepare() // Projects have to be stored in separate transaction since projectHolder's @@ -113,6 +116,7 @@ class TestDataService( updateLanguageStats(builder) activityHolder.enableAutoCompletion = true + languageStatsListener.bypass = false } @Transactional diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ImplicitUserLegacyData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ImplicitUserLegacyData.kt index 15476e0668..826fcc47fc 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ImplicitUserLegacyData.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ImplicitUserLegacyData.kt @@ -1,6 +1,10 @@ package io.tolgee.development.testDataBuilder.data -import io.tolgee.model.* +import io.tolgee.model.ApiKey +import io.tolgee.model.Organization +import io.tolgee.model.Pat +import io.tolgee.model.Project +import io.tolgee.model.UserAccount import io.tolgee.model.enums.OrganizationRoleType import io.tolgee.model.enums.Scope diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeySearchTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeySearchTestData.kt index 6cd5ac49e5..5331bce5e3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeySearchTestData.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeySearchTestData.kt @@ -2,7 +2,7 @@ package io.tolgee.development.testDataBuilder.data import io.tolgee.development.testDataBuilder.builders.ProjectBuilder import net.datafaker.Faker -import java.util.UUID +import java.util.* class KeySearchTestData : BaseTestData() { diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectLeavingTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectLeavingTestData.kt index 3067bc4c3a..bf56a13ae6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectLeavingTestData.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectLeavingTestData.kt @@ -43,13 +43,11 @@ class ProjectLeavingTestData : BaseTestData() { addOrganization { name = "Not owned organization" notOwnedOrganization = this - slug = "not-owned-organization" } addOrganization { name = "Owned organization" organization = this - slug = "owned-organization" }.apply { addRole { type = OrganizationRoleType.OWNER diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectTransferringTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectTransferringTestData.kt index 334268d16c..48e780d296 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectTransferringTestData.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectTransferringTestData.kt @@ -34,12 +34,10 @@ class ProjectTransferringTestData { addOrganization { name = "Not owned organization" notOwnedOrganization = this - slug = "not-owned-organization" } addOrganization { name = "Another organization" - slug = "another-organization" anotherOrganization = this }.build { addRole { @@ -51,7 +49,6 @@ class ProjectTransferringTestData { addOrganization { name = "Owned organization" organization = this - slug = "owned-organization" }.build organizationBuilder@{ addRole { type = OrganizationRoleType.OWNER diff --git a/backend/data/src/main/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialect.kt b/backend/data/src/main/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialect.kt index 9c8e7e18d4..fc8f04b169 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialect.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialect.kt @@ -1,58 +1,19 @@ package io.tolgee.dialects.postgres -import com.vladmihalcea.hibernate.type.array.StringArrayType -import com.vladmihalcea.hibernate.type.json.JsonBinaryType -import org.hibernate.NullPrecedence -import org.hibernate.dialect.PostgreSQL10Dialect -import org.hibernate.dialect.function.SQLFunction -import org.hibernate.engine.spi.Mapping -import org.hibernate.engine.spi.SessionFactoryImplementor -import org.hibernate.type.FloatType -import org.hibernate.type.Type -import java.sql.Types +import org.hibernate.boot.model.FunctionContributions +import org.hibernate.dialect.DatabaseVersion +import org.hibernate.dialect.PostgreSQLDialect +import org.hibernate.type.StandardBasicTypes @Suppress("unused") -class CustomPostgreSQLDialect : PostgreSQL10Dialect() { - init { - registerHibernateType(2003, StringArrayType::class.java.name) - } - - override fun renderOrderByElement( - expression: String?, - collation: String?, - order: String?, - nulls: NullPrecedence? - ): String { - if (nulls == NullPrecedence.NONE) { - if (order == "asc") { - return super.renderOrderByElement(expression, collation, order, NullPrecedence.FIRST) - } - if (order == "desc") { - return super.renderOrderByElement(expression, collation, order, NullPrecedence.LAST) - } - } - return super.renderOrderByElement(expression, collation, order, nulls) - } +class CustomPostgreSQLDialect : PostgreSQLDialect(DatabaseVersion.make(13)) { - init { - registerFunction( + override fun contributeFunctions(functionContributions: FunctionContributions) { + super.contributeFunctions(functionContributions) + functionContributions.functionRegistry.registerPattern( "similarity", - object : SQLFunction { - override fun hasArguments(): Boolean = true - - override fun hasParenthesesIfNoArguments() = false - - override fun getReturnType(firstArgumentType: Type?, mapping: Mapping?) = FloatType() - - override fun render( - firstArgumentType: Type, - arguments: MutableList, - factory: SessionFactoryImplementor - ): String { - return "similarity(${arguments[0]}, ${arguments[1]})" - } - } + "similarity(?1, ?2)", + functionContributions.typeConfiguration.basicTypeRegistry.resolve(StandardBasicTypes.FLOAT) ) - registerHibernateType(Types.OTHER, JsonBinaryType::class.java.name) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/RelatedKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/RelatedKeyDto.kt index 1d86c3ed2d..a4b761e5a4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/RelatedKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/RelatedKeyDto.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty data class RelatedKeyDto( var namespace: String? = null, diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/ApiKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/ApiKeyDto.kt index 2a297e4da8..a47a73a12b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/ApiKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/ApiKeyDto.kt @@ -19,7 +19,7 @@ package io.tolgee.dtos.cacheable import io.tolgee.model.ApiKey import io.tolgee.model.enums.Scope import java.io.Serializable -import java.util.Date +import java.util.* data class ApiKeyDto( val id: Long, diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/UserAccountDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/UserAccountDto.kt index 4c75d58ccb..3b86d3c73e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/UserAccountDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/cacheable/UserAccountDto.kt @@ -2,7 +2,7 @@ package io.tolgee.dtos.cacheable import io.tolgee.model.UserAccount import java.io.Serializable -import java.util.Date +import java.util.* data class UserAccountDto( val name: String, diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/AzureContentStorageConfigDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/AzureContentStorageConfigDto.kt index 7249d65782..95d5fcf269 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/AzureContentStorageConfigDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/AzureContentStorageConfigDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.contentDelivery import io.tolgee.model.contentDelivery.AzureBlobConfig -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size class AzureContentStorageConfigDto : AzureBlobConfig { @field:Size(max = 255) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/ContentStorageRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/ContentStorageRequest.kt index 4b23deb712..86f35e9f59 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/ContentStorageRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/ContentStorageRequest.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.contentDelivery -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size data class ContentStorageRequest( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/S3ContentStorageConfigDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/S3ContentStorageConfigDto.kt index ed8152565b..e85f461003 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/S3ContentStorageConfigDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/contentDelivery/S3ContentStorageConfigDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.contentDelivery import io.tolgee.model.contentDelivery.S3Config -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size class S3ContentStorageConfigDto : S3Config { @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/BusinessEventReportRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/BusinessEventReportRequest.kt index 066b14bb9c..dc644039bb 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/BusinessEventReportRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/BusinessEventReportRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank data class BusinessEventReportRequest( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/ContentDeliveryConfigRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/ContentDeliveryConfigRequest.kt index 6d03ee334a..15753d972f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/ContentDeliveryConfigRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/ContentDeliveryConfigRequest.kt @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.dtos.IExportParams import io.tolgee.dtos.request.export.ExportFormat import io.tolgee.model.enums.TranslationState -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class ContentDeliveryConfigRequest : IExportParams { @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/GenerateSlugDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/GenerateSlugDto.kt index 57625fc259..56e12aeb74 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/GenerateSlugDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/GenerateSlugDto.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank data class GenerateSlugDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/GetKeysRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/GetKeysRequestDto.kt index 78201ace4b..cb30e8596f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/GetKeysRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/GetKeysRequestDto.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request import io.swagger.v3.oas.annotations.media.Schema -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty class GetKeysRequestDto { var keys: List = listOf() diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/IdentifyRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/IdentifyRequest.kt index 0a02b1644f..9977f6c7dd 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/IdentifyRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/IdentifyRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank data class IdentifyRequest( @NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/KeyDefinitionDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/KeyDefinitionDto.kt index f8777c95d0..5ea331b9b5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/KeyDefinitionDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/KeyDefinitionDto.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty class KeyDefinitionDto { @get:NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/LanguageDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/LanguageDto.kt index 0184b4debc..8cdf4110e6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/LanguageDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/LanguageDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request import io.swagger.v3.oas.annotations.media.Schema -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size data class LanguageDto( @Schema(example = "Czech", description = "Language name in english") diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserMfaRecoveryRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserMfaRecoveryRequestDto.kt index 68f138e7cd..6d68227100 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserMfaRecoveryRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserMfaRecoveryRequestDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @JsonIgnoreProperties(ignoreUnknown = true) data class UserMfaRecoveryRequestDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpDisableRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpDisableRequestDto.kt index bac170074a..fa6c0945e7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpDisableRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpDisableRequestDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @JsonIgnoreProperties(ignoreUnknown = true) data class UserTotpDisableRequestDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpEnableRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpEnableRequestDto.kt index b417480b31..1488b308d6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpEnableRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserTotpEnableRequestDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size @JsonIgnoreProperties(ignoreUnknown = true) data class UserTotpEnableRequestDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdatePasswordRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdatePasswordRequestDto.kt index ef66b3112b..230a3b2557 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdatePasswordRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdatePasswordRequestDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @JsonIgnoreProperties(ignoreUnknown = true) data class UserUpdatePasswordRequestDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdateRequestDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdateRequestDto.kt index f270023162..5acfe02465 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdateRequestDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/UserUpdateRequestDto.kt @@ -2,8 +2,8 @@ package io.tolgee.dtos.request import com.fasterxml.jackson.annotation.JsonIgnoreProperties import io.swagger.v3.oas.annotations.media.Schema -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @JsonIgnoreProperties(ignoreUnknown = true) data class UserUpdateRequestDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/WebhookConfigRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/WebhookConfigRequest.kt index e2a60d1166..b619f7be15 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/WebhookConfigRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/WebhookConfigRequest.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request -import javax.validation.constraints.Size +import jakarta.validation.constraints.Size data class WebhookConfigRequest( @field:Size(max = 255) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/CreateApiKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/CreateApiKeyDto.kt index b216fcf6b8..fdfe360c48 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/CreateApiKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/CreateApiKeyDto.kt @@ -5,11 +5,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonSetter import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.Scope +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull import org.hibernate.validator.constraints.Length import java.util.stream.Collectors -import javax.validation.constraints.Min -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull data class CreateApiKeyDto( @field:NotNull diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/V2EditApiKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/V2EditApiKeyDto.kt index b25cad7490..be9805c7bb 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/V2EditApiKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/apiKey/V2EditApiKeyDto.kt @@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonSetter import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.Scope -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotEmpty data class V2EditApiKeyDto( @field:NotEmpty diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPassword.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPassword.kt index 047f008f89..4fc877906a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPassword.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPassword.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.auth -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size data class ResetPassword( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPasswordRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPasswordRequest.kt index 27db24a913..05f0d24963 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPasswordRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/ResetPasswordRequest.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.auth -import javax.validation.constraints.Email -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotBlank data class ResetPasswordRequest( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/SignUpDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/SignUpDto.kt index 4a8549cf32..68d9a14222 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/SignUpDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/auth/SignUpDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request.auth -import javax.validation.constraints.Email -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size data class SignUpDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/ComplexEditKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/ComplexEditKeyDto.kt index f0fb9245fb..fed7a2326f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/ComplexEditKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/ComplexEditKeyDto.kt @@ -5,9 +5,9 @@ import com.fasterxml.jackson.annotation.JsonSetter import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.AssignableTranslationState import io.tolgee.util.getSafeNamespace +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank @Validated data class ComplexEditKeyDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/CreateKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/CreateKeyDto.kt index 8a89db557c..4ffd8785a4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/CreateKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/CreateKeyDto.kt @@ -5,9 +5,9 @@ import com.fasterxml.jackson.annotation.JsonSetter import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.AssignableTranslationState import io.tolgee.util.getSafeNamespace +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank @Validated class CreateKeyDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/DeprecatedEditKeyDTO.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/DeprecatedEditKeyDTO.kt index 7df38fb43f..66c2d4906e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/DeprecatedEditKeyDTO.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/DeprecatedEditKeyDTO.kt @@ -3,7 +3,7 @@ package io.tolgee.dtos.request.key import com.fasterxml.jackson.annotation.JsonIgnore import io.swagger.v3.oas.annotations.Hidden import io.tolgee.dtos.PathDTO -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank @Deprecated(message = "Ugly naming", ReplaceWith("io/tolgee/dtos/request/EditKeyDTO.kt")) data class DeprecatedEditKeyDTO( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/EditKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/EditKeyDto.kt index 290c6be4c1..41141a82d5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/EditKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/EditKeyDto.kt @@ -5,8 +5,8 @@ import com.fasterxml.jackson.annotation.JsonSetter import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.constants.ValidationConstants import io.tolgee.util.getSafeNamespace +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank data class EditKeyDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/OldEditKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/OldEditKeyDto.kt index 959c01c61f..1a1ad45fda 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/OldEditKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/OldEditKeyDto.kt @@ -3,8 +3,8 @@ package io.tolgee.dtos.request.key import com.fasterxml.jackson.annotation.JsonIgnore import io.swagger.v3.oas.annotations.Hidden import io.tolgee.dtos.PathDTO +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank data class OldEditKeyDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/TagKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/TagKeyDto.kt index 18d9d2b6bd..71844bf156 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/TagKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/TagKeyDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request.key import io.tolgee.constants.ValidationConstants -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size data class TagKeyDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/UpdateNamespaceDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/UpdateNamespaceDto.kt index 3bf4352f93..da51f668e7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/UpdateNamespaceDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/key/UpdateNamespaceDto.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.key -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull data class UpdateNamespaceDto( @field:NotNull diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationDto.kt index 0468353dbe..e72bfca793 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request.organization import io.swagger.v3.oas.annotations.media.Schema -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size data class OrganizationDto( @field:NotBlank @field:Size(min = 3, max = 50) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationInviteUserDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationInviteUserDto.kt index 67052a6cd4..872337be10 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationInviteUserDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/OrganizationInviteUserDto.kt @@ -2,9 +2,9 @@ package io.tolgee.dtos.request.organization import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.OrganizationRoleType -import javax.validation.constraints.Email -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size data class OrganizationInviteUserDto( @field:NotNull diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/SetOrganizationRoleDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/SetOrganizationRoleDto.kt index 7346175ecc..85f65f21f8 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/SetOrganizationRoleDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/organization/SetOrganizationRoleDto.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.organization import io.tolgee.model.enums.OrganizationRoleType -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank data class SetOrganizationRoleDto( @NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/CreatePatDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/CreatePatDto.kt index 2e648276e2..89aa700c11 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/CreatePatDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/CreatePatDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request.pat import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank @Validated data class CreatePatDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/UpdatePatDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/UpdatePatDto.kt index 9a64de0097..d104d22b09 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/UpdatePatDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/pat/UpdatePatDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request.pat import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank @Validated data class UpdatePatDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/CreateProjectDTO.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/CreateProjectDTO.kt index 3d86fbfa77..bb819a2be6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/CreateProjectDTO.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/CreateProjectDTO.kt @@ -2,12 +2,12 @@ package io.tolgee.dtos.request.project import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.dtos.request.LanguageDto -import javax.validation.Valid -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.validation.Valid +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size data class CreateProjectDTO( @field:NotBlank @field:Size(min = 3, max = 50) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/EditProjectDTO.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/EditProjectDTO.kt index 62d3fdc18a..8db7253eb8 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/EditProjectDTO.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/EditProjectDTO.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request.project -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size data class EditProjectDTO( @field:NotBlank @field:Size(min = 3, max = 50) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/ProjectInviteUserDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/ProjectInviteUserDto.kt index c80ec47f32..63a9de2545 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/ProjectInviteUserDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/project/ProjectInviteUserDto.kt @@ -2,8 +2,8 @@ package io.tolgee.dtos.request.project import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.model.enums.ProjectPermissionType -import javax.validation.constraints.Email -import javax.validation.constraints.Size +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Size data class ProjectInviteUserDto( var type: ProjectPermissionType? = null, diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/GetScreenshotsByKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/GetScreenshotsByKeyDto.kt index 5435290747..3cd5240193 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/GetScreenshotsByKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/GetScreenshotsByKeyDto.kt @@ -1,6 +1,6 @@ package io.tolgee.dtos.request.screenshot -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank data class GetScreenshotsByKeyDto( @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/UploadScreenshotDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/UploadScreenshotDto.kt index cb0322d108..f64425c9a7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/UploadScreenshotDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/screenshot/UploadScreenshotDto.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.screenshot +import jakarta.validation.constraints.NotBlank import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank @Validated data class UploadScreenshotDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysDto.kt index 1a92d9a688..7da91bfe2a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysDto.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.request.translation +import jakarta.validation.Valid import org.springframework.validation.annotation.Validated -import javax.validation.Valid @Validated data class ImportKeysDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysItemDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysItemDto.kt index 3e52cd9204..72eb8e2ed2 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysItemDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/ImportKeysItemDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request.translation import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull class ImportKeysItemDto( /** diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/SetTranslationsWithKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/SetTranslationsWithKeyDto.kt index 4e6b761099..1b136d0ae1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/SetTranslationsWithKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/SetTranslationsWithKeyDto.kt @@ -1,10 +1,10 @@ package io.tolgee.dtos.request.translation import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.hibernate.validator.constraints.Length import org.springframework.validation.annotation.Validated -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull @Validated data class SetTranslationsWithKeyDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentDto.kt index 52095319f2..7817dee9b0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentDto.kt @@ -1,8 +1,8 @@ package io.tolgee.dtos.request.translation.comment import io.tolgee.model.enums.TranslationCommentState +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank data class TranslationCommentDto( @field:Length(max = 10000) diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentWithLangKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentWithLangKeyDto.kt index d1eddaaf19..cece2b3982 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentWithLangKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/comment/TranslationCommentWithLangKeyDto.kt @@ -1,9 +1,9 @@ package io.tolgee.dtos.request.translation.comment import io.tolgee.model.enums.TranslationCommentState +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull data class TranslationCommentWithLangKeyDto( @field:NotNull diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/importKeysResolvable/ImportKeysResolvableItemDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/importKeysResolvable/ImportKeysResolvableItemDto.kt index 1a58e0097c..89ae3fd735 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/importKeysResolvable/ImportKeysResolvableItemDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/request/translation/importKeysResolvable/ImportKeysResolvableItemDto.kt @@ -2,9 +2,9 @@ package io.tolgee.dtos.request.translation.importKeysResolvable import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.dtos.request.key.KeyScreenshotDto +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.hibernate.validator.constraints.Length -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull class ImportKeysResolvableItemDto( /** diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/response/DeprecatedKeyDto.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/response/DeprecatedKeyDto.kt index b0241a7e31..db8de9d6cd 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/response/DeprecatedKeyDto.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/response/DeprecatedKeyDto.kt @@ -3,8 +3,8 @@ package io.tolgee.dtos.response import com.fasterxml.jackson.annotation.JsonIgnore import io.swagger.v3.oas.annotations.media.Schema import io.tolgee.dtos.PathDTO -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @Deprecated(message = "Ugly parameter name") data class DeprecatedKeyDto( diff --git a/backend/data/src/main/kotlin/io/tolgee/dtos/security/LoginRequest.kt b/backend/data/src/main/kotlin/io/tolgee/dtos/security/LoginRequest.kt index 2adfb843b7..ec0a391213 100644 --- a/backend/data/src/main/kotlin/io/tolgee/dtos/security/LoginRequest.kt +++ b/backend/data/src/main/kotlin/io/tolgee/dtos/security/LoginRequest.kt @@ -1,7 +1,7 @@ package io.tolgee.dtos.security -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty class LoginRequest { @field:NotBlank diff --git a/backend/data/src/main/kotlin/io/tolgee/events/BeforeOrganizationDeleteEvent.kt b/backend/data/src/main/kotlin/io/tolgee/events/BeforeOrganizationDeleteEvent.kt new file mode 100644 index 0000000000..d4b7356dfb --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/events/BeforeOrganizationDeleteEvent.kt @@ -0,0 +1,7 @@ +package io.tolgee.events + +import io.tolgee.model.Organization + +open class BeforeOrganizationDeleteEvent( + val organization: Organization +) diff --git a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobConfiguration.kt index d6299eeb63..4c16ec4298 100644 --- a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobConfiguration.kt +++ b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobConfiguration.kt @@ -9,10 +9,12 @@ import io.tolgee.repository.UserAccountRepository import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.PermissionService +import jakarta.persistence.EntityManager import org.springframework.batch.core.Job import org.springframework.batch.core.Step -import org.springframework.batch.core.configuration.annotation.JobBuilderFactory -import org.springframework.batch.core.configuration.annotation.StepBuilderFactory +import org.springframework.batch.core.job.builder.JobBuilder +import org.springframework.batch.core.repository.JobRepository +import org.springframework.batch.core.step.builder.StepBuilder import org.springframework.batch.item.ItemReader import org.springframework.batch.item.ItemWriter import org.springframework.batch.item.data.RepositoryItemReader @@ -20,8 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.domain.Sort.Direction -import javax.persistence.EntityManager -import javax.sql.DataSource +import org.springframework.transaction.PlatformTransactionManager @Suppress("SpringJavaInjectionPointsAutowiringInspection") @Configuration @@ -32,12 +33,6 @@ class AllOrganizationOwnerJobConfiguration { const val STEP_SIZE = 100 } - @Autowired - lateinit var jobBuilderFactory: JobBuilderFactory - - @Autowired - lateinit var stepBuilderFactory: StepBuilderFactory - @Autowired lateinit var entityManager: EntityManager @@ -60,13 +55,13 @@ class AllOrganizationOwnerJobConfiguration { lateinit var userAccountRepository: UserAccountRepository @Autowired - lateinit var dataSource: DataSource + lateinit var platformTransactionManager: PlatformTransactionManager @Bean(JOB_NAME) - fun job(): Job { - return jobBuilderFactory[JOB_NAME] - .flow(noOrgProjectsStep) - .next(noRoleUserStep) + fun job(jobRepository: JobRepository): Job { + return JobBuilder(JOB_NAME, jobRepository) + .flow(getNoOrgProjectsStep(jobRepository)) + .next(getNoRoleUserStep(jobRepository)) .end() .build() } @@ -102,12 +97,13 @@ class AllOrganizationOwnerJobConfiguration { } } - val noOrgProjectsStep: Step - get() = stepBuilderFactory["noOrProjectStep"] - .chunk(STEP_SIZE) + fun getNoOrgProjectsStep(jobRepository: JobRepository): Step { + return StepBuilder("noOrProjectStep", jobRepository) + .chunk(STEP_SIZE, platformTransactionManager) .reader(noOrgProjectReader) .writer(noOrgProjectWriter) .build() + } val noRoleUserReader: ItemReader get() = RepositoryItemReader().apply { @@ -123,10 +119,9 @@ class AllOrganizationOwnerJobConfiguration { } } - val noRoleUserStep: Step - get() = stepBuilderFactory["noRoleUserStep"] - .chunk(STEP_SIZE) - .reader(noRoleUserReader) - .writer(noRoleUserWriter) - .build() + fun getNoRoleUserStep(jobRepository: JobRepository): Step = StepBuilder("noRoleUserStep", jobRepository) + .chunk(STEP_SIZE, platformTransactionManager) + .reader(noRoleUserReader) + .writer(noRoleUserWriter) + .build() } diff --git a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobRunner.kt b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobRunner.kt index d43ce10c23..c2834fb086 100644 --- a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobRunner.kt +++ b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobRunner.kt @@ -48,6 +48,6 @@ class AllOrganizationOwnerJobRunner( } val json = jacksonObjectMapper().writeValueAsBytes(mapOf("users" to userIds, "projects" to projectIds)) val hash = DigestUtils.sha256Hex(json) - return JobParameters(mapOf("idsHash" to JobParameter(hash))) + return JobParameters(mapOf("idsHash" to JobParameter(hash, String::class.java))) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobConfiguration.kt index 5f4801c3c8..27a4fa5759 100644 --- a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobConfiguration.kt +++ b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobConfiguration.kt @@ -1,10 +1,12 @@ package io.tolgee.jobs.migration.translationStats import io.tolgee.repository.TranslationRepository +import jakarta.persistence.EntityManager import org.springframework.batch.core.Job import org.springframework.batch.core.Step -import org.springframework.batch.core.configuration.annotation.JobBuilderFactory -import org.springframework.batch.core.configuration.annotation.StepBuilderFactory +import org.springframework.batch.core.job.builder.JobBuilder +import org.springframework.batch.core.repository.JobRepository +import org.springframework.batch.core.step.builder.StepBuilder import org.springframework.batch.item.ItemReader import org.springframework.batch.item.ItemWriter import org.springframework.batch.item.data.RepositoryItemReader @@ -12,7 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.domain.Sort -import javax.persistence.EntityManager +import org.springframework.transaction.PlatformTransactionManager import javax.sql.DataSource @Suppress("SpringJavaInjectionPointsAutowiringInspection") @@ -24,23 +26,23 @@ class TranslationStatsJobConfiguration { } @Autowired - lateinit var jobBuilderFactory: JobBuilderFactory + lateinit var entityManager: EntityManager @Autowired - lateinit var stepBuilderFactory: StepBuilderFactory + lateinit var translationRepository: TranslationRepository @Autowired - lateinit var entityManager: EntityManager + lateinit var dataSource: DataSource @Autowired - lateinit var translationRepository: TranslationRepository + lateinit var jobRepository: JobRepository @Autowired - lateinit var dataSource: DataSource + lateinit var platformTransactionManager: PlatformTransactionManager @Bean(JOB_NAME) fun translationStatsJob(): Job { - return jobBuilderFactory[JOB_NAME] + return JobBuilder(JOB_NAME, jobRepository) .flow(step) .end() .build() @@ -67,8 +69,8 @@ class TranslationStatsJobConfiguration { } val step: Step - get() = stepBuilderFactory["step"] - .chunk(100) + get() = StepBuilder("step", jobRepository) + .chunk(100, platformTransactionManager) .reader(reader) .processor(TranslationProcessor()) .writer(writer) diff --git a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationsStatsUpdateJobRunner.kt b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationsStatsUpdateJobRunner.kt index 87b3f7d812..2c03d47d93 100644 --- a/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationsStatsUpdateJobRunner.kt +++ b/backend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationsStatsUpdateJobRunner.kt @@ -43,6 +43,6 @@ class TranslationsStatsUpdateJobRunner( return null } val hash = DigestUtils.sha256Hex(ids.flatMap { it.toBigInteger().toByteArray().toList() }.toByteArray()) - return JobParameters(mapOf("idsHash" to JobParameter(hash))) + return JobParameters(mapOf("idsHash" to JobParameter(hash, String::class.java))) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/ApiKey.kt b/backend/data/src/main/kotlin/io/tolgee/model/ApiKey.kt index bbe4172c89..8260006836 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/ApiKey.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/ApiKey.kt @@ -1,21 +1,21 @@ package io.tolgee.model import io.tolgee.model.enums.Scope +import jakarta.persistence.Column +import jakarta.persistence.ElementCollection +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.persistence.Temporal +import jakarta.persistence.TemporalType +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull import java.util.* -import javax.persistence.Column -import javax.persistence.ElementCollection -import javax.persistence.Entity -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.Table -import javax.persistence.Temporal -import javax.persistence.TemporalType -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull @Entity @Table( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/AuditModel.kt b/backend/data/src/main/kotlin/io/tolgee/model/AuditModel.kt index e800eb959e..9f6318f864 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/AuditModel.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/AuditModel.kt @@ -1,16 +1,16 @@ package io.tolgee.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import jakarta.persistence.Column +import jakarta.persistence.EntityListeners +import jakarta.persistence.MappedSuperclass +import jakarta.persistence.Temporal +import jakarta.persistence.TemporalType import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.io.Serializable import java.util.* -import javax.persistence.Column -import javax.persistence.EntityListeners -import javax.persistence.MappedSuperclass -import javax.persistence.Temporal -import javax.persistence.TemporalType @MappedSuperclass @EntityListeners(AuditingEntityListener::class) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/AutoTranslationConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/AutoTranslationConfig.kt index ae3a7cc265..1ea73f0ef4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/AutoTranslationConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/AutoTranslationConfig.kt @@ -1,9 +1,9 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne import org.hibernate.annotations.ColumnDefault -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToOne @Entity class AutoTranslationConfig : StandardAuditModel() { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncement.kt b/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncement.kt index 482bc360dc..8a7dda5179 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncement.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncement.kt @@ -1,8 +1,8 @@ package io.tolgee.model import io.tolgee.model.enums.Announcement +import jakarta.persistence.* import java.io.Serializable -import javax.persistence.* @Entity @IdClass(DismissedAnnouncementId::class) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncementId.kt b/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncementId.kt index 9eda05cdac..40177d3a1d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncementId.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncementId.kt @@ -1,8 +1,9 @@ package io.tolgee.model + import io.tolgee.model.enums.Announcement import java.io.Serializable -class DismissedAnnouncementId : Serializable { - val user: Long? = null +data class DismissedAnnouncementId( + val user: Long? = null, val announcement: Announcement? = null -} +) : Serializable diff --git a/backend/data/src/main/kotlin/io/tolgee/model/EmailVerification.kt b/backend/data/src/main/kotlin/io/tolgee/model/EmailVerification.kt index 8a1ae27a1a..f5aaeb7513 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/EmailVerification.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/EmailVerification.kt @@ -1,13 +1,13 @@ package io.tolgee.model -import javax.persistence.Entity -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.validation.constraints.Email -import javax.validation.constraints.NotBlank +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotBlank @Entity @Table(uniqueConstraints = []) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.kt b/backend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.kt index 7d16b44001..61a1b7bdc4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.kt @@ -1,15 +1,15 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Temporal import java.util.* -import javax.persistence.Entity -import javax.persistence.Id -import javax.persistence.Temporal @Entity class ForcedServerDateTime { @Id val id = 1 - @Temporal(javax.persistence.TemporalType.TIMESTAMP) + @Temporal(jakarta.persistence.TemporalType.TIMESTAMP) var time: Date = Date() } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/InstanceId.kt b/backend/data/src/main/kotlin/io/tolgee/model/InstanceId.kt index 6529d1426a..b07f68f4c0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/InstanceId.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/InstanceId.kt @@ -1,8 +1,8 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.Id import java.util.* -import javax.persistence.Entity -import javax.persistence.Id @Entity class InstanceId : AuditModel() { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Invitation.kt b/backend/data/src/main/kotlin/io/tolgee/model/Invitation.kt index d6cdfa59e0..f40d51b8bf 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Invitation.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Invitation.kt @@ -1,14 +1,14 @@ package io.tolgee.model -import javax.persistence.CascadeType -import javax.persistence.Entity -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotBlank +import jakarta.persistence.CascadeType +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotBlank @Entity @Table( @@ -36,4 +36,24 @@ class Invitation( var name: String? = null var email: String? = null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Invitation + + if (id != other.id) return false + if (code != other.code) return false + if (name != other.name) return false + return email == other.email + } + + override fun hashCode(): Int { + var result = id?.hashCode() ?: 0 + result = 31 * result + code.hashCode() + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (email?.hashCode() ?: 0) + return result + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Language.kt b/backend/data/src/main/kotlin/io/tolgee/model/Language.kt index 34e8bee3c3..b5274f9cd2 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Language.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Language.kt @@ -10,14 +10,14 @@ import io.tolgee.events.OnLanguagePreRemove import io.tolgee.model.mtServiceConfig.MtServiceConfig import io.tolgee.model.translation.Translation import io.tolgee.service.dataImport.ImportService +import jakarta.persistence.* +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size import org.springframework.beans.factory.ObjectFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Configurable import org.springframework.context.ApplicationEventPublisher import org.springframework.transaction.annotation.Transactional -import javax.persistence.* -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size @Entity @EntityListeners(Language.Companion.LanguageListeners::class) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/LanguageStats.kt b/backend/data/src/main/kotlin/io/tolgee/model/LanguageStats.kt index 38652aa408..681ee2a215 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/LanguageStats.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/LanguageStats.kt @@ -4,10 +4,10 @@ package io.tolgee.model -import javax.persistence.Entity -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint +import jakarta.persistence.Entity +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint @Entity @Table(uniqueConstraints = [UniqueConstraint(columnNames = ["language_id"], name = "language_stats_language_id_key")]) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/MtCreditBucket.kt b/backend/data/src/main/kotlin/io/tolgee/model/MtCreditBucket.kt index b5174cdd35..6e6080f0f6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/MtCreditBucket.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/MtCreditBucket.kt @@ -1,11 +1,11 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint import org.hibernate.annotations.ColumnDefault import java.util.* -import javax.persistence.Entity -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint @Entity @Table( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Organization.kt b/backend/data/src/main/kotlin/io/tolgee/model/Organization.kt index b427f0197c..d4b1707a70 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Organization.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Organization.kt @@ -1,20 +1,22 @@ package io.tolgee.model import com.fasterxml.jackson.annotation.JsonIgnore -import javax.persistence.CascadeType -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import org.hibernate.annotations.Filter +import java.util.* @Entity @Table( @@ -38,7 +40,7 @@ class Organization( @OneToOne(mappedBy = "organization", cascade = [CascadeType.REMOVE], fetch = FetchType.LAZY) var mtCreditBucket: MtCreditBucket? = null -) : ModelWithAvatar, AuditModel() { +) : ModelWithAvatar, AuditModel(), SoftDeletable { @OneToOne(mappedBy = "organization", optional = false, orphanRemoval = true) lateinit var basePermission: Permission @@ -48,10 +50,16 @@ class Organization( var memberRoles: MutableList = mutableListOf() @OneToMany(mappedBy = "organizationOwner") + @field:Filter( + name = "deletedFilter", + condition = "(deleted_at IS NULL)" + ) var projects: MutableList = mutableListOf() @OneToMany(mappedBy = "preferredOrganization") var preferredBy: MutableList = mutableListOf() override var avatarHash: String? = null + + override var deletedAt: Date? = null } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt b/backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt index 228afeab03..7334de0940 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt @@ -1,14 +1,14 @@ package io.tolgee.model import io.tolgee.model.enums.OrganizationRoleType -import javax.persistence.Entity -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.ManyToOne -import javax.persistence.OneToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotNull +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotNull @Entity @Table( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Pat.kt b/backend/data/src/main/kotlin/io/tolgee/model/Pat.kt index dce1272df3..a925afe43d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Pat.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Pat.kt @@ -1,15 +1,14 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.persistence.Temporal +import jakarta.persistence.TemporalType +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull import java.util.* -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.Table -import javax.persistence.Temporal -import javax.persistence.TemporalType -import javax.persistence.Transient -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull @Entity @Table(uniqueConstraints = [UniqueConstraint(columnNames = ["tokenHash"], name = "pat_token_hash_unique")]) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Permission.kt b/backend/data/src/main/kotlin/io/tolgee/model/Permission.kt index 7e0aa3cacd..1de59a9e96 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Permission.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Permission.kt @@ -1,42 +1,31 @@ package io.tolgee.model -import com.vladmihalcea.hibernate.type.array.EnumArrayType +import io.hypersistence.utils.hibernate.type.array.EnumArrayType import io.tolgee.dtos.cacheable.IPermission import io.tolgee.dtos.request.project.LanguagePermissions import io.tolgee.model.enums.ProjectPermissionType import io.tolgee.model.enums.Scope +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.JoinTable +import jakarta.persistence.ManyToMany +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import org.hibernate.annotations.Parameter import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.JoinColumn -import javax.persistence.JoinTable -import javax.persistence.ManyToMany -import javax.persistence.ManyToOne -import javax.persistence.OneToOne -import javax.persistence.PrePersist -import javax.persistence.PreUpdate @Suppress("LeakingThis") @Entity -@TypeDef( - name = "enum-array", - typeClass = EnumArrayType::class, - parameters = [ - Parameter( - name = EnumArrayType.SQL_ARRAY_TYPE, - value = "varchar" - ) - ] -) @EntityListeners(Permission.Companion.PermissionListeners::class) class Permission( @Id @@ -55,7 +44,15 @@ class Permission( @OneToOne(fetch = FetchType.LAZY) var invitation: Invitation? = null, ) : AuditModel(), IPermission { - @Type(type = "enum-array") + @Type( + EnumArrayType::class, + parameters = [ + Parameter( + name = EnumArrayType.SQL_ARRAY_TYPE, + value = "varchar" + ) + ] + ) @Column(name = "scopes", columnDefinition = "varchar[]") private var _scopes: Array? = null set(value) { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Project.kt b/backend/data/src/main/kotlin/io/tolgee/model/Project.kt index 5933b8a50a..01a1fa9d6d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Project.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Project.kt @@ -9,26 +9,26 @@ import io.tolgee.model.key.Key import io.tolgee.model.key.Namespace import io.tolgee.model.mtServiceConfig.MtServiceConfig import io.tolgee.model.webhook.WebhookConfig +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.OrderBy +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size import java.util.* -import javax.persistence.CascadeType -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.FetchType -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.persistence.OrderBy -import javax.persistence.PrePersist -import javax.persistence.PreUpdate -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Pattern -import javax.validation.constraints.Size @Entity @Table(uniqueConstraints = [UniqueConstraint(columnNames = ["address_part"], name = "project_address_part_unique")]) @@ -52,8 +52,7 @@ class Project( @field:Size(min = 3, max = 60) @field:Pattern(regexp = "^[a-z0-9-]*[a-z]+[a-z0-9-]*$", message = "invalid_pattern") var slug: String? = null, -) : AuditModel(), ModelWithAvatar, EntityWithId { - +) : AuditModel(), ModelWithAvatar, EntityWithId, SoftDeletable { @OrderBy("id") @OneToMany(fetch = FetchType.LAZY, mappedBy = "project") var languages: MutableSet = LinkedHashSet() @@ -106,6 +105,8 @@ class Project( @OneToMany(orphanRemoval = true, mappedBy = "project") var webhookConfigs: MutableList = mutableListOf() + override var deletedAt: Date? = null + constructor(name: String, description: String? = null, slug: String?, organizationOwner: Organization) : this(id = 0L, name, description, slug) { this.organizationOwner = organizationOwner diff --git a/backend/data/src/main/kotlin/io/tolgee/model/QuickStart.kt b/backend/data/src/main/kotlin/io/tolgee/model/QuickStart.kt index 0792009bca..3d39f31f36 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/QuickStart.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/QuickStart.kt @@ -1,18 +1,16 @@ package io.tolgee.model -import com.vladmihalcea.hibernate.type.array.ListArrayType +import io.hypersistence.utils.hibernate.type.array.StringArrayType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.MapsId +import jakarta.persistence.OneToOne import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.Id -import javax.persistence.JoinColumn -import javax.persistence.ManyToOne -import javax.persistence.MapsId -import javax.persistence.OneToOne @Entity -@TypeDef(name = "string-array", typeClass = ListArrayType::class) data class QuickStart( @OneToOne @MapsId @@ -31,7 +29,7 @@ data class QuickStart( var open: Boolean = true - @Type(type = "string-array") + @Type(StringArrayType::class) @Column(columnDefinition = "text[]") - var completedSteps: MutableList = mutableListOf() + var completedSteps: Array = arrayOf() } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Screenshot.kt b/backend/data/src/main/kotlin/io/tolgee/model/Screenshot.kt index a554d46a60..6ccf21e209 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Screenshot.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Screenshot.kt @@ -7,12 +7,12 @@ package io.tolgee.model import io.tolgee.activity.annotation.ActivityEntityDescribingPaths import io.tolgee.activity.annotation.ActivityLoggedEntity import io.tolgee.model.key.screenshotReference.KeyScreenshotReference +import jakarta.persistence.Entity +import jakarta.persistence.Index +import jakarta.persistence.OneToMany +import jakarta.persistence.Table import org.apache.commons.codec.digest.DigestUtils import org.hibernate.annotations.ColumnDefault -import javax.persistence.Entity -import javax.persistence.Index -import javax.persistence.OneToMany -import javax.persistence.Table @Entity @ActivityLoggedEntity @@ -70,9 +70,7 @@ class Screenshot : StandardAuditModel() { other as Screenshot - if (id != other.id) return false - - return true + return id == other.id } override fun hashCode(): Int { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/SoftDeletable.kt b/backend/data/src/main/kotlin/io/tolgee/model/SoftDeletable.kt new file mode 100644 index 0000000000..831198c72e --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/model/SoftDeletable.kt @@ -0,0 +1,7 @@ +package io.tolgee.model + +import java.util.* + +interface SoftDeletable { + var deletedAt: Date? +} diff --git a/backend/data/src/main/kotlin/io/tolgee/model/StandardAuditModel.kt b/backend/data/src/main/kotlin/io/tolgee/model/StandardAuditModel.kt index f77c497ae8..a82db38c84 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/StandardAuditModel.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/StandardAuditModel.kt @@ -1,11 +1,11 @@ package io.tolgee.model +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.MappedSuperclass +import jakarta.persistence.SequenceGenerator import org.springframework.data.util.ProxyUtils -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.MappedSuperclass -import javax.persistence.SequenceGenerator @MappedSuperclass abstract class StandardAuditModel : AuditModel(), EntityWithId { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/UploadedImage.kt b/backend/data/src/main/kotlin/io/tolgee/model/UploadedImage.kt index facd46b7da..6338e78999 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/UploadedImage.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/UploadedImage.kt @@ -4,12 +4,12 @@ package io.tolgee.model +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint import org.hibernate.annotations.ColumnDefault -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint @Entity @Table(uniqueConstraints = [UniqueConstraint(columnNames = ["filename"], name = "uploaded_image_filename")]) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt b/backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt index c0fb083102..1d0211c906 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt @@ -1,15 +1,24 @@ package io.tolgee.model -import com.vladmihalcea.hibernate.type.array.ListArrayType +import io.hypersistence.utils.hibernate.type.array.ListArrayType +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.OrderBy +import jakarta.validation.constraints.NotBlank import org.hibernate.annotations.ColumnDefault import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef import java.util.* -import javax.persistence.* -import javax.validation.constraints.NotBlank @Entity -@TypeDef(name = "string-array", typeClass = ListArrayType::class) data class UserAccount( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -32,7 +41,7 @@ data class UserAccount( @Column(name = "totp_key", columnDefinition = "bytea") var totpKey: ByteArray? = null - @Type(type = "string-array") + @Type(ListArrayType::class) @Column(name = "mfa_recovery_codes", columnDefinition = "text[]") var mfaRecoveryCodes: List = emptyList() diff --git a/backend/data/src/main/kotlin/io/tolgee/model/UserPreferences.kt b/backend/data/src/main/kotlin/io/tolgee/model/UserPreferences.kt index 07b8471199..ec16c274d3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/UserPreferences.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/UserPreferences.kt @@ -1,12 +1,12 @@ package io.tolgee.model -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.Id -import javax.persistence.JoinColumn -import javax.persistence.ManyToOne -import javax.persistence.MapsId -import javax.persistence.OneToOne +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.MapsId +import jakarta.persistence.OneToOne @Entity class UserPreferences( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityDescribingEntity.kt b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityDescribingEntity.kt index 166af57d49..5dd54909c4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityDescribingEntity.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityDescribingEntity.kt @@ -1,24 +1,19 @@ package io.tolgee.model.activity -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.activity.data.EntityDescriptionRef import io.tolgee.activity.data.RevisionType +import jakarta.persistence.Entity +import jakarta.persistence.Enumerated +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import jakarta.persistence.ManyToOne import org.hibernate.annotations.NotFound import org.hibernate.annotations.NotFoundAction import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import java.io.Serializable -import javax.persistence.Entity -import javax.persistence.Enumerated -import javax.persistence.Id -import javax.persistence.IdClass -import javax.persistence.ManyToOne @Entity -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) @IdClass(ActivityDescribingEntityId::class) class ActivityDescribingEntity( @ManyToOne @@ -33,10 +28,10 @@ class ActivityDescribingEntity( val entityId: Long ) : Serializable { - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var data: Map = mutableMapOf() - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var describingRelations: Map? = null @Enumerated diff --git a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityModifiedEntity.kt b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityModifiedEntity.kt index 6621cdf894..4ba74cf8e4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityModifiedEntity.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityModifiedEntity.kt @@ -1,26 +1,21 @@ package io.tolgee.model.activity -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.activity.data.EntityDescriptionRef import io.tolgee.activity.data.PropertyModification import io.tolgee.activity.data.RevisionType +import jakarta.persistence.Entity +import jakarta.persistence.Enumerated +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import jakarta.persistence.ManyToOne import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import java.io.Serializable -import javax.persistence.Entity -import javax.persistence.Enumerated -import javax.persistence.Id -import javax.persistence.IdClass -import javax.persistence.ManyToOne /** * Entity which is modified by the activity. */ @Entity -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) @IdClass(ActivityModifiedEntityId::class) class ActivityModifiedEntity( @ManyToOne @@ -44,20 +39,20 @@ class ActivityModifiedEntity( /** * Map of field to object containing old and new values */ - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var modifications: MutableMap = mutableMapOf() /** * Data, which are discribing the entity, but are not modified by the change */ - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var describingData: Map? = null /** * Relations describing the entity. * e.g. For translation, we would also need key and language data */ - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var describingRelations: Map? = null @Enumerated diff --git a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.kt b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.kt index 0e08d0a9ed..f518c29e7d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.kt @@ -1,36 +1,32 @@ package io.tolgee.model.activity -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.activity.data.ActivityType import io.tolgee.component.CurrentDateProvider import io.tolgee.model.batch.BatchJob import io.tolgee.model.batch.BatchJobChunkExecution -import org.hibernate.annotations.NotFound -import org.hibernate.annotations.NotFoundAction +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Index +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.PrePersist +import jakarta.persistence.SequenceGenerator +import jakarta.persistence.Table +import jakarta.persistence.Temporal +import jakarta.persistence.TemporalType import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import org.springframework.beans.factory.ObjectFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Configurable import java.util.* -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id -import javax.persistence.Index -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.persistence.PrePersist -import javax.persistence.SequenceGenerator -import javax.persistence.Table -import javax.persistence.Temporal -import javax.persistence.TemporalType @Entity @Table( @@ -41,9 +37,6 @@ import javax.persistence.TemporalType ] ) @EntityListeners(ActivityRevision.Companion.ActivityRevisionListener::class) -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) class ActivityRevision : java.io.Serializable { @Id @@ -68,7 +61,7 @@ class ActivityRevision : java.io.Serializable { */ var authorId: Long? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var meta: MutableMap? = null @Enumerated(EnumType.STRING) @@ -80,7 +73,6 @@ class ActivityRevision : java.io.Serializable { var projectId: Long? = null @OneToMany(mappedBy = "activityRevision") - @NotFound(action = NotFoundAction.IGNORE) var describingRelations: MutableList = mutableListOf() @OneToMany(mappedBy = "activityRevision") diff --git a/backend/data/src/main/kotlin/io/tolgee/model/automations/Automation.kt b/backend/data/src/main/kotlin/io/tolgee/model/automations/Automation.kt index 7db5a0cbe4..69d6095fbf 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/automations/Automation.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/automations/Automation.kt @@ -2,10 +2,10 @@ package io.tolgee.model.automations import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToMany +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany @Entity class Automation( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationAction.kt b/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationAction.kt index 3dcaf5f131..bf2f7e4b4a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationAction.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationAction.kt @@ -1,19 +1,13 @@ package io.tolgee.model.automations -import com.vladmihalcea.hibernate.type.json.JsonBinaryType import io.tolgee.model.StandardAuditModel import io.tolgee.model.contentDelivery.ContentDeliveryConfig import io.tolgee.model.webhook.WebhookConfig -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne @Entity -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) class AutomationAction( @ManyToOne(fetch = FetchType.LAZY) var automation: Automation, diff --git a/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationTrigger.kt b/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationTrigger.kt index 6b7658ae82..9fd3f26444 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationTrigger.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/automations/AutomationTrigger.kt @@ -2,11 +2,11 @@ package io.tolgee.model.automations import io.tolgee.activity.data.ActivityType import io.tolgee.model.StandardAuditModel -import javax.persistence.Entity -import javax.persistence.EnumType.STRING -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.ManyToOne +import jakarta.persistence.Entity +import jakarta.persistence.EnumType.STRING +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne @Entity class AutomationTrigger( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJob.kt b/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJob.kt index 1ed55f46c8..0e86cda01a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJob.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJob.kt @@ -1,6 +1,6 @@ package io.tolgee.model.batch -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.batch.JobCharacter import io.tolgee.batch.data.BatchJobDto import io.tolgee.batch.data.BatchJobType @@ -8,24 +8,18 @@ import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount import io.tolgee.model.activity.ActivityRevision +import jakarta.persistence.Entity +import jakarta.persistence.EnumType.STRING +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.persistence.Table import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import java.util.* -import javax.persistence.Entity -import javax.persistence.EnumType.STRING -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.Index -import javax.persistence.ManyToOne -import javax.persistence.OneToOne -import javax.persistence.Table @Entity -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) -@Table(name = "tolgee_batch_job", indexes = [Index(columnList = "debouncingKey")]) +@Table(name = "tolgee_batch_job") class BatchJob : StandardAuditModel(), IBatchJob { @ManyToOne(fetch = FetchType.LAZY) lateinit var project: Project @@ -33,7 +27,7 @@ class BatchJob : StandardAuditModel(), IBatchJob { @ManyToOne(fetch = FetchType.LAZY) var author: UserAccount? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var target: List = listOf() var totalItems: Int = 0 @@ -51,7 +45,7 @@ class BatchJob : StandardAuditModel(), IBatchJob { @OneToOne(mappedBy = "batchJob", fetch = FetchType.LAZY) var activityRevision: ActivityRevision? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var params: Any? = null val chunkedTarget get() = chunkTarget(chunkSize, target) @@ -71,7 +65,6 @@ class BatchJob : StandardAuditModel(), IBatchJob { var lastDebouncingEvent: Date? = null - @Type(type = "text") var debouncingKey: String? = null companion object { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJobChunkExecution.kt b/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJobChunkExecution.kt index d17070e61a..e56075d2b6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJobChunkExecution.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/batch/BatchJobChunkExecution.kt @@ -1,34 +1,29 @@ package io.tolgee.model.batch -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.constants.Message import io.tolgee.model.StandardAuditModel import io.tolgee.model.activity.ActivityRevision +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.persistence.Table import org.hibernate.annotations.ColumnDefault import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import java.util.* -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToOne -import javax.persistence.Table @Entity @Table( name = "tolgee_batch_job_chunk_execution", indexes = [ - javax.persistence.Index(columnList = "chunkNumber"), - javax.persistence.Index(columnList = "status"), + jakarta.persistence.Index(columnList = "chunkNumber"), + jakarta.persistence.Index(columnList = "status"), ] ) -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) class BatchJobChunkExecution : StandardAuditModel() { @ManyToOne(fetch = FetchType.LAZY, optional = false) lateinit var batchJob: BatchJob @@ -38,7 +33,7 @@ class BatchJobChunkExecution : StandardAuditModel() { var chunkNumber: Int = 0 - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var successTargets: List = listOf() @Column(columnDefinition = "text") diff --git a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/AzureContentStorageConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/AzureContentStorageConfig.kt index 924c3d03e4..f8c9febf64 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/AzureContentStorageConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/AzureContentStorageConfig.kt @@ -1,13 +1,13 @@ package io.tolgee.model.contentDelivery -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.Id -import javax.persistence.JoinColumn -import javax.persistence.MapsId -import javax.persistence.OneToOne -import javax.validation.constraints.NotBlank +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.MapsId +import jakarta.persistence.OneToOne +import jakarta.validation.constraints.NotBlank @Entity() class AzureContentStorageConfig( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentDeliveryConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentDeliveryConfig.kt index 6557f5864d..d8237cb0da 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentDeliveryConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentDeliveryConfig.kt @@ -1,30 +1,25 @@ package io.tolgee.model.contentDelivery -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.dtos.IExportParams import io.tolgee.dtos.request.export.ExportFormat import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel import io.tolgee.model.automations.AutomationAction import io.tolgee.model.enums.TranslationState +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs import java.util.* -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.Table -import javax.persistence.UniqueConstraint -@Entity() +@Entity @Table( uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "slug"])] ) -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) class ContentDeliveryConfig( @ManyToOne(fetch = FetchType.LAZY) var project: Project, @@ -41,26 +36,26 @@ class ContentDeliveryConfig( var lastPublished: Date? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) override var languages: Set? = null override var format: ExportFormat = ExportFormat.JSON override var structureDelimiter: Char? = '.' - @Type(type = "jsonb") + @Type(JsonBinaryType::class) override var filterKeyId: List? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) override var filterKeyIdNot: List? = null override var filterTag: String? = null override var filterKeyPrefix: String? = null - @Type(type = "jsonb") + @Type(JsonBinaryType::class) override var filterState: List? = listOf( TranslationState.TRANSLATED, TranslationState.REVIEWED, ) - @Type(type = "jsonb") + @Type(JsonBinaryType::class) override var filterNamespace: List? = null } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentStorage.kt b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentStorage.kt index 5658b2860f..b6e475775c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentStorage.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/ContentStorage.kt @@ -2,11 +2,11 @@ package io.tolgee.model.contentDelivery import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToOne -import javax.validation.constraints.NotBlank +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.validation.constraints.NotBlank @Entity() class ContentStorage( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/S3ContentStorageConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/S3ContentStorageConfig.kt index 2f121cfb24..8611958ead 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/S3ContentStorageConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/contentDelivery/S3ContentStorageConfig.kt @@ -1,13 +1,13 @@ package io.tolgee.model.contentDelivery -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.Id -import javax.persistence.JoinColumn -import javax.persistence.MapsId -import javax.persistence.OneToOne -import javax.validation.constraints.NotBlank +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.MapsId +import jakarta.persistence.OneToOne +import jakarta.validation.constraints.NotBlank @Entity() class S3ContentStorageConfig( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/Import.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/Import.kt index aa8df654bd..0f7e3b4aa3 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/Import.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/Import.kt @@ -3,12 +3,12 @@ package io.tolgee.model.dataImport import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotNull +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotNull @Entity @Table(uniqueConstraints = [UniqueConstraint(columnNames = ["author_id", "project_id"])]) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportFile.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportFile.kt index 382e227ad6..9137eb2034 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportFile.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportFile.kt @@ -5,11 +5,11 @@ import io.tolgee.model.dataImport.issues.ImportFileIssue import io.tolgee.model.dataImport.issues.ImportFileIssueParam import io.tolgee.model.dataImport.issues.issueTypes.FileIssueType import io.tolgee.model.dataImport.issues.paramTypes.FileIssueParamType -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.validation.constraints.Size +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.validation.constraints.Size @Entity class ImportFile( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportKey.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportKey.kt index 7ac0cf6e67..29bac88d13 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportKey.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportKey.kt @@ -2,13 +2,13 @@ package io.tolgee.model.dataImport import io.tolgee.model.StandardAuditModel import io.tolgee.model.key.KeyMeta -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size @Entity class ImportKey( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportLanguage.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportLanguage.kt index d82bdf9178..0d411fd5a6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportLanguage.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportLanguage.kt @@ -2,11 +2,11 @@ package io.tolgee.model.dataImport import io.tolgee.model.Language import io.tolgee.model.StandardAuditModel -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.validation.constraints.Size +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.validation.constraints.Size @Entity class ImportLanguage( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportTranslation.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportTranslation.kt index f2302f0afc..5d5b690941 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportTranslation.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/ImportTranslation.kt @@ -1,15 +1,15 @@ package io.tolgee.model.dataImport -import com.sun.istack.NotNull import io.tolgee.model.StandardAuditModel import io.tolgee.model.translation.Translation +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne +import jakarta.validation.constraints.NotNull import org.apache.commons.codec.digest.MurmurHash3 import java.nio.ByteBuffer import java.util.* -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.ManyToOne -import javax.persistence.OneToOne @Entity class ImportTranslation( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssue.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssue.kt index ede8e41407..7049eb2ee8 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssue.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssue.kt @@ -3,11 +3,11 @@ package io.tolgee.model.dataImport.issues import io.tolgee.model.StandardAuditModel import io.tolgee.model.dataImport.ImportFile import io.tolgee.model.dataImport.issues.issueTypes.FileIssueType -import javax.persistence.Entity -import javax.persistence.Enumerated -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.validation.constraints.NotNull +import jakarta.persistence.Entity +import jakarta.persistence.Enumerated +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.validation.constraints.NotNull @Entity class ImportFileIssue( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssueParam.kt b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssueParam.kt index fa6fa53f8a..9643d66c8c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssueParam.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/dataImport/issues/ImportFileIssueParam.kt @@ -2,10 +2,10 @@ package io.tolgee.model.dataImport.issues import io.tolgee.model.StandardAuditModel import io.tolgee.model.dataImport.issues.paramTypes.FileIssueParamType -import javax.persistence.Entity -import javax.persistence.Enumerated -import javax.persistence.ManyToOne -import javax.validation.constraints.NotBlank +import jakarta.persistence.Entity +import jakarta.persistence.Enumerated +import jakarta.persistence.ManyToOne +import jakarta.validation.constraints.NotBlank @Entity class ImportFileIssueParam( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/enums/ProjectPermissionType.kt b/backend/data/src/main/kotlin/io/tolgee/model/enums/ProjectPermissionType.kt index 30eee3ebe2..b89925f3b1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/enums/ProjectPermissionType.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/enums/ProjectPermissionType.kt @@ -61,7 +61,7 @@ enum class ProjectPermissionType(val availableScopes: Array) { companion object { fun getRoles(): Map> { val result = mutableMapOf>() - entries.forEach { value -> result[value.name] = value.availableScopes } + values().forEach { value -> result[value.name] = value.availableScopes } return result.toMap() } } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/Key.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/Key.kt index eb0ed43501..b9760915ae 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/Key.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/Key.kt @@ -13,23 +13,23 @@ import io.tolgee.model.StandardAuditModel import io.tolgee.model.dataImport.WithKeyMeta import io.tolgee.model.key.screenshotReference.KeyScreenshotReference import io.tolgee.model.translation.Translation +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.PrePersist +import jakarta.persistence.PreRemove +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size import org.springframework.beans.factory.ObjectFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Configurable import org.springframework.context.ApplicationEventPublisher -import javax.persistence.CascadeType -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.persistence.PrePersist -import javax.persistence.PreRemove -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size @Entity @ActivityLoggedEntity diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyCodeReference.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyCodeReference.kt index ee480b2564..fbdd2e0bcf 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyCodeReference.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyCodeReference.kt @@ -1,13 +1,13 @@ package io.tolgee.model.key -import com.sun.istack.NotNull import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.validation.constraints.NotBlank +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull @Entity class KeyCodeReference( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyComment.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyComment.kt index f766d39cb9..465e94ffa6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyComment.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyComment.kt @@ -2,12 +2,12 @@ package io.tolgee.model.key import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull @Entity class KeyComment( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyMeta.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyMeta.kt index f355c867f7..8dba03f54d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/KeyMeta.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/KeyMeta.kt @@ -7,14 +7,14 @@ import io.tolgee.activity.propChangesProvider.TagsPropChangesProvider import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount import io.tolgee.model.dataImport.ImportKey -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.ManyToMany -import javax.persistence.OneToMany -import javax.persistence.OneToOne -import javax.persistence.OrderBy -import javax.persistence.PrePersist -import javax.persistence.PreUpdate +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.ManyToMany +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.OrderBy +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate @Entity @EntityListeners(KeyMeta.Companion.KeyMetaListener::class) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/Namespace.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/Namespace.kt index 6fddf9d5bf..5ad95afb05 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/Namespace.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/Namespace.kt @@ -6,13 +6,13 @@ import io.tolgee.activity.annotation.ActivityLoggedProp import io.tolgee.activity.annotation.ActivityReturnsExistence import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel +import jakarta.persistence.Entity +import jakarta.persistence.Index +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length -import javax.persistence.Entity -import javax.persistence.Index -import javax.persistence.ManyToOne -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotBlank @Entity @ActivityLoggedEntity diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/Tag.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/Tag.kt index 5b18d02537..d95dc06d8d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/Tag.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/Tag.kt @@ -2,14 +2,14 @@ package io.tolgee.model.key import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.Index -import javax.persistence.ManyToMany -import javax.persistence.ManyToOne -import javax.persistence.OrderBy -import javax.persistence.Table -import javax.validation.constraints.NotEmpty +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.Index +import jakarta.persistence.ManyToMany +import jakarta.persistence.ManyToOne +import jakarta.persistence.OrderBy +import jakarta.persistence.Table +import jakarta.validation.constraints.NotEmpty @Entity @Table(indexes = [Index(columnList = "project_id, name", unique = true)]) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyInScreenshotPosition.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyInScreenshotPosition.kt index da161e4a5b..836b0fd942 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyInScreenshotPosition.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyInScreenshotPosition.kt @@ -1,6 +1,6 @@ package io.tolgee.model.key.screenshotReference -class KeyInScreenshotPosition( +data class KeyInScreenshotPosition( val x: Int, val y: Int, val width: Int, diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReference.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReference.kt index 27c89cc35d..ace5bba210 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReference.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReference.kt @@ -1,22 +1,17 @@ package io.tolgee.model.key.screenshotReference -import com.vladmihalcea.hibernate.type.json.JsonBinaryType +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import io.tolgee.model.Screenshot import io.tolgee.model.key.Key +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import jakarta.persistence.ManyToOne import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.hibernate.annotations.TypeDefs -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.Id -import javax.persistence.IdClass -import javax.persistence.ManyToOne @Entity @IdClass(KeyScreenshotReferenceId::class) -@TypeDefs( - value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)] -) class KeyScreenshotReference { @ManyToOne(optional = false) @Id @@ -26,7 +21,7 @@ class KeyScreenshotReference { @Id lateinit var screenshot: Screenshot - @Type(type = "jsonb") + @Type(JsonBinaryType::class) var positions: MutableList? = mutableListOf() @Column(columnDefinition = "text", length = 5000) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReferenceId.kt b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReferenceId.kt index e664319c55..c081af295c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReferenceId.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/key/screenshotReference/KeyScreenshotReferenceId.kt @@ -2,7 +2,7 @@ package io.tolgee.model.key.screenshotReference import java.io.Serializable -class KeyScreenshotReferenceId( +data class KeyScreenshotReferenceId( var key: Long? = null, var screenshot: Long? = null ) : Serializable diff --git a/backend/data/src/main/kotlin/io/tolgee/model/keyBigMeta/KeysDistance.kt b/backend/data/src/main/kotlin/io/tolgee/model/keyBigMeta/KeysDistance.kt index 38cf62803f..0ae926aeae 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/keyBigMeta/KeysDistance.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/keyBigMeta/KeysDistance.kt @@ -2,14 +2,14 @@ package io.tolgee.model.keyBigMeta import io.tolgee.model.AuditModel import io.tolgee.model.Project +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import jakarta.persistence.Index +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table import org.springframework.data.domain.Persistable -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.Id -import javax.persistence.IdClass -import javax.persistence.Index -import javax.persistence.ManyToOne -import javax.persistence.Table @Entity @Table( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/mtServiceConfig/MtServiceConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/mtServiceConfig/MtServiceConfig.kt index c58519ce08..4e2c207473 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/mtServiceConfig/MtServiceConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/mtServiceConfig/MtServiceConfig.kt @@ -5,14 +5,14 @@ import io.tolgee.model.Language import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel import io.tolgee.service.machineTranslation.MtServiceInfo +import jakarta.persistence.ElementCollection +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToOne import org.hibernate.annotations.ColumnDefault -import javax.persistence.ElementCollection -import javax.persistence.Entity -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToOne @Entity class MtServiceConfig : StandardAuditModel() { diff --git a/backend/data/src/main/kotlin/io/tolgee/model/translation/Translation.kt b/backend/data/src/main/kotlin/io/tolgee/model/translation/Translation.kt index 1254e004c7..d89a516f5a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/translation/Translation.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/translation/Translation.kt @@ -12,19 +12,19 @@ import io.tolgee.model.StandardAuditModel import io.tolgee.model.enums.TranslationState import io.tolgee.model.key.Key import io.tolgee.util.TranslationStatsUtil +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import jakarta.validation.constraints.NotNull import org.hibernate.annotations.ColumnDefault -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EntityListeners -import javax.persistence.Enumerated -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.persistence.PrePersist -import javax.persistence.PreUpdate -import javax.persistence.Table -import javax.persistence.UniqueConstraint -import javax.validation.constraints.NotNull @Entity @Table( diff --git a/backend/data/src/main/kotlin/io/tolgee/model/translation/TranslationComment.kt b/backend/data/src/main/kotlin/io/tolgee/model/translation/TranslationComment.kt index 72b59bc77c..cf47b17650 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/translation/TranslationComment.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/translation/TranslationComment.kt @@ -7,14 +7,14 @@ import io.tolgee.activity.annotation.ActivityLoggedProp import io.tolgee.model.StandardAuditModel import io.tolgee.model.UserAccount import io.tolgee.model.enums.TranslationCommentState +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType.LAZY +import jakarta.persistence.Index +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.FetchType.LAZY -import javax.persistence.Index -import javax.persistence.ManyToOne -import javax.persistence.Table -import javax.validation.constraints.NotBlank @Entity @ActivityLoggedEntity diff --git a/backend/data/src/main/kotlin/io/tolgee/model/views/LanguageView.kt b/backend/data/src/main/kotlin/io/tolgee/model/views/LanguageView.kt new file mode 100644 index 0000000000..39332164ca --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/model/views/LanguageView.kt @@ -0,0 +1,10 @@ +package io.tolgee.model.views + +import io.tolgee.model.Language + +interface LanguageView { + var language: Language + var base: Boolean +} + +class LanguageViewImpl(override var language: Language, override var base: Boolean) : LanguageView diff --git a/backend/data/src/main/kotlin/io/tolgee/model/views/ProjectWithStatsView.kt b/backend/data/src/main/kotlin/io/tolgee/model/views/ProjectWithStatsView.kt index 0aa109c1b5..f946b05ffa 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/views/ProjectWithStatsView.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/views/ProjectWithStatsView.kt @@ -1,12 +1,11 @@ package io.tolgee.model.views import io.tolgee.dtos.query_results.ProjectStatistics -import io.tolgee.model.Language class ProjectWithStatsView( view: ProjectWithLanguagesView, val stats: ProjectStatistics, - val languages: List, + val languages: List, ) : ProjectWithLanguagesView( view.id, view.name, diff --git a/backend/data/src/main/kotlin/io/tolgee/model/webhook/WebhookConfig.kt b/backend/data/src/main/kotlin/io/tolgee/model/webhook/WebhookConfig.kt index 10eaf9b9e6..aab0bab535 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/webhook/WebhookConfig.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/webhook/WebhookConfig.kt @@ -3,12 +3,12 @@ package io.tolgee.model.webhook import io.tolgee.model.Project import io.tolgee.model.StandardAuditModel import io.tolgee.model.automations.AutomationAction +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import jakarta.validation.constraints.NotBlank import java.util.* -import javax.persistence.Entity -import javax.persistence.FetchType -import javax.persistence.ManyToOne -import javax.persistence.OneToMany -import javax.validation.constraints.NotBlank @Entity class WebhookConfig( diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/InvitationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/InvitationRepository.kt index b99b50dee6..fd97d14ebb 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/InvitationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/InvitationRepository.kt @@ -13,7 +13,16 @@ interface InvitationRepository : JpaRepository { fun deleteAllByCreatedAtLessThan(date: Date) fun findOneByCode(code: String?): Optional fun findAllByPermissionProjectOrderByCreatedAt(project: Project): LinkedHashSet - fun getAllByOrganizationRoleOrganizationOrderByCreatedAt(organization: Organization): List + @Query( + """ + from Invitation i + left join fetch i.organizationRole orl + left join fetch i.permission p + where i.organizationRole.organization = :organization + order by i.createdAt + """ + ) + fun getAllForOrganization(organization: Organization): List @Query( """ diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/KeyCodeReferenceRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/KeyCodeReferenceRepository.kt index dd29a972af..756322b006 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/KeyCodeReferenceRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/KeyCodeReferenceRepository.kt @@ -23,13 +23,13 @@ interface KeyCodeReferenceRepository : JpaRepository { """delete from KeyCodeReference kcr where kcr.keyMeta in (select km from kcr.keyMeta km where km.key.id in :keyIds)""" ) - fun deleteAllByKeyIds(keyIds: Any) + fun deleteAllByKeyIds(keyIds: Collection) @Modifying - @Transactional @Query( """delete from KeyCodeReference kcr where kcr.keyMeta in - (select km from kcr.keyMeta km where km.key.id = :keyId)""" + (select km from kcr.keyMeta km where km.key.id in + (select k.id from Key k where k.project.id = :projectId))""" ) - fun deleteAllByKeyId(keyId: Long) + fun deleteAllByProject(projectId: Long) } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/KeyCommentRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/KeyCommentRepository.kt index 98e4c7481c..7dbbbf09f1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/KeyCommentRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/KeyCommentRepository.kt @@ -27,10 +27,10 @@ interface KeyCommentRepository : JpaRepository { fun deleteAllByKeyIds(keyIds: Collection) @Modifying - @Transactional @Query( "delete from KeyComment kc " + - "where kc.keyMeta in (select km from kc.keyMeta km where km.key.id = :keyId)" + "where kc.keyMeta in (select km from kc.keyMeta km where km.key.id in " + + "(select k.id from Key k where k.project.id = :projectId))" ) - fun deleteAllByKeyId(keyId: Long) + fun deleteAllByProject(projectId: Long) } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/KeyMetaRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/KeyMetaRepository.kt index ceea359efd..efa4d7eee9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/KeyMetaRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/KeyMetaRepository.kt @@ -42,7 +42,6 @@ interface KeyMetaRepository : JpaRepository { fun deleteAllByKeyIds(keyIds: Collection) @Modifying - @Transactional - @Query("delete from KeyMeta km where km.key.id = :keyId") - fun deleteAllByKeyId(keyId: Long) + @Query("delete from KeyMeta km where km.key.id in (select k.id from Key k where k.project.id = :projectId)") + fun deleteAllByProject(projectId: Long) } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt index 260bc7ddbf..55b5e7dbb6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt @@ -39,8 +39,8 @@ interface KeyRepository : JpaRepository { ) fun getAllByProjectIdSortedById(projectId: Long): List - @Query("select k.id from Key k where k.project.id = :projectId") - fun getIdsByProjectId(projectId: Long?): List + @Query("select k from Key k left join fetch k.keyMeta km where k.project.id = :projectId") + fun getByProjectIdWithFetchedMetas(projectId: Long?): List fun deleteAllByIdIn(ids: Collection) fun findAllByIdIn(ids: Collection): List @@ -156,8 +156,8 @@ interface KeyRepository : JpaRepository { """ from Language l join l.translations t - where t.key.id = :keyId - and l.project.id = :projectId + where t.id in (select t.id from Key k join k.translations t where k.id = :keyId) + and l.project.id = :projectId and t.state = io.tolgee.model.enums.TranslationState.DISABLED order by l.id """ diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt index fe29e2c1f4..5803647611 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt @@ -1,9 +1,11 @@ package io.tolgee.repository import io.tolgee.model.Language +import io.tolgee.model.views.LanguageView import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository import java.util.* @@ -13,9 +15,41 @@ interface LanguageRepository : JpaRepository { fun findByNameAndProject(name: String?, project: io.tolgee.model.Project): Optional fun findByTagAndProjectId(abbreviation: String?, projectId: Long): Optional fun findAllByProjectId(projectId: Long?): Set - fun findAllByProjectId(projectId: Long?, pageable: Pageable): Page + + @Query( + """ + select l as language, (pb.id = l.id) as base + from Language l + join l.project p + left join p.baseLanguage pb + where l.project.id = :projectId + """ + ) + fun findAllByProjectId(projectId: Long?, pageable: Pageable): Page fun findAllByTagInAndProjectId(abbreviation: Collection?, projectId: Long?): List fun deleteAllByProjectId(projectId: Long?) + + @Query( + """ + select l as language, (l.id = bl.id) as base + from Language l + join l.project p + join p.baseLanguage bl + where l.id = :id + """ + ) + fun findView(id: Long): LanguageView? + + @Query( + """ + select l as language, (l.id = coalesce(bl.id, 0)) as base + from Language l + join l.project p + left join p.baseLanguage bl + where l.project.id in :projectIds + """ + ) + fun getViewsOfProjects(projectIds: List): List fun countByIdInAndProjectId(languageIds: Set, projectId: Long): Int fun findAllByProjectIdAndIdInOrderById(projectId: Long, languageIds: List): List } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NamespaceRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NamespaceRepository.kt index 31767abfce..3e05424e4a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/NamespaceRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NamespaceRepository.kt @@ -4,6 +4,7 @@ import io.tolgee.model.key.Namespace import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @@ -41,4 +42,13 @@ interface NamespaceRepository : JpaRepository { """ ) fun findOneByProjectIdAndName(projectId: Long, name: String): Namespace? + + @Modifying + @Query( + """ + delete from namespace where project_id = :projectId + """, + nativeQuery = true + ) + fun deleteAllByProjectId(projectId: Long) } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt index f07a4bd222..4062bdb6ce 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt @@ -12,7 +12,15 @@ import org.springframework.stereotype.Repository @Repository interface OrganizationRepository : JpaRepository { - fun getOneBySlug(slug: String): Organization? + + @Query( + """ + from Organization o + left join fetch o.basePermission as bp + where o.slug = :slug and o.deletedAt is null + """ + ) + fun findBySlug(slug: String): Organization? @Query( """select distinct o as organization, r.type as currentUserRole @@ -20,22 +28,22 @@ interface OrganizationRepository : JpaRepository { join fetch o.basePermission as bp left join OrganizationRole r on r.user.id = :userId and r.organization = o and (r.type = :roleType or :roleType is null) - left join o.projects p + left join o.projects p on p.deletedAt is null left join p.permissions perm on perm.user.id = :userId where (perm is not null or r is not null) and (:search is null or (lower(o.name) like lower(concat('%', cast(:search as text), '%')))) - and (:exceptOrganizationId is null or (o.id <> :exceptOrganizationId)) + and (:exceptOrganizationId is null or (o.id <> :exceptOrganizationId)) and o.deletedAt is null """, countQuery = """select count(o) from Organization o left join OrganizationRole r on r.user.id = :userId and r.organization = o and (r.type = :roleType or :roleType is null) - left join o.projects p + left join o.projects p on p.deletedAt is null left join p.permissions perm on perm.user.id = :userId where (perm is not null or r is not null) and (:search is null or (lower(o.name) like lower(concat('%', cast(:search as text), '%')))) - and (:exceptOrganizationId is null or (o.id <> :exceptOrganizationId)) + and (:exceptOrganizationId is null or (o.id <> :exceptOrganizationId)) and o.deletedAt is null """ ) fun findAllPermitted( @@ -50,11 +58,12 @@ interface OrganizationRepository : JpaRepository { """ select o from Organization o + left join fetch o.basePermission bp left join o.memberRoles mr on mr.user.id = :userId - left join o.projects p + left join o.projects p on p.deletedAt is null left join p.permissions perm on perm.user.id = :userId - where (perm is not null or mr is not null) and o.id <> :exceptOrganizationId - group by mr.id, o.id + where (perm is not null or mr is not null) and o.id <> :exceptOrganizationId and o.deletedAt is null + group by mr.id, o.id, bp.id order by mr.id asc nulls last """ ) @@ -69,14 +78,28 @@ interface OrganizationRepository : JpaRepository { select count(o) > 0 from Organization o left join o.memberRoles mr on mr.user.id = :userId - left join o.projects p + left join o.projects p on p.deletedAt is null left join p.permissions perm on perm.user.id = :userId - where (perm is not null or mr is not null) and o.id = :organizationId + where (perm is not null or mr is not null) and o.id = :organizationId and o.deletedAt is null """ ) fun canUserView(userId: Long, organizationId: Long): Boolean - fun countAllBySlug(slug: String): Long + @Query( + """ + select count(o) > 0 + from Organization o + where o.slug = :slug + """ + ) + fun organizationWithSlugExists(slug: String): Boolean + + @Query( + """ + from Organization o + where o.name = :name and o.deletedAt is null + """ + ) fun findAllByName(name: String): List @Query( @@ -84,7 +107,7 @@ interface OrganizationRepository : JpaRepository { from Organization o join o.memberRoles mr on mr.user = :user join mr.user u - where o.name = u.name and mr.type = 1 + where o.name = u.name and mr.type = 1 and o.deletedAt is null """ ) fun findUsersDefaultOrganization(user: UserAccount): Organization? @@ -95,11 +118,13 @@ interface OrganizationRepository : JpaRepository { join fetch o.basePermission bp left join OrganizationRole r on r.user.id = :userId and r.organization = o where (:search is null or (lower(o.name) like lower(concat('%', cast(:search as text), '%')))) + and o.deletedAt is null """, countQuery = """select count(o) from Organization o where (:search is null or (lower(o.name) like lower(concat('%', cast(:search as text), '%')))) + and o.deletedAt is null """ ) fun findAllViews(pageable: Pageable, search: String?, userId: Long): Page @@ -109,6 +134,7 @@ interface OrganizationRepository : JpaRepository { from Organization o join o.memberRoles mr on mr.user = :userAccount and mr.type = :type join o.memberRoles mra on mra.type = :type + where o.deletedAt is null group by o.id, mr.id having count(mra.id) = 1 """ @@ -121,9 +147,28 @@ interface OrganizationRepository : JpaRepository { @Query( """ from Organization o - join o.projects p on p.id = :projectId + join o.projects p on p.id = :projectId and p.deletedAt is null join fetch o.basePermission + where o.deletedAt is null """ ) fun getProjectOwner(projectId: Long): Organization + + @Query( + """ + from Organization o + left join fetch o.basePermission + left join fetch o.mtCreditBucket + where o = :organization + """ + ) + fun fetchData(organization: Organization): Organization + + @Query( + """ + from Organization o + where o.id = :id and o.deletedAt is null + """ + ) + fun find(id: Long): Organization? } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/PermissionRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/PermissionRepository.kt index 376304948c..90fb92e37f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/PermissionRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/PermissionRepository.kt @@ -31,6 +31,15 @@ interface PermissionRepository : JpaRepository { @Query("select p.id from Permission p where p.project.id = :projectId") fun getIdsByProject(projectId: Long): List + @Query( + """select p from Permission p + left join fetch p.viewLanguages + left join fetch p.translateLanguages + left join fetch p.stateChangeLanguages + where p.project.id = :projectId""" + ) + fun getByProjectWithFetchedLanguages(projectId: Long): List + @Query( """select distinct p from Permission p diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt index 2e0d43643f..276f0f157a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt @@ -26,11 +26,11 @@ interface ProjectRepository : JpaRepository { } @Query( - """from Project r + """select r, p, o, role from Project r left join fetch Permission p on p.project = r and p.user.id = :userAccountId left join fetch Organization o on r.organizationOwner = o left join fetch OrganizationRole role on role.organization = o and role.user.id = :userAccountId - where p is not null or (role is not null) + where (p is not null or (role is not null)) and r.deletedAt is null order by r.name """ ) @@ -49,7 +49,7 @@ interface ProjectRepository : JpaRepository { :search is null or (lower(r.name) like lower(concat('%', cast(:search as text), '%')) or lower(o.name) like lower(concat('%', cast(:search as text),'%'))) ) - and (:organizationId is null or o.id = :organizationId) + and (:organizationId is null or o.id = :organizationId) and r.deletedAt is null """ ) fun findAllPermitted( @@ -67,26 +67,30 @@ interface ProjectRepository : JpaRepository { @Query( """ $BASE_VIEW_QUERY - where r.id = :projectId + where r.id = :projectId and r.deletedAt is null """ ) fun findViewById(userAccountId: Long, projectId: Long): ProjectView? - fun findAllByName(name: String): List - @Query( """ - from Project p left join fetch p.languages l left join fetch p.baseLanguage where p.id in :projectIds + from Project p where p.name = :name and p.deletedAt is null """ ) - fun getWithLanguages(projectIds: Iterable): List + fun findAllByName(name: String): List + @Query( + """ + from Project p + where p.name = :name and p.organizationOwner = :organization and p.deletedAt is null + """ + ) fun findAllByNameAndOrganizationOwner(name: String, organization: Organization): List @Query( """ from Project p - where p.organizationOwner is null and p.userOwner is not null + where p.organizationOwner is null and p.userOwner is not null and p.deletedAt is null """ ) fun findAllWithUserOwner(pageable: Pageable): Page @@ -95,7 +99,7 @@ interface ProjectRepository : JpaRepository { """ select p.id from Project p - where p.organizationOwner is null + where p.organizationOwner is null and p.deletedAt is null """ ) fun findAllWithUserOwnerIds(): List @@ -106,8 +110,15 @@ interface ProjectRepository : JpaRepository { from Project p join p.permissions pp on pp.user.id in :userIds join fetch p.baseLanguage - where p.organizationOwner.id = :organizationId + where p.organizationOwner.id = :organizationId and p.deletedAt is null """ ) fun getProjectsWithDirectPermissions(organizationId: Long, userIds: List): List> + + @Query( + """ + from Project where id = :id and deletedAt is null + """ + ) + fun find(id: Long): Project? } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/ScreenshotRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/ScreenshotRepository.kt index f048136de7..7e9ee3c36d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/ScreenshotRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/ScreenshotRepository.kt @@ -9,19 +9,24 @@ import org.springframework.stereotype.Repository @Repository interface ScreenshotRepository : JpaRepository { - @Query("FROM Screenshot s join s.keyScreenshotReferences ksr where ksr.key = :key") + @Query( + """FROM Screenshot s where s.id in + (select ksr.screenshot.id from Key k join k.keyScreenshotReferences ksr where k = :key) + """ + ) fun findAllByKey(key: Key): List - @Query("FROM Screenshot s join fetch s.keyScreenshotReferences ksr join ksr.key k where k.project.id = :projectId") + @Query( + """FROM Screenshot s join fetch s.keyScreenshotReferences ksr + where s.id in (select ksr.screenshot.id from Key k join k.keyScreenshotReferences ksr where k.project.id = :projectId) + """ + ) fun getAllByKeyProjectId(projectId: Long): List - @Query("FROM Screenshot s join fetch s.keyScreenshotReferences ksr where ksr.key.id = :id") - fun getAllByKeyId(id: Long): List - - @Query("FROM Screenshot s join s.keyScreenshotReferences ksr where ksr.key.id in :keyIds") - fun getAllByKeyIdIn(keyIds: Collection): List - - @Query("SELECT count(s.id) FROM Screenshot s join s.keyScreenshotReferences ksr where ksr.key = :key") + @Query( + """SELECT count(s.id) FROM Screenshot s where s.id in + (select ksr.screenshot.id from Key k join k.keyScreenshotReferences ksr where k = :key)""" + ) fun countByKey(key: Key): Long @Query( diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/TagRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/TagRepository.kt index 05f579188e..4044360707 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/TagRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/TagRepository.kt @@ -68,4 +68,22 @@ interface TagRepository : JpaRepository { """ ) fun deleteAllUnused(projectId: Long) + + @Modifying(flushAutomatically = true) + @Query( + """ + delete from Tag t + where (t.id in (select tag.id from Tag tag join tag.keyMetas km join km.key k where k in :keys)) + """ + ) + fun deleteAllByKeyIn(keys: Collection) + + @Query( + """ + from Tag t + join fetch t.keyMetas km + where km.key.id in :keyIds + """ + ) + fun getAllByKeyIds(keyIds: Collection): List } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/TranslationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/TranslationRepository.kt index fde3246c54..9c89a8553e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/TranslationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/TranslationRepository.kt @@ -81,7 +81,7 @@ interface TranslationRepository : JpaRepository { target.text <> '' and target.text is not null where baseTranslation.language = :baseLanguage and - similarity(baseTranslation.text, :baseTranslationText) > 0.5 and + cast(similarity(baseTranslation.text, :baseTranslationText) as float)> 0.5F and (:key is null or key <> :key) order by similarity desc """ @@ -163,4 +163,13 @@ interface TranslationRepository : JpaRepository { fun findAllByKeyIdInAndLanguageIdIn(keysIds: List, languagesIds: List): List fun getAllByKeyIdInAndLanguageIdIn(keyIds: List, languageIds: List): List + + @Query( + """ + from Translation t + join t.key k + where k.project.id = :projectId + """ + ) + fun getAllByProjectId(projectId: Long): List } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt index a2524c7207..cdd65fbb78 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt @@ -28,8 +28,8 @@ interface UserAccountRepository : JpaRepository { @Query( """update UserAccount ua set - ua.deletedAt = now(), - ua.tokensValidNotBefore = now(), + ua.deletedAt = :now, + ua.tokensValidNotBefore = :now, ua.password = null, ua.totpKey = null, ua.mfaRecoveryCodes = null, @@ -41,7 +41,7 @@ interface UserAccountRepository : JpaRepository { where ua = :user """ ) - fun softDeleteUser(user: UserAccount) + fun softDeleteUser(user: UserAccount, now: Date) @Query( """ @@ -63,7 +63,7 @@ interface UserAccountRepository : JpaRepository { from UserAccount ua left join ua.organizationRoles mr on mr.organization.id = :organizationId left join ua.permissions pp - left join pp.project p + left join pp.project p on p.deletedAt is null left join p.organizationOwner o on o.id = :organizationId where (o is not null or mr is not null) and ((lower(ua.name) like lower(concat('%', cast(:search as text),'%')) @@ -82,9 +82,10 @@ interface UserAccountRepository : JpaRepository { """ select ua.id as id, ua.name as name, ua.username as username, p as directPermission, orl.type as organizationRole, ua.avatarHash as avatarHash - from UserAccount ua, Project r - left join fetch Permission p on ua = p.user and p.project.id = :projectId - left join OrganizationRole orl on orl.user = ua and r.organizationOwner = orl.organization + from UserAccount ua + left join Project r on r.id = :projectId + left join ua.permissions p on p.project.id = :projectId + left join ua.organizationRoles orl on orl.organization = r.organizationOwner where r.id = :projectId and (p is not null or orl is not null) and (:exceptUserId is null or ua.id <> :exceptUserId) and ((lower(ua.name) @@ -153,4 +154,24 @@ interface UserAccountRepository : JpaRepository { """ ) fun findDisabled(id: Long): UserAccount + + @Query( + """ + from UserAccount ua + left join fetch ua.emailVerification + left join fetch ua.permissions + where ua.id = :id + """ + ) + fun findWithFetchedEmailVerificationAndPermissions(id: Long): UserAccount? + + @Query( + """ + from UserAccount ua + left join fetch ua.emailVerification + left join fetch ua.permissions + where ua.username in :usernames and ua.deletedAt is null + """ + ) + fun findActiveWithFetchedDataByUserNames(usernames: List): List } diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/translation/TranslationCommentRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/translation/TranslationCommentRepository.kt index a464a71e3d..2aca539683 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/translation/TranslationCommentRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/translation/TranslationCommentRepository.kt @@ -18,4 +18,14 @@ interface TranslationCommentRepository : JpaRepository fun deleteAllByTranslationIdIn(translationIds: Collection) fun deleteByTranslationIdIn(ids: Collection) + + @Query( + """ + from TranslationComment tc + join tc.translation t + join t.key k + where k.project.id = :projectId + """ + ) + fun getAllByProjectId(projectId: Long): List } diff --git a/backend/data/src/main/kotlin/io/tolgee/security/RequestContextService.kt b/backend/data/src/main/kotlin/io/tolgee/security/RequestContextService.kt index 33e8f58eb5..4ce216c969 100644 --- a/backend/data/src/main/kotlin/io/tolgee/security/RequestContextService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/security/RequestContextService.kt @@ -21,14 +21,17 @@ import io.tolgee.dtos.cacheable.ProjectDto import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.ProjectService +import jakarta.servlet.http.HttpServletRequest +import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import org.springframework.web.servlet.HandlerMapping -import javax.servlet.http.HttpServletRequest @Service class RequestContextService( private val authenticationFacade: AuthenticationFacade, + @Lazy private val projectService: ProjectService, + @Lazy private val organizationService: OrganizationService, ) { /** diff --git a/backend/data/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFacade.kt b/backend/data/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFacade.kt index 229a60497e..6683760e46 100644 --- a/backend/data/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFacade.kt +++ b/backend/data/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFacade.kt @@ -17,9 +17,13 @@ package io.tolgee.security.authentication import io.tolgee.constants.Message -import io.tolgee.dtos.cacheable.* +import io.tolgee.dtos.cacheable.ApiKeyDto +import io.tolgee.dtos.cacheable.PatDto +import io.tolgee.dtos.cacheable.UserAccountDto import io.tolgee.exceptions.AuthenticationException -import io.tolgee.model.* +import io.tolgee.model.ApiKey +import io.tolgee.model.Pat +import io.tolgee.model.UserAccount import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PatService import io.tolgee.service.security.UserAccountService diff --git a/backend/data/src/main/kotlin/io/tolgee/security/authentication/TolgeeAuthentication.kt b/backend/data/src/main/kotlin/io/tolgee/security/authentication/TolgeeAuthentication.kt index 9c03588a52..3c0d4ac461 100644 --- a/backend/data/src/main/kotlin/io/tolgee/security/authentication/TolgeeAuthentication.kt +++ b/backend/data/src/main/kotlin/io/tolgee/security/authentication/TolgeeAuthentication.kt @@ -17,7 +17,9 @@ package io.tolgee.security.authentication import io.tolgee.dtos.cacheable.UserAccountDto -import io.tolgee.model.* +import io.tolgee.model.ApiKey +import io.tolgee.model.Pat +import io.tolgee.model.UserAccount import org.springframework.security.core.Authentication import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority diff --git a/backend/data/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitService.kt b/backend/data/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitService.kt index 9519747663..00ba30ade5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitService.kt @@ -20,11 +20,11 @@ import io.tolgee.component.CurrentDateProvider import io.tolgee.component.LockingProvider import io.tolgee.configuration.tolgee.RateLimitProperties import io.tolgee.constants.Caches +import jakarta.servlet.http.HttpServletRequest import org.springframework.cache.Cache import org.springframework.cache.CacheManager import org.springframework.stereotype.Service import java.time.Duration -import javax.servlet.http.HttpServletRequest @Service class RateLimitService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt b/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt index 63b56fee0c..9bba1426c5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/AvatarService.kt @@ -6,12 +6,12 @@ import io.tolgee.constants.FileStoragePath import io.tolgee.dtos.Avatar import io.tolgee.model.ModelWithAvatar import io.tolgee.util.ImageConverter +import jakarta.xml.bind.DatatypeConverter import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.awt.Dimension import java.io.InputStream import java.security.MessageDigest -import javax.xml.bind.DatatypeConverter @Service class AvatarService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/InstanceIdService.kt b/backend/data/src/main/kotlin/io/tolgee/service/InstanceIdService.kt index b1015e0bc8..40375ae536 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/InstanceIdService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/InstanceIdService.kt @@ -3,10 +3,10 @@ package io.tolgee.service import io.tolgee.model.InstanceId import io.tolgee.util.executeInNewTransaction import io.tolgee.util.tryUntilItDoesntBreakConstraint +import jakarta.persistence.EntityManager import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @Service class InstanceIdService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/InvitationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/InvitationService.kt index d50bde1a5c..5c578fe869 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/InvitationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/InvitationService.kt @@ -19,6 +19,7 @@ import io.tolgee.repository.InvitationRepository import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.organization.OrganizationRoleService import io.tolgee.service.security.PermissionService +import io.tolgee.util.Logging import org.apache.commons.lang3.RandomStringUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -35,7 +36,7 @@ class InvitationService @Autowired constructor( private val permissionService: PermissionService, private val invitationEmailSender: InvitationEmailSender, private val businessEventPublisher: BusinessEventPublisher -) { +) : Logging { @Transactional fun create(params: CreateProjectInvitationParams): Invitation { return create(params) { invitation -> @@ -204,7 +205,9 @@ class InvitationService @Autowired constructor( } fun getForOrganization(organization: Organization): List { - return invitationRepository.getAllByOrganizationRoleOrganizationOrderByCreatedAt(organization) + return traceLogMeasureTime("get invitations for organization") { + invitationRepository.getAllForOrganization(organization) + } } private fun checkEmailNotAlreadyInvited(params: CreateProjectInvitationParams) { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt b/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt index 51e78ca6dd..2f05bf8cfa 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt @@ -7,12 +7,14 @@ import io.tolgee.model.Language import io.tolgee.model.Language.Companion.fromRequestDTO import io.tolgee.model.Project import io.tolgee.model.enums.Scope +import io.tolgee.model.views.LanguageView import io.tolgee.repository.LanguageRepository -import io.tolgee.service.machineTranslation.MtServiceConfigService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.PermissionService import io.tolgee.service.security.SecurityService +import io.tolgee.service.translation.AutoTranslationService import io.tolgee.service.translation.TranslationService +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page @@ -20,7 +22,6 @@ import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.* -import javax.persistence.EntityManager @Service class LanguageService( @@ -30,15 +31,13 @@ class LanguageService( private val permissionService: PermissionService, @Lazy private val securityService: SecurityService, + @Lazy + private val autoTranslationService: AutoTranslationService, ) { @set:Autowired @set:Lazy lateinit var translationService: TranslationService - @set:Autowired - @set:Lazy - lateinit var mtServiceConfigService: MtServiceConfigService - @Transactional fun createLanguage(dto: LanguageDto?, project: Project): Language { val language = fromRequestDTO(dto!!) @@ -93,6 +92,16 @@ class LanguageService( return find(id) ?: throw NotFoundException(Message.LANGUAGE_NOT_FOUND) } + @Transactional + fun getView(id: Long): LanguageView { + return findView(id) ?: throw NotFoundException(Message.LANGUAGE_NOT_FOUND) + } + + @Transactional + fun findView(id: Long): LanguageView? { + return languageRepository.findView(id) + } + fun find(id: Long): Language? { return languageRepository.findById(id).orElse(null) } @@ -164,9 +173,17 @@ class LanguageService( } fun deleteAllByProject(projectId: Long) { - findAll(projectId).forEach { - deleteLanguage(it.id) - } + translationService.deleteAllByProject(projectId) + autoTranslationService.deleteConfigsByProject(projectId) + entityManager.createNativeQuery( + "delete from language_stats " + + "where language_id in (select id from language where project_id = :projectId)" + ) + .setParameter("projectId", projectId) + .executeUpdate() + entityManager.createNativeQuery("DELETE FROM language WHERE project_id = :projectId") + .setParameter("projectId", projectId) + .executeUpdate() } fun save(language: Language): Language { @@ -177,7 +194,7 @@ class LanguageService( return this.languageRepository.saveAll(languages) } - fun getPaged(projectId: Long, pageable: Pageable): Page { + fun getPaged(projectId: Long, pageable: Pageable): Page { return this.languageRepository.findAllByProjectId(projectId, pageable) } @@ -188,4 +205,9 @@ class LanguageService( fun getLanguageIdsByTags(projectId: Long, languageTags: Collection): Map { return languageRepository.findAllByTagInAndProjectId(languageTags, projectId).associateBy { it.tag } } + + @Transactional + fun getViewsOfProjects(projectIds: List): List { + return languageRepository.getViewsOfProjects(projectIds) + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/QuickStartService.kt b/backend/data/src/main/kotlin/io/tolgee/service/QuickStartService.kt index a4358994af..ad93917f00 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/QuickStartService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/QuickStartService.kt @@ -25,7 +25,7 @@ class QuickStartService( fun completeStep(userAccount: UserAccountDto, step: String): QuickStart? { val quickStart = quickStartRepository.findByUserAccountId(userAccount.id) if (quickStart?.completedSteps?.let { !it.contains(step) } == true) { - quickStart.completedSteps.add(step) + quickStart.completedSteps = quickStart.completedSteps.plus(step) quickStartRepository.save(quickStart) } return quickStart diff --git a/backend/data/src/main/kotlin/io/tolgee/service/StartupImportService.kt b/backend/data/src/main/kotlin/io/tolgee/service/StartupImportService.kt index ef0c78f1fe..060ed8e317 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/StartupImportService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/StartupImportService.kt @@ -20,13 +20,13 @@ import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.UserAccountService import io.tolgee.util.Logging import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.springframework.context.ApplicationContext import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.File import java.util.* -import javax.persistence.EntityManager @Service class StartupImportService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/TelemetryService.kt b/backend/data/src/main/kotlin/io/tolgee/service/TelemetryService.kt index 1e428d932b..7b4d83a184 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/TelemetryService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/TelemetryService.kt @@ -3,11 +3,11 @@ package io.tolgee.service import io.tolgee.component.HttpClient import io.tolgee.configuration.tolgee.TelemetryProperties import io.tolgee.dtos.TelemetryReportRequest +import jakarta.persistence.EntityManager import org.springframework.http.HttpMethod import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @Service class TelemetryService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/automations/AutomationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/automations/AutomationService.kt index 43fc2d36dd..ded8e1c022 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/automations/AutomationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/automations/AutomationService.kt @@ -13,12 +13,12 @@ import io.tolgee.model.automations.AutomationTriggerType import io.tolgee.model.contentDelivery.ContentDeliveryConfig import io.tolgee.model.webhook.WebhookConfig import io.tolgee.repository.AutomationRepository +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional import org.springframework.cache.Cache import org.springframework.cache.CacheManager import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Service -import javax.persistence.EntityManager -import javax.transaction.Transactional @Service class AutomationService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/bigMeta/BigMetaService.kt b/backend/data/src/main/kotlin/io/tolgee/service/bigMeta/BigMetaService.kt index a53c84956a..5094c08471 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/bigMeta/BigMetaService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/bigMeta/BigMetaService.kt @@ -15,15 +15,15 @@ import io.tolgee.repository.KeysDistanceRepository import io.tolgee.util.equalNullable import io.tolgee.util.executeInNewTransaction import io.tolgee.util.runSentryCatching +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.JoinType import org.springframework.context.event.EventListener import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.CriteriaQuery -import javax.persistence.criteria.JoinType @Service class BigMetaService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/contentDelivery/ContentDeliveryConfigService.kt b/backend/data/src/main/kotlin/io/tolgee/service/contentDelivery/ContentDeliveryConfigService.kt index ee31ead898..d388505768 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/contentDelivery/ContentDeliveryConfigService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/contentDelivery/ContentDeliveryConfigService.kt @@ -13,12 +13,12 @@ import io.tolgee.repository.contentDelivery.ContentDeliveryConfigRepository import io.tolgee.service.automations.AutomationService import io.tolgee.service.project.ProjectService import io.tolgee.util.SlugGenerator +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service -import javax.persistence.EntityManager -import javax.transaction.Transactional import kotlin.random.Random @Service diff --git a/backend/data/src/main/kotlin/io/tolgee/service/dataImport/ImportService.kt b/backend/data/src/main/kotlin/io/tolgee/service/dataImport/ImportService.kt index 2ee20d5568..2e7ae1a23b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/dataImport/ImportService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/dataImport/ImportService.kt @@ -31,14 +31,13 @@ import io.tolgee.repository.dataImport.issues.ImportFileIssueParamRepository import io.tolgee.repository.dataImport.issues.ImportFileIssueRepository import io.tolgee.service.key.KeyMetaService import io.tolgee.util.getSafeNamespace -import org.hibernate.annotations.QueryHints +import jakarta.persistence.EntityManager import org.springframework.context.ApplicationContext import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.interceptor.TransactionInterceptor -import javax.persistence.EntityManager @Service @Transactional @@ -183,7 +182,6 @@ class ImportService( """ ) .setParameter("import", import) - .setHint(QueryHints.PASS_DISTINCT_THROUGH, false) .resultList as List result = entityManager.createQuery( @@ -195,7 +193,6 @@ class ImportService( where ik in :keys """ ).setParameter("keys", result) - .setHint(QueryHints.PASS_DISTINCT_THROUGH, false) .resultList as List return result diff --git a/backend/data/src/main/kotlin/io/tolgee/service/export/ExportService.kt b/backend/data/src/main/kotlin/io/tolgee/service/export/ExportService.kt index fd1ab71274..00e7f469e0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/export/ExportService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/export/ExportService.kt @@ -9,10 +9,10 @@ import io.tolgee.model.Language import io.tolgee.service.export.dataProvider.ExportDataProvider import io.tolgee.service.export.dataProvider.ExportTranslationView import io.tolgee.service.project.ProjectService +import jakarta.persistence.EntityManager import org.springframework.stereotype.Service import java.io.InputStream import java.time.Duration -import javax.persistence.EntityManager @Service class ExportService( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/export/dataProvider/ExportDataProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/export/dataProvider/ExportDataProvider.kt index f1ff10e116..9ffb9ca9d6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/export/dataProvider/ExportDataProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/export/dataProvider/ExportDataProvider.kt @@ -16,15 +16,15 @@ import io.tolgee.model.key.Tag import io.tolgee.model.key.Tag_ import io.tolgee.model.translation.Translation import io.tolgee.model.translation.Translation_ -import javax.persistence.EntityManager -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.CriteriaQuery -import javax.persistence.criteria.Join -import javax.persistence.criteria.JoinType -import javax.persistence.criteria.ListJoin -import javax.persistence.criteria.Predicate -import javax.persistence.criteria.Root -import javax.persistence.criteria.SetJoin +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.Join +import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.ListJoin +import jakarta.persistence.criteria.Predicate +import jakarta.persistence.criteria.Root +import jakarta.persistence.criteria.SetJoin class ExportDataProvider( private val entityManager: EntityManager, @@ -151,10 +151,7 @@ class ExportDataProvider( ): ListJoin { val translation = key.join(Key_.translations, JoinType.LEFT) translation.on( - cb.and( - cb.equal(key, translation.get(Translation_.key)), - cb.equal(language, translation.get(Translation_.language)) - ) + cb.equal(language, translation.get(Translation_.language)) ) return translation } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/export/exporters/XliffFileExporter.kt b/backend/data/src/main/kotlin/io/tolgee/service/export/exporters/XliffFileExporter.kt index 46adbe7a6e..e15ca18c96 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/export/exporters/XliffFileExporter.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/export/exporters/XliffFileExporter.kt @@ -89,7 +89,7 @@ class XliffFileExporter( return ResultItem(document, fileBodyElement) } - private fun String.parseHtml(): MutableIterator { + private fun String.parseHtml(): MutableIterator { val fragment = DocumentHelper .parseText("$this") return fragment.rootElement.nodeIterator() @@ -103,6 +103,7 @@ class XliffFileExporter( private fun Element.addFromHtmlOrText(string: String) { try { string.parseHtml().forEach { node -> + if (node !is Node) return@forEach node.parent = null this.add(node) } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/KeyMetaService.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/KeyMetaService.kt index c32fd01cc2..224fd08dae 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/KeyMetaService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/KeyMetaService.kt @@ -10,11 +10,11 @@ import io.tolgee.model.key.WithKeyMetaReference import io.tolgee.repository.KeyCodeReferenceRepository import io.tolgee.repository.KeyCommentRepository import io.tolgee.repository.KeyMetaRepository -import org.hibernate.annotations.QueryHints.PASS_DISTINCT_THROUGH +import io.tolgee.util.Logging +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service -import javax.persistence.EntityManager @Service class KeyMetaService( @@ -22,7 +22,7 @@ class KeyMetaService( private val keyCodeReferenceRepository: KeyCodeReferenceRepository, private val keyCommentRepository: KeyCommentRepository, private val entityManager: EntityManager, -) { +) : Logging { @set:Autowired @set:Lazy lateinit var tagService: TagService @@ -68,7 +68,6 @@ class KeyMetaService( """ ) .setParameter("import", import) - .setHint(PASS_DISTINCT_THROUGH, false) .resultList as List result = entityManager.createQuery( @@ -80,7 +79,6 @@ class KeyMetaService( where ikm in :metas """ ).setParameter("metas", result) - .setHint(PASS_DISTINCT_THROUGH, false) .resultList as List return result @@ -96,7 +94,6 @@ class KeyMetaService( """ ) .setParameter("project", project) - .setHint(PASS_DISTINCT_THROUGH, false) .resultList as List result = entityManager.createQuery( @@ -107,7 +104,6 @@ class KeyMetaService( where ikm in :metas """ ).setParameter("metas", result) - .setHint(PASS_DISTINCT_THROUGH, false) .resultList as List return result @@ -140,9 +136,40 @@ class KeyMetaService( } fun deleteAllByKeyId(id: Long) { - tagService.deleteAllByKeyIdIn(listOf(id)) - keyCommentRepository.deleteAllByKeyId(id) - keyCodeReferenceRepository.deleteAllByKeyId(id) - this.keyMetaRepository.deleteAllByKeyId(id) + deleteAllByKeyIdIn(listOf(id)) + } + + fun deleteAllByProject(projectId: Long) { + tagService.deleteAllByProject(projectId) + entityManager.createNativeQuery( + """ + delete from key_comment where key_meta_id in ( + select id from key_meta where key_id in ( + select id from key where project_id = :projectId + ) + ) + """ + ).setParameter("projectId", projectId) + .executeUpdate() + + entityManager.createNativeQuery( + """ + delete from key_code_reference where key_meta_id in ( + select id from key_meta where key_id in ( + select id from key where project_id = :projectId + ) + ) + """ + ).setParameter("projectId", projectId) + .executeUpdate() + + entityManager.createNativeQuery( + """ + delete from key_meta where key_id in ( + select id from key where project_id = :projectId + ) + """ + ).setParameter("projectId", projectId) + .executeUpdate() } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt index dcbbf8b27e..46b49cf8dd 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt @@ -21,7 +21,9 @@ import io.tolgee.repository.LanguageRepository import io.tolgee.service.key.utils.KeyInfoProvider import io.tolgee.service.key.utils.KeysImporter import io.tolgee.service.translation.TranslationService +import io.tolgee.util.Logging import io.tolgee.util.setSimilarityLimit +import jakarta.persistence.EntityManager import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page @@ -30,7 +32,6 @@ import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.* -import javax.persistence.EntityManager @Service class KeyService( @@ -44,8 +45,7 @@ class KeyService( @Lazy private var translationService: TranslationService, private val languageRepository: LanguageRepository -) { - +) : Logging { fun getAll(projectId: Long): Set { return keyRepository.getAllByProjectId(projectId) } @@ -217,20 +217,41 @@ class KeyService( @Transactional fun deleteMultiple(ids: Collection) { - translationService.deleteAllByKeys(ids) - keyMetaService.deleteAllByKeyIdIn(ids) - screenshotService.deleteAllByKeyId(ids) - val keys = keyRepository.findAllByIdInForDelete(ids) + traceLogMeasureTime("delete multiple keys: delete translations") { + translationService.deleteAllByKeys(ids) + } + + traceLogMeasureTime("delete multiple keys: delete key metas") { + keyMetaService.deleteAllByKeyIdIn(ids) + } + + traceLogMeasureTime("delete multiple keys: delete screenshots") { + screenshotService.deleteAllByKeyId(ids) + } + + val keys = traceLogMeasureTime("delete multiple keys: fetch keys") { + keyRepository.findAllByIdInForDelete(ids) + } + val namespaces = keys.map { it.namespace } - keyRepository.deleteAllByIdIn(keys.map { it.id }) + + traceLogMeasureTime("delete multiple keys: delete the keys") { + keyRepository.deleteAll(keys) + } + namespaceService.deleteUnusedNamespaces(namespaces) } @Transactional fun deleteAllByProject(projectId: Long) { - val ids = keyRepository.getIdsByProjectId(projectId) - keyMetaService.deleteAllByKeyIdIn(ids) - this.deleteMultiple(ids) + keyMetaService.deleteAllByProject(projectId) + screenshotService.deleteAllByProject(projectId) + + entityManager.createNativeQuery("""delete from key where project_id = :projectId""") + .setParameter("projectId", projectId) + .executeUpdate() + + namespaceService.deleteAllByProject(projectId) } fun checkInProject(key: Key, projectId: Long) { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/NamespaceService.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/NamespaceService.kt index 55f07da5a1..dd4aedc8a1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/NamespaceService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/NamespaceService.kt @@ -9,9 +9,9 @@ import io.tolgee.model.key.Namespace import io.tolgee.repository.NamespaceRepository import io.tolgee.util.getSafeNamespace import io.tolgee.util.tryUntilItDoesntBreakConstraint +import jakarta.persistence.EntityManager import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service -import javax.persistence.EntityManager @Service class NamespaceService( @@ -127,4 +127,8 @@ class NamespaceService( namespace.name = dto.name!! return save(namespace) } + + fun deleteAllByProject(projectId: Long) { + namespaceRepository.deleteAllByProjectId(projectId) + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/ResolvingKeyImporter.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/ResolvingKeyImporter.kt index 4535761cea..411711306b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/ResolvingKeyImporter.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/ResolvingKeyImporter.kt @@ -27,11 +27,11 @@ import io.tolgee.service.LanguageService import io.tolgee.service.security.SecurityService import io.tolgee.service.translation.TranslationService import io.tolgee.util.equalNullable +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.Join +import jakarta.persistence.criteria.JoinType import org.springframework.context.ApplicationContext import java.io.Serializable -import javax.persistence.EntityManager -import javax.persistence.criteria.Join -import javax.persistence.criteria.JoinType class ResolvingKeyImporter( val applicationContext: ApplicationContext, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/ScreenshotService.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/ScreenshotService.kt index 6bfc9319f5..1bb266d045 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/ScreenshotService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/ScreenshotService.kt @@ -23,11 +23,11 @@ import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.ImageUploadService import io.tolgee.service.ImageUploadService.Companion.UPLOADED_IMAGES_STORAGE_FOLDER_NAME import io.tolgee.util.ImageConverter +import jakarta.persistence.EntityManager import org.springframework.core.io.InputStreamSource import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.awt.Dimension -import javax.persistence.EntityManager import kotlin.math.roundToInt @Service @@ -198,6 +198,7 @@ class ScreenshotService( image?.let { fileStorage.storeFile(screenshot.getFilePath(), it) } } + @Transactional fun findAll(key: Key): List { return screenshotRepository.findAllByKey(key) } @@ -219,10 +220,12 @@ class ScreenshotService( removeScreenshotReferences(key, listOf(screenshot)) } + @Transactional fun removeScreenshotReferences(key: Key, screenshots: List) { removeScreenshotReferencesById(key, screenshots.map { it.id }) } + @Transactional fun removeScreenshotReferencesById(key: Key, screenshotIds: List?) { screenshotIds ?: return val references = keyScreenshotReferenceRepository.findAll(key, screenshotIds) @@ -237,6 +240,7 @@ class ScreenshotService( } } + @Transactional fun removeScreenshotReferences(references: List) { val screenshotIds = references.map { it.screenshot.id }.toSet() keyScreenshotReferenceRepository.deleteAll(references) @@ -265,7 +269,26 @@ class ScreenshotService( fun deleteAllByProject(projectId: Long) { val all = screenshotRepository.getAllByKeyProjectId(projectId) all.forEach { this.deleteFile(it) } - screenshotRepository.deleteAll(all) + + entityManager.createNativeQuery( + """ + DELETE FROM key_screenshot_reference WHERE key_id IN ( + SELECT id FROM key WHERE project_id = :projectId + ) + """ + ).setParameter("projectId", projectId) + .executeUpdate() + + entityManager.createNativeQuery( + """ + DELETE FROM screenshot WHERE id IN ( + SELECT screenshot_id FROM key_screenshot_reference WHERE key_id IN ( + SELECT id FROM key WHERE project_id = :projectId + ) + ) + """ + ).setParameter("projectId", projectId) + .executeUpdate() } fun deleteAllByKeyId(keyId: Long) { @@ -301,33 +324,13 @@ class ScreenshotService( return screenshotRepository.getKeysWithScreenshots(ids) } - fun getScreenshotReferences(screenshots: Collection): List { - return screenshotRepository.getScreenshotReferences(screenshots) - } - fun saveAllReferences(data: List) { keyScreenshotReferenceRepository.saveAll(data) } fun getScreenshotsForKeys(keyIds: Collection): Map> { - val keys = this.getKeysWithScreenshots(keyIds).toSet() - - val allScreenshots = keys - .flatMap { key -> - key.keyScreenshotReferences.map { scr -> scr.screenshot } - } - .toSet() // remove dupes - .let { - screenshotRepository.getScreenshotsWithReferences(it) - }.toSet() - - val keyIdScreenshotsMap = allScreenshots - .flatMap { it.keyScreenshotReferences } - .groupBy { it.key.id } - - return keys.associate { - it.id to (keyIdScreenshotsMap[it.id]?.map { it.screenshot } ?: emptyList()) - } + return this.getKeysWithScreenshots(keyIds) + .associate { it.id to it.keyScreenshotReferences.map { it.screenshot }.toSet().toList() } } fun getKeyScreenshotReferences(importedKeys: List, locations: List): List { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/TagService.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/TagService.kt index 55beaa92e6..f4597d9d3d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/TagService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/TagService.kt @@ -8,6 +8,8 @@ import io.tolgee.model.dataImport.WithKeyMeta import io.tolgee.model.key.Key import io.tolgee.model.key.Tag import io.tolgee.repository.TagRepository +import io.tolgee.util.Logging +import jakarta.persistence.EntityManager import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -19,11 +21,12 @@ class TagService( private val tagRepository: TagRepository, private val keyMetaService: KeyMetaService, @Lazy - private val keyService: KeyService -) { + private val keyService: KeyService, + private val entityManager: EntityManager +) : Logging { fun tagKey(key: Key, tagName: String): Tag { val keyMeta = keyMetaService.getOrCreateForKey(key) - val tag = find(key.project!!, tagName)?.let { + val tag = find(key.project, tagName)?.let { if (!keyMeta.tags.contains(it)) { it.keyMetas.add(keyMeta) keyMeta.tags.add(it) @@ -31,7 +34,7 @@ class TagService( it } ?: let { Tag().apply { - project = key.project!! + project = key.project keyMetas.add(keyMeta) name = tagName keyMeta.tags.add(this) @@ -208,14 +211,21 @@ class TagService( } fun deleteAllByKeyIdIn(keyIds: Collection) { - val keys = tagRepository.getKeysWithTags(keyIds) + val keys = traceLogMeasureTime("tagService: deleteAllByKeyIdIn: getKeysWithTags") { + tagRepository.getKeysWithTags(keyIds) + } deleteAllTagsForKeys(keys) } private fun deleteAllTagsForKeys(keys: Iterable) { val tagIds = keys.flatMap { it.keyMeta?.tags?.map { it.id } ?: listOf() }.toSet() // get tags with fetched keyMetas - val tagKeyMetasMap = tagRepository.getTagsWithKeyMetas(tagIds).map { it.id to it.keyMetas }.toMap() + val tagKeyMetasMap = + traceLogMeasureTime("tagService: deleteAllTagsForKeys: getTagsWithKeyMetas") { + tagRepository.getTagsWithKeyMetas(tagIds).associate { + it.id to it.keyMetas + } + } keys.forEach { key -> key.keyMeta?.let { keyMeta -> keyMeta.tags.forEach { tag -> @@ -231,4 +241,22 @@ class TagService( } } } + + /** + * We don't need to store history or handle events when deleting whole project. + * So we can go for native query. + */ + fun deleteAllByProject(projectId: Long) { + entityManager.createNativeQuery( + """ + delete from key_meta_tags kmt + where kmt.key_metas_id in + (select km.id from key_meta km join key k on km.key_id = k.id where k.project_id = :projectId)""" + ).setParameter("projectId", projectId).executeUpdate() + entityManager.createNativeQuery( + """ + delete from tag where project_id = :projectId + """ + ).setParameter("projectId", projectId).executeUpdate() + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/key/utils/KeyInfoProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/key/utils/KeyInfoProvider.kt index 111b36b2c0..077e3e4c14 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/key/utils/KeyInfoProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/key/utils/KeyInfoProvider.kt @@ -11,11 +11,11 @@ import io.tolgee.model.key.Namespace_ import io.tolgee.service.key.ScreenshotService import io.tolgee.service.translation.TranslationService import io.tolgee.util.equalNullable +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.Join +import jakarta.persistence.criteria.JoinType import org.springframework.context.ApplicationContext -import javax.persistence.EntityManager -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.Join -import javax.persistence.criteria.JoinType class KeyInfoProvider( applicationContext: ApplicationContext, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtCreditBucketService.kt b/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtCreditBucketService.kt index 31f872e004..d3d6d5a517 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtCreditBucketService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtCreditBucketService.kt @@ -21,6 +21,7 @@ import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional import java.util.* +import kotlin.time.ExperimentalTime import kotlin.time.measureTime @Service @@ -62,6 +63,7 @@ class MtCreditBucketService( } @Transactional(noRollbackFor = [OutOfCreditsException::class]) + @ExperimentalTime fun checkPositiveBalance(project: Project) { lockingProvider.withLocking(getMtCreditBucketLockName(project)) { tryUntilItDoesntBreakConstraint { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtServiceConfigService.kt b/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtServiceConfigService.kt index 146c1c12a9..279debc126 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtServiceConfigService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/MtServiceConfigService.kt @@ -245,6 +245,7 @@ class MtServiceConfigService( } } + @Transactional fun getProjectSettings(project: Project): List { return getStoredConfigs(project.id) .sortedBy { it.targetLanguage?.tag } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt index 22292418c5..e1b0e15b44 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt @@ -1,11 +1,13 @@ package io.tolgee.service.organization +import io.tolgee.component.CurrentDateProvider import io.tolgee.configuration.tolgee.TolgeeProperties import io.tolgee.constants.Caches import io.tolgee.constants.Message import io.tolgee.dtos.request.organization.OrganizationDto import io.tolgee.dtos.request.organization.OrganizationRequestParamsDto import io.tolgee.dtos.request.validators.exceptions.ValidationException +import io.tolgee.events.BeforeOrganizationDeleteEvent import io.tolgee.exceptions.NotFoundException import io.tolgee.model.Organization import io.tolgee.model.Permission @@ -20,6 +22,7 @@ import io.tolgee.service.InvitationService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.PermissionService import io.tolgee.service.security.UserPreferencesService +import io.tolgee.util.Logging import io.tolgee.util.SlugGenerator import org.springframework.beans.factory.annotation.Autowired import org.springframework.cache.Cache @@ -27,11 +30,11 @@ import org.springframework.cache.CacheManager import org.springframework.cache.annotation.CacheEvict import org.springframework.cache.annotation.Cacheable import org.springframework.cache.annotation.Caching +import org.springframework.context.ApplicationEventPublisher import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable -import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.InputStream @@ -51,7 +54,9 @@ class OrganizationService( private val tolgeeProperties: TolgeeProperties, private val permissionService: PermissionService, private val cacheManager: CacheManager, -) { + private val currentDateProvider: CurrentDateProvider, + private val eventPublisher: ApplicationEventPublisher +) : Logging { private val cache: Cache? by lazy { cacheManager.getCache(Caches.ORGANIZATIONS) } @set:Autowired @@ -161,19 +166,19 @@ class OrganizationService( } fun get(id: Long): Organization { - return organizationRepository.findByIdOrNull(id) ?: throw NotFoundException(Message.ORGANIZATION_NOT_FOUND) + return organizationRepository.find(id) ?: throw NotFoundException(Message.ORGANIZATION_NOT_FOUND) } fun find(id: Long): Organization? { - return organizationRepository.findByIdOrNull(id) + return organizationRepository.find(id) } fun get(slug: String): Organization { - return organizationRepository.getOneBySlug(slug) ?: throw NotFoundException(Message.ORGANIZATION_NOT_FOUND) + return find(slug) ?: throw NotFoundException(Message.ORGANIZATION_NOT_FOUND) } fun find(slug: String): Organization? { - return organizationRepository.getOneBySlug(slug) + return organizationRepository.findBySlug(slug) } @Cacheable(cacheNames = [Caches.ORGANIZATIONS], key = "{'id', #id}") @@ -214,17 +219,10 @@ class OrganizationService( ] ) fun delete(organization: Organization) { - projectService.findAllInOrganization(organization.id).forEach { - projectService.deleteProject(it.id) - } - - invitationService.getForOrganization(organization).forEach { invitation -> - invitationService.delete(invitation) - } - - // `get` is important to help reducing the likelihood of a race-condition - // One may still occur, as a con of not relying on a DB-level cascade delete logic. - get(organization.id).preferredBy + organization.deletedAt = currentDateProvider.date + save(organization) + eventPublisher.publishEvent(BeforeOrganizationDeleteEvent(organization)) + organization.preferredBy .toList() // we need to clone it so hibernate doesn't change it concurrently .forEach { it.preferredOrganization = findOrCreatePreferred( @@ -233,11 +231,33 @@ class OrganizationService( ) userPreferencesService.save(it) } + } - organizationRoleService.deleteAllInOrganization(organization) + @Transactional + fun deleteHard(organization: Organization) { + traceLogMeasureTime("deleteProjects") { + projectService.findAllInOrganization(organization.id).forEach { + projectService.deleteProject(it.id) + } + } - this.organizationRepository.delete(organization) - avatarService.unlinkAvatarFiles(organization) + traceLogMeasureTime("deleteInvitations") { + invitationService.getForOrganization(organization).forEach { invitation -> + invitationService.delete(invitation) + } + } + + traceLogMeasureTime("deleteOrganizationRoles") { + organizationRoleService.deleteAllInOrganization(organization) + } + + traceLogMeasureTime("deleteTheOrganization") { + this.organizationRepository.fetchData(organization) + this.organizationRepository.delete(organization) + } + traceLogMeasureTime("unlinkAvatarFiles") { + avatarService.unlinkAvatarFiles(organization) + } } @Transactional @@ -263,11 +283,11 @@ class OrganizationService( } /** - * Checks address part uniqueness + * Checks slug uniqueness * @return Returns true if valid */ fun validateSlugUniqueness(slug: String): Boolean { - return organizationRepository.countAllBySlug(slug) < 1 + return !organizationRepository.organizationWithSlugExists(slug) } fun isThereAnotherOwner(id: Long): Boolean { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt index aa85378ee4..4660c11f93 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt @@ -1,8 +1,8 @@ package io.tolgee.service.organization +import jakarta.persistence.EntityManager import org.springframework.stereotype.Service import java.math.BigDecimal -import javax.persistence.EntityManager @Service class OrganizationStatsService( @@ -13,7 +13,7 @@ class OrganizationStatsService( .createQuery( """ select count(l) from Language l - where l.project.id = :projectId + where l.project.id = :projectId and l.project.deletedAt is null """.trimIndent() ) .setParameter("projectId", projectId) @@ -25,7 +25,7 @@ class OrganizationStatsService( .createQuery( """ select count(k) from Key k - where k.project.id = :projectId + where k.project.id = :projectId and k.project.deletedAt is null """.trimIndent() ) .setParameter("projectId", projectId) @@ -40,12 +40,12 @@ class OrganizationStatsService( from (select p.id as projectId, count(l.id) as languageCount from project as p join language as l on l.project_id = p.id - where p.organization_owner_id = :organizationId + where p.organization_owner_id = :organizationId and p.deleted_at is null group by p.id) as languageCounts join (select p.id as projectId, count(k.id) as keyCount from project as p join key as k on k.project_id = p.id - where p.organization_owner_id = :organizationId + where p.organization_owner_id = :organizationId and p.deleted_at is null group by p.id) as keyCounts on keyCounts.projectId = languageCounts.projectId) """.trimIndent() ).setParameter("organizationId", organizationId).singleResult as BigDecimal? ?: 0 @@ -57,7 +57,7 @@ class OrganizationStatsService( """ select count(t) from Translation t where t.key.project.organizationOwner.id = :organizationId and - t.state <> io.tolgee.model.enums.TranslationState.UNTRANSLATED + t.state <> io.tolgee.model.enums.TranslationState.UNTRANSLATED and t.key.project.deletedAt is null """.trimIndent() ).setParameter("organizationId", organizationId).singleResult as Long? ?: 0 } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/LanguageStatsService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/LanguageStatsService.kt index 76a593eab2..ebdd31eb18 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/LanguageStatsService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/LanguageStatsService.kt @@ -11,11 +11,11 @@ import io.tolgee.util.Logging import io.tolgee.util.debug import io.tolgee.util.executeInNewRepeatableTransaction import io.tolgee.util.logger +import jakarta.persistence.EntityManager import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.TransactionDefinition import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager @Transactional @Service diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt index 7239087709..507b487f71 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt @@ -2,6 +2,7 @@ package io.tolgee.service.project import io.tolgee.activity.ActivityHolder import io.tolgee.batch.BatchJobService +import io.tolgee.component.CurrentDateProvider import io.tolgee.constants.Caches import io.tolgee.constants.Message import io.tolgee.dtos.cacheable.ProjectDto @@ -35,7 +36,9 @@ import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PermissionService import io.tolgee.service.security.SecurityService import io.tolgee.service.translation.TranslationService +import io.tolgee.util.Logging import io.tolgee.util.SlugGenerator +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.cache.annotation.CacheEvict import org.springframework.cache.annotation.Cacheable @@ -43,11 +46,9 @@ import org.springframework.context.annotation.Lazy import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable -import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.InputStream -import javax.persistence.EntityManager @Transactional @Service @@ -62,8 +63,9 @@ class ProjectService( @Lazy private val projectHolder: ProjectHolder, @Lazy - private val batchJobService: BatchJobService -) { + private val batchJobService: BatchJobService, + private val currentDateProvider: CurrentDateProvider +) : Logging { @set:Autowired @set:Lazy lateinit var keyService: KeyService @@ -107,7 +109,7 @@ class ProjectService( @Transactional @Cacheable(cacheNames = [Caches.PROJECTS], key = "#id") fun findDto(id: Long): ProjectDto? { - return projectRepository.findById(id).orElse(null)?.let { + return projectRepository.find(id)?.let { ProjectDto.fromEntity(it) } } @@ -119,11 +121,11 @@ class ProjectService( } fun get(id: Long): Project { - return projectRepository.findByIdOrNull(id) ?: throw NotFoundException(Message.PROJECT_NOT_FOUND) + return find(id) ?: throw NotFoundException(Message.PROJECT_NOT_FOUND) } fun find(id: Long): Project? { - return projectRepository.findByIdOrNull(id) + return projectRepository.find(id) } @Transactional @@ -207,10 +209,13 @@ class ProjectService( return this.projectRepository.findAllByOrganizationOwnerId(organizationId) } - fun addPermittedLanguagesToProjects(projectsPage: Page): Page { + private fun addPermittedLanguagesToProjects( + projectsPage: Page, + userId: Long + ): Page { val projectLanguageMap = permissionService.getPermittedTranslateLanguagesForProjectIds( projectsPage.content.map { it.id }, - authenticationFacade.authenticatedUser.id + userId ) val newContent = projectsPage.content.map { ProjectWithLanguagesView.fromProjectView(it, projectLanguageMap[it.id]) @@ -219,37 +224,61 @@ class ProjectService( return PageImpl(newContent, projectsPage.pageable, projectsPage.totalElements) } - fun getProjectsWithFetchedLanguages(projectIds: Iterable): List { - return projectRepository.getWithLanguages(projectIds) - } - @Transactional @CacheEvict(cacheNames = [Caches.PROJECTS], key = "#id") fun deleteProject(id: Long) { val project = get(id) + project.deletedAt = currentDateProvider.date + save(project) + } - try { - projectHolder.project - } catch (e: ProjectNotSelectedException) { - projectHolder.project = ProjectDto.fromEntity(project) - } + @Transactional + @CacheEvict(cacheNames = [Caches.PROJECTS], key = "#id") + fun hardDeleteProject(id: Long) { + traceLogMeasureTime("deleteProject") { + val project = get(id) + + try { + projectHolder.project + } catch (e: ProjectNotSelectedException) { + projectHolder.project = ProjectDto.fromEntity(project) + } - importService.getAllByProject(id).forEach { - importService.deleteImport(it) - } + importService.getAllByProject(id).forEach { + importService.deleteImport(it) + } + + // otherwise we cannot delete the languages + project.baseLanguage = null + projectRepository.saveAndFlush(project) + + traceLogMeasureTime("deleteProject: delete api keys") { + apiKeyService.deleteAllByProject(project.id) + } + + traceLogMeasureTime("deleteProject: delete permissions") { + permissionService.deleteAllByProject(project.id) + } + + traceLogMeasureTime("deleteProject: delete screenshots") { + screenshotService.deleteAllByProject(project.id) + } + + mtServiceConfigService.deleteAllByProjectId(project.id) + + traceLogMeasureTime("deleteProject: delete languages") { + languageService.deleteAllByProject(project.id) + } + + traceLogMeasureTime("deleteProject: delete keys") { + keyService.deleteAllByProject(project.id) + } - // otherwise we cannot delete the languages - project.baseLanguage = null - projectRepository.saveAndFlush(project) - apiKeyService.deleteAllByProject(project.id) - permissionService.deleteAllByProject(project.id) - screenshotService.deleteAllByProject(project.id) - languageService.deleteAllByProject(project.id) - keyService.deleteAllByProject(project.id) - avatarService.unlinkAvatarFiles(project) - batchJobService.deleteAllByProjectId(project.id) - bigMetaService.deleteAllByProjectId(project.id) - projectRepository.delete(project) + avatarService.unlinkAvatarFiles(project) + batchJobService.deleteAllByProjectId(project.id) + bigMetaService.deleteAllByProjectId(project.id) + projectRepository.delete(project) + } } /** @@ -311,14 +340,28 @@ class ProjectService( pageable: Pageable, search: String?, organizationId: Long? = null + ): Page { + return findPermittedInOrganizationPaged( + pageable = pageable, + search = search, + organizationId = organizationId, + userAccountId = authenticationFacade.authenticatedUser.id + ) + } + + fun findPermittedInOrganizationPaged( + pageable: Pageable, + search: String?, + organizationId: Long? = null, + userAccountId: Long ): Page { val withoutPermittedLanguages = projectRepository.findAllPermitted( - authenticationFacade.authenticatedUser.id, + userAccountId, pageable, search, organizationId ) - return addPermittedLanguagesToProjects(withoutPermittedLanguages) + return addPermittedLanguagesToProjects(withoutPermittedLanguages, userAccountId) } @CacheEvict(cacheNames = [Caches.PROJECTS], allEntries = true) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectStatsService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectStatsService.kt index e0e234a3e3..89e794f7ce 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectStatsService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectStatsService.kt @@ -7,11 +7,11 @@ import io.tolgee.model.Project_ import io.tolgee.model.views.projectStats.ProjectStatsView import io.tolgee.repository.activity.ActivityRevisionRepository import io.tolgee.service.query_builders.ProjectStatsProvider +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.JoinType import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate -import javax.persistence.EntityManager -import javax.persistence.criteria.JoinType @Transactional @Service diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/LanguageStatsProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/LanguageStatsProvider.kt index ee4c06869e..170bdc6954 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/LanguageStatsProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/LanguageStatsProvider.kt @@ -9,14 +9,14 @@ import io.tolgee.model.key.Key_ import io.tolgee.model.translation.Translation import io.tolgee.model.translation.Translation_ import io.tolgee.model.views.projectStats.ProjectLanguageStatsResultView -import javax.persistence.EntityManager -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.CriteriaQuery -import javax.persistence.criteria.Expression -import javax.persistence.criteria.JoinType -import javax.persistence.criteria.ListJoin -import javax.persistence.criteria.Root -import javax.persistence.criteria.Selection +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.ListJoin +import jakarta.persistence.criteria.Root +import jakarta.persistence.criteria.Selection open class LanguageStatsProvider( val entityManager: EntityManager, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/ProjectStatsProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/ProjectStatsProvider.kt index 33038efe8d..14620a7ddf 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/ProjectStatsProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/ProjectStatsProvider.kt @@ -13,10 +13,10 @@ import io.tolgee.model.key.Tag import io.tolgee.model.key.Tag_ import io.tolgee.model.views.projectStats.ProjectStatsView import io.tolgee.util.KotlinCriteriaBuilder -import javax.persistence.EntityManager -import javax.persistence.criteria.JoinType -import javax.persistence.criteria.Root -import javax.persistence.criteria.Selection +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.Root +import jakarta.persistence.criteria.Selection open class ProjectStatsProvider( val entityManager: EntityManager, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/CursorPredicateProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/CursorPredicateProvider.kt index 138d0efc7c..9fbe9b2b66 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/CursorPredicateProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/CursorPredicateProvider.kt @@ -8,11 +8,11 @@ import io.tolgee.util.greaterThanNullable import io.tolgee.util.greaterThanOrEqualToNullable import io.tolgee.util.lessThanNullable import io.tolgee.util.lessThanOrEqualToNullable +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.Predicate +import jakarta.persistence.criteria.Selection import org.springframework.data.domain.Sort -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.Expression -import javax.persistence.criteria.Predicate -import javax.persistence.criteria.Selection class CursorPredicateProvider( private val cb: CriteriaBuilder, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryBase.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryBase.kt index 26752e2f9d..8de494f7a9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryBase.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryBase.kt @@ -18,14 +18,15 @@ import io.tolgee.model.translation.TranslationComment_ import io.tolgee.model.translation.Translation_ import io.tolgee.model.views.KeyWithTranslationsView import io.tolgee.model.views.TranslationView -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.CriteriaQuery -import javax.persistence.criteria.Expression -import javax.persistence.criteria.JoinType -import javax.persistence.criteria.ListJoin -import javax.persistence.criteria.Path -import javax.persistence.criteria.Predicate -import javax.persistence.criteria.Root +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.ListJoin +import jakarta.persistence.criteria.Path +import jakarta.persistence.criteria.Predicate +import jakarta.persistence.criteria.Root +import jakarta.persistence.criteria.Subquery class QueryBase( private val cb: CriteriaBuilder, @@ -141,12 +142,19 @@ class QueryBase( val screenshotRoot = screenshotSubquery.from(Screenshot::class.java) val screenshotCount = cb.count(screenshotRoot.get(Screenshot_.id)) screenshotSubquery.select(screenshotCount) - val referencesJoin = screenshotRoot.join(Screenshot_.keyScreenshotReferences) - screenshotSubquery.where(cb.equal(this.root, referencesJoin.get(KeyScreenshotReference_.key))) + screenshotSubquery.where(screenshotRoot.get(Screenshot_.id).`in`(getScreenshotIdFilterSubquery())) screenshotCountExpression = screenshotSubquery.selection this.querySelection[KeyWithTranslationsView::screenshotCount.name] = screenshotCountExpression } + private fun getScreenshotIdFilterSubquery(): Subquery { + val subquery = this.query.subquery(Long::class.java) + val subQueryRoot = subquery.from(Key::class.java) + val keyScreenshotReference = subQueryRoot.join(Key_.keyScreenshotReferences) + subquery.where(cb.equal(subQueryRoot.get(Key_.id), this.root.get(Key_.id))) + return subquery.select(keyScreenshotReference.get(KeyScreenshotReference_.screenshot).get(Screenshot_.id)) + } + private fun addNotFilteringTranslationFields( language: Language, translation: ListJoin diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryGlobalFiltering.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryGlobalFiltering.kt index 957c7d5c0c..112e2147e7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryGlobalFiltering.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryGlobalFiltering.kt @@ -4,10 +4,10 @@ import io.tolgee.dtos.request.translation.TranslationFilters import io.tolgee.model.key.KeyMeta_ import io.tolgee.model.key.Key_ import io.tolgee.model.key.Tag_ +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.Predicate import java.util.* -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.JoinType -import javax.persistence.criteria.Predicate class QueryGlobalFiltering( private val params: TranslationFilters, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QuerySelection.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QuerySelection.kt index c5ddb00d97..f8c94c0a27 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QuerySelection.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QuerySelection.kt @@ -3,7 +3,7 @@ package io.tolgee.service.query_builders.translationViewBuilder import io.tolgee.model.Language import io.tolgee.model.views.KeyWithTranslationsView import io.tolgee.model.views.TranslationView -import javax.persistence.criteria.Selection +import jakarta.persistence.criteria.Selection import kotlin.reflect.KProperty1 class QuerySelection : LinkedHashMap>() { diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryTranslationFiltering.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryTranslationFiltering.kt index 4222d5eab2..77a0eb19c6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryTranslationFiltering.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/QueryTranslationFiltering.kt @@ -4,10 +4,10 @@ import io.tolgee.dtos.request.translation.TranslationFilterByState import io.tolgee.dtos.request.translation.TranslationFilters import io.tolgee.model.Language import io.tolgee.model.enums.TranslationState -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.Expression -import javax.persistence.criteria.Path -import javax.persistence.criteria.Predicate +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.Path +import jakarta.persistence.criteria.Predicate class QueryTranslationFiltering( private val params: TranslationFilters, diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationViewDataProvider.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationViewDataProvider.kt index dfe1a38ab7..b68bc9fc54 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationViewDataProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationViewDataProvider.kt @@ -5,12 +5,12 @@ import io.tolgee.model.Language import io.tolgee.model.views.KeyWithTranslationsView import io.tolgee.service.key.TagService import io.tolgee.service.query_builders.CursorUtil +import jakarta.persistence.EntityManager import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class TranslationViewDataProvider( diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationsViewQueryBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationsViewQueryBuilder.kt index 2e1ef22462..3730852fc9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationsViewQueryBuilder.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/translationViewBuilder/TranslationsViewQueryBuilder.kt @@ -3,9 +3,11 @@ package io.tolgee.service.query_builders.translationViewBuilder import io.tolgee.dtos.request.translation.TranslationFilters import io.tolgee.dtos.response.CursorValue import io.tolgee.model.* +import jakarta.persistence.criteria.* +import org.hibernate.query.NullPrecedence +import org.hibernate.query.sqm.tree.select.SqmSortSpecification import org.springframework.data.domain.* import java.util.* -import javax.persistence.criteria.* class TranslationsViewQueryBuilder( private val cb: CriteriaBuilder, @@ -32,17 +34,7 @@ class TranslationsViewQueryBuilder( val queryBase = getBaseQuery(query) val paths = queryBase.querySelection.values.toTypedArray() query.multiselect(*paths) - val orderList = sort.asSequence().filter { queryBase.querySelection[it.property] != null }.map { - val expression = queryBase.querySelection[it.property] as Expression<*> - when (it.direction) { - Sort.Direction.DESC -> cb.desc(expression) - else -> cb.asc(expression) - } - }.toMutableList() - - if (orderList.isEmpty()) { - orderList.add(cb.asc(queryBase.keyNameExpression)) - } + val orderList = getOrderList(queryBase) val where = queryBase.whereConditions.toMutableList() val cursorPredicateProvider = CursorPredicateProvider(cb, cursor, queryBase.querySelection) @@ -56,6 +48,31 @@ class TranslationsViewQueryBuilder( return query } + private fun getOrderList(queryBase: QueryBase>): MutableList { + val orderList = sort.asSequence().filter { queryBase.querySelection[it.property] != null }.map { + val expression = queryBase.querySelection[it.property] as Expression<*> + when (it.direction) { + Sort.Direction.DESC -> cb.desc(expression) + else -> cb.asc(expression) + } + }.toMutableList() + + if (orderList.isEmpty()) { + orderList.add(cb.asc(queryBase.keyNameExpression)) + } + + orderList.forEach { + (it as? SqmSortSpecification)?.let { sortSpec -> + when { + sortSpec.isAscending -> sortSpec.nullPrecedence(NullPrecedence.FIRST) + else -> sortSpec.nullPrecedence(NullPrecedence.LAST) + } + } + } + + return orderList + } + val countQuery: CriteriaQuery get() { val query = cb.createQuery(Long::class.java) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/security/ApiKeyService.kt b/backend/data/src/main/kotlin/io/tolgee/service/security/ApiKeyService.kt index 6ef8d72fc8..e14027dfb4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/security/ApiKeyService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/security/ApiKeyService.kt @@ -19,6 +19,7 @@ import io.tolgee.security.PROJECT_API_KEY_PREFIX import io.tolgee.util.Logging import io.tolgee.util.logger import io.tolgee.util.runSentryCatching +import jakarta.persistence.EntityManager import org.springframework.cache.Cache import org.springframework.cache.CacheManager import org.springframework.cache.annotation.CacheEvict @@ -30,7 +31,6 @@ import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.* -import javax.persistence.EntityManager @Service class ApiKeyService( @@ -129,12 +129,13 @@ class ApiKeyService( } fun deleteAllByProject(projectId: Long) { + val apiKeys = getAllByProject(projectId) cache?.let { // Manual bulk cache eviction - getAllByProject(projectId).forEach { p -> it.evict(p.keyHash) } + apiKeys.forEach { p -> it.evict(p.keyHash) } } - apiKeyRepository.deleteAllByProjectId(projectId) + apiKeyRepository.deleteAll(apiKeys) } fun hashKey(key: String) = keyGenerator.hash(key) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt b/backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt index 645dc5269d..2ec387e1c6 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt @@ -151,8 +151,8 @@ class PermissionService( * No need to evict cache, since this is only used when project is deleted */ fun deleteAllByProject(projectId: Long) { - val ids = permissionRepository.getIdsByProject(projectId) - permissionRepository.deleteByIdIn(ids) + val permissions = permissionRepository.getByProjectWithFetchedLanguages(projectId) + permissionRepository.deleteAll(permissions) } @Transactional diff --git a/backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt b/backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt index 337eac3260..b6a5c25f61 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt @@ -24,9 +24,12 @@ import io.tolgee.repository.UserAccountRepository import io.tolgee.service.AvatarService import io.tolgee.service.EmailVerificationService import io.tolgee.service.organization.OrganizationService +import io.tolgee.util.Logging +import jakarta.persistence.EntityManager import org.apache.commons.lang3.time.DateUtils import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator import org.springframework.beans.factory.annotation.Autowired +import org.springframework.cache.CacheManager import org.springframework.cache.annotation.CacheEvict import org.springframework.cache.annotation.Cacheable import org.springframework.context.ApplicationEventPublisher @@ -38,7 +41,6 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.InputStream import java.util.* -import javax.persistence.EntityManager @Service class UserAccountService( @@ -51,7 +53,8 @@ class UserAccountService( private val organizationService: OrganizationService, private val entityManager: EntityManager, private val currentDateProvider: CurrentDateProvider, -) { + private val cacheManager: CacheManager +) : Logging { @Autowired lateinit var emailVerificationService: EmailVerificationService @@ -138,34 +141,52 @@ class UserAccountService( @CacheEvict(Caches.USER_ACCOUNTS, key = "#userAccount.id") @Transactional fun delete(userAccount: UserAccount) { - userAccount.emailVerification?.let { + traceLogMeasureTime("deleteUser") { + val toDelete = + userAccountRepository.findWithFetchedEmailVerificationAndPermissions(userAccount.id) + ?: throw NotFoundException() + deleteWithFetchedData(toDelete) + } + } + + private fun deleteWithFetchedData(toDelete: UserAccount) { + toDelete.emailVerification?.let { entityManager.remove(it) } - userAccount.apiKeys?.forEach { + toDelete.apiKeys?.forEach { entityManager.remove(it) } - userAccount.pats?.forEach { + toDelete.pats?.forEach { entityManager.remove(it) } - userAccount.permissions.forEach { + toDelete.permissions.forEach { entityManager.remove(it) } - userAccount.preferences?.let { + toDelete.preferences?.let { entityManager.remove(it) } - organizationService.getAllSingleOwnedByUser(userAccount).forEach { + organizationService.getAllSingleOwnedByUser(toDelete).forEach { it.preferredBy.removeIf { preferences -> - preferences.userAccount.id == userAccount.id + preferences.userAccount.id == toDelete.id } organizationService.delete(it) } - userAccount.organizationRoles.forEach { + toDelete.organizationRoles.forEach { entityManager.remove(it) } - userAccountRepository.softDeleteUser(userAccount) + userAccountRepository.softDeleteUser(toDelete, currentDateProvider.date) applicationEventPublisher.publishEvent(OnUserCountChanged(this)) } + @Transactional + fun deleteByUserNames(usernames: List) { + val data = userAccountRepository.findActiveWithFetchedDataByUserNames(usernames) + data.forEach { + deleteWithFetchedData(it) + cacheManager.getCache(Caches.USER_ACCOUNTS)?.evict(it.id) + } + } + fun dtoToEntity(request: SignUpDto): UserAccount { val encodedPassword = passwordEncoder.encode(request.password!!) return UserAccount(name = request.name, username = request.email, password = encodedPassword) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/translation/AutoTranslationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/translation/AutoTranslationService.kt index e1a7ebe859..31c02911c4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/translation/AutoTranslationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/translation/AutoTranslationService.kt @@ -16,10 +16,10 @@ import io.tolgee.repository.AutoTranslationConfigRepository import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.LanguageService import io.tolgee.service.machineTranslation.MtService +import jakarta.persistence.EntityManager import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service -import javax.persistence.EntityManager @Service class AutoTranslationService( @@ -264,6 +264,14 @@ class AutoTranslationService( return saveConfig(config) } + fun deleteConfigsByProject(projectId: Long) { + entityManager.createNativeQuery( + "DELETE FROM auto_translation_config WHERE project_id = :projectId" + ) + .setParameter("projectId", projectId) + .executeUpdate() + } + fun saveConfig(config: AutoTranslationConfig): AutoTranslationConfig { return autoTranslationConfigRepository.save(config) } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationCommentService.kt b/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationCommentService.kt index 94651df9ce..53994590e9 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationCommentService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationCommentService.kt @@ -9,6 +9,7 @@ import io.tolgee.model.translation.Translation import io.tolgee.model.translation.TranslationComment import io.tolgee.repository.translation.TranslationCommentRepository import io.tolgee.security.authentication.AuthenticationFacade +import jakarta.persistence.EntityManager import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service @@ -18,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional class TranslationCommentService( private val translationCommentRepository: TranslationCommentRepository, private val authenticationFacade: AuthenticationFacade, + private val entityManager: EntityManager ) { @Transactional fun create( @@ -88,4 +90,12 @@ class TranslationCommentService( fun deleteByTranslationIdIn(ids: Collection) { return translationCommentRepository.deleteByTranslationIdIn(ids) } + + fun deleteAllByProject(projectId: Long) { + entityManager.createNativeQuery( + "DELETE FROM translation_comment WHERE translation_id IN " + + "(SELECT id FROM translation WHERE key_id IN " + + "(SELECT id FROM key WHERE project_id = :projectId))" + ).setParameter("projectId", projectId).executeUpdate() + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationService.kt index fb4ba18d19..1a7c7bf74b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/translation/TranslationService.kt @@ -23,6 +23,7 @@ import io.tolgee.service.dataImport.ImportService import io.tolgee.service.key.KeyService import io.tolgee.service.project.ProjectService import io.tolgee.service.query_builders.translationViewBuilder.TranslationViewDataProvider +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationEventPublisher import org.springframework.context.annotation.Lazy @@ -31,7 +32,6 @@ import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.* -import javax.persistence.EntityManager @Service @Transactional @@ -260,7 +260,7 @@ class TranslationService( } fun saveAll(entities: Iterable) { - entities.map { save(it) } + entities.forEach { save(it) } } fun setStateBatch(translation: Translation, state: TranslationState): Translation { @@ -458,4 +458,14 @@ class TranslationService( }.filter { it.state !== TranslationState.DISABLED } } } + + fun deleteAllByProject(projectId: Long) { + translationCommentService.deleteAllByProject(projectId) + entityManager.createNativeQuery( + "DELETE FROM translation " + + "WHERE " + + "key_id IN (SELECT id FROM key WHERE project_id = :projectId) or " + + "language_id IN (SELECT id FROM language WHERE project_id = :projectId)" + ).setParameter("projectId", projectId).executeUpdate() + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/util/EntityUtil.kt b/backend/data/src/main/kotlin/io/tolgee/util/EntityUtil.kt index 58861fe2d4..965e94538b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/EntityUtil.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/EntityUtil.kt @@ -1,8 +1,8 @@ package io.tolgee.util +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component import java.util.concurrent.ConcurrentHashMap -import javax.persistence.EntityManager @Component class EntityUtil( diff --git a/backend/data/src/main/kotlin/io/tolgee/util/ImageConverter.kt b/backend/data/src/main/kotlin/io/tolgee/util/ImageConverter.kt index 9d04a6eb1a..ad88188ede 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/ImageConverter.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/ImageConverter.kt @@ -2,6 +2,7 @@ package io.tolgee.util import java.awt.Dimension import java.awt.Image +import java.awt.RenderingHints import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.io.InputStream @@ -23,13 +24,13 @@ class ImageConverter( Dimension(sourceBufferedImage.width, sourceBufferedImage.height) } - fun getImage(compressionQuality: Float = 0.5f, targetDimension: Dimension? = null): ByteArrayOutputStream { - val resizedImage = getScaledImage(targetDimension) - val bufferedImage = convertToBufferedImage(resizedImage) - return writeImage(bufferedImage, compressionQuality) + fun getImage(compressionQuality: Float = 0.8f, targetDimension: Dimension? = null): ByteArrayOutputStream { + val resultingTargetDimension = targetDimension ?: this.targetDimension + val resizedImage = getScaledImage(resultingTargetDimension) + return writeImage(resizedImage, compressionQuality) } - fun getThumbnail(size: Int = 150, compressionQuality: Float = 0.5f): ByteArrayOutputStream { + fun getThumbnail(size: Int = 150, compressionQuality: Float = 0.8f): ByteArrayOutputStream { val originalWidth = sourceBufferedImage.width val originalHeight = sourceBufferedImage.height val newWidth: Int @@ -43,9 +44,8 @@ class ImageConverter( newWidth = (originalWidth * size) / originalHeight } - val resizedImage = sourceBufferedImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH) - val bufferedImage = convertToBufferedImage(resizedImage) - return writeImage(bufferedImage, compressionQuality) + val resizedImage = getScaledImage(Dimension(newWidth, newHeight)) + return writeImage(resizedImage, compressionQuality) } private fun writeImage(bufferedImage: BufferedImage, compressionQuality: Float): ByteArrayOutputStream { @@ -70,13 +70,19 @@ class ImageConverter( return writerParams } - private fun getScaledImage(targetDimension: Dimension?): Image { - val resultingTargetDimension = targetDimension ?: this.targetDimension - return sourceBufferedImage.getScaledInstance( - resultingTargetDimension.width, - resultingTargetDimension.height, - Image.SCALE_SMOOTH + private fun getScaledImage(targetDimension: Dimension): BufferedImage { + val resized = BufferedImage(targetDimension.width, targetDimension.height, sourceBufferedImage.type) + val g = resized.createGraphics() + g.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR ) + g.drawImage( + sourceBufferedImage, 0, 0, targetDimension.width, targetDimension.height, 0, 0, sourceBufferedImage.width, + sourceBufferedImage.height, null + ) + g.dispose() + return resized } private fun getWriter() = ImageIO.getImageWritersByFormatName("png").next() as ImageWriter @@ -93,12 +99,13 @@ class ImageConverter( return@lazy Dimension(sourceBufferedImage.width, sourceBufferedImage.height) } - private fun convertToBufferedImage(img: Image): BufferedImage { + private fun convertToBufferedImage(img: Image, width: Int, height: Int): BufferedImage { if (img is BufferedImage) { return img } + val bi = BufferedImage( - img.getWidth(null), img.getHeight(null), + width, height, BufferedImage.TYPE_INT_ARGB ) val graphics2D = bi.createGraphics() diff --git a/backend/data/src/main/kotlin/io/tolgee/util/KotlinCriteriaBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/util/KotlinCriteriaBuilder.kt index 1e898161c8..fd948b2945 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/KotlinCriteriaBuilder.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/KotlinCriteriaBuilder.kt @@ -1,8 +1,8 @@ package io.tolgee.util -import javax.persistence.EntityManager -import javax.persistence.criteria.Expression -import javax.persistence.criteria.Predicate +import jakarta.persistence.EntityManager +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.Predicate abstract class KotlinCriteriaBuilder(entityManager: EntityManager, result: Class) { val cb = entityManager.criteriaBuilder diff --git a/backend/data/src/main/kotlin/io/tolgee/util/StreamingResponseBodyProvider.kt b/backend/data/src/main/kotlin/io/tolgee/util/StreamingResponseBodyProvider.kt index ab11669ac5..1322db53e0 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/StreamingResponseBodyProvider.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/StreamingResponseBodyProvider.kt @@ -16,11 +16,11 @@ package io.tolgee.util +import jakarta.persistence.EntityManager import org.hibernate.Session import org.springframework.stereotype.Component import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody import java.io.OutputStream -import javax.persistence.EntityManager @Component class StreamingResponseBodyProvider( @@ -29,10 +29,14 @@ class StreamingResponseBodyProvider( fun createStreamingResponseBody(fn: (os: OutputStream) -> Unit): StreamingResponseBody { return StreamingResponseBody { val session = entityManager.unwrap(Session::class.java) - fn(it) + + session.doWork { connection -> + fn(it) + // Manually dispose the connection because spring has a hard time doing so by itself + connection.close() + } // Manually dispose the connection because spring has a hard time doing so by itself - session.disconnect() session.close() } } diff --git a/backend/data/src/main/kotlin/io/tolgee/util/criteriaBuilderExt.kt b/backend/data/src/main/kotlin/io/tolgee/util/criteriaBuilderExt.kt index ea92db4795..58e546ce6c 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/criteriaBuilderExt.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/criteriaBuilderExt.kt @@ -1,12 +1,12 @@ package io.tolgee.util -import javax.persistence.EntityManager -import javax.persistence.TypedQuery -import javax.persistence.criteria.CriteriaBuilder -import javax.persistence.criteria.CriteriaQuery -import javax.persistence.criteria.Expression -import javax.persistence.criteria.Predicate -import javax.persistence.criteria.Root +import jakarta.persistence.EntityManager +import jakarta.persistence.TypedQuery +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.Expression +import jakarta.persistence.criteria.Predicate +import jakarta.persistence.criteria.Root fun CriteriaBuilder.greaterThanNullable( expression: Expression, diff --git a/backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt b/backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt index 142a29803a..2300b7bea1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt @@ -3,10 +3,28 @@ package io.tolgee.util import org.slf4j.Logger import org.slf4j.LoggerFactory -interface Logging +interface Logging { + fun traceLogMeasureTime(operationName: String, fn: () -> T): T { + if (logger.isTraceEnabled) { + val start = System.currentTimeMillis() + logger.trace("Operation $operationName started") + val result = fn() + val end = System.currentTimeMillis() + logger.trace("Execution time $operationName: ${end - start}ms") + return result + } + return fn() + } +} val T.logger: Logger get() = LoggerFactory.getLogger(javaClass) +fun Logger.trace(message: () -> String) { + if (this.isTraceEnabled) { + this.trace(message()) + } +} + fun Logger.debug(message: () -> String) { if (this.isDebugEnabled) { this.debug(message()) diff --git a/backend/data/src/main/kotlin/io/tolgee/util/setSimilarityLimit.kt b/backend/data/src/main/kotlin/io/tolgee/util/setSimilarityLimit.kt index 4946f4b100..23a0ee172f 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/setSimilarityLimit.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/setSimilarityLimit.kt @@ -1,6 +1,6 @@ package io.tolgee.util -import javax.persistence.EntityManager +import jakarta.persistence.EntityManager fun EntityManager.setSimilarityLimit(limit: Double) { this.createNativeQuery("select set_limit($limit);").resultList diff --git a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt index c71c19f49d..3190354c5e 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt @@ -1,12 +1,12 @@ package io.tolgee.util +import jakarta.persistence.OptimisticLockException import org.springframework.dao.CannotAcquireLockException import org.springframework.dao.DataIntegrityViolationException import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.TransactionDefinition import org.springframework.transaction.TransactionStatus import org.springframework.transaction.support.TransactionTemplate -import javax.persistence.OptimisticLockException fun executeInNewTransaction( transactionManager: PlatformTransactionManager, diff --git a/backend/data/src/main/kotlin/io/tolgee/util/tryUntilItDoesntBreakContraint.kt b/backend/data/src/main/kotlin/io/tolgee/util/tryUntilItDoesntBreakContraint.kt index 8e62fb7871..69a8bacc9a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/util/tryUntilItDoesntBreakContraint.kt +++ b/backend/data/src/main/kotlin/io/tolgee/util/tryUntilItDoesntBreakContraint.kt @@ -1,8 +1,8 @@ package io.tolgee.util +import jakarta.persistence.PersistenceException import org.springframework.dao.CannotAcquireLockException import org.springframework.dao.DataIntegrityViolationException -import javax.persistence.PersistenceException inline fun tryUntilItDoesntBreakConstraint(fn: () -> T): T { var exception: Exception? = null diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml index 39e6e00ed0..593c903964 100644 --- a/backend/data/src/main/resources/db/changelog/schema.xml +++ b/backend/data/src/main/resources/db/changelog/schema.xml @@ -1788,9 +1788,6 @@ - - - @@ -1833,97 +1830,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2976,4 +2885,68 @@ + + + + + SELECT CASE + WHEN EXISTS (SELECT 1 + FROM information_schema.columns + WHERE table_name = 'batch_job_execution_params' + AND column_name = 'parameter_value') + THEN 'true' + ELSE 'false' + END; + + + SELECT CASE + WHEN EXISTS (SELECT 1 + FROM information_schema.columns + WHERE table_name = 'batch_job_execution_params') + THEN 'true' + ELSE 'false' + END; + + + + + ALTER TABLE BATCH_STEP_EXECUTION ADD CREATE_TIME TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00'; + + ALTER TABLE BATCH_STEP_EXECUTION ALTER COLUMN START_TIME DROP NOT NULL; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DATE_VAL; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN LONG_VAL; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS DROP COLUMN DOUBLE_VAL; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN TYPE_CD TYPE VARCHAR(100); + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME TYPE_CD TO PARAMETER_TYPE; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN KEY_NAME TYPE VARCHAR(100); + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME KEY_NAME TO PARAMETER_NAME; + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS ALTER COLUMN STRING_VAL TYPE VARCHAR(2500); + + ALTER TABLE BATCH_JOB_EXECUTION_PARAMS RENAME STRING_VAL TO PARAMETER_VALUE; + + + + + + + + + + + + + + + CREATE INDEX project_deleted_at_null ON project((deleted_at IS NULL)); + CREATE INDEX organization_deleted_at_null ON organization((deleted_at IS NULL)); + + diff --git a/backend/data/src/test/kotlin/io/tolgee/security/authentication/JwtServiceTest.kt b/backend/data/src/test/kotlin/io/tolgee/security/authentication/JwtServiceTest.kt index f28299ae01..9c672d3e6b 100644 --- a/backend/data/src/test/kotlin/io/tolgee/security/authentication/JwtServiceTest.kt +++ b/backend/data/src/test/kotlin/io/tolgee/security/authentication/JwtServiceTest.kt @@ -24,7 +24,11 @@ import io.tolgee.dtos.cacheable.UserAccountDto import io.tolgee.exceptions.AuthenticationException import io.tolgee.service.security.UserAccountService import io.tolgee.testing.assertions.Assertions.assertThat -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import java.util.* diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/cachePurging/AzureContentStorageConfigCachePurgingTest.kt b/backend/data/src/test/kotlin/io/tolgee/unit/cachePurging/AzureContentStorageConfigCachePurgingTest.kt index 01494236b2..8b8d4fba4a 100644 --- a/backend/data/src/test/kotlin/io/tolgee/unit/cachePurging/AzureContentStorageConfigCachePurgingTest.kt +++ b/backend/data/src/test/kotlin/io/tolgee/unit/cachePurging/AzureContentStorageConfigCachePurgingTest.kt @@ -16,6 +16,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.springframework.http.HttpEntity import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatusCode import org.springframework.http.ResponseEntity import org.springframework.web.client.RestTemplate @@ -43,11 +44,11 @@ class AzureContentStorageConfigCachePurgingTest() { val restTemplateMock: RestTemplate = mock() val azureCredentialProviderMock: AzureCredentialProvider = mock() val purging = AzureContentDeliveryCachePurging(config, restTemplateMock, azureCredentialProviderMock) - val responseMock: ResponseEntity<*> = Mockito.mock(ResponseEntity::class.java, Mockito.RETURNS_DEEP_STUBS) + val responseMock: ResponseEntity<*> = Mockito.mock(ResponseEntity::class.java) whenever(restTemplateMock.exchange(any(), any(), any(), eq(String::class.java))).doAnswer { responseMock as ResponseEntity } - whenever(responseMock.statusCode.is2xxSuccessful).thenReturn(true) + whenever(responseMock.statusCode).thenReturn(HttpStatusCode.valueOf(200)) val credentialMck: ClientSecretCredential = Mockito.mock(ClientSecretCredential::class.java, Mockito.RETURNS_DEEP_STUBS) diff --git a/backend/development/build.gradle b/backend/development/build.gradle index 572dbc7827..b3f64ff721 100644 --- a/backend/development/build.gradle +++ b/backend/development/build.gradle @@ -10,7 +10,7 @@ buildscript { plugins { id 'io.spring.dependency-management' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id 'java' id 'org.jetbrains.kotlin.jvm' id "kotlin-jpa" @@ -30,7 +30,6 @@ configurations { apply plugin: 'java' apply plugin: 'idea' -apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: "org.jetbrains.kotlin.plugin.jpa" apply plugin: "kotlin-allopen" @@ -41,7 +40,7 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } idea { @@ -51,9 +50,9 @@ idea { } allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Component") annotation("org.springframework.transaction.annotation.Transactional") annotation("org.springframework.stereotype.Service") @@ -69,8 +68,8 @@ dependencies { api "org.springframework.boot:spring-boot-configuration-processor" api project(":data") api project(":ee-app") - api libs.springDocOpenApiDataRest - api libs.springDocOpenApiWebMvcCore + api libs.springDocOpenApiCommon + api libs.springDocWebmvcApi implementation libs.kotlinReflect implementation libs.kotlinCoroutines } @@ -84,8 +83,11 @@ sourceSets { test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt index b390f81e44..3efd5c466b 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt @@ -6,13 +6,13 @@ import io.tolgee.configuration.tolgee.TolgeeProperties import io.tolgee.dtos.request.SetPropertyDto import io.tolgee.exceptions.BadRequestException import io.tolgee.exceptions.NotFoundException +import jakarta.validation.Valid import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/SqlController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/SqlController.kt index 3b5281bf78..70b420016f 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/SqlController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/SqlController.kt @@ -1,14 +1,13 @@ package io.tolgee.controllers.internal import io.swagger.v3.oas.annotations.Hidden -import org.hibernate.Session +import jakarta.persistence.EntityManager import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.persistence.EntityManager @RestController @CrossOrigin(origins = ["*"]) @@ -22,14 +21,12 @@ class SqlController( @PostMapping(value = ["/list"]) @Transactional fun getList(@RequestBody query: String): MutableList? { - val session = entityManager.unwrap(Session::class.java) - return session.createNativeQuery(query).list() + return entityManager.createNativeQuery(query).resultList } @PostMapping(value = ["/execute"]) @Transactional fun execute(@RequestBody query: String) { - val session = entityManager.unwrap(Session::class.java) - session.createNativeQuery(query).executeUpdate() + entityManager.createNativeQuery(query).executeUpdate() } } diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/AbstractE2eDataController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/AbstractE2eDataController.kt index b263e0cef9..89af35817e 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/AbstractE2eDataController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/AbstractE2eDataController.kt @@ -7,6 +7,7 @@ import io.tolgee.service.project.ProjectService import io.tolgee.service.security.UserAccountService import io.tolgee.util.executeInNewRepeatableTransaction import io.tolgee.util.tryUntilItDoesntBreakConstraint +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext import org.springframework.http.ResponseEntity @@ -14,7 +15,6 @@ import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.GetMapping import java.io.FileNotFoundException -import javax.persistence.EntityManager abstract class AbstractE2eDataController { abstract val testData: TestDataBuilder diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ImportE2eDataController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ImportE2eDataController.kt index 7c883e5bd7..12193b6da1 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ImportE2eDataController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ImportE2eDataController.kt @@ -8,12 +8,12 @@ import io.tolgee.model.enums.ProjectPermissionType import io.tolgee.service.dataImport.ImportService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.UserAccountService +import jakarta.persistence.EntityManager import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.persistence.EntityManager @RestController @CrossOrigin(origins = ["*"]) diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/OrganizationE2eDataController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/OrganizationE2eDataController.kt index 860caa7b8d..c5573b4572 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/OrganizationE2eDataController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/OrganizationE2eDataController.kt @@ -7,6 +7,10 @@ import io.tolgee.exceptions.NotFoundException import io.tolgee.service.organization.OrganizationRoleService import io.tolgee.service.organization.OrganizationService import io.tolgee.service.security.UserAccountService +import io.tolgee.util.Logging +import io.tolgee.util.executeInNewRepeatableTransaction +import org.springframework.transaction.PlatformTransactionManager +import org.springframework.transaction.TransactionDefinition import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping @@ -22,45 +26,66 @@ class OrganizationE2eDataController( private val organizationService: OrganizationService, private val userAccountService: UserAccountService, private val dbPopulatorReal: DbPopulatorReal, - private val organizationRoleService: OrganizationRoleService -) { + private val organizationRoleService: OrganizationRoleService, + private val transactionManager: PlatformTransactionManager +) : Logging { @GetMapping(value = ["/generate"]) @Transactional - fun createOrganizations() { - data.forEach { - val organization = organizationService.create( + fun createOrganizations(): Map> { + val organizations = data.map { + organizationService.create( it.dto, this.dbPopulatorReal.createUserIfNotExists(it.owner.email, null, it.owner.name) ) } - data.forEach { - val organization = organizationService.find(it.dto.slug!!) - it.members.forEach { memberUserName -> - val user = userAccountService.findActive(memberUserName) ?: throw NotFoundException() - organizationRoleService.grantMemberRoleToUser(user, organization!!) - } + data.forEach { dataItem -> + organizationService.findAllByName(dataItem.dto.name).forEach { organization -> + dataItem.members.forEach { memberUserName -> + val user = userAccountService.findActive(memberUserName) ?: throw NotFoundException() + organizationRoleService.grantMemberRoleToUser(user, organization) + } - it.otherOwners.forEach { memberUserName -> - val user = userAccountService.findActive(memberUserName) ?: throw NotFoundException() - organizationRoleService.grantOwnerRoleToUser(user, organization!!) + dataItem.otherOwners.forEach { memberUserName -> + val user = userAccountService.findActive(memberUserName) ?: throw NotFoundException() + organizationRoleService.grantOwnerRoleToUser(user, organization) + } } } + return organizations.map { it.name to mapOf("slug" to it.slug) }.toMap() } @GetMapping(value = ["/clean"]) - @Transactional fun cleanupOrganizations() { - organizationService.find("what-a-nice-organization")?.let { - organizationService.delete(it) - } - data.forEach { - organizationService.find(it.dto.slug!!)?.let { organization -> - organizationService.delete(organization) - } - userAccountService.findActive(it.owner.email)?.let { userAccount -> - if (userAccount.name != "admin") { - userAccountService.delete(userAccount) + traceLogMeasureTime("cleanupOrganizations") { + executeInNewRepeatableTransaction( + transactionManager, + isolationLevel = TransactionDefinition.ISOLATION_SERIALIZABLE + ) { + traceLogMeasureTime("delete what-a-nice-organization") { + organizationService.findAllByName("What a nice organization").forEach { + organizationService.delete(it) + } + } + data.forEach { + traceLogMeasureTime("delete organization ${it.dto.slug}") { + val orgs = + traceLogMeasureTime("find organization") { + organizationService.findAllByName(it.dto.name) + } + orgs.forEach { organization -> + traceLogMeasureTime("delete organization") { + organizationService.delete(organization) + } + } + } + } + traceLogMeasureTime("delete users") { + val owners = data.mapNotNull { + if (it.owner.name == "admin") return@mapNotNull null + it.owner.email + } + userAccountService.deleteByUserNames(owners) } } } @@ -84,7 +109,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Google", description = "An organization made by google company", - slug = "google" ), owner = UserData("admin") ), @@ -92,7 +116,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Netsuite", description = "A system for everything", - slug = "netsuite" ), owner = UserData("evan@netsuite.com", "Evan Goldberg") ), @@ -100,7 +123,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Microsoft", description = "A first software company ever or something like that.", - slug = "microsoft" ), owner = UserData("gates@microsoft.com", "Bill Gates"), members = mutableListOf("admin") @@ -109,7 +131,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Tolgee", description = "This is us", - slug = "tolgee" ), owner = UserData("admin"), otherOwners = mutableListOf("evan@netsuite.com"), @@ -123,7 +144,6 @@ class OrganizationE2eDataController( |They also develop amazing things like react and other open source stuff. |However, they sell our data to companies. """.trimMargin(), - slug = "facebook" ), owner = UserData("cukrberg@facebook.com", "Mark Cukrberg"), otherOwners = mutableListOf("admin") @@ -132,7 +152,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Unknown company", description = "We are very unknown.", - slug = "unknown-company" ), owner = UserData("admin") ), @@ -140,7 +159,6 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "Techfides solutions s.r.o", description = "Lets develop the future", - slug = "techfides-solutions" ), owner = UserData("admin") @@ -155,13 +173,12 @@ class OrganizationE2eDataController( dto = OrganizationDto( name = "ZZZ Cool company $number", description = "We are Z Cool company $number. What a nice day!", - slug = "zzz-cool-company-$number" ), otherOwners = mutableListOf("admin"), owner = UserData(email), ) ) - data.find { item -> item.dto.slug == "facebook" }!!.otherOwners.add(email) + data.find { item -> item.dto.name == "Facebook" }!!.otherOwners.add(email) } } } diff --git a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ProjectsE2eDataController.kt b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ProjectsE2eDataController.kt index b31f2ea5a8..b4c11fcf1e 100644 --- a/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ProjectsE2eDataController.kt +++ b/backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2e_data/ProjectsE2eDataController.kt @@ -19,6 +19,10 @@ import io.tolgee.service.organization.OrganizationRoleService import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.ProjectService import io.tolgee.service.security.UserAccountService +import io.tolgee.util.Logging +import io.tolgee.util.executeInNewRepeatableTransaction +import jakarta.persistence.EntityManager +import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping @@ -41,7 +45,9 @@ class ProjectsE2eDataController( private val permissionRepository: PermissionRepository, private val keyService: KeyService, private val languageService: LanguageService, -) { + private val entityManager: EntityManager, + private val transactionManager: PlatformTransactionManager +) : Logging { @GetMapping(value = ["/generate"]) @Transactional fun createProjects() { @@ -57,7 +63,7 @@ class ProjectsE2eDataController( userAccountRepository.saveAll(createdUsers.values) - organizations.forEach { + val createdOrganizations = organizations.map { val organization = Organization( name = it.name, slug = organizationService.generateSlug(it.name), @@ -79,16 +85,19 @@ class ProjectsE2eDataController( organizationRoleService.grantMemberRoleToUser(user, organization) } } + organization } projects.forEach { projectData -> - val organizationOwner = organizationService.get(projectData.organizationOwner) + val organizationOwner = createdOrganizations.find { + it.name.lowercase().replace("[^A-Za-z0-9]".toRegex(), "-") == projectData.organizationOwner.lowercase() + } val project = projectRepository.save( Project( name = projectData.name, slug = projectService.generateSlug(projectData.name), - organizationOwner = organizationOwner + organizationOwner = organizationOwner!! ) ) @@ -112,23 +121,26 @@ class ProjectsE2eDataController( } @GetMapping(value = ["/clean"]) - @Transactional fun cleanupProjects() { - projectService.deleteAllByName("I am a great project") + executeInNewRepeatableTransaction(transactionManager = transactionManager) { + entityManager.createNativeQuery("SET join_collapse_limit TO 1").executeUpdate() + projectService.deleteAllByName("I am a great project") - projects.forEach { - projectService.deleteAllByName(it.name) - } + projects.forEach { + traceLogMeasureTime("deleteAllByName: ${it.name}") { + projectService.deleteAllByName(it.name) + } + } - organizations.forEach { - organizationService.findAllByName(it.name).forEach { org -> - organizationService.delete(org) + organizations.forEach { + organizationService.findAllByName(it.name).forEach { org -> + organizationService.delete(org) + } } - } - users.forEach { - userAccountService.findActive(username = it.email)?.let { - userAccountRepository.delete(it) + traceLogMeasureTime("deleteUsers") { + val usernames = users.map { it.email } + userAccountService.deleteByUserNames(usernames) } } } diff --git a/backend/misc/build.gradle b/backend/misc/build.gradle index 9f86bf1fbf..7b23fb5b74 100644 --- a/backend/misc/build.gradle +++ b/backend/misc/build.gradle @@ -23,7 +23,7 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } dependencies { diff --git a/backend/misc/src/main/kotlin/io/tolgee/fixtures/verifySignatureHeader.kt b/backend/misc/src/main/kotlin/io/tolgee/fixtures/verifySignatureHeader.kt index 4c89ef77b0..8ab93d96ef 100644 --- a/backend/misc/src/main/kotlin/io/tolgee/fixtures/verifySignatureHeader.kt +++ b/backend/misc/src/main/kotlin/io/tolgee/fixtures/verifySignatureHeader.kt @@ -3,6 +3,7 @@ package io.tolgee.fixtures import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import java.nio.charset.StandardCharsets +import java.util.HexFormat import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @@ -49,10 +50,9 @@ fun verifyWebhookSignatureHeader( class SignatureVerificationException(message: String) : Exception(message) -@OptIn(ExperimentalStdlibApi::class) fun computeHmacSha256(key: String, message: String): String { val hasher = Mac.getInstance("HmacSHA256") hasher.init(SecretKeySpec(key.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) val hash = hasher.doFinal(message.toByteArray(StandardCharsets.UTF_8)) - return hash.toHexString(HexFormat.Default) + return HexFormat.of().formatHex(hash) } diff --git a/backend/misc/src/main/kotlin/io/tolgee/misc/dockerRunner/DockerContainerRunner.kt b/backend/misc/src/main/kotlin/io/tolgee/misc/dockerRunner/DockerContainerRunner.kt index b7d71c5bb9..8978514de9 100644 --- a/backend/misc/src/main/kotlin/io/tolgee/misc/dockerRunner/DockerContainerRunner.kt +++ b/backend/misc/src/main/kotlin/io/tolgee/misc/dockerRunner/DockerContainerRunner.kt @@ -32,6 +32,10 @@ class DockerContainerRunner( private val command: String = "", private val timeout: Long = 10000 ) { + + var containerExisted: Boolean = false + private set + fun run() { if (stopBeforeStart) { stop() @@ -46,8 +50,8 @@ class DockerContainerRunner( private fun startExistingOrNewContainer() { try { startExistingContainer() + containerExisted = true } catch (e: CommandRunFailedException) { - e.printStackTrace() startNewContainer() } } diff --git a/backend/security/build.gradle b/backend/security/build.gradle index 8cd3674797..f79b8706d9 100644 --- a/backend/security/build.gradle +++ b/backend/security/build.gradle @@ -8,7 +8,7 @@ plugins { id 'java' id 'io.spring.dependency-management' id 'org.jetbrains.kotlin.jvm' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id "kotlin-allopen" } @@ -18,7 +18,6 @@ group = 'io.tolgee.security' apply plugin: 'java' apply plugin: 'idea' apply plugin: "org.jetbrains.kotlin.plugin.spring" -apply plugin: 'org.springframework.boot' apply plugin: "kotlin-allopen" apply plugin: 'io.spring.dependency-management' @@ -27,7 +26,7 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } allOpen { @@ -47,16 +46,11 @@ dependencies { /** * SPRING DOC */ - implementation libs.springDocOpenApiWebMvcCore + implementation libs.springDocWebmvcApi implementation libs.springDocOpenApiUi - implementation libs.springDocOpenApiKotlin - implementation libs.springDocOpenApiDataRest - implementation libs.springDocOpenApiHateoas + implementation libs.springDocOpenApiCommon - implementation dependencies.create(libs.redissonSpringBootStarter.get()) { - exclude group: 'org.redisson', module: 'redisson-spring-data-31' - } - implementation libs.redissonSpringData + implementation libs.redissonSpringBootStarter /** * Misc @@ -68,6 +62,7 @@ dependencies { implementation libs.jjwtJackson implementation("com.github.ben-manes.caffeine:caffeine:3.0.5") api libs.postHog + implementation libs.kotlinReflect /** * Tests @@ -81,7 +76,7 @@ dependencies { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" } sourceSets { @@ -89,8 +84,11 @@ sourceSets { test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationDisabledFilter.kt b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationDisabledFilter.kt index 40bf6b47e2..9ba31b945d 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationDisabledFilter.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationDisabledFilter.kt @@ -19,12 +19,12 @@ package io.tolgee.security.authentication import io.tolgee.configuration.tolgee.AuthenticationProperties import io.tolgee.dtos.cacheable.UserAccountDto import io.tolgee.service.security.UserAccountService +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class AuthenticationDisabledFilter( diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFilter.kt b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFilter.kt index 6bc14d2c0f..60b8a974f2 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFilter.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationFilter.kt @@ -25,18 +25,22 @@ import io.tolgee.security.ratelimit.RateLimitService import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PatService import io.tolgee.service.security.UserAccountService +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.context.annotation.Lazy import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class AuthenticationFilter( private val authenticationProperties: AuthenticationProperties, + @Lazy private val currentDateProvider: CurrentDateProvider, + @Lazy private val rateLimitService: RateLimitService, + @Lazy private val jwtService: JwtService, private val userAccountService: UserAccountService, private val apiKeyService: ApiKeyService, diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationInterceptor.kt b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationInterceptor.kt index 2bf07f98bd..7197b9a8c1 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationInterceptor.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authentication/AuthenticationInterceptor.kt @@ -18,14 +18,14 @@ package io.tolgee.security.authentication import io.tolgee.constants.Message import io.tolgee.exceptions.PermissionException +import jakarta.servlet.DispatcherType +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.core.Ordered import org.springframework.core.annotation.AnnotationUtils import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor -import javax.servlet.DispatcherType -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse /** * This interceptor validates the user authentication for use in the authorization phase. diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authorization/AbstractAuthorizationInterceptor.kt b/backend/security/src/main/kotlin/io/tolgee/security/authorization/AbstractAuthorizationInterceptor.kt index 59b39f1789..d49a1bb101 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authorization/AbstractAuthorizationInterceptor.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authorization/AbstractAuthorizationInterceptor.kt @@ -16,13 +16,13 @@ package io.tolgee.security.authorization +import jakarta.servlet.DispatcherType +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.core.Ordered import org.springframework.core.annotation.AnnotationUtils import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor -import javax.servlet.DispatcherType -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse abstract class AbstractAuthorizationInterceptor : HandlerInterceptor, Ordered { override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptor.kt b/backend/security/src/main/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptor.kt index 87f74b041c..c37869cdf2 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptor.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptor.kt @@ -24,12 +24,13 @@ import io.tolgee.security.OrganizationHolder import io.tolgee.security.RequestContextService import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.organization.OrganizationRoleService +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory +import org.springframework.context.annotation.Lazy import org.springframework.core.annotation.AnnotationUtils import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse /** * This interceptor performs authorization step to access organization-related endpoints. @@ -38,7 +39,9 @@ import javax.servlet.http.HttpServletResponse @Component class OrganizationAuthorizationInterceptor( private val authenticationFacade: AuthenticationFacade, + @Lazy private val organizationRoleService: OrganizationRoleService, + @Lazy private val requestContextService: RequestContextService, private val organizationHolder: OrganizationHolder, ) : AbstractAuthorizationInterceptor() { diff --git a/backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt b/backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt index 30f25104ab..650a1aaef3 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt @@ -27,12 +27,12 @@ import io.tolgee.security.RequestContextService import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.organization.OrganizationService import io.tolgee.service.security.SecurityService +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.core.annotation.AnnotationUtils import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse /** * This interceptor performs authorization step to perform operations on a project or organization. diff --git a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalIpRateLimitFilter.kt b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalIpRateLimitFilter.kt index bac6791186..c92450ea95 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalIpRateLimitFilter.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalIpRateLimitFilter.kt @@ -17,11 +17,11 @@ package io.tolgee.security.ratelimit import io.tolgee.security.authentication.AuthenticationFacade +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class GlobalIpRateLimitFilter( diff --git a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilter.kt b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilter.kt index 99f09ffb88..ad3587cd82 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilter.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilter.kt @@ -17,11 +17,11 @@ package io.tolgee.security.ratelimit import io.tolgee.security.authentication.AuthenticationFacade +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class GlobalUserRateLimitFilter( diff --git a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptor.kt b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptor.kt index d321078c2d..9dbfaf291d 100644 --- a/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptor.kt +++ b/backend/security/src/main/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptor.kt @@ -19,6 +19,9 @@ package io.tolgee.security.ratelimit import io.tolgee.configuration.tolgee.RateLimitProperties import io.tolgee.dtos.cacheable.UserAccountDto import io.tolgee.security.authentication.AuthenticationFacade +import jakarta.servlet.DispatcherType +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.core.Ordered import org.springframework.core.annotation.AnnotationUtils import org.springframework.stereotype.Component @@ -26,9 +29,6 @@ import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor import org.springframework.web.servlet.HandlerMapping import java.time.Duration -import javax.servlet.DispatcherType -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Component class RateLimitInterceptor( diff --git a/backend/security/src/test/kotlin/io/tolgee/security/authentication/AuthenticationFilterTest.kt b/backend/security/src/test/kotlin/io/tolgee/security/authentication/AuthenticationFilterTest.kt index ef47beb954..132325aa64 100644 --- a/backend/security/src/test/kotlin/io/tolgee/security/authentication/AuthenticationFilterTest.kt +++ b/backend/security/src/test/kotlin/io/tolgee/security/authentication/AuthenticationFilterTest.kt @@ -31,7 +31,11 @@ import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PatService import io.tolgee.service.security.UserAccountService import io.tolgee.testing.assertions.Assertions.assertThat -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import org.mockito.kotlin.any import org.springframework.mock.web.MockFilterChain diff --git a/backend/security/src/test/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptorTest.kt b/backend/security/src/test/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptorTest.kt index 55b295c975..86aa2c0938 100644 --- a/backend/security/src/test/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptorTest.kt +++ b/backend/security/src/test/kotlin/io/tolgee/security/authorization/OrganizationAuthorizationInterceptorTest.kt @@ -38,7 +38,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RestController -import java.lang.Exception class OrganizationAuthorizationInterceptorTest { private val authenticationFacade = Mockito.mock(AuthenticationFacade::class.java) diff --git a/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilterTest.kt b/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilterTest.kt index a30767c6da..0e94822c80 100644 --- a/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilterTest.kt +++ b/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/GlobalUserRateLimitFilterTest.kt @@ -18,7 +18,11 @@ package io.tolgee.security.ratelimit import io.tolgee.dtos.cacheable.UserAccountDto import io.tolgee.security.authentication.AuthenticationFacade -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import org.mockito.kotlin.any import org.springframework.mock.web.MockFilterChain diff --git a/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptorTest.kt b/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptorTest.kt index d401b266a5..5703794981 100644 --- a/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptorTest.kt +++ b/backend/security/src/test/kotlin/io/tolgee/security/ratelimit/RateLimitInterceptorTest.kt @@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerMapping -import java.util.Date +import java.util.* import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantLock import kotlin.reflect.jvm.javaMethod diff --git a/backend/testing/build.gradle b/backend/testing/build.gradle index 5081f578dd..cd75021152 100644 --- a/backend/testing/build.gradle +++ b/backend/testing/build.gradle @@ -10,7 +10,7 @@ buildscript { plugins { id 'io.spring.dependency-management' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id 'java' id 'org.jetbrains.kotlin.jvm' id "kotlin-jpa" @@ -36,7 +36,6 @@ configurations { apply plugin: 'java' apply plugin: 'idea' -apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: "org.jetbrains.kotlin.plugin.jpa" apply plugin: "kotlin-allopen" @@ -48,7 +47,7 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } idea { @@ -58,9 +57,9 @@ idea { } allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Component") annotation("org.springframework.transaction.annotation.Transactional") annotation("org.springframework.stereotype.Service") @@ -123,20 +122,23 @@ dependencies { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" } project.tasks.findByName("compileKotlin").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } project.tasks.findByName("compileJava").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } -project.tasks.findByName("bootJarMainClassName").onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } +project.tasks.findByName("bootJarMainClassName")?.onlyIf { System.getenv("SKIP_SERVER_BUILD") != "true" } sourceSets { main.kotlin.srcDirs = ['src/main/kotlin', 'src/main/java'] test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt b/backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt index 1176af0998..aa079cbe25 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt @@ -2,6 +2,8 @@ package io.tolgee.fixtures import io.tolgee.configuration.tolgee.TolgeeProperties import io.tolgee.testing.assertions.Assertions +import jakarta.mail.internet.MimeMessage +import jakarta.mail.internet.MimeMultipart import org.assertj.core.api.AbstractStringAssert import org.mockito.Mockito import org.mockito.kotlin.KArgumentCaptor @@ -14,8 +16,6 @@ import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.JavaMailSenderImpl import org.springframework.stereotype.Component -import javax.mail.internet.MimeMessage -import javax.mail.internet.MimeMultipart @Component class EmailTestUtil() { diff --git a/backend/testing/src/main/kotlin/io/tolgee/fixtures/MimeMessageParser.kt b/backend/testing/src/main/kotlin/io/tolgee/fixtures/MimeMessageParser.kt new file mode 100644 index 0000000000..8607c7d5d4 --- /dev/null +++ b/backend/testing/src/main/kotlin/io/tolgee/fixtures/MimeMessageParser.kt @@ -0,0 +1,379 @@ +package io.tolgee.fixtures + +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.UnsupportedEncodingException +import java.util.* + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Parses a MimeMessage and stores the individual parts such a plain text, + * HTML text and attachments. + * + * @since 1.3 + */ +open class MimeMessageParser(message: jakarta.mail.internet.MimeMessage) { + /** The MimeMessage to convert */ + private val mimeMessage: jakarta.mail.internet.MimeMessage + /** @return Returns the plainContent if any + */ + /** Plain mail content from MimeMessage */ + var plainContent: String? = null + private set + /** @return Returns the htmlContent if any + */ + /** Html mail content from MimeMessage */ + var htmlContent: String? = null + private set + + /** List of attachments of MimeMessage */ + val attachmentList: MutableList + + /** Attachments stored by their content-id */ + private val cidMap: MutableMap + /** @return Returns the isMultiPart. + */ + /** Is this a Multipart email */ + var isMultipart: Boolean + private set + + /** + * Constructs an instance with the MimeMessage to be extracted. + * + * @param message the message to parse + */ + init { + attachmentList = ArrayList() + cidMap = HashMap() + mimeMessage = message + isMultipart = false + } + + /** + * Does the actual extraction. + * + * @return this instance + * @throws Exception parsing the mime message failed + */ + @Throws(Exception::class) + fun parse(): MimeMessageParser { + this.parse(null, mimeMessage) + return this + } + + @get:Throws(Exception::class) + val to: List + /** + * @return the 'to' recipients of the message + * @throws Exception determining the recipients failed + */ + get() { + val recipients: Array = + mimeMessage.getRecipients(jakarta.mail.Message.RecipientType.TO) + return if (recipients != null) listOf(*recipients) else ArrayList() + } + + @get:Throws(Exception::class) + val cc: List + /** + * @return the 'cc' recipients of the message + * @throws Exception determining the recipients failed + */ + get() { + val recipients: Array = + mimeMessage.getRecipients(jakarta.mail.Message.RecipientType.CC) + return if (recipients != null) listOf(*recipients) else ArrayList() + } + + @get:Throws(Exception::class) + val bcc: List + /** + * @return the 'bcc' recipients of the message + * @throws Exception determining the recipients failed + */ + get() { + val recipients: Array = + mimeMessage.getRecipients(jakarta.mail.Message.RecipientType.BCC) + return if (recipients != null) listOf(*recipients) else ArrayList() + } + + @get:Throws(Exception::class) + val from: String? + /** + * @return the 'from' field of the message + * @throws Exception parsing the mime message failed + */ + get() { + val addresses: Array = mimeMessage.from + return if (addresses == null || addresses.size == 0) { + null + } else (addresses[0] as jakarta.mail.internet.InternetAddress).address + } + + @get:Throws(Exception::class) + val replyTo: String? + /** + * @return the 'replyTo' address of the email + * @throws Exception parsing the mime message failed + */ + get() { + val addresses: Array = mimeMessage.replyTo + return if (addresses == null || addresses.size == 0) { + null + } else (addresses[0] as jakarta.mail.internet.InternetAddress).address + } + + @get:Throws(Exception::class) + val subject: String + /** + * @return the mail subject + * @throws Exception parsing the mime message failed + */ + get() = mimeMessage.subject + + /** + * Extracts the content of a MimeMessage recursively. + * + * @param parent the parent multi-part + * @param part the current MimePart + * @throws MessagingException parsing the MimeMessage failed + * @throws IOException parsing the MimeMessage failed + */ + @Throws(jakarta.mail.MessagingException::class, IOException::class) + protected fun parse(parent: jakarta.mail.Multipart?, part: jakarta.mail.internet.MimePart) { + if (isMimeType( + part, + "text/plain" + ) && plainContent == null && !jakarta.mail.Part.ATTACHMENT.equals(part.disposition, ignoreCase = true) + ) { + plainContent = part.content.toString() + } else { + if (isMimeType( + part, + "text/html" + ) && htmlContent == null && !jakarta.mail.Part.ATTACHMENT.equals(part.disposition, ignoreCase = true) + ) { + htmlContent = part.content.toString() + } else { + if (isMimeType(part, "multipart/*")) { + isMultipart = true + val mp: jakarta.mail.Multipart = part.content as jakarta.mail.Multipart + val count: Int = mp.count + + // iterate over all MimeBodyPart + for (i in 0 until count) { + parse(mp, mp.getBodyPart(i) as jakarta.mail.internet.MimeBodyPart) + } + } else { + val cid = stripContentId(part.contentID) + val ds: jakarta.activation.DataSource = createDataSource(parent, part) + if (cid != null) { + cidMap[cid] = ds + } + attachmentList.add(ds) + } + } + } + } + + /** + * Strips the content id of any whitespace and angle brackets. + * @param contentId the string to strip + * @return a stripped version of the content id + */ + private fun stripContentId(contentId: String?): String? { + return contentId?.trim { it <= ' ' }?.replace("[\\<\\>]".toRegex(), "") + } + + /** + * Checks whether the MimePart contains an object of the given mime type. + * + * @param part the current MimePart + * @param mimeType the mime type to check + * @return `true` if the MimePart matches the given mime type, `false` otherwise + * @throws MessagingException parsing the MimeMessage failed + * @throws IOException parsing the MimeMessage failed + */ + @Throws(jakarta.mail.MessagingException::class, IOException::class) + private fun isMimeType(part: jakarta.mail.internet.MimePart, mimeType: String): Boolean { + // Do not use part.isMimeType(String) as it is broken for MimeBodyPart + // and does not really check the actual content type. + return try { + val ct: jakarta.mail.internet.ContentType = + jakarta.mail.internet.ContentType(part.dataHandler.contentType) + ct.match(mimeType) + } catch (ex: jakarta.mail.internet.ParseException) { + part.contentType.equals(mimeType, ignoreCase = true) + } + } + + /** + * Parses the MimePart to create a DataSource. + * + * @param parent the parent multi-part + * @param part the current part to be processed + * @return the DataSource + * @throws MessagingException creating the DataSource failed + * @throws IOException creating the DataSource failed + */ + @Throws(jakarta.mail.MessagingException::class, IOException::class) + protected fun createDataSource( + parent: jakarta.mail.Multipart?, + part: jakarta.mail.internet.MimePart + ): jakarta.activation.DataSource { + val dataHandler: jakarta.activation.DataHandler = part.dataHandler + val dataSource: jakarta.activation.DataSource = dataHandler.dataSource + val contentType = getBaseMimeType(dataSource.contentType) + var content: ByteArray + dataSource.inputStream.use { inputStream -> content = getContent(inputStream) } + val result: jakarta.mail.util.ByteArrayDataSource = jakarta.mail.util.ByteArrayDataSource(content, contentType) + val dataSourceName = getDataSourceName(part, dataSource) + result.name = dataSourceName + return result + } + + /** @return Returns the mimeMessage. + */ + fun getMimeMessage(): jakarta.mail.internet.MimeMessage { + return mimeMessage + } + + val contentIds: Collection + /** + * Returns a collection of all content-ids in the parsed message. + * + * + * The content-ids are stripped of any angle brackets, i.e. "part1" instead + * of "<part1>". + * + * @return the collection of content ids. + * @since 1.3.4 + */ + get() = Collections.unmodifiableSet(cidMap.keys) + + /** @return true if a plain content is available + */ + fun hasPlainContent(): Boolean { + return plainContent != null + } + + /** @return true if HTML content is available + */ + fun hasHtmlContent(): Boolean { + return htmlContent != null + } + + /** @return true if attachments are available + */ + fun hasAttachments(): Boolean { + return !attachmentList.isEmpty() + } + + /** + * Find an attachment using its name. + * + * @param name the name of the attachment + * @return the corresponding datasource or null if nothing was found + */ + fun findAttachmentByName(name: String): jakarta.activation.DataSource? { + var dataSource: jakarta.activation.DataSource + for (element in attachmentList) { + dataSource = element + if (name.equals(dataSource.name, ignoreCase = true)) { + return dataSource + } + } + return null + } + + /** + * Find an attachment using its content-id. + * + * + * The content-id must be stripped of any angle brackets, + * i.e. "part1" instead of "<part1>". + * + * @param cid the content-id of the attachment + * @return the corresponding datasource or null if nothing was found + * @since 1.3.4 + */ + fun findAttachmentByCid(cid: String): jakarta.activation.DataSource? { + return cidMap[cid] + } + + /** + * Determines the name of the data source if it is not already set. + * + * @param part the mail part + * @param dataSource the data source + * @return the name of the data source or `null` if no name can be determined + * @throws MessagingException accessing the part failed + * @throws UnsupportedEncodingException decoding the text failed + */ + @Throws(jakarta.mail.MessagingException::class, UnsupportedEncodingException::class) + protected fun getDataSourceName(part: jakarta.mail.Part, dataSource: jakarta.activation.DataSource): String? { + var result: String? = dataSource.name + if (result == null || result.isEmpty()) { + result = part.fileName + } + result = if (result != null && !result.isEmpty()) { + jakarta.mail.internet.MimeUtility.decodeText(result) + } else { + null + } + return result + } + + /** + * Read the content of the input stream. + * + * @param is the input stream to process + * @return the content of the input stream + * @throws IOException reading the input stream failed + */ + @Throws(IOException::class) + private fun getContent(`is`: InputStream): ByteArray { + val os = ByteArrayOutputStream() + val isReader = BufferedInputStream(`is`) + BufferedOutputStream(os).use { osWriter -> + var ch: Int + while (isReader.read().also { ch = it } != -1) { + osWriter.write(ch) + } + osWriter.flush() + return os.toByteArray() + } + } + + /** + * Parses the mimeType. + * + * @param fullMimeType the mime type from the mail api + * @return the real mime type + */ + private fun getBaseMimeType(fullMimeType: String): String { + val pos = fullMimeType.indexOf(';') + return if (pos >= 0) { + fullMimeType.substring(0, pos) + } else fullMimeType + } +} diff --git a/backend/testing/src/main/kotlin/io/tolgee/fixtures/retry.kt b/backend/testing/src/main/kotlin/io/tolgee/fixtures/retry.kt index dd39a41fa8..3c59a72005 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/fixtures/retry.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/fixtures/retry.kt @@ -1,6 +1,10 @@ package io.tolgee.fixtures -fun retry(retries: Int = 3, fn: () -> Unit) { +fun retry( + retries: Int = 3, + exceptionMatcher: (Throwable) -> Boolean = { true }, + fn: () -> Unit +) { val thrown = mutableListOf() var passed = false while (thrown.size < retries + 1) { @@ -9,7 +13,11 @@ fun retry(retries: Int = 3, fn: () -> Unit) { passed = true break } catch (th: Throwable) { - thrown.add(th) + if (exceptionMatcher(th)) { + thrown.add(th) + } else { + throw th + } passed = false } } diff --git a/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt b/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt index 7fbd079d98..7acdc02bfd 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt @@ -1,6 +1,7 @@ package io.tolgee.testing import io.tolgee.CleanDbTestListener +import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.TestExecutionListeners @@ -8,7 +9,6 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution import org.springframework.test.context.support.DirtiesContextTestExecutionListener import org.springframework.test.context.transaction.TestTransaction import org.springframework.test.context.transaction.TransactionalTestExecutionListener -import javax.persistence.EntityManager @TestExecutionListeners( value = [ diff --git a/backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt b/backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt index b5ddd5c830..a99d124bc3 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt @@ -12,7 +12,7 @@ import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.ResultActions import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder import java.time.Duration -import java.util.Date +import java.util.* abstract class AuthorizedControllerTest : AbstractControllerTest(), AuthRequestPerformer { private var _userAccount: UserAccount? = null diff --git a/backend/testing/src/main/kotlin/io/tolgee/testing/PermissionTestUtil.kt b/backend/testing/src/main/kotlin/io/tolgee/testing/PermissionTestUtil.kt index 8dbfda7dce..d4d20d83b1 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/testing/PermissionTestUtil.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/testing/PermissionTestUtil.kt @@ -39,9 +39,15 @@ class PermissionTestUtil( val langByTag = { tag: String -> languages.find { it.tag == tag }!!.id } val query = getQueryFn(langByTag) + val typeAndQuery = if (type.isEmpty()) { + "?$query" + } else { + "/$type?$query" + } + test.performAuthPut( "/v2/projects/${project.id}/users/${user.id}" + - "/set-permissions/$type?$query", + "/set-permissions$typeAndQuery", null ) } @@ -57,9 +63,15 @@ class PermissionTestUtil( val langByTag = { tag: String -> languages.find { it.tag == tag }!!.id } val query = getQueryFn(langByTag) + val typeAndQuery = if (type.isEmpty()) { + "?$query" + } else { + "/$type?$query" + } + test.performAuthPut( "/v2/projects/${project.id}/users/${user.id}" + - "/set-permissions/$type?$query", + "/set-permissions$typeAndQuery", null ).andIsOk diff --git a/backend/testing/src/main/kotlin/io/tolgee/testing/assertions/UserApiAppAction.kt b/backend/testing/src/main/kotlin/io/tolgee/testing/assertions/PakAction.kt similarity index 98% rename from backend/testing/src/main/kotlin/io/tolgee/testing/assertions/UserApiAppAction.kt rename to backend/testing/src/main/kotlin/io/tolgee/testing/assertions/PakAction.kt index 2d281e1046..aed189e04d 100644 --- a/backend/testing/src/main/kotlin/io/tolgee/testing/assertions/UserApiAppAction.kt +++ b/backend/testing/src/main/kotlin/io/tolgee/testing/assertions/PakAction.kt @@ -8,7 +8,7 @@ import org.springframework.test.web.servlet.RequestBuilder import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -class UserApiAppAction( +class PakAction( var method: HttpMethod? = null, var body: Any? = null, var apiKey: String? = null, diff --git a/build.gradle b/build.gradle index 01ad9f303d..39ca5d7b75 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,19 @@ buildscript { } } + +plugins { + id("org.openrewrite.rewrite") version("6.3.16") +} + +rewrite { + activeRecipe("org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta") +} + +dependencies { + rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.1.0") +} + project.ext { dbSchemaContainerName = 'tolgee_postgres_dbschema' } @@ -148,6 +161,7 @@ task ktlintFormat(type: JavaExec, group: "formatting") { subprojects { task allDeps(type: DependencyReportTask) {} + ext['hibernate.version'] = hibernateVersion tasks.withType(Test) { testLogging { diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 3ff8f39e77..c7b40de221 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,4 +1,4 @@ -FROM tolgee/base:jdk-14-postgres-13 +FROM tolgee/base:jdk-17-postgres-13 ############# ### Tolgee # diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index a391135622..061930c1ae 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,11 +1,14 @@ -FROM alpine:edge +FROM alpine:3.18 -RUN apk --no-cache add openjdk14 - -#################################################################################### -### Postgres from postgres official Dockerfile # -### https://github.com/docker-library/postgres/blob/master/13/alpine/Dockerfile # -#################################################################################### +######################################################################################## +### Postgres from postgres official Dockerfile # +### https://github.com/docker-library/postgres/blob/master/13/alpine3.18/Dockerfile # +######################################################################################## +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# # 70 is the standard uid/gid for "postgres" in Alpine # https://git.alpinelinux.org/aports/tree/main/postgresql/postgresql.pre-install?h=3.12-stable @@ -24,8 +27,12 @@ ENV LANG en_US.utf8 RUN mkdir /docker-entrypoint-initdb.d ENV PG_MAJOR 13 -ENV PG_VERSION 13.5 -ENV PG_SHA256 9b81067a55edbaabc418aacef457dd8477642827499560b00615a6ea6c13f6b3 +ENV PG_VERSION 13.12 +ENV PG_SHA256 0da1edcee3514b7bc7ba6dbaf0c00499e8ac1590668e8789c50253a6249f218b + +ENV DOCKER_PG_LLVM_DEPS \ + llvm15-dev \ + clang15 RUN set -eux; \ \ @@ -41,10 +48,12 @@ RUN set -eux; \ rm postgresql.tar.bz2; \ \ apk add --no-cache --virtual .build-deps \ + $DOCKER_PG_LLVM_DEPS \ bison \ coreutils \ dpkg-dev dpkg \ flex \ + g++ \ gcc \ krb5-dev \ libc-dev \ @@ -52,15 +61,12 @@ RUN set -eux; \ libxml2-dev \ libxslt-dev \ linux-headers \ - llvm-dev clang g++ \ make \ openldap-dev \ openssl-dev \ -# configure: error: prove not found - perl-utils \ -# configure: error: Perl module IPC::Run is required to run TAP tests - perl-ipc-run \ perl-dev \ + perl-ipc-run \ + perl-utils \ python3-dev \ tcl-dev \ util-linux-dev \ @@ -79,9 +85,16 @@ RUN set -eux; \ # explicitly update autoconf config.guess and config.sub so they support more arches/libcs wget -O config/config.guess 'https://git.savannah.gnu.org/cgit/config.git/plain/config.guess?id=7d3d27baf8107b630586c962c057e22149653deb'; \ wget -O config/config.sub 'https://git.savannah.gnu.org/cgit/config.git/plain/config.sub?id=7d3d27baf8107b630586c962c057e22149653deb'; \ + \ +# https://git.alpinelinux.org/aports/tree/community/postgresql12/APKBUILD?h=3.18-stable&id=a470294e6d6ca7059e41c54769b7c3c26ec901d4#n158 + export LLVM_CONFIG="/usr/lib/llvm15/bin/llvm-config"; \ +# https://git.alpinelinux.org/aports/tree/community/postgresql12/APKBUILD?h=3.18-stable&id=a470294e6d6ca7059e41c54769b7c3c26ec901d4#n163 + export CLANG=clang-15; \ + \ # configure options taken from: # https://anonscm.debian.org/cgit/pkg-postgresql/postgresql.git/tree/debian/rules?h=9.5 ./configure \ + --enable-option-checking=fatal \ --build="$gnuArch" \ # "/usr/src/postgresql/src/backend/access/common/tupconvert.c:105: undefined reference to `libintl_gettext'" # --enable-nls \ @@ -98,7 +111,6 @@ RUN set -eux; \ --prefix=/usr/local \ --with-includes=/usr/local/include \ --with-libraries=/usr/local/lib \ - --with-krb5 \ --with-gssapi \ --with-ldap \ --with-tcl \ @@ -128,9 +140,13 @@ RUN set -eux; \ $runDeps \ bash \ su-exec \ -# tzdata is optional, but only adds around 1Mb to image size and is recommended by Django documentation: -# https://docs.djangoproject.com/en/1.10/ref/databases/#optimizing-postgresql-s-configuration tzdata \ + zstd \ +# https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split + icu-data-full \ +# nss_wrapper is not availble on ppc64le: "test case segfaults in ppc64le" +# https://git.alpinelinux.org/aports/commit/testing/nss_wrapper/APKBUILD?h=3.17-stable&id=94d81ceeb58cff448d489bbcbe9a6d40c9991663 + $([ "$(apk --print-arch)" != 'ppc64le' ] && echo 'nss_wrapper') \ ; \ apk del --no-network .build-deps; \ cd /; \ @@ -139,7 +155,8 @@ RUN set -eux; \ /usr/local/share/doc \ /usr/local/share/man \ ; \ - \ + echo '{"spdxVersion":"SPDX-2.3","SPDXID":"SPDXRef-DOCUMENT","name":"postgres-sbom","packages":[{"name":"postgres","versionInfo":"13.12","SPDXID":"SPDXRef-Package--postgres","externalRefs":[{"referenceCategory":"PACKAGE-MANAGER","referenceType":"purl","referenceLocator":"pkg:generic/postgres@13.12?os_name=alpine&os_version=3.18"}],"licenseDeclared":"PostgreSQL"}]}' > /usr/local/postgres.spdx.json \ + ; \ postgres --version # make the sample config easier to munge (and "correct by default") @@ -148,17 +165,18 @@ RUN set -eux; \ sed -ri "s!^#?(listen_addresses)\s*=\s*\S+.*!\1 = '*'!" /usr/local/share/postgresql/postgresql.conf.sample; \ grep -F "listen_addresses = '*'" /usr/local/share/postgresql/postgresql.conf.sample -RUN mkdir -p /var/run/postgresql && chown -R postgres:postgres /var/run/postgresql && chmod 2777 /var/run/postgresql +RUN mkdir -p /var/run/postgresql && chown -R postgres:postgres /var/run/postgresql && chmod 3777 /var/run/postgresql ENV PGDATA /var/lib/postgresql/data # this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values) -RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA" +RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 1777 "$PGDATA" VOLUME /var/lib/postgresql/data -## Postgres entrypoint run by supervisor COPY postgres-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/postgres-entrypoint.sh +RUN apk --no-cache add openjdk17 + # We set the default STOPSIGNAL to SIGINT, which corresponds to what PostgreSQL # calls "Fast Shutdown mode" wherein new connections are disallowed and any # in-progress transactions are aborted, allowing PostgreSQL to stop cleanly and diff --git a/docker/base/README.md b/docker/base/README.md index 9cccb766e0..03f857e8d9 100644 --- a/docker/base/README.md +++ b/docker/base/README.md @@ -4,5 +4,16 @@ This Docker image contains JDK and Postgres to run Tolgee. ## It is published manually to DockerHub To build it and publish run: - docker buildx build . -t tolgee/base:jdk-14-postgres-13 --platform linux/arm64,linux/amd64 --push + docker buildx build . -t tolgee/base:jdk-17-postgres-13 --platform linux/arm64,linux/amd64 --push + + +## Troubleshooting + +Sometimes it fails, with + + ERROR: Multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use") + +Which can be solved by creating a new builder: + + docker buildx create --platform linux/arm64,linux/amd64 --use diff --git a/docker/base/postgres-entrypoint.sh b/docker/base/postgres-entrypoint.sh index 550f7299ff..a383a36487 100644 --- a/docker/base/postgres-entrypoint.sh +++ b/docker/base/postgres-entrypoint.sh @@ -11,7 +11,7 @@ file_env() { local fileVar="${var}_FILE" local def="${2:-}" if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then - echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + printf >&2 'error: both %s and %s are set (but are exclusive)\n' "$var" "$fileVar" exit 1 fi local val="$def" @@ -38,14 +38,14 @@ docker_create_db_directories() { mkdir -p "$PGDATA" # ignore failure since there are cases where we can't chmod (and PostgreSQL might fail later anyhow - it's picky about permissions of this directory) - chmod 700 "$PGDATA" || : + chmod 00700 "$PGDATA" || : # ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289 mkdir -p /var/run/postgresql || : - chmod 775 /var/run/postgresql || : + chmod 03775 /var/run/postgresql || : # Create the transaction log directory before initdb is run so the directory is owned by the correct user - if [ -n "$POSTGRES_INITDB_WALDIR" ]; then + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then mkdir -p "$POSTGRES_INITDB_WALDIR" if [ "$user" = '0' ]; then find "$POSTGRES_INITDB_WALDIR" \! -user postgres -exec chown postgres '{}' + @@ -77,21 +77,22 @@ docker_init_database_dir() { NSS_WRAPPER_GROUP="$(mktemp)" export LD_PRELOAD="$wrapper" NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP local gid; gid="$(id -g)" - echo "postgres:x:$uid:$gid:PostgreSQL:$PGDATA:/bin/false" > "$NSS_WRAPPER_PASSWD" - echo "postgres:x:$gid:" > "$NSS_WRAPPER_GROUP" + printf 'postgres:x:%s:%s:PostgreSQL:%s:/bin/false\n' "$uid" "$gid" "$PGDATA" > "$NSS_WRAPPER_PASSWD" + printf 'postgres:x:%s:\n' "$gid" > "$NSS_WRAPPER_GROUP" break fi done fi - if [ -n "$POSTGRES_INITDB_WALDIR" ]; then + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then set -- --waldir "$POSTGRES_INITDB_WALDIR" "$@" fi - eval 'initdb --username="$POSTGRES_USER" --pwfile=<(echo "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"' + # --pwfile refuses to handle a properly-empty file (hence the "\n"): https://github.com/docker-library/postgres/issues/1025 + eval 'initdb --username="$POSTGRES_USER" --pwfile=<(printf "%s\n" "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"' # unset/cleanup "nss_wrapper" bits - if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then + if [[ "${LD_PRELOAD:-}" == */libnss_wrapper.so ]]; then rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP fi @@ -157,7 +158,7 @@ docker_process_init_files() { # psql here for backwards compatibility "${psql[@]}" psql=( docker_process_sql ) - echo + printf '\n' local f for f; do case "$f" in @@ -165,19 +166,20 @@ docker_process_init_files() { # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936 # https://github.com/docker-library/postgres/pull/452 if [ -x "$f" ]; then - echo "$0: running $f" + printf '%s: running %s\n' "$0" "$f" "$f" else - echo "$0: sourcing $f" + printf '%s: sourcing %s\n' "$0" "$f" . "$f" fi ;; - *.sql) echo "$0: running $f"; docker_process_sql -f "$f"; echo ;; - *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;; - *.sql.xz) echo "$0: running $f"; xzcat "$f" | docker_process_sql; echo ;; - *) echo "$0: ignoring $f" ;; + *.sql) printf '%s: running %s\n' "$0" "$f"; docker_process_sql -f "$f"; printf '\n' ;; + *.sql.gz) printf '%s: running %s\n' "$0" "$f"; gunzip -c "$f" | docker_process_sql; printf '\n' ;; + *.sql.xz) printf '%s: running %s\n' "$0" "$f"; xzcat "$f" | docker_process_sql; printf '\n' ;; + *.sql.zst) printf '%s: running %s\n' "$0" "$f"; zstd -dc "$f" | docker_process_sql; printf '\n' ;; + *) printf '%s: ignoring %s\n' "$0" "$f" ;; esac - echo + printf '\n' done } @@ -208,7 +210,7 @@ docker_setup_db() { POSTGRES_DB= docker_process_sql --dbname postgres --set db="$POSTGRES_DB" <<-'EOSQL' CREATE DATABASE :"db" ; EOSQL - echo + printf '\n' fi } @@ -240,18 +242,14 @@ pg_setup_hba_conf() { local auth # check the default/configured encryption and use that as the auth method auth="$(postgres -C password_encryption "$@")" - # postgres 9 only reports "on" and not "md5" - if [ "$auth" = 'on' ]; then - auth='md5' - fi : "${POSTGRES_HOST_AUTH_METHOD:=$auth}" { - echo + printf '\n' if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then - echo '# warning trust is enabled for all connections' - echo '# see https://www.postgresql.org/docs/12/auth-trust.html' + printf '# warning trust is enabled for all connections\n' + printf '# see https://www.postgresql.org/docs/12/auth-trust.html\n' fi - echo "host all all all $POSTGRES_HOST_AUTH_METHOD" + printf 'host all all all %s\n' "$POSTGRES_HOST_AUTH_METHOD" } >> "$PGDATA/pg_hba.conf" } @@ -331,13 +329,17 @@ _main() { docker_temp_server_stop unset PGPASSWORD - echo - echo 'PostgreSQL init process complete; ready for start up.' - echo + cat <<-'EOM' + + PostgreSQL init process complete; ready for start up. + + EOM else - echo - echo 'PostgreSQL Database directory appears to contain a database; Skipping initialization' - echo + cat <<-'EOM' + + PostgreSQL Database directory appears to contain a database; Skipping initialization + + EOM fi fi diff --git a/e2e/cypress.config.ts b/e2e/cypress.config.ts index 0691895fa5..2ba11fcf11 100644 --- a/e2e/cypress.config.ts +++ b/e2e/cypress.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ chromeWebSecurity: false, viewportHeight: 1080, viewportWidth: 1440, + defaultCommandTimeout: 20000, e2e: { // We've imported your old cypress plugins here. // You may want to clean this up later by importing these. diff --git a/e2e/cypress/common/loading.ts b/e2e/cypress/common/loading.ts index 6b6a279774..350ee02152 100644 --- a/e2e/cypress/common/loading.ts +++ b/e2e/cypress/common/loading.ts @@ -3,7 +3,10 @@ export const expectGlobalLoading = () => { return cy.gcy('global-loading').should('not.exist'); }; -export const waitForGlobalLoading = (waitTime = 100, timeout = 4000) => { +export const waitForGlobalLoading = ( + waitTime = 100, + timeout = Cypress.config('defaultCommandTimeout') +) => { cy.wait(waitTime); return cy.gcy('global-loading', { timeout }).should('not.exist'); }; diff --git a/e2e/cypress/common/translations.ts b/e2e/cypress/common/translations.ts index b927dd0e78..a1b2ada827 100644 --- a/e2e/cypress/common/translations.ts +++ b/e2e/cypress/common/translations.ts @@ -166,6 +166,8 @@ export const forEachView = ( }; export function createProjectWithThreeLanguages() { + let project: ProjectDTO; + return login() .then(() => createProject({ @@ -190,7 +192,7 @@ export function createProjectWithThreeLanguages() { }) ) .then((r) => { - const project = r.body as ProjectDTO; + project = r.body as ProjectDTO; selectLangsInLocalstorage(project.id, ['en']); const promises = []; for (let i = 1; i < 5; i++) { @@ -204,7 +206,9 @@ export function createProjectWithThreeLanguages() { } selectLangsInLocalstorage(project.id, ['en', 'cs', 'es']); - - return Cypress.Promise.all(promises).then(() => project); + return Cypress.Promise.all(promises); + }) + .then(() => { + return project; }); } diff --git a/e2e/cypress/e2e/organizations/organizationBasePermissions.cy.ts b/e2e/cypress/e2e/organizations/organizationBasePermissions.cy.ts index 4b0ced3392..636c66406d 100644 --- a/e2e/cypress/e2e/organizations/organizationBasePermissions.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationBasePermissions.cy.ts @@ -9,18 +9,22 @@ import { } from '../../common/permissionsMenu'; describe('Organization Base permissions', () => { + let organizationData: Record; + beforeEach(() => { login(); organizationTestData.clean(); - organizationTestData.generate(); - visitProfile(); + organizationTestData.generate().then((res) => { + organizationData = res.body as any; + visitProfile('Tolgee'); + }); }); it('changes member privileges', () => { gcy('organization-side-menu').contains('Member permissions').click(); gcy('permissions-menu-button').click(); permissionsMenuSelectRole('Translate', { confirm: true }); - visitMemberPrivileges(); + visitMemberPrivileges('Tolgee'); gcy('permissions-menu-button').contains('Translate'); }); @@ -28,12 +32,12 @@ describe('Organization Base permissions', () => { gcy('organization-side-menu').contains('Member permissions').click(); gcy('permissions-menu-button').click(); permissionsMenuSelectRole('Translate', { confirm: true }); - visitProfile(); + visitProfile('Tolgee'); gcy('organization-name-field').within(() => cy.get('input').should('have.value', 'Tolgee') ); gcy('organization-address-part-field').within(() => - cy.get('input').should('have.value', 'tolgee') + cy.get('input').should('contain.value', 'tolgee') ); gcy('organization-description-field').within(() => cy.get('input').should('have.value', 'This is us') @@ -52,11 +56,20 @@ describe('Organization Base permissions', () => { organizationTestData.clean(); }); - const visitProfile = () => { - cy.visit(`${HOST}/organizations/tolgee/profile`); + const visitProfile = (name: string) => { + visitPath(name, `/profile`); }; - const visitMemberPrivileges = () => { - cy.visit(`${HOST}/organizations/tolgee/member-privileges`); + const visitMemberPrivileges = (name: string) => { + visitPath(name, `/member-privileges`); + }; + + function visitSlug(slug: string, path: string) { + cy.visit(`${HOST}/organizations/${slug}${path}`); + } + + const visitPath = (name: string, path: string) => { + const slug = organizationData[name].slug; + visitSlug(slug, path); }; }); diff --git a/e2e/cypress/e2e/organizations/organizationInvitations.cy.ts b/e2e/cypress/e2e/organizations/organizationInvitations.cy.ts index e6f92b00bd..3e2536076d 100644 --- a/e2e/cypress/e2e/organizations/organizationInvitations.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationInvitations.cy.ts @@ -12,11 +12,15 @@ import { import { organizationTestData } from '../../common/apiCalls/testData/testData'; describe('Organization Invitations', () => { + let organizationData: Record; + beforeEach(() => { login(); organizationTestData.clean(); - organizationTestData.generate(); - visit(); + organizationTestData.generate().then((res) => { + organizationData = res.body as any; + visit(); + }); }); it('generates invitations', () => { @@ -65,13 +69,20 @@ describe('Organization Invitations', () => { organizationTestData.clean(); }); + function getTolgeeSlug() { + return organizationData['Tolgee'].slug; + } + const visit = () => { - cy.visit(`${HOST}/organizations/tolgee/members`); + const slug = getTolgeeSlug(); + cy.visit(`${HOST}/organizations/${slug}/members`); }; const generateInvitation = (roleType: 'MEMBER' | 'OWNER', email = false) => { let clipboard: string; - cy.visit(`${HOST}/organizations/tolgee/members`, { + const slug = getTolgeeSlug(); + + cy.visit(`${HOST}/organizations/${slug}/members`, { onBeforeLoad(win) { if (!email) { cy.stub(win, 'prompt').callsFake((_, input) => { diff --git a/e2e/cypress/e2e/organizations/organizationsList.cy.ts b/e2e/cypress/e2e/organizations/organizationsList.cy.ts index 499ff12198..c56c9b5f48 100644 --- a/e2e/cypress/e2e/organizations/organizationsList.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationsList.cy.ts @@ -28,7 +28,7 @@ describe('Organization List', () => { cy.get('input').type('What a nice organization') ); gcy('organization-address-part-field').within(() => - cy.get('input').should('have.value', 'what-a-nice-organization') + cy.get('input').should('contain.value', 'what-a-nice-organization') ); gcy('organization-description-field').within(() => cy.get('input').type('Very nice organization! Which is nice to create!') @@ -47,7 +47,7 @@ describe('Organization List', () => { cy.get('input').type('What a nice organization') ); gcy('organization-address-part-field').within(() => - cy.get('input').should('have.value', 'what-a-nice-organization') + cy.get('input').should('contain.value', 'what-a-nice-organization') ); clickGlobalSave(); assertMessage('Organization created'); diff --git a/e2e/cypress/e2e/organizations/organizationsMembers.cy.ts b/e2e/cypress/e2e/organizations/organizationsMembers.cy.ts index d8f2a7e008..782a951067 100644 --- a/e2e/cypress/e2e/organizations/organizationsMembers.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationsMembers.cy.ts @@ -10,11 +10,19 @@ import { organizationTestData } from '../../common/apiCalls/testData/testData'; import { login } from '../../common/apiCalls/common'; describe('Organization Members', () => { + let organizationData: Record; + beforeEach(() => { login(); organizationTestData.clean(); - organizationTestData.generate(); - visit(); + organizationTestData + .generate() + .then((res) => { + return (organizationData = res.body as any); + }) + .then(() => { + visit('Tolgee'); + }); }); afterEach(() => { @@ -91,7 +99,7 @@ describe('Organization Members', () => { }); it('Paginates', () => { - cy.visit(`${HOST}/organizations/facebook/members`); + visit('Facebook'); gcy('global-paginated-list').contains('Cukrberg').should('be.visible'); gcy('global-paginated-list') .contains('owner@zzzcool16.com') @@ -102,12 +110,9 @@ describe('Organization Members', () => { .should('be.visible'); }); - after(() => { - organizationTestData.clean(); - }); - - const visit = () => { - cy.visit(`${HOST}/organizations/tolgee/members`); + const visit = (name: string) => { + const slug = organizationData[name].slug; + cy.visit(`${HOST}/organizations/${slug}/members`); }; const setGoldbergMember = () => { diff --git a/e2e/cypress/e2e/organizations/organizationsSettings.cy.ts b/e2e/cypress/e2e/organizations/organizationsSettings.cy.ts index 778d58d0ed..04f3c719e1 100644 --- a/e2e/cypress/e2e/organizations/organizationsSettings.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationsSettings.cy.ts @@ -11,16 +11,19 @@ import { login } from '../../common/apiCalls/common'; import { organizationTestData } from '../../common/apiCalls/testData/testData'; describe('Organization Settings', () => { + let organizationData: Record; + beforeEach(() => { login(); organizationTestData.clean(); - organizationTestData.generate(); - visitProfile(); + organizationTestData.generate().then((res) => { + organizationData = res.body as any; + visit('Tolgee'); + }); }); const newValues = { name: 'What a nice organization', - addressPart: 'what-a-nice-organization', description: 'This is an nice updated value!', }; @@ -28,15 +31,22 @@ describe('Organization Settings', () => { gcy('organization-name-field').within(() => cy.get('input').clear().type(newValues.name) ); - gcy('organization-address-part-field').within(() => - cy.get('input').should('have.value', newValues.addressPart) - ); + gcy('organization-address-part-field').within(() => { + cy.get('input').should('contain.value', 'what-a-nice-organization'); + }); gcy('organization-description-field').within(() => cy.get('input').clear().type(newValues.description) ); clickGlobalSave(); cy.contains('Organization settings updated').should('be.visible'); - cy.visit(`${HOST}/organizations/what-a-nice-organization/profile`); + + cy.reload(); + + cy.gcy('global-user-menu-button').click(); + cy.gcy('user-menu-organization-settings') + .contains('Organization settings') + .click(); + gcy('organization-name-field').within(() => cy.get('input').should('have.value', newValues.name) ); @@ -47,7 +57,7 @@ describe('Organization Settings', () => { it('Gates cannot change Tolgee settings', () => { login('gates@microsoft.com'); - visitProfile(); + visit('Tolgee'); switchToOrganization('Tolgee'); cy.gcy('global-user-menu-button').click(); cy.gcy('user-menu-organization-settings') @@ -77,7 +87,13 @@ describe('Organization Settings', () => { organizationTestData.clean(); }); - const visitProfile = () => { - cy.visit(`${HOST}/organizations/tolgee/profile`); + function visitSlug(slug: string) { + cy.visit(`${HOST}/organizations/${slug}/profile`); + } + + const visit = (name: string) => { + cy.log(`Visiting ${name}`); + const slug = organizationData[name].slug; + visitSlug(slug); }; }); diff --git a/e2e/cypress/e2e/organizations/organizationsSwitching.cy.ts b/e2e/cypress/e2e/organizations/organizationsSwitching.cy.ts index d9465f8939..c4f9b7a645 100644 --- a/e2e/cypress/e2e/organizations/organizationsSwitching.cy.ts +++ b/e2e/cypress/e2e/organizations/organizationsSwitching.cy.ts @@ -9,11 +9,17 @@ import { } from '../../common/shared'; describe('Organization switching', () => { + let organizationData: Record; + beforeEach(() => { login(); - organizationTestData.clean(); - organizationTestData.generate(); - visit(); + organizationTestData.clean({ + timeout: 120000, + }); + organizationTestData.generate().then((res) => { + organizationData = res.body as any; + visit(); + }); }); afterEach(() => { @@ -30,8 +36,13 @@ describe('Organization switching', () => { assertOrganizationIsPreferred('Facebook'); }); + function visitMembers() { + const slug = organizationData['Tolgee'].slug; + cy.visit(`${HOST}/organizations/${slug}/members`); + } + it('switches correctly when going directly to organization', () => { - cy.visit(`${HOST}/organizations/tolgee/members`); + visitMembers(); switchToOrganizationWithSearch('admin'); gcy('organization-switch').contains('admin').should('be.visible'); cy.go('back'); @@ -39,7 +50,7 @@ describe('Organization switching', () => { }); it('switches correctly when in organization settings', () => { - cy.visit(`${HOST}/organizations/tolgee/members`); + visitMembers(); switchToOrganization('Microsoft'); cy.waitForDom(); gcy('settings-menu-item') diff --git a/e2e/cypress/e2e/projects/leaving.cy.ts b/e2e/cypress/e2e/projects/leaving.cy.ts index 1015242297..d7eb73934b 100644 --- a/e2e/cypress/e2e/projects/leaving.cy.ts +++ b/e2e/cypress/e2e/projects/leaving.cy.ts @@ -5,7 +5,7 @@ import { assertMessage, confirmHardMode } from '../../common/shared'; import { projectLeavingTestData } from '../../common/apiCalls/testData/testData'; import { login } from '../../common/apiCalls/common'; -describe('Projects Basics', () => { +describe('Leaving project', () => { beforeEach(() => { projectLeavingTestData.clean(); projectLeavingTestData.generate(); diff --git a/ee/backend/app/build.gradle b/ee/backend/app/build.gradle index 5efd89bdc6..9745b2be2b 100644 --- a/ee/backend/app/build.gradle +++ b/ee/backend/app/build.gradle @@ -2,18 +2,17 @@ buildscript { repositories { mavenCentral() } - dependencies { - classpath "org.hibernate:hibernate-gradle-plugin:5.6.10.Final" - } + dependencies {} } plugins { id 'io.spring.dependency-management' id 'org.jetbrains.kotlin.jvm' id 'org.liquibase.gradle' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id "java" id "kotlin-allopen" + id "org.hibernate.orm" } apply plugin: "kotlin-allopen" @@ -21,9 +20,9 @@ apply plugin: "org.jetbrains.kotlin.plugin.spring" apply plugin: 'org.hibernate.orm' allOpen { - annotation("javax.persistence.Entity") - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Service") annotation("org.springframework.stereotype.Component") annotation("org.springframework.transaction.annotation.Transactional") @@ -37,7 +36,7 @@ repositories { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" } apply from: "$rootDir/gradle/liquibase.gradle" @@ -58,19 +57,24 @@ dependencies { /** * SPRING DOC */ - implementation libs.springDocOpenApiWebMvcCore + implementation libs.springDocWebmvcApi implementation libs.springDocOpenApiUi - implementation libs.springDocOpenApiKotlin - implementation libs.springDocOpenApiDataRest - implementation libs.springDocOpenApiHateoas + implementation libs.springDocOpenApiCommon + + /** + * MISC + */ implementation libs.hibernateTypes + implementation libs.jacksonKotlin /** * Liquibase */ implementation libs.liquibaseCore liquibaseRuntime libs.liquibaseCore + liquibaseRuntime libs.jacksonModuleKotlin + liquibaseRuntime libs.liquibasePicoli liquibaseRuntime 'org.postgresql:postgresql' liquibaseRuntime('org.liquibase:liquibase-groovy-dsl:3.0.2') liquibaseRuntime libs.liquibaseHibernate @@ -82,11 +86,21 @@ dependencies { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +hibernate { + enhancement { + lazyInitialization = true + dirtyTracking = true + } +} + +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} jar { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/EeProperties.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/EeProperties.kt index fb020778c8..7deb697636 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/EeProperties.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/EeProperties.kt @@ -1,10 +1,8 @@ package io.tolgee.ee import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.ConstructorBinding @ConfigurationProperties(prefix = "tolgee.ee") -@ConstructorBinding class EeProperties( var licenseServer: String = "https://app.tolgee.io", ) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/AdvancedPermissionController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/AdvancedPermissionController.kt index 2ea02ab0a8..0d81a04e25 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/AdvancedPermissionController.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/AdvancedPermissionController.kt @@ -15,7 +15,7 @@ import io.tolgee.security.authentication.RequiresSuperAuthentication import io.tolgee.security.authorization.RequiresOrganizationRole import io.tolgee.security.authorization.RequiresProjectPermissions import io.tolgee.service.organization.OrganizationRoleService -import org.springdoc.api.annotations.ParameterObject +import org.springdoc.core.annotations.ParameterObject import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/ContentStorageController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/ContentStorageController.kt index 54e0f867e3..c36c8049a7 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/ContentStorageController.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/ContentStorageController.kt @@ -14,7 +14,8 @@ import io.tolgee.model.enums.Scope import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -27,7 +28,6 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection") @RestController diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/WebhookConfigController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/WebhookConfigController.kt index 9599dc56f9..7a17053863 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/WebhookConfigController.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/WebhookConfigController.kt @@ -14,7 +14,8 @@ import io.tolgee.model.webhook.WebhookConfig import io.tolgee.security.ProjectHolder import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authorization.RequiresProjectPermissions -import org.springdoc.api.annotations.ParameterObject +import jakarta.validation.Valid +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PagedResourcesAssembler import org.springframework.hateoas.PagedModel @@ -27,7 +28,6 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @Suppress("MVCPathVariableInspection") @RestController diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/AzureContentStorageConfigProcessor.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/AzureContentStorageConfigProcessor.kt index 85491ffce7..64faa2c9ff 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/AzureContentStorageConfigProcessor.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/AzureContentStorageConfigProcessor.kt @@ -7,8 +7,8 @@ import io.tolgee.exceptions.BadRequestException import io.tolgee.model.contentDelivery.AzureContentStorageConfig import io.tolgee.model.contentDelivery.ContentStorage import io.tolgee.model.contentDelivery.ContentStorageType +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class AzureContentStorageConfigProcessor : ContentStorageConfigProcessor { diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/ContentStorageConfigProcessor.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/ContentStorageConfigProcessor.kt index a04a4ec1d9..b2061489c0 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/ContentStorageConfigProcessor.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/ContentStorageConfigProcessor.kt @@ -4,7 +4,7 @@ import io.tolgee.dtos.contentDelivery.ContentStorageRequest import io.tolgee.model.contentDelivery.ContentStorage import io.tolgee.model.contentDelivery.ContentStorageType import io.tolgee.model.contentDelivery.StorageConfig -import javax.persistence.EntityManager +import jakarta.persistence.EntityManager interface ContentStorageConfigProcessor { fun getItemFromDto(dto: ContentStorageRequest): StorageConfig? diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/S3ContentStorageConfigProcessor.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/S3ContentStorageConfigProcessor.kt index 8c9641d074..e5ba6d9f7e 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/S3ContentStorageConfigProcessor.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/S3ContentStorageConfigProcessor.kt @@ -7,8 +7,8 @@ import io.tolgee.exceptions.BadRequestException import io.tolgee.model.contentDelivery.ContentStorage import io.tolgee.model.contentDelivery.ContentStorageType import io.tolgee.model.contentDelivery.S3ContentStorageConfig +import jakarta.persistence.EntityManager import org.springframework.stereotype.Component -import javax.persistence.EntityManager @Component class S3ContentStorageConfigProcessor : ContentStorageConfigProcessor { diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/configuration/EeLiquibaseConfiguration.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/configuration/EeLiquibaseConfiguration.kt index 832a658d12..1e90abf847 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/configuration/EeLiquibaseConfiguration.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/configuration/EeLiquibaseConfiguration.kt @@ -1,5 +1,6 @@ package io.tolgee.ee.configuration +import io.tolgee.PostgresRunner import liquibase.integration.spring.SpringLiquibase import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.context.annotation.Bean @@ -13,9 +14,10 @@ import javax.sql.DataSource class EeLiquibaseConfiguration { @Bean("ee-liquibase") - fun liquibase(dataSource: DataSource): SpringLiquibase { + fun liquibase(dataSource: DataSource, postgresRunner: PostgresRunner?): SpringLiquibase { val liquibase = SpringLiquibase() + liquibase.setShouldRun(postgresRunner?.shouldRunMigrations != false) liquibase.dataSource = dataSource liquibase.changeLog = "classpath:db/changelog/ee-schema.xml" liquibase.defaultSchema = "ee" diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionDto.kt index 359f2c72ef..2eda9eaa50 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionDto.kt @@ -1,6 +1,6 @@ package io.tolgee.ee.data -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class GetMySubscriptionDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt index 7ea69663db..61d345120f 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt @@ -1,7 +1,7 @@ package io.tolgee.ee.data -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank class PrepareSetLicenseKeyDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReleaseKeyDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReleaseKeyDto.kt index 92020571f9..d1546458e5 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReleaseKeyDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReleaseKeyDto.kt @@ -1,6 +1,6 @@ package io.tolgee.ee.data -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class ReleaseKeyDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt index 0f4495a90f..dfbdbc8596 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt @@ -1,7 +1,7 @@ package io.tolgee.ee.data -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank class ReportUsageDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyDto.kt index 8e3deac130..4cc6b919ad 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyDto.kt @@ -1,6 +1,6 @@ package io.tolgee.ee.data -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class SetLicenseKeyDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt index c5f56c6b76..0a0f096c4f 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt @@ -1,7 +1,7 @@ package io.tolgee.ee.data -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank class SetLicenseKeyLicensingDto( @field:NotBlank diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt index befd543815..233ddc0ca8 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt @@ -1,34 +1,23 @@ package io.tolgee.ee.model -import com.vladmihalcea.hibernate.type.array.EnumArrayType +import io.hypersistence.utils.hibernate.type.array.EnumArrayType import io.tolgee.constants.Feature import io.tolgee.ee.data.SubscriptionStatus import io.tolgee.model.AuditModel +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import org.hibernate.annotations.ColumnDefault import org.hibernate.annotations.Parameter import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef import java.util.* -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.EnumType -import javax.persistence.Enumerated -import javax.persistence.Id -import javax.persistence.Table -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull @Entity -@TypeDef( - name = "enum-array", - typeClass = EnumArrayType::class, - parameters = [ - Parameter( - name = EnumArrayType.SQL_ARRAY_TYPE, - value = "varchar" - ) - ] -) @Table(schema = "ee") class EeSubscription : AuditModel() { @field:Id @@ -45,7 +34,7 @@ class EeSubscription : AuditModel() { var cancelAtPeriodEnd: Boolean = false - @Type(type = "enum-array") + @Type(EnumArrayType::class, parameters = [Parameter(name = EnumArrayType.SQL_ARRAY_TYPE, value = "varchar")]) @Column(name = "enabled_features", columnDefinition = "varchar[]") var enabledFeatures: Array = arrayOf() get() { diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/ContentStorageService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/ContentStorageService.kt index e17e3fcffe..3cda80413f 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/ContentStorageService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/ContentStorageService.kt @@ -13,12 +13,12 @@ import io.tolgee.model.contentDelivery.ContentStorage import io.tolgee.model.contentDelivery.ContentStorageType import io.tolgee.model.contentDelivery.StorageConfig import io.tolgee.repository.contentDelivery.ContentStorageRepository +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import java.io.Serializable -import javax.persistence.EntityManager -import javax.transaction.Transactional @Service class ContentStorageService( @@ -55,7 +55,7 @@ class ContentStorageService( } private fun clearOther(contentStorage: ContentStorage) { - ContentStorageType.entries.toTypedArray().forEach { + ContentStorageType.values().forEach { getProcessor(it).clearParentEntity(contentStorage, entityManager) } } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/WebhookConfigService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/WebhookConfigService.kt index ab93be6d36..5d9b898335 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/WebhookConfigService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/WebhookConfigService.kt @@ -10,10 +10,10 @@ import io.tolgee.model.Project import io.tolgee.model.webhook.WebhookConfig import io.tolgee.repository.WebhookConfigRepository import io.tolgee.service.automations.AutomationService +import jakarta.transaction.Transactional import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service -import javax.transaction.Transactional @Service class WebhookConfigService( diff --git a/ee/backend/tests/build.gradle b/ee/backend/tests/build.gradle index 878da9948a..feabd8d3e5 100644 --- a/ee/backend/tests/build.gradle +++ b/ee/backend/tests/build.gradle @@ -9,7 +9,7 @@ buildscript { plugins { id 'io.spring.dependency-management' id 'org.jetbrains.kotlin.jvm' - id 'org.springframework.boot' + id 'org.springframework.boot' apply false id "java" id "kotlin-allopen" } @@ -18,8 +18,8 @@ apply plugin: "kotlin-allopen" apply plugin: "org.jetbrains.kotlin.plugin.spring" allOpen { - annotation("javax.persistence.MappedSuperclass") - annotation("javax.persistence.Embeddable") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") annotation("org.springframework.stereotype.Service") annotation("org.springframework.stereotype.Component") annotation("org.springframework.transaction.annotation.Transactional") @@ -33,7 +33,7 @@ repositories { test { useJUnitPlatform() - maxHeapSize = "2048m" + maxHeapSize = "4096m" } dependencies { @@ -55,8 +55,11 @@ dependencies { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } -tasks.findByName("jar").enabled(true) -tasks.findByName("bootJar").enabled(false) +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} diff --git a/ee/backend/tests/src/test/resources/application.yaml b/ee/backend/tests/src/test/resources/application.yaml index 36a4a01d0d..7b0f3ab515 100644 --- a/ee/backend/tests/src/test/resources/application.yaml +++ b/ee/backend/tests/src/test/resources/application.yaml @@ -7,6 +7,8 @@ spring: show-sql: true properties: hibernate: + order_by: + default_null_ordering: first jdbc: batch_size: 1000 order_inserts: true @@ -34,8 +36,9 @@ spring: tolgee: postgres-autostart: enabled: true - container-name: tolgee_backend_tests_postgres + container-name: tolgee_backend_tests_postgres_main port: 55433 + stop: false data-path: ./build/test_data authentication: native-enabled: true @@ -54,6 +57,7 @@ tolgee: internal: fake-mt-providers: true mock-free-plan: true + disable-initial-user-creation: true cache: caffeine-max-size: 1000 machine-translation: diff --git a/gradle.properties b/gradle.properties index d488ebe2e5..598eae6393 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,10 @@ kotlinVersion=1.9.10 -springBootVersion=2.7.13 -springDocVersion=1.7.0 +springBootVersion=3.1.5 +springDocVersion=2.2.0 jjwtVersion=0.11.2 +hibernateVersion=6.3.1.Final amazonAwsSdkVersion=2.20.8 springDependencyManagementVersion=1.0.11.RELEASE -org.gradle.jvmargs=-Xmx8g -Dkotlin.daemon.jvm.options=-Xmx6g +org.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx6g org.gradle.parallel=true jacksonVersion=2.13.5 diff --git a/gradle/liquibase.gradle b/gradle/liquibase.gradle index 5131124600..1d26c3955a 100644 --- a/gradle/liquibase.gradle +++ b/gradle/liquibase.gradle @@ -4,7 +4,7 @@ ext { url : "jdbc:postgresql://localhost:55432/postgres?currentSchema=$schema", referenceUrl: referenceUrlPrefix + '?dialect=io.tolgee.dialects.postgres.CustomPostgreSQLDialect' + - '&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy' + + '&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy' + '&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy', username : 'postgres', password : 'postgres', diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa7..afba109285 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4e86b92707..3499ded5c1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index bc0c663417..a9b018dc33 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,9 @@ pluginManagement { if (requested.id.id == 'org.liquibase.gradle') { useModule('org.liquibase.gradle:org.liquibase.gradle.gradle.plugin:2.1.1') } + if (requested.id.id == 'org.hibernate.orm') { + useVersion(hibernateVersion) + } } } } @@ -34,11 +37,11 @@ dependencyResolutionManagement { library('kotlin', 'org.jetbrains.kotlin', 'kotlin-gradle-plugin').version(kotlinVersion) library('kotlinReflect', 'org.jetbrains.kotlin', 'kotlin-reflect').version(kotlinVersion) library('kotlinCoroutines', 'org.jetbrains.kotlinx', 'kotlinx-coroutines-core').version('1.6.1') - library('springDocOpenApiWebMvcCore', 'org.springdoc', 'springdoc-openapi-webmvc-core').version(springDocVersion) - library('springDocOpenApiUi', 'org.springdoc', 'springdoc-openapi-ui').version(springDocVersion) + library('springDocWebmvcApi', 'org.springdoc', 'springdoc-openapi-starter-webmvc-api').version(springDocVersion) + library('springDocOpenApiUi', 'org.springdoc', 'springdoc-openapi-starter-webmvc-ui').version(springDocVersion) library('springDocOpenApiKotlin', 'org.springdoc', 'springdoc-openapi-kotlin').version(springDocVersion) - library('springDocOpenApiDataRest', 'org.springdoc', 'springdoc-openapi-data-rest').version(springDocVersion) - library('springDocOpenApiHateoas', 'org.springdoc', 'springdoc-openapi-hateoas').version(springDocVersion) + library('springDocOpenApiCommon', 'org.springdoc', 'springdoc-openapi-starter-common').version(springDocVersion) + library('jacksonKotlin', 'com.fasterxml.jackson.module', 'jackson-module-kotlin').version('2.15.3') library('jjwtApi', 'io.jsonwebtoken', 'jjwt-api').version(jjwtVersion) library('jjwtImpl', 'io.jsonwebtoken', 'jjwt-impl').version(jjwtVersion) library('jjwtJackson', 'io.jsonwebtoken', 'jjwt-jackson').version(jjwtVersion) @@ -53,9 +56,10 @@ dependencyResolutionManagement { library('amazonTranslate', "software.amazon.awssdk:translate:$amazonAwsSdkVersion") library('googleCloud', "com.google.cloud:libraries-bom:24.0.0") library('sentry', "io.sentry:sentry-spring-boot-starter:5.7.3") - library('liquibaseCore', "org.liquibase:liquibase-core:4.9.1") - library('liquibaseHibernate', "org.liquibase.ext:liquibase-hibernate5:4.9.1") - library('hibernateTypes', "com.vladmihalcea:hibernate-types-55:2.14.1") + library('liquibaseCore', "org.liquibase:liquibase-core:4.25.0") + library('liquibaseHibernate', "org.liquibase.ext:liquibase-hibernate6:4.25.0") + library('liquibasePicoli', "info.picocli:picocli:4.6.3") + library('hibernateTypes', "io.hypersistence:hypersistence-utils-hibernate-62:3.6.0") library('redissonSpringBootStarter', "org.redisson:redisson-spring-boot-starter:3.23.2") library('redissonSpringData', 'org.redisson:redisson-spring-data-27:3.23.2') library('postHog', 'com.posthog.java:posthog:1.1.0') diff --git a/webapp/package.json b/webapp/package.json index d25838bce3..fbc66df843 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -64,7 +64,7 @@ "scripts": { "start": "NODE_OPTIONS=--openssl-legacy-provider craco start", "develop": "start", - "startConcurrently": "concurrently \"npm run start\"", + "startConcurrently": "NODE_OPTIONS=--openssl-legacy-provider concurrently \"npm run start\"", "build": "NODE_OPTIONS=--openssl-legacy-provider craco build", "test": "craco test", "schema": "openapi-typescript http://localhost:8080/v3/api-docs/All%20Internal%20-%20for%20Tolgee%20Web%20application --output ./src/service/apiSchema.generated.ts", @@ -74,7 +74,7 @@ "tsc": "tsc", "unused-exports": "ts-unused-exports tsconfig.json --findCompletelyUnusedFiles --ignoreFiles=GoToDocsButton", "tsc:prod": "tsc --project tsconfig.prod.json", - "load-translations": "tolgee pull ./src/i18n -o", + "load-translations": "tolgee pull ./src/i18n -o --verbose", "check-translations": "tolgee extract check './src/**/*.ts?(x)'" }, "eslintConfig": { diff --git a/webapp/src/service/billingApiSchema.generated.ts b/webapp/src/service/billingApiSchema.generated.ts index bbe29961b3..e08b313982 100644 --- a/webapp/src/service/billingApiSchema.generated.ts +++ b/webapp/src/service/billingApiSchema.generated.ts @@ -491,7 +491,7 @@ export interface components { }; CollectionModelStripeProductModel: { _embedded?: { - stripeProductModels?: components["schemas"]["StripeProductModel"][]; + stripeProducts?: components["schemas"]["StripeProductModel"][]; }; }; StripeProductModel: { diff --git a/webapp/src/views/administration/components/CloudPlanForm.tsx b/webapp/src/views/administration/components/CloudPlanForm.tsx index ac635b79a5..9cf7bdc45b 100644 --- a/webapp/src/views/administration/components/CloudPlanForm.tsx +++ b/webapp/src/views/administration/components/CloudPlanForm.tsx @@ -60,7 +60,7 @@ export function CloudPlanForm({ method: 'get', }); - const products = productsLoadable.data?._embedded?.stripeProductModels; + const products = productsLoadable.data?._embedded?.stripeProducts; const typeOptions = [ { value: 'PAY_AS_YOU_GO', label: 'Pay as you go' }, diff --git a/webapp/src/views/administration/components/EePlanForm.tsx b/webapp/src/views/administration/components/EePlanForm.tsx index 75e18b9d33..bf2faaa120 100644 --- a/webapp/src/views/administration/components/EePlanForm.tsx +++ b/webapp/src/views/administration/components/EePlanForm.tsx @@ -52,7 +52,7 @@ export function EePlanForm({ planId, initialData, onSubmit, loading }: Props) { method: 'get', }); - const products = productsLoadable.data?._embedded?.stripeProductModels; + const products = productsLoadable.data?._embedded?.stripeProducts; return ( { const editOrganization = useApiMutation({ url: '/v2/organizations/{id}', method: 'put', + invalidatePrefix: '/v2/organizations', }); const deleteOrganization = useApiMutation({ url: '/v2/organizations/{id}', method: 'delete', + invalidatePrefix: '/v2/organizations', }); const isAdmin = useIsAdmin();