Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class ApiKeyController(
viewLanguageIds = computed.viewLanguageIds.toNormalizedPermittedLanguageSet(),
stateChangeLanguageIds = computed.stateChangeLanguageIds.toNormalizedPermittedLanguageSet(),
suggestLanguageIds = computed.suggestLanguageIds.toNormalizedPermittedLanguageSet(),
suggestManageLanguageIds = computed.suggestManageLanguageIds.toNormalizedPermittedLanguageSet(),
scopes = securityService.getCurrentPermittedScopes(projectIdNotNull).toTypedArray(),
project = simpleProjectModelAssembler.toModel(projectService.get(projectIdNotNull)),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.tolgee.service.project.ProjectFeatureGuard
import io.tolgee.service.project.ProjectService
import io.tolgee.service.project.ProjectStatsService
import io.tolgee.service.qa.TranslationQaIssueService
import io.tolgee.service.security.SecurityService
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -47,6 +48,7 @@ class ProjectStatsController(
private val branchService: BranchService,
private val projectFeatureGuard: ProjectFeatureGuard,
private val translationQaIssueService: TranslationQaIssueService,
private val securityService: SecurityService,
) {
@Operation(summary = "Get project stats")
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
Expand Down Expand Up @@ -82,7 +84,7 @@ class ProjectStatsController(
baseWordsCount = totals.baseWordsCount,
translatedPercentage = totals.translatedPercent,
reviewedPercentage = totals.reviewedPercent,
membersCount = projectStats.memberCount,
membersCount = visibleMembersCount(projectStats.memberCount),
tagCount = projectStats.tagCount,
languageStats =
statsLanguagePairs.map {
Expand Down Expand Up @@ -114,4 +116,9 @@ class ProjectStatsController(
fun getProjectDailyActivity(): Map<LocalDate, Long> {
return projectStatsService.getProjectDailyActivity(projectHolder.project.id)
}

private fun visibleMembersCount(memberCount: Long): Long {
if (!securityService.shouldExposeMemberInfo()) return 0
return memberCount
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ 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.model.enums.Scope
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authorization.RequiresProjectPermissions
import io.tolgee.security.authorization.UseDefaultPermissions
import io.tolgee.service.dataImport.ImportSettingsService
import jakarta.validation.Valid
Expand Down Expand Up @@ -57,7 +59,7 @@ class ImportSettingsController(
description = "Stores import settings for the authenticated user and the project.",
)
@AllowApiAccess
@UseDefaultPermissions
@RequiresProjectPermissions([Scope.TRANSLATIONS_EDIT])
fun store(
@Valid @RequestBody dto: ImportSettingsRequest,
): ImportSettingsModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.dtos.MtCreditBalanceDto
import io.tolgee.hateoas.machineTranslation.CreditBalanceModel
import io.tolgee.model.enums.Scope
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authorization.RequiresOrganizationRole
import io.tolgee.security.authorization.UseDefaultPermissions
import io.tolgee.security.authorization.RequiresProjectPermissions
import io.tolgee.service.machineTranslation.mtCreditsConsumption.MtCreditsService
import io.tolgee.service.organization.OrganizationService
import org.springframework.web.bind.annotation.CrossOrigin
Expand All @@ -30,7 +31,7 @@ class MtCreditsController(
summary = "Get credit balance for project",
description = "Returns machine translation credit balance for specified project",
)
@UseDefaultPermissions
@RequiresProjectPermissions([Scope.ORGANIZATION_QUOTAS_VIEW])
@AllowApiAccess
fun getProjectCredits(
@PathVariable projectId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2020. Tolgee
*/

package io.tolgee.api.v2.controllers.project

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.activity.RequestActivity
import io.tolgee.activity.data.ActivityType
import io.tolgee.dtos.request.project.SetProjectPublicRequest
import io.tolgee.hateoas.project.ProjectModel
import io.tolgee.hateoas.project.ProjectModelAssembler
import io.tolgee.model.enums.Scope
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.RequiresSuperAuthentication
import io.tolgee.security.authorization.RequiresProjectPermissions
import io.tolgee.service.organization.OrganizationRoleService
import io.tolgee.service.project.ProjectService
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

@Suppress(names = ["MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection"])
@RestController
@CrossOrigin(origins = ["*"])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
@RequestMapping(value = ["/v2/projects"])
@Tag(
name = "Project Publishing",
description = "Marks a project as public or private (organization owner or server admin)",
)
class ProjectsPublishingController(
private val projectService: ProjectService,
private val projectHolder: ProjectHolder,
private val organizationRoleService: OrganizationRoleService,
private val projectModelAssembler: ProjectModelAssembler,
) {
@PutMapping(value = ["/{projectId:[0-9]+}/publishing"])
@Operation(
summary = "Set project publishing state",
description =
"Marks the project as public or private. " +
"Only the organization owner or a server admin can change this.",
)
@RequestActivity(ActivityType.EDIT_PROJECT)
@RequiresProjectPermissions([Scope.PROJECT_EDIT])
@RequiresSuperAuthentication
fun setProjectPublic(
@RequestBody @Valid
dto: SetProjectPublicRequest,
): ProjectModel {
organizationRoleService.checkUserIsOwnerOrServerAdmin(projectHolder.project.organizationOwnerId)
val project = projectService.setPublic(projectHolder.project.id, dto.public)
return projectModelAssembler.toModel(projectService.getView(project.id))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.tolgee.api.v2.controllers.project

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.api.v2.controllers.IController
import io.tolgee.facade.ProjectWithStatsFacade
import io.tolgee.hateoas.project.ProjectWithStatsModel
import io.tolgee.openApiDocs.OpenApiHideFromPublicDocs
import io.tolgee.service.project.ProjectService
import org.springdoc.core.annotations.ParameterObject
import org.springframework.data.domain.Pageable
import org.springframework.data.web.SortDefault
import org.springframework.hateoas.PagedModel
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.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@CrossOrigin(origins = ["*"])
@RequestMapping(value = ["/v2/public/projects"])
@Tag(name = "Public projects")
@OpenApiHideFromPublicDocs
class PublicProjectsController(
private val projectService: ProjectService,
private val projectWithStatsFacade: ProjectWithStatsFacade,
) : IController {
@Operation(
summary = "Get all public projects with stats",
description =
"Returns all public projects (including statistics), discoverable by anyone — no authentication required",
)
@GetMapping("/with-stats")
@Transactional(readOnly = true)
fun getAllPublicWithStatistics(
@ParameterObject @SortDefault("name") pageable: Pageable,
@RequestParam("search") search: String?,
): PagedModel<ProjectWithStatsModel> {
val projects = projectService.findAllPublicPaged(pageable, search)
return projectWithStatsFacade.getPagedModelWithStats(projects)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import io.tolgee.api.IProjectActivityModelAssembler
import io.tolgee.api.v2.controllers.ApiKeyController
import io.tolgee.model.views.activity.ProjectActivityView
import io.tolgee.service.AvatarService
import io.tolgee.service.security.SecurityService
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.stereotype.Component

@Component
class ProjectActivityModelAssembler(
private val avatarService: AvatarService,
private val modifiedEntityModelAssembler: ModifiedEntityModelAssembler,
private val securityService: SecurityService,
) : RepresentationModelAssemblerSupport<ProjectActivityView, ProjectActivityModel>(
ApiKeyController::class.java,
ProjectActivityModel::class.java,
Expand All @@ -26,7 +28,7 @@ class ProjectActivityModelAssembler(
ProjectActivityAuthorModel(
id = view.authorId!!,
name = view.authorName,
username = view.authorUsername!!,
username = securityService.maskedMemberField(view.authorUsername),
avatar = avatarService.getAvatarLinks(view.authorAvatarHash),
deleted = view.authorDeleted,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class ApiKeyPermissionsModel(
override val translateLanguageIds: Set<Long>?,
override var stateChangeLanguageIds: Set<Long>?,
override val suggestLanguageIds: Collection<Long>?,
override val suggestManageLanguageIds: Collection<Long>?,
override var scopes: Array<Scope> = arrayOf(),
@get:Schema(
description =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import io.tolgee.service.AvatarService
import io.tolgee.service.key.KeySearchResultView
import io.tolgee.service.key.KeyTrashPurgeScheduler
import io.tolgee.service.security.SecurityService
import io.tolgee.util.addDays
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.stereotype.Component

@Component
class TrashedKeyModelAssembler(
private val avatarService: AvatarService,
private val securityService: SecurityService,
) : RepresentationModelAssemblerSupport<KeySearchResultView, TrashedKeyModel>(
KeyTrashController::class.java,
TrashedKeyModel::class.java,
Expand All @@ -34,7 +36,7 @@ class TrashedKeyModelAssembler(
return entity.deletedByUserId?.let { userId ->
SimpleUserAccountModel(
id = userId,
username = entity.deletedByUserUsername ?: "",
username = securityService.maskedMemberField(entity.deletedByUserUsername),
name = entity.deletedByUserName,
avatar = avatarService.getAvatarLinks(entity.deletedByUserAvatarHash),
deleted = entity.deletedByUserDeleted ?: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import io.tolgee.model.views.KeyWithTranslationsView
import io.tolgee.service.AvatarService
import io.tolgee.service.key.KeyTrashPurgeScheduler
import io.tolgee.service.security.SecurityService
import io.tolgee.util.addDays
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.stereotype.Component
Expand All @@ -16,6 +17,7 @@ import org.springframework.stereotype.Component
class TrashedKeyWithTranslationsModelAssembler(
private val avatarService: AvatarService,
private val screenshotModelAssembler: ScreenshotModelAssembler,
private val securityService: SecurityService,
) : RepresentationModelAssemblerSupport<KeyWithTranslationsView, TrashedKeyWithTranslationsModel>(
KeyTrashController::class.java,
TrashedKeyWithTranslationsModel::class.java,
Expand Down Expand Up @@ -62,7 +64,7 @@ class TrashedKeyWithTranslationsModelAssembler(
return view.deletedByUserId?.let { userId ->
SimpleUserAccountModel(
id = userId,
username = view.deletedByUserUsername ?: "",
username = securityService.maskedMemberField(view.deletedByUserUsername),
name = view.deletedByUserName,
avatar = avatarService.getAvatarLinks(view.deletedByUserAvatarHash),
deleted = view.deletedByUserDeletedAt != null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,12 @@ interface IPermissionModel {
example = "[200001, 200004]",
)
val suggestLanguageIds: Collection<Long>?

@get:Schema(
description =
"List of languages user can manage suggestions for. If null, managing " +
"suggestions for all languages is permitted.",
example = "[200001, 200004]",
)
val suggestManageLanguageIds: Collection<Long>?
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ open class PermissionModel(
override val viewLanguageIds: Collection<Long>?,
override val stateChangeLanguageIds: Collection<Long>?,
override val suggestLanguageIds: Collection<Long>?,
override val suggestManageLanguageIds: Collection<Long>?,
) : RepresentationModel<PermissionModel>(),
IDeprecatedPermissionModel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PermissionModelAssembler :
translateLanguageIds = entity.translateLanguageIds,
stateChangeLanguageIds = entity.stateChangeLanguageIds,
suggestLanguageIds = entity.suggestLanguageIds,
suggestManageLanguageIds = entity.suggestManageLanguageIds,
type = entity.type,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PermissionWithAgencyModel(
override val viewLanguageIds: Collection<Long>?,
override val stateChangeLanguageIds: Collection<Long>?,
override val suggestLanguageIds: Collection<Long>?,
override val suggestManageLanguageIds: Collection<Long>?,
val agency: TranslationAgencySimpleModel?,
) : RepresentationModel<PermissionModel>(),
IDeprecatedPermissionModel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PermissionWithAgencyModelAssembler(
stateChangeLanguageIds = entity.stateChangeLanguageIds,
viewLanguageIds = entity.viewLanguageIds,
suggestLanguageIds = entity.suggestLanguageIds,
suggestManageLanguageIds = entity.suggestManageLanguageIds,
type = entity.type,
agency = entity.agency?.let { translationAgencySimpleModelAssembler.toModel(it) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ open class ProjectModel(
val useNamespaces: Boolean,
val useBranching: Boolean,
val useQaChecks: Boolean,
@Schema(description = "Whether the project is public — discoverable and open to community suggestions")
val public: Boolean,
val defaultNamespace: NamespaceModel?,
val organizationRole: OrganizationRoleType?,
@Schema(description = "Current user's direct permission", example = "MANAGE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class ProjectModelAssembler(
useNamespaces = view.useNamespaces,
useBranching = view.useBranching,
useQaChecks = view.useQaChecks,
public = view.public,
defaultNamespace = defaultNamespace,
directPermission = view.directPermission?.let { permissionModelAssembler.toModel(it) },
computedPermission = computedPermissionModelAssembler.toModel(computedPermissions),
Expand All @@ -72,6 +73,7 @@ class ProjectModelAssembler(
view.organizationOwner.basePermission,
view.directPermission,
authenticationFacade.authenticatedUserOrNull?.role,
isProjectPublic = view.public,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ open class ProjectWithStatsModel(
val computedPermission: ComputedPermissionModel,
val stats: ProjectStatistics,
val languages: List<LanguageModel>,
@Schema(description = "Whether the project is public — discoverable and open to community suggestions")
val public: Boolean,
@Schema(description = "Whether to disable ICU placeholder visualization in the editor and it's support.")
var icuPlaceholders: Boolean,
) : RepresentationModel<ProjectWithStatsModel>()
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ProjectWithStatsModelAssembler(
view.organizationOwner.basePermission,
view.directPermission,
authenticationFacade.authenticatedUserOrNull?.role,
isProjectPublic = view.public,
)

return ProjectWithStatsModel(
Expand All @@ -56,6 +57,7 @@ class ProjectWithStatsModelAssembler(
computedPermission = computedPermissionModelAssembler.toModel(computedPermissions),
stats = view.stats,
languages = view.languages.map { languageModelAssembler.toModel(it) },
public = view.public,
icuPlaceholders = view.icuPlaceholders,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import io.tolgee.api.v2.controllers.translation.TranslationsController
import io.tolgee.dtos.queryResults.TranslationHistoryView
import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import io.tolgee.service.AvatarService
import io.tolgee.service.security.SecurityService
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.stereotype.Component

@Component
class TranslationHistoryModelAssembler(
private val avatarService: AvatarService,
private val securityService: SecurityService,
) : RepresentationModelAssemblerSupport<TranslationHistoryView, TranslationHistoryModel>(
TranslationsController::class.java,
TranslationHistoryModel::class.java,
Expand All @@ -24,7 +26,7 @@ class TranslationHistoryModelAssembler(
SimpleUserAccountModel(
id = it,
name = view.authorName,
username = view.authorEmail ?: "",
username = securityService.maskedMemberField(view.authorEmail),
avatar = avatar,
deleted = view.authorDeletedAt != null,
)
Expand Down
Loading
Loading