refactor project structure

This commit is contained in:
Ske
2019-05-08 00:06:27 +02:00
parent 23d8592394
commit c5d2b7c251
21 changed files with 54 additions and 25 deletions

53
PluralKit.Core/Models.cs Normal file
View 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 ?? ""}";
}
}

View 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
View 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
View 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 });
}
}
}

View 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
View 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";
}
}