Add basic API, only with system endpoints
This commit is contained in:
		@@ -1,55 +0,0 @@
 | 
			
		||||
using System.Data;
 | 
			
		||||
using Dapper;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
using Npgsql;
 | 
			
		||||
 | 
			
		||||
namespace PluralKit
 | 
			
		||||
{
 | 
			
		||||
    public static class DatabaseUtils
 | 
			
		||||
    {
 | 
			
		||||
        public static void Init()
 | 
			
		||||
        {
 | 
			
		||||
            // Dapper by default tries to pass ulongs to Npgsql, which rejects them since PostgreSQL technically
 | 
			
		||||
            // doesn't support unsigned types on its own.
 | 
			
		||||
            // Instead we add a custom mapper to encode them as signed integers instead, converting them back and forth.
 | 
			
		||||
            SqlMapper.RemoveTypeMap(typeof(ulong));
 | 
			
		||||
            SqlMapper.AddTypeHandler<ulong>(new UlongEncodeAsLongHandler());
 | 
			
		||||
            Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
 | 
			
		||||
 | 
			
		||||
            // Also, use NodaTime. it's good.
 | 
			
		||||
            NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
 | 
			
		||||
            // With the thing we add above, Npgsql already handles NodaTime integration
 | 
			
		||||
            // This makes Dapper confused since it thinks it has to convert it anyway and doesn't understand the types
 | 
			
		||||
            // So we add a custom type handler that literally just passes the type through to Npgsql
 | 
			
		||||
            SqlMapper.AddTypeHandler(new PassthroughTypeHandler<Instant>());
 | 
			
		||||
            SqlMapper.AddTypeHandler(new PassthroughTypeHandler<LocalDate>());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        class UlongEncodeAsLongHandler : SqlMapper.TypeHandler<ulong>
 | 
			
		||||
        {
 | 
			
		||||
            public override ulong Parse(object value)
 | 
			
		||||
            {
 | 
			
		||||
                // Cast to long to unbox, then to ulong (???)
 | 
			
		||||
                return (ulong)(long)value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public override void SetValue(IDbDataParameter parameter, ulong value)
 | 
			
		||||
            {
 | 
			
		||||
                parameter.Value = (long)value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        class PassthroughTypeHandler<T> : SqlMapper.TypeHandler<T>
 | 
			
		||||
        {
 | 
			
		||||
            public override void SetValue(IDbDataParameter parameter, T value)
 | 
			
		||||
            {
 | 
			
		||||
                parameter.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public override T Parse(object value)
 | 
			
		||||
            {
 | 
			
		||||
                return (T) value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Dapper.Contrib.Extensions;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
using NodaTime.Text;
 | 
			
		||||
 | 
			
		||||
@@ -6,39 +7,38 @@ namespace PluralKit
 | 
			
		||||
{
 | 
			
		||||
    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 Instant Created { get; set; }
 | 
			
		||||
        public string UiTz { get; set; }
 | 
			
		||||
        [Key] [JsonIgnore] public int Id { get; set; }
 | 
			
		||||
        [JsonProperty("id")] public string Hid { get; set; }
 | 
			
		||||
        [JsonProperty("name")] public string Name { get; set; }
 | 
			
		||||
        [JsonProperty("description")] public string Description { get; set; }
 | 
			
		||||
        [JsonProperty("tag")] public string Tag { get; set; }
 | 
			
		||||
        [JsonProperty("avatar_url")] public string AvatarUrl { get; set; }
 | 
			
		||||
        [JsonIgnore] public string Token { get; set; }
 | 
			
		||||
        [JsonProperty("created")] public Instant Created { get; set; }
 | 
			
		||||
        [JsonProperty("tz")] public string UiTz { get; set; }
 | 
			
		||||
 | 
			
		||||
        public int MaxMemberNameLength => Tag != null ? 32 - Tag.Length - 1 : 32;
 | 
			
		||||
        [JsonIgnore] public int MaxMemberNameLength => Tag != null ? 32 - Tag.Length - 1 : 32;
 | 
			
		||||
 | 
			
		||||
        public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz);
 | 
			
		||||
        [JsonIgnore] public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 LocalDate? Birthday { get; set; }
 | 
			
		||||
        public string Pronouns { get; set; }
 | 
			
		||||
        public string Description { get; set; }
 | 
			
		||||
        public string Prefix { get; set; }
 | 
			
		||||
        public string Suffix { get; set; }
 | 
			
		||||
        public Instant Created { get; set; }
 | 
			
		||||
        [JsonIgnore] public int Id { get; set; }
 | 
			
		||||
        [JsonProperty("id")] public string Hid { get; set; }
 | 
			
		||||
        [JsonIgnore] public int System { get; set; }
 | 
			
		||||
        [JsonProperty("color")] public string Color { get; set; }
 | 
			
		||||
        [JsonProperty("avatar_url")] public string AvatarUrl { get; set; }
 | 
			
		||||
        [JsonProperty("name")] public string Name { get; set; }
 | 
			
		||||
        [JsonProperty("birthday")] public LocalDate? Birthday { get; set; }
 | 
			
		||||
        [JsonProperty("pronouns")] public string Pronouns { get; set; }
 | 
			
		||||
        [JsonProperty("description")] public string Description { get; set; }
 | 
			
		||||
        [JsonProperty("prefix")] public string Prefix { get; set; }
 | 
			
		||||
        [JsonProperty("suffix")] public string Suffix { get; set; }
 | 
			
		||||
        [JsonProperty("created")] public Instant 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
 | 
			
		||||
        [JsonIgnore] public string BirthdayString
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
@@ -50,8 +50,8 @@ namespace PluralKit
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool HasProxyTags => Prefix != null || Suffix != null;
 | 
			
		||||
        public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}";
 | 
			
		||||
        [JsonIgnore] public bool HasProxyTags => Prefix != null || Suffix != null;
 | 
			
		||||
        [JsonIgnore] public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class PKSwitch
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,15 @@
 | 
			
		||||
      <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.4" />
 | 
			
		||||
      <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
 | 
			
		||||
      <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
 | 
			
		||||
      <PackageReference Include="NodaTime" Version="3.0.0-alpha01" />
 | 
			
		||||
      <PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.2.0" />
 | 
			
		||||
      <PackageReference Include="Npgsql" Version="4.0.6" />
 | 
			
		||||
      <PackageReference Include="Npgsql.NodaTime" Version="4.0.6" />
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
      <None Remove="db_schema.sql" />
 | 
			
		||||
      <EmbeddedResource Include="db_schema.sql" />
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,64 +1,19 @@
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.IO;
 | 
			
		||||
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
 | 
			
		||||
            )");
 | 
			
		||||
        public static async Task CreateTables(IDbConnection connection)
 | 
			
		||||
        {
 | 
			
		||||
            // Load the schema from disk (well, embedded resource) and execute the commands in there
 | 
			
		||||
            using (var stream = typeof(Schema).Assembly.GetManifestResourceStream("PluralKit.Core.db_schema.sql"))
 | 
			
		||||
            using (var reader = new StreamReader(stream))
 | 
			
		||||
            {
 | 
			
		||||
                var result = await reader.ReadToEndAsync();
 | 
			
		||||
                await connection.ExecuteAsync(result);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,17 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using Dapper;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
using NodaTime.Serialization.JsonNet;
 | 
			
		||||
using NodaTime.Text;
 | 
			
		||||
using Npgsql;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace PluralKit
 | 
			
		||||
@@ -258,4 +265,70 @@ namespace PluralKit
 | 
			
		||||
        public static IPattern<LocalDateTime> LocalDateTimeFormat = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss");
 | 
			
		||||
        public static IPattern<ZonedDateTime> ZonedDateTimeFormat = ZonedDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss x", DateTimeZoneProviders.Tzdb);
 | 
			
		||||
    }
 | 
			
		||||
    public static class InitUtils
 | 
			
		||||
    {
 | 
			
		||||
        public static IConfigurationBuilder BuildConfiguration(string[] args) => new ConfigurationBuilder()
 | 
			
		||||
            .SetBasePath(Directory.GetCurrentDirectory())
 | 
			
		||||
            .AddJsonFile("pluralkit.conf", true)
 | 
			
		||||
            .AddEnvironmentVariables()
 | 
			
		||||
            .AddCommandLine(args);
 | 
			
		||||
 | 
			
		||||
        public static void Init()
 | 
			
		||||
        {
 | 
			
		||||
            InitDatabase();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private static void InitDatabase()
 | 
			
		||||
        {
 | 
			
		||||
            // Dapper by default tries to pass ulongs to Npgsql, which rejects them since PostgreSQL technically
 | 
			
		||||
            // doesn't support unsigned types on its own.
 | 
			
		||||
            // Instead we add a custom mapper to encode them as signed integers instead, converting them back and forth.
 | 
			
		||||
            SqlMapper.RemoveTypeMap(typeof(ulong));
 | 
			
		||||
            SqlMapper.AddTypeHandler<ulong>(new UlongEncodeAsLongHandler());
 | 
			
		||||
            Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
 | 
			
		||||
 | 
			
		||||
            // Also, use NodaTime. it's good.
 | 
			
		||||
            NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
 | 
			
		||||
            // With the thing we add above, Npgsql already handles NodaTime integration
 | 
			
		||||
            // This makes Dapper confused since it thinks it has to convert it anyway and doesn't understand the types
 | 
			
		||||
            // So we add a custom type handler that literally just passes the type through to Npgsql
 | 
			
		||||
            SqlMapper.AddTypeHandler(new PassthroughTypeHandler<Instant>());
 | 
			
		||||
            SqlMapper.AddTypeHandler(new PassthroughTypeHandler<LocalDate>());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static JsonSerializerSettings BuildSerializerSettings() => new JsonSerializerSettings().BuildSerializerSettings();
 | 
			
		||||
 | 
			
		||||
        public static JsonSerializerSettings BuildSerializerSettings(this JsonSerializerSettings settings)
 | 
			
		||||
        {
 | 
			
		||||
            settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
 | 
			
		||||
            return settings;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public class UlongEncodeAsLongHandler : SqlMapper.TypeHandler<ulong>
 | 
			
		||||
    {
 | 
			
		||||
        public override ulong Parse(object value)
 | 
			
		||||
        {
 | 
			
		||||
            // Cast to long to unbox, then to ulong (???)
 | 
			
		||||
            return (ulong)(long)value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void SetValue(IDbDataParameter parameter, ulong value)
 | 
			
		||||
        {
 | 
			
		||||
            parameter.Value = (long)value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class PassthroughTypeHandler<T> : SqlMapper.TypeHandler<T>
 | 
			
		||||
    {
 | 
			
		||||
        public override void SetValue(IDbDataParameter parameter, T value)
 | 
			
		||||
        {
 | 
			
		||||
            parameter.Value = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override T Parse(object value)
 | 
			
		||||
        {
 | 
			
		||||
            return (T) value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								PluralKit.Core/db_schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								PluralKit.Core/db_schema.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
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'
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table if not exists accounts
 | 
			
		||||
(
 | 
			
		||||
    uid    bigint primary key,
 | 
			
		||||
    system serial not null references systems (id) on delete cascade
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table if not exists webhooks
 | 
			
		||||
(
 | 
			
		||||
    channel bigint primary key,
 | 
			
		||||
    webhook bigint not null,
 | 
			
		||||
    token   text   not null
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table if not exists servers
 | 
			
		||||
(
 | 
			
		||||
    id          bigint primary key,
 | 
			
		||||
    log_channel bigint
 | 
			
		||||
);
 | 
			
		||||
		Reference in New Issue
	
	Block a user