Repository: IgnisDa/ryot (language: rust)

Issue:
Migrate `user_to_collection` table to `user_to_entity`



Discussion / hints:

Fix patch:
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index dbc2e666ef..8cf39d9ad5 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -29,6 +29,7 @@ mod m20240827_zz_changes_for_daily_user_activity;
 mod m20240828_add_last_login_on_column_to_user;
 mod m20240828_zz_add_columns_to_daily_user_activity;
 mod m20240829_change_structure_for_exercise_extra_information;
+mod m20240903_add_changes_for_user_to_collection_removal;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -43,7 +44,6 @@ pub use m20230505_create_review::Review as AliasedReview;
 pub use m20230822_create_exercise::Exercise as AliasedExercise;
 pub use m20231016_create_collection_to_entity::CollectionToEntity as AliasedCollectionToEntity;
 pub use m20231017_create_user_to_entity::UserToEntity as AliasedUserToEntity;
-pub use m20240509_create_user_to_collection::UserToCollection as AliasedUserToCollection;
 
 pub struct Migrator;
 
@@ -79,6 +79,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20240828_add_last_login_on_column_to_user::Migration),
             Box::new(m20240828_zz_add_columns_to_daily_user_activity::Migration),
             Box::new(m20240829_change_structure_for_exercise_extra_information::Migration),
+            Box::new(m20240903_add_changes_for_user_to_collection_removal::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20231017_create_user_to_entity.rs b/crates/migrations/src/m20231017_create_user_to_entity.rs
index 2fe1b1143c..c6fa8a4a02 100644
--- a/crates/migrations/src/m20231017_create_user_to_entity.rs
+++ b/crates/migrations/src/m20231017_create_user_to_entity.rs
@@ -4,7 +4,7 @@ use sea_orm_migration::prelude::*;
 use super::{
     m20230410_create_metadata::Metadata, m20230413_create_person::Person,
     m20230417_create_user::User, m20230501_create_metadata_group::MetadataGroup,
-    m20230822_create_exercise::Exercise,
+    m20230504_create_collection::Collection, m20230822_create_exercise::Exercise,
 };
 
 #[derive(DeriveMigrationName)]
@@ -13,7 +13,9 @@ pub struct Migration;
 pub static PERSON_FK_NAME: &str = "user_to_entity-fk4";
 pub static PERSON_INDEX_NAME: &str = "user_to_entity-uqi3";
 pub static METADATA_GROUP_FK_NAME: &str = "user_to_entity-fk5";
+pub static COLLECTION_FK_NAME: &str = "user_to_entity-fk6";
 pub static METADATA_GROUP_INDEX_NAME: &str = "user_to_entity-uqi4";
+pub static COLLECTION_INDEX_NAME: &str = "user_to_entity-uqi5";
 pub static CONSTRAINT_SQL: &str = indoc! { r#"
     ALTER TABLE "user_to_entity" DROP CONSTRAINT IF EXISTS "user_to_entity__ensure_one_entity";
     ALTER TABLE "user_to_entity"
@@ -22,7 +24,8 @@ pub static CONSTRAINT_SQL: &str = indoc! { r#"
         (CASE WHEN "metadata_id" IS NOT NULL THEN 1 ELSE 0 END) +
         (CASE WHEN "person_id" IS NOT NULL THEN 1 ELSE 0 END) +
         (CASE WHEN "exercise_id" IS NOT NULL THEN 1 ELSE 0 END) +
-        (CASE WHEN "metadata_group_id" IS NOT NULL THEN 1 ELSE 0 END)
+        (CASE WHEN "metadata_group_id" IS NOT NULL THEN 1 ELSE 0 END) +
+        (CASE WHEN "collection_id" IS NOT NULL THEN 1 ELSE 0 END)
         = 1
     );
 "# };
@@ -31,8 +34,6 @@ pub static CONSTRAINT_SQL: &str = indoc! { r#"
 /// - the user has it in their seen history
 /// - added it to a collection
 /// - has reviewed it
-/// - owns it
-/// - added a reminder
 #[derive(Iden)]
 pub enum UserToEntity {
     Table,
@@ -46,6 +47,7 @@ pub enum UserToEntity {
     ExerciseId,
     PersonId,
     MetadataGroupId,
+    CollectionId,
     // specifics
     ExerciseExtraInformation,
     ExerciseNumTimesInteracted,
@@ -80,6 +82,7 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(UserToEntity::MetadataGroupId).text())
                     .col(ColumnDef::new(UserToEntity::PersonId).text())
                     .col(ColumnDef::new(UserToEntity::MetadataId).text())
+                    .col(ColumnDef::new(UserToEntity::CollectionId).text())
                     .col(ColumnDef::new(UserToEntity::UserId).text().not_null())
                     .col(
                         ColumnDef::new(UserToEntity::Id)
@@ -128,6 +131,14 @@ impl MigrationTrait for Migration {
                             .on_delete(ForeignKeyAction::Cascade)
                             .on_update(ForeignKeyAction::Cascade),
                     )
+                    .foreign_key(
+                        ForeignKey::create()
+                            .name(COLLECTION_FK_NAME)
+                            .from(UserToEntity::Table, UserToEntity::CollectionId)
+                            .to(Collection::Table, MetadataGroup::Id)
+                            .on_delete(ForeignKeyAction::Cascade)
+                            .on_update(ForeignKeyAction::Cascade),
+                    )
                     .to_owned(),
             )
             .await?;
@@ -175,6 +186,17 @@ impl MigrationTrait for Migration {
                     .to_owned(),
             )
             .await?;
+        manager
+            .create_index(
+                Index::create()
+                    .unique()
+                    .name(COLLECTION_INDEX_NAME)
+                    .table(UserToEntity::Table)
+                    .col(UserToEntity::UserId)
+                    .col(UserToEntity::CollectionId)
+                    .to_owned(),
+            )
+            .await?;
         db.execute_unprepared(CONSTRAINT_SQL).await?;
         Ok(())
     }
diff --git a/crates/migrations/src/m20240509_create_user_to_collection.rs b/crates/migrations/src/m20240509_create_user_to_collection.rs
index 584a6c4be4..e1acf2aba0 100644
--- a/crates/migrations/src/m20240509_create_user_to_collection.rs
+++ b/crates/migrations/src/m20240509_create_user_to_collection.rs
@@ -1,3 +1,4 @@
+// FIXME: Delete this migration
 use sea_orm_migration::prelude::*;
 
 use super::{m20230417_create_user::User, m20230504_create_collection::Collection};
diff --git a/crates/migrations/src/m20240903_add_changes_for_user_to_collection_removal.rs b/crates/migrations/src/m20240903_add_changes_for_user_to_collection_removal.rs
new file mode 100644
index 0000000000..da95d0ed8f
--- /dev/null
+++ b/crates/migrations/src/m20240903_add_changes_for_user_to_collection_removal.rs
@@ -0,0 +1,41 @@
+use sea_orm_migration::prelude::*;
+
+use crate::m20231017_create_user_to_entity::CONSTRAINT_SQL;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        let db = manager.get_connection();
+        if !manager
+            .has_column("user_to_entity", "collection_id")
+            .await?
+        {
+            db.execute_unprepared(
+                r#"
+ALTER TABLE "user_to_entity" ADD COLUMN "collection_id" TEXT;
+ALTER TABLE "user_to_entity" ADD CONSTRAINT "user_to_entity-fk6" FOREIGN KEY ("collection_id") REFERENCES "collection" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
+CREATE UNIQUE INDEX "user_to_entity-uqi5" ON "user_to_entity" ("user_id", "collection_id");
+"#,
+            )
+            .await?;
+            db.execute_unprepared(CONSTRAINT_SQL).await?;
+        }
+        if manager.has_table("user_to_collection").await? {
+            db.execute_unprepared(
+                r#"
+INSERT INTO "user_to_entity" ("user_id", "collection_id") SELECT "user_id", "collection_id" FROM "user_to_collection";
+DROP TABLE "user_to_collection";
+"#,
+            )
+            .await?;
+        }
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/crates/models/database/src/collection.rs b/crates/models/database/src/collection.rs
index 2b4f3aa07c..4c43ba0e3c 100644
--- a/crates/models/database/src/collection.rs
+++ b/crates/models/database/src/collection.rs
@@ -37,8 +37,8 @@ pub enum Relation {
         on_delete = "Cascade"
     )]
     User,
-    #[sea_orm(has_many = "super::user_to_collection::Entity")]
-    UserToCollection,
+    #[sea_orm(has_many = "super::user_to_entity::Entity")]
+    UserToEntity,
 }
 
 impl Related<super::collection_to_entity::Entity> for Entity {
@@ -59,9 +59,9 @@ impl Related<super::user::Entity> for Entity {
     }
 }
 
-impl Related<super::user_to_collection::Entity> for Entity {
+impl Related<super::user_to_entity::Entity> for Entity {
     fn to() -> RelationDef {
-        Relation::UserToCollection.def()
+        Relation::UserToEntity.def()
     }
 }
 
diff --git a/crates/models/database/src/lib.rs b/crates/models/database/src/lib.rs
index 1d61e66c79..e2eabc7d07 100644
--- a/crates/models/database/src/lib.rs
+++ b/crates/models/database/src/lib.rs
@@ -24,6 +24,5 @@ pub mod review;
 pub mod seen;
 pub mod user;
 pub mod user_measurement;
-pub mod user_to_collection;
 pub mod user_to_entity;
 pub mod workout;
diff --git a/crates/models/database/src/prelude.rs b/crates/models/database/src/prelude.rs
index 0128234ada..bd4c56078d 100644
--- a/crates/models/database/src/prelude.rs
+++ b/crates/models/database/src/prelude.rs
@@ -21,6 +21,5 @@ pub use super::review::Entity as Review;
 pub use super::seen::Entity as Seen;
 pub use super::user::Entity as User;
 pub use super::user_measurement::Entity as UserMeasurement;
-pub use super::user_to_collection::Entity as UserToCollection;
 pub use super::user_to_entity::Entity as UserToEntity;
 pub use super::workout::Entity as Workout;
diff --git a/crates/models/database/src/user.rs b/crates/models/database/src/user.rs
index b98fe6c32e..81f332f8d5 100644
--- a/crates/models/database/src/user.rs
+++ b/crates/models/database/src/user.rs
@@ -52,8 +52,6 @@ pub enum Relation {
     Seen,
     #[sea_orm(has_many = "super::user_measurement::Entity")]
     UserMeasurement,
-    #[sea_orm(has_many = "super::user_to_collection::Entity")]
-    UserToCollection,
     #[sea_orm(has_many = "super::user_to_entity::Entity")]
     UserToEntity,
     #[sea_orm(has_many = "super::workout::Entity")]
@@ -108,12 +106,6 @@ impl Related<super::user_measurement::Entity> for Entity {
     }
 }
 
-impl Related<super::user_to_collection::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::UserToCollection.def()
-    }
-}
-
 impl Related<super::user_to_entity::Entity> for Entity {
     fn to() -> RelationDef {
         Relation::UserToEntity.def()
@@ -126,15 +118,6 @@ impl Related<super::workout::Entity> for Entity {
     }
 }
 
-impl Related<super::collection::Entity> for Entity {
-    fn to() -> RelationDef {
-        super::user_to_collection::Relation::Collection.def()
-    }
-    fn via() -> Option<RelationDef> {
-        Some(super::user_to_collection::Relation::User.def().rev())
-    }
-}
-
 #[async_trait]
 impl ActiveModelBehavior for ActiveModel {
     async fn before_save<C>(mut self, _db: &C, _insert: bool) -> Result<Self, DbErr>
diff --git a/crates/models/database/src/user_to_collection.rs b/crates/models/database/src/user_to_collection.rs
deleted file mode 100644
index e5b16f4388..0000000000
--- a/crates/models/database/src/user_to_collection.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "user_to_collection")]
-pub struct Model {
-    #[sea_orm(primary_key, auto_increment = false)]
-    pub collection_id: String,
-    #[sea_orm(primary_key, auto_increment = false)]
-    pub user_id: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::collection::Entity",
-        from = "Column::CollectionId",
-        to = "super::collection::Column::Id",
-        on_update = "Cascade",
-        on_delete = "Cascade"
-    )]
-    Collection,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id",
-        on_update = "Cascade",
-        on_delete = "Cascade"
-    )]
-    User,
-}
-
-impl Related<super::collection::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Collection.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
diff --git a/crates/models/database/src/user_to_entity.rs b/crates/models/database/src/user_to_entity.rs
index 316ce812cb..7fa472d42a 100644
--- a/crates/models/database/src/user_to_entity.rs
+++ b/crates/models/database/src/user_to_entity.rs
@@ -20,6 +20,7 @@ pub struct Model {
     pub person_id: Option<String>,
     pub metadata_id: Option<String>,
     pub exercise_id: Option<String>,
+    pub collection_id: Option<String>,
     pub exercise_extra_information: Option<UserToExerciseExtraInformation>,
     pub exercise_num_times_interacted: Option<i32>,
     #[graphql(skip)]
@@ -31,6 +32,14 @@ pub struct Model {
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::collection::Entity",
+        from = "Column::CollectionId",
+        to = "super::collection::Column::Id",
+        on_update = "Cascade",
+        on_delete = "Cascade"
+    )]
+    Collection,
     #[sea_orm(
         belongs_to = "super::exercise::Entity",
         from = "Column::ExerciseId",
@@ -73,6 +82,12 @@ pub enum Relation {
     User,
 }
 
+impl Related<super::collection::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Collection.def()
+    }
+}
+
 impl Related<super::exercise::Entity> for Entity {
     fn to() -> RelationDef {
         Relation::Exercise.def()
diff --git a/crates/services/collection/src/lib.rs b/crates/services/collection/src/lib.rs
index d264a0f15b..2cba2e9a41 100644
--- a/crates/services/collection/src/lib.rs
+++ b/crates/services/collection/src/lib.rs
@@ -10,9 +10,9 @@ use database_models::{
     collection, collection_to_entity,
     prelude::{
         Collection, CollectionToEntity, Exercise, Metadata, MetadataGroup, Person, User,
-        UserToCollection, Workout,
+        UserToEntity, Workout,
     },
-    user_to_collection,
+    user_to_entity,
 };
 use database_utils::{
     add_entity_to_collection, create_or_update_collection, ilike_sql, item_reviews,
@@ -26,7 +26,7 @@ use media_models::{
 };
 use migrations::{
     AliasedCollection, AliasedCollectionToEntity, AliasedExercise, AliasedMetadata,
-    AliasedMetadataGroup, AliasedPerson, AliasedUser, AliasedUserToCollection,
+    AliasedMetadataGroup, AliasedPerson, AliasedUser, AliasedUserToEntity,
 };
 use sea_orm::{
     ColumnTrait, DatabaseConnection, EntityTrait, ItemsAndPagesNumber, Iterable, JoinType,
@@ -76,7 +76,7 @@ impl CollectionService {
             }
         }
         let collaborators_subquery = Query::select()
-            .from(UserToCollection)
+            .from(UserToEntity)
             .expr(
                 Func::cust(JsonAgg).arg(
                     Func::cust(JsonBuildObject)
@@ -89,16 +89,13 @@ impl CollectionService {
             .join(
                 JoinType::InnerJoin,
                 AliasedUser::Table,
-                Expr::col((
-                    AliasedUserToCollection::Table,
-                    AliasedUserToCollection::UserId,
-                ))
-                .equals((AliasedUser::Table, AliasedUser::Id)),
+                Expr::col((AliasedUserToEntity::Table, AliasedUserToEntity::UserId))
+                    .equals((AliasedUser::Table, AliasedUser::Id)),
             )
             .and_where(
                 Expr::col((
-                    AliasedUserToCollection::Table,
-                    AliasedUserToCollection::CollectionId,
+                    AliasedUserToEntity::Table,
+                    AliasedUserToEntity::CollectionId,
                 ))
                 .equals((AliasedCollection::Table, AliasedCollection::Id)),
             )
@@ -116,8 +113,8 @@ impl CollectionService {
                     AliasedCollectionToEntity::CollectionId,
                 ))
                 .equals((
-                    AliasedUserToCollection::Table,
-                    AliasedUserToCollection::CollectionId,
+                    AliasedUserToEntity::Table,
+                    AliasedUserToEntity::CollectionId,
                 )),
             )
             .to_owned();
@@ -162,8 +159,8 @@ impl CollectionService {
             )
             .order_by_desc(collection::Column::LastUpdatedOn)
             .left_join(User)
-            .left_join(UserToCollection)
-            .filter(user_to_collection::Column::UserId.eq(user_id))
+            .left_join(UserToEntity)
+            .filter(user_to_entity::Column::UserId.eq(user_id))
             .into_model::<CollectionItem>()
             .all(&self.db)
             .await
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index a6556c2f69..c76ec75f53 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -30,9 +30,9 @@ use database_models::{
         CalendarEvent, Collection, CollectionToEntity, Genre, ImportReport, Integration, Metadata,
         MetadataGroup, MetadataToGenre, MetadataToMetadata, MetadataToMetadataGroup,
         MetadataToPerson, NotificationPlatform, Person, QueuedNotification, Review, Seen, User,
-        UserToCollection, UserToEntity,
+        UserToEntity,
     },
-    queued_notification, review, seen, user, user_to_collection, user_to_entity,
+    queued_notification, review, seen, user, user_to_entity,
 };
 use database_utils::{
     add_entity_to_collection, admin_account_guard, apply_collection_filter,
@@ -1376,8 +1376,8 @@ impl MiscellaneousService {
             let collections = Collection::find()
                 .column(collection::Column::Id)
                 .column(collection::Column::UserId)
-                .left_join(UserToCollection)
-                .filter(user_to_collection::Column::UserId.eq(&user_id))
+                .left_join(UserToEntity)
+                .filter(user_to_entity::Column::UserId.eq(&user_id))
                 .all(&self.db)
                 .await
                 .unwrap();
@@ -2076,8 +2076,8 @@ impl MiscellaneousService {
         let collections = Collection::find()
             .select_only()
             .column(collection::Column::Id)
-            .left_join(UserToCollection)
-            .filter(user_to_collection::Column::UserId.eq(&user_id))
+            .left_join(UserToEntity)
+            .filter(user_to_entity::Column::UserId.eq(&user_id))
             .into_tuple::<String>()
             .all(&txn)
             .await
@@ -3881,7 +3881,7 @@ impl MiscellaneousService {
                 let reminder: UserMediaReminder =
                     serde_json::from_str(&serde_json::to_string(&reminder)?)?;
                 let col = col.unwrap();
-                let related_users = col.find_related(UserToCollection).all(&self.db).await?;
+                let related_users = col.find_related(UserToEntity).all(&self.db).await?;
                 if get_current_date(self.timezone.as_ref()) == reminder.reminder {
                     for user in related_users {
                         self.queue_notifications_to_user_platforms(&user.user_id, &reminder.text)
diff --git a/crates/utils/database/src/lib.rs b/crates/utils/database/src/lib.rs
index 4a8f20237b..ee6a9fa33b 100644
--- a/crates/utils/database/src/lib.rs
+++ b/crates/utils/database/src/lib.rs
@@ -19,9 +19,9 @@ use database_models::{
     metadata,
     prelude::{
         Collection, CollectionToEntity, DailyUserActivity, Metadata, Review, Seen, User,
-        UserMeasurement, UserToCollection, Workout,
+        UserMeasurement, UserToEntity, Workout,
     },
-    review, seen, user, user_measurement, user_to_collection, workout,
+    review, seen, user, user_measurement, user_to_entity, workout,
 };
 use dependent_models::UserWorkoutDetails;
 use enums::{EntityLot, MediaLot, SeenState, UserLot, Visibility};
@@ -134,8 +134,8 @@ pub async fn entity_in_collections_with_collection_to_entity_ids(
     entity_lot: EntityLot,
 ) -> Result<Vec<(collection::Model, Uuid)>> {
     let user_collections = Collection::find()
-        .left_join(UserToCollection)
-        .filter(user_to_collection::Column::UserId.eq(user_id))
+        .left_join(UserToEntity)
+        .filter(user_to_entity::Column::UserId.eq(user_id))
         .all(db)
         .await
         .unwrap();
@@ -243,8 +243,8 @@ pub async fn add_entity_to_collection(
     perform_core_application_job: &MemoryStorage<CoreApplicationJob>,
 ) -> Result<bool> {
     let collection = Collection::find()
-        .left_join(UserToCollection)
-        .filter(user_to_collection::Column::UserId.eq(user_id))
+        .left_join(UserToEntity)
+        .filter(user_to_entity::Column::UserId.eq(user_id))
         .filter(collection::Column::Name.eq(input.collection_name))
         .one(db)
         .await
@@ -305,9 +305,9 @@ pub async fn remove_entity_from_collection(
     input: ChangeCollectionToEntityInput,
 ) -> Result<StringIdObject> {
     let collect = Collection::find()
-        .left_join(UserToCollection)
+        .left_join(UserToEntity)
         .filter(collection::Column::Name.eq(input.collection_name))
-        .filter(user_to_collection::Column::UserId.eq(input.creator_user_id))
+        .filter(user_to_entity::Column::UserId.eq(input.creator_user_id))
         .one(db)
         .await
         .unwrap()
@@ -442,11 +442,12 @@ pub async fn create_or_update_collection(
             let collaborators = vec![user_id.to_owned()];
             let inserts = collaborators
                 .into_iter()
-                .map(|c| user_to_collection::ActiveModel {
+                .map(|c| user_to_entity::ActiveModel {
                     user_id: ActiveValue::Set(c),
-                    collection_id: ActiveValue::Set(id.clone()),
+                    collection_id: ActiveValue::Set(Some(id.clone())),
+                    ..Default::default()
                 });
-            UserToCollection::insert_many(inserts)
+            UserToEntity::insert_many(inserts)
                 .on_conflict(OnConflict::new().do_nothing().to_owned())
                 .exec_without_returning(&txn)
                 .await?;


Test patch: