From 3b5bd291f906e0037e472386e1c18f53bc1328ae Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Thu, 25 May 2023 14:41:03 +0800 Subject: [PATCH] complete controllers --- Server/Controller/BaseController.cs | 79 ++++++- Server/Controller/BaseController.sqlite.cs | 75 ++++++ Server/Controller/EventController.cs | 3 +- Server/Controller/FlowerController.cs | 22 +- Server/Controller/ImageController.cs | 55 +++++ Server/Controller/UserController.cs | 154 +++++++++++-- Server/Data/Model/FlowerItem.cs | 6 + Server/Data/Model/RecordItem.cs | 6 + Server/Data/Model/UserItem.cs | 7 + .../20230525004719_AddAvatarMemo.Designer.cs | 214 ++++++++++++++++++ .../20230525004719_AddAvatarMemo.cs | 48 ++++ .../Migrations/FlowerDatabaseModelSnapshot.cs | 12 + Server/Program.cs | 39 ++-- 13 files changed, 664 insertions(+), 56 deletions(-) create mode 100644 Server/Controller/BaseController.sqlite.cs create mode 100644 Server/Controller/ImageController.cs create mode 100644 Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs create mode 100644 Server/Migrations/20230525004719_AddAvatarMemo.cs diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs index 3825fe6..a9a4f61 100644 --- a/Server/Controller/BaseController.cs +++ b/Server/Controller/BaseController.cs @@ -1,6 +1,8 @@ using Blahblah.FlowerStory.Server.Data; using Blahblah.FlowerStory.Server.Data.Model; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; using System.Security.Cryptography; using System.Text; @@ -9,10 +11,23 @@ namespace Blahblah.FlowerStory.Server.Controller; /// /// 基础服务抽象类 /// -public abstract class BaseController : ControllerBase +public abstract partial class BaseController : ControllerBase { private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden."; + /// + /// 支持的图片文件签名 + /// + protected static readonly List PhotoSignatures = new() + { + // jpeg + new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 }, + // png + new byte[] { 0x89, 0x50, 0x4E, 0x47 } + }; + /// /// 自定义认证头的关键字 /// @@ -114,7 +129,7 @@ public abstract class BaseController : ControllerBase logger?.LogWarning("token \"{hash}\" has expired after {date}", token.Id, token.ExpireDate); return (Unauthorized(), null); } - var user = database.Users.Find(token.UserId); + var user = QueryUserItem(token.UserId); if (user == null) { logger?.LogWarning("user not found with id {id}", token.UserId); @@ -133,10 +148,8 @@ public abstract class BaseController : ControllerBase { token.ExpireDateUnixTime = expires; } - database.Tokens.Update(token); user.ActiveDateUnixTime = now.ToUnixTimeMilliseconds(); - database.Users.Update(user); } return (null, user); @@ -159,4 +172,62 @@ public abstract class BaseController : ControllerBase } return count; } + + /// + /// 读取文件到 byte 数组 + /// + /// 来自请求的文件 + /// + protected FileResult? WrapFormFile(IFormFile file) + { + if (file == null) + { + return null; + } + using var stream = file.OpenReadStream(); + + // check header + var headers = new byte[PhotoSignatures.Max(s => s.Length)]; + int count = stream.Read(headers, 0, headers.Length); + if (PhotoSignatures.Any(s => headers.Take(s.Length).SequenceEqual(s))) + { + using var ms = new MemoryStream(); + ms.Write(headers, 0, count); + + // reading + const int size = 16384; + var buffer = new byte[size]; + while ((count = stream.Read(buffer, 0, size)) > 0) + { + ms.Write(buffer, 0, count); + } + var data = ms.ToArray(); + var name = file.FileName; + name = $"{Path.GetFileNameWithoutExtension(name)}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.{Path.GetExtension(name)}"; + + return new FileResult + { + Filename = name, + Content = data + }; + } + return null; + } +} + +/// +/// 文件结果 +/// +public record FileResult +{ + /// + /// 文件名 + /// + public string? Filename { get; init; } + + /// + /// 文件内容 + /// + [Required] + public required byte[] Content { get; init; } } diff --git a/Server/Controller/BaseController.sqlite.cs b/Server/Controller/BaseController.sqlite.cs new file mode 100644 index 0000000..3ea3b3c --- /dev/null +++ b/Server/Controller/BaseController.sqlite.cs @@ -0,0 +1,75 @@ +using Blahblah.FlowerStory.Server.Data.Model; +using Microsoft.EntityFrameworkCore; + +namespace Blahblah.FlowerStory.Server.Controller; + +partial class BaseController +{ + + /// + /// 根据 uid 获取用户对象 + /// + /// 用户唯一 id + /// + protected UserItem? QueryUserItem(int uid) + { + return database.Users + .FromSql($"SELECT \"uid\",\"id\",\"\" AS \"password\",\"level\",\"regdate\",\"activedate\",\"name\",\"email\",\"mobile\",NULL AS \"avatar\" FROM [users] WHERE \"uid\" = {uid} LIMIT 1") + .SingleOrDefault(); + } + + /// + /// 根据 id 获取登录使用的用户对象 + /// + /// 用户 id + /// + 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") + .SingleOrDefault(); + } + + /// + /// 获取用户头像数据 + /// + /// 用户唯一 id + /// + protected byte[]? QueryUserAvatar(int uid) + { + return database.Database.SqlQuery($"SELECT \"avatar\" AS \"Value\" FROM \"users\" WHERE \"uid\" = {uid} LIMIT 1").SingleOrDefault(); + } + + /// + /// 移除用户头像 + /// + /// 用户唯一 id + /// + protected int RemoveUserAvatar(int uid) + { + return database.Database.ExecuteSql($"UPDATE \"users\" SET \"avatar\" = NULL WHERE \"uid\" = {uid}"); + } + + /// + /// 移除用户名下的花草 + /// + /// 用户唯一 id + /// 花草唯一 id + /// + protected int RemoveUserFlower(int uid, int fid) + { + return database.Database.ExecuteSql($"DELETE FROM \"flowers\" WHERE \"uid\" = {uid} AND \"fid\" = {fid}"); + } + + /// + /// 批量移除用户名下的花草 + /// + /// 用户唯一 id + /// 花草唯一 id 的数组 + /// + protected int RemoveUserFlowers(int uid, int[] fids) + { + var idfilter = string.Join(", ", fids); + return database.Database.ExecuteSql($"DELETE FROM \"flowers\" WHERE \"uid\" = {uid} AND \"fid\" IN ({idfilter})"); + } +} diff --git a/Server/Controller/EventController.cs b/Server/Controller/EventController.cs index e80ddb5..a8ebc52 100644 --- a/Server/Controller/EventController.cs +++ b/Server/Controller/EventController.cs @@ -8,7 +8,6 @@ namespace Blahblah.FlowerStory.Server.Controller; /// 事件相关服务 /// [ApiController] -[Consumes("application/json")] [Produces("application/json")] [Route("api/event")] public class EventController : BaseController @@ -46,7 +45,7 @@ public class EventController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("query")] + [Route("query", Name = "queryEvents")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] diff --git a/Server/Controller/FlowerController.cs b/Server/Controller/FlowerController.cs index 9484808..507c632 100644 --- a/Server/Controller/FlowerController.cs +++ b/Server/Controller/FlowerController.cs @@ -10,7 +10,6 @@ namespace Blahblah.FlowerStory.Server.Controller; /// 花草相关服务 /// [ApiController] -[Consumes("application/json")] [Produces("application/json")] [Route("api/flower")] public class FlowerController : BaseController @@ -50,7 +49,7 @@ public class FlowerController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("query")] + [Route("query", Name = "queryFlowers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -128,7 +127,7 @@ public class FlowerController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("remove")] + [Route("remove", Name = "removeFlower")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -146,7 +145,7 @@ public class FlowerController : BaseController return NotFound(); } - var count = database.Database.ExecuteSql($"DELETE FROM [flowers] WHERE \"uid\" = {user.Id} AND \"fid\" = {id}"); + var count = RemoveUserFlower(user.Id, id); SaveDatabase(); @@ -172,12 +171,13 @@ public class FlowerController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("removeany")] + [Route("removeany", Name = "removeFlowers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [HttpPost] + [Consumes("application/json")] public ActionResult RemoveFlower([FromBody] int[] ids) { var (result, user) = CheckPermission(); @@ -190,8 +190,7 @@ public class FlowerController : BaseController return NotFound(); } - var idfilter = string.Join(", ", ids); - var count = database.Database.ExecuteSql($"DELETE FROM [flowers] WHERE \"uid\" = {user.Id} AND \"fid\" IN ({idfilter})"); + var count = RemoveUserFlowers(user.Id, ids); SaveDatabase(); @@ -221,12 +220,13 @@ public class FlowerController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("add")] + [Route("add", Name = "addFlower")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [HttpPost] + [Consumes("application/json")] public ActionResult AddFlower([FromBody] FlowerParameter flower) { var (result, user) = CheckPermission(); @@ -278,12 +278,13 @@ public class FlowerController : BaseController /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户或者未找到将修改的花草对象 - [Route("update")] + [Route("update", Name = "updateFlower")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [HttpPut] + [Consumes("application/json")] public ActionResult Update([FromBody] FlowerUpdateParameter update) { var (result, user) = CheckPermission(); @@ -296,7 +297,7 @@ public class FlowerController : BaseController return NotFound(); } - var flower = database.Flowers.FirstOrDefault(f => f.Id == update.Id && f.OwnerId == user.Id); + var flower = database.Flowers.SingleOrDefault(f => f.Id == update.Id && f.OwnerId == user.Id); if (flower == null) { return NotFound(update.Id); @@ -306,7 +307,6 @@ public class FlowerController : BaseController flower.DateBuyUnixTime = update.DateBuy; flower.Cost = update.Cost; flower.Purchase = update.Purchase; - database.Flowers.Update(flower); SaveDatabase(); return Ok(user); diff --git a/Server/Controller/ImageController.cs b/Server/Controller/ImageController.cs new file mode 100644 index 0000000..3d1a815 --- /dev/null +++ b/Server/Controller/ImageController.cs @@ -0,0 +1,55 @@ +using Blahblah.FlowerStory.Server.Data; +using Microsoft.AspNetCore.Mvc; + +namespace Blahblah.FlowerStory.Server.Controller; + +/// +/// 图片相关服务 +/// +[Produces("image/png")] +[Route("photo")] +public class ImageController : BaseController +{ + /// + public ImageController(FlowerDatabase database, ILogger? logger = null) : base(database, logger) + { + } + + /// + /// 请求用户头像 + /// + /// + /// 请求示例: + /// + /// GET /photo/avatar + /// Authorization: authorization id + /// + /// + /// 认证通过则显示用户头像 + /// 返回头像 + /// 认证失败 + /// 未找到头像 + [Route("avatar", Name = "getAvatar")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [HttpGet] + public ActionResult GetUserAvatar() + { + var (result, token) = CheckToken(); + if (result != null) + { + return result; + } + if (token == null) + { + return Unauthorized(); + } + var avatar = QueryUserAvatar(token.UserId); + if (avatar == null) + { + return NotFound(); + } + return File(avatar, "image/png"); + } +} diff --git a/Server/Controller/UserController.cs b/Server/Controller/UserController.cs index e5ed4e7..0d490a3 100644 --- a/Server/Controller/UserController.cs +++ b/Server/Controller/UserController.cs @@ -9,7 +9,6 @@ namespace Blahblah.FlowerStory.Server.Controller /// 用户会话相关服务 /// [ApiController] - [Consumes("application/json")] [Produces("application/json")] [Route("api/user")] public partial class UserController : BaseController @@ -37,17 +36,19 @@ namespace Blahblah.FlowerStory.Server.Controller /// 返回自定义认证头 /// 认证失败 /// 未找到用户 - [Route("auth")] + [Route("auth", Name = "authenticate")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [HttpPost] + [Consumes("application/json")] public ActionResult Authenticate([FromBody] LoginParamter login) { #if DEBUG logger?.LogInformation("user \"{user}\" try to login with password \"{password}\"", login.Id, login.Password); #endif - var user = database.Users.FirstOrDefault(u => u.UserId == login.Id); + //var user = database.Users.SingleOrDefault(u => u.UserId == login.Id); + var user = QueryUserItemForAuthentication(login.Id); if (user == null) { logger?.LogWarning("user \"{user}\" not found", login.Id); @@ -63,8 +64,19 @@ namespace Blahblah.FlowerStory.Server.Controller } // record the session - // TODO: singleton token, mobile - var expires = 1200; + // TODO: singleton token + string clientApp; + int expires; + if (Request.Headers.TryGetValue("X-ClientAgent", out var clientAgent)) + { + clientApp = "app"; + expires = 30 * 24 * 60 * 60; // 30 days + } + else + { + clientApp = "browser"; + expires = 20 * 60; // 20 mins + } var now = DateTimeOffset.UtcNow; var token = new TokenItem { @@ -74,13 +86,13 @@ namespace Blahblah.FlowerStory.Server.Controller ActiveDateUnixTime = now.ToUnixTimeMilliseconds(), ExpireDateUnixTime = now.AddSeconds(expires).ToUnixTimeMilliseconds(), ExpireSeconds = expires, - ClientApp = "browser", // TODO: support app later - ClientAgent = Request.Headers.UserAgent + ClientApp = clientApp, + ClientAgent = clientAgent.Count > 0 ? clientAgent : Request.Headers.UserAgent, + DeviceId = Request.Headers.TryGetValue("X-DeviceId", out var deviceId) ? deviceId.ToString() : null }; database.Tokens.Add(token); user.ActiveDateUnixTime = token.ActiveDateUnixTime; - database.Users.Update(user); SaveDatabase(); Response.Headers.Add(AuthHeader, token.Id); @@ -100,7 +112,7 @@ namespace Blahblah.FlowerStory.Server.Controller /// 注销失败则返回错误内容 /// 注销成功 /// 认证失败 - [Route("logout")] + [Route("logout", Name = "logout")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [HttpPost] @@ -142,17 +154,17 @@ namespace Blahblah.FlowerStory.Server.Controller /// 成功注册则返回已注册的用户对象 /// 返回已注册的用户对象 /// 用户重复或其他服务器错误 - [Route("register")] + [Route("register", Name = "register")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [HttpPost] + [Consumes("application/json")] public ActionResult Register([FromBody] UserParameter user) { #if DEBUG logger?.LogInformation("user register, {user}", user); #endif - var u = database.Users.FirstOrDefault(u => u.UserId == user.Id); - if (u != null) + if (database.Users.Any(u => u.UserId == user.Id)) { logger?.LogWarning("duplicate user \"{id}\"", user.Id); return Problem("duplicateUser", "user/register", 500); @@ -191,7 +203,7 @@ namespace Blahblah.FlowerStory.Server.Controller /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("profile")] + [Route("profile", Name = "getProfile")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -236,12 +248,15 @@ namespace Blahblah.FlowerStory.Server.Controller /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户 - [Route("update")] + /// 提交正文过大 + [Route("update", Name = "updateProfile")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)] [HttpPut] + [Consumes("application/json")] public ActionResult Update([FromBody] UpdateParameter update) { #if DEBUG @@ -257,23 +272,120 @@ namespace Blahblah.FlowerStory.Server.Controller return NotFound(); } - //var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - //user.ActiveDateUnixTime = now; user.Name = update.UserName; user.Email = update.Email; user.Mobile = update.Mobile; - database.Users.Update(user); SaveDatabase(); return Ok(user); } -//#if DEBUG + /// + /// 修改用户头像 + /// + /// + /// 请求示例: + /// + /// PUT /api/user/update_avatar + /// Authorization: authorization id + /// + /// 参数: + /// + /// avatar: IFormFile? + /// + /// + /// 用户头像 + /// 修改成功则返回 HTTP 204 + /// 修改成功 + /// 头像内容格式非法 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户 + /// 提交正文过大 + [Route("update_avatar", Name = "updateAvatar")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)] + [HttpPut] + [Consumes("multipart/form-data")] + [RequestSizeLimit(5 * 1024 * 1024)] + public ActionResult UploadAvatar(IFormFile? avatar) + { +#if DEBUG + logger?.LogInformation("user update avatar, {avatar}", avatar); +#endif + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + if (avatar?.Length > 0) + { + var file = WrapFormFile(avatar); + if (file == null) + { + return BadRequest(); + } + user.Avatar = file.Content; + } + SaveDatabase(); + + return Ok(user); + } + + /// + /// 移除用户头像 + /// + /// + /// 请求示例: + /// + /// DELETE /api/user/remove_avatar + /// Authorization: authorization id + /// + /// + /// 移除成功则返回修改的数据库行数 + /// 修改的数据库行数 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户 + [Route("remove_avatar", Name = "removeAvatar")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [HttpDelete] + public ActionResult RemoveAvatar() + { + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + var count = RemoveUserAvatar(user.Id); + SaveDatabase(); + + return Ok(count); + } + + //#if DEBUG /// /// #DEBUG 获取所有用户 /// /// - [Route("debug_list")] + [Route("debug_list", Name = "debug_list")] [HttpGet] public ActionResult GetUsers() { @@ -284,12 +396,12 @@ namespace Blahblah.FlowerStory.Server.Controller /// #DEBUG 获取所有 token /// /// - [Route("debug_tokens")] + [Route("debug_tokens", Name = "debug_tokens")] [HttpGet] public ActionResult GetTokens() { return Ok(database.Tokens.ToArray()); } -//#endif + //#endif } } diff --git a/Server/Data/Model/FlowerItem.cs b/Server/Data/Model/FlowerItem.cs index 2290e09..80d9e65 100644 --- a/Server/Data/Model/FlowerItem.cs +++ b/Server/Data/Model/FlowerItem.cs @@ -65,6 +65,12 @@ public class FlowerItem [Column("photo")] public byte[]? Photo { get; set; } + /// + /// 备注 + /// + [Column("memo")] + public string? Memo { get; set; } + /// /// 购买时间 /// diff --git a/Server/Data/Model/RecordItem.cs b/Server/Data/Model/RecordItem.cs index 65e556b..654f659 100644 --- a/Server/Data/Model/RecordItem.cs +++ b/Server/Data/Model/RecordItem.cs @@ -58,6 +58,12 @@ public class RecordItem [Column("photo")] public byte[]? Photo { get; set; } + /// + /// 备注 + /// + [Column("memo")] + public string? Memo { get; set; } + /// /// 操作时间 /// diff --git a/Server/Data/Model/UserItem.cs b/Server/Data/Model/UserItem.cs index 440a556..5377286 100644 --- a/Server/Data/Model/UserItem.cs +++ b/Server/Data/Model/UserItem.cs @@ -77,6 +77,13 @@ public class UserItem [Column("mobile")] public string? Mobile { get; set; } + /// + /// 用户头像 + /// + [Column("avatar")] + [JsonIgnore] + public byte[]? Avatar { get; set; } + /// /// 注册时间 /// diff --git a/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs b/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs new file mode 100644 index 0000000..9f36f2b --- /dev/null +++ b/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs @@ -0,0 +1,214 @@ +// +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("20230525004719_AddAvatarMemo")] + partial class AddAvatarMemo + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.5"); + + 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("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("Photo") + .HasColumnType("BLOB") + .HasColumnName("photo"); + + b.Property("Purchase") + .HasColumnType("TEXT") + .HasColumnName("purchase"); + + b.HasKey("Id"); + + b.ToTable("flowers"); + }); + + 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("DateUnixTime") + .HasColumnType("INTEGER") + .HasColumnName("date") + .HasAnnotation("Relational:JsonPropertyName", "date"); + + b.Property("EventId") + .HasColumnType("INTEGER") + .HasColumnName("eid"); + + b.Property("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("Photo") + .HasColumnType("BLOB") + .HasColumnName("photo"); + + b.HasKey("Id"); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/20230525004719_AddAvatarMemo.cs b/Server/Migrations/20230525004719_AddAvatarMemo.cs new file mode 100644 index 0000000..ab51165 --- /dev/null +++ b/Server/Migrations/20230525004719_AddAvatarMemo.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + /// + public partial class AddAvatarMemo : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "avatar", + table: "users", + type: "BLOB", + nullable: true); + + migrationBuilder.AddColumn( + name: "memo", + table: "records", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "memo", + table: "flowers", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "avatar", + table: "users"); + + migrationBuilder.DropColumn( + name: "memo", + table: "records"); + + migrationBuilder.DropColumn( + name: "memo", + table: "flowers"); + } + } +} diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs index beaed6f..a736f1b 100644 --- a/Server/Migrations/FlowerDatabaseModelSnapshot.cs +++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs @@ -37,6 +37,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnName("datebuy") .HasAnnotation("Relational:JsonPropertyName", "dateBuy"); + b.Property("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT") @@ -83,6 +87,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnType("INTEGER") .HasColumnName("eid"); + b.Property("Memo") + .HasColumnType("TEXT") + .HasColumnName("memo"); + b.Property("OwnerId") .HasColumnType("INTEGER") .HasColumnName("uid"); @@ -157,6 +165,10 @@ namespace Blahblah.FlowerStory.Server.Migrations .HasColumnType("INTEGER") .HasColumnName("activedate"); + b.Property("Avatar") + .HasColumnType("BLOB") + .HasColumnName("avatar"); + b.Property("Email") .HasColumnType("TEXT") .HasColumnName("email"); diff --git a/Server/Program.cs b/Server/Program.cs index 371f02c..8e02d63 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -25,7 +25,7 @@ public class Program builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { - //options.OperationFilter(); + options.OperationFilter(); var scheme = new OpenApiSecurityScheme { @@ -53,7 +53,7 @@ public class Program Description = "

¼¼ĵݼ԰еĵεΡ

API ĵ

" }); - options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml")); + options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"), true); }); builder.Services.AddDbContext(options => options.UseSqlite("DataSource=flower.db;Cache=Shared")); @@ -84,23 +84,26 @@ public class SwaggerHttpHeaderOperation : IOperationFilter /// public void Apply(OpenApiOperation operation, OperationFilterContext context) { - var required = context.ApiDescription.RelativePath switch + switch (context.ApiDescription.RelativePath) { - "user/update" or - "user/profile" or - "user/logout" => true, - _ => false - }; - if (required) - { - operation.Parameters.Add(new OpenApiParameter - { - Name = "Authorization", - Description = "Ȩ Token", - In = ParameterLocation.Header, - Required = true, - Schema = new OpenApiSchema { Type = "string" } - }); + case "api/user/auth": + operation.Parameters.Add(new OpenApiParameter + { + Name = "X-ClientAgent", + Description = "ͻ˴", + In = ParameterLocation.Header, + Required = false, + Schema = new OpenApiSchema { Type = "string" } + }); + operation.Parameters.Add(new OpenApiParameter + { + Name = "X-DeviceId", + Description = "豸 id", + In = ParameterLocation.Header, + Required = false, + Schema = new OpenApiSchema { Type = "string" } + }); + break; } } } \ No newline at end of file