Files
outline/server/models/Relationship.ts
T
codegen-sh[bot] c2069db882 Migrate Backlink model to Relationship (#9370)
* Migrate Backlink model to generic Relationship model

- Create new Relationship model with type field to support different relationship types
- Add database migration to create relationships table and migrate existing backlinks
- Update Backlink model to delegate to Relationship model for backward compatibility
- Update BacklinksProcessor to use Relationship model with backlink type
- Update API routes to use new Relationship model
- Update test files to use Relationship model
- Maintain backward compatibility through database view and model delegation

Fixes #9366

* Update migration to rename table instead of creating new one

- Rename existing backlinks table to relationships instead of creating new table
- Add type column with default value to existing table
- Update existing rows to have type='backlink'
- Avoid expensive data migration by keeping existing data in place
- Maintain backward compatibility with database view
- Update rollback to reverse table rename and column addition

This approach is much more efficient for large datasets as it avoids copying millions of rows.

* Remove unnecessary UPDATE statement from migration

The UPDATE statement is not needed since defaultValue automatically
applies to existing rows when adding a column with a default value.

Thanks @tommoor for catching this!

* Wrap up migration in transaction

- Wrap all migration operations in a transaction for atomicity
- Add transaction parameter to all queryInterface calls
- Follow the same pattern as other migrations in the codebase
- Ensures all operations succeed or fail together

* Remove Backlink class entirely and use Relationship everywhere

- Delete server/models/Backlink.ts
- Remove Backlink export from server/models/index.ts
- Remove Backlink import and association from Document model
- All functionality now uses Relationship model with RelationshipType.Backlink
- Maintains same API through Relationship model methods
- Cleaner architecture with single relationship model

* Update documents.test.ts to use RelationshipType enum instead of string

- Import RelationshipType from Relationship model
- Replace type: "backlink" with type: RelationshipType.Backlink
- Improves type safety and consistency with enum usage

* Address code review feedback

- Add transaction wrapper to migration down method for safer rollback
- Remove unused findByTypeForUser method from Relationship model
- Method wasn't used and won't work for all relationship types (e.g., user mentions)
- Clean up code structure and improve safety

* Restore imports

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-07 15:50:33 -04:00

80 lines
1.8 KiB
TypeScript

import { 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",
}
@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
*/
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),
{ userId: user.id }
);
return documents.map((doc) => doc.id);
}
}
export default Relationship;