feat: Collection admins (#5273
* Split permissions for reading documents from updating collection * fix: Admins should have collection read permission, tests * tsc * Add admin option to permission selector * Combine publish and create permissions, update -> createDocuments where appropriate * Plural -> singular * wip * Quick version of collection structure loading, will revisit * Remove documentIds method * stash * fixing tests to account for admin creation * Add self-hosted migration * fix: Allow groups to have admin permission * Prefetch collection documents * fix: Document explorer (move/publish) not working with async documents * fix: Cannot re-parent document to collection by drag and drop * fix: Cannot drag to import into collection item without admin permission * Remove unused isEditor getter
This commit is contained in:
@@ -695,6 +695,12 @@ describe("#documents.list", () => {
|
||||
const { user, collection } = await seed();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.destroy({
|
||||
where: {
|
||||
userId: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
});
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -766,12 +772,18 @@ describe("#documents.list", () => {
|
||||
const { user, collection } = await seed();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.create({
|
||||
createdById: user.id,
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
permission: CollectionPermission.Read,
|
||||
});
|
||||
await CollectionUser.update(
|
||||
{
|
||||
userId: user.id,
|
||||
permission: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
createdById: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -891,6 +903,12 @@ describe("#documents.drafts", () => {
|
||||
await document.save();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.destroy({
|
||||
where: {
|
||||
userId: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
});
|
||||
const res = await server.post("/api/documents.drafts", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -1792,6 +1810,12 @@ describe("#documents.viewed", () => {
|
||||
});
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.destroy({
|
||||
where: {
|
||||
userId: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
});
|
||||
const res = await server.post("/api/documents.viewed", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -2565,12 +2589,18 @@ describe("#documents.update", () => {
|
||||
await document.save();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.create({
|
||||
createdById: user.id,
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
permission: CollectionPermission.ReadWrite,
|
||||
});
|
||||
await CollectionUser.update(
|
||||
{
|
||||
userId: user.id,
|
||||
permission: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
createdById: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -2634,18 +2664,24 @@ describe("#documents.update", () => {
|
||||
});
|
||||
|
||||
it("allows editing by read-write collection user", async () => {
|
||||
const { admin, document, collection } = await seed();
|
||||
const { user, document, collection } = await seed();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.create({
|
||||
collectionId: collection.id,
|
||||
userId: admin.id,
|
||||
createdById: admin.id,
|
||||
permission: CollectionPermission.ReadWrite,
|
||||
});
|
||||
await CollectionUser.update(
|
||||
{
|
||||
createdById: user.id,
|
||||
permission: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: admin.getJwtToken(),
|
||||
token: user.getJwtToken(),
|
||||
id: document.id,
|
||||
text: "Changed text",
|
||||
},
|
||||
@@ -2653,19 +2689,25 @@ describe("#documents.update", () => {
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.text).toBe("Changed text");
|
||||
expect(body.data.updatedBy.id).toBe(admin.id);
|
||||
expect(body.data.updatedBy.id).toBe(user.id);
|
||||
});
|
||||
|
||||
it("does not allow editing by read-only collection user", async () => {
|
||||
const { user, document, collection } = await seed();
|
||||
collection.permission = null;
|
||||
await collection.save();
|
||||
await CollectionUser.create({
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
permission: CollectionPermission.Read,
|
||||
});
|
||||
await CollectionUser.update(
|
||||
{
|
||||
createdById: user.id,
|
||||
permission: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -2680,6 +2722,12 @@ describe("#documents.update", () => {
|
||||
const { user, document, collection } = await seed();
|
||||
collection.permission = CollectionPermission.Read;
|
||||
await collection.save();
|
||||
await CollectionUser.destroy({
|
||||
where: {
|
||||
userId: user.id,
|
||||
collectionId: collection.id,
|
||||
},
|
||||
});
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
@@ -2831,10 +2879,6 @@ describe("#documents.update", () => {
|
||||
expect(body.data.document.collectionId).toBe(collection.id);
|
||||
expect(body.data.document.title).toBe("Updated title");
|
||||
expect(body.data.document.text).toBe("Updated text");
|
||||
expect(body.data.collection.icon).toBe(collection.icon);
|
||||
expect(body.data.collection.documents.length).toBe(
|
||||
collection.documentStructure!.length + 1
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ router.post(
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "read", collection);
|
||||
authorize(user, "readDocument", collection);
|
||||
|
||||
// index sort is special because it uses the order of the documents in the
|
||||
// collection.documentStructure rather than a database column
|
||||
@@ -342,7 +342,7 @@ router.post(
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "read", collection);
|
||||
authorize(user, "readDocument", collection);
|
||||
}
|
||||
|
||||
const collectionIds = collectionId
|
||||
@@ -599,7 +599,7 @@ router.post(
|
||||
}
|
||||
|
||||
if (document.collection) {
|
||||
authorize(user, "update", collection);
|
||||
authorize(user, "updateDocument", collection);
|
||||
}
|
||||
|
||||
if (document.deletedAt) {
|
||||
@@ -686,7 +686,7 @@ router.post(
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "read", collection);
|
||||
authorize(user, "readDocument", collection);
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
@@ -780,7 +780,7 @@ router.post(
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "read", collection);
|
||||
authorize(user, "readDocument", collection);
|
||||
}
|
||||
|
||||
let collaboratorIds = undefined;
|
||||
@@ -921,7 +921,7 @@ router.post(
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId!);
|
||||
}
|
||||
authorize(user, "publish", collection);
|
||||
authorize(user, "createDocument", collection);
|
||||
}
|
||||
|
||||
collection = await sequelize.transaction(async (transaction) => {
|
||||
@@ -982,7 +982,7 @@ router.post(
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "update", collection);
|
||||
authorize(user, "updateDocument", collection);
|
||||
|
||||
if (parentDocumentId) {
|
||||
const parent = await Document.findByPk(parentDocumentId, {
|
||||
@@ -1205,7 +1205,7 @@ router.post(
|
||||
teamId: user.teamId,
|
||||
},
|
||||
});
|
||||
authorize(user, "publish", collection);
|
||||
authorize(user, "createDocument", collection);
|
||||
let parentDocument;
|
||||
|
||||
if (parentDocumentId) {
|
||||
@@ -1282,7 +1282,7 @@ router.post(
|
||||
teamId: user.teamId,
|
||||
},
|
||||
});
|
||||
authorize(user, "publish", collection);
|
||||
authorize(user, "createDocument", collection);
|
||||
}
|
||||
|
||||
let parentDocument;
|
||||
|
||||
@@ -8,7 +8,9 @@ const DocumentsSortParamsSchema = z.object({
|
||||
/** Specifies the attributes by which documents will be sorted in the list */
|
||||
sort: z
|
||||
.string()
|
||||
.refine((val) => ["createdAt", "updatedAt", "index", "title"].includes(val))
|
||||
.refine((val) =>
|
||||
["createdAt", "updatedAt", "publishedAt", "index", "title"].includes(val)
|
||||
)
|
||||
.default("updatedAt"),
|
||||
|
||||
/** Specifies the sort order with respect to sort field */
|
||||
|
||||
Reference in New Issue
Block a user