From 7a44b7fd9ccd45e5fddb0a87fe790dcd004550e7 Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Wed, 9 Aug 2023 17:34:18 +0800 Subject: [PATCH] comment system --- Server/Controller/BaseController.cs | 13 +- Server/Controller/BaseController.sqlite.cs | 2 +- Server/Controller/CommentApiController.cs | 265 +++++++++++ .../CommentApiController.structs.cs | 44 ++ Server/Controller/EventApiController.cs | 72 +-- Server/Controller/FlowerApiController.cs | 30 +- Server/Data/FlowerDatabase.cs | 5 + Server/Data/Model/CommentItem.cs | 99 ++++ Server/Data/Model/FlowerItem.cs | 8 +- Server/Data/Model/PhotoItem.cs | 10 +- Server/Data/Model/RecordItem.cs | 26 +- Server/Data/Model/TokenItem.cs | 12 +- Server/Data/Model/UserItem.cs | 6 +- .../20230809083422_Add-Comments.Designer.cs | 445 ++++++++++++++++++ .../Migrations/20230809083422_Add-Comments.cs | 94 ++++ .../Migrations/FlowerDatabaseModelSnapshot.cs | 84 ++++ Server/Program.cs | 2 +- 17 files changed, 1152 insertions(+), 65 deletions(-) create mode 100644 Server/Controller/CommentApiController.cs create mode 100644 Server/Controller/CommentApiController.structs.cs create mode 100644 Server/Data/Model/CommentItem.cs create mode 100644 Server/Migrations/20230809083422_Add-Comments.Designer.cs create mode 100644 Server/Migrations/20230809083422_Add-Comments.cs diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs index 403f6cc..46de864 100644 --- a/Server/Controller/BaseController.cs +++ b/Server/Controller/BaseController.cs @@ -297,15 +297,16 @@ public abstract partial class BaseController : ControllerBase { return data; } - using var bitmap = SKBitmap.Decode(data); - if (maxWidth >= bitmap.Width) + using var image = SKImage.FromEncodedData(data); + if (maxWidth >= image.Width) { - using var enc = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80); + using var enc = image.Encode(SKEncodedImageFormat.Jpeg, 80); return enc.ToArray(); } - var height = maxWidth * bitmap.Height / bitmap.Width; - using var image = bitmap.Resize(new SKImageInfo(maxWidth, height), quality); - using var encode = image.Encode(SKEncodedImageFormat.Jpeg, 80); + var height = maxWidth * image.Height / image.Width; + using var bitmap = SKBitmap.FromImage(image); + using var resized = bitmap.Resize(new SKImageInfo(maxWidth, height), quality); + using var encode = resized.Encode(SKEncodedImageFormat.Jpeg, 80); return encode.ToArray(); } diff --git a/Server/Controller/BaseController.sqlite.cs b/Server/Controller/BaseController.sqlite.cs index 9af40fd..44d01c5 100644 --- a/Server/Controller/BaseController.sqlite.cs +++ b/Server/Controller/BaseController.sqlite.cs @@ -47,7 +47,7 @@ partial class BaseController protected UserItem? QueryUserItemForAuthentication(string id) { return database.Users - .FromSql($"SELECT \"uid\",\"id\",\"password\",0 AS \"level\",0 AS \"regdate\",\"activedate\",\"\" AS \"name\",NULL AS \"email\",NULL AS \"mobile\",NULL as \"avatar\" FROM \"users\" WHERE \"id\" = {id} LIMIT 1") + .FromSql($"SELECT \"uid\",\"id\",\"password\",\"level\",\"regdate\",\"activedate\",\"name\",\"email\",\"mobile\",NULL as \"avatar\" FROM \"users\" WHERE \"id\" = {id} LIMIT 1") .SingleOrDefault(); } diff --git a/Server/Controller/CommentApiController.cs b/Server/Controller/CommentApiController.cs new file mode 100644 index 0000000..ac56e76 --- /dev/null +++ b/Server/Controller/CommentApiController.cs @@ -0,0 +1,265 @@ +using Blahblah.FlowerStory.Server.Data; +using Blahblah.FlowerStory.Server.Data.Model; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; + +namespace Blahblah.FlowerStory.Server.Controller; + +/// +/// 评论相关 API 服务 +/// +[ApiController] +[Produces("application/json")] +[Route("api/comment")] +public class CommentApiController : BaseController +{ + static readonly int?[] specialTypes = { 1, 2, 3 }; + + /// + public CommentApiController(FlowerDatabase database, ILogger? logger = null) : base(database, logger) + { + } + + /// + /// 获取符合条件的评论 + /// + /// + /// 请求示例: + /// + /// GET /api/comment/query + /// Authorization: authorization id + /// + /// 参数: + /// + /// rid: int + /// t: int? + /// from: long? + /// to: long? + /// size: int? + /// + /// + /// 事件唯一 id + /// 评论类型 id + /// 起始日期 + /// 结束日期 + /// 分页大小 + /// 会话有效则返回符合条件的评论集 + /// 返回符合条件的评论集 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户 + [Route("query", Name = "queryComments")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesErrorResponseType(typeof(ErrorResponse))] + [HttpGet] + [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] + public ActionResult GetComments( + [FromQuery(Name = "rid")][Required] int recordId, + [FromQuery(Name = "t")] int? typeId, + [FromQuery] long? from, + [FromQuery] long? to, + [FromQuery(Name = "size")] int? pageSize = 20) + { + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + SaveDatabase(); + + var comments = database.Comments.Where(c => c.RecordId == recordId); + + if (typeId == null) + { + comments = comments.Where(c => !specialTypes.Contains(c.CommentCategoryId)); + } + else + { + comments = comments.Where(c => c.CommentCategoryId == typeId); + } + if (from != null) + { + comments = comments.Where(r => r.DateUnixTime >= from); + } + if (to != null) + { + comments = comments.Where(r => r.DateUnixTime <= to); + } + + comments = comments.Select(r => new CommentItem + { + Id = r.Id, + RecordId = r.RecordId, + CommentCategoryId = r.CommentCategoryId, + OwnerId = r.OwnerId, + ByUserId = r.ByUserId, + DateUnixTime = r.DateUnixTime, + Latitude = r.Latitude, + Longitude = r.Longitude, + Text = r.Text, + ByUserName = string.IsNullOrEmpty(r.ByUserName) && r.ByUserId != null ? database.Users.Single(u => u.Id == r.ByUserId).Name : r.ByUserName + }); + + var size = pageSize ?? 20; + comments = comments.OrderByDescending(r => r.DateUnixTime).Take(size); + + var array = comments.ToArray(); + + foreach (var r in array) + { + if (string.IsNullOrEmpty(r.ByUserName)) + { + r.ByUserName = user.Name; + } + } + + return Ok(array); + } + + /// + /// 移除用户的评论 + /// + /// + /// 请求示例: + /// + /// DELETE /api/comment/remove + /// Authorization: authorization id + /// + /// 参数: + /// + /// id: int + /// + /// + /// 评论唯一 id + /// 会话有效则返回操作影响的数据库行数 + /// 返回操作影响的数据库行数 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户 + [Route("remove", Name = "removeComment")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesErrorResponseType(typeof(ErrorResponse))] + [HttpDelete] + public ActionResult RemoveComment([FromQuery][Required] int id) + { + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + var records = database.Records.Where(r => database.Comments.Any(c => c.OwnerId == user.Id && c.Id == id && c.RecordId == r.Id)).ToList(); + var count = database.Comments.Where(c => c.OwnerId == user.Id && c.Id == id).ExecuteDelete(); + + if (count > 0) + { + foreach (var r in records) + { + r.LikeCount = database.Comments.Count(c => c.CommentCategoryId == 1 && c.RecordId == r.Id); + r.FavoriteCount = database.Comments.Count(c => c.CommentCategoryId == 2 && c.RecordId == r.Id); + r.CommentCount = database.Comments.Count(c => !specialTypes.Contains(c.CommentCategoryId) && c.RecordId == r.Id); + } + } + + SaveDatabase(); + + return Ok(count); + } + + /// + /// 用户添加评论 + /// + /// + /// 请求示例: + /// + /// POST /api/comment/add + /// Authorization: authorization id + /// + /// 参数: + /// + /// recordId: 1 + /// typeId: 0 + /// text: "这朵花好漂亮" + /// byUser: "来宾" + /// lon: 29.5462794 + /// lat: 106.5380034 + /// + /// + /// 评论参数 + /// 添加成功则返回已添加的评论对象 + /// 返回已添加的评论对象 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户 + [Route("add", Name = "addComment")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesErrorResponseType(typeof(ErrorResponse))] + [HttpPost] + public ActionResult AddEvent([FromBody] CommentParameter comment) + { + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + var now = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var item = new CommentItem + { + RecordId = comment.RecordId, + OwnerId = user.Id, + CommentCategoryId = comment.TypeId, + DateUnixTime = now, + ByUserId = comment.ByUser == null ? user.Id : null, + ByUserName = comment.ByUser, + Text = comment.Text, + Latitude = comment.Latitude, + Longitude = comment.Longitude + }; + database.Comments.Add(item); + + var record = database.Records.SingleOrDefault(r => r.Id == comment.RecordId); + if (record != null) + { + switch (comment.TypeId) + { + case 1: + record.LikeCount = (record.LikeCount ?? 0) + 1; + break; + case 2: + record.FavoriteCount = (record.FavoriteCount ?? 0) + 1; + break; + case not 3: + record.CommentCount = (record.CommentCount ?? 0) + 1; + break; + } + } + + SaveDatabase(); + + return Ok(item); + } +} diff --git a/Server/Controller/CommentApiController.structs.cs b/Server/Controller/CommentApiController.structs.cs new file mode 100644 index 0000000..df0de28 --- /dev/null +++ b/Server/Controller/CommentApiController.structs.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Blahblah.FlowerStory.Server.Controller; + +/// +/// 评论参数 +/// +public record CommentParameter +{ + /// + /// 事件唯一 id + /// + [Required] + public required int RecordId { get; init; } + + /// + /// 内容 + /// + [Required] + public required string Text { get; init; } + + /// + /// 评论分类 id + /// + public int? TypeId { get; init; } + + /// + /// 操作人姓名 + /// + public string? ByUser { get; init; } + + /// + /// 纬度 + /// + [JsonPropertyName("lat")] + public double? Latitude { get; init; } + + /// + /// 经度 + /// + [JsonPropertyName("lon")] + public double? Longitude { get; set; } +} diff --git a/Server/Controller/EventApiController.cs b/Server/Controller/EventApiController.cs index 54e23c2..73f129b 100644 --- a/Server/Controller/EventApiController.cs +++ b/Server/Controller/EventApiController.cs @@ -20,7 +20,7 @@ public class EventApiController : BaseController } /// - /// 获取用户相关所有符合条件的事件 + /// 获取符合条件的公共事件 /// /// /// 请求示例: @@ -30,11 +30,13 @@ public class EventApiController : BaseController /// /// 参数: /// + /// fid: int? /// eid: int? /// key: string? /// from: long? /// to: long? /// p: bool? + /// size: int? /// /// /// 花草唯一 id @@ -43,8 +45,9 @@ public class EventApiController : BaseController /// 起始日期 /// 结束日期 /// 是否包含图片 - /// 会话有效则返回符合条件的花草集 - /// 返回符合条件的花草集 + /// 分页大小 + /// 会话有效则返回符合条件的事件集 + /// 返回符合条件的事件集 /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 @@ -62,7 +65,8 @@ public class EventApiController : BaseController [FromQuery] string? key, [FromQuery] long? from, [FromQuery] long? to, - [FromQuery(Name = "p")] bool? includePhoto) + [FromQuery(Name = "p")] bool? includePhoto, + [FromQuery(Name = "size")] int? pageSize = 20) { var (result, user) = CheckPermission(); if (result != null) @@ -76,21 +80,8 @@ public class EventApiController : BaseController SaveDatabase(); - var records = database.Records.Where(r => r.OwnerId == user.Id).Select(r => new RecordItem - { - Id = r.Id, - OwnerId = r.OwnerId, - ByUserId = r.ByUserId, - DateUnixTime = r.DateUnixTime, - EventId = r.EventId, - FlowerId = r.FlowerId, - IsHidden = r.IsHidden, - Latitude = r.Latitude, - Longitude = r.Longitude, - Title = r.Title, - Memo = r.Memo, - ByUserName = string.IsNullOrEmpty(r.ByUserName) && r.ByUserId != null ? database.Users.Single(u => u.Id == r.ByUserId).Name : r.ByUserName - }); + var records = database.Records.Where(r => r.IsHidden != true); + if (flowerId != null) { records = records.Where(r => r.FlowerId == flowerId); @@ -116,23 +107,48 @@ public class EventApiController : BaseController if (includePhoto == true) { - foreach (var r in records) - { - r.Photos = database.Photos.Where(p => p.RecordId == r.Id).ToList(); - foreach (var photo in r.Photos) - { - photo.Url = $"photo/flower/{r.FlowerId}/{photo.Path}?thumb=1"; - } - } + records = records.Include(r => r.Photos); } + records = records.Select(r => new RecordItem + { + Id = r.Id, + OwnerId = r.OwnerId, + ByUserId = r.ByUserId, + DateUnixTime = r.DateUnixTime, + EventId = r.EventId, + FlowerId = r.FlowerId, + IsHidden = r.IsHidden, + Latitude = r.Latitude, + Longitude = r.Longitude, + Title = r.Title, + Memo = r.Memo, + ByUserName = string.IsNullOrEmpty(r.ByUserName) && r.ByUserId != null ? database.Users.Single(u => u.Id == r.ByUserId).Name : r.ByUserName, + Photos = r.Photos, + LikeCount = r.LikeCount, + FavoriteCount = r.FavoriteCount, + CommentCount = r.CommentCount + }); + + var size = pageSize ?? 20; + records = records.OrderByDescending(r => r.DateUnixTime).Take(size); + var array = records.ToArray(); + foreach (var r in array) { if (string.IsNullOrEmpty(r.ByUserName)) { r.ByUserName = user.Name; } + if (r.Photos == null) + { + continue; + } + foreach (var photo in r.Photos) + { + photo.Url = $"photo/flower/{r.FlowerId}/{photo.Path}?thumb=1"; + } } return Ok(array); @@ -306,7 +322,7 @@ public class EventApiController : BaseController /// 参数: /// /// flowerId: 1 - /// eventId": 4 // 浇水 + /// eventId: 4 // 浇水 /// byUser: "朋友" /// memo: "快干死了" /// lon: 29.5462794 diff --git a/Server/Controller/FlowerApiController.cs b/Server/Controller/FlowerApiController.cs index 1ab3a52..06b4c79 100644 --- a/Server/Controller/FlowerApiController.cs +++ b/Server/Controller/FlowerApiController.cs @@ -168,7 +168,7 @@ public class FlowerApiController : BaseController bool? includePhoto, double? latitude, double? longitude, int? distance, int? page = 0, int? pageSize = 20) { - IEnumerable flowers; + IQueryable flowers; if (userId != null) { flowers = database.Flowers.Where(f => f.OwnerId == userId); @@ -210,9 +210,17 @@ public class FlowerApiController : BaseController flowers = flowers.Where(f => database.Records.Any(r => r.FlowerId == f.Id && r.EventId == eventId)); } + if (includePhoto == true) + { + flowers = flowers.Include(f => f.Photos); + } + + flowers = flowers.OrderByDescending(f => f.DateBuyUnixTime); + + IEnumerable items; if (distance != null && latitude != null && longitude != null) { - flowers = flowers.Where(f => f.Latitude != null && f.Longitude != null) + items = flowers.Where(f => f.Latitude != null && f.Longitude != null) .AsEnumerable() .Where(f => { @@ -221,18 +229,26 @@ public class FlowerApiController : BaseController return d <= distance; }); } + else + { + items = flowers; + } - int count = flowers.Count(); + int count = items.Count(); var size = pageSize ?? 20; var p = page ?? 0; - flowers = flowers.OrderByDescending(f => f.DateBuyUnixTime).Skip(p * size).Take(size); + items = items.Skip(p * size).Take(size); if (includePhoto == true) { - foreach (var f in flowers) + foreach (var f in items) { - f.Photos = database.Photos.Where(p => p.FlowerId == f.Id && p.RecordId == null).ToList(); + //f.Photos = database.Photos.Where(p => p.FlowerId == f.Id && p.RecordId == null).ToList(); + if (f.Photos == null) + { + continue; + } foreach (var photo in f.Photos) { photo.Url = $"photo/flower/{f.Id}/{photo.Path}?thumb=1"; @@ -242,7 +258,7 @@ public class FlowerApiController : BaseController return new() { - Flowers = flowers.ToArray(), + Flowers = items.ToArray(), Count = count }; } diff --git a/Server/Data/FlowerDatabase.cs b/Server/Data/FlowerDatabase.cs index 624da0b..99ab5ac 100644 --- a/Server/Data/FlowerDatabase.cs +++ b/Server/Data/FlowerDatabase.cs @@ -41,4 +41,9 @@ public class FlowerDatabase : DbContext /// 照片集 /// public DbSet Photos { get; set; } + + /// + /// 评论集 + /// + public DbSet Comments { get; set; } } diff --git a/Server/Data/Model/CommentItem.cs b/Server/Data/Model/CommentItem.cs new file mode 100644 index 0000000..239d8ef --- /dev/null +++ b/Server/Data/Model/CommentItem.cs @@ -0,0 +1,99 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Blahblah.FlowerStory.Server.Data.Model; + +/// +/// 评论对象 +/// +[Table("comments")] +public class CommentItem +{ + /// + /// 自增 id,主键 + /// + [Column("cid")] + [Key] + [Required] + public int Id { get; set; } + + /// + /// 关联人 id + /// + [Column("uid")] + [ForeignKey(nameof(Owner))] + [Required] + public int OwnerId { get; set; } + + /// + /// 关联人 + /// + [JsonIgnore] + public UserItem? Owner { get; set; } + + /// + /// 操作时间 + /// + [Column("date")] + [Required] + [JsonPropertyName("date")] + public long DateUnixTime { get; set; } + + /// + /// 关联的事件 id + /// + [Column("rid")] + [Required] + [ForeignKey(nameof(Record))] + public int RecordId { get; set; } + + /// + /// 关联的事件 + /// + [JsonIgnore] + public RecordItem Record { get; set; } = null!; + + /// + /// 评论类别:1-点赞,2-收藏,3-标签,default-评论 + /// + [Column("category")] + public int? CommentCategoryId { get; set; } + + /// + /// 操作人 uid + /// + [Column("byuid")] + public int? ByUserId { get; set; } + + /// + /// 操作人名称 + /// + [Column("byname")] + public string? ByUserName { get; set; } + + /// + /// 备注 + /// + [Column("text")] + public string? Text { get; set; } + + /// + /// 操作时间 + /// + [NotMapped] + [JsonIgnore] + public DateTimeOffset Date => DateTimeOffset.FromUnixTimeMilliseconds(DateUnixTime); + + /// + /// 纬度 + /// + [Column("latitude")] + public double? Latitude { get; set; } + + /// + /// 经度 + /// + [Column("longitude")] + public double? Longitude { get; set; } +} diff --git a/Server/Data/Model/FlowerItem.cs b/Server/Data/Model/FlowerItem.cs index eb4a067..9e53b02 100644 --- a/Server/Data/Model/FlowerItem.cs +++ b/Server/Data/Model/FlowerItem.cs @@ -24,7 +24,7 @@ public class FlowerItem : ILocation [Column("uid")] [ForeignKey(nameof(Owner))] [Required] - public required int OwnerId { get; set; } + public int OwnerId { get; set; } /// /// 所有人 @@ -37,14 +37,14 @@ public class FlowerItem : ILocation /// [Column("categoryid")] [Required] - public required int CategoryId { get; set; } + public int CategoryId { get; set; } /// /// 花草名称 /// [Column("name")] [Required] - public required string Name { get; set; } + public string Name { get; set; } = null!; /// /// 购买时间 @@ -52,7 +52,7 @@ public class FlowerItem : ILocation [Column("datebuy")] [Required] [JsonPropertyName("dateBuy")] - public required long DateBuyUnixTime { get; set; } + public long DateBuyUnixTime { get; set; } /// /// 购买花费 diff --git a/Server/Data/Model/PhotoItem.cs b/Server/Data/Model/PhotoItem.cs index 6d1dc27..87a8c9c 100644 --- a/Server/Data/Model/PhotoItem.cs +++ b/Server/Data/Model/PhotoItem.cs @@ -24,7 +24,7 @@ public class PhotoItem [Column("uid")] [ForeignKey(nameof(Owner))] [Required] - public required int OwnerId { get; set; } + public int OwnerId { get; set; } /// /// 关联人 @@ -63,21 +63,21 @@ public class PhotoItem /// [Column("filetype")] [Required] - public required string FileType { get; set; } + public string FileType { get; set; } = null!; /// /// 文件名 /// [Column("filename")] [Required] - public required string FileName { get; set; } + public string FileName { get; set; } = null!; /// /// 文件路径 /// [Column("path")] [Required] - public required string Path { get; set; } + public string Path { get; set; } = null!; /// /// 上传时间 @@ -85,7 +85,7 @@ public class PhotoItem [Column("dateupload")] [Required] [JsonPropertyName("dateUpload")] - public required long DateUploadUnixTime { get; set; } + public long DateUploadUnixTime { get; set; } /// /// 上传时间 diff --git a/Server/Data/Model/RecordItem.cs b/Server/Data/Model/RecordItem.cs index c939819..4689ba3 100644 --- a/Server/Data/Model/RecordItem.cs +++ b/Server/Data/Model/RecordItem.cs @@ -24,7 +24,7 @@ public class RecordItem : ILocation [Column("uid")] [ForeignKey(nameof(Owner))] [Required] - public required int OwnerId { get; set; } + public int OwnerId { get; set; } /// /// 关联人 @@ -38,7 +38,7 @@ public class RecordItem : ILocation [Column("fid")] [ForeignKey(nameof(Flower))] [Required] - public required int FlowerId { get; set; } + public int FlowerId { get; set; } /// /// 关联花草 @@ -51,7 +51,7 @@ public class RecordItem : ILocation /// [Column("eid")] [Required] - public required int EventId { get; set; } + public int EventId { get; set; } /// /// 操作时间 @@ -59,7 +59,7 @@ public class RecordItem : ILocation [Column("date")] [Required] [JsonPropertyName("date")] - public required long DateUnixTime { get; set; } + public long DateUnixTime { get; set; } /// /// 是否为私有记录 @@ -114,4 +114,22 @@ public class RecordItem : ILocation /// [Column("longitude")] public double? Longitude { get; set; } + + /// + /// 点赞数 + /// + [Column("likes")] + public int? LikeCount { get; set; } + + /// + /// 收藏数 + /// + [Column("favs")] + public int? FavoriteCount { get; set; } + + /// + /// 评论数 + /// + [Column("comments")] + public int? CommentCount { get; set; } } diff --git a/Server/Data/Model/TokenItem.cs b/Server/Data/Model/TokenItem.cs index fd0667c..f79586d 100644 --- a/Server/Data/Model/TokenItem.cs +++ b/Server/Data/Model/TokenItem.cs @@ -16,14 +16,14 @@ public class TokenItem [Column("tid")] [Key] [Required] - public required string Id { get; set; } + public string Id { get; set; } = null!; /// /// 关联用户 uid /// [Column("uid")] [Required] - public required int UserId { get; set; } + public int UserId { get; set; } /// /// 登录时间 @@ -31,7 +31,7 @@ public class TokenItem [Column("logondate")] [Required] [JsonPropertyName("logonDate")] - public required long LogonDateUnixTime { get; set; } + public long LogonDateUnixTime { get; set; } /// /// 活动时间 @@ -39,7 +39,7 @@ public class TokenItem [Column("activedate")] [Required] [JsonPropertyName("activeDate")] - public required long ActiveDateUnixTime { get; set; } + public long ActiveDateUnixTime { get; set; } /// /// 过期时间 @@ -47,14 +47,14 @@ public class TokenItem [Column("expiredate")] [Required] [JsonPropertyName("expireDate")] - public required long ExpireDateUnixTime { get; set; } + public long ExpireDateUnixTime { get; set; } /// /// 过期秒数 /// [Column("expiresecs")] [Required] - public required int ExpireSeconds { get; set; } + public int ExpireSeconds { get; set; } /// /// 验证码 diff --git a/Server/Data/Model/UserItem.cs b/Server/Data/Model/UserItem.cs index a10122f..dd240d9 100644 --- a/Server/Data/Model/UserItem.cs +++ b/Server/Data/Model/UserItem.cs @@ -23,7 +23,7 @@ public class UserItem /// [Column("id")] [Required] - public required string UserId { get; set; } + public string UserId { get; set; } = null!; /// /// 密码,值为 SHA256(password+id+salt) @@ -41,7 +41,7 @@ public class UserItem /// [Column("level")] [Required] - public required int Level { get; set; } + public int Level { get; set; } /// /// 注册时间 @@ -49,7 +49,7 @@ public class UserItem [Column("regdate")] [Required] [JsonPropertyName("registerDate")] - public required long RegisterDateUnixTime { get; set; } + public long RegisterDateUnixTime { get; set; } /// /// 最后变动时间 diff --git a/Server/Migrations/20230809083422_Add-Comments.Designer.cs b/Server/Migrations/20230809083422_Add-Comments.Designer.cs new file mode 100644 index 0000000..ad99706 --- /dev/null +++ b/Server/Migrations/20230809083422_Add-Comments.Designer.cs @@ -0,0 +1,445 @@ +// +using System; +using Blahblah.FlowerStory.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + [DbContext(typeof(FlowerDatabase))] + [Migration("20230809083422_Add-Comments")] + partial class AddComments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.9"); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.CommentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("cid"); + + b.Property("ByUserId") + .HasColumnType("INTEGER") + .HasColumnName("byuid"); + + b.Property("ByUserName") + .HasColumnType("TEXT") + .HasColumnName("byname"); + + b.Property("CommentCategoryId") + .HasColumnType("INTEGER") + .HasColumnName("category"); + + b.Property("DateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("date") + .HasAnnotation("Relational:JsonPropertyName", "date"); + + b.Property("Latitude") + .HasColumnType("REAL") + .HasColumnName("latitude"); + + b.Property("Longitude") + .HasColumnType("REAL") + .HasColumnName("longitude"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("RecordId") + .HasColumnType("INTEGER") + .HasColumnName("rid"); + + b.Property("Text") + .HasColumnType("TEXT") + .HasColumnName("text"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("RecordId"); + + b.ToTable("comments"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("fid"); + + b.Property("CategoryId") + .HasColumnType("INTEGER") + .HasColumnName("categoryid"); + + b.Property("Cost") + .HasColumnType("real") + .HasColumnName("cost"); + + b.Property("DateBuyUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("datebuy") + .HasAnnotation("Relational:JsonPropertyName", "dateBuy"); + + b.Property("Latitude") + .HasColumnType("REAL") + .HasColumnName("latitude"); + + b.Property("Longitude") + .HasColumnType("REAL") + .HasColumnName("longitude"); + + b.Property("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("Purchase") + .HasColumnType("TEXT") + .HasColumnName("purchase"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("flowers"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.PhotoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("pid"); + + b.Property("DateUploadUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("dateupload") + .HasAnnotation("Relational:JsonPropertyName", "dateUpload"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("filename"); + + b.Property("FileType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("filetype"); + + b.Property("FlowerId") + .HasColumnType("INTEGER") + .HasColumnName("fid"); + + b.Property("Height") + .HasColumnType("INTEGER") + .HasColumnName("height"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("RecordId") + .HasColumnType("INTEGER") + .HasColumnName("rid"); + + b.Property("Width") + .HasColumnType("INTEGER") + .HasColumnName("width"); + + b.HasKey("Id"); + + b.HasIndex("FlowerId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("RecordId"); + + b.ToTable("photos"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("rid"); + + b.Property("ByUserId") + .HasColumnType("INTEGER") + .HasColumnName("byuid"); + + b.Property("ByUserName") + .HasColumnType("TEXT") + .HasColumnName("byname"); + + b.Property("CommentCount") + .HasColumnType("INTEGER") + .HasColumnName("comments"); + + b.Property("DateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("date") + .HasAnnotation("Relational:JsonPropertyName", "date"); + + b.Property("EventId") + .HasColumnType("INTEGER") + .HasColumnName("eid"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER") + .HasColumnName("favs"); + + b.Property("FlowerId") + .HasColumnType("INTEGER") + .HasColumnName("fid"); + + b.Property("IsHidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Latitude") + .HasColumnType("REAL") + .HasColumnName("latitude"); + + b.Property("LikeCount") + .HasColumnType("INTEGER") + .HasColumnName("likes"); + + b.Property("Longitude") + .HasColumnType("REAL") + .HasColumnName("longitude"); + + b.Property("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id"); + + b.HasIndex("FlowerId"); + + b.HasIndex("OwnerId"); + + b.ToTable("records"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.TokenItem", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("tid"); + + b.Property("ActiveDateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("activedate") + .HasAnnotation("Relational:JsonPropertyName", "activeDate"); + + b.Property("ClientAgent") + .HasColumnType("TEXT") + .HasColumnName("clientagent"); + + b.Property("ClientApp") + .HasColumnType("TEXT") + .HasColumnName("clientapp"); + + b.Property("DeviceId") + .HasColumnType("TEXT") + .HasColumnName("deviceid"); + + b.Property("ExpireDateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("expiredate") + .HasAnnotation("Relational:JsonPropertyName", "expireDate"); + + b.Property("ExpireSeconds") + .HasColumnType("INTEGER") + .HasColumnName("expiresecs"); + + b.Property("LogonDateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("logondate") + .HasAnnotation("Relational:JsonPropertyName", "logonDate"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("VerifyCode") + .HasColumnType("TEXT") + .HasColumnName("verifycode"); + + b.HasKey("Id"); + + b.ToTable("tokens"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.UserItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("ActiveDateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("activedate"); + + b.Property("Avatar") + .HasColumnType("BLOB") + .HasColumnName("avatar"); + + b.Property("Email") + .HasColumnType("TEXT") + .HasColumnName("email"); + + b.Property("Level") + .HasColumnType("INTEGER") + .HasColumnName("level"); + + b.Property("Mobile") + .HasColumnType("TEXT") + .HasColumnName("mobile"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("RegisterDateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("regdate") + .HasAnnotation("Relational:JsonPropertyName", "registerDate"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.HasKey("Id"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.CommentItem", b => + { + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.RecordItem", "Record") + .WithMany() + .HasForeignKey("RecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Record"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b => + { + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.PhotoItem", b => + { + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", "Flower") + .WithMany("Photos") + .HasForeignKey("FlowerId"); + + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.RecordItem", "Record") + .WithMany("Photos") + .HasForeignKey("RecordId"); + + b.Navigation("Flower"); + + b.Navigation("Owner"); + + b.Navigation("Record"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b => + { + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", "Flower") + .WithMany() + .HasForeignKey("FlowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Flower"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b => + { + b.Navigation("Photos"); + }); + + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b => + { + b.Navigation("Photos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/20230809083422_Add-Comments.cs b/Server/Migrations/20230809083422_Add-Comments.cs new file mode 100644 index 0000000..b3148c4 --- /dev/null +++ b/Server/Migrations/20230809083422_Add-Comments.cs @@ -0,0 +1,94 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + /// + public partial class AddComments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "comments", + table: "records", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "favs", + table: "records", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "likes", + table: "records", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateTable( + name: "comments", + columns: table => new + { + cid = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + uid = table.Column(type: "INTEGER", nullable: false), + date = table.Column(type: "INTEGER", nullable: false), + rid = table.Column(type: "INTEGER", nullable: false), + category = table.Column(type: "INTEGER", nullable: true), + byuid = table.Column(type: "INTEGER", nullable: true), + byname = table.Column(type: "TEXT", nullable: true), + text = table.Column(type: "TEXT", nullable: true), + latitude = table.Column(type: "REAL", nullable: true), + longitude = table.Column(type: "REAL", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_comments", x => x.cid); + table.ForeignKey( + name: "FK_comments_records_rid", + column: x => x.rid, + principalTable: "records", + principalColumn: "rid", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_comments_users_uid", + column: x => x.uid, + principalTable: "users", + principalColumn: "uid", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_comments_rid", + table: "comments", + column: "rid"); + + migrationBuilder.CreateIndex( + name: "IX_comments_uid", + table: "comments", + column: "uid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "comments"); + + migrationBuilder.DropColumn( + name: "comments", + table: "records"); + + migrationBuilder.DropColumn( + name: "favs", + table: "records"); + + migrationBuilder.DropColumn( + name: "likes", + table: "records"); + } + } +} diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs index 678e7ae..18c943d 100644 --- a/Server/Migrations/FlowerDatabaseModelSnapshot.cs +++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs @@ -17,6 +17,59 @@ namespace Blahblah.FlowerStory.Server.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "7.0.9"); + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.CommentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("cid"); + + b.Property("ByUserId") + .HasColumnType("INTEGER") + .HasColumnName("byuid"); + + b.Property("ByUserName") + .HasColumnType("TEXT") + .HasColumnName("byname"); + + b.Property("CommentCategoryId") + .HasColumnType("INTEGER") + .HasColumnName("category"); + + b.Property("DateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("date") + .HasAnnotation("Relational:JsonPropertyName", "date"); + + b.Property("Latitude") + .HasColumnType("REAL") + .HasColumnName("latitude"); + + b.Property("Longitude") + .HasColumnType("REAL") + .HasColumnName("longitude"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("RecordId") + .HasColumnType("INTEGER") + .HasColumnName("rid"); + + b.Property("Text") + .HasColumnType("TEXT") + .HasColumnName("text"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("RecordId"); + + b.ToTable("comments"); + }); + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b => { b.Property("Id") @@ -142,6 +195,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnType("TEXT") .HasColumnName("byname"); + b.Property("CommentCount") + .HasColumnType("INTEGER") + .HasColumnName("comments"); + b.Property("DateUnixTime") .HasColumnType("INTEGER") .HasColumnName("date") @@ -151,6 +208,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnType("INTEGER") .HasColumnName("eid"); + b.Property("FavoriteCount") + .HasColumnType("INTEGER") + .HasColumnName("favs"); + b.Property("FlowerId") .HasColumnType("INTEGER") .HasColumnName("fid"); @@ -163,6 +224,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnType("REAL") .HasColumnName("latitude"); + b.Property("LikeCount") + .HasColumnType("INTEGER") + .HasColumnName("likes"); + b.Property("Longitude") .HasColumnType("REAL") .HasColumnName("longitude"); @@ -290,6 +355,25 @@ namespace Blahblah.FlowerStory.Server.Migrations b.ToTable("users"); }); + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.CommentItem", b => + { + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blahblah.FlowerStory.Server.Data.Model.RecordItem", "Record") + .WithMany() + .HasForeignKey("RecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Record"); + }); + modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b => { b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner") diff --git a/Server/Program.cs b/Server/Program.cs index 6d69172..86247e2 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -11,7 +11,7 @@ public class Program /// public const string ProjectName = "Flower Story"; /// - public const string Version = "1.1.808"; + public const string Version = "1.2.809"; /// public static string DataPath => #if DEBUG