diff --git a/Module/ModLogs/Entry.cs b/Module/ModLogs/Entry.cs
index 3867b32..4e7918b 100644
--- a/Module/ModLogs/Entry.cs
+++ b/Module/ModLogs/Entry.cs
@@ -1,11 +1,14 @@
-using System;
+using Npgsql;
+using System;
using System.Collections.Generic;
+using System.Data.Common;
using System.Text;
+using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModLogs
{
///
- /// Represents a log entry.
+ /// Represents a log entry in the database.
///
class Entry
{
@@ -44,19 +47,122 @@ namespace Noikoio.RegexBot.Module.ModLogs
///
public ulong? TargetChannel => _channelId;
///
- /// Gets this log entry's 'type', or category.
+ /// Gets this log entry's category.
///
- public string LogType => _type;
+ public string Category => _type;
///
/// Gets the content of this log entry.
///
public string Message => _message;
- public Entry()
+ public Entry(DbDataReader r)
{
- throw new NotImplementedException();
+ // Double-check ordinals if making changes to QueryColumns
+
+ _logId = r.GetInt32(0);
+ _ts = r.GetDateTime(1).ToUniversalTime();
+ unchecked
+ {
+ _guildId = (ulong)r.GetInt64(2);
+ _targetId = (ulong)r.GetInt64(3);
+ if (r.IsDBNull(4)) _invokeId = null;
+ else _invokeId = (ulong)r.GetInt64(4);
+ if (r.IsDBNull(5)) _channelId = null;
+ else _channelId = (ulong)r.GetInt64(5);
+ }
+ _type = r.GetString(6);
+ _message = r.GetString(7);
}
+ // TODO lazy loading of channel, user, etc from caches
+ // TODO methods for updating this log entry(?)
+
// TODO figure out some helper methods to retrieve data of other entities by ID, if it becomes necessary
+
+ #region Queries
+ // Double-check constructor if making changes to this constant
+ const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, category, message";
+
+ ///
+ /// Attempts to look up a log entry with the given ID.
+ ///
+ /// Null if no result.
+ public static async Task QueryIdAsync(ulong guild, int id)
+ {
+ using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync())
+ {
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} "
+ + "WHERE guild_id = @Guild and id = @Id";
+ c.Parameters.Add("@Guild", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild;
+ c.Parameters.Add("@Id", NpgsqlTypes.NpgsqlDbType.Numeric).Value = id;
+ c.Prepare();
+ using (var r = await c.ExecuteReaderAsync())
+ {
+ if (r.Read()) return new Entry(r);
+ else return null;
+ }
+ }
+ }
+ }
+
+ public static async Task> QueryLogAsync
+ (ulong guild,
+ ulong? target = null,
+ ulong? invoker = null,
+ ulong? channel = null,
+ IEnumerable category = null)
+ {
+ // Enforce some limits - can't search too broadly here. Requires this at a minimum:
+ if (target.HasValue == false && invoker.HasValue == false)
+ {
+ throw new ArgumentNullException("Query requires at minimum searching of a target or invoker.");
+ }
+
+ var result = new List();
+ using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync())
+ {
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} WHERE";
+
+ bool and = false;
+ if (target.HasValue)
+ {
+ if (and) c.CommandText += " AND";
+ else and = true;
+ c.CommandText += " target_id = @TargetId";
+ c.Parameters.Add("@TargetId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = target.Value;
+ }
+ if (invoker.HasValue)
+ {
+ if (and) c.CommandText += " AND";
+ else and = true;
+ c.CommandText += " invoke_id = @InvokeId";
+ c.Parameters.Add("@InvokeId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = invoker.Value;
+ }
+ if (channel.HasValue)
+ {
+ if (and) c.CommandText += " AND";
+ else and = true;
+ c.CommandText += " target_channel_id = @ChannelId";
+ c.Parameters.Add("@ChannelId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = channel.Value;
+ }
+ c.Prepare();
+
+ using (var r = await c.ExecuteReaderAsync())
+ {
+ while (r.Read())
+ {
+ result.Add(new Entry(r));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+ #endregion
}
}
diff --git a/Module/ModLogs/Sql.cs b/Module/ModLogs/Sql.cs
new file mode 100644
index 0000000..c500698
--- /dev/null
+++ b/Module/ModLogs/Sql.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Noikoio.RegexBot.Module.ModLogs
+{
+ ///
+ /// Contains common constants and static methods used for accessing the log database.
+ ///
+ class Sql
+ {
+ public const string TableLog = "modlogs_entries";
+ public const string TableLogIncr = TableLog + "_id";
+ public const string TableMsgCache = "modlogs_msgcache";
+
+ static void CreateTables()
+ {
+ using (var db = RegexBot.Config.Database.GetOpenConnectionAsync().GetAwaiter().GetResult())
+ {
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableLog + " ("
+ + "id int primary key, "
+ + "entry_ts timestamptz not null, "
+ + "guild_id bigint not null, "
+ + "target_id bigint not null, "
+ + $"invoke_id bigint null references {EntityCache.Sql.TableUser}.user_id, "
+ + "target_channel_id bigint null, " // TODO channel cache reference?
+ + "category text not null, "
+ + "message text not null, "
+ + $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.Sql.TableUser} (user_id, guild_id)";
+ c.ExecuteNonQuery();
+ }
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = $"CREATE SEQUENCE IF NOT EXISTS {TableLogIncr} "
+ + $"START 100 MAXVALUE {int.MaxValue}";
+ c.ExecuteNonQuery();
+ }
+ }
+ }
+
+ #region Log entry manipulation
+ // what was I doing again
+ #endregion
+ }
+}