Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9aaacc2
migrate from MySQL to PostgreSQL
Pierre-Demessence Apr 1, 2026
a9f1915
chore(k8s): remove MySQL configurations and update PostgreSQL images
Pierre-Demessence Apr 1, 2026
cbeb692
Merge branch 'master' into feature/postgre
Pierre-Demessence Apr 1, 2026
8b80c66
feat(k8s): add AWS credentials for PostgreSQL backups
Pierre-Demessence Apr 1, 2026
805bc64
Update README.md
Pierre-Demessence Apr 3, 2026
9c45b01
docs(README): update database container command in instructions
Pierre-Demessence Apr 3, 2026
44caba0
refactor(database): simplify SQL queries in services
Pierre-Demessence Apr 3, 2026
1811d8d
chore(deps): update Insight.Database to version 8.0.6
Pierre-Demessence Apr 3, 2026
832a135
Merge branch 'master' into feature/postgre
Pierre-Demessence Apr 3, 2026
5877953
chore(k8s): update bot image to specific version 832a135
Pierre-Demessence Apr 3, 2026
6dbfa78
chore(k8s): remove unnecessary middleware annotation from Ingress
Pierre-Demessence Apr 3, 2026
bec3057
feat(pgadmin): add configuration for PostgreSQL connection
Pierre-Demessence Apr 3, 2026
20cfffa
feat(pgadmin): add PostgreSQL server configuration and init container…
Pierre-Demessence Apr 3, 2026
f0c6e41
feat(pgadmin): add pgAdmin server configuration and credentials for d…
Pierre-Demessence Apr 3, 2026
17fc156
refactor(casino): change types from ulong to long since postgre doesn…
Pierre-Demessence Apr 3, 2026
ef5497c
fix(bot): update bot image to latest version 17fc156
Pierre-Demessence Apr 3, 2026
f30e121
feat(adminer): add deployment, service, and ingress for adminer for dev
Pierre-Demessence Apr 3, 2026
e9c7811
Added adminer for prod and docker-compose
Pierre-Demessence Apr 3, 2026
0c0f359
Removed pgadmin
Pierre-Demessence Apr 3, 2026
1ac55cd
feat(migration): add MySQL to PostgreSQL migration plan
Pierre-Demessence Apr 3, 2026
38c6ce1
feat(migration): update pgloader script to use placeholders for crede…
Pierre-Demessence Apr 3, 2026
af1c2f5
refactor(casino): align token_transactions PG schema with MySQL to pr…
Pierre-Demessence Apr 4, 2026
9e0af15
docs: add comprehensive MySQL-to-PostgreSQL code changes reference
Pierre-Demessence Apr 4, 2026
d6223c0
feat(k8s): include token_transactions in pgloader migration configs
Pierre-Demessence Apr 4, 2026
7dbc47b
chore(k8s): update bot image to version d6223c0
Pierre-Demessence Apr 4, 2026
46ede2c
rework doc
Pierre-Demessence Apr 4, 2026
9203c79
fix(docs): clarify handling of birthday column in migration
Pierre-Demessence Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DiscordBot/DiscordBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Insight.Database" Version="8.0.5" />
<PackageReference Include="Insight.Database" Version="8.0.6" />
<PackageReference Include="Insight.Database.Providers.PostgreSQL" Version="8.0.6" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="Discord.Net" Version="3.17.4" />
<PackageReference Include="Magick.NET-Q8-x64" Version="7.5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="MySql.Data" Version="9.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pathoschild.NaturalTimeParser" Version="0.2.0-alpha" />
<PackageReference Include="System.ServiceModel.Syndication" Version="9.0.6" />
Expand Down
37 changes: 28 additions & 9 deletions DiscordBot/Domain/Casino/CasinoUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class CasinoUser
{
public int Id { get; set; }
public required string UserID { get; set; }
public ulong Tokens { get; set; }
public long Tokens { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime LastDailyReward { get; set; }
Expand All @@ -16,8 +16,17 @@ public class TokenTransaction
{
public int Id { get; set; }
public required string UserID { get; set; }
public long Amount { get; set; } // Can be negative for spending
public TransactionType Type { get; set; } // Enum for transaction types
public string? TargetUserID { get; set; }
public long Amount { get; set; }
public string TransactionType { get; set; } = "";

// Computed from TransactionType string — not mapped to DB
[JsonIgnore]
public TransactionKind Kind
{
get => Enum.TryParse<TransactionKind>(TransactionType, true, out var result) ? result : TransactionKind.Admin;
set => TransactionType = value.ToString();
}

private Dictionary<string, string>? _details;

Expand All @@ -28,17 +37,26 @@ public Dictionary<string, string>? Details
set => _details = value;
}

// This property will be mapped to the database JSON column
public string? DetailsJson
// Maps to DB column "description" (text). Stores Details dict as JSON, deserializes with fallback for plain text (from MySQL migration)
public string? Description
{
get => Details != null && Details.Any() ? JsonConvert.SerializeObject(Details) : null;
set => Details = !string.IsNullOrEmpty(value) ? JsonConvert.DeserializeObject<Dictionary<string, string>>(value) : new Dictionary<string, string>();
set
{
if (string.IsNullOrEmpty(value))
{
_details = new Dictionary<string, string>();
return;
}
try { _details = JsonConvert.DeserializeObject<Dictionary<string, string>>(value); }
catch (JsonException) { _details = new Dictionary<string, string> { ["text"] = value }; }
}
}

public DateTime CreatedAt { get; set; }
}

public enum TransactionType
public enum TransactionKind
{
TokenInitialisation,
DailyReward,
Expand Down Expand Up @@ -96,8 +114,9 @@ public static class CasinoProps
// TokenTransaction properties
public const string TransactionId = nameof(TokenTransaction.Id);
public const string TransactionUserID = nameof(TokenTransaction.UserID);
public const string TargetUserID = nameof(TokenTransaction.TargetUserID);
public const string Amount = nameof(TokenTransaction.Amount);
public const string TransactionType = nameof(TokenTransaction.Type);
public const string Details = nameof(TokenTransaction.DetailsJson);
public const string TransactionType = nameof(TokenTransaction.TransactionType);
public const string Details = nameof(TokenTransaction.Description);
public const string TransactionCreatedAt = nameof(TokenTransaction.CreatedAt);
}
6 changes: 3 additions & 3 deletions DiscordBot/Domain/Casino/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public interface ICasinoGame
public IReadOnlyList<(GamePlayer player, long payout)> EndGame();

public abstract GamePlayerResult GetPlayerGameResult(GamePlayer player);
public abstract long CalculatePayout(GamePlayer player, ulong totalPot);
public abstract long CalculatePayout(GamePlayer player, long totalPot);

public void Reset();

Expand Down Expand Up @@ -149,8 +149,8 @@ public void StartGame(IEnumerable<GamePlayer> players)
// Default implementation does nothing, override in specific games if needed
protected virtual void FinalizeGame(List<GamePlayer> players) { }
public abstract GamePlayerResult GetPlayerGameResult(GamePlayer player);
protected ulong GetTotalPot => (ulong)Players.Sum(p => (long)p.Bet);
public abstract long CalculatePayout(GamePlayer player, ulong totalPot);
protected long GetTotalPot => Players.Sum(p => p.Bet);
public abstract long CalculatePayout(GamePlayer player, long totalPot);

/// <summary>
/// Determines if the game should enter the FINISHED state. <br />
Expand Down
2 changes: 1 addition & 1 deletion DiscordBot/Domain/Casino/GamePlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class GamePlayer
/// <summary>
/// The bet amount placed by the player
/// </summary>
public required ulong Bet { get; set; }
public required long Bet { get; set; }
/// <summary>
/// The final result of the player in the game (won, lost, tie)
/// </summary>
Expand Down
10 changes: 5 additions & 5 deletions DiscordBot/Domain/Casino/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public interface IGameSession
public Type ActionType { get; }

public DiscordGamePlayer? GetPlayer(ulong userId);
public bool AddPlayer(ulong userId, ulong bet);
public bool AddPlayer(ulong userId, long bet);
public bool AddPlayerAI();
public void RemovePlayer(ulong userId);
public void RemovePlayerAI();
public void SetPlayerReady(ulong userId, bool isReady);
public void SetPlayerBet(ulong userId, ulong bet);
public void SetPlayerBet(ulong userId, long bet);
public void DoPlayerAction(ulong userId, Enum action);

public bool HasNextDealerAction();
Expand Down Expand Up @@ -75,7 +75,7 @@ public GameSession(TGame game, int maxSeats)
/// </summary>
public bool CanStart => Game.State == GameState.NotStarted && PlayerCount >= Game.MinPlayers && AllPlayersReady;

public ulong GetTotalPot => (ulong)Players.Sum(p => (long)p.Bet);
public long GetTotalPot => Players.Sum(p => p.Bet);

public bool ShouldFinish() => Game.ShouldFinish();

Expand All @@ -94,7 +94,7 @@ public void Reset()

public DiscordGamePlayer? GetPlayer(ulong userId) => Players.FirstOrDefault(p => p.UserId == userId);

public bool AddPlayer(ulong userId, ulong bet)
public bool AddPlayer(ulong userId, long bet)
{
if (!CanJoin) return false;
if (Players.Any(p => p.UserId == userId)) return false; // Player already in game
Expand Down Expand Up @@ -156,7 +156,7 @@ public void SetPlayerReady(ulong userId, bool ready = true)
if (ready && CanStart) Game.StartGame(Players);
}

public void SetPlayerBet(ulong userId, ulong bet)
public void SetPlayerBet(ulong userId, long bet)
{
if (Game.State != GameState.NotStarted) return; // Cannot change bet after the game has started
var player = GetPlayer(userId);
Expand Down
6 changes: 3 additions & 3 deletions DiscordBot/Domain/Casino/Games/Cards/Blackjack/Blackjack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ public override GamePlayerResult GetPlayerGameResult(GamePlayer player)
return GamePlayerResult.NoResult;
}

public override long CalculatePayout(GamePlayer player, ulong _totalPot)
public override long CalculatePayout(GamePlayer player, long _totalPot)
{
return player.Result switch
{
GamePlayerResult.Won => (long)player.Bet,
GamePlayerResult.Lost => -(long)player.Bet,
GamePlayerResult.Won => player.Bet,
GamePlayerResult.Lost => -player.Bet,
GamePlayerResult.Tie => 0,
_ => 0
};
Expand Down
11 changes: 5 additions & 6 deletions DiscordBot/Domain/Casino/Games/Cards/Poker/Poker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ public override GamePlayerResult GetPlayerGameResult(GamePlayer player)
return winners.Any(w => w.player == player) ? GamePlayerResult.Won : GamePlayerResult.Lost;
}

public override long CalculatePayout(GamePlayer player, ulong totalPot)
public override long CalculatePayout(GamePlayer player, long totalPot)
{
var result = GetPlayerGameResult(player);
if (result == GamePlayerResult.Lost)
return -(long)player.Bet;
return -player.Bet;

// Calculate winner's share
var allHands = Players.Where(p => GameData[p].FinalHand != null)
Expand All @@ -244,12 +244,11 @@ public override long CalculatePayout(GamePlayer player, ulong totalPot)

if (winner.player != null)
{
// Winner gets their share of the total pot minus their original bet
var winnings = (long)(totalPot * (ulong)winner.share);
return winnings - (long)player.Bet;
var winnings = (long)(totalPot * winner.share);
return winnings - player.Bet;
}

return -(long)player.Bet;
return -player.Bet;
}

public override bool ShouldFinish() => State == GameState.InProgress && Players.All(p => GameData[p].HasDiscarded);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ public override GamePlayerResult GetPlayerGameResult(GamePlayer player)
return playerWins ? GamePlayerResult.Won : GamePlayerResult.Lost;
}

public override long CalculatePayout(GamePlayer player, ulong totalPot)
public override long CalculatePayout(GamePlayer player, long totalPot)
{
return player.Result switch
{
GamePlayerResult.Won => (long)totalPot - (long)player.Bet, // Winner gets the total pot minus their bet
GamePlayerResult.Lost => -(long)player.Bet, // Loser loses their bet
GamePlayerResult.Tie => 0, // Tie gets bet back (no loss, no gain)
GamePlayerResult.Won => totalPot - player.Bet,
GamePlayerResult.Lost => -player.Bet,
GamePlayerResult.Tie => 0,
_ => 0
};
}
Expand Down
14 changes: 7 additions & 7 deletions DiscordBot/Domain/ProfileData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ public class ProfileData
public ulong UserId { get; set; }
public string Nickname { get; set; }
public string Username { get; set; }
public uint XpTotal { get; set; }
public uint XpRank { get; set; }
public uint KarmaRank { get; set; }
public uint Karma { get; set; }
public uint Level { get; set; }
public long XpTotal { get; set; }
public long XpRank { get; set; }
public long KarmaRank { get; set; }
public int Karma { get; set; }
public int Level { get; set; }
public double XpLow { get; set; }
public double XpHigh { get; set; }
public uint XpShown { get; set; }
public uint MaxXpShown { get; set; }
public int XpShown { get; set; }
public int MaxXpShown { get; set; }
public float XpPercentage { get; set; }
public Color MainRoleColor { get; set; }
public MagickImage Picture { get; set; }
Expand Down
16 changes: 8 additions & 8 deletions DiscordBot/Extensions/CasinoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public interface ICasinoRepo
// Casino User Operations
[Sql($@"
INSERT INTO {CasinoProps.CasinoTableName} ({CasinoProps.UserID}, {CasinoProps.Tokens}, {CasinoProps.CreatedAt}, {CasinoProps.UpdatedAt}, {CasinoProps.LastDailyReward})
VALUES (@{CasinoProps.UserID}, @{CasinoProps.Tokens}, @{CasinoProps.CreatedAt}, @{CasinoProps.UpdatedAt}, @{CasinoProps.LastDailyReward});
SELECT * FROM {CasinoProps.CasinoTableName} WHERE {CasinoProps.UserID} = @{CasinoProps.UserID}")]
VALUES (@{CasinoProps.UserID}, @{CasinoProps.Tokens}, @{CasinoProps.CreatedAt}, @{CasinoProps.UpdatedAt}, @{CasinoProps.LastDailyReward})
RETURNING *")]
Task<CasinoUser> InsertCasinoUser(CasinoUser user);

[Sql($"SELECT * FROM {CasinoProps.CasinoTableName} WHERE {CasinoProps.UserID} = @userId")]
Expand All @@ -19,10 +19,10 @@ public interface ICasinoRepo
Task<IList<CasinoUser>> GetTopTokenHolders(int limit);

[Sql($"UPDATE {CasinoProps.CasinoTableName} SET {CasinoProps.Tokens} = @tokens, {CasinoProps.UpdatedAt} = @updatedAt WHERE {CasinoProps.UserID} = @userId")]
Task UpdateTokens(string userId, ulong tokens, DateTime updatedAt);
Task UpdateTokens(string userId, long tokens, DateTime updatedAt);

[Sql($"UPDATE {CasinoProps.CasinoTableName} SET {CasinoProps.Tokens} = @tokens, {CasinoProps.UpdatedAt} = @updatedAt, {CasinoProps.LastDailyReward} = @lastDailyReward WHERE {CasinoProps.UserID} = @userId")]
Task UpdateTokensAndDailyReward(string userId, ulong tokens, DateTime updatedAt, DateTime lastDailyReward);
Task UpdateTokensAndDailyReward(string userId, long tokens, DateTime updatedAt, DateTime lastDailyReward);

[Sql($"DELETE FROM {CasinoProps.CasinoTableName} WHERE {CasinoProps.UserID} = @userId")]
Task DeleteCasinoUser(string userId);
Expand All @@ -32,9 +32,9 @@ public interface ICasinoRepo

// Token Transaction Operations
[Sql($@"
INSERT INTO {CasinoProps.TransactionTableName} ({CasinoProps.TransactionUserID}, {CasinoProps.Amount}, {CasinoProps.TransactionType}, {CasinoProps.Details}, {CasinoProps.TransactionCreatedAt})
VALUES (@{CasinoProps.TransactionUserID}, @{CasinoProps.Amount}, @{CasinoProps.TransactionType}, @{CasinoProps.Details}, @{CasinoProps.TransactionCreatedAt});
SELECT * FROM {CasinoProps.TransactionTableName} WHERE {CasinoProps.TransactionId} = LAST_INSERT_ID()")]
INSERT INTO {CasinoProps.TransactionTableName} ({CasinoProps.TransactionUserID}, {CasinoProps.TargetUserID}, {CasinoProps.Amount}, {CasinoProps.TransactionType}, {CasinoProps.Details}, {CasinoProps.TransactionCreatedAt})
VALUES (@{CasinoProps.TransactionUserID}, @{CasinoProps.TargetUserID}, @{CasinoProps.Amount}, @{CasinoProps.TransactionType}, @{CasinoProps.Details}, @{CasinoProps.TransactionCreatedAt})
RETURNING *")]
Task<TokenTransaction> InsertTransaction(TokenTransaction tokenTransaction);

[Sql($"SELECT * FROM {CasinoProps.TransactionTableName} WHERE {CasinoProps.TransactionUserID} = @userId ORDER BY {CasinoProps.TransactionCreatedAt} DESC LIMIT @limit")]
Expand All @@ -47,7 +47,7 @@ public interface ICasinoRepo
Task ClearAllTransactions();

[Sql($"SELECT * FROM {CasinoProps.TransactionTableName} WHERE {CasinoProps.TransactionType} = @transactionType ORDER BY {CasinoProps.TransactionCreatedAt} DESC")]
Task<IList<TokenTransaction>> GetTransactionsOfType(TransactionType transactionType);
Task<IList<TokenTransaction>> GetTransactionsOfType(string transactionType);

// Test connection
[Sql($"SELECT COUNT(*) FROM {CasinoProps.CasinoTableName}")]
Expand Down
6 changes: 3 additions & 3 deletions DiscordBot/Extensions/DBConnectionExtension.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System.Data.Common;
using Insight.Database;
using Npgsql;

namespace DiscordBot.Extensions;

public static class DBConnectionExtension
{
public static async Task<bool> ColumnExists(this DbConnection connection, string tableName, string columnName)
{
// Execute the query `SHOW COLUMNS FROM `{tableName}` LIKE '{columnName}'` and check if any rows are returned
var query = $"SHOW COLUMNS FROM `{tableName}` LIKE '{columnName}'";
var response = await connection.QuerySqlAsync(query);
const string query = "SELECT 1 FROM information_schema.columns WHERE LOWER(table_name) = LOWER(@tableName) AND LOWER(column_name) = LOWER(@columnName)";
var response = await connection.QuerySqlAsync(query, new { tableName, columnName });
return response.Count > 0;
}
}
Loading
Loading