Files
Tom Moor 7663c2a643 feat: Add backlinks to publicly shared documents (#11141)
* Add backlinks support for publicly shared documents

Include backlinks in the documents.info API response for publicly shared documents, filtering to only show backlinks that exist within the shared tree.

Changes:
- Add findSourceDocumentIdsInSharedTree method to Relationship model to find backlinks within allowed document IDs
- Export getAllIdsInSharedTree helper from shareLoader for reuse
- Update presentDocument to accept and include backlinkIds in response
- Modify documents.info endpoint to fetch and include backlinks for public shares
- Add backlinkIds property to Document model and update backlinks getter to use it when available

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor

* refactor

* wip

* tsc

* fix: Signed-out view throw, spacing

* revert

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 16:18:45 -05:00

110 lines
2.7 KiB
TypeScript

import type { InferAttributes, InferCreationAttributes } from "sequelize";
import {
DataType,
BelongsTo,
ForeignKey,
Column,
Table,
} from "sequelize-typescript";
import Document from "./Document";
import User from "./User";
import IdModel from "./base/IdModel";
import Fix from "./decorators/Fix";
export enum RelationshipType {
Backlink = "backlink",
Similar = "similar",
}
@Table({ tableName: "relationships", modelName: "relationship" })
@Fix
class Relationship extends IdModel<
InferAttributes<Relationship>,
Partial<InferCreationAttributes<Relationship>>
> {
@BelongsTo(() => User, "userId")
user: User;
@ForeignKey(() => User)
@Column(DataType.UUID)
userId: string;
@BelongsTo(() => Document, "documentId")
document: Document;
@ForeignKey(() => Document)
@Column(DataType.UUID)
documentId: string;
@BelongsTo(() => Document, "reverseDocumentId")
reverseDocument: Document;
@ForeignKey(() => Document)
@Column(DataType.UUID)
reverseDocumentId: string;
@Column({
type: DataType.ENUM(...Object.values(RelationshipType)),
allowNull: false,
defaultValue: RelationshipType.Backlink,
})
type: RelationshipType;
/**
* Find all backlinks for a document that the user has access to.
*
* @param documentId The document ID to find backlinks for.
* @param user The user to check access for.
* @deprecated
*/
public static async findSourceDocumentIdsForUser(
documentId: string,
user: User
) {
const relationships = await this.findAll({
attributes: ["reverseDocumentId"],
where: {
documentId,
type: RelationshipType.Backlink,
},
});
const documents = await Document.findByIds(
relationships.map((relationship) => relationship.reverseDocumentId),
{
attributes: ["id"],
userId: user.id,
includeState: false,
includeViews: false,
}
);
return documents.map((doc) => doc.id);
}
/**
* Find all backlinks for a document that are within a shared tree.
*
* @param documentId The document ID to find backlinks for.
* @param allowedDocumentIds Array of document IDs that are accessible in the shared tree.
* @returns Array of document IDs that link to the target document and are within the shared tree.
*/
public static async findSourceDocumentIdsInSharedTree(
documentId: string,
allowedDocumentIds: string[]
) {
const relationships = await this.findAll({
attributes: ["reverseDocumentId"],
where: {
documentId,
type: RelationshipType.Backlink,
reverseDocumentId: allowedDocumentIds,
},
});
return relationships.map((relationship) => relationship.reverseDocumentId);
}
}
export default Relationship;