Add database migration and model changes for Star folders

- Add parentId and isFolder columns to stars table
- Add self-referential relationship for hierarchical structure
- Add database constraints to ensure folders don't have content
- Add indexes for efficient folder queries
- Update server and client Star models with new fields
- Add helper methods for folder operations
- Update Star presenter to include new fields

This enables users to organize their starred items into folders
while maintaining backward compatibility with existing stars.
This commit is contained in:
codegen-sh[bot]
2025-09-14 13:43:33 +00:00
parent bf68a1d2bf
commit a340c9ea41
4 changed files with 114 additions and 1 deletions
+35 -1
View File
@@ -14,6 +14,16 @@ class Star extends Model {
@observable
index: string;
/** Whether this star represents a folder */
@Field
@observable
isFolder: boolean;
/** The parent folder ID */
@Field
@observable
parentId?: string;
/** The document ID that is starred. */
documentId?: string;
@@ -43,7 +53,31 @@ class Star extends Model {
*/
previous(): Star | undefined {
const index = this.store.orderedData.indexOf(this);
return this.store.orderedData[index + 1];
return this.store.orderedData[index - 1];
}
/**
* Returns true if this star is a folder.
*/
get isStarFolder(): boolean {
return this.isFolder === true;
}
/**
* Returns the children of this folder, or empty array if not a folder.
*/
get folderChildren(): Star[] {
if (!this.isStarFolder) {
return [];
}
return this.store.orderedData.filter(star => star.parentId === this.id);
}
/**
* Returns true if this star has content (document or collection).
*/
get hasContent(): boolean {
return !!(this.documentId || this.collectionId);
}
}
@@ -0,0 +1,63 @@
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
// Add parentId column for hierarchical structure
await queryInterface.addColumn("stars", "parentId", {
type: Sequelize.UUID,
allowNull: true,
references: {
model: "stars",
key: "id",
},
onDelete: "CASCADE",
});
// Add isFolder column to distinguish folders from regular stars
await queryInterface.addColumn("stars", "isFolder", {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false,
});
// Add index for efficient parent-child queries
await queryInterface.addIndex("stars", ["parentId"], {
name: "stars_parent_id",
});
// Add composite index for user-specific folder queries
await queryInterface.addIndex("stars", ["userId", "parentId"], {
name: "stars_user_id_parent_id",
});
// Add composite index for filtering folders vs regular stars
await queryInterface.addIndex("stars", ["userId", "isFolder"], {
name: "stars_user_id_is_folder",
});
// Add check constraint to ensure folders don't have documentId or collectionId
await queryInterface.sequelize.query(`
ALTER TABLE stars ADD CONSTRAINT stars_folder_content_check
CHECK (
(isFolder = true AND documentId IS NULL AND collectionId IS NULL) OR
(isFolder = false)
)
`);
},
down: async (queryInterface, Sequelize) => {
// Remove check constraint
await queryInterface.sequelize.query(`
ALTER TABLE stars DROP CONSTRAINT IF EXISTS stars_folder_content_check
`);
// Remove indexes
await queryInterface.removeIndex("stars", "stars_user_id_is_folder");
await queryInterface.removeIndex("stars", "stars_user_id_parent_id");
await queryInterface.removeIndex("stars", "stars_parent_id");
// Remove columns
await queryInterface.removeColumn("stars", "isFolder");
await queryInterface.removeColumn("stars", "parentId");
},
};
+14
View File
@@ -6,6 +6,7 @@ import {
ForeignKey,
Table,
Length,
HasMany,
} from "sequelize-typescript";
import Collection from "./Collection";
import Document from "./Document";
@@ -26,6 +27,9 @@ class Star extends IdModel<
@Column
index: string | null;
@Column(DataType.BOOLEAN)
isFolder: boolean;
// associations
@BelongsTo(() => User, "userId")
@@ -48,6 +52,16 @@ class Star extends IdModel<
@ForeignKey(() => Collection)
@Column(DataType.UUID)
collectionId: string | null;
@BelongsTo(() => Star, "parentId")
parent: Star | null;
@ForeignKey(() => Star)
@Column(DataType.UUID)
parentId: string | null;
@HasMany(() => Star, "parentId")
children: Star[];
}
export default Star;
+2
View File
@@ -5,6 +5,8 @@ export default function presentStar(star: Star) {
id: star.id,
documentId: star.documentId,
collectionId: star.collectionId,
parentId: star.parentId,
isFolder: star.isFolder,
index: star.index,
createdAt: star.createdAt,
updatedAt: star.updatedAt,