mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
fix: Filter relationships returned from list endpoint (#11738)
* fix: Filter relationships returned from list endpoint * fix: BacklinksProcessor does not check teamId * Port from upstream
This commit is contained in:
@@ -61,7 +61,7 @@ function Overview({ collection, readOnly }: Props) {
|
||||
() => ({
|
||||
padding: "0 32px",
|
||||
margin: "0 -32px",
|
||||
paddingBottom: `calc(50vh - ${childOffsetHeight}px)`,
|
||||
paddingBottom: `calc(30vh - ${childOffsetHeight}px)`,
|
||||
}),
|
||||
[childOffsetHeight]
|
||||
);
|
||||
|
||||
@@ -172,7 +172,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
() => ({
|
||||
padding: "0 32px",
|
||||
margin: "0 -32px",
|
||||
paddingBottom: `calc(50vh - ${childOffsetHeight}px)`,
|
||||
paddingBottom: `calc(30vh - ${childOffsetHeight}px)`,
|
||||
}),
|
||||
[childOffsetHeight]
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ function References({ document }: Props) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isShare) {
|
||||
void documents.fetchBacklinks(document.id);
|
||||
void documents.fetchRelationships(document.id);
|
||||
}
|
||||
}, [isShare, documents, document.id]);
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ export default class DocumentsStore extends Store<Document> {
|
||||
@observable
|
||||
backlinks: Map<string, string[]> = new Map();
|
||||
|
||||
@observable
|
||||
similar: Map<string, string[]> = new Map();
|
||||
|
||||
@observable
|
||||
movingDocumentId: string | null | undefined;
|
||||
|
||||
@@ -254,16 +257,27 @@ export default class DocumentsStore extends Store<Document> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchBacklinks = async (documentId: string): Promise<void> => {
|
||||
const documents = await this.fetchAll({
|
||||
backlinkDocumentId: documentId,
|
||||
});
|
||||
fetchRelationships = async (documentId: string): Promise<void> => {
|
||||
const res = await client.post("/relationships.list", { documentId });
|
||||
invariant(res?.data, "Relationships not available");
|
||||
|
||||
runInAction("DocumentsStore#fetchBacklinks", () => {
|
||||
this.backlinks.set(
|
||||
documentId,
|
||||
documents.map((doc) => doc.id)
|
||||
);
|
||||
runInAction("DocumentsStore#fetchRelationships", () => {
|
||||
res.data.documents.forEach(this.add);
|
||||
this.addPolicies(res.policies);
|
||||
|
||||
const backlinkIds: string[] = [];
|
||||
const similarIds: string[] = [];
|
||||
|
||||
for (const relationship of res.data.relationships) {
|
||||
if (relationship.type === "backlink") {
|
||||
backlinkIds.push(relationship.reverseDocumentId);
|
||||
} else if (relationship.type === "similar") {
|
||||
similarIds.push(relationship.reverseDocumentId);
|
||||
}
|
||||
}
|
||||
|
||||
this.backlinks.set(documentId, backlinkIds);
|
||||
this.similar.set(documentId, similarIds);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -276,6 +290,15 @@ export default class DocumentsStore extends Store<Document> {
|
||||
);
|
||||
}
|
||||
|
||||
getSimilarDocuments(documentId: string): Document[] {
|
||||
const documentIds = this.similar.get(documentId) || [];
|
||||
return orderBy(
|
||||
compact(documentIds.map((id) => this.data.get(id))),
|
||||
"title",
|
||||
"asc"
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
fetchChildDocuments = async (documentId: string): Promise<void> => {
|
||||
const res = await client.post(`/documents.list`, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parser } from "@server/editor";
|
||||
import { Relationship } from "@server/models";
|
||||
import { RelationshipType } from "@server/models/Relationship";
|
||||
import { buildDocument } from "@server/test/factories";
|
||||
import { buildDocument, buildTeam } from "@server/test/factories";
|
||||
|
||||
import BacklinksProcessor from "./BacklinksProcessor";
|
||||
|
||||
@@ -9,8 +9,10 @@ const ip = "127.0.0.1";
|
||||
|
||||
describe("documents.publish", () => {
|
||||
it("should create new backlink records", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
text: `[this is a link](${otherDocument.url})`,
|
||||
});
|
||||
|
||||
@@ -33,9 +35,11 @@ describe("documents.publish", () => {
|
||||
});
|
||||
|
||||
it("should not fail when linked document is destroyed", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
await otherDocument.destroy();
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
version: 0,
|
||||
text: `[ ] checklist item`,
|
||||
});
|
||||
@@ -61,12 +65,41 @@ describe("documents.publish", () => {
|
||||
});
|
||||
expect(backlinks.length).toBe(0);
|
||||
});
|
||||
|
||||
it("should not create backlink records for cross-team links", async () => {
|
||||
const teamA = await buildTeam();
|
||||
const teamB = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: teamB.id });
|
||||
const document = await buildDocument({
|
||||
teamId: teamA.id,
|
||||
text: `[this is a link](${otherDocument.url})`,
|
||||
});
|
||||
|
||||
const processor = new BacklinksProcessor();
|
||||
await processor.perform({
|
||||
name: "documents.publish",
|
||||
documentId: document.id,
|
||||
collectionId: document.collectionId!,
|
||||
teamId: document.teamId,
|
||||
actorId: document.createdById,
|
||||
ip,
|
||||
});
|
||||
const backlinks = await Relationship.findAll({
|
||||
where: {
|
||||
reverseDocumentId: document.id,
|
||||
type: RelationshipType.Backlink,
|
||||
},
|
||||
});
|
||||
expect(backlinks.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("documents.update", () => {
|
||||
it("should not fail on a document with no previous revisions", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
text: `[this is a link](${otherDocument.url})`,
|
||||
});
|
||||
|
||||
@@ -91,8 +124,10 @@ describe("documents.update", () => {
|
||||
});
|
||||
|
||||
it("should not fail when previous revision is different document version", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
version: undefined,
|
||||
text: `[ ] checklist item`,
|
||||
});
|
||||
@@ -122,8 +157,9 @@ describe("documents.update", () => {
|
||||
});
|
||||
|
||||
it("should create new backlink records", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const document = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({ teamId: team.id });
|
||||
document.content = parser
|
||||
.parse(`[this is a link](${otherDocument.url})`)
|
||||
?.toJSON();
|
||||
@@ -150,9 +186,11 @@ describe("documents.update", () => {
|
||||
});
|
||||
|
||||
it("should destroy removed backlink records", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const yetAnotherDocument = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const yetAnotherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
text: `[this is a link](${otherDocument.url})
|
||||
|
||||
[this is a another link](${yetAnotherDocument.url})`,
|
||||
@@ -199,8 +237,9 @@ describe("documents.update", () => {
|
||||
|
||||
describe("documents.delete", () => {
|
||||
it("should destroy related backlinks", async () => {
|
||||
const otherDocument = await buildDocument();
|
||||
const document = await buildDocument();
|
||||
const team = await buildTeam();
|
||||
const otherDocument = await buildDocument({ teamId: team.id });
|
||||
const document = await buildDocument({ teamId: team.id });
|
||||
document.content = parser
|
||||
.parse(`[this is a link](${otherDocument.url})`)
|
||||
?.toJSON();
|
||||
|
||||
@@ -26,10 +26,14 @@ export default class BacklinksProcessor extends BaseProcessor {
|
||||
await Promise.all(
|
||||
linkIds.map(async (linkId) => {
|
||||
const linkedDocument = await Document.findByPk(linkId, {
|
||||
attributes: ["id"],
|
||||
attributes: ["id", "teamId"],
|
||||
});
|
||||
|
||||
if (!linkedDocument || linkedDocument.id === event.documentId) {
|
||||
if (
|
||||
!linkedDocument ||
|
||||
linkedDocument.id === event.documentId ||
|
||||
linkedDocument.teamId !== document.teamId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,10 +76,14 @@ export default class BacklinksProcessor extends BaseProcessor {
|
||||
await Promise.all(
|
||||
linkIds.map(async (linkId) => {
|
||||
const linkedDocument = await Document.findByPk(linkId, {
|
||||
attributes: ["id"],
|
||||
attributes: ["id", "teamId"],
|
||||
});
|
||||
|
||||
if (!linkedDocument || linkedDocument.id === event.documentId) {
|
||||
if (
|
||||
!linkedDocument ||
|
||||
linkedDocument.id === event.documentId ||
|
||||
linkedDocument.teamId !== document.teamId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@ describe("#relationships.list", () => {
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
|
||||
expect(body.data.relationships).toHaveLength(1);
|
||||
expect(body.data.relationships).toHaveLength(0);
|
||||
expect(body.data.documents).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,16 +77,22 @@ router.post(
|
||||
{ userId: user.id }
|
||||
);
|
||||
|
||||
const policies = presentPolicies(user, [...documents, ...relationships]);
|
||||
const documentIds = new Set(documents.map((d) => d.id));
|
||||
const filteredRelationships = relationships.filter((relationship) =>
|
||||
documentIds.has(
|
||||
where.reverseDocumentId
|
||||
? relationship.documentId
|
||||
: relationship.reverseDocumentId
|
||||
)
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
pagination: ctx.state.pagination,
|
||||
data: {
|
||||
relationships: relationships.map(presentRelationship),
|
||||
relationships: filteredRelationships.map(presentRelationship),
|
||||
documents: await presentDocuments(ctx, documents),
|
||||
policies: presentPolicies(user, documents),
|
||||
},
|
||||
policies,
|
||||
policies: presentPolicies(user, [...documents, ...filteredRelationships]),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user