From 4cdeb8a7c769025e8923d6da0054a64b2e479ac5 Mon Sep 17 00:00:00 2001 From: Jetsparrow Date: Fri, 13 May 2022 18:52:52 +0300 Subject: [PATCH] split plan and role, fix lots of small issues, move registration logic to code --- JetHerald/.config/dotnet-tools.json | 12 + JetHerald/Contracts/IDb.cs | 8 + .../Controllers/Api/HeartbeatController.cs | 4 +- .../Controllers/Ui/AdminToolsController.cs | 10 +- JetHerald/Controllers/Ui/LoginController.cs | 2 +- JetHerald/Controllers/Ui/MainController.cs | 9 +- .../Controllers/Ui/RegistrationController.cs | 13 +- JetHerald/JetHerald.csproj | 8 +- JetHerald/NLog.config | 28 --- JetHerald/Program.cs | 16 +- JetHerald/Services/Db.cs | 51 +++-- JetHerald/Views/AdminTools/Index.cshtml | 4 +- JetHerald/Views/AdminTools/ViewInvites.cshtml | 12 +- JetHerald/appsettings.Development.json | 16 +- JetHerald/appsettings.json | 21 -- JetHerald/nlog.debug.config | 2 +- sql/herald-db-dump-1.0.sql | 207 ++++++++++++++---- 17 files changed, 283 insertions(+), 140 deletions(-) create mode 100644 JetHerald/.config/dotnet-tools.json delete mode 100644 JetHerald/NLog.config delete mode 100644 JetHerald/appsettings.json diff --git a/JetHerald/.config/dotnet-tools.json b/JetHerald/.config/dotnet-tools.json new file mode 100644 index 0000000..e80b80f --- /dev/null +++ b/JetHerald/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "6.0.5", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/JetHerald/Contracts/IDb.cs b/JetHerald/Contracts/IDb.cs index f941569..7ab8072 100644 --- a/JetHerald/Contracts/IDb.cs +++ b/JetHerald/Contracts/IDb.cs @@ -45,6 +45,7 @@ public class User public byte[] PasswordSalt { get; set; } public int HashType { get; set; } public uint PlanId { get; set; } + public uint RoleId { get; set; } public string Allow { get; set; } @@ -58,6 +59,7 @@ public class UserInvite public uint UserInviteId { get; set; } public string InviteCode { get; set; } public uint PlanId { get; set; } + public uint RoleId { get; set; } public uint RedeemedBy { get; set; } } @@ -74,5 +76,11 @@ public class Plan public string Name { get; set; } public int MaxTopics { get; set; } public double TimeoutMultiplier { get; set; } +} + +public class Role +{ + public uint RoleId { get; set; } + public string Name { get; set; } public string Allow { get; set; } } diff --git a/JetHerald/Controllers/Api/HeartbeatController.cs b/JetHerald/Controllers/Api/HeartbeatController.cs index 4b9e372..6258944 100644 --- a/JetHerald/Controllers/Api/HeartbeatController.cs +++ b/JetHerald/Controllers/Api/HeartbeatController.cs @@ -76,9 +76,9 @@ public class HeartbeatController : ControllerBase if (Timeouts.IsTimedOut(t.TopicId)) return StatusCode(StatusCodes.Status429TooManyRequests); - var affected = await Db.ReportHeartbeat(t.TopicId, heart, args.ExpiryTimeout); + var wasBeating = await Db.ReportHeartbeat(t.TopicId, heart, args.ExpiryTimeout); - if (affected == 0) + if (wasBeating == 0) await Herald.BroadcastMessageRaw(t.TopicId, $"!{t.Description}!:\nHeart \"{heart}\" has started beating at {DateTime.UtcNow:O}"); Timeouts.ApplyCost(t.TopicId, Config.HeartbeatCost); diff --git a/JetHerald/Controllers/Ui/AdminToolsController.cs b/JetHerald/Controllers/Ui/AdminToolsController.cs index a2d19b8..517d9a6 100644 --- a/JetHerald/Controllers/Ui/AdminToolsController.cs +++ b/JetHerald/Controllers/Ui/AdminToolsController.cs @@ -33,10 +33,12 @@ public class AdminToolsController : Controller { var invites = await Db.GetInvites(); var plans = await Db.GetPlans(); + var roles = await Db.GetRoles(); return View(new ViewInvitesModel { Invites = invites.ToArray(), - Plans = plans.ToDictionary(p => p.PlanId) + Plans = plans.ToDictionary(p => p.PlanId), + Roles = roles.ToDictionary(r => r.RoleId) }); } @@ -44,16 +46,20 @@ public class AdminToolsController : Controller { [BindProperty(Name = "planId"), BindRequired] public uint PlanId { get; set; } + [BindProperty(Name = "roleId"), BindRequired] + public uint RoleId { get; set; } } [HttpPost, Route("ui/admintools/invites/create")] public async Task CreateInvite(CreateInviteRequest req) { - await Db.CreateUserInvite(req.PlanId, TokenHelper.GetToken(AuthCfg.InviteCodeLength)); + await Db.CreateUserInvite(req.PlanId, req.RoleId, TokenHelper.GetToken(AuthCfg.InviteCodeLength)); return RedirectToAction(nameof(ViewInvites)); } } + public class ViewInvitesModel { public UserInvite[] Invites { get; set; } public Dictionary Plans { get; set; } + public Dictionary Roles { get; set; } } \ No newline at end of file diff --git a/JetHerald/Controllers/Ui/LoginController.cs b/JetHerald/Controllers/Ui/LoginController.cs index f0ddf8a..e6b9284 100644 --- a/JetHerald/Controllers/Ui/LoginController.cs +++ b/JetHerald/Controllers/Ui/LoginController.cs @@ -73,7 +73,7 @@ public class LoginController : Controller } catch (ArgumentException) { - return RedirectToAction("Dashboard", "Dashboard"); + return RedirectToAction("Index", "Dashboard"); } } diff --git a/JetHerald/Controllers/Ui/MainController.cs b/JetHerald/Controllers/Ui/MainController.cs index 44233fb..e8ad224 100644 --- a/JetHerald/Controllers/Ui/MainController.cs +++ b/JetHerald/Controllers/Ui/MainController.cs @@ -1,25 +1,26 @@ using Microsoft.AspNetCore.Mvc; namespace JetHerald.Controllers.Ui; +[Route("ui")] public class MainController : Controller { - [Route("/error/")] + [Route("error")] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(); } - [Route("/403")] + [Route("403")] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error403() { return View(); } - [Route("/404")] + [Route("404")] public IActionResult Error404() { return View(); } - [Route("/400")] + [Route("400")] public IActionResult Error400() { return View(); diff --git a/JetHerald/Controllers/Ui/RegistrationController.cs b/JetHerald/Controllers/Ui/RegistrationController.cs index ae76384..53f7c2a 100644 --- a/JetHerald/Controllers/Ui/RegistrationController.cs +++ b/JetHerald/Controllers/Ui/RegistrationController.cs @@ -27,7 +27,7 @@ public class RegistrationController : Controller } public RegistrationController( - ILogger log, + ILogger log, Db db, IOptionsSnapshot authConfig) { @@ -83,14 +83,17 @@ public class RegistrationController : Controller } var user = new User() { + RoleId = invite.RoleId, PlanId = invite.PlanId, Login = req.Login, Name = req.Name, + HashType = Cfg.HashType, PasswordSalt = RandomNumberGenerator.GetBytes(64) }; - user.PasswordHash = AuthUtils.GetHashFor(req.Password, user.PasswordSalt, Cfg.HashType); - var newUser = await Db.RegisterUserFromInvite(user, invite.UserInviteId); - var userIdentity = AuthUtils.CreateIdentity(newUser.UserId, newUser.Login, newUser.Name, newUser.Allow); + user.PasswordHash = AuthUtils.GetHashFor(req.Password, user.PasswordSalt, user.HashType); + user = await Db.RegisterUser(user); + await Db.RedeemInvite(invite.UserInviteId, user.UserId); + var userIdentity = AuthUtils.CreateIdentity(user.UserId, user.Login, user.Name, user.Allow); var principal = new ClaimsPrincipal(userIdentity); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); @@ -100,7 +103,7 @@ public class RegistrationController : Controller } catch (ArgumentException) { - return RedirectToAction("News", "Issue"); + return RedirectToAction("Index", "Dashboard"); } } } diff --git a/JetHerald/JetHerald.csproj b/JetHerald/JetHerald.csproj index cfdca24..75d8ecb 100644 --- a/JetHerald/JetHerald.csproj +++ b/JetHerald/JetHerald.csproj @@ -6,7 +6,7 @@ - + @@ -15,10 +15,4 @@ - - - Always - - - diff --git a/JetHerald/NLog.config b/JetHerald/NLog.config deleted file mode 100644 index 847b540..0000000 --- a/JetHerald/NLog.config +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JetHerald/Program.cs b/JetHerald/Program.cs index fb1a4a3..dbad9fe 100644 --- a/JetHerald/Program.cs +++ b/JetHerald/Program.cs @@ -35,7 +35,6 @@ try builder.WebHost.ConfigureAppConfiguration((hostingContext, config) => { - //config.SetBasePath(Directory.GetCurrentDirectory()); config.AddIniFile("secrets.ini", optional: true, reloadOnChange: true); config.AddIniFile($"secrets.{hostingContext.HostingEnvironment.EnvironmentName}.ini", @@ -78,10 +77,10 @@ try options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.HttpOnly = true; // options.SessionStore = new JetHeraldTicketStore(); - options.LoginPath = "/login"; - options.LogoutPath = "/logout"; + options.LoginPath = "/ui/login"; + options.LogoutPath = "/ui/logout"; options.ReturnUrlParameter = "redirect"; - options.AccessDeniedPath = "/403"; + options.AccessDeniedPath = "/ui/403"; options.ClaimsIssuer = "JetHerald"; }); services.AddPermissions(); @@ -96,6 +95,9 @@ try var adminUser = await db.GetUser("admin"); if (adminUser == null) { + var adminRole = (await db.GetRoles()).First(r => r.Name == "admin"); + var unlimitedPlan = (await db.GetPlans()).First(p => p.Name == "unlimited"); + var authCfg = app.Services.GetService>().Value; var password = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48)); adminUser = new JetHerald.Contracts.User() @@ -103,10 +105,12 @@ try Login = "admin", Name = "Administrator", PasswordSalt = RandomNumberGenerator.GetBytes(64), - HashType = authCfg.HashType + HashType = authCfg.HashType, + RoleId = adminRole.RoleId, + PlanId = unlimitedPlan.PlanId }; adminUser.PasswordHash = AuthUtils.GetHashFor(password, adminUser.PasswordSalt, adminUser.HashType); - var newUser = await db.RegisterUser(adminUser, "admin"); + var newUser = await db.RegisterUser(adminUser); log.Warn($"Created administrative account {adminUser.Login}:{password}. Be sure to save these credentials somewhere!"); } } diff --git a/JetHerald/Services/Db.cs b/JetHerald/Services/Db.cs index 544df1b..6d34f47 100644 --- a/JetHerald/Services/Db.cs +++ b/JetHerald/Services/Db.cs @@ -19,6 +19,11 @@ public class Db return await c.QueryAsync("SELECT * FROM plan"); } + public async Task> GetRoles() + { + using var c = GetConnection(); + return await c.QueryAsync("SELECT * FROM role"); + } public async Task> GetInvites() { using var c = GetConnection(); @@ -33,12 +38,15 @@ public class Db new { userId }); } - public async Task CreateUserInvite(uint planId, string inviteCode) + public async Task CreateUserInvite(uint planId, uint roleId, string inviteCode) { using var c = GetConnection(); - await c.ExecuteAsync( - "INSERT INTO userinvite (PlanId, InviteCode) VALUES (@planId, @inviteCode)", - new { planId, inviteCode }); + await c.ExecuteAsync(@" + INSERT INTO userinvite + ( PlanId, RoleId, InviteCode) + VALUES + (@planId, @roleId, @inviteCode)", + new { planId, roleId, inviteCode }); } public async Task GetTopic(string name) @@ -79,11 +87,12 @@ public class Db public async Task GetUser(string login) { using var c = GetConnection(); - return await c.QuerySingleOrDefaultAsync( - " SELECT u.*, p.* " + - " FROM user u " + - " LEFT JOIN plan p ON p.PlanId = u.PlanId " + - " WHERE u.Login = @login", + return await c.QuerySingleOrDefaultAsync(@" + SELECT u.*, up.*, ur.* + FROM user u + JOIN plan up ON u.PlanId = up.PlanId + JOIN role ur ON u.RoleId = ur.RoleId + WHERE u.Login = @login;", new { login }); } @@ -135,24 +144,24 @@ public class Db return topic; } - public async Task RegisterUserFromInvite(User user, uint inviteId) + public async Task RegisterUser(User user) { using var c = GetConnection(); - uint userId = await c.QuerySingleOrDefaultAsync( - "CALL register_user_from_invite(@inviteId, @Login, @Name, @PasswordHash, @PasswordSalt, @HashType);", - new { inviteId, user.Login, user.Name, user.PasswordHash, user.PasswordSalt, user.HashType }); - + uint userId = await c.QuerySingleOrDefaultAsync(@" + INSERT INTO user + ( Login, Name, PasswordHash, PasswordSalt, HashType, PlanId, RoleId) + VALUES + (@Login, @Name, @PasswordHash, @PasswordSalt, @HashType, @PlanId, @RoleId);", + param:user); return await GetUser(user.Login); } - public async Task RegisterUser(User user, string plan) + public async Task RedeemInvite(uint inviteId, uint userId) { using var c = GetConnection(); - uint userId = await c.QuerySingleOrDefaultAsync( - "CALL register_user(@plan, @Login, @Name, @PasswordHash, @PasswordSalt, @HashType);", - new { plan, user.Login, user.Name, user.PasswordHash, user.PasswordSalt, user.HashType }); - - return await GetUser(user.Login); + await c.ExecuteAsync( + @"UPDATE userinvite SET RedeemedBy = @userId WHERE UserInviteId = @inviteId", + new { inviteId, userId }); } public async Task GetInviteByCode(string inviteCode) @@ -274,6 +283,6 @@ public class Db Config = cfg; } IOptionsMonitor Config { get; } - MySqlConnection GetConnection() => new(Config.CurrentValue.DefaultConnection); + public MySqlConnection GetConnection() => new(Config.CurrentValue.DefaultConnection); } diff --git a/JetHerald/Views/AdminTools/Index.cshtml b/JetHerald/Views/AdminTools/Index.cshtml index b17d6d0..24e1808 100644 --- a/JetHerald/Views/AdminTools/Index.cshtml +++ b/JetHerald/Views/AdminTools/Index.cshtml @@ -1,6 +1,6 @@  \ No newline at end of file diff --git a/JetHerald/Views/AdminTools/ViewInvites.cshtml b/JetHerald/Views/AdminTools/ViewInvites.cshtml index 046c434..3bb6c96 100644 --- a/JetHerald/Views/AdminTools/ViewInvites.cshtml +++ b/JetHerald/Views/AdminTools/ViewInvites.cshtml @@ -1,13 +1,21 @@ @model ViewInvitesModel
- + + + +

@@ -17,7 +25,7 @@ @foreach (var invite in Model.Invites) {
  • - @invite.InviteCode.Substring(0, 8)... (@Model.Plans[invite.PlanId].Name) + @invite.InviteCode.Substring(0, 8)... r:(@Model.Roles[invite.RoleId].Name) p:(@Model.Plans[invite.PlanId].Name)
    diff --git a/JetHerald/appsettings.Development.json b/JetHerald/appsettings.Development.json index e203e94..c93958d 100644 --- a/JetHerald/appsettings.Development.json +++ b/JetHerald/appsettings.Development.json @@ -5,5 +5,19 @@ "System": "Information", "Microsoft": "Information" } + }, + "Telegram": { + "UseProxy": "true" + }, + "Timeout": { + "DebtLimitSeconds": 600, + "HeartbeatCost": 60, + "ReportCost": 60 + }, + "PathBase": "/", + "AuthConfig": { + "InviteCodeLength": 128, + "TicketIdLengthBytes": 64, + "HashType": 1 } -} +} \ No newline at end of file diff --git a/JetHerald/appsettings.json b/JetHerald/appsettings.json deleted file mode 100644 index 061a45a..0000000 --- a/JetHerald/appsettings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "Telegram": { - "UseProxy": "true" - }, - "Timeout": { - "DebtLimitSeconds": 600, - "HeartbeatCost": 60, - "ReportCost": 60 - }, - "PathBase": "/", - "AuthConfig": { - "InviteCodeLength" : 128, - "TicketIdLengthBytes": 64, - "HashType": 1 - } -} \ No newline at end of file diff --git a/JetHerald/nlog.debug.config b/JetHerald/nlog.debug.config index 847b540..a5586b3 100644 --- a/JetHerald/nlog.debug.config +++ b/JetHerald/nlog.debug.config @@ -12,7 +12,7 @@ - diff --git a/sql/herald-db-dump-1.0.sql b/sql/herald-db-dump-1.0.sql index 10d8156..be7c68a 100644 --- a/sql/herald-db-dump-1.0.sql +++ b/sql/herald-db-dump-1.0.sql @@ -1,6 +1,4 @@ -CREATE DATABASE IF NOT EXISTS `herald` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */; -USE `herald`; --- MySQL dump 10.13 Distrib 8.0.28, for Win64 (x86_64) +-- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64) -- -- Host: localhost Database: herald -- ------------------------------------------------------ @@ -9,7 +7,7 @@ USE `herald`; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!50503 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; @@ -28,17 +26,19 @@ CREATE TABLE `heart` ( `HeartId` bigint unsigned NOT NULL AUTO_INCREMENT, `TopicId` int unsigned NOT NULL, `Heart` varchar(32) NOT NULL, - `Status` enum('beating','stopped','deleted') NOT NULL DEFAULT 'beating', + `Name` varchar(45) GENERATED ALWAYS AS (`Heart`) VIRTUAL, + `Status` varchar(16) NOT NULL DEFAULT 'beating', `LastBeatTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `ExpiryTs` timestamp NOT NULL, `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `UpdateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`HeartId`), UNIQUE KEY `idx_TopicId_Heart_UNIQUE` (`TopicId`,`Heart`), - CONSTRAINT `fk_topic` FOREIGN KEY (`TopicId`) REFERENCES `topic` (`TopicId`) -) ENGINE=InnoDB AUTO_INCREMENT=115140 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + CONSTRAINT `fk_heart_TopicId` FOREIGN KEY (`TopicId`) REFERENCES `topic` (`TopicId`) +) ENGINE=InnoDB AUTO_INCREMENT=227102 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `heartevent` -- @@ -50,15 +50,68 @@ CREATE TABLE `heartevent` ( `HeartEventId` bigint unsigned NOT NULL AUTO_INCREMENT, `TopicId` int unsigned NOT NULL, `Heart` varchar(32) NOT NULL, - `Status` enum('created','processing','reported') NOT NULL DEFAULT 'created', - `Event` enum('created','started','stopped','deleted') NOT NULL COMMENT 'ENUM(''created'',''started'',''stopped'',''deleted'')', + `Status` varchar(16) NOT NULL DEFAULT 'created', + `Event` varchar(16) NOT NULL COMMENT 'ENUM(''created'',''started'',''stopped'',''deleted'')', `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`HeartEventId`), KEY `idx_topic` (`TopicId`,`CreateTs`), KEY `idx_reported` (`Status`) -) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=372 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `plan` +-- + +DROP TABLE IF EXISTS `plan`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `plan` ( + `PlanId` int unsigned NOT NULL AUTO_INCREMENT, + `Name` varchar(45) NOT NULL, + `MaxTopics` int unsigned NOT NULL, + `TimeoutMultiplier` double NOT NULL, + PRIMARY KEY (`PlanId`), + UNIQUE KEY `Name_UNIQUE` (`Name`) +) ENGINE=InnoDB AUTO_INCREMENT=4099 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `plan` +-- + +LOCK TABLES `plan` WRITE; +/*!40000 ALTER TABLE `plan` DISABLE KEYS */; +INSERT INTO `plan` VALUES (4096,'none',0,1),(4097,'unlimited',1000000,0); +/*!40000 ALTER TABLE `plan` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `role` +-- + +DROP TABLE IF EXISTS `role`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `role` ( + `RoleId` int unsigned NOT NULL AUTO_INCREMENT, + `Name` varchar(45) NOT NULL, + `Allow` text NOT NULL, + PRIMARY KEY (`RoleId`) +) ENGINE=InnoDB AUTO_INCREMENT=4099 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `role` +-- + +LOCK TABLES `role` WRITE; +/*!40000 ALTER TABLE `role` DISABLE KEYS */; +INSERT INTO `role` VALUES (4096,'anonymous','login;register;'),(4097,'admin','**;'),(4098,'client','dashboard;profile;topic;'); +/*!40000 ALTER TABLE `role` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `topic` -- @@ -69,11 +122,11 @@ DROP TABLE IF EXISTS `topic`; CREATE TABLE `topic` ( `TopicId` int unsigned NOT NULL AUTO_INCREMENT, `Creator` varchar(45) NOT NULL, + `CreatorId` int unsigned NOT NULL, `Name` varchar(45) NOT NULL, `Description` varchar(45) NOT NULL, `ReadToken` char(32) NOT NULL, `WriteToken` char(32) NOT NULL, - `AdminToken` char(32) NOT NULL, `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `UpdateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`TopicId`), @@ -95,14 +148,87 @@ CREATE TABLE `topic_sub` ( `Sub` varchar(45) NOT NULL, `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`TopicId`,`Sub`), - CONSTRAINT `FK_TOPICID` FOREIGN KEY (`TopicId`) REFERENCES `topic` (`TopicId`) ON DELETE CASCADE + CONSTRAINT `fk_topic_sub_TopicId` FOREIGN KEY (`TopicId`) REFERENCES `topic` (`TopicId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Dumping events for database 'herald' +-- Table structure for table `user` -- +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user` ( + `UserId` int unsigned NOT NULL AUTO_INCREMENT, + `PlanId` int unsigned NOT NULL, + `RoleId` int unsigned NOT NULL, + `Login` varchar(45) NOT NULL, + `Name` varchar(255) NOT NULL, + `PasswordHash` varbinary(255) NOT NULL, + `PasswordSalt` varbinary(255) NOT NULL, + `HashType` tinyint NOT NULL DEFAULT '1', + `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `UpdateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`UserId`), + KEY `idx_user_PlanId` (`PlanId`), + KEY `fk_user_RoleId_idx` (`RoleId`), + CONSTRAINT `fk_user_PlanId` FOREIGN KEY (`PlanId`) REFERENCES `plan` (`PlanId`), + CONSTRAINT `fk_user_RoleId` FOREIGN KEY (`RoleId`) REFERENCES `role` (`RoleId`) +) ENGINE=InnoDB AUTO_INCREMENT=4111 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user` +-- + +LOCK TABLES `user` WRITE; +/*!40000 ALTER TABLE `user` DISABLE KEYS */; +INSERT INTO `user` VALUES (4096,4096,4096,'Anonymous','Anonymous',0xBADC0D35,0xBADC0D35,0,NOW(),NOW()); +/*!40000 ALTER TABLE `user` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `userinvite` +-- + +DROP TABLE IF EXISTS `userinvite`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `userinvite` ( + `UserInviteId` int NOT NULL AUTO_INCREMENT, + `PlanId` int unsigned NOT NULL, + `RoleId` int unsigned NOT NULL DEFAULT '4096', + `InviteCode` varchar(255) NOT NULL, + `RedeemedBy` int unsigned DEFAULT NULL, + `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `UpdateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`UserInviteId`), + UNIQUE KEY `InviteCode_UNIQUE` (`InviteCode`), + KEY `fk_PlanId_idx` (`PlanId`), + KEY `fk_userinvite_RoleId_idx` (`RoleId`), + CONSTRAINT `fk_userinvite_PlanId` FOREIGN KEY (`PlanId`) REFERENCES `plan` (`PlanId`), + CONSTRAINT `fk_userinvite_RoleId` FOREIGN KEY (`RoleId`) REFERENCES `role` (`RoleId`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `usersession` +-- + +DROP TABLE IF EXISTS `usersession`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `usersession` ( + `SessionId` varchar(255) NOT NULL, + `SessionData` blob NOT NULL, + `ExpiryTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `CreateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `UpdateTs` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`SessionId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Dumping routines for database 'herald' -- @@ -116,33 +242,31 @@ CREATE TABLE `topic_sub` ( /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; -CREATE PROCEDURE `process_hearts`() +CREATE DEFINER=`herald`@`%` PROCEDURE `process_hearts`() BEGIN + START TRANSACTION; + SET @ts = NOW(); -START TRANSACTION; + INSERT INTO heartevent + (TopicId, Heart, Event) + SELECT + TopicID, Heart, 'stopped' + FROM heart + WHERE ExpiryTs < @ts AND Status = 'beating'; - SET @ts = NOW(); - - INSERT INTO heartevent - (TopicId, Heart, `Event`) - SELECT - TopicId, Heart, 'stopped' - FROM heart - WHERE ExpiryTs < @ts AND `Status` = 'beating'; - - UPDATE heart SET `Status` = 'stopped' WHERE ExpiryTs < @ts AND `Status` = 'beating' AND HeartId > 0; - - SET @id = (SELECT HeartEventId FROM heartevent WHERE `Status` = 'created' ORDER BY HeartEventId DESC LIMIT 1); - UPDATE heartevent SET `Status` = 'processing' WHERE HeartEventId = @id; - SELECT ha.*, t.`Description` - FROM heartevent ha - JOIN topic t USING (TopicId) - WHERE HeartEventId = @id; + UPDATE heart set Status = 'stopped' WHERE ExpiryTs < @ts AND Status = 'beating' AND HeartId > 0; + SET @id = (SELECT HeartEventId FROM heartevent WHERE `Status` = 'created' ORDER BY HeartEventId DESC LIMIT 1); -COMMIT; + UPDATE heartevent SET `Status` = 'processing' WHERE HeartEventId = @id; + SELECT ha.*, t.`Description` + FROM heartevent ha + JOIN topic t USING (TopicId) + WHERE HeartEventId = @id; + COMMIT; END ;; DELIMITER ; + /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; @@ -157,19 +281,28 @@ DELIMITER ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; -CREATE PROCEDURE `report_heartbeat`(_topicId INT UNSIGNED, _heart VARCHAR(45), _timeoutSeconds INTEGER UNSIGNED) +CREATE DEFINER=`herald`@`%` PROCEDURE `report_heartbeat`(_topicId INT UNSIGNED, _heart VARCHAR(45), _timeoutSeconds INTEGER UNSIGNED) BEGIN DECLARE beating INTEGER DEFAULT 0; START TRANSACTION; - SET beating = EXISTS(SELECT * FROM heart WHERE TopicId = _topicId AND Heart = _heart AND `Status` = `beating`); + SET beating = EXISTS(SELECT * FROM heart WHERE TopicId = _topicId AND Heart = _heart AND `Status` = 'beating'); INSERT INTO heart (TopicId, Heart, `Status`, ExpiryTs) VALUES (_topicId, _heart, 'beating', CURRENT_TIMESTAMP() + INTERVAL _timeoutSeconds SECOND) ON DUPLICATE KEY UPDATE `Status` = 'beating', + LastBeatTs = CURRENT_TIMESTAMP(), ExpiryTs = CURRENT_TIMESTAMP() + INTERVAL _timeoutSeconds SECOND; - SELECT beating; + + IF NOT beating THEN + INSERT INTO heartevent + (TopicId, Heart, Event) + SELECT + _topicId, _heart, 'started'; + END IF; + + SELECT beating; COMMIT; END ;; DELIMITER ; @@ -187,4 +320,4 @@ DELIMITER ; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2022-01-28 17:03:44 +-- Dump completed on 2022-05-13 18:44:48