feat: Add read-only collections (#1991)

closes #1017
This commit is contained in:
Tom Moor
2021-03-30 21:02:08 -07:00
committed by GitHub
parent d7acf616cf
commit 7e1b07ef98
50 changed files with 940 additions and 558 deletions

View File

@@ -25,7 +25,14 @@ const Collection = sequelize.define(
type: DataTypes.STRING,
defaultValue: null,
},
private: DataTypes.BOOLEAN,
permission: {
type: DataTypes.STRING,
defaultValue: null,
allowNull: true,
validate: {
isIn: [["read", "read_write"]],
},
},
maintainerApprovalRequired: DataTypes.BOOLEAN,
documentStructure: DataTypes.JSONB,
sharing: {
@@ -199,7 +206,7 @@ Collection.addHook("afterDestroy", async (model: Collection) => {
});
Collection.addHook("afterCreate", (model: Collection, options) => {
if (model.private) {
if (model.permission !== "read_write") {
return CollectionUser.findOrCreate({
where: {
collectionId: model.id,

View File

@@ -254,7 +254,7 @@ describe("#membershipUserIds", () => {
const collection = await buildCollection({
userId: users[0].id,
private: true,
permission: null,
teamId,
});

View File

@@ -178,7 +178,7 @@ describe("#searchForTeam", () => {
test("should not return search results from private collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({
private: true,
permission: null,
teamId: team.id,
});
await buildDocument({

View File

@@ -15,11 +15,11 @@ describe("afterDestroy hook", () => {
const user2 = await buildUser({ teamId });
const collection1 = await buildCollection({
private: true,
permission: null,
teamId,
});
const collection2 = await buildCollection({
private: true,
permission: null,
teamId,
});

View File

@@ -225,10 +225,16 @@ Team.prototype.activateUser = async function (user: User, admin: User) {
Team.prototype.collectionIds = async function (paranoid: boolean = true) {
let models = await Collection.findAll({
attributes: ["id", "private"],
where: { teamId: this.id, private: false },
attributes: ["id"],
where: {
teamId: this.id,
permission: {
[Op.ne]: null,
},
},
paranoid,
});
return models.map((c) => c.id);
};

View File

@@ -1,38 +1,56 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { buildTeam } from "../test/factories";
// @flow
import { buildTeam, buildCollection } from "../test/factories";
import { flushdb } from "../test/support";
beforeEach(() => flushdb());
it("should set subdomain if available", async () => {
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("testy");
expect(subdomain).toEqual("testy");
expect(team.subdomain).toEqual("testy");
describe("collectionIds", () => {
it("should return non-private collection ids", async () => {
const team = await buildTeam();
const collection = await buildCollection({ teamId: team.id });
// build a collection in another team
await buildCollection();
// build a private collection
await buildCollection({ teamId: team.id, permission: null });
const response = await team.collectionIds();
expect(response.length).toEqual(1);
expect(response[0]).toEqual(collection.id);
});
});
it("should set subdomain append if unavailable", async () => {
await buildTeam({ subdomain: "myteam" });
describe("provisionSubdomain", () => {
it("should set subdomain if available", async () => {
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("testy");
expect(subdomain).toEqual("testy");
expect(team.subdomain).toEqual("testy");
});
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("myteam1");
expect(team.subdomain).toEqual("myteam1");
});
it("should increment subdomain append if unavailable", async () => {
await buildTeam({ subdomain: "myteam" });
await buildTeam({ subdomain: "myteam1" });
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("myteam2");
expect(team.subdomain).toEqual("myteam2");
});
it("should do nothing if subdomain already set", async () => {
const team = await buildTeam({ subdomain: "example" });
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("example");
expect(team.subdomain).toEqual("example");
it("should set subdomain append if unavailable", async () => {
await buildTeam({ subdomain: "myteam" });
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("myteam1");
expect(team.subdomain).toEqual("myteam1");
});
it("should increment subdomain append if unavailable", async () => {
await buildTeam({ subdomain: "myteam" });
await buildTeam({ subdomain: "myteam1" });
const team = await buildTeam();
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("myteam2");
expect(team.subdomain).toEqual("myteam2");
});
it("should do nothing if subdomain already set", async () => {
const team = await buildTeam({ subdomain: "example" });
const subdomain = await team.provisionSubdomain("myteam");
expect(subdomain).toEqual("example");
expect(team.subdomain).toEqual("example");
});
});

View File

@@ -91,7 +91,7 @@ User.prototype.collectionIds = async function (options = {}) {
const collectionStubs = await Collection.scope({
method: ["withMembership", this.id],
}).findAll({
attributes: ["id", "private"],
attributes: ["id", "permission"],
where: { teamId: this.teamId },
paranoid: true,
...options,
@@ -100,7 +100,8 @@ User.prototype.collectionIds = async function (options = {}) {
return collectionStubs
.filter(
(c) =>
!c.private ||
c.permission === "read" ||
c.permission === "read_write" ||
c.memberships.length > 0 ||
c.collectionGroupMemberships.length > 0
)

View File

@@ -1,10 +1,75 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { buildUser } from "../test/factories";
// @flow
import { CollectionUser } from "../models";
import { buildUser, buildTeam, buildCollection } from "../test/factories";
import { flushdb } from "../test/support";
beforeEach(() => flushdb());
it("should set JWT secret", async () => {
const user = await buildUser();
expect(user.getJwtToken()).toBeTruthy();
describe("user model", () => {
describe("getJwtToken", () => {
it("should set JWT secret", async () => {
const user = await buildUser();
expect(user.getJwtToken()).toBeTruthy();
});
});
describe("collectionIds", () => {
it("should return read_write collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
teamId: team.id,
permission: "read_write",
});
const response = await user.collectionIds();
expect(response.length).toEqual(1);
expect(response[0]).toEqual(collection.id);
});
it("should return read collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
teamId: team.id,
permission: "read",
});
const response = await user.collectionIds();
expect(response.length).toEqual(1);
expect(response[0]).toEqual(collection.id);
});
it("should not return private collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
await buildCollection({
teamId: team.id,
permission: null,
});
const response = await user.collectionIds();
expect(response.length).toEqual(0);
});
it("should not return private collection with membership", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
teamId: team.id,
permission: null,
});
await CollectionUser.create({
createdById: user.id,
collectionId: collection.id,
userId: user.id,
permission: "read",
});
const response = await user.collectionIds();
expect(response.length).toEqual(1);
expect(response[0]).toEqual(collection.id);
});
});
});