diff --git a/backend/src/CollabApp.Application/Services/Room/RoomService.cs b/backend/src/CollabApp.Application/Services/Room/RoomService.cs
index 92794d8453d903bfd330deb8c2c3f037d803bb74..446d42a220b9dcf506f1c617b9c140b138b80f51 100644
--- a/backend/src/CollabApp.Application/Services/Room/RoomService.cs
+++ b/backend/src/CollabApp.Application/Services/Room/RoomService.cs
@@ -239,7 +239,7 @@ public class RoomService : IRoomService
};
}
- // 检查用户是否已在房间中
+ // 检查用户是否已在房间中(包括软删除的记录)
var existingPlayer = await _roomPlayerRepository.GetSingleAsync(rp => rp.RoomId == roomId && rp.UserId == userId);
if (existingPlayer != null)
{
@@ -251,6 +251,42 @@ public class RoomService : IRoomService
};
}
+ // 检查是否存在已软删除的记录,如果有,重新激活而不是创建新记录
+ // 使用原生查询来检查软删除的记录
+ var softDeletedPlayerQuery = await _roomPlayerRepository.GetManyAsync(rp => true); // 获取所有记录
+ var allPlayers = softDeletedPlayerQuery.ToList();
+ var softDeletedPlayer = allPlayers.FirstOrDefault(p => p.RoomId == roomId && p.UserId == userId && p.IsDeleted);
+
+ if (softDeletedPlayer != null)
+ {
+ // 重新激活已软删除的记录
+ softDeletedPlayer.IsDeleted = false;
+ softDeletedPlayer.SetReady(false); // 重置准备状态
+ softDeletedPlayer.UpdatedAt = DateTime.UtcNow;
+
+ // 重新分配加入顺序和颜色
+ var currentPlayersInRoom = await _roomPlayerRepository.GetManyAsync(rp => rp.RoomId == roomId);
+ var playersList = currentPlayersInRoom.ToList();
+ softDeletedPlayer.SetJoinOrder(playersList.Count + 1);
+ softDeletedPlayer.SetPlayerColor(GetNextAvailableColor(playersList));
+
+ await _roomPlayerRepository.UpdateAsync(softDeletedPlayer);
+ await _roomPlayerRepository.SaveChangesAsync();
+
+ // 更新房间的玩家数量
+ room.IncrementPlayerCount();
+ await _roomRepository.UpdateAsync(room);
+ await _roomRepository.SaveChangesAsync();
+
+ var reactivatedRoomDetail = await GetRoomDetailAsync(roomId, userId);
+ return new JoinRoomResult
+ {
+ Success = true,
+ Message = "成功重新加入房间",
+ RoomDetail = reactivatedRoomDetail
+ };
+ }
+
// 验证用户是否存在
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
@@ -264,15 +300,15 @@ public class RoomService : IRoomService
}
// 获取当前房间玩家数量
- var currentPlayersInRoom = await _roomPlayerRepository.GetManyAsync(rp => rp.RoomId == roomId);
- var playersList = currentPlayersInRoom.ToList();
+ var currentActivePlayersInRoom = await _roomPlayerRepository.GetManyAsync(rp => rp.RoomId == roomId);
+ var activePlayersList = currentActivePlayersInRoom.ToList();
- // 创建RoomPlayer记录
+ // 创建新的RoomPlayer记录
var roomPlayer = RoomPlayer.CreateRoomPlayer(
roomId,
userId,
- playersList.Count + 1, // 设置加入顺序
- GetNextAvailableColor(playersList)
+ activePlayersList.Count + 1, // 设置加入顺序
+ GetNextAvailableColor(activePlayersList)
);
await _roomPlayerRepository.AddAsync(roomPlayer);
diff --git a/backend/src/CollabApp.Infrastructure/Data/ApplicationDbContext.cs b/backend/src/CollabApp.Infrastructure/Data/ApplicationDbContext.cs
index 9615dd4b05af8b9ff99cebba7405d76adcdf67c4..19ec3029e46092782746235ddfa0f639f943bca2 100644
--- a/backend/src/CollabApp.Infrastructure/Data/ApplicationDbContext.cs
+++ b/backend/src/CollabApp.Infrastructure/Data/ApplicationDbContext.cs
@@ -159,8 +159,11 @@ public class ApplicationDbContext : DbContext
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
- // 复合唯一索引 - 确保同一用户在同一房间只能有一条记录
- entity.HasIndex(e => new { e.RoomId, e.UserId }).IsUnique().HasDatabaseName("IX_RoomPlayers_RoomId_UserId");
+ // 复合唯一索引 - 确保同一用户在同一房间只能有一条记录(只对未删除的记录生效)
+ entity.HasIndex(e => new { e.RoomId, e.UserId })
+ .IsUnique()
+ .HasDatabaseName("IX_RoomPlayers_RoomId_UserId")
+ .HasFilter("NOT is_deleted"); // 只对未删除的记录应用唯一约束
entity.HasIndex(e => e.JoinOrder).HasDatabaseName("IX_RoomPlayers_JoinOrder");
});
diff --git a/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.Designer.cs b/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ab3264b30b459615c41058e0b0d70393dffeba78
--- /dev/null
+++ b/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.Designer.cs
@@ -0,0 +1,1058 @@
+//
+using System;
+using CollabApp.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace CollabApp.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250819163316_FixRoomPlayerUniqueConstraint")]
+ partial class FixRoomPlayerUniqueConstraint
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Auth.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AccessToken")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("access_token");
+
+ b.Property("AccessTokenExpiresAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("access_token_expires_at");
+
+ b.Property("AvatarUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("avatar_url");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeviceInfo")
+ .HasColumnType("json")
+ .HasColumnName("device_info");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("LastActivityAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_activity_at");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_login_at");
+
+ b.Property("Nickname")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("nickname");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("password_hash");
+
+ b.Property("PasswordSalt")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("password_salt");
+
+ b.Property("PrivacySettings")
+ .HasColumnType("json")
+ .HasColumnName("privacy_settings");
+
+ b.Property("RefreshToken")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("refresh_token");
+
+ b.Property("RefreshTokenExpiresAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("refresh_token_expires_at");
+
+ b.Property("RememberMe")
+ .HasColumnType("boolean")
+ .HasColumnName("remember_me");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.Property("TokenRevokedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("token_revoked_at");
+
+ b.Property("TokenRevokedReason")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("token_revoked_reason");
+
+ b.Property("TokenStatus")
+ .HasColumnType("integer")
+ .HasColumnName("token_status");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("username");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccessToken")
+ .HasDatabaseName("IX_Users_AccessToken")
+ .HasFilter("[access_token] IS NOT NULL");
+
+ b.HasIndex("LastActivityAt")
+ .HasDatabaseName("IX_Users_LastActivity");
+
+ b.HasIndex("RefreshToken")
+ .HasDatabaseName("IX_Users_RefreshToken")
+ .HasFilter("[refresh_token] IS NOT NULL");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Users_Status");
+
+ b.HasIndex("Username")
+ .IsUnique()
+ .HasDatabaseName("IX_Users_Username");
+
+ b.HasIndex("TokenStatus", "AccessTokenExpiresAt")
+ .HasDatabaseName("IX_Users_TokenStatus_AccessTokenExpires");
+
+ b.HasIndex("TokenStatus", "RefreshTokenExpiresAt")
+ .HasDatabaseName("IX_Users_TokenStatus_RefreshTokenExpires");
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Auth.UserStatistics", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CurrentRank")
+ .HasColumnType("integer")
+ .HasColumnName("current_rank");
+
+ b.Property("HighestRank")
+ .HasColumnType("integer")
+ .HasColumnName("highest_rank");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("Losses")
+ .HasColumnType("integer")
+ .HasColumnName("losses");
+
+ b.Property("MaxArea")
+ .HasPrecision(10, 2)
+ .HasColumnType("numeric(10,2)")
+ .HasColumnName("max_area");
+
+ b.Property("TotalGames")
+ .HasColumnType("integer")
+ .HasColumnName("total_games");
+
+ b.Property("TotalPlayTime")
+ .HasColumnType("integer")
+ .HasColumnName("total_play_time");
+
+ b.Property("TotalScore")
+ .HasColumnType("integer")
+ .HasColumnName("total_score");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("WinRate")
+ .HasPrecision(5, 2)
+ .HasColumnType("numeric(5,2)")
+ .HasColumnName("win_rate");
+
+ b.Property("Wins")
+ .HasColumnType("integer")
+ .HasColumnName("wins");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CurrentRank")
+ .HasDatabaseName("IX_UserStatistics_CurrentRank");
+
+ b.HasIndex("TotalScore")
+ .HasDatabaseName("IX_UserStatistics_TotalScore");
+
+ b.HasIndex("UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_UserStatistics_UserId");
+
+ b.HasIndex("WinRate")
+ .HasDatabaseName("IX_UserStatistics_WinRate");
+
+ b.ToTable("user_statistics");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.Game", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("Duration")
+ .HasColumnType("integer")
+ .HasColumnName("duration");
+
+ b.Property("EnableDynamicBalance")
+ .HasColumnType("boolean")
+ .HasColumnName("enable_dynamic_balance");
+
+ b.Property("FinishedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("finished_at");
+
+ b.Property("GameData")
+ .HasColumnType("json")
+ .HasColumnName("game_data");
+
+ b.Property("GameMode")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("game_mode");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("MapHeight")
+ .HasColumnType("integer")
+ .HasColumnName("map_height");
+
+ b.Property("MapShape")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("map_shape");
+
+ b.Property("MapWidth")
+ .HasColumnType("integer")
+ .HasColumnName("map_width");
+
+ b.Property("MaxPowerUps")
+ .HasColumnType("integer")
+ .HasColumnName("max_powerups");
+
+ b.Property("PowerUpSpawnInterval")
+ .HasColumnType("integer")
+ .HasColumnName("powerup_spawn_interval");
+
+ b.Property("RoomId")
+ .HasColumnType("uuid")
+ .HasColumnName("room_id");
+
+ b.Property("SpecialEventChance")
+ .HasColumnType("integer")
+ .HasColumnName("special_event_chance");
+
+ b.Property("StartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("started_at");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("WinnerId")
+ .HasColumnType("uuid")
+ .HasColumnName("winner_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FinishedAt")
+ .HasDatabaseName("IX_Games_FinishedAt");
+
+ b.HasIndex("RoomId")
+ .HasDatabaseName("IX_Games_RoomId");
+
+ b.HasIndex("StartedAt")
+ .HasDatabaseName("IX_Games_StartedAt");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Games_Status");
+
+ b.HasIndex("WinnerId")
+ .HasDatabaseName("IX_Games_WinnerId");
+
+ b.ToTable("games");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.GameAction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActionData")
+ .IsRequired()
+ .HasColumnType("json")
+ .HasColumnName("action_data");
+
+ b.Property("ActionType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("action_type");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("GameId")
+ .HasColumnType("uuid")
+ .HasColumnName("game_id");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("Timestamp")
+ .HasColumnType("bigint")
+ .HasColumnName("timestamp");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ActionType")
+ .HasDatabaseName("IX_GameActions_ActionType");
+
+ b.HasIndex("GameId")
+ .HasDatabaseName("IX_GameActions_GameId");
+
+ b.HasIndex("Timestamp")
+ .HasDatabaseName("IX_GameActions_Timestamp");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("IX_GameActions_UserId");
+
+ b.HasIndex("GameId", "Timestamp")
+ .HasDatabaseName("IX_GameActions_GameId_Timestamp");
+
+ b.ToTable("game_actions");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.GamePlayer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActionsCount")
+ .HasColumnType("integer")
+ .HasColumnName("actions_count");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CurrentPowerUp")
+ .HasColumnType("text")
+ .HasColumnName("current_powerup");
+
+ b.Property("DeathCount")
+ .HasColumnType("integer")
+ .HasColumnName("death_count");
+
+ b.Property("FinalArea")
+ .HasPrecision(10, 2)
+ .HasColumnType("numeric(10,2)")
+ .HasColumnName("final_area");
+
+ b.Property("FinalRank")
+ .HasColumnType("integer")
+ .HasColumnName("final_rank");
+
+ b.Property("GameId")
+ .HasColumnType("uuid")
+ .HasColumnName("game_id");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("KillCount")
+ .HasColumnType("integer")
+ .HasColumnName("kill_count");
+
+ b.Property("MaxTerritoryArea")
+ .HasPrecision(10, 2)
+ .HasColumnType("numeric(10,2)")
+ .HasColumnName("max_territory_area");
+
+ b.Property("PlayTime")
+ .HasColumnType("integer")
+ .HasColumnName("play_time");
+
+ b.Property("PlayerColor")
+ .IsRequired()
+ .HasMaxLength(7)
+ .HasColumnType("character varying(7)")
+ .HasColumnName("player_color");
+
+ b.Property("PositionX")
+ .HasColumnType("real")
+ .HasColumnName("position_x");
+
+ b.Property("PositionY")
+ .HasColumnType("real")
+ .HasColumnName("position_y");
+
+ b.Property("PowerUpUsageCount")
+ .HasColumnType("integer")
+ .HasColumnName("powerup_usage_count");
+
+ b.Property("RespawnTimestamp")
+ .HasColumnType("bigint")
+ .HasColumnName("respawn_timestamp");
+
+ b.Property("ScoreChange")
+ .HasColumnType("integer")
+ .HasColumnName("score_change");
+
+ b.Property("SpawnX")
+ .HasColumnType("real")
+ .HasColumnName("spawn_x");
+
+ b.Property("SpawnY")
+ .HasColumnType("real")
+ .HasColumnName("spawn_y");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.Property("TeamId")
+ .HasColumnType("integer")
+ .HasColumnName("team_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FinalArea")
+ .HasDatabaseName("IX_GamePlayers_FinalArea");
+
+ b.HasIndex("FinalRank")
+ .HasDatabaseName("IX_GamePlayers_FinalRank");
+
+ b.HasIndex("ScoreChange")
+ .HasDatabaseName("IX_GamePlayers_ScoreChange");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("GameId", "UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_GamePlayers_GameId_UserId");
+
+ b.ToTable("game_players");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Notification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)")
+ .HasColumnName("content");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("Data")
+ .HasColumnType("json")
+ .HasColumnName("data");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("IsRead")
+ .HasColumnType("boolean")
+ .HasColumnName("is_read");
+
+ b.Property("NotificationType")
+ .HasColumnType("integer")
+ .HasColumnName("notification_type");
+
+ b.Property("ReadAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("read_at");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("title");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("IX_Notifications_CreatedAt");
+
+ b.HasIndex("NotificationType")
+ .HasDatabaseName("IX_Notifications_Type");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("IX_Notifications_UserId");
+
+ b.HasIndex("UserId", "CreatedAt")
+ .HasDatabaseName("IX_Notifications_UserId_CreatedAt");
+
+ b.HasIndex("UserId", "IsRead")
+ .HasDatabaseName("IX_Notifications_UserId_IsRead");
+
+ b.ToTable("notifications");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Ranking", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CurrentRank")
+ .HasColumnType("integer")
+ .HasColumnName("current_rank");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("PeriodEnd")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("period_end");
+
+ b.Property("PeriodStart")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("period_start");
+
+ b.Property("RankingType")
+ .HasColumnType("integer")
+ .HasColumnName("ranking_type");
+
+ b.Property("Score")
+ .HasColumnType("integer")
+ .HasColumnName("score");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UpdatedAt")
+ .HasDatabaseName("IX_Rankings_UpdatedAt");
+
+ b.HasIndex("RankingType", "CurrentRank")
+ .HasDatabaseName("IX_Rankings_Type_Rank");
+
+ b.HasIndex("RankingType", "Score")
+ .HasDatabaseName("IX_Rankings_Type_Score");
+
+ b.HasIndex("UserId", "RankingType", "PeriodStart", "PeriodEnd")
+ .IsUnique()
+ .HasDatabaseName("IX_Rankings_UserId_Type_Period");
+
+ b.ToTable("rankings");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.RankingHistory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("Rank")
+ .HasColumnType("integer")
+ .HasColumnName("rank");
+
+ b.Property("RankingType")
+ .HasColumnType("integer")
+ .HasColumnName("ranking_type");
+
+ b.Property("RecordedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("recorded_at");
+
+ b.Property("Score")
+ .HasColumnType("integer")
+ .HasColumnName("score");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("IX_RankingHistories_UserId");
+
+ b.HasIndex("RankingType", "RecordedAt")
+ .HasDatabaseName("IX_RankingHistories_Type_RecordedAt");
+
+ b.HasIndex("UserId", "RankingType", "RecordedAt")
+ .HasDatabaseName("IX_RankingHistories_UserId_Type_RecordedAt");
+
+ b.ToTable("ranking_histories");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.Room", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CurrentPlayers")
+ .HasColumnType("integer")
+ .HasColumnName("current_players");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("IsPrivate")
+ .HasColumnType("boolean")
+ .HasColumnName("is_private");
+
+ b.Property("MaxPlayers")
+ .HasColumnType("integer")
+ .HasColumnName("max_players");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("name");
+
+ b.Property("OwnerId")
+ .HasColumnType("uuid")
+ .HasColumnName("owner_id");
+
+ b.Property("Password")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("password");
+
+ b.Property("Settings")
+ .HasColumnType("json")
+ .HasColumnName("settings");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("IX_Rooms_CreatedAt");
+
+ b.HasIndex("OwnerId")
+ .HasDatabaseName("IX_Rooms_OwnerId");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Rooms_Status");
+
+ b.HasIndex("Status", "IsPrivate")
+ .HasDatabaseName("IX_Rooms_Status_IsPrivate");
+
+ b.ToTable("rooms");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.RoomMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property("MessageType")
+ .HasColumnType("integer")
+ .HasColumnName("message_type");
+
+ b.Property("RoomId")
+ .HasColumnType("uuid")
+ .HasColumnName("room_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("IX_RoomMessages_CreatedAt");
+
+ b.HasIndex("RoomId")
+ .HasDatabaseName("IX_RoomMessages_RoomId");
+
+ b.HasIndex("UserId")
+ .HasDatabaseName("IX_RoomMessages_UserId");
+
+ b.HasIndex("RoomId", "CreatedAt")
+ .HasDatabaseName("IX_RoomMessages_RoomId_CreatedAt");
+
+ b.ToTable("room_messages");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.RoomPlayer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("IsReady")
+ .HasColumnType("boolean")
+ .HasColumnName("is_ready");
+
+ b.Property("JoinOrder")
+ .HasColumnType("integer")
+ .HasColumnName("join_order");
+
+ b.Property("PlayerColor")
+ .HasMaxLength(7)
+ .HasColumnType("character varying(7)")
+ .HasColumnName("player_color");
+
+ b.Property("RoomId")
+ .HasColumnType("uuid")
+ .HasColumnName("room_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("JoinOrder")
+ .HasDatabaseName("IX_RoomPlayers_JoinOrder");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("RoomId", "UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_RoomPlayers_RoomId_UserId")
+ .HasFilter("NOT is_deleted");
+
+ b.ToTable("room_players");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Auth.UserStatistics", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithOne("Statistics")
+ .HasForeignKey("CollabApp.Domain.Entities.Auth.UserStatistics", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.Game", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Room.Room", "Room")
+ .WithMany("Games")
+ .HasForeignKey("RoomId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "Winner")
+ .WithMany()
+ .HasForeignKey("WinnerId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.Navigation("Room");
+
+ b.Navigation("Winner");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.GameAction", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Game.Game", "Game")
+ .WithMany("Actions")
+ .HasForeignKey("GameId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Game");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.GamePlayer", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Game.Game", "Game")
+ .WithMany("Players")
+ .HasForeignKey("GameId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany("GamePlayers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Game");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Notification", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany("Notifications")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Ranking", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.RankingHistory", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.Room", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "Owner")
+ .WithMany("OwnedRooms")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.RoomMessage", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Room.Room", "Room")
+ .WithMany("Messages")
+ .HasForeignKey("RoomId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Room");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.RoomPlayer", b =>
+ {
+ b.HasOne("CollabApp.Domain.Entities.Room.Room", "Room")
+ .WithMany("Players")
+ .HasForeignKey("RoomId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("CollabApp.Domain.Entities.Auth.User", "User")
+ .WithMany("RoomPlayers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Room");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Auth.User", b =>
+ {
+ b.Navigation("GamePlayers");
+
+ b.Navigation("Notifications");
+
+ b.Navigation("OwnedRooms");
+
+ b.Navigation("RoomPlayers");
+
+ b.Navigation("Statistics");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Game.Game", b =>
+ {
+ b.Navigation("Actions");
+
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("CollabApp.Domain.Entities.Room.Room", b =>
+ {
+ b.Navigation("Games");
+
+ b.Navigation("Messages");
+
+ b.Navigation("Players");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.cs b/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.cs
new file mode 100644
index 0000000000000000000000000000000000000000..878007e586c3eea6cbaecc8888acab7af02da683
--- /dev/null
+++ b/backend/src/CollabApp.Infrastructure/Migrations/20250819163316_FixRoomPlayerUniqueConstraint.cs
@@ -0,0 +1,652 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace CollabApp.Infrastructure.Migrations
+{
+ ///
+ public partial class FixRoomPlayerUniqueConstraint : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "users",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ username = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
+ password_hash = table.Column(type: "character varying(255)", maxLength: 255, nullable: false),
+ password_salt = table.Column(type: "character varying(255)", maxLength: 255, nullable: false),
+ nickname = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
+ avatar_url = table.Column(type: "character varying(255)", maxLength: 255, nullable: true),
+ privacy_settings = table.Column(type: "json", nullable: true),
+ last_login_at = table.Column(type: "timestamp with time zone", nullable: true),
+ status = table.Column(type: "integer", nullable: false),
+ access_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: true),
+ refresh_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: true),
+ access_token_expires_at = table.Column(type: "timestamp with time zone", nullable: true),
+ refresh_token_expires_at = table.Column(type: "timestamp with time zone", nullable: true),
+ remember_me = table.Column(type: "boolean", nullable: false),
+ token_status = table.Column(type: "integer", nullable: false),
+ last_activity_at = table.Column(type: "timestamp with time zone", nullable: true),
+ device_info = table.Column(type: "json", nullable: true),
+ token_revoked_reason = table.Column(type: "character varying(200)", maxLength: 200, nullable: true),
+ token_revoked_at = table.Column(type: "timestamp with time zone", nullable: true),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "notifications",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column(type: "uuid", nullable: false),
+ notification_type = table.Column(type: "integer", nullable: false),
+ title = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ content = table.Column(type: "character varying(500)", maxLength: 500, nullable: false),
+ is_read = table.Column(type: "boolean", nullable: false),
+ data = table.Column(type: "json", nullable: true),
+ read_at = table.Column(type: "timestamp with time zone", nullable: true),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_notifications", x => x.Id);
+ table.ForeignKey(
+ name: "FK_notifications_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ranking_histories",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column(type: "uuid", nullable: false),
+ ranking_type = table.Column(type: "integer", nullable: false),
+ rank = table.Column(type: "integer", nullable: false),
+ score = table.Column(type: "integer", nullable: false),
+ recorded_at = table.Column(type: "timestamp with time zone", nullable: false),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ranking_histories", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ranking_histories_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "rankings",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column(type: "uuid", nullable: false),
+ ranking_type = table.Column(type: "integer", nullable: false),
+ current_rank = table.Column(type: "integer", nullable: false),
+ score = table.Column(type: "integer", nullable: false),
+ period_start = table.Column(type: "timestamp with time zone", nullable: false),
+ period_end = table.Column(type: "timestamp with time zone", nullable: false),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_rankings", x => x.Id);
+ table.ForeignKey(
+ name: "FK_rankings_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "rooms",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ owner_id = table.Column(type: "uuid", nullable: false),
+ max_players = table.Column(type: "integer", nullable: false),
+ current_players = table.Column(type: "integer", nullable: false),
+ password = table.Column(type: "character varying(255)", maxLength: 255, nullable: true),
+ is_private = table.Column(type: "boolean", nullable: false),
+ status = table.Column(type: "integer", nullable: false),
+ settings = table.Column(type: "json", nullable: true),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_rooms", x => x.Id);
+ table.ForeignKey(
+ name: "FK_rooms_users_owner_id",
+ column: x => x.owner_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "user_statistics",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column(type: "uuid", nullable: false),
+ total_games = table.Column(type: "integer", nullable: false),
+ wins = table.Column(type: "integer", nullable: false),
+ losses = table.Column(type: "integer", nullable: false),
+ win_rate = table.Column(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false),
+ total_score = table.Column(type: "integer", nullable: false),
+ max_area = table.Column(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false),
+ total_play_time = table.Column(type: "integer", nullable: false),
+ current_rank = table.Column(type: "integer", nullable: false),
+ highest_rank = table.Column(type: "integer", nullable: false),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_user_statistics", x => x.Id);
+ table.ForeignKey(
+ name: "FK_user_statistics_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "games",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ room_id = table.Column(type: "uuid", nullable: false),
+ game_mode = table.Column(type: "text", nullable: false),
+ map_width = table.Column(type: "integer", nullable: false),
+ map_height = table.Column(type: "integer", nullable: false),
+ duration = table.Column(type: "integer", nullable: false),
+ map_shape = table.Column(type: "text", nullable: false),
+ powerup_spawn_interval = table.Column(type: "integer", nullable: false),
+ max_powerups = table.Column(type: "integer", nullable: false),
+ special_event_chance = table.Column(type: "integer", nullable: false),
+ enable_dynamic_balance = table.Column(type: "boolean", nullable: false),
+ status = table.Column(type: "integer", nullable: false),
+ winner_id = table.Column(type: "uuid", nullable: true),
+ started_at = table.Column(type: "timestamp with time zone", nullable: true),
+ finished_at = table.Column(type: "timestamp with time zone", nullable: true),
+ game_data = table.Column(type: "json", nullable: true),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_games", x => x.Id);
+ table.ForeignKey(
+ name: "FK_games_rooms_room_id",
+ column: x => x.room_id,
+ principalTable: "rooms",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_games_users_winner_id",
+ column: x => x.winner_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.SetNull);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "room_messages",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ room_id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column(type: "uuid", nullable: false),
+ message = table.Column(type: "text", nullable: false),
+ message_type = table.Column(type: "integer", nullable: false),
+ created_at = table.Column(type: "timestamp with time zone", nullable: false),
+ updated_at = table.Column(type: "timestamp with time zone", nullable: false),
+ is_deleted = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_room_messages", x => x.Id);
+ table.ForeignKey(
+ name: "FK_room_messages_rooms_room_id",
+ column: x => x.room_id,
+ principalTable: "rooms",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_room_messages_users_user_id",
+ column: x => x.user_id,
+ principalTable: "users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "room_players",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ room_id = table.Column(type: "uuid", nullable: false),
+ user_id = table.Column