From 212985e18fd8d8e915fb9e69f09605acc08f8b19 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 31 Aug 2022 08:12:27 +0200 Subject: [PATCH] feat: Allow viewers to be upgraded to editors on individual collections (#4023) * Improve types * More types, fix default permission for viewers added to collection * fix change of default role for CollectionGroup * Restore policy * test * tests --- app/components/InputSelectPermission.tsx | 5 +- app/models/Collection.ts | 3 +- app/models/CollectionGroupMembership.ts | 10 +- app/models/Membership.ts | 10 +- app/scenes/CollectionNew.tsx | 7 +- .../AddGroupsToCollection.tsx | 1 - .../AddPeopleToCollection.tsx | 1 - .../CollectionGroupMemberListItem.tsx | 27 +- .../components/MemberListItem.tsx | 27 +- app/scenes/CollectionPermissions/index.tsx | 13 +- app/scenes/DocumentReparent.tsx | 5 +- app/stores/CollectionGroupMembershipsStore.ts | 3 +- app/stores/CollectionsStore.ts | 7 +- app/stores/MembershipsStore.ts | 3 +- server/middlewares/authentication.ts | 6 +- server/models/Collection.ts | 20 +- server/models/CollectionGroup.ts | 9 +- server/models/CollectionUser.ts | 9 +- server/models/Team.ts | 3 +- server/models/User.test.ts | 7 +- server/models/User.ts | 11 +- server/policies/collection.test.ts | 333 ++++++++++++------ server/policies/collection.ts | 42 ++- server/policies/document.test.ts | 34 +- server/policies/document.ts | 9 - server/policies/team.test.ts | 1 + .../presenters/collectionGroupMembership.ts | 3 +- server/presenters/membership.ts | 3 +- server/queues/tasks/ImportTask.ts | 5 +- .../__snapshots__/collections.test.ts.snap | 9 - server/routes/api/collections.test.ts | 101 ++---- server/routes/api/collections.ts | 52 ++- server/routes/api/documents.test.ts | 13 +- server/routes/api/documents.ts | 147 ++++---- server/routes/api/shares.test.ts | 3 +- server/routes/api/views.test.ts | 5 +- server/test/factories.ts | 3 +- server/test/support.ts | 3 +- server/types.ts | 4 +- server/validation.ts | 8 + shared/i18n/locales/en_US/translation.json | 2 +- shared/types.ts | 5 + 42 files changed, 537 insertions(+), 435 deletions(-) diff --git a/app/components/InputSelectPermission.tsx b/app/components/InputSelectPermission.tsx index bbe37751f..6d406e161 100644 --- a/app/components/InputSelectPermission.tsx +++ b/app/components/InputSelectPermission.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; import { $Diff } from "utility-types"; +import { CollectionPermission } from "@shared/types"; import InputSelect, { Props, Option } from "./InputSelect"; export default function InputSelectPermission( @@ -31,11 +32,11 @@ export default function InputSelectPermission( options={[ { label: t("View and edit"), - value: "read_write", + value: CollectionPermission.ReadWrite, }, { label: t("View only"), - value: "read", + value: CollectionPermission.Read, }, { label: t("No access"), diff --git a/app/models/Collection.ts b/app/models/Collection.ts index c13d5efe9..623e78837 100644 --- a/app/models/Collection.ts +++ b/app/models/Collection.ts @@ -1,5 +1,6 @@ import { trim } from "lodash"; import { action, computed, observable } from "mobx"; +import { CollectionPermission } from "@shared/types"; import { sortNavigationNodes } from "@shared/utils/collections"; import CollectionsStore from "~/stores/CollectionsStore"; import Document from "~/models/Document"; @@ -39,7 +40,7 @@ export default class Collection extends ParanoidModel { @Field @observable - permission: "read" | "read_write" | void; + permission: CollectionPermission | void; @Field @observable diff --git a/app/models/CollectionGroupMembership.ts b/app/models/CollectionGroupMembership.ts index 893ddd4bc..4563eefd4 100644 --- a/app/models/CollectionGroupMembership.ts +++ b/app/models/CollectionGroupMembership.ts @@ -1,4 +1,5 @@ import { computed } from "mobx"; +import { CollectionPermission } from "@shared/types"; import BaseModel from "./BaseModel"; class CollectionGroupMembership extends BaseModel { @@ -8,16 +9,11 @@ class CollectionGroupMembership extends BaseModel { collectionId: string; - permission: string; + permission: CollectionPermission; @computed get isEditor(): boolean { - return this.permission === "read_write"; - } - - @computed - get isMaintainer(): boolean { - return this.permission === "maintainer"; + return this.permission === CollectionPermission.ReadWrite; } } diff --git a/app/models/Membership.ts b/app/models/Membership.ts index 6b6647059..17ea36bdc 100644 --- a/app/models/Membership.ts +++ b/app/models/Membership.ts @@ -1,4 +1,5 @@ import { computed } from "mobx"; +import { CollectionPermission } from "@shared/types"; import BaseModel from "./BaseModel"; class Membership extends BaseModel { @@ -8,16 +9,11 @@ class Membership extends BaseModel { collectionId: string; - permission: string; + permission: CollectionPermission; @computed get isEditor(): boolean { - return this.permission === "read_write"; - } - - @computed - get isMaintainer(): boolean { - return this.permission === "maintainer"; + return this.permission === CollectionPermission.ReadWrite; } } diff --git a/app/scenes/CollectionNew.tsx b/app/scenes/CollectionNew.tsx index 8e26bb6bc..e6293212a 100644 --- a/app/scenes/CollectionNew.tsx +++ b/app/scenes/CollectionNew.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import * as React from "react"; import { withTranslation, Trans, WithTranslation } from "react-i18next"; import { randomElement } from "@shared/random"; +import { CollectionPermission } from "@shared/types"; import { colorPalette } from "@shared/utils/collections"; import { CollectionValidation } from "@shared/validations"; import RootStore from "~/stores/RootStore"; @@ -38,7 +39,7 @@ class CollectionNew extends React.Component { sharing = true; @observable - permission = "read_write"; + permission = CollectionPermission.ReadWrite; @observable isSaving: boolean; @@ -100,8 +101,8 @@ class CollectionNew extends React.Component { this.hasOpenedIconPicker = true; }; - handlePermissionChange = (newPermission: string) => { - this.permission = newPermission; + handlePermissionChange = (permission: CollectionPermission) => { + this.permission = permission; }; handleSharingChange = (ev: React.ChangeEvent) => { diff --git a/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx b/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx index 32981beb3..59059f52c 100644 --- a/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx +++ b/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx @@ -59,7 +59,6 @@ class AddGroupsToCollection extends React.Component { this.props.collectionGroupMemberships.create({ collectionId: this.props.collection.id, groupId: group.id, - permission: "read_write", }); this.props.toasts.showToast( t("{{ groupName }} was added to the collection", { diff --git a/app/scenes/CollectionPermissions/AddPeopleToCollection.tsx b/app/scenes/CollectionPermissions/AddPeopleToCollection.tsx index 596fe25d0..5cefd0932 100644 --- a/app/scenes/CollectionPermissions/AddPeopleToCollection.tsx +++ b/app/scenes/CollectionPermissions/AddPeopleToCollection.tsx @@ -57,7 +57,6 @@ class AddPeopleToCollection extends React.Component { this.props.memberships.create({ collectionId: this.props.collection.id, userId: user.id, - permission: "read_write", }); this.props.toasts.showToast( t("{{ userName }} was added to the collection", { diff --git a/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.tsx b/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.tsx index ae63edc89..8bc8eae78 100644 --- a/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.tsx +++ b/app/scenes/CollectionPermissions/components/CollectionGroupMemberListItem.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; +import { CollectionPermission } from "@shared/types"; import CollectionGroupMembership from "~/models/CollectionGroupMembership"; import Group from "~/models/Group"; import GroupListItem from "~/components/GroupListItem"; @@ -10,7 +11,7 @@ import CollectionGroupMemberMenu from "~/menus/CollectionGroupMemberMenu"; type Props = { group: Group; collectionGroupMembership: CollectionGroupMembership | null | undefined; - onUpdate: (permission: string) => void; + onUpdate: (permission: CollectionPermission) => void; onRemove: () => void; }; @@ -21,19 +22,6 @@ const CollectionGroupMemberListItem = ({ onRemove, }: Props) => { const { t } = useTranslation(); - const PERMISSIONS = React.useMemo( - () => [ - { - label: t("View only"), - value: "read", - }, - { - label: t("View and edit"), - value: "read_write", - }, - ], - [t] - ); return (