diff --git a/Doc/README.md b/Doc/README.md
new file mode 100644
index 0000000..9f1423e
--- /dev/null
+++ b/Doc/README.md
@@ -0,0 +1,3 @@
+# Flower Story
+
+花事记录,贴心的帮您记录花园中的点点滴滴。
\ No newline at end of file
diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs
new file mode 100644
index 0000000..abebc0b
--- /dev/null
+++ b/Server/Controller/BaseController.cs
@@ -0,0 +1,135 @@
+using Blahblah.FlowerStory.Server.Data;
+using Blahblah.FlowerStory.Server.Data.Model;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Blahblah.FlowerStory.Server.Controller;
+
+///
+/// 基础服务抽象类
+///
+public abstract class BaseController : ControllerBase
+{
+ private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden.";
+
+ ///
+ /// 自定义认证头的关键字
+ ///
+ protected const string AuthHeader = "X-Auth";
+ ///
+ /// 禁用用户
+ ///
+ protected const int UserDisabled = -1;
+ ///
+ /// 普通用户
+ ///
+ protected const int UserCommon = 0;
+ ///
+ /// 管理员用户
+ ///
+ protected const int UserAdmin = 99;
+
+ ///
+ /// 数据库对象
+ ///
+ protected readonly FlowerDatabase database;
+ ///
+ /// 日志对象
+ ///
+ protected readonly ILogger? logger;
+
+ ///
+ /// 构造基础服务类
+ ///
+ /// 数据库对象
+ /// 日志对象
+ protected BaseController(FlowerDatabase database, ILogger? logger = null)
+ {
+ this.database = database;
+ this.logger = logger;
+ }
+
+ ///
+ /// 计算密码的 hash
+ ///
+ /// 密码原文
+ /// 用户 id
+ /// 密码 hash,值为 SHA256(password+id+salt)
+ ///
+ protected string HashPassword(string password, string id)
+ {
+ if (string.IsNullOrEmpty(password))
+ {
+ throw new ArgumentNullException(nameof(password));
+ }
+ if (string.IsNullOrEmpty(id))
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+ var data = Encoding.UTF8.GetBytes($"{password}{id}{Salt}");
+ var hash = SHA256.HashData(data);
+ return Convert.ToHexString(hash);
+ }
+
+ ///
+ /// 检查当前会话权限
+ ///
+ /// 需要大于等于该权限,默认为 0 - UserCommon
+ /// 若检查失败,第一个参数返回异常 ActionResult。第二个参数返回会话对应的用户对象
+ protected (ActionResult? Result, UserItem? User) CheckPermission(int? level = 0)
+ {
+ if (!Request.Headers.TryGetValue(AuthHeader, out var h))
+ {
+ logger?.LogWarning("request with no {auth} header", AuthHeader);
+ return (BadRequest(), null);
+ }
+ string hash = h.ToString();
+ var token = database.Tokens.Find(hash);
+ if (token == null)
+ {
+ logger?.LogWarning("token \"{hash}\" not found", hash);
+ return (Unauthorized(), null);
+ }
+ if (token.ExpireDate < DateTimeOffset.UtcNow)
+ {
+ logger?.LogWarning("token \"{hash}\" has expired after {date}", hash, token.ExpireDate);
+ return (Unauthorized(), null);
+ }
+ var user = database.Users.Find(token.UserId);
+ if (user == null)
+ {
+ logger?.LogWarning("user not found with id {id}", token.UserId);
+ return (NotFound(), null);
+ }
+ if (user.Level < level)
+ {
+ logger?.LogWarning("user \"{id}\" level ({level}) lower than required ({required})", user.UserId, user.Level, level);
+ return (Forbid(), user);
+ }
+
+ token.ActiveDateUnixTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ database.Tokens.Update(token);
+
+ return (null, user);
+ }
+
+ ///
+ /// 保存数据库变动并输出日志
+ ///
+ ///
+ protected int SaveDatabase()
+ {
+ var count = database.SaveChanges();
+ if (count > 0)
+ {
+ logger?.LogInformation("{number} of entries written to database.", count);
+ }
+ else
+ {
+ logger?.LogWarning("no data written to database.");
+ }
+ return count;
+ }
+}
diff --git a/Server/Controller/UserController.cs b/Server/Controller/UserController.cs
index a33b71a..2b8451a 100644
--- a/Server/Controller/UserController.cs
+++ b/Server/Controller/UserController.cs
@@ -4,57 +4,224 @@ using Microsoft.AspNetCore.Mvc;
namespace Blahblah.FlowerStory.Server.Controller
{
+ ///
+ /// 用户会话相关服务
+ ///
[ApiController]
+ [Produces("application/json")]
[Route("users")]
- public class UserController : ControllerBase
+ public partial class UserController : BaseController
{
- private readonly FlowerDatabase database;
- private readonly ILogger logger;
-
- public UserController(FlowerDatabase db, ILogger logger)
+ ///
+ /// 构造用户会话服务
+ ///
+ /// 数据库对象
+ /// 日志对象
+ public UserController(FlowerDatabase db, ILogger logger) : base(db, logger)
{
- database = db;
- this.logger = logger;
}
+ ///
+ /// 用户登录
+ ///
+ ///
+ /// 提交示例:
+ ///
+ /// POST /users/auth
+ /// {
+ /// "id": "blahblah",
+ /// "password": "pwd123"
+ /// }
+ ///
+ ///
+ /// 登录参数
+ /// 成功登录则返回自定义认证头
+ /// 返回自定义认证头
+ /// 认证失败
+ /// 未找到用户
+ [Route("auth")]
+ [Consumes("application/json")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpPost]
+ 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);
+ if (user == null)
+ {
+ logger?.LogWarning("user \"{user}\" not found", login.Id);
+ return NotFound();
+ }
+
+ // comput password hash with salt
+ string hash = HashPassword(login.Password, login.Id);
+ if (hash != user.Password)
+ {
+ logger?.LogWarning("hash result {hash}, unauthorized with hash {password}", hash, user.Password);
+ return Unauthorized();
+ }
+
+ // record the session
+ // TODO: singleton token
+ var now = DateTimeOffset.UtcNow;
+ var expires = 1200; // 20 minutes
+ var token = new TokenItem
+ {
+ Id = Guid.NewGuid().ToString("N"),
+ UserId = user.Id,
+ LogonDateUnixTime = now.ToUnixTimeMilliseconds(),
+ ActiveDateUnixTime = now.ToUnixTimeMilliseconds(),
+ ExpireDateUnixTime = now.AddSeconds(expires).ToUnixTimeMilliseconds(),
+ ExpireSeconds = expires,
+ ClientApp = "browser", // TODO: support app later
+ ClientAgent = Request.Headers.UserAgent
+ };
+ database.Tokens.Add(token);
+
+ user.ActiveDateUnixTime = token.ActiveDateUnixTime;
+ database.Users.Update(user);
+ SaveDatabase();
+
+ Response.Headers.Add(AuthHeader, token.Id);
+ return Ok();
+ }
+
+ ///
+ /// 注册用户
+ ///
+ ///
+ /// 提交示例:
+ ///
+ /// POST /users/register
+ /// {
+ /// "id": "blahblah",
+ /// "password": "pwd123",
+ /// "userName": "Blah blah",
+ /// "email": "blah@example.com",
+ /// "mobile": "18012345678"
+ /// }
+ ///
+ ///
+ /// 注册参数
+ /// 成功注册则返回已注册的用户对象
+ /// 返回已注册的用户对象
+ /// 用户重复或其他服务器错误
+ [Route("register")]
+ [Consumes("application/json")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status500InternalServerError)]
+ [HttpPost]
+ 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)
+ {
+ logger?.LogWarning("duplicate user \"{id}\"", user.Id);
+ return Problem("duplicateUser", "users/register", 500);
+ }
+
+ var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ var item = new UserItem
+ {
+ UserId = user.Id,
+ Password = HashPassword(user.Password, user.Id),
+ Level = UserCommon,
+ RegisterDateUnixTime = now,
+ ActiveDateUnixTime = now,
+ Name = user.UserName,
+ Email = user.Email,
+ Mobile = user.Mobile
+ };
+ database.Users.Add(item);
+ SaveDatabase();
+
+ return Ok(item);
+ }
+
+ ///
+ /// 修改用户
+ ///
+ ///
+ /// 提交示例:
+ ///
+ /// POST /users/update
+ /// {
+ /// "userName": "Blah blah",
+ /// "email": "blah@example.com",
+ /// "mobile": "18012345678"
+ /// }
+ ///
+ ///
+ /// 修改参数
+ /// 修改成功则返回已修改的用户对象
+ /// 返回已修改的用户对象
+ /// 认证头未找到
+ /// 服务器未找到登录会话
+ /// 用户权限不足
+ /// 未找到关联用户
+ [Route("update")]
+ [Consumes("application/json")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpPost]
+ public ActionResult Update([FromBody] UpdateParameter update)
+ {
+#if DEBUG
+ logger?.LogInformation("user update, {user}", update);
+#endif
+ var (result, user) = CheckPermission();
+ if (result != null)
+ {
+ return result;
+ }
+ if (user == null)
+ {
+ 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
+ ///
+ /// 获取所有用户
+ ///
+ ///
[Route("query")]
[HttpGet]
public ActionResult GetUsers()
{
- //var forecast = Enumerable.Range(1, 5).Select(index =>
- // new WeatherForecast
- // {
- // Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- // TemperatureC = Random.Shared.Next(-20, 55),
- // Summary = summaries[Random.Shared.Next(summaries.Length)]
- // })
- // .ToArray();
- //return Ok(forecast);
return Ok(database.Users.ToArray());
}
- [Route("update")]
- [HttpPost]
- public ActionResult UpdateUser([FromBody] UserItem item)
+ ///
+ /// 获取所有 token
+ ///
+ ///
+ [Route("tokens")]
+ [HttpGet]
+ public ActionResult GetTokens()
{
- if (item.Id > 0)
- {
- database.Update(item);
- }
- else
- {
- database.Add(item);
- }
- var count = database.SaveChanges();
- if (count > 0)
- {
- logger.LogInformation("{number} of entries written to database.", count);
- }
- else
- {
- logger.LogWarning("no data written to database.");
- }
- return Ok(item.Id);
+ return Ok(database.Tokens.ToArray());
}
+//#endif
}
}
diff --git a/Server/Controller/UserController.structs.cs b/Server/Controller/UserController.structs.cs
new file mode 100644
index 0000000..9dc5751
--- /dev/null
+++ b/Server/Controller/UserController.structs.cs
@@ -0,0 +1,30 @@
+namespace Blahblah.FlowerStory.Server.Controller;
+
+partial class UserController
+{
+}
+
+///
+/// 登录参数
+///
+/// 用户 id
+/// 密码
+public record LoginParamter(string Id, string Password);
+
+///
+/// 用户注册参数
+///
+/// 用户 id
+/// 密码
+/// 用户名
+/// 邮箱
+/// 联系电话
+public record UserParameter(string Id, string Password, string UserName, string? Email, string? Mobile) : UpdateParameter(UserName, Email, Mobile);
+
+///
+/// 用户修改参数
+///
+/// 用户名
+/// 邮箱
+/// 联系电话
+public record UpdateParameter(string UserName, string? Email, string? Mobile);
diff --git a/Server/Data/FlowerDatabase.cs b/Server/Data/FlowerDatabase.cs
index 370fd97..d707642 100644
--- a/Server/Data/FlowerDatabase.cs
+++ b/Server/Data/FlowerDatabase.cs
@@ -3,13 +3,37 @@ using Microsoft.EntityFrameworkCore;
namespace Blahblah.FlowerStory.Server.Data;
+///
+/// 数据库管理类
+///
public class FlowerDatabase : DbContext
{
- public FlowerDatabase(DbContextOptions options) : base(options) { }
+ ///
+ /// 构造数据库对象
+ ///
+ /// 选项参数
+ public FlowerDatabase(DbContextOptions options) : base(options)
+ {
+ //Database.Migrate();
+ }
+ ///
+ /// 用户集
+ ///
public DbSet Users { get; set; }
+ ///
+ /// 花草集
+ ///
public DbSet Flowers { get; set; }
+ ///
+ /// 记录集
+ ///
public DbSet Records { get; set; }
+
+ ///
+ /// 会话令牌集
+ ///
+ public DbSet Tokens { get; set; }
}
diff --git a/Server/Data/Model/FlowerItem.cs b/Server/Data/Model/FlowerItem.cs
index be85b4a..0232d7d 100644
--- a/Server/Data/Model/FlowerItem.cs
+++ b/Server/Data/Model/FlowerItem.cs
@@ -1,32 +1,67 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
namespace Blahblah.FlowerStory.Server.Data.Model;
+///
+/// 花草对象
+///
[Table("flowers")]
public class FlowerItem
{
- [Column("fid"), Key, Required]
+ ///
+ /// 自增 id,主键
+ ///
+ [Column("fid")]
+ [Key]
+ [Required]
public int Id { get; set; }
- [Column("categoryid"), Required]
+ ///
+ /// 类别 id
+ ///
+ [Column("categoryid")]
+ [Required]
public int CategoryId { get; set; }
- [Column("name"), Required]
+ ///
+ /// 花草名称
+ ///
+ [Column("name")]
+ [Required]
public required string Name { get; set; }
- [Column("datebuy"), Required]
+ ///
+ /// 购买时间
+ ///
+ [Column("datebuy")]
+ [Required]
+ [JsonPropertyName("dateBuy")]
public long DateBuyUnixTime { get; set; }
+ ///
+ /// 购买花费
+ ///
[Column("cost", TypeName = "real")]
public decimal? Cost { get; set; }
+ ///
+ /// 购买渠道
+ ///
[Column("purchase")]
public string? Purchase { get; set; }
+ ///
+ /// 购买照片
+ ///
[Column("photo")]
public byte[]? Photo { get; set; }
+ ///
+ /// 购买时间
+ ///
[NotMapped]
+ [JsonIgnore]
public DateTimeOffset DateBuy => DateTimeOffset.FromUnixTimeMilliseconds(DateBuyUnixTime);
}
diff --git a/Server/Data/Model/RecordItem.cs b/Server/Data/Model/RecordItem.cs
index bd9c42e..de8ddbb 100644
--- a/Server/Data/Model/RecordItem.cs
+++ b/Server/Data/Model/RecordItem.cs
@@ -1,29 +1,60 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
namespace Blahblah.FlowerStory.Server.Data.Model;
+///
+/// 记录对象
+///
[Table("records")]
public class RecordItem
{
- [Column("rid"), Key, Required]
+ ///
+ /// 自增 id,主键
+ ///
+ [Column("rid")]
+ [Key]
+ [Required]
public int Id { get; set; }
- [Column("eid"), Required]
+ ///
+ /// 事件类型
+ ///
+ [Column("eid")]
+ [Required]
public int EventId { get; set; }
- [Column("date"), Required]
+ ///
+ /// 操作时间
+ ///
+ [Column("date")]
+ [Required]
+ [JsonPropertyName("date")]
public long DateUnixTime { get; set; }
+ ///
+ /// 操作人 uid
+ ///
[Column("byuid")]
public int? ByUserId { get; set; }
+ ///
+ /// 操作人名称
+ ///
[Column("byname")]
public string? ByUserName { get; set; }
+ ///
+ /// 事件关联照片
+ ///
[Column("photo")]
public byte[]? Photo { get; set; }
+ ///
+ /// 操作时间
+ ///
[NotMapped]
+ [JsonIgnore]
public DateTimeOffset Date => DateTimeOffset.FromUnixTimeMilliseconds(DateUnixTime);
}
diff --git a/Server/Data/Model/TokenItem.cs b/Server/Data/Model/TokenItem.cs
new file mode 100644
index 0000000..dabe634
--- /dev/null
+++ b/Server/Data/Model/TokenItem.cs
@@ -0,0 +1,103 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
+
+namespace Blahblah.FlowerStory.Server.Data.Model;
+
+///
+/// 会话令牌对象
+///
+[Table("tokens")]
+public class TokenItem
+{
+ ///
+ /// token 唯一 id
+ ///
+ [Column("tid")]
+ [Key]
+ [Required]
+ public required string Id { get; set; }
+
+ ///
+ /// 关联用户 uid
+ ///
+ [Column("uid")]
+ [Required]
+ public int UserId { get; set; }
+
+ ///
+ /// 登录时间
+ ///
+ [Column("logondate")]
+ [Required]
+ [JsonPropertyName("logonDate")]
+ public long LogonDateUnixTime { get; set; }
+
+ ///
+ /// 活动时间
+ ///
+ [Column("activedate")]
+ [Required]
+ [JsonPropertyName("activeDate")]
+ public long ActiveDateUnixTime { get; set; }
+
+ ///
+ /// 过期时间
+ ///
+ [Column("expiredate")]
+ [Required]
+ [JsonPropertyName("expireDate")]
+ public long ExpireDateUnixTime { get; set; }
+
+ ///
+ /// 过期秒数
+ ///
+ [Column("expiresecs")]
+ [Required]
+ public int ExpireSeconds { get; set; }
+
+ ///
+ /// 验证码
+ ///
+ [Column("verifycode")]
+ public string? VerifyCode { get; set; }
+
+ ///
+ /// 客户端类型
+ ///
+ [Column("clientapp")]
+ public string? ClientApp { get; set; }
+
+ ///
+ /// 客户端设备 id
+ ///
+ [Column("deviceid")]
+ public string? DeviceId { get; set; }
+
+ ///
+ /// 客户端代理标识
+ ///
+ [Column("clientagent")]
+ public string? ClientAgent { get; set; }
+
+ ///
+ /// 登录时间
+ ///
+ [NotMapped]
+ [JsonIgnore]
+ public DateTimeOffset LogonDate => DateTimeOffset.FromUnixTimeMilliseconds(LogonDateUnixTime);
+
+ ///
+ /// 活动时间
+ ///
+ [NotMapped]
+ [JsonIgnore]
+ public DateTimeOffset ActiveDate => DateTimeOffset.FromUnixTimeMilliseconds(ActiveDateUnixTime);
+
+ ///
+ /// 过期时间
+ ///
+ [NotMapped]
+ [JsonIgnore]
+ public DateTimeOffset ExpireDate => DateTimeOffset.FromUnixTimeMilliseconds(ExpireDateUnixTime);
+}
diff --git a/Server/Data/Model/UserItem.cs b/Server/Data/Model/UserItem.cs
index 7835930..440a556 100644
--- a/Server/Data/Model/UserItem.cs
+++ b/Server/Data/Model/UserItem.cs
@@ -1,32 +1,93 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
namespace Blahblah.FlowerStory.Server.Data.Model;
+///
+/// 用户对象
+///
[Table("users")]
public class UserItem
{
- [Column("uid"), Key, Required]
+ ///
+ /// 自增 id,主键
+ ///
+ [Column("uid")]
+ [Key]
+ [Required]
public int Id { get; set; }
- [Column("id"), Required]
+
+ ///
+ /// 用户 id
+ ///
+ [Column("id")]
+ [Required]
public required string UserId { get; set; }
- [Column("password"), Required]
- public required string Password { get; set; }
- [Column("level"), Required]
+
+ ///
+ /// 密码,值为 SHA256(password+id+salt)
+ ///
+ [Column("password")]
+ [Required]
+ [JsonIgnore]
+ public string? Password { get; set; }
+
+ ///
+ /// 用户级别
+ /// -1: Disabled
+ /// 0: Common
+ /// 99: Admin
+ ///
+ [Column("level")]
+ [Required]
public int Level { get; set; }
- [Column("regdate"), Required]
+
+ ///
+ /// 注册时间
+ ///
+ [Column("regdate")]
+ [Required]
+ [JsonPropertyName("registerDate")]
public long RegisterDateUnixTime { get; set; }
+
+ ///
+ /// 最后变动时间
+ ///
[Column("activedate")]
+ [JsonIgnore]
public long? ActiveDateUnixTime { get; set; }
- [Column("name"), Required]
+
+ ///
+ /// 用户名
+ ///
+ [Column("name")]
+ [Required]
public required string Name { get; set; }
+
+ ///
+ /// 邮箱
+ ///
[Column("email")]
public string? Email { get; set; }
+
+ ///
+ /// 联系电话
+ ///
[Column("mobile")]
public string? Mobile { get; set; }
+ ///
+ /// 注册时间
+ ///
[NotMapped]
+ [JsonIgnore]
public DateTimeOffset RegisterDate => DateTimeOffset.FromUnixTimeMilliseconds(RegisterDateUnixTime);
+
+ ///
+ /// 最后变动时间
+ ///
[NotMapped]
+ [JsonIgnore]
public DateTimeOffset? ActiveDate => ActiveDateUnixTime == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(ActiveDateUnixTime.Value);
}
diff --git a/Server/Dockerfile b/Server/Dockerfile
new file mode 100644
index 0000000..a4907fc
--- /dev/null
+++ b/Server/Dockerfile
@@ -0,0 +1,8 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0
+COPY . /app
+WORKDIR /app
+EXPOSE 80
+
+ENTRYPOINT ["dotnet", "Server.dll"]
\ No newline at end of file
diff --git a/Server/Migrations/20230523031232_AddTokens.Designer.cs b/Server/Migrations/20230523031232_AddTokens.Designer.cs
new file mode 100644
index 0000000..bfdf5f1
--- /dev/null
+++ b/Server/Migrations/20230523031232_AddTokens.Designer.cs
@@ -0,0 +1,191 @@
+//
+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("20230523031232_AddTokens")]
+ partial class AddTokens
+ {
+ ///
+ 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("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ 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("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");
+
+ 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");
+
+ b.Property("ExpireSeconds")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiresecs");
+
+ b.Property("LogonDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("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("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/20230523031232_AddTokens.cs b/Server/Migrations/20230523031232_AddTokens.cs
new file mode 100644
index 0000000..ae656ca
--- /dev/null
+++ b/Server/Migrations/20230523031232_AddTokens.cs
@@ -0,0 +1,41 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Blahblah.FlowerStory.Server.Migrations
+{
+ ///
+ public partial class AddTokens : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "tokens",
+ columns: table => new
+ {
+ tid = table.Column(type: "TEXT", nullable: false),
+ uid = table.Column(type: "INTEGER", nullable: false),
+ logondate = table.Column(type: "INTEGER", nullable: false),
+ activedate = table.Column(type: "INTEGER", nullable: false),
+ expiredate = table.Column(type: "INTEGER", nullable: false),
+ expiresecs = table.Column(type: "INTEGER", nullable: false),
+ verifycode = table.Column(type: "TEXT", nullable: true),
+ clientapp = table.Column(type: "TEXT", nullable: true),
+ deviceid = table.Column(type: "TEXT", nullable: true),
+ clientagent = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_tokens", x => x.tid);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "tokens");
+ }
+ }
+}
diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
index 697c3d1..cea8de6 100644
--- a/Server/Migrations/FlowerDatabaseModelSnapshot.cs
+++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
@@ -34,7 +34,8 @@ namespace Blahblah.FlowerStory.Server.Migrations
b.Property("DateBuyUnixTime")
.HasColumnType("INTEGER")
- .HasColumnName("datebuy");
+ .HasColumnName("datebuy")
+ .HasAnnotation("Relational:JsonPropertyName", "dateBuy");
b.Property("Name")
.IsRequired()
@@ -71,7 +72,8 @@ namespace Blahblah.FlowerStory.Server.Migrations
b.Property("DateUnixTime")
.HasColumnType("INTEGER")
- .HasColumnName("date");
+ .HasColumnName("date")
+ .HasAnnotation("Relational:JsonPropertyName", "date");
b.Property("EventId")
.HasColumnType("INTEGER")
@@ -86,6 +88,53 @@ namespace Blahblah.FlowerStory.Server.Migrations
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");
+
+ 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");
+
+ b.Property("ExpireSeconds")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiresecs");
+
+ b.Property("LogonDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("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")
@@ -121,7 +170,8 @@ namespace Blahblah.FlowerStory.Server.Migrations
b.Property("RegisterDateUnixTime")
.HasColumnType("INTEGER")
- .HasColumnName("regdate");
+ .HasColumnName("regdate")
+ .HasAnnotation("Relational:JsonPropertyName", "registerDate");
b.Property("UserId")
.IsRequired()
diff --git a/Server/Program.cs b/Server/Program.cs
index c3d5e37..3cbb0c4 100644
--- a/Server/Program.cs
+++ b/Server/Program.cs
@@ -1,11 +1,19 @@
-using Blahblah.FlowerStory.Server.Controller;
using Blahblah.FlowerStory.Server.Data;
using Microsoft.EntityFrameworkCore;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
namespace Blahblah.FlowerStory.Server;
+///
public class Program
{
+ ///
+ public const string ProjectName = "Flower Story";
+ ///
+ public const string Version = "0.23.523";
+
+ ///
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
@@ -15,17 +23,32 @@ public class Program
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
- builder.Services.AddSwaggerGen();
+ builder.Services.AddSwaggerGen(options =>
+ {
+ options.OperationFilter();
+
+ options.SwaggerDoc(Version, new OpenApiInfo
+ {
+ Title = ProjectName,
+ Version = Version,
+ Description = "¼¼ĵݼеĵεΡ
API ĵ
"
+ });
+
+ options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"));
+ });
builder.Services.AddDbContext(options => options.UseSqlite("DataSource=flower.db;Cache=Shared"));
var app = builder.Build();
// Configure the HTTP request pipeline.
- if (app.Environment.IsDevelopment())
+ //if (app.Environment.IsDevelopment())
{
app.UseSwagger();
- app.UseSwaggerUI();
+ app.UseSwaggerUI(options =>
+ {
+ options.SwaggerEndpoint($"/swagger/{Version}/swagger.json", ProjectName);
+ });
}
app.UseAuthorization();
@@ -33,4 +56,20 @@ public class Program
app.Run();
}
+}
+
+///
+public class SwaggerHttpHeaderOperation : IOperationFilter
+{
+ ///
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ operation.Parameters.Add(new OpenApiParameter
+ {
+ Name = "X-Auth",
+ In = ParameterLocation.Header,
+ Required = false,
+ Schema = new OpenApiSchema { Type = "string" }
+ });
+ }
}
\ No newline at end of file
diff --git a/Server/Server.csproj b/Server/Server.csproj
index 6b809c5..6a39827 100644
--- a/Server/Server.csproj
+++ b/Server/Server.csproj
@@ -1,4 +1,4 @@
-
+
net7.0
@@ -6,6 +6,8 @@
enable
Blahblah.FlowerStory.Server
false
+ True
+ README.md
@@ -19,9 +21,16 @@
+
+ PreserveNewest
+
Never
+
+
+
+
diff --git a/Server/WeatherForecast.cs b/Server/WeatherForecast.cs
deleted file mode 100644
index 6b09b0d..0000000
--- a/Server/WeatherForecast.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Blahblah.FlowerStory.Server;
-
-public class WeatherForecast
-{
- public DateOnly Date { get; set; }
-
- public int TemperatureC { get; set; }
-
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
- public string? Summary { get; set; }
-}
\ No newline at end of file