Skip to content

Commit 8ee8c4f

Browse files
committed
feat: permissions handling for glossaries + refactoring and todo cleanup
1 parent 8749d7d commit 8ee8c4f

File tree

19 files changed

+144
-59
lines changed

19 files changed

+144
-59
lines changed

backend/data/src/main/kotlin/io/tolgee/model/glossary/Glossary.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,22 @@ import jakarta.persistence.JoinTable
1515
import jakarta.persistence.ManyToMany
1616
import jakarta.persistence.ManyToOne
1717
import jakarta.persistence.OneToMany
18+
import jakarta.persistence.Table
1819
import jakarta.persistence.Temporal
1920
import jakarta.persistence.TemporalType
21+
import jakarta.persistence.UniqueConstraint
2022
import jakarta.validation.constraints.Size
2123
import java.util.Date
2224

23-
// TODO: unique organizationOwner+name
24-
2525
@Entity
2626
@ActivityLoggedEntity
27+
@Table(
28+
uniqueConstraints = [
29+
UniqueConstraint(
30+
columnNames = ["organization_owner_id", "name"],
31+
),
32+
],
33+
)
2734
class Glossary(
2835
@field:Size(min = 3, max = 50)
2936
@ActivityLoggedProp

backend/data/src/main/kotlin/io/tolgee/model/glossary/GlossaryTermTranslation.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ import java.util.*
1111
@EntityListeners(GlossaryTermTranslation.Companion.GlossaryTermTranslationListener::class)
1212
@ActivityLoggedEntity
1313
@Table(
14-
name = "GlossaryTermTranslation",
1514
indexes = [
16-
Index(columnList = "textLowercased"),
15+
Index(columnList = "text_lowercased"),
1716
],
1817
uniqueConstraints = [
19-
UniqueConstraint(columnNames = ["term_id", "languageTag"]),
18+
UniqueConstraint(columnNames = ["term_id", "language_tag"]),
2019
],
2120
)
2221
class GlossaryTermTranslation(

backend/data/src/main/resources/db/changelog/schema.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4297,4 +4297,7 @@
42974297
<changeSet author="anty (generated)" id="1744881555572-11">
42984298
<addForeignKeyConstraint baseColumnNames="organization_owner_id" baseTableName="glossary" constraintName="FKtiuusr7urs46e15gjy94jhdfm" deferrable="false" initiallyDeferred="false" referencedColumnNames="id" referencedTableName="organization" validate="true"/>
42994299
</changeSet>
4300+
<changeSet author="anty (generated)" id="1746016606559-1">
4301+
<addUniqueConstraint columnNames="organization_owner_id, name" constraintName="UK6lhjfekqt9yfoxa2qdolqe180" tableName="glossary"/>
4302+
</changeSet>
43004303
</databaseChangeLog>

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/glossary/GlossaryController.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class GlossaryController(
4545
@PostMapping
4646
@Operation(summary = "Create glossary")
4747
@AllowApiAccess(AuthTokenType.ONLY_PAT)
48-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
48+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
4949
@Transactional
5050
fun create(
5151
@PathVariable
@@ -65,7 +65,7 @@ class GlossaryController(
6565
@PutMapping("/{glossaryId:[0-9]+}")
6666
@Operation(summary = "Update glossary")
6767
@AllowApiAccess(AuthTokenType.ONLY_PAT)
68-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
68+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
6969
@Transactional
7070
fun update(
7171
@PathVariable
@@ -88,7 +88,7 @@ class GlossaryController(
8888
@DeleteMapping("/{glossaryId:[0-9]+}")
8989
@Operation(summary = "Delete glossary")
9090
@AllowApiAccess(AuthTokenType.ONLY_PAT)
91-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
91+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
9292
@Transactional
9393
fun delete(
9494
@PathVariable

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/glossary/GlossaryTermController.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class GlossaryTermController(
4545
@PostMapping("/terms")
4646
@Operation(summary = "Create a new glossary term")
4747
@AllowApiAccess(AuthTokenType.ONLY_PAT)
48-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
48+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
4949
@Transactional
5050
fun create(
5151
@PathVariable
@@ -70,7 +70,7 @@ class GlossaryTermController(
7070
@DeleteMapping("/terms")
7171
@Operation(summary = "Batch delete multiple terms")
7272
@AllowApiAccess(AuthTokenType.ONLY_PAT)
73-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
73+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
7474
@Transactional
7575
fun deleteMultiple(
7676
@PathVariable organizationId: Long,
@@ -88,7 +88,7 @@ class GlossaryTermController(
8888
@PutMapping("/terms/{termId:[0-9]+}")
8989
@Operation(summary = "Update glossary term")
9090
@AllowApiAccess(AuthTokenType.ONLY_PAT)
91-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
91+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
9292
@Transactional
9393
fun update(
9494
@PathVariable organizationId: Long,
@@ -111,7 +111,7 @@ class GlossaryTermController(
111111
@DeleteMapping("/terms/{termId:[0-9]+}")
112112
@Operation(summary = "Delete glossary term")
113113
@AllowApiAccess(AuthTokenType.ONLY_PAT)
114-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
114+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
115115
@Transactional
116116
fun delete(
117117
@PathVariable organizationId: Long,

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/glossary/GlossaryTermHighlightsController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class GlossaryTermHighlightsController(
3939
text: String,
4040
@RequestParam("languageTag")
4141
languageTag: String,
42-
): CollectionModel<GlossaryTermHighlightDto> { // TODO: use CollectionModel
42+
): CollectionModel<GlossaryTermHighlightDto> {
4343
enabledFeaturesProvider.checkFeatureEnabled(
4444
organizationHolder.organization.id,
4545
Feature.GLOSSARY,

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/glossary/GlossaryTermTranslationController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class GlossaryTermTranslationController(
3333
@PostMapping()
3434
@Operation(summary = "Set a new glossary term translation for language")
3535
@AllowApiAccess(AuthTokenType.ONLY_PAT)
36-
@RequiresOrganizationRole(OrganizationRoleType.OWNER) // TODO special role for glossaries
36+
@RequiresOrganizationRole(OrganizationRoleType.MAINTAINER)
3737
@Transactional
3838
fun update(
3939
@PathVariable

ee/backend/app/src/main/kotlin/io/tolgee/ee/repository/glossary/GlossaryRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import java.util.Date
1313
@Repository
1414
@Lazy
1515
interface GlossaryRepository : JpaRepository<Glossary, Long> {
16+
// TODO: rework - use only glossary id for query, check organization id in service?
17+
1618
@Query(
1719
"""
1820
from Glossary

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/glossary/GlossaryService.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ class GlossaryService(
5656
organizationOwner = organization
5757
baseLanguageTag = dto.baseLanguageTag
5858

59-
// TODO project permissions handling?
60-
val projects = projectService.findAll(dto.assignedProjects ?: emptySet())
61-
assignedProjects.addAll(projects)
59+
updateAssignedProjects(this, dto.assignedProjects ?: emptySet())
6260
}
6361
return glossaryRepository.save(glossary)
6462
}
@@ -73,13 +71,26 @@ class GlossaryService(
7371
glossary.baseLanguageTag = dto.baseLanguageTag
7472
val newAssignedProjects = dto.assignedProjects
7573
if (newAssignedProjects != null) {
76-
glossary.assignedProjects.clear()
77-
val projects = projectService.findAll(newAssignedProjects)
78-
glossary.assignedProjects.addAll(projects)
74+
updateAssignedProjects(glossary, newAssignedProjects)
7975
}
8076
return glossaryRepository.save(glossary)
8177
}
8278

79+
private fun updateAssignedProjects(
80+
glossary: Glossary,
81+
newAssignedProjects: Iterable<Long>,
82+
) {
83+
glossary.assignedProjects.clear()
84+
val projects = projectService.findAll(newAssignedProjects)
85+
projects.forEach {
86+
if (it.organizationOwner.id != glossary.organizationOwner.id) {
87+
// Project belongs to another organization
88+
throw NotFoundException(Message.PROJECT_NOT_FOUND)
89+
}
90+
}
91+
glossary.assignedProjects.addAll(projects)
92+
}
93+
8394
fun delete(
8495
organizationId: Long,
8596
glossaryId: Long,
@@ -92,9 +103,12 @@ class GlossaryService(
92103
glossaryId: Long,
93104
projectId: Long,
94105
) {
95-
// TODO project permissions handling?
96106
val glossary = get(organizationId, glossaryId)
97107
val project = projectService.get(projectId)
108+
if (project.organizationOwner.id != organizationId) {
109+
// Project belongs to another organization
110+
throw NotFoundException(Message.PROJECT_NOT_FOUND)
111+
}
98112
glossary.assignedProjects.add(project)
99113
glossaryRepository.save(glossary)
100114
}

webapp/src/ee/glossary/components/GlossaryBatchToolbar.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import { T } from '@tolgee/react';
1515
import { SelectionService } from 'tg.service/useSelectionService';
1616
import { messageService } from 'tg.service/MessageService';
1717
import { TranslatedError } from 'tg.translationTools/TranslatedError';
18+
import { components } from 'tg.service/apiSchema.generated';
19+
20+
type OrganizationModel = components['schemas']['OrganizationModel'];
1821

1922
const StyledCard = styled(Card)`
2023
display: flex;
@@ -41,13 +44,13 @@ const StyledCheckbox = styled(Checkbox)`
4144
`;
4245

4346
type Props = {
44-
organizationId: number;
47+
organization: OrganizationModel;
4548
glossaryId: number;
4649
selectionService: SelectionService<number>;
4750
};
4851

4952
export const GlossaryBatchToolbar: React.VFC<Props> = ({
50-
organizationId,
53+
organization,
5154
glossaryId,
5255
selectionService,
5356
}) => {
@@ -71,7 +74,7 @@ export const GlossaryBatchToolbar: React.VFC<Props> = ({
7174
deleteSelectedMutation.mutate(
7275
{
7376
path: {
74-
organizationId,
77+
organizationId: organization.id,
7578
glossaryId,
7679
},
7780
content: {
@@ -95,6 +98,10 @@ export const GlossaryBatchToolbar: React.VFC<Props> = ({
9598
});
9699
};
97100

101+
const canDelete = ['OWNER', 'MAINTAINER'].includes(
102+
organization.currentUserRole || ''
103+
);
104+
98105
return (
99106
<StyledCard
100107
sx={{
@@ -115,6 +122,7 @@ export const GlossaryBatchToolbar: React.VFC<Props> = ({
115122
</Select>
116123
<LoadingButton
117124
disableElevation
125+
disabled={!canDelete}
118126
variant="contained"
119127
color="primary"
120128
sx={{ minWidth: 0, minHeight: 0, width: 40, height: 40, padding: 0 }}

webapp/src/ee/glossary/components/GlossaryEmptyListMessage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ export const GlossaryEmptyListMessage: React.VFC<Props> = ({
6969
<StyledDescription variant="body1">
7070
<T keyName="glossary_empty_placeholder_add_term_description" />
7171
</StyledDescription>
72-
<Button onClick={onCreate} variant="contained" color="primary">
72+
<Button
73+
onClick={onCreate}
74+
disabled={!onCreate}
75+
variant="contained"
76+
color="primary"
77+
>
7378
<T keyName="glossary_empty_placeholder_add_term_button" />
7479
</Button>
7580
<Link href="https://docs.tolgee.io/platform/projects_and_organizations/glossary">
@@ -86,7 +91,12 @@ export const GlossaryEmptyListMessage: React.VFC<Props> = ({
8691
<StyledDescription variant="body1">
8792
<T keyName="glossary_empty_placeholder_import_terms_description" />
8893
</StyledDescription>
89-
<Button onClick={onImport} variant="contained" color="primary">
94+
<Button
95+
onClick={onImport}
96+
disabled={!onImport}
97+
variant="contained"
98+
color="primary"
99+
>
90100
<T keyName="glossary_empty_placeholder_import_terms_button" />
91101
</Button>
92102
<Link href="https://docs.tolgee.io/platform/projects_and_organizations/glossary/import/csv-format">

webapp/src/ee/glossary/components/GlossaryListItem.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CircledLanguageIconList } from 'tg.component/languages/CircledLanguageI
88
import { languageInfo } from '@tginternal/language-util/lib/generated/languageInfo';
99
import { GlossaryListItemMenu } from 'tg.ee.module/glossary/components/GlossaryListItemMenu';
1010

11-
type SimpleOrganizationModel = components['schemas']['SimpleOrganizationModel'];
11+
type OrganizationModel = components['schemas']['OrganizationModel'];
1212
type GlossaryModel = components['schemas']['GlossaryModel'];
1313

1414
const StyledContainer = styled('div')`
@@ -70,7 +70,7 @@ const StyledNameText = styled(Typography)`
7070

7171
type Props = {
7272
glossary: GlossaryModel;
73-
organization: SimpleOrganizationModel;
73+
organization: OrganizationModel;
7474
};
7575

7676
export const GlossaryListItem: React.VFC<Props> = ({
@@ -90,8 +90,9 @@ export const GlossaryListItem: React.VFC<Props> = ({
9090
originalName: languageData?.originalName || languageTag,
9191
tag: languageTag,
9292
},
93+
// Future improvement - display all languages used in glossary
94+
// not just base language
9395
];
94-
// TODO: All languages used in glossary - will need backend changes
9596

9697
return (
9798
<StyledContainer

webapp/src/ee/glossary/components/GlossaryListItemMenu.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ import { Link } from 'react-router-dom';
1010
import { LINKS, PARAMS } from 'tg.constants/links';
1111
import { GlossaryCreateEditDialog } from 'tg.ee.module/glossary/views/GlossaryCreateEditDialog';
1212

13-
type SimpleOrganizationModel = components['schemas']['SimpleOrganizationModel'];
13+
type OrganizationModel = components['schemas']['OrganizationModel'];
1414
type GlossaryModel = components['schemas']['GlossaryModel'];
1515

1616
type Props = {
1717
glossary: GlossaryModel;
18-
organization: SimpleOrganizationModel;
18+
organization: OrganizationModel;
1919
};
2020

2121
export const GlossaryListItemMenu: FC<Props> = ({ glossary, organization }) => {
2222
const { t } = useTranslate();
2323
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
2424
const [isEditing, setIsEditing] = React.useState(false);
2525

26-
const canManage = true; // TODO: Permissions
26+
const canManage = ['OWNER', 'MAINTAINER'].includes(
27+
organization.currentUserRole || ''
28+
);
2729

2830
const deleteMutation = useApiMutation({
2931
url: '/v2/organizations/{organizationId}/glossaries/{glossaryId}',
@@ -44,7 +46,7 @@ export const GlossaryListItemMenu: FC<Props> = ({ glossary, organization }) => {
4446
onConfirm() {
4547
deleteMutation.mutate({
4648
path: {
47-
organizationId: organization.id,
49+
organizationId: glossary.organizationOwner.id,
4850
glossaryId: glossary.id,
4951
},
5052
});

0 commit comments

Comments
 (0)