public async Task InvalidateSystem(int systemId)
        {
            if (_cache.TryGetValue <CachedAccount>(KeyForSystem(systemId), out var systemCache))
            {
                // If we have the system cached here, just invalidate for all the accounts we have in the cache
                _logger.Debug("Invalidating cache for system {System} and accounts {Accounts}", systemId, systemCache.Accounts);
                _cache.Remove(KeyForSystem(systemId));
                foreach (var account in systemCache.Accounts)
                {
                    _cache.Remove(KeyForAccount(account));
                }
                return;
            }

            // If we don't, look up the accounts from the database and invalidate *those*

            _cache.Remove(KeyForSystem(systemId));
            using var conn = await _db.Obtain();

            var accounts = (await conn.QueryAsync <ulong>("select uid from accounts where system = @System", new { System = systemId })).ToArray();

            _logger.Debug("Invalidating cache for system {System} and accounts {Accounts}", systemId, accounts);
            foreach (var account in accounts)
            {
                _cache.Remove(KeyForAccount(account));
            }
        }
Esempio n. 2
0
        public async Task <ActionResult <IEnumerable <SwitchesReturn> > > GetSwitches(string hid, [FromQuery(Name = "before")] Instant?before)
        {
            if (before == null)
            {
                before = SystemClock.Instance.GetCurrentInstant();
            }

            var system = await _data.GetSystemByHid(hid);

            if (system == null)
            {
                return(NotFound("System not found."));
            }

            using (var conn = await _conn.Obtain())
            {
                var res = await conn.QueryAsync <SwitchesReturn>(
                    @"select *, array(
                        select members.hid from switch_members, members
                        where switch_members.switch = switches.id and members.id = switch_members.member
                    ) as members from switches
                    where switches.system = @System and switches.timestamp < @Before
                    order by switches.timestamp desc
                    limit 100;", new { System = system.Id, Before = before });

                return(Ok(res));
            }
        }
Esempio n. 3
0
        public async Task <ActionResult <IEnumerable <SwitchesReturn> > > GetSwitches(string hid, [FromQuery(Name = "before")] Instant?before)
        {
            if (before == null)
            {
                before = SystemClock.Instance.GetCurrentInstant();
            }

            var system = await _data.GetSystemByHid(hid);

            if (system == null)
            {
                return(NotFound("System not found."));
            }

            if (!system.FrontHistoryPrivacy.CanAccess(_auth.ContextFor(system)))
            {
                return(StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view front history."));
            }

            using (var conn = await _conn.Obtain())
            {
                var res = await conn.QueryAsync <SwitchesReturn>(
                    @"select *, array(
                        select members.hid from switch_members, members
                        where switch_members.switch = switches.id and members.id = switch_members.member
                    ) as members from switches
                    where switches.system = @System and switches.timestamp < @Before
                    order by switches.timestamp desc
                    limit 100;", new { System = system.Id, Before = before });

                return(Ok(res));
            }
        }
Esempio n. 4
0
 public async Task InvalidateResultsForSystem(PKSystem system)
 {
     _logger.Information("Invalidating proxy cache for system {System}", system.Id);
     using (var conn = await _conn.Obtain())
         foreach (var accountId in await conn.QueryAsync <ulong>("select uid from accounts where system = @Id", system))
         {
             _cache.Remove(GetKey(accountId));
         }
 }
Esempio n. 5
0
        public async Task <ITextChannel> GetLogChannel(IGuild guild)
        {
            using (var conn = await _conn.Obtain())
            {
                var server =
                    await conn.QueryFirstOrDefaultAsync <ServerDefinition>("select * from servers where id = @Id",
                                                                           new { Id = guild.Id });

                if (server?.LogChannel == null)
                {
                    return(null);
                }
                return(await _client.GetChannelAsync(server.LogChannel.Value) as ITextChannel);
            }
        }
Esempio n. 6
0
        public async Task HandleMessage(SocketMessage msg)
        {
            RegisterMessageMetrics(msg);

            // Ignore system messages (member joined, message pinned, etc)
            var arg = msg as SocketUserMessage;

            if (arg == null)
            {
                return;
            }

            // Ignore bot messages
            if (arg.Author.IsBot || arg.Author.IsWebhook)
            {
                return;
            }

            int argPos = 0;

            // Check if message starts with the command prefix
            if (arg.HasStringPrefix("pk;", ref argPos, StringComparison.OrdinalIgnoreCase) ||
                arg.HasStringPrefix("pk!", ref argPos, StringComparison.OrdinalIgnoreCase) ||
                arg.HasMentionPrefix(_client.CurrentUser, ref argPos))
            {
                _logger.Debug("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id);

                // Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string
                var trimStartLengthDiff = arg.Content.Substring(argPos).Length -
                                          arg.Content.Substring(argPos).TrimStart().Length;
                argPos += trimStartLengthDiff;

                // If it does, fetch the sender's system (because most commands need that) into the context,
                // and start command execution
                // Note system may be null if user has no system, hence `OrDefault`
                PKSystem system;
                using (var conn = await _connectionFactory.Obtain())
                    system = await conn.QueryFirstOrDefaultAsync <PKSystem>(
                        "select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system",
                        new { Id = arg.Author.Id });

                await _commands.ExecuteAsync(new PKCommandContext(_client, arg, system, _services), argPos,
                                             _services);
            }
            else
            {
                // If not, try proxying anyway
                try
                {
                    await _proxy.HandleMessageAsync(arg);
                }
                catch (PKError e)
                {
                    await msg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}");
                }
            }
        }
        public static async IAsyncEnumerable <T> QueryStreamAsync <T>(this DbConnectionFactory connFactory, string sql, object param)
        {
            using var conn = await connFactory.Obtain();

            await using var reader = (DbDataReader) await conn.ExecuteReaderAsync(sql, param);

            var parser = reader.GetRowParser <T>();

            while (reader.Read())
            {
                yield return(parser(reader));
            }
        }
Esempio n. 8
0
        private async Task ApplyMigration(int migrationId)
        {
            // migrationId is the *target* version
            using var conn = await _conn.Obtain();

            using var tx = conn.BeginTransaction();

            // See if we even have the info table... if not, we implicitly define the version as -1
            // This means migration 0 will get executed, which ensures we're at a consistent state.
            // *Technically* this also means schema version 0 will be identified as -1, but since we're only doing these
            // checks in the above for loop, this doesn't matter.
            var hasInfoTable = await conn.QuerySingleOrDefaultAsync <int>("select count(*) from information_schema.tables where table_name = 'info'") == 1;

            int currentVersion;

            if (hasInfoTable)
            {
                currentVersion = await conn.QuerySingleOrDefaultAsync <int>("select schema_version from info");
            }
            else
            {
                currentVersion = -1;
            }

            if (currentVersion >= migrationId)
            {
                return; // Don't execute the migration if we're already at the target version.
            }
            using var stream = typeof(SchemaService).Assembly.GetManifestResourceStream($"PluralKit.Core.Migrations.{migrationId}.sql");
            if (stream == null)
            {
                throw new ArgumentException("Invalid migration ID");
            }

            using var reader = new StreamReader(stream);
            var migrationQuery = await reader.ReadToEndAsync();

            _logger.Information("Current schema version is {CurrentVersion}, applying migration {MigrationId}", currentVersion, migrationId);
            await conn.ExecuteAsync(migrationQuery, transaction : tx);

            tx.Commit();

            // If the above migration creates new enum/composite types, we must tell Npgsql to reload the internal type caches
            // This will propagate to every other connection as well, since it marks the global type mapper collection dirty.
            // TODO: find a way to get around the cast to our internal tracker wrapper... this could break if that ever changes
            ((PerformanceTrackingConnection)conn)._impl.ReloadTypes();
        }
Esempio n. 9
0
 public async Task <IEnumerable <PKMember> > GetConflictingProxies(PKSystem system, ProxyTag tag)
 {
     using (var conn = await _conn.Obtain())
         // return await conn.QueryAsync<PKMember>("select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix ilike @Prefix and suffix ilike @Suffix", new
         // {
         //     System = system.Id,
         //     Prefix = tag.Prefix.Replace("%", "\\%") + "%",
         //     Suffix = "%" + tag.Suffix.Replace("%", "\\%")
         // });
         return(await conn.QueryAsync <PKMember>("select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix = @Prefix and suffix = @Suffix", new
         {
             System = system.Id,
             Prefix = tag.Prefix,
             Suffix = tag.Suffix
         }));
 }
Esempio n. 10
0
        public async Task HandleMessage(SocketMessage arg)
        {
            if (_client.GetShardFor((arg.Channel as IGuildChannel)?.Guild).ConnectionState != ConnectionState.Connected)
            {
                return; // Discard messages while the bot "catches up" to avoid unnecessary CPU pressure causing timeouts
            }
            RegisterMessageMetrics(arg);

            // Ignore system messages (member joined, message pinned, etc)
            var msg = arg as SocketUserMessage;

            if (msg == null)
            {
                return;
            }

            // Ignore bot messages
            if (msg.Author.IsBot || msg.Author.IsWebhook)
            {
                return;
            }

            int argPos = -1;

            // Check if message starts with the command prefix
            if (msg.Content.StartsWith("pk;", StringComparison.InvariantCultureIgnoreCase))
            {
                argPos = 3;
            }
            else if (msg.Content.StartsWith("pk!", StringComparison.InvariantCultureIgnoreCase))
            {
                argPos = 3;
            }
            else if (Utils.HasMentionPrefix(msg.Content, ref argPos, out var id)) // Set argPos to the proper value
            {
                if (id != _client.CurrentUser.Id)                                 // But undo it if it's someone else's ping
                {
                    argPos = -1;
                }
            }

            // If it does, try executing a command
            if (argPos > -1)
            {
                _logger.Verbose("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id);

                // Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string
                var trimStartLengthDiff = msg.Content.Substring(argPos).Length -
                                          msg.Content.Substring(argPos).TrimStart().Length;
                argPos += trimStartLengthDiff;

                // If it does, fetch the sender's system (because most commands need that) into the context,
                // and start command execution
                // Note system may be null if user has no system, hence `OrDefault`
                PKSystem system;
                using (var conn = await _connectionFactory.Obtain())
                    system = await conn.QueryFirstOrDefaultAsync <PKSystem>(
                        "select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system",
                        new { Id = msg.Author.Id });

                try
                {
                    await _tree.ExecuteCommand(new Context(_services, msg, argPos, system));
                }
                catch (Exception e)
                {
                    await HandleCommandError(msg, e);

                    // HandleCommandError only *reports* the error, we gotta pass it through to the parent
                    // error handler by rethrowing:
                    throw;
                }
            }
            else
            {
                // If not, try proxying anyway
                try
                {
                    await _proxy.HandleMessageAsync(msg);
                }
                catch (PKError e)
                {
                    await arg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}");
                }
            }
        }