Skip to content

Commit

Permalink
feat: Visual editor & Formats support (#2145)
Browse files Browse the repository at this point in the history
Co-authored-by: Štěpán Granát <[email protected]>
  • Loading branch information
JanCizmar and stepan662 authored Mar 5, 2024
1 parent 59fdc7e commit 7fef4e3
Show file tree
Hide file tree
Showing 597 changed files with 29,052 additions and 9,021 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ max_line_length = 120

[backend/data/src/main/kotlin/io/tolgee/service/dataImport/processors/messageFormat/data/PluralData.kt]
max_line_length = 500

[**/in/**/*.{kt, kts}]
ktlint_standard_package-name = disabled
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.tolgee.hateoas.apiKey.ApiKeyPermissionsModel
import io.tolgee.hateoas.apiKey.ApiKeyWithLanguagesModel
import io.tolgee.hateoas.apiKey.RevealedApiKeyModel
import io.tolgee.hateoas.apiKey.RevealedApiKeyModelAssembler
import io.tolgee.hateoas.project.SimpleProjectModelAssembler
import io.tolgee.model.ApiKey
import io.tolgee.model.UserAccount
import io.tolgee.model.enums.ProjectPermissionType
Expand Down Expand Up @@ -64,6 +65,7 @@ class ApiKeyController(
@Suppress("SpringJavaInjectionPointsAutowiringInspection")
private val pagedResourcesAssembler: PagedResourcesAssembler<ApiKey>,
private val permissionService: PermissionService,
private val simpleProjectModelAssembler: SimpleProjectModelAssembler,
) {
@PostMapping(path = ["/api-keys"])
@Operation(summary = "Creates new API key with provided scopes")
Expand Down Expand Up @@ -176,6 +178,7 @@ class ApiKeyController(
viewLanguageIds = computed.viewLanguageIds.toNormalizedPermittedLanguageSet(),
stateChangeLanguageIds = computed.stateChangeLanguageIds.toNormalizedPermittedLanguageSet(),
scopes = scopes,
project = simpleProjectModelAssembler.toModel(projectService.get(projectIdNotNull)),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class KeyController(
val key = keyService.findOptional(id).orElseThrow { NotFoundException() }
key.checkInProject()
keyService.edit(id, dto)
val view = KeyView(key.id, key.name, key?.namespace?.name, key.keyMeta?.description)
val view = KeyView(key.id, key.name, key?.namespace?.name, key.keyMeta?.description, key.keyMeta?.custom)
return keyModelAssembler.toModel(view)
}

Expand Down Expand Up @@ -166,6 +166,19 @@ class KeyController(
return keyPagedResourcesAssembler.toModel(data, keyModelAssembler)
}

@GetMapping(value = ["{id}"])
@Transactional
@Operation(summary = "Returns single key")
@RequiresProjectPermissions([Scope.KEYS_VIEW])
@AllowApiAccess
fun get(
@PathVariable
id: Long,
): KeyModel {
val key = keyService.getView(projectHolder.project.id, id)
return keyModelAssembler.toModel(key)
}

@DeleteMapping(value = [""])
@Transactional
@Operation(summary = "Deletes one or multiple keys by their IDs in request body")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import io.tolgee.activity.RequestActivity
import io.tolgee.activity.data.ActivityType
import io.tolgee.constants.Message
import io.tolgee.dtos.request.AutoTranslationSettingsDto
import io.tolgee.dtos.request.project.CreateProjectDTO
import io.tolgee.dtos.request.project.EditProjectDTO
import io.tolgee.dtos.request.project.CreateProjectRequest
import io.tolgee.dtos.request.project.EditProjectRequest
import io.tolgee.dtos.request.project.SetPermissionLanguageParams
import io.tolgee.exceptions.BadRequestException
import io.tolgee.facade.ProjectPermissionFacade
Expand Down Expand Up @@ -223,7 +223,7 @@ class V2ProjectsController(
@AllowApiAccess(tokenType = AuthTokenType.ONLY_PAT)
fun createProject(
@RequestBody @Valid
dto: CreateProjectDTO,
dto: CreateProjectRequest,
): ProjectModel {
organizationRoleService.checkUserIsOwner(dto.organizationId)
val project = projectService.createProject(dto)
Expand All @@ -238,7 +238,7 @@ class V2ProjectsController(
@AllowApiAccess
fun editProject(
@RequestBody @Valid
dto: EditProjectDTO,
dto: EditProjectRequest,
): ProjectModel {
val project = projectService.editProject(projectHolder.project.id, dto)
return projectModelAssembler.toModel(projectService.getView(project.id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class V2ExportController(
) {
@GetMapping(value = [""])
@Operation(summary = "Exports data")
@RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ])
@RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW])
@AllowApiAccess
fun export(
@ParameterObject params: ExportParams,
Expand All @@ -66,7 +66,7 @@ class V2ExportController(
summary = """Exports data (post). Useful when providing params exceeding allowed query size.
""",
)
@RequiresProjectPermissions([ Scope.TRANSLATIONS_VIEW ])
@RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW])
@AllowApiAccess
fun exportPost(
@RequestBody params: ExportParams,
Expand Down Expand Up @@ -95,12 +95,10 @@ class V2ExportController(
params: ExportParams,
exported: Map<String, InputStream>,
): ResponseEntity<StreamingResponseBody> {
if (params.zip) {
return getZipResponseEntity(exported)
} else if (exported.entries.size == 1) {
if (exported.entries.size == 1 && !params.zip) {
return exportSingleFile(exported, params)
}
throw BadRequestException(message = Message.MULTIPLE_FILES_MUST_BE_ZIPPED)
return getZipResponseEntity(exported)
}

private fun checkExportNotEmpty(exported: Map<String, InputStream>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2020. Tolgee
*/

package io.tolgee.api.v2.controllers.dataImport

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.dtos.request.dataImport.ImportSettingsRequest
import io.tolgee.hateoas.dataImport.ImportSettingsModel
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authorization.UseDefaultPermissions
import io.tolgee.service.dataImport.ImportSettingsService
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
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

@Suppress("MVCPathVariableInspection")
@RestController
@CrossOrigin(origins = ["*"])
@RequestMapping(value = ["/v2/projects/{projectId:\\d+}/import-settings", "/v2/projects/import-settings"])
@Tag(
name = "Import Settings",
description =
"These endpoints enable you to store default settings for import. " +
"These settings are only used in the UI of Tolgee platform. " +
"It's not the default for importing via API endpoints. " +
"The settings are stored per user and per project.",
)
class ImportSettingsController(
private val projectHolder: ProjectHolder,
private val importSettingsService: ImportSettingsService,
private val authenticationFacade: AuthenticationFacade,
) {
@GetMapping("")
@Operation(description = "Returns import settings for the authenticated user and the project.")
@AllowApiAccess
@UseDefaultPermissions
fun get(): ImportSettingsModel {
val projectId = projectHolder.project.id
val settings = importSettingsService.get(authenticationFacade.authenticatedUserEntity, projectId)
return ImportSettingsModel(settings)
}

@PutMapping("")
@Operation(description = "Stores import settings for the authenticated user and the project.")
@AllowApiAccess
@UseDefaultPermissions
fun store(
@Valid @RequestBody dto: ImportSettingsRequest,
): ImportSettingsModel {
val projectId = projectHolder.project.id
val settings = importSettingsService.store(authenticationFacade.authenticatedUserEntity, projectId, dto)
return ImportSettingsModel(settings)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* Copyright (c) 2020. Tolgee
*/

package io.tolgee.api.v2.controllers
package io.tolgee.api.v2.controllers.dataImport

import com.fasterxml.jackson.databind.ObjectMapper
import io.sentry.Sentry
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.tags.Tag
Expand All @@ -14,6 +14,7 @@ import io.tolgee.dtos.dataImport.ImportAddFilesParams
import io.tolgee.dtos.dataImport.ImportFileDto
import io.tolgee.dtos.dataImport.SetFileNamespaceRequest
import io.tolgee.exceptions.BadRequestException
import io.tolgee.exceptions.ErrorException
import io.tolgee.exceptions.ErrorResponseBody
import io.tolgee.exceptions.NotFoundException
import io.tolgee.hateoas.dataImport.ImportAddFilesResultModel
Expand All @@ -27,7 +28,6 @@ import io.tolgee.hateoas.dataImport.ImportTranslationModelAssembler
import io.tolgee.model.Language
import io.tolgee.model.dataImport.ImportFile
import io.tolgee.model.dataImport.ImportLanguage
import io.tolgee.model.dataImport.ImportTranslation
import io.tolgee.model.enums.Scope
import io.tolgee.model.views.ImportFileIssueView
import io.tolgee.model.views.ImportLanguageView
Expand All @@ -42,8 +42,10 @@ import io.tolgee.service.dataImport.ImportService
import io.tolgee.service.dataImport.status.ImportApplicationStatus
import io.tolgee.service.dataImport.status.ImportApplicationStatusItem
import io.tolgee.service.key.NamespaceService
import io.tolgee.util.Logging
import io.tolgee.util.StreamingResponseBodyProvider
import jakarta.servlet.http.HttpServletRequest
import io.tolgee.util.filterFiles
import io.tolgee.util.logger
import org.springdoc.core.annotations.ParameterObject
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
Expand Down Expand Up @@ -92,8 +94,7 @@ class V2ImportController(
private val namespaceService: NamespaceService,
private val importFileIssueModelAssembler: ImportFileIssueModelAssembler,
private val streamingResponseBodyProvider: StreamingResponseBodyProvider,
private val objectMapper: ObjectMapper,
) {
) : Logging {
@PostMapping("", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@Operation(description = "Prepares provided files to import.", summary = "Add files")
@RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW])
Expand All @@ -102,7 +103,11 @@ class V2ImportController(
@RequestPart("files") files: Array<MultipartFile>,
@ParameterObject params: ImportAddFilesParams,
): ImportAddFilesResultModel {
val fileDtos = files.map { ImportFileDto(it.originalFilename ?: "", it.inputStream.readAllBytes()) }
val filteredFiles = filterFiles(files.map { (it.originalFilename ?: "") to it })
val fileDtos =
filteredFiles.map {
ImportFileDto(it.originalFilename ?: "", it.inputStream.readAllBytes())
}
val errors =
importService.addFiles(
files = fileDtos,
Expand Down Expand Up @@ -153,8 +158,32 @@ class V2ImportController(
val writeStatus = { status: ImportApplicationStatus ->
write(ImportApplicationStatusItem(status))
}

this.importService.import(projectId, authenticationFacade.authenticatedUser.id, forceMode, writeStatus)
try {
this.importService.import(projectId, authenticationFacade.authenticatedUser.id, forceMode, writeStatus)
} catch (e: Exception) {
if (e !is BadRequestException) {
Sentry.captureException(e)
logger.error("Unexpected error while importing", e)
}
when (e) {
is ErrorException ->
write(
ImportApplicationStatusItem(
ImportApplicationStatus.ERROR,
errorStatusCode = e.httpStatus.value(),
errorResponseBody = ErrorResponseBody(e.code, e.params),
),
)

else ->
write(
ImportApplicationStatusItem(
ImportApplicationStatus.ERROR,
errorStatusCode = 500,
),
)
}
}
}
}

Expand Down Expand Up @@ -212,7 +241,14 @@ class V2ImportController(
pageable: Pageable,
): PagedModel<ImportTranslationModel> {
checkImportLanguageInProject(languageId)
val translations = importService.getTranslationsView(languageId, pageable, onlyConflicts, onlyUnresolved, search)
val translations =
importService.getTranslationsView(
languageId,
pageable,
onlyConflicts,
onlyUnresolved,
search,
)
return pagedTranslationsResourcesAssembler.toModel(translations, importTranslationModelAssembler)
}

Expand Down Expand Up @@ -299,7 +335,6 @@ class V2ImportController(
fun selectNamespace(
@PathVariable fileId: Long,
@RequestBody req: SetFileNamespaceRequest,
request: HttpServletRequest,
) {
val file = checkFileFromProject(fileId)
this.importService.selectNamespace(file, req.namespace)
Expand Down Expand Up @@ -409,8 +444,7 @@ class V2ImportController(
override: Boolean,
) {
checkImportLanguageInProject(languageId)
val translation = checkTranslationOfLanguage(translationId, languageId)
return importService.resolveTranslationConflict(translation, override)
return importService.resolveTranslationConflict(translationId, languageId, override)
}

private fun checkFileFromProject(fileId: Long): ImportFile {
Expand All @@ -437,16 +471,4 @@ class V2ImportController(
}
return language
}

private fun checkTranslationOfLanguage(
translationId: Long,
languageId: Long,
): ImportTranslation {
val translation = importService.findTranslation(translationId) ?: throw NotFoundException()

if (translation.language.id != languageId) {
throw BadRequestException(io.tolgee.constants.Message.IMPORT_LANGUAGE_NOT_FROM_PROJECT)
}
return translation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ class TranslationSuggestionController(
securityService.checkLanguageTranslatePermission(projectHolder.project.id, listOf(targetLanguage.id))

val data =
dto.baseText?.let { baseText -> translationMemoryService.suggest(baseText, targetLanguage, pageable) }
dto.baseText?.let { baseText ->
translationMemoryService.suggest(
baseText,
isPlural = dto.isPlural ?: false,
targetLanguage,
pageable,
)
}
?: let {
val keyId = dto.keyId ?: throw BadRequestException(Message.KEY_NOT_FOUND)
val key = keyService.findOptional(keyId).orElseThrow { NotFoundException(Message.KEY_NOT_FOUND) }
Expand Down
Loading

0 comments on commit 7fef4e3

Please sign in to comment.