mirror of
https://github.com/Jetsparrow/jetherald.git
synced 2026-01-20 23:56:08 +03:00
split plan and role, fix lots of small issues, move registration logic to code
This commit is contained in:
parent
95e8d12751
commit
4cdeb8a7c7
12
JetHerald/.config/dotnet-tools.json
Normal file
12
JetHerald/.config/dotnet-tools.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "6.0.5",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -73,7 +73,7 @@ public class LoginController : Controller
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return RedirectToAction("Dashboard", "Dashboard");
|
||||
return RedirectToAction("Index", "Dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -1,13 +1,21 @@
|
||||
@model ViewInvitesModel
|
||||
|
||||
<form asp-controller="AdminTools" asp-action="CreateInvite" method="POST" enctype="application/x-www-form-urlencoded">
|
||||
<label for="planselector">Plan: </label>
|
||||
<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)
|
||||
{
|
||||
<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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user