#nullable enable using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Dapper; namespace PluralKit.Core { public partial class ModelRepository { public Task GetSystem(IPKConnection conn, SystemId id) => conn.QueryFirstOrDefaultAsync("select * from systems where id = @id", new { id }); public Task GetSystemByAccount(IPKConnection conn, ulong accountId) => conn.QuerySingleOrDefaultAsync( "select systems.* from systems, accounts where accounts.system = systems.id and accounts.uid = @Id", new { Id = accountId }); public Task GetSystemByHid(IPKConnection conn, string hid) => conn.QuerySingleOrDefaultAsync("select * from systems where systems.hid = @Hid", new { Hid = hid.ToLower() }); public Task> GetSystemAccounts(IPKConnection conn, SystemId system) => conn.QueryAsync("select uid from accounts where system = @Id", new { Id = system }); public IAsyncEnumerable GetSystemMembers(IPKConnection conn, SystemId system) => conn.QueryStreamAsync("select * from members where system = @SystemID", new { SystemID = system }); public Task GetSystemMemberCount(IPKConnection conn, SystemId id, PrivacyLevel? privacyFilter = null) { var query = new StringBuilder("select count(*) from members where system = @Id"); if (privacyFilter != null) query.Append($" and member_visibility = {(int)privacyFilter.Value}"); return conn.QuerySingleAsync(query.ToString(), new { Id = id }); } public async Task CreateSystem(IPKConnection conn, string? systemName = null, IPKTransaction? tx = null) { var system = await conn.QuerySingleAsync( "insert into systems (hid, name) values (find_free_system_hid(), @Name) returning *", new { Name = systemName }, transaction: tx); _logger.Information("Created {SystemId}", system.Id); return system; } public Task UpdateSystem(IPKConnection conn, SystemId id, SystemPatch patch, IPKTransaction? tx = null) { _logger.Information("Updated {SystemId}: {@SystemPatch}", id, patch); var (query, pms) = patch.Apply(UpdateQueryBuilder.Update("systems", "id = @id")) .WithConstant("id", id) .Build("returning *"); return conn.QueryFirstAsync(query, pms, transaction: tx); } public async Task AddAccount(IPKConnection conn, SystemId system, ulong accountId) { // We have "on conflict do nothing" since linking an account when it's already linked to the same system is idempotent // This is used in import/export, although the pk;link command checks for this case beforehand await conn.ExecuteAsync("insert into accounts (uid, system) values (@Id, @SystemId) on conflict do nothing", new { Id = accountId, SystemId = system }); _logger.Information("Linked account {UserId} to {SystemId}", accountId, system); } public async Task RemoveAccount(IPKConnection conn, SystemId system, ulong accountId) { await conn.ExecuteAsync("delete from accounts where uid = @Id and system = @SystemId", new { Id = accountId, SystemId = system }); _logger.Information("Unlinked account {UserId} from {SystemId}", accountId, system); } public Task DeleteSystem(IPKConnection conn, SystemId id) { _logger.Information("Deleted {SystemId}", id); return conn.ExecuteAsync("delete from systems where id = @Id", new { Id = id }); } } }