refactor project structure
This commit is contained in:
53
PluralKit.Core/Models.cs
Normal file
53
PluralKit.Core/Models.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PluralKit
|
||||
{
|
||||
[Table("systems")]
|
||||
public class PKSystem
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public string Hid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Tag { get; set; }
|
||||
public string AvatarUrl { get; set; }
|
||||
public string Token { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public string UiTz { get; set; }
|
||||
|
||||
public int MaxMemberNameLength => Tag != null ? 32 - Tag.Length - 1 : 32;
|
||||
}
|
||||
|
||||
[Table("members")]
|
||||
public class PKMember
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Hid { get; set; }
|
||||
public int System { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string AvatarUrl { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DateTime? Birthday { get; set; }
|
||||
public string Pronouns { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Prefix { get; set; }
|
||||
public string Suffix { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
/// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" is hidden
|
||||
public string BirthdayString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Birthday == null) return null;
|
||||
if (Birthday?.Year == 1) return Birthday?.ToString("MMMM dd");
|
||||
return Birthday?.ToString("MMMM dd, yyyy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasProxyTags => Prefix != null || Suffix != null;
|
||||
public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}";
|
||||
}
|
||||
}
|
||||
13
PluralKit.Core/PluralKit.Core.csproj
Normal file
13
PluralKit.Core/PluralKit.Core.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="1.60.6" />
|
||||
<PackageReference Include="Dapper.Contrib" Version="1.60.1" />
|
||||
<PackageReference Include="Npgsql" Version="4.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
64
PluralKit.Core/Schema.cs
Normal file
64
PluralKit.Core/Schema.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Data;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
|
||||
namespace PluralKit {
|
||||
public static class Schema {
|
||||
public static async Task CreateTables(IDbConnection connection) {
|
||||
await connection.ExecuteAsync(@"create table if not exists systems (
|
||||
id serial primary key,
|
||||
hid char(5) unique not null,
|
||||
name text,
|
||||
description text,
|
||||
tag text,
|
||||
avatar_url text,
|
||||
token text,
|
||||
created timestamp not null default (current_timestamp at time zone 'utc'),
|
||||
ui_tz text not null default 'UTC'
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists members (
|
||||
id serial primary key,
|
||||
hid char(5) unique not null,
|
||||
system serial not null references systems(id) on delete cascade,
|
||||
color char(6),
|
||||
avatar_url text,
|
||||
name text not null,
|
||||
birthday date,
|
||||
pronouns text,
|
||||
description text,
|
||||
prefix text,
|
||||
suffix text,
|
||||
created timestamp not null default (current_timestamp at time zone 'utc')
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists accounts (
|
||||
uid bigint primary key,
|
||||
system serial not null references systems(id) on delete cascade
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists messages (
|
||||
mid bigint primary key,
|
||||
channel bigint not null,
|
||||
member serial not null references members(id) on delete cascade,
|
||||
sender bigint not null
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists switches (
|
||||
id serial primary key,
|
||||
system serial not null references systems(id) on delete cascade,
|
||||
timestamp timestamp not null default (current_timestamp at time zone 'utc')
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists switch_members (
|
||||
id serial primary key,
|
||||
switch serial not null references switches(id) on delete cascade,
|
||||
member serial not null references members(id) on delete cascade
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists webhooks (
|
||||
channel bigint primary key,
|
||||
webhook bigint not null,
|
||||
token text not null
|
||||
)");
|
||||
await connection.ExecuteAsync(@"create table if not exists servers (
|
||||
id bigint primary key,
|
||||
log_channel bigint
|
||||
)");
|
||||
}
|
||||
}
|
||||
}
|
||||
136
PluralKit.Core/Stores.cs
Normal file
136
PluralKit.Core/Stores.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PluralKit {
|
||||
public class SystemStore {
|
||||
private IDbConnection conn;
|
||||
|
||||
public SystemStore(IDbConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
public async Task<PKSystem> Create(string systemName = null) {
|
||||
// TODO: handle HID collision case
|
||||
var hid = Utils.GenerateHid();
|
||||
return await conn.QuerySingleAsync<PKSystem>("insert into systems (hid, name) values (@Hid, @Name) returning *", new { Hid = hid, Name = systemName });
|
||||
}
|
||||
|
||||
public async Task Link(PKSystem system, ulong accountId) {
|
||||
await conn.ExecuteAsync("insert into accounts (uid, system) values (@Id, @SystemId)", new { Id = accountId, SystemId = system.Id });
|
||||
}
|
||||
|
||||
public async Task<PKSystem> GetByAccount(ulong accountId) {
|
||||
return await conn.QuerySingleAsync<PKSystem>("select systems.* from systems, accounts where accounts.system = system.id and accounts.uid = @Id", new { Id = accountId });
|
||||
}
|
||||
|
||||
public async Task<PKSystem> GetByHid(string hid) {
|
||||
return await conn.QuerySingleAsync<PKSystem>("select * from systems where systems.hid = @Hid", new { Hid = hid.ToLower() });
|
||||
}
|
||||
|
||||
public async Task<PKSystem> GetByToken(string token) {
|
||||
return await conn.QuerySingleAsync<PKSystem>("select * from systems where token = @Token", new { Token = token });
|
||||
}
|
||||
|
||||
public async Task Save(PKSystem system) {
|
||||
await conn.ExecuteAsync("update systems set name = @Name, description = @Description, tag = @Tag, avatar_url = @AvatarUrl, token = @Token, ui_tz = @UiTz where id = @Id", system);
|
||||
}
|
||||
|
||||
public async Task Delete(PKSystem system) {
|
||||
await conn.ExecuteAsync("delete from systems where id = @Id", system);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ulong>> GetLinkedAccountIds(PKSystem system)
|
||||
{
|
||||
return await conn.QueryAsync<ulong>("select uid from accounts where system = @Id", new { Id = system.Id });
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberStore {
|
||||
private IDbConnection conn;
|
||||
|
||||
public MemberStore(IDbConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
public async Task<PKMember> Create(PKSystem system, string name) {
|
||||
// TODO: handle collision
|
||||
var hid = Utils.GenerateHid();
|
||||
return await conn.QuerySingleAsync<PKMember>("insert into members (hid, system, name) values (@Hid, @SystemId, @Name) returning *", new {
|
||||
Hid = hid,
|
||||
SystemID = system.Id,
|
||||
Name = name
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<PKMember> GetByHid(string hid) {
|
||||
return await conn.QuerySingleOrDefaultAsync<PKMember>("select * from members where hid = @Hid", new { Hid = hid.ToLower() });
|
||||
}
|
||||
|
||||
public async Task<PKMember> GetByName(PKSystem system, string name) {
|
||||
// QueryFirst, since members can (in rare cases) share names
|
||||
return await conn.QueryFirstOrDefaultAsync<PKMember>("select * from members where lower(name) = @Name and system = @SystemID", new { Name = name, SystemID = system.Id });
|
||||
}
|
||||
|
||||
public async Task<ICollection<PKMember>> GetUnproxyableMembers(PKSystem system) {
|
||||
return (await GetBySystem(system))
|
||||
.Where((m) => {
|
||||
var proxiedName = $"{m.Name} {system.Tag}";
|
||||
return proxiedName.Length > 32 || proxiedName.Length < 2;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PKMember>> GetBySystem(PKSystem system) {
|
||||
return await conn.QueryAsync<PKMember>("select * from members where system = @SystemID", new { SystemID = system.Id });
|
||||
}
|
||||
|
||||
public async Task Save(PKMember member) {
|
||||
await conn.ExecuteAsync("update members set name = @Name, description = @Description, color = @Color, avatar_url = @AvatarUrl, birthday = @Birthday, pronouns = @Pronouns, prefix = @Prefix, suffix = @Suffix where id = @Id", member);
|
||||
}
|
||||
|
||||
public async Task Delete(PKMember member) {
|
||||
await conn.ExecuteAsync("delete from members where id = @Id", member);
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageStore {
|
||||
public class StoredMessage {
|
||||
public ulong Mid;
|
||||
public ulong ChannelId;
|
||||
public ulong SenderId;
|
||||
public PKMember Member;
|
||||
public PKSystem System;
|
||||
}
|
||||
|
||||
private IDbConnection _connection;
|
||||
|
||||
public MessageStore(IDbConnection connection) {
|
||||
this._connection = connection;
|
||||
}
|
||||
|
||||
public async Task Store(ulong senderId, ulong messageId, ulong channelId, PKMember member) {
|
||||
await _connection.ExecuteAsync("insert into messages(mid, channel, member, sender) values(@MessageId, @ChannelId, @MemberId, @SenderId)", new {
|
||||
MessageId = messageId,
|
||||
ChannelId = channelId,
|
||||
MemberId = member.Id,
|
||||
SenderId = senderId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<StoredMessage> Get(ulong id) {
|
||||
return (await _connection.QueryAsync<StoredMessage, PKMember, PKSystem, StoredMessage>("select * from messages, members, systems where mid = @Id and messages.member = members.id and systems.id = members.system", (msg, member, system) => {
|
||||
msg.System = system;
|
||||
msg.Member = member;
|
||||
return msg;
|
||||
}, new { Id = id })).FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task Delete(ulong id) {
|
||||
await _connection.ExecuteAsync("delete from messages where mid = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
}
|
||||
28
PluralKit.Core/TaskUtils.cs
Normal file
28
PluralKit.Core/TaskUtils.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralKit {
|
||||
public static class TaskUtils {
|
||||
public static async Task CatchException(this Task task, Action<Exception> handler) {
|
||||
try {
|
||||
await task;
|
||||
} catch (Exception e) {
|
||||
handler(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan? timeout) {
|
||||
// https://stackoverflow.com/a/22078975
|
||||
using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout ?? TimeSpan.FromMilliseconds(-1), timeoutCancellationTokenSource.Token));
|
||||
if (completedTask == task) {
|
||||
timeoutCancellationTokenSource.Cancel();
|
||||
return await task; // Very important in order to propagate exceptions
|
||||
} else {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
PluralKit.Core/Utils.cs
Normal file
32
PluralKit.Core/Utils.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace PluralKit
|
||||
{
|
||||
public static class Utils
|
||||
{
|
||||
public static string GenerateHid()
|
||||
{
|
||||
var rnd = new Random();
|
||||
var charset = "abcdefghijklmnopqrstuvwxyz";
|
||||
string hid = "";
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
hid += charset[rnd.Next(charset.Length)];
|
||||
}
|
||||
return hid;
|
||||
}
|
||||
|
||||
public static string Truncate(this string str, int maxLength, string ellipsis = "...") {
|
||||
if (str.Length < maxLength) return str;
|
||||
return str.Substring(0, maxLength - ellipsis.Length) + ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Emojis {
|
||||
public static readonly string Warn = "\u26A0";
|
||||
public static readonly string Success = "\u2705";
|
||||
public static readonly string Error = "\u274C";
|
||||
public static readonly string Note = "\u2757";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user