split plan and role, fix lots of small issues, move registration logic to code

This commit is contained in:
Jetsparrow 2022-05-13 18:52:52 +03:00
parent 95e8d12751
commit 4cdeb8a7c7
17 changed files with 283 additions and 140 deletions

View File

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "6.0.5",
"commands": [
"dotnet-ef"
]
}
}
}

View File

@ -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; }
}

View File

@ -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);

View File

@ -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<IActionResult> 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<uint, Plan> Plans { get; set; }
public Dictionary<uint, Role> Roles { get; set; }
}

View File

@ -73,7 +73,7 @@ public class LoginController : Controller
}
catch (ArgumentException)
{
return RedirectToAction("Dashboard", "Dashboard");
return RedirectToAction("Index", "Dashboard");
}
}

View File

@ -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();

View File

@ -27,7 +27,7 @@ public class RegistrationController : Controller
}
public RegistrationController(
ILogger<LoginController> log,
ILogger<RegistrationController> log,
Db db,
IOptionsSnapshot<AuthConfig> 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");
}
}
}

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Dapper.Transaction" Version="2.0.35.2" />
<PackageReference Include="DSharpPlus" Version="4.1.0" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.1" />
@ -15,10 +15,4 @@
<PackageReference Include="Telegram.Bot.Extensions.Polling" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target xsi:type="File" name="allfile" fileName="herald.log"
layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />
<target name="logconsole" xsi:type="Console"
layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=Message,StackTrace,Data:maxInnerExceptionLevel=10}"/>
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<logger name="*" minlevel="Trace" writeTo="logconsole" />
<!--Skip non-critical Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
</rules>
</nlog>

View File

@ -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<IOptions<AuthConfig>>().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!");
}
}

View File

@ -19,6 +19,11 @@ public class Db
return await c.QueryAsync<Plan>("SELECT * FROM plan");
}
public async Task<IEnumerable<Role>> GetRoles()
{
using var c = GetConnection();
return await c.QueryAsync<Role>("SELECT * FROM role");
}
public async Task<IEnumerable<UserInvite>> 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<Topic> GetTopic(string name)
@ -79,11 +87,12 @@ public class Db
public async Task<User> GetUser(string login)
{
using var c = GetConnection();
return await c.QuerySingleOrDefaultAsync<User>(
" SELECT u.*, p.* " +
" FROM user u " +
" LEFT JOIN plan p ON p.PlanId = u.PlanId " +
" WHERE u.Login = @login",
return await c.QuerySingleOrDefaultAsync<User>(@"
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<User> RegisterUserFromInvite(User user, uint inviteId)
public async Task<User> RegisterUser(User user)
{
using var c = GetConnection();
uint userId = await c.QuerySingleOrDefaultAsync<uint>(
"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<uint>(@"
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<User> RegisterUser(User user, string plan)
public async Task RedeemInvite(uint inviteId, uint userId)
{
using var c = GetConnection();
uint userId = await c.QuerySingleOrDefaultAsync<uint>(
"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<UserInvite> GetInviteByCode(string inviteCode)
@ -274,6 +283,6 @@ public class Db
Config = cfg;
}
IOptionsMonitor<ConnectionStrings> Config { get; }
MySqlConnection GetConnection() => new(Config.CurrentValue.DefaultConnection);
public MySqlConnection GetConnection() => new(Config.CurrentValue.DefaultConnection);
}

View File

@ -1,6 +1,6 @@
<ul class="issues-list">
<li><a asp-action="CreateProject">Create new project code</a></li>
<li><a asp-action="ViewUsers">View all registered users</a></li>
<li><a asp-action="ViewInvites">View unredeemed invites</a></li>
<!-- <li><a asp-action="ViewInvites">View unredeemed invites</a></li>
<li><a asp-action="ViewRoles">View all roles</a></li>
-->
</ul>

View File

@ -1,6 +1,13 @@
@model ViewInvitesModel
<form asp-controller="AdminTools" asp-action="CreateInvite" method="POST" enctype="application/x-www-form-urlencoded">
<label for="roleselector">Role:</label>
<select name="roleId" required id="roleselector">
@foreach (var role in Model.Roles.Values)
{
<option value="@role.RoleId">@role.Name</option>
}
</select>
<label for="planselector">Plan:</label>
<select name="planId" required id="planselector">
@foreach (var plan in Model.Plans.Values)
@ -8,6 +15,7 @@
<option value="@plan.PlanId">@plan.Name</option>
}
</select>
<input type="submit" value="Create invite" class="h2 submitpost" style="margin-top:10px; width:initial">
</form>
<br>
@ -17,7 +25,7 @@
@foreach (var invite in Model.Invites)
{
<li>
<span style="font-family:monospace">@invite.InviteCode.Substring(0, 8)... (@Model.Plans[invite.PlanId].Name)</span>
<span style="font-family:monospace">@invite.InviteCode.Substring(0, 8)... r:(@Model.Roles[invite.RoleId].Name) p:(@Model.Plans[invite.PlanId].Name)</span>
<form asp-controller="Admin" asp-action="DeleteInvite" asp-route-inviteId="@invite.UserInviteId" method="POST" style="display:inline">
<input type="submit" value="❌" style="display:inline; color:red;" class="buttonlink">
</form>

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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();
INSERT INTO heartevent
(TopicId, Heart, `Event`)
(TopicId, Heart, Event)
SELECT
TopicId, Heart, 'stopped'
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;
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;
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,18 +281,27 @@ 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;
IF NOT beating THEN
INSERT INTO heartevent
(TopicId, Heart, Event)
SELECT
_topicId, _heart, 'started';
END IF;
SELECT beating;
COMMIT;
END ;;
@ -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