diff --git a/.gitignore b/.gitignore index ca1c7a3..de0b159 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +flower.db + # User-specific files *.rsuser *.suo diff --git a/FlowerStory/App.xaml b/App/App.xaml similarity index 100% rename from FlowerStory/App.xaml rename to App/App.xaml diff --git a/FlowerStory/App.xaml.cs b/App/App.xaml.cs similarity index 100% rename from FlowerStory/App.xaml.cs rename to App/App.xaml.cs diff --git a/FlowerStory/AppShell.xaml b/App/AppShell.xaml similarity index 100% rename from FlowerStory/AppShell.xaml rename to App/AppShell.xaml diff --git a/FlowerStory/AppShell.xaml.cs b/App/AppShell.xaml.cs similarity index 100% rename from FlowerStory/AppShell.xaml.cs rename to App/AppShell.xaml.cs diff --git a/App/Constants.cs b/App/Constants.cs new file mode 100644 index 0000000..13ccab7 --- /dev/null +++ b/App/Constants.cs @@ -0,0 +1,62 @@ +namespace Blahblah.FlowerStory; + +public sealed class Constants +{ + public const string CategoryOther = "other"; + public const string EventUnknown = "unknown"; + + public const SQLite.SQLiteOpenFlags Flags = + SQLite.SQLiteOpenFlags.ReadWrite | + SQLite.SQLiteOpenFlags.Create | + SQLite.SQLiteOpenFlags.SharedCache; + + private const string databaseFilename = "flowerstory.db3"; + public static string DatabasePath => Path.Combine(FileSystem.AppDataDirectory, databaseFilename); + + public static readonly Dictionary Categories = new() + { + [0] = CategoryOther, + [1] = "cactus", // 仙人球 + [2] = "hibiscus", // 扶桑花 + [3] = "bougainvillea", // 三角梅 + [4] = "sunflower", // 太阳花 + [5] = "milanflower", // 米兰花 + [6] = "jasmine", // 茉莉花 + [7] = "periwinkle", // 长春花 + [8] = "nasturtium", // 旱金莲 + [9] = "mirabilis", // 紫薇花 + [10] = "tigerthornplum", // 虎刺梅 + [11] = "geranium", // 天竺葵 + [12] = "ballorchid", // 球兰 + [13] = "medalchrysanthemum", // 勋章菊 + [14] = "dianthus", // 石竹 + [15] = "fivecolorplum", // 五色梅 + [16] = "fuchsia", // 倒挂金钟 + [17] = "bamboocrabapple", // 竹节海棠 + [18] = "impatiens", // 凤仙花 + [19] = "beautysakura", // 美女樱 + [20] = "petunias", // 矮牵牛 + [21] = "desertrose", // 沙漠玫瑰 + [22] = "trailingcampanula", // 蔓性风铃花 + [23] = "chineserose", // 月季花 + }; + + public static readonly Dictionary Events = new() + { + [0] = new(EventUnknown), + [1] = new("buy", true), // 购买 + [2] = new("born", true), // 出生 + [3] = new("changebasin"), // 换盆 + [4] = new("watering"), // 浇水 + [5] = new("fertilize"), // 施肥 + [6] = new("germination"), // 发芽 + [7] = new("blossom"), // 开花 + [8] = new("fallenleaves"), // 落叶 + [9] = new("prune"), // 修剪 + [10] = new("sick"), // 生病 + [11] = new("death", true), // 死亡 + [12] = new("sell", true), // 出售 + }; +} + +public record Event(string Name, bool Unique = false); diff --git a/App/Data/FlowerDatabase.cs b/App/Data/FlowerDatabase.cs new file mode 100644 index 0000000..1aaa4d0 --- /dev/null +++ b/App/Data/FlowerDatabase.cs @@ -0,0 +1,44 @@ +using Blahblah.FlowerStory.Data.Model; +using Microsoft.Extensions.Logging; +using SQLite; + +namespace Blahblah.FlowerStory.Data; + +public class FlowerDatabase +{ + private SQLiteAsyncConnection database; + + private readonly ILogger logger; + public FlowerDatabase(ILogger logger) + { + this.logger = logger; + } + + private async Task Init() + { + if (database is not null) + { + return; + } + + database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags); + +#if DEBUG + var result = +#endif + await database.CreateTablesAsync(); + +#if DEBUG + foreach (var item in result.Results) + { + logger.LogDebug("create table {table}, result: {result}", item.Key, item.Value); + } +#endif + } + + public async Task> GetFlowers() + { + await Init(); + return await database.Table().ToListAsync(); + } +} diff --git a/App/Data/Model/FlowerItem.cs b/App/Data/Model/FlowerItem.cs new file mode 100644 index 0000000..61e5e0f --- /dev/null +++ b/App/Data/Model/FlowerItem.cs @@ -0,0 +1,45 @@ +using SQLite; + +namespace Blahblah.FlowerStory.Data.Model; + +[Table("flowers")] +public class FlowerItem +{ + [Column("fid"), PrimaryKey, AutoIncrement] + public int Id { get; set; } + + [Column("categoryid")] + public int CategoryId { get; set; } + + private string categoryName; + public string CategoryName + { + get + { + if (categoryName == null) + { + if (!Constants.Categories.TryGetValue(CategoryId, out categoryName)) + { + categoryName = Constants.CategoryOther; + } + // TODO: i18n + } + return categoryName; + } + } + + [Column("name")] + public string Name { get; set; } + + [Column("datebuy")] + public DateTimeOffset DateBuy { get; set; } + + [Column("cost")] + public decimal? Cost { get; set; } + + [Column("purchase")] + public string Purchase { get; set; } + + [Column("photo")] + public byte[] Photo { get; set; } +} diff --git a/App/Data/Model/RecordItem.cs b/App/Data/Model/RecordItem.cs new file mode 100644 index 0000000..196fe54 --- /dev/null +++ b/App/Data/Model/RecordItem.cs @@ -0,0 +1,46 @@ +using SQLite; + +namespace Blahblah.FlowerStory.Data.Model; + +[Table("records")] +public class RecordItem +{ + [Column("rid"), PrimaryKey, AutoIncrement] + public int Id { get; set; } + + [Column("eid")] + public int EventId { get; set; } + + private string eventName; + public string EventName + { + get + { + if (eventName == null) + { + if (Constants.Events.TryGetValue(EventId, out var @event)) + { + eventName = @event.Name; + } + else + { + eventName = Constants.EventUnknown; + } + // TODO: i18n + } + return eventName; + } + } + + [Column("date")] + public DateTimeOffset Date { get; set; } + + [Column("byuid")] + public int? ByUserId { get; set; } + + [Column("byname")] + public string ByUserName { get; set; } + + [Column("photo")] + public byte[] Photo { get; set; } +} diff --git a/App/Data/Model/UserItem.cs b/App/Data/Model/UserItem.cs new file mode 100644 index 0000000..17a5b71 --- /dev/null +++ b/App/Data/Model/UserItem.cs @@ -0,0 +1,11 @@ +namespace Blahblah.FlowerStory.Data.Model; + +public class UserItem +{ + public string Id { get; set; } + public int Level { get; set; } + public DateTimeOffset RegisterDate { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string Mobile { get; set; } +} diff --git a/FlowerStory/FlowerStory.csproj b/App/FlowerStory.csproj similarity index 85% rename from FlowerStory/FlowerStory.csproj rename to App/FlowerStory.csproj index b7acce2..42d7ff7 100644 --- a/FlowerStory/FlowerStory.csproj +++ b/App/FlowerStory.csproj @@ -1,7 +1,7 @@  - net7.0-android;net7.0-ios + net7.0-android Exe Blahblah.FlowerStory @@ -25,24 +25,26 @@ 23.0 + + android-x64 + + apk - android-arm64 + android-arm64;android-x64 True + false + Automatic manual - false - Automatic iPhone Developer - false - Automatic iPhone Distribution @@ -69,5 +71,7 @@ + + diff --git a/FlowerStory/MainPage.xaml b/App/MainPage.xaml similarity index 100% rename from FlowerStory/MainPage.xaml rename to App/MainPage.xaml diff --git a/App/MainPage.xaml.cs b/App/MainPage.xaml.cs new file mode 100644 index 0000000..32fe91b --- /dev/null +++ b/App/MainPage.xaml.cs @@ -0,0 +1,50 @@ +using Blahblah.FlowerStory.Data; +using Microsoft.Extensions.Logging; + +namespace Blahblah.FlowerStory +{ + public partial class MainPage : ContentPage + { + int count = 0; + readonly FlowerDatabase database; + + private readonly ILogger logger; + public MainPage(FlowerDatabase database, ILogger logger) + { + this.logger = logger; + + this.database = database; + InitializeComponent(); + } + + private void OnCounterClicked(object sender, EventArgs e) + { + count++; + + if (count == 1) + CounterBtn.Text = $"Clicked {count} time"; + else + CounterBtn.Text = $"Clicked {count} times"; + + SemanticScreenReader.Announce(CounterBtn.Text); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + + Task.Run(async () => + { + try + { + var list = await database.GetFlowers(); + logger.LogInformation("got {count} flowers.", list.Count); + } + catch (Exception ex) + { + logger.LogError("error occurs, {exception}", ex); + } + }); + } + } +} \ No newline at end of file diff --git a/FlowerStory/MauiProgram.cs b/App/MauiProgram.cs similarity index 76% rename from FlowerStory/MauiProgram.cs rename to App/MauiProgram.cs index 468902f..af7a9b8 100644 --- a/FlowerStory/MauiProgram.cs +++ b/App/MauiProgram.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +using Blahblah.FlowerStory.Data; +#if DEBUG +using Microsoft.Extensions.Logging; +#endif namespace Blahblah.FlowerStory { @@ -22,6 +25,9 @@ namespace Blahblah.FlowerStory builder.Logging.AddDebug(); #endif + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + return builder.Build(); } } diff --git a/FlowerStory/Platforms/Android/AndroidManifest.xml b/App/Platforms/Android/AndroidManifest.xml similarity index 100% rename from FlowerStory/Platforms/Android/AndroidManifest.xml rename to App/Platforms/Android/AndroidManifest.xml diff --git a/FlowerStory/Platforms/Android/MainActivity.cs b/App/Platforms/Android/MainActivity.cs similarity index 100% rename from FlowerStory/Platforms/Android/MainActivity.cs rename to App/Platforms/Android/MainActivity.cs diff --git a/FlowerStory/Platforms/Android/MainApplication.cs b/App/Platforms/Android/MainApplication.cs similarity index 100% rename from FlowerStory/Platforms/Android/MainApplication.cs rename to App/Platforms/Android/MainApplication.cs diff --git a/FlowerStory/Platforms/Android/Resources/values/colors.xml b/App/Platforms/Android/Resources/values/colors.xml similarity index 100% rename from FlowerStory/Platforms/Android/Resources/values/colors.xml rename to App/Platforms/Android/Resources/values/colors.xml diff --git a/FlowerStory/Platforms/iOS/AppDelegate.cs b/App/Platforms/iOS/AppDelegate.cs similarity index 100% rename from FlowerStory/Platforms/iOS/AppDelegate.cs rename to App/Platforms/iOS/AppDelegate.cs diff --git a/FlowerStory/Platforms/iOS/Info.plist b/App/Platforms/iOS/Info.plist similarity index 100% rename from FlowerStory/Platforms/iOS/Info.plist rename to App/Platforms/iOS/Info.plist diff --git a/FlowerStory/Platforms/iOS/Program.cs b/App/Platforms/iOS/Program.cs similarity index 100% rename from FlowerStory/Platforms/iOS/Program.cs rename to App/Platforms/iOS/Program.cs diff --git a/FlowerStory/Resources/AppIcon/appicon.svg b/App/Resources/AppIcon/appicon.svg similarity index 100% rename from FlowerStory/Resources/AppIcon/appicon.svg rename to App/Resources/AppIcon/appicon.svg diff --git a/FlowerStory/Resources/AppIcon/appiconfg.svg b/App/Resources/AppIcon/appiconfg.svg similarity index 100% rename from FlowerStory/Resources/AppIcon/appiconfg.svg rename to App/Resources/AppIcon/appiconfg.svg diff --git a/FlowerStory/Resources/Fonts/OpenSans-Regular.ttf b/App/Resources/Fonts/OpenSans-Regular.ttf similarity index 100% rename from FlowerStory/Resources/Fonts/OpenSans-Regular.ttf rename to App/Resources/Fonts/OpenSans-Regular.ttf diff --git a/FlowerStory/Resources/Fonts/OpenSans-Semibold.ttf b/App/Resources/Fonts/OpenSans-Semibold.ttf similarity index 100% rename from FlowerStory/Resources/Fonts/OpenSans-Semibold.ttf rename to App/Resources/Fonts/OpenSans-Semibold.ttf diff --git a/FlowerStory/Resources/Images/dotnet_bot.svg b/App/Resources/Images/dotnet_bot.svg similarity index 100% rename from FlowerStory/Resources/Images/dotnet_bot.svg rename to App/Resources/Images/dotnet_bot.svg diff --git a/FlowerStory/Resources/Raw/AboutAssets.txt b/App/Resources/Raw/AboutAssets.txt similarity index 100% rename from FlowerStory/Resources/Raw/AboutAssets.txt rename to App/Resources/Raw/AboutAssets.txt diff --git a/FlowerStory/Resources/Splash/splash.svg b/App/Resources/Splash/splash.svg similarity index 100% rename from FlowerStory/Resources/Splash/splash.svg rename to App/Resources/Splash/splash.svg diff --git a/FlowerStory/Resources/Styles/Colors.xaml b/App/Resources/Styles/Colors.xaml similarity index 100% rename from FlowerStory/Resources/Styles/Colors.xaml rename to App/Resources/Styles/Colors.xaml diff --git a/FlowerStory/Resources/Styles/Styles.xaml b/App/Resources/Styles/Styles.xaml similarity index 100% rename from FlowerStory/Resources/Styles/Styles.xaml rename to App/Resources/Styles/Styles.xaml diff --git a/Doc/Flower Story - Database.md b/Doc/Flower Story - Database.md new file mode 100644 index 0000000..2711372 --- /dev/null +++ b/Doc/Flower Story - Database.md @@ -0,0 +1,67 @@ +# Flower Story - Database + +## users +| column | type | isnull | description | +|---------- |--------------|:------:|-------------| +| uid | unique int | no | +| id | text | no | 用户 id +| password | text | no | 加盐密码 [1](#ref-anchor-1) +| level | integer | no | 用户级别 [2](#ref-anchor-2) +| regdate | numeric | no | 注册日期 +| activedate | numeric | | 最后活跃日期 +| name | text | no | 用户名称 +| email | text | | 邮箱 +| mobile | text | | 联系电话 +--- +1. 密码存储为 `sha256(password + uid + salt)` +2. 级别暂定如下 + * -1: disabled + * 0: user + * 99: admin +--- + +## categories +| column | type | isnull | description | +|---------- |--------------|:------:|-------------| +| cid | unique int | no | +| name | text | no | 花草种类名称 +--- + +## flowers +| column | type | isnull | description | +|---------- |--------------|:------:|-------------| +| fid | unique int | no | +| categoryid | integer | no | 种类 id +| name | text | no | 花草名称 +| datebuy | numeric | no | 购买时间 +| cost | real | | 金额 +| purchase | text | | 购入渠道 +| photo | blob | | 靓照 [3](#ref-anchor-3) +--- +3. 若数据为 `NULL`,则读取 `/assets/$uid/photos/$fid.jpg` 显示为购买时的照片 +--- + +## events +| column | type | isnull | description | +|---------- |--------------|:------:|-------------| +| eid | unique int | no | +| name | text | | 事件名称 [4](#ref-anchor-4) +| unique | integer | | 是否唯一 [5](#ref-anchor-5) +--- +4. 事件名称,如购买、出生、换盆、浇水、施肥、发芽、开花、落叶、修剪、生病、死亡、出售等等 +5. 事件是否唯一,如购买、出生、死亡、出售这些事件具有唯一性 +--- + +## records +| column | type | isnull | description | +|---------- |--------------|:------:|-------------| +| rid | unique int | no | +| eid | integer | no | 事件 id +| date | numeric | no | 事件发生日期 +| byuid | integer | | 操作人 uid +| byname | text | | 操作人 [6](#ref-anchor-6) +| photo | blob | | 事件照片 [7](#ref-anchor-7) +--- +6. 操作人名称,不是必须为系统内的人员 +7. 若数据为 `NULL`,则读取 `/assets/$uid/photos/rid/*.jpg` 显示为该事件关联的照片 +--- \ No newline at end of file diff --git a/FlowerStory.sln b/FlowerStory.sln index 8ef6516..15e39f2 100644 --- a/FlowerStory.sln +++ b/FlowerStory.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.33711.374 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowerStory", "FlowerStory\FlowerStory.csproj", "{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlowerStory", "App\FlowerStory.csproj", "{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{A551F94A-1997-4A20-A1E8-157050D92CEF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,6 +19,10 @@ Global {A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.Build.0 = Release|Any CPU {A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {A551F94A-1997-4A20-A1E8-157050D92CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A551F94A-1997-4A20-A1E8-157050D92CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A551F94A-1997-4A20-A1E8-157050D92CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A551F94A-1997-4A20-A1E8-157050D92CEF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FlowerStory/MainPage.xaml.cs b/FlowerStory/MainPage.xaml.cs deleted file mode 100644 index 59cd59f..0000000 --- a/FlowerStory/MainPage.xaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Blahblah.FlowerStory -{ - public partial class MainPage : ContentPage - { - int count = 0; - - public MainPage() - { - InitializeComponent(); - } - - private void OnCounterClicked(object sender, EventArgs e) - { - count++; - - if (count == 1) - CounterBtn.Text = $"Clicked {count} time"; - else - CounterBtn.Text = $"Clicked {count} times"; - - SemanticScreenReader.Announce(CounterBtn.Text); - } - } -} \ No newline at end of file diff --git a/Server/.config/dotnet-tools.json b/Server/.config/dotnet-tools.json new file mode 100644 index 0000000..34c3e19 --- /dev/null +++ b/Server/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "7.0.5", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/Server/Controller/UserController.cs b/Server/Controller/UserController.cs new file mode 100644 index 0000000..a33b71a --- /dev/null +++ b/Server/Controller/UserController.cs @@ -0,0 +1,60 @@ +using Blahblah.FlowerStory.Server.Data; +using Blahblah.FlowerStory.Server.Data.Model; +using Microsoft.AspNetCore.Mvc; + +namespace Blahblah.FlowerStory.Server.Controller +{ + [ApiController] + [Route("users")] + public class UserController : ControllerBase + { + private readonly FlowerDatabase database; + private readonly ILogger logger; + + public UserController(FlowerDatabase db, ILogger logger) + { + database = db; + this.logger = logger; + } + + [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) + { + 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); + } + } +} diff --git a/Server/Data/FlowerDatabase.cs b/Server/Data/FlowerDatabase.cs new file mode 100644 index 0000000..370fd97 --- /dev/null +++ b/Server/Data/FlowerDatabase.cs @@ -0,0 +1,15 @@ +using Blahblah.FlowerStory.Server.Data.Model; +using Microsoft.EntityFrameworkCore; + +namespace Blahblah.FlowerStory.Server.Data; + +public class FlowerDatabase : DbContext +{ + public FlowerDatabase(DbContextOptions options) : base(options) { } + + public DbSet Users { get; set; } + + public DbSet Flowers { get; set; } + + public DbSet Records { get; set; } +} diff --git a/Server/Data/Model/FlowerItem.cs b/Server/Data/Model/FlowerItem.cs new file mode 100644 index 0000000..be85b4a --- /dev/null +++ b/Server/Data/Model/FlowerItem.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Blahblah.FlowerStory.Server.Data.Model; + +[Table("flowers")] +public class FlowerItem +{ + [Column("fid"), Key, Required] + public int Id { get; set; } + + [Column("categoryid"), Required] + public int CategoryId { get; set; } + + [Column("name"), Required] + public required string Name { get; set; } + + [Column("datebuy"), Required] + 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] + public DateTimeOffset DateBuy => DateTimeOffset.FromUnixTimeMilliseconds(DateBuyUnixTime); +} diff --git a/Server/Data/Model/RecordItem.cs b/Server/Data/Model/RecordItem.cs new file mode 100644 index 0000000..bd9c42e --- /dev/null +++ b/Server/Data/Model/RecordItem.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Blahblah.FlowerStory.Server.Data.Model; + +[Table("records")] +public class RecordItem +{ + [Column("rid"), Key, Required] + public int Id { get; set; } + + [Column("eid"), Required] + public int EventId { get; set; } + + [Column("date"), Required] + public long DateUnixTime { get; set; } + + [Column("byuid")] + public int? ByUserId { get; set; } + + [Column("byname")] + public string? ByUserName { get; set; } + + [Column("photo")] + public byte[]? Photo { get; set; } + + [NotMapped] + public DateTimeOffset Date => DateTimeOffset.FromUnixTimeMilliseconds(DateUnixTime); +} diff --git a/Server/Data/Model/UserItem.cs b/Server/Data/Model/UserItem.cs new file mode 100644 index 0000000..7835930 --- /dev/null +++ b/Server/Data/Model/UserItem.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Blahblah.FlowerStory.Server.Data.Model; + +[Table("users")] +public class UserItem +{ + [Column("uid"), Key, Required] + public int Id { get; set; } + [Column("id"), Required] + public required string UserId { get; set; } + [Column("password"), Required] + public required string Password { get; set; } + [Column("level"), Required] + public int Level { get; set; } + [Column("regdate"), Required] + public long RegisterDateUnixTime { get; set; } + [Column("activedate")] + public long? ActiveDateUnixTime { get; set; } + [Column("name"), Required] + public required string Name { get; set; } + [Column("email")] + public string? Email { get; set; } + [Column("mobile")] + public string? Mobile { get; set; } + + [NotMapped] + public DateTimeOffset RegisterDate => DateTimeOffset.FromUnixTimeMilliseconds(RegisterDateUnixTime); + [NotMapped] + public DateTimeOffset? ActiveDate => ActiveDateUnixTime == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(ActiveDateUnixTime.Value); +} diff --git a/Server/Migrations/20230522090224_InitialCreateDb.Designer.cs b/Server/Migrations/20230522090224_InitialCreateDb.Designer.cs new file mode 100644 index 0000000..e5a8942 --- /dev/null +++ b/Server/Migrations/20230522090224_InitialCreateDb.Designer.cs @@ -0,0 +1,141 @@ +// +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("20230522090224_InitialCreateDb")] + partial class InitialCreateDb + { + /// + 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("DateBuy") + .HasColumnType("numeric") + .HasColumnName("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("Date") + .HasColumnType("numeric") + .HasColumnName("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.UserItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("ActiveDate") + .HasColumnType("numeric") + .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("RegisterDate") + .HasColumnType("numeric") + .HasColumnName("regdate"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.HasKey("Id"); + + b.ToTable("users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/20230522090224_InitialCreateDb.cs b/Server/Migrations/20230522090224_InitialCreateDb.cs new file mode 100644 index 0000000..0133c0f --- /dev/null +++ b/Server/Migrations/20230522090224_InitialCreateDb.cs @@ -0,0 +1,83 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + /// + public partial class InitialCreateDb : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "flowers", + columns: table => new + { + fid = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + categoryid = table.Column(type: "INTEGER", nullable: false), + name = table.Column(type: "TEXT", nullable: false), + datebuy = table.Column(type: "numeric", nullable: false), + cost = table.Column(type: "real", nullable: true), + purchase = table.Column(type: "TEXT", nullable: true), + photo = table.Column(type: "BLOB", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_flowers", x => x.fid); + }); + + migrationBuilder.CreateTable( + name: "records", + columns: table => new + { + rid = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + eid = table.Column(type: "INTEGER", nullable: false), + date = table.Column(type: "numeric", nullable: false), + byuid = table.Column(type: "INTEGER", nullable: true), + byname = table.Column(type: "TEXT", nullable: true), + photo = table.Column(type: "BLOB", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_records", x => x.rid); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + uid = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + id = table.Column(type: "TEXT", nullable: false), + password = table.Column(type: "TEXT", nullable: false), + level = table.Column(type: "INTEGER", nullable: false), + regdate = table.Column(type: "numeric", nullable: false), + activedate = table.Column(type: "numeric", nullable: true), + name = table.Column(type: "TEXT", nullable: false), + email = table.Column(type: "TEXT", nullable: true), + mobile = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_users", x => x.uid); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "flowers"); + + migrationBuilder.DropTable( + name: "records"); + + migrationBuilder.DropTable( + name: "users"); + } + } +} diff --git a/Server/Migrations/20230522143925_ChangeDateColumnType.Designer.cs b/Server/Migrations/20230522143925_ChangeDateColumnType.Designer.cs new file mode 100644 index 0000000..a27cb2c --- /dev/null +++ b/Server/Migrations/20230522143925_ChangeDateColumnType.Designer.cs @@ -0,0 +1,141 @@ +// +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("20230522143925_ChangeDateColumnType")] + partial class ChangeDateColumnType + { + /// + 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"); + + 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"); + + 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.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"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.HasKey("Id"); + + b.ToTable("users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/20230522143925_ChangeDateColumnType.cs b/Server/Migrations/20230522143925_ChangeDateColumnType.cs new file mode 100644 index 0000000..eacee39 --- /dev/null +++ b/Server/Migrations/20230522143925_ChangeDateColumnType.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + /// + public partial class ChangeDateColumnType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "regdate", + table: "users", + type: "INTEGER", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "numeric"); + + migrationBuilder.AlterColumn( + name: "activedate", + table: "users", + type: "INTEGER", + nullable: true, + oldClrType: typeof(DateTimeOffset), + oldType: "numeric", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "date", + table: "records", + type: "INTEGER", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "numeric"); + + migrationBuilder.AlterColumn( + name: "datebuy", + table: "flowers", + type: "INTEGER", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "numeric"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "regdate", + table: "users", + type: "numeric", + nullable: false, + oldClrType: typeof(long), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "activedate", + table: "users", + type: "numeric", + nullable: true, + oldClrType: typeof(long), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "date", + table: "records", + type: "numeric", + nullable: false, + oldClrType: typeof(long), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "datebuy", + table: "flowers", + type: "numeric", + nullable: false, + oldClrType: typeof(long), + oldType: "INTEGER"); + } + } +} diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs new file mode 100644 index 0000000..697c3d1 --- /dev/null +++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs @@ -0,0 +1,138 @@ +// +using System; +using Blahblah.FlowerStory.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Blahblah.FlowerStory.Server.Migrations +{ + [DbContext(typeof(FlowerDatabase))] + partial class FlowerDatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(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"); + + 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"); + + 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.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"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.HasKey("Id"); + + b.ToTable("users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Program.cs b/Server/Program.cs new file mode 100644 index 0000000..c3d5e37 --- /dev/null +++ b/Server/Program.cs @@ -0,0 +1,36 @@ +using Blahblah.FlowerStory.Server.Controller; +using Blahblah.FlowerStory.Server.Data; +using Microsoft.EntityFrameworkCore; + +namespace Blahblah.FlowerStory.Server; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddControllers(); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + builder.Services.AddDbContext(options => options.UseSqlite("DataSource=flower.db;Cache=Shared")); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseAuthorization(); + app.MapControllers(); + + app.Run(); + } +} \ No newline at end of file diff --git a/Server/Properties/launchSettings.json b/Server/Properties/launchSettings.json new file mode 100644 index 0000000..1f921a5 --- /dev/null +++ b/Server/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:2132", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5247", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Server/Server.csproj b/Server/Server.csproj new file mode 100644 index 0000000..6b809c5 --- /dev/null +++ b/Server/Server.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + Blahblah.FlowerStory.Server + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + Never + + + + diff --git a/Server/WeatherForecast.cs b/Server/WeatherForecast.cs new file mode 100644 index 0000000..6b09b0d --- /dev/null +++ b/Server/WeatherForecast.cs @@ -0,0 +1,12 @@ +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 diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Server/appsettings.json b/Server/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Server/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}