diff --git a/ApplicationDbContext/ApplicationDbContext.cs b/ApplicationDbContext/ApplicationDbContext.cs new file mode 100644 index 0000000..acd319b --- /dev/null +++ b/ApplicationDbContext/ApplicationDbContext.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using WebAppServer1.Models; + +namespace WebAppServer1.ApplicationDbContext +{ + public class AppDbContext :DbContext + { + public AppDbContext(DbContextOptions options) : base(options) { } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // 好友关系双向约束 + modelBuilder.Entity() + .HasOne(f => f.User) + .WithMany(u => u.Friends) + .HasForeignKey(f => f.UserId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(f => f.FriendUser) + .WithMany() + .HasForeignKey(f => f.FriendId) + .OnDelete(DeleteBehavior.Restrict); + + // 群成员约束 + modelBuilder.Entity() + .HasOne(gm => gm.Group) + .WithMany(g => g.Members) + .HasForeignKey(gm => gm.GroupId); + + modelBuilder.Entity() + .HasOne(gm => gm.User) + .WithMany() + .HasForeignKey(gm => gm.UserId); + + // 消息约束 + modelBuilder.Entity() + .HasOne(m => m.Sender) + .WithMany(u => u.Messages) + .HasForeignKey(m => m.SenderId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(m => m.Receiver) + .WithMany() + .HasForeignKey(m => m.ReceiverId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(m => m.Group) + .WithMany() + .HasForeignKey(m => m.GroupId); + } + public DbSet Users { get; set; } + public DbSet Friends { get; set; } + public DbSet Groups { get; set; } + public DbSet GroupMembers { get; set; } + public DbSet Messages { get; set; } + public DbSet Notifications { get; set; } + public DbSet Files { get; set; } + public DbSet Tokens { get; set; } + public DbSet userProfileHistories { get; set; } + public DbSet LoginRecords { get; set; } + } +} diff --git a/Authentication/Authentication.cs b/Authentication/Authentication.cs new file mode 100644 index 0000000..d96e67a --- /dev/null +++ b/Authentication/Authentication.cs @@ -0,0 +1,6 @@ +namespace WebAppServer1.Authentication +{ + public class Authentication + { + } +} diff --git a/Authentication/JwtService.cs b/Authentication/JwtService.cs new file mode 100644 index 0000000..4675795 --- /dev/null +++ b/Authentication/JwtService.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using WebAppServer1.ApplicationDbContext; +using WebAppServer1.Models; + +namespace WebAppServer1.Authentication +{ + public class TokenService + { + private readonly IConfiguration _config; + private readonly AppDbContext pgSql; + + public TokenService(IConfiguration config, AppDbContext appDbContext) + { + _config = config; + pgSql = appDbContext; + } + + public string GenerateAccessToken(string username, int userid) + { + var claims = new List() + { + new Claim(ClaimTypes.Name, username), + //new Claim(ClaimTypes.Email, username), + new Claim(ClaimTypes.NameIdentifier, userid.ToString()), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: _config["Jwt:Issuer"], + audience: _config["Jwt:Audience"], + claims: claims, + expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(_config["Jwt:AccessTokenExpirationMinutes"])), + signingCredentials: creds); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public async Task GenerateRefreshToken(int userid, string username) + { + var refreshToken = Guid.NewGuid().ToString("N"); + var refreshTokenExpiry = DateTime.UtcNow.AddDays(Convert.ToDouble(_config["Jwt:RefreshTokenExpirationDays"])); + var tokens = new Tokens + { + UserId = userid, + UserName = username, + RefreshToken = refreshToken, + IsRevoked = false, + IssuedAt = DateTime.UtcNow, + ExpiresAt = refreshTokenExpiry, + }; + pgSql.Add(tokens); + await pgSql.SaveChangesAsync(); + return refreshToken; + } + + public async Task ValidateRefreshToken(string refreshToken) + { + var exists = await pgSql.Tokens.AnyAsync(t => t.RefreshToken == refreshToken); + if (!exists) { return false; } + var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken); + if (token == null || token.IsRevoked ) { return false; } + if (token.ExpiresAt < DateTime.UtcNow) + { + token.IsRevoked = true; + await pgSql.SaveChangesAsync(); + return false; + } + return true; + } + + public async Task RevokeRefreshToken(string refreshToken) + { + var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken); + pgSql.Tokens.Remove(token!); + await pgSql.SaveChangesAsync(); + } + } + + +} diff --git a/Chat/Chat.cs b/Chat/Chat.cs new file mode 100644 index 0000000..10551b8 --- /dev/null +++ b/Chat/Chat.cs @@ -0,0 +1,249 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity.Data; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using StackExchange.Redis; +using System.Diagnostics; +using WebAppServer1.ApplicationDbContext; +using WebAppServer1.Authentication; +using WebAppServer1.Models; +using WebAppServer1.Tool; + +namespace WebAppServer1.Chat +{ + public class Chat : Hub + { + + private readonly ILogger logger; + private readonly IConnectionMultiplexer _redis; + private readonly AppDbContext pgSql; + private readonly TokenService jwtService; + + + public Chat(ILogger _logger, IConnectionMultiplexer redis, AppDbContext _pgsql,TokenService _jwtService) + { + logger = _logger; + _redis = redis; + pgSql = _pgsql; + jwtService = _jwtService; + } + + //wss子端点API + + public override async Task OnConnectedAsync() + { + var db = _redis.GetDatabase(); + await db.SetAddAsync("AllConnections", Context.ConnectionId); + await BroadcastOnlineCount(); + await base.OnConnectedAsync(); + } + + public override async Task OnDisconnectedAsync(Exception? exception) + { + var db = _redis.GetDatabase(); + await db.SetRemoveAsync("AllConnections", Context.ConnectionId); + // 遍历所有群组,移除该连接 + var server = _redis.GetServer(_redis.GetEndPoints().First()); + foreach (var key in server.Keys(pattern: "Group:*")) + { + await db.SetRemoveAsync(key, Context.ConnectionId); + var groupName = key.ToString().Replace("Group:", ""); + await BroadcastGroupCount(groupName); + } + await BroadcastOnlineCount(); + + await base.OnDisconnectedAsync(exception); + } + + //简单广播消息 + [Authorize] + public async Task SendMessage(string user, string message) + { + logger.LogWarning("广播消息"); + //测试发送 + var exuser = Context.User?.Identity?.Name ?? "匿名"; + await Clients.All.SendAsync("ReceiveMessage", exuser, message); + } + + //群聊分组消息 + public async Task JoinGroup(string groupName) + { + var db = _redis.GetDatabase(); + await Groups.AddToGroupAsync(Context.ConnectionId, groupName); + await db.SetAddAsync($"Group:{groupName}", Context.ConnectionId); + await BroadcastGroupCount(groupName); + + logger.LogWarning($"用户: {Context.ConnectionId}加入了{groupName}群组!"); + await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, "系统", $"{Context.ConnectionId} 加入了群组"); + } + + public async Task LeaveGroup(string groupName) + { + var db = _redis.GetDatabase(); + await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); + await db.SetRemoveAsync($"Group:{groupName}", Context.ConnectionId); + await BroadcastGroupCount(groupName); + logger.LogInformation($"用户{Context.ConnectionId}---离开了---{groupName}群组"); + await Clients.Group(groupName).SendAsync("ReceiveMessage",groupName, "系统", $"{Context.ConnectionId} 离开了群聊 {groupName}"); + } + [Authorize] + public async Task SendMessageToGroup(string groupName, string user, string message) + { + logger.LogInformation($"用户{user}发来的的消息:{message}"); + await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, user, message); + } + + //发送已读回执 + public async Task SendReadReceipt(int messageId) + { + var update = await pgSql.Messages.FindAsync(messageId); + if (update == null) { return; }; + update.IsRead = true; + pgSql.Messages.Update(update); + await pgSql.SaveChangesAsync(); + await Clients.User(update.SenderId.ToString()).SendAsync("SendReadReceipt",messageId); + } + + //发送私聊消息 + [Authorize] + public async Task SendPrivateMessage(string userid, string message) + { + var receiver = await pgSql.Users.FindAsync(int.Parse(userid)); + if (receiver == null) + { + await Clients.Caller.SendAsync("SendPrivateMessage", false, "用户不存在", Context.UserIdentifier); + return; + } + var newMesssage = new Message + { + SenderId = int.Parse(Context.UserIdentifier!), + ReceiverId = receiver.Id, + Content = message, + CreatedAt = DateTime.UtcNow, + MessageType = MessageType.Text, + IsDeleted = false, + IsRead = false + }; + pgSql.Messages.Add(newMesssage); + await pgSql.SaveChangesAsync(); + await Clients.User(userid).SendAsync("SendPrivateMessage", true, message,Context.UserIdentifier,newMesssage.Id); + await Clients.Caller.SendAsync("SendPrivateMessage", false, message, Context.UserIdentifier,newMesssage.Id); + } + + //搜索好友 + public async Task SearchFriends(string username) + { + User? user = null; + + if (int.TryParse(username, out int userId)) + { + user = await pgSql.Users.FindAsync(userId); + Console.WriteLine("按 ID 查询: " + userId); + } + if (user == null) + { + user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username); + } + return user == null + ? new SearchFriendsResultResponse { success = false } + : new SearchFriendsResultResponse + { + success = true, + userid = user.Id, + username = user.Nickname + }; + } + + //获取用户表单测试 + public async Task> GetFormData() + { + var users = await pgSql.Users + .Select(u => new { + Id = u.Id, + Name = u.Username, + Password = u.PasswordHash, + CreatedDate = u.CreatedAt, + LastModifiedDate = u.LastActive + }) + .ToListAsync(); + + return users.Cast().ToList(); + } + + + //刷新Token + // 刷新 Token + public async Task RefreshAccessToken(string refreshToken) + { + var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken); + + if (token == null || token.IsRevoked || token.ExpiresAt < DateTime.UtcNow) + { + await Clients.Caller.SendAsync("RefreshFailed", "Refresh token invalid or expired"); + return; + } + + var newAccessToken = jwtService.GenerateAccessToken(token.UserName, token.UserId); + var newRefreshToken = await jwtService.GenerateRefreshToken(token.UserId, token.UserName); + + token.IsRevoked = true; + await pgSql.SaveChangesAsync(); + + await Clients.Caller.SendAsync("ReceiveNewAccessToken", newAccessToken, newRefreshToken); + } + + //登录认证 + public async Task LoginAuthentication(string username, string password) + { + var exists = await pgSql.Users.AnyAsync(a => a.Username == username && a.PasswordHash == password); + if (!exists) + { + await Clients.Client(Context.ConnectionId).SendAsync("LoginResult",false,"用户名或密码错误!"); + return; + } + var user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username); + if (user == null) { return; } + var accessToken = jwtService.GenerateAccessToken(user.Username,user.Id); + var refreshToken = await jwtService.GenerateRefreshToken(user.Id, username); + await Clients.Client(Context.ConnectionId).SendAsync("LoginResult", true, accessToken, refreshToken); + logger.LogInformation($"用户:{username}正在进行登录认证!密码:{password}===accessToken:{accessToken}"); + } + //新用户注册 + public async Task Register() + { + + var newUser = new User + { + Username = "user_" + Guid.NewGuid().ToString("N").Substring(0, 8), + Nickname = GenerateTool.GenerateRandomNickname(), + PasswordHash = GenerateTool.GeneratePassword(), + CreatedAt = DateTime.UtcNow, + LastActive = DateTime.UtcNow + }; + pgSql.Add(newUser); + await pgSql.SaveChangesAsync(); + logger.LogInformation($"新用户:{newUser.Username}正在进行登录注册操作!密码:{newUser.Nickname}"); + await Clients.Client(Context.ConnectionId).SendAsync("RegisterResult", true,"恭喜注册成功!"); + await LoginAuthentication(newUser.Username,newUser.PasswordHash); + + } + + //统计在线人数 + private async Task BroadcastOnlineCount() + { + var db = _redis.GetDatabase(); + var count = await db.SetLengthAsync("AllConnections"); + await Clients.All.SendAsync("OnlineCount", count); + } + + //统计群组在线人数 + private async Task BroadcastGroupCount(string groupName) + { + var db = _redis.GetDatabase(); + var count = await db.SetLengthAsync($"Group:{groupName}"); + await Clients.Group(groupName).SendAsync("GroupCount", groupName, count); + } + + + } +} diff --git a/Migrations/20260516024729_InitialCreate.Designer.cs b/Migrations/20260516024729_InitialCreate.Designer.cs new file mode 100644 index 0000000..e51b44b --- /dev/null +++ b/Migrations/20260516024729_InitialCreate.Designer.cs @@ -0,0 +1,55 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WebAppServer1.ApplicationDbContext; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260516024729_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Password") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260516024729_InitialCreate.cs b/Migrations/20260516024729_InitialCreate.cs new file mode 100644 index 0000000..eaa9827 --- /dev/null +++ b/Migrations/20260516024729_InitialCreate.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: true), + CreatedDate = table.Column(type: "timestamp with time zone", nullable: false), + LastModifiedDate = table.Column(type: "timestamp with time zone", nullable: false), + Password = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Migrations/20260517061910_tokens.Designer.cs b/Migrations/20260517061910_tokens.Designer.cs new file mode 100644 index 0000000..9403bfc --- /dev/null +++ b/Migrations/20260517061910_tokens.Designer.cs @@ -0,0 +1,90 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WebAppServer1.ApplicationDbContext; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260517061910_tokens")] + partial class tokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WebAppServer1.Models.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260517061910_tokens.cs b/Migrations/20260517061910_tokens.cs new file mode 100644 index 0000000..828cd85 --- /dev/null +++ b/Migrations/20260517061910_tokens.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + /// + public partial class tokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Tokens", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RefreshToken = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "integer", nullable: false), + UserName = table.Column(type: "text", nullable: false), + IssuedAt = table.Column(type: "timestamp with time zone", nullable: false), + ExpiresAt = table.Column(type: "timestamp with time zone", nullable: false), + IsRevoked = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tokens"); + } + } +} diff --git a/Migrations/20260519032425_InitialFriends.Designer.cs b/Migrations/20260519032425_InitialFriends.Designer.cs new file mode 100644 index 0000000..23c3237 --- /dev/null +++ b/Migrations/20260519032425_InitialFriends.Designer.cs @@ -0,0 +1,509 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WebAppServer1.ApplicationDbContext; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260519032425_InitialFriends")] + partial class InitialFriends + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FileType") + .HasColumnType("integer"); + + b.Property("FileUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploaderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("UploaderId"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FriendId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("UserId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupMembers"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DeviceInfo") + .IsRequired() + .HasColumnType("text"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("LoginTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("LoginRecords"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("MessageType") + .HasColumnType("integer"); + + b.Property("ReceiverId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("ReceiverId"); + + b.HasIndex("SenderId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsOnline") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Signature") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ChangedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("text"); + + b.Property("NewValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("OldValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userProfileHistories"); + }); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.HasOne("WebAppServer1.Models.Message", "Message") + .WithMany() + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.HasOne("WebAppServer1.Models.User", "FriendUser") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany("Friends") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FriendUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.HasOne("WebAppServer1.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("WebAppServer1.Models.User", "Receiver") + .WithMany() + .HasForeignKey("ReceiverId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("WebAppServer1.Models.User", "Sender") + .WithMany("Messages") + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("Receiver"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Navigation("Friends"); + + b.Navigation("Messages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260519032425_InitialFriends.cs b/Migrations/20260519032425_InitialFriends.cs new file mode 100644 index 0000000..354e27a --- /dev/null +++ b/Migrations/20260519032425_InitialFriends.cs @@ -0,0 +1,408 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + /// + public partial class InitialFriends : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Password", + table: "Users", + newName: "Username"); + + migrationBuilder.RenameColumn( + name: "Name", + table: "Users", + newName: "Signature"); + + migrationBuilder.RenameColumn( + name: "LastModifiedDate", + table: "Users", + newName: "LastActive"); + + migrationBuilder.RenameColumn( + name: "CreatedDate", + table: "Users", + newName: "CreatedAt"); + + migrationBuilder.AddColumn( + name: "AvatarUrl", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Email", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "IsOnline", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "PasswordHash", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateTable( + name: "Friends", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + FriendId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Friends", x => x.Id); + table.ForeignKey( + name: "FK_Friends_Users_FriendId", + column: x => x.FriendId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Friends_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Groups", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + OwnerId = table.Column(type: "integer", nullable: false), + AvatarUrl = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Groups", x => x.Id); + table.ForeignKey( + name: "FK_Groups_Users_OwnerId", + column: x => x.OwnerId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LoginRecords", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + LoginTime = table.Column(type: "timestamp with time zone", nullable: false), + IpAddress = table.Column(type: "text", nullable: false), + DeviceInfo = table.Column(type: "text", nullable: false), + IsSuccess = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LoginRecords", x => x.Id); + table.ForeignKey( + name: "FK_LoginRecords_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Notifications", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "integer", nullable: false), + Content = table.Column(type: "text", nullable: false), + IsRead = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Notifications", x => x.Id); + table.ForeignKey( + name: "FK_Notifications_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "userProfileHistories", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + FieldName = table.Column(type: "text", nullable: false), + OldValue = table.Column(type: "text", nullable: false), + NewValue = table.Column(type: "text", nullable: false), + ChangedAt = table.Column(type: "timestamp with time zone", nullable: false), + ChangedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_userProfileHistories", x => x.Id); + table.ForeignKey( + name: "FK_userProfileHistories_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GroupMembers", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + GroupId = table.Column(type: "integer", nullable: false), + UserId = table.Column(type: "integer", nullable: false), + Role = table.Column(type: "integer", nullable: false), + JoinedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GroupMembers", x => x.Id); + table.ForeignKey( + name: "FK_GroupMembers_Groups_GroupId", + column: x => x.GroupId, + principalTable: "Groups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GroupMembers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Messages", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SenderId = table.Column(type: "integer", nullable: false), + ReceiverId = table.Column(type: "integer", nullable: true), + GroupId = table.Column(type: "integer", nullable: true), + Content = table.Column(type: "text", nullable: false), + MessageType = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + IsRead = table.Column(type: "boolean", nullable: false), + IsDeleted = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Messages", x => x.Id); + table.ForeignKey( + name: "FK_Messages_Groups_GroupId", + column: x => x.GroupId, + principalTable: "Groups", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Messages_Users_ReceiverId", + column: x => x.ReceiverId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Messages_Users_SenderId", + column: x => x.SenderId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Files", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UploaderId = table.Column(type: "integer", nullable: false), + MessageId = table.Column(type: "integer", nullable: false), + FileUrl = table.Column(type: "text", nullable: false), + FileType = table.Column(type: "integer", nullable: false), + Size = table.Column(type: "integer", nullable: false), + UploadedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Files", x => x.Id); + table.ForeignKey( + name: "FK_Files_Messages_MessageId", + column: x => x.MessageId, + principalTable: "Messages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Files_Users_UploaderId", + column: x => x.UploaderId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Files_MessageId", + table: "Files", + column: "MessageId"); + + migrationBuilder.CreateIndex( + name: "IX_Files_UploaderId", + table: "Files", + column: "UploaderId"); + + migrationBuilder.CreateIndex( + name: "IX_Friends_FriendId", + table: "Friends", + column: "FriendId"); + + migrationBuilder.CreateIndex( + name: "IX_Friends_UserId", + table: "Friends", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupMembers_GroupId", + table: "GroupMembers", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupMembers_UserId", + table: "GroupMembers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Groups_OwnerId", + table: "Groups", + column: "OwnerId"); + + migrationBuilder.CreateIndex( + name: "IX_LoginRecords_UserId", + table: "LoginRecords", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Messages_GroupId", + table: "Messages", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_Messages_ReceiverId", + table: "Messages", + column: "ReceiverId"); + + migrationBuilder.CreateIndex( + name: "IX_Messages_SenderId", + table: "Messages", + column: "SenderId"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_UserId", + table: "Notifications", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_userProfileHistories_UserId", + table: "userProfileHistories", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Files"); + + migrationBuilder.DropTable( + name: "Friends"); + + migrationBuilder.DropTable( + name: "GroupMembers"); + + migrationBuilder.DropTable( + name: "LoginRecords"); + + migrationBuilder.DropTable( + name: "Notifications"); + + migrationBuilder.DropTable( + name: "userProfileHistories"); + + migrationBuilder.DropTable( + name: "Messages"); + + migrationBuilder.DropTable( + name: "Groups"); + + migrationBuilder.DropColumn( + name: "AvatarUrl", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Email", + table: "Users"); + + migrationBuilder.DropColumn( + name: "IsOnline", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PasswordHash", + table: "Users"); + + migrationBuilder.RenameColumn( + name: "Username", + table: "Users", + newName: "Password"); + + migrationBuilder.RenameColumn( + name: "Signature", + table: "Users", + newName: "Name"); + + migrationBuilder.RenameColumn( + name: "LastActive", + table: "Users", + newName: "LastModifiedDate"); + + migrationBuilder.RenameColumn( + name: "CreatedAt", + table: "Users", + newName: "CreatedDate"); + } + } +} diff --git a/Migrations/20260519043509_addNickName.Designer.cs b/Migrations/20260519043509_addNickName.Designer.cs new file mode 100644 index 0000000..c794b51 --- /dev/null +++ b/Migrations/20260519043509_addNickName.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WebAppServer1.ApplicationDbContext; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260519043509_addNickName")] + partial class addNickName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FileType") + .HasColumnType("integer"); + + b.Property("FileUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploaderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("UploaderId"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FriendId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("UserId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupMembers"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DeviceInfo") + .IsRequired() + .HasColumnType("text"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("LoginTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("LoginRecords"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("MessageType") + .HasColumnType("integer"); + + b.Property("ReceiverId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("ReceiverId"); + + b.HasIndex("SenderId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsOnline") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("Nickname") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Signature") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ChangedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("text"); + + b.Property("NewValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("OldValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userProfileHistories"); + }); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.HasOne("WebAppServer1.Models.Message", "Message") + .WithMany() + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.HasOne("WebAppServer1.Models.User", "FriendUser") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany("Friends") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FriendUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.HasOne("WebAppServer1.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("WebAppServer1.Models.User", "Receiver") + .WithMany() + .HasForeignKey("ReceiverId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("WebAppServer1.Models.User", "Sender") + .WithMany("Messages") + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("Receiver"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Navigation("Friends"); + + b.Navigation("Messages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260519043509_addNickName.cs b/Migrations/20260519043509_addNickName.cs new file mode 100644 index 0000000..2ca672c --- /dev/null +++ b/Migrations/20260519043509_addNickName.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + /// + public partial class addNickName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Nickname", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Nickname", + table: "Users"); + } + } +} diff --git a/Migrations/AppDbContextModelSnapshot.cs b/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..10a10a7 --- /dev/null +++ b/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,510 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WebAppServer1.ApplicationDbContext; + +#nullable disable + +namespace WebAppServer1.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FileType") + .HasColumnType("integer"); + + b.Property("FileUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploaderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("UploaderId"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FriendId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FriendId"); + + b.HasIndex("UserId"); + + b.ToTable("Friends"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupMembers"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DeviceInfo") + .IsRequired() + .HasColumnType("text"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("LoginTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("LoginRecords"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("MessageType") + .HasColumnType("integer"); + + b.Property("ReceiverId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("ReceiverId"); + + b.HasIndex("SenderId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsOnline") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("Nickname") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Signature") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ChangedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("text"); + + b.Property("NewValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("OldValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userProfileHistories"); + }); + + modelBuilder.Entity("WebAppServer1.Models.FileEntity", b => + { + b.HasOne("WebAppServer1.Models.Message", "Message") + .WithMany() + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Message"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Friend", b => + { + b.HasOne("WebAppServer1.Models.User", "FriendUser") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany("Friends") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FriendUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.HasOne("WebAppServer1.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("WebAppServer1.Models.GroupMember", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.LoginRecord", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Message", b => + { + b.HasOne("WebAppServer1.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("WebAppServer1.Models.User", "Receiver") + .WithMany() + .HasForeignKey("ReceiverId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("WebAppServer1.Models.User", "Sender") + .WithMany("Messages") + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("Receiver"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Notification", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.UserProfileHistory", b => + { + b.HasOne("WebAppServer1.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebAppServer1.Models.Group", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("WebAppServer1.Models.User", b => + { + b.Navigation("Friends"); + + b.Navigation("Messages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Models/ResultResponse.cs b/Models/ResultResponse.cs new file mode 100644 index 0000000..17257e6 --- /dev/null +++ b/Models/ResultResponse.cs @@ -0,0 +1,15 @@ +using MessagePack; + +namespace WebAppServer1.Models +{ + [MessagePackObject] + public class SearchFriendsResultResponse + { + [Key("success")] + public bool success { get; set; } + [Key("username")] + public string username { get; set; } + [Key("userid")] + public int userid { get; set; } + }; +} diff --git a/Models/Tokens.cs b/Models/Tokens.cs new file mode 100644 index 0000000..233e8bf --- /dev/null +++ b/Models/Tokens.cs @@ -0,0 +1,13 @@ +namespace WebAppServer1.Models +{ + public class Tokens + { + public int Id { get; set; } + public required string RefreshToken { get; set; } + public int UserId { get; set; } + public required string UserName { get; set; } + public DateTime IssuedAt { get; set; } + public DateTime ExpiresAt { get; set; } + public bool IsRevoked { get; set; } + } +} diff --git a/Models/Users.cs b/Models/Users.cs new file mode 100644 index 0000000..16e931e --- /dev/null +++ b/Models/Users.cs @@ -0,0 +1,174 @@ +using MessagePack; + +namespace WebAppServer1.Models +{ + // 用户表 + public class User + { + public int Id { get; set; } + public string Username { get; set; } = string.Empty; + public string Nickname { get; set; } = string.Empty; // 可修改,不唯一 + public string Email { get; set; } = string.Empty; + public string PasswordHash { get; set; } = string.Empty; + public string AvatarUrl { get; set; } = string.Empty; + public string Signature { get; set; } = string.Empty; + public DateTime LastActive { get; set; } + public DateTime CreatedAt { get; set; } + public bool IsOnline { get; set; } + + public ICollection Friends { get; set; } = new List(); + public ICollection Messages { get; set; } = new List(); + } + + // 好友关系表 + public class Friend + { + public int Id { get; set; } + public int UserId { get; set; } + public int FriendId { get; set; } + public FriendStatus Status { get; set; } + public DateTime CreatedAt { get; set; } + + public User User { get; set; } + public User FriendUser { get; set; } + } + + public enum FriendStatus + { + Pending, + Accepted, + Blocked + } + + // 群聊表 + public class Group + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public int OwnerId { get; set; } + public string AvatarUrl { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + + public User Owner { get; set; } + public ICollection Members { get; set; } = new List(); + } + + // 群成员表 + public class GroupMember + { + public int Id { get; set; } + public int GroupId { get; set; } + public int UserId { get; set; } + public GroupRole Role { get; set; } + public DateTime JoinedAt { get; set; } + + public Group Group { get; set; } + public User User { get; set; } + } + + public enum GroupRole + { + Member, + Admin, + Owner + } + + // 消息表 + public class Message + { + public int Id { get; set; } + public int SenderId { get; set; } + public int? ReceiverId { get; set; } // 私聊 + public int? GroupId { get; set; } // 群聊 + public string Content { get; set; } = string.Empty; + public MessageType MessageType { get; set; } + public DateTime CreatedAt { get; set; } + public bool IsRead { get; set; } + public bool IsDeleted { get; set; } + + public User Sender { get; set; } + public User Receiver { get; set; } + public Group Group { get; set; } + } + + public enum MessageType + { + Text, + Image, + File, + Video, + Voice + } + + // 通知表 + public class Notification + { + public int Id { get; set; } + public int UserId { get; set; } + public NotificationType Type { get; set; } + public string Content { get; set; } = string.Empty; + public bool IsRead { get; set; } + public DateTime CreatedAt { get; set; } + + public User User { get; set; } + } + + public enum NotificationType + { + FriendRequest, + Message, + System + } + + // 文件表 + public class FileEntity + { + public int Id { get; set; } + public int UploaderId { get; set; } + public int MessageId { get; set; } + public string FileUrl { get; set; } = string.Empty; + public FileType FileType { get; set; } + public int Size { get; set; } + public DateTime UploadedAt { get; set; } + + public User Uploader { get; set; } + public Message Message { get; set; } + } + + public enum FileType + { + Image, + Video, + Document, + Audio + } + + public class UserProfileHistory + { + public int Id { get; set; } + public int UserId { get; set; } + public string FieldName { get; set; } = string.Empty; + public string OldValue { get; set; } = string.Empty; + public string NewValue { get; set; } = string.Empty; + public DateTime ChangedAt { get; set; } + public string ChangedBy { get; set; } = string.Empty; + + public User User { get; set; } + } + + public class LoginRecord + { + public int Id { get; set; } + public int UserId { get; set; } + public DateTime LoginTime { get; set; } + public string IpAddress { get; set; } = string.Empty; + public string DeviceInfo { get; set; } = string.Empty; + public bool IsSuccess { get; set; } + + public User User { get; set; } + } + + + +} + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..356008b --- /dev/null +++ b/Program.cs @@ -0,0 +1,152 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using StackExchange.Redis; +using System.Text; +using WebAppServer1.ApplicationDbContext; +using WebAppServer1.Authentication; +using WebAppServer1.Chat; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +//注册SignalR服务 +builder.Services.AddSignalR(options => +{ + options.MaximumReceiveMessageSize = 1024 * 1024 * 10; // 10MB +}) +.AddMessagePackProtocol(options => +{ + options.SerializerOptions = MessagePack.MessagePackSerializerOptions.Standard + .WithSecurity(MessagePack.MessagePackSecurity.UntrustedData); +}); + +//连接并注册Redis服务 +var redis = ConnectionMultiplexer.Connect("83.229.121.44:6378,password=redis_4GG7KG,defaultDatabase=5"); +builder.Services.AddSingleton(redis); + +// 服务启动时清理 Redis +//var _redis = scope.ServiceProvider.GetRequiredService(); +var db = redis.GetDatabase(); +var server = redis.GetServer(redis.GetEndPoints().First()); + +// 清理全局连接 +await db.KeyDeleteAsync("AllConnections"); + +// 清理所有群组 +foreach (var _key in server.Keys(pattern: "Group:*")) +{ + await db.KeyDeleteAsync(_key); +} + +//连接数据库 +// 添加 DbContext 并使用 PostgreSQL +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("PostgresConnection"))); + +//配置跨域 +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowVueApp", + policy => + { + policy.WithOrigins("http://192.168.1.254:5173", "http://localhost:5173") // 前端地址 + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); +}); + +//配置jwt +// JWT 配置 +var jwtSettings = builder.Configuration.GetSection("Jwt"); +var keyString = jwtSettings["Key"] ?? throw new InvalidOperationException("JWT Key 未配置"); +var key = Encoding.UTF8.GetBytes(keyString); + +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(options => +{ + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSettings["Issuer"], + ValidAudience = jwtSettings["Audience"], + IssuerSigningKey = new SymmetricSecurityKey(key) + }; + + // 允许 SignalR 使用 JWT + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/Chat")) + { + context.Token = accessToken; + } + return Task.CompletedTask; + } + }; +}); +builder.Services.AddScoped(); + + +var app = builder.Build(); + +app.UseRouting(); + +//启用跨域配置 +app.UseCors("AllowVueApp"); + +//启用jwt +app.UseAuthentication(); +app.UseAuthorization(); + + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +//app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +//映射端点 +app.MapHub("/Chat"); + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast"); + +app.Run(); + +internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..b33b135 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://0.0.0.0:5162", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + + } + } + diff --git a/Tool/GenerateTool.cs b/Tool/GenerateTool.cs new file mode 100644 index 0000000..6d45359 --- /dev/null +++ b/Tool/GenerateTool.cs @@ -0,0 +1,45 @@ +using System.Security.Cryptography; +using System.Text; + +namespace WebAppServer1.Tool +{ + public class GenerateTool + { + public static string GeneratePassword() + { + const string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const string digits = "0123456789"; + var sb = new StringBuilder(); + + // 生成 2 个字母 + for (int i = 0; i < 2; i++) + { + int index = RandomNumberGenerator.GetInt32(letters.Length); + sb.Append(letters[index]); + } + + // 生成 4 个数字 + for (int i = 0; i < 4; i++) + { + int index = RandomNumberGenerator.GetInt32(digits.Length); + sb.Append(digits[index]); + } + + return sb.ToString(); + } + + public static string GenerateRandomNickname() + { + string[] part1 = { "自由的", "快乐的", "孤独的", "勇敢的", "神秘的" }; + string[] part2 = { "小鸟", "星星", "花朵", "少年", "旅人" }; + string[] part3 = { "在唱歌", "在飞翔", "在微笑", "在流浪", "在思考" }; + + var random = new Random(); + string nickname = part1[random.Next(part1.Length)] + + part2[random.Next(part2.Length)] + + part3[random.Next(part3.Length)]; + return nickname; + } + + } +} diff --git a/WebAppServer1.csproj b/WebAppServer1.csproj new file mode 100644 index 0000000..60dec30 --- /dev/null +++ b/WebAppServer1.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/WebAppServer1.http b/WebAppServer1.http new file mode 100644 index 0000000..c2c7374 --- /dev/null +++ b/WebAppServer1.http @@ -0,0 +1,6 @@ +@WebAppServer1_HostAddress = http://localhost:5162 + +GET {{WebAppServer1_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/WebAppServer1.slnx b/WebAppServer1.slnx new file mode 100644 index 0000000..34abafc --- /dev/null +++ b/WebAppServer1.slnx @@ -0,0 +1,3 @@ + + + diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..7e5ed48 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + + "ConnectionStrings": { + "PostgresConnection": "Host=localhost;Port=5555;Database=webapp;Username=postgres;Password=123456" + }, + "Jwt": { + "Key": "YourSuperSecretKeyForJwtTokenGeneration2024!@#$%^&*()", + "Issuer": "SignalRChat", + "Audience": "SignalRChatUsers", + "AccessTokenExpirationMinutes": 5, + "RefreshTokenExpirationDays": 60 + }, + + "AllowedHosts": "*" +}