feat: Add ability to star collection (#3327)
* Migrations, models, commands * ui * Move starred hint to location state * lint * tsc * refactor * Add collection empty state in expanded sidebar * Add empty placeholder within starred collections * Drag and drop improves, Relative refactor * fix: Starring untitled draft leaves empty space * fix: Creating draft in starred collection shouldnt open main * fix: Dupe drop cursor * Final fixes * fix: Canonical redirect replaces starred location state * fix: Don't show reorder cursor at the top of collection with no permission to edit when dragging
This commit is contained in:
@@ -53,15 +53,6 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`#documents.starred should require authentication 1`] = `
|
||||
Object {
|
||||
"error": "authentication_required",
|
||||
"message": "Authentication required",
|
||||
"ok": false,
|
||||
"status": 401,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`#documents.unstar should require authentication 1`] = `
|
||||
Object {
|
||||
"error": "authentication_required",
|
||||
|
||||
@@ -1455,45 +1455,6 @@ describe("#documents.viewed", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.starred", () => {
|
||||
it("should return empty result if no stars", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.starred", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("should return starred documents", async () => {
|
||||
const { user, document } = await seed();
|
||||
await Star.create({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
});
|
||||
const res = await server.post("/api/documents.starred", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(1);
|
||||
expect(body.data[0].id).toEqual(document.id);
|
||||
expect(body.policies[0].abilities.update).toEqual(true);
|
||||
});
|
||||
|
||||
it("should require authentication", async () => {
|
||||
const res = await server.post("/api/documents.starred");
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(401);
|
||||
expect(body).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.move", () => {
|
||||
it("should move the document", async () => {
|
||||
const { user, document } = await seed();
|
||||
|
||||
@@ -313,62 +313,6 @@ router.post("documents.viewed", auth(), pagination(), async (ctx) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Deprecated – use stars.list instead
|
||||
router.post("documents.starred", auth(), pagination(), async (ctx) => {
|
||||
let { direction } = ctx.body;
|
||||
const { sort = "updatedAt" } = ctx.body;
|
||||
|
||||
assertSort(sort, Document);
|
||||
if (direction !== "ASC") {
|
||||
direction = "DESC";
|
||||
}
|
||||
const { user } = ctx.state;
|
||||
const collectionIds = await user.collectionIds();
|
||||
const stars = await Star.findAll({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
order: [[sort, direction]],
|
||||
include: [
|
||||
{
|
||||
model: Document,
|
||||
where: {
|
||||
collectionId: collectionIds,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}),
|
||||
as: "collection",
|
||||
},
|
||||
{
|
||||
model: Star,
|
||||
as: "starred",
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
offset: ctx.state.pagination.offset,
|
||||
limit: ctx.state.pagination.limit,
|
||||
});
|
||||
|
||||
const documents = stars.map((star) => star.document);
|
||||
const data = await Promise.all(
|
||||
documents.map((document) => presentDocument(document))
|
||||
);
|
||||
const policies = presentPolicies(user, documents);
|
||||
|
||||
ctx.body = {
|
||||
pagination: ctx.state.pagination,
|
||||
data,
|
||||
policies,
|
||||
};
|
||||
});
|
||||
|
||||
router.post("documents.drafts", auth(), pagination(), async (ctx) => {
|
||||
let { direction } = ctx.body;
|
||||
const { collectionId, dateFilter, sort = "updatedAt" } = ctx.body;
|
||||
|
||||
@@ -3,8 +3,9 @@ import { Sequelize } from "sequelize";
|
||||
import starCreator from "@server/commands/starCreator";
|
||||
import starDestroyer from "@server/commands/starDestroyer";
|
||||
import starUpdater from "@server/commands/starUpdater";
|
||||
import { sequelize } from "@server/database/sequelize";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { Document, Star } from "@server/models";
|
||||
import { Document, Star, Collection } from "@server/models";
|
||||
import { authorize } from "@server/policies";
|
||||
import {
|
||||
presentStar,
|
||||
@@ -18,27 +19,43 @@ import pagination from "./middlewares/pagination";
|
||||
const router = new Router();
|
||||
|
||||
router.post("stars.create", auth(), async (ctx) => {
|
||||
const { documentId } = ctx.body;
|
||||
const { documentId, collectionId } = ctx.body;
|
||||
const { index } = ctx.body;
|
||||
assertUuid(documentId, "documentId is required");
|
||||
|
||||
const { user } = ctx.state;
|
||||
const document = await Document.findByPk(documentId, {
|
||||
userId: user.id,
|
||||
});
|
||||
authorize(user, "star", document);
|
||||
|
||||
assertUuid(
|
||||
documentId || collectionId,
|
||||
"documentId or collectionId is required"
|
||||
);
|
||||
|
||||
if (documentId) {
|
||||
const document = await Document.findByPk(documentId, {
|
||||
userId: user.id,
|
||||
});
|
||||
authorize(user, "star", document);
|
||||
}
|
||||
|
||||
if (collectionId) {
|
||||
const collection = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "star", collection);
|
||||
}
|
||||
|
||||
if (index) {
|
||||
assertIndexCharacters(index);
|
||||
}
|
||||
|
||||
const star = await starCreator({
|
||||
user,
|
||||
documentId,
|
||||
ip: ctx.request.ip,
|
||||
index,
|
||||
});
|
||||
|
||||
const star = await sequelize.transaction(async (transaction) =>
|
||||
starCreator({
|
||||
user,
|
||||
documentId,
|
||||
collectionId,
|
||||
ip: ctx.request.ip,
|
||||
index,
|
||||
transaction,
|
||||
})
|
||||
);
|
||||
ctx.body = {
|
||||
data: presentStar(star),
|
||||
policies: presentPolicies(user, [star]),
|
||||
@@ -72,12 +89,17 @@ router.post("stars.list", auth(), pagination(), async (ctx) => {
|
||||
});
|
||||
}
|
||||
|
||||
const documents = await Document.defaultScopeWithUser(user.id).findAll({
|
||||
where: {
|
||||
id: stars.map((star) => star.documentId),
|
||||
collectionId: collectionIds,
|
||||
},
|
||||
});
|
||||
const documentIds = stars
|
||||
.map((star) => star.documentId)
|
||||
.filter(Boolean) as string[];
|
||||
const documents = documentIds.length
|
||||
? await Document.defaultScopeWithUser(user.id).findAll({
|
||||
where: {
|
||||
id: documentIds,
|
||||
collectionId: collectionIds,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const policies = presentPolicies(user, [...documents, ...stars]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user