feat: moved to C# thanks to deepseek

This commit is contained in:
didier committed 2026-07-12 21:01:09 -04:00
1 parent ab81a1c4be
commit 29ed46aa7f
34 files changed
+1583 -14

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
using Phenix.Models;
namespace Phenix.Services;
public interface IGameService
{
Task Init();
Task<List<Game>> GetAllGames();
Task<Game?> GetGameById(string id);
Task<(Game? Game, string Error)> CreateNewGame(string date, string time, string home, string away);
Task<bool> UpdateGame(string id, int? homeScore, int? awayScore, bool? rain, bool? practice);
Task<bool> DeleteGameById(string id);
Task<bool> SwitchVisitors(string id);
}
+67
View File
@@ -0,0 +1,67 @@
using System.Security.Cryptography;
using System.Text;
namespace Phenix.Services;
public class LoginService
{
private readonly Dictionary<string, string> _users;
private readonly string _sessionKey;
private static readonly (string User, string Hash)[] OfficialUsers =
[
("marqueur", "34373780cc542a1eddb6a08488ec149323b94d66025421acd98ffc20eb5055a0:6f2fa05777d45a2da9db79b7fd11e3bc69eb1807642ccb53d41a0b9eb71a3311"),
("comite", "8649feff48a50f53448e59ce480db97b5549eef1d92c886c49b5a14ee22648e5:a0017b879c8834d2f34938e7a4fa3f4c7be8f70545b9bb22a28cf56ab2eb26d9"),
];
public LoginService(string sessionKey)
{
_users = OfficialUsers.ToDictionary(x => x.User, x => x.Hash);
_sessionKey = sessionKey;
}
public bool ValidateCredentials(string username, string password)
{
if (!_users.TryGetValue(username, out var hashWithSalt))
return false;
return VerifyPassword(password, hashWithSalt);
}
public string SignSession(long expiresAt)
{
return ShittyHmac(expiresAt.ToString(), _sessionKey);
}
public bool VerifySession(string? expiresAt, string? signature)
{
if (expiresAt is null || signature is null)
return false;
if (!long.TryParse(expiresAt, out var ts))
return false;
if (signature != ShittyHmac(ts.ToString(), _sessionKey))
return false;
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() < ts;
}
private static bool VerifyPassword(string password, string hashWithSalt)
{
var parts = hashWithSalt.Split(':');
if (parts.Length != 2) return false;
var salt = parts[0];
var expected = parts[1];
return ShittyHmac(password, salt) == expected;
}
private static string HashMessage(string message)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(message));
return Convert.ToHexStringLower(bytes);
}
private static string ShittyHmac(string message, string salt)
{
var h1 = HashMessage(message + salt);
var h2 = HashMessage(salt + message);
return HashMessage(h1 + h2);
}
}
+200
View File
@@ -0,0 +1,200 @@
using System.Data.Common;
using Npgsql;
using Phenix.Models;
namespace Phenix.Services;
public class PostgresGameService : IGameService
{
private readonly string _connectionString;
public PostgresGameService(string connectionString)
{
_connectionString = connectionString;
}
public async Task Init()
{
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS games (
id TEXT PRIMARY KEY,
date TEXT NOT NULL,
time TEXT NOT NULL,
home TEXT NOT NULL,
away TEXT NOT NULL,
year INTEGER NOT NULL,
home_score INTEGER,
away_score INTEGER,
practice BOOLEAN NOT NULL DEFAULT FALSE,
rain BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE(date, time)
)
""";
await cmd.ExecuteNonQueryAsync();
}
public async Task<List<Game>> GetAllGames()
{
var games = new List<Game>();
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM games ORDER BY date, time";
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
games.Add(ReadGame(reader));
return games;
}
public async Task<Game?> GetGameById(string id)
{
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM games WHERE id = @id";
cmd.Parameters.AddWithValue("@id", id);
await using var reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
return ReadGame(reader);
return null;
}
public async Task<(Game? Game, string Error)> CreateNewGame(string date, string time, string home, string away)
{
date = CleanDate(date);
time = CleanTime(time);
if (!DateOnly.TryParse(date, out _))
return (null, $"Invalide date format {date}");
if (!TimeOnly.TryParse(time, out _))
return (null, $"Invalide time format {time}");
if (!ValidTeams.Contains(away))
return (null, $"Invalide away format {away}");
if (!ValidTeams.Contains(home))
return (null, $"Invalide home format {home}");
if (home == away)
return (null, $"Home and away teams must be different (received {home} for both)");
var year = DateOnly.Parse(date).Year;
var game = new Game
{
Id = Guid.NewGuid().ToString(),
Date = date,
Time = time,
Home = home,
Away = away,
Year = year,
};
try
{
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
INSERT INTO games (id, date, time, home, away, year)
VALUES (@id, @date, @time, @home, @away, @year)
""";
cmd.Parameters.AddWithValue("@id", game.Id);
cmd.Parameters.AddWithValue("@date", game.Date);
cmd.Parameters.AddWithValue("@time", game.Time);
cmd.Parameters.AddWithValue("@home", game.Home);
cmd.Parameters.AddWithValue("@away", game.Away);
cmd.Parameters.AddWithValue("@year", game.Year);
await cmd.ExecuteNonQueryAsync();
}
catch (PostgresException ex) when (ex.SqlState == "23505")
{
return (null, "game already exists at this date/time");
}
return (game, "");
}
public async Task<bool> UpdateGame(string id, int? homeScore, int? awayScore, bool? rain, bool? practice)
{
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
UPDATE games SET
home_score = COALESCE(@homeScore, home_score),
away_score = COALESCE(@awayScore, away_score),
rain = COALESCE(@rain, rain),
practice = COALESCE(@practice, practice)
WHERE id = @id
""";
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@homeScore", (object?)homeScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@awayScore", (object?)awayScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@rain", rain ?? (object?)DBNull.Value);
cmd.Parameters.AddWithValue("@practice", practice ?? (object?)DBNull.Value);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public async Task<bool> DeleteGameById(string id)
{
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "DELETE FROM games WHERE id = @id";
cmd.Parameters.AddWithValue("@id", id);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public async Task<bool> SwitchVisitors(string id)
{
var game = await GetGameById(id);
if (game is null) return false;
await using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
UPDATE games SET home = @home, away = @away,
home_score = @homeScore, away_score = @awayScore
WHERE id = @id
""";
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@home", game.Away);
cmd.Parameters.AddWithValue("@away", game.Home);
cmd.Parameters.AddWithValue("@homeScore", (object?)game.AwayScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@awayScore", (object?)game.HomeScore ?? DBNull.Value);
return await cmd.ExecuteNonQueryAsync() > 0;
}
private static Game ReadGame(DbDataReader r) => new()
{
Id = r.GetString(r.GetOrdinal("id")),
Date = r.GetString(r.GetOrdinal("date")),
Time = r.GetString(r.GetOrdinal("time")),
Home = r.GetString(r.GetOrdinal("home")),
Away = r.GetString(r.GetOrdinal("away")),
Year = r.GetInt32(r.GetOrdinal("year")),
HomeScore = r.IsDBNull(r.GetOrdinal("home_score")) ? null : r.GetInt32(r.GetOrdinal("home_score")),
AwayScore = r.IsDBNull(r.GetOrdinal("away_score")) ? null : r.GetInt32(r.GetOrdinal("away_score")),
Practice = r.GetBoolean(r.GetOrdinal("practice")),
Rain = r.GetBoolean(r.GetOrdinal("rain")),
};
private static readonly string[] ValidTeams =
["jaune", "rouge", "bleu", "gris", "vert", "noir", "orange", "mauve"];
private static string CleanDate(string raw)
{
var parts = raw.Split('-');
if (parts.Length != 3) return raw;
return $"{parts[0]}-{parts[1].PadLeft(2, '0')}-{parts[2].PadLeft(2, '0')}";
}
private static string CleanTime(string raw)
{
var sep = raw.Contains('h') ? 'h' : ':';
var parts = raw.Split(sep);
if (parts.Length != 2) return raw;
return $"{parts[0].PadLeft(2, '0')}:{parts[1].PadLeft(2, '0')}";
}
}
+200
View File
@@ -0,0 +1,200 @@
using System.Data.Common;
using Microsoft.Data.Sqlite;
using Phenix.Models;
namespace Phenix.Services;
public class SqliteGameService : IGameService
{
private readonly string _connectionString;
public SqliteGameService(string connectionString)
{
_connectionString = connectionString;
}
public async Task Init()
{
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS games (
id TEXT PRIMARY KEY,
date TEXT NOT NULL,
time TEXT NOT NULL,
home TEXT NOT NULL,
away TEXT NOT NULL,
year INTEGER NOT NULL,
home_score INTEGER,
away_score INTEGER,
practice INTEGER NOT NULL DEFAULT 0,
rain INTEGER NOT NULL DEFAULT 0,
UNIQUE(date, time)
)
""";
await cmd.ExecuteNonQueryAsync();
}
public async Task<List<Game>> GetAllGames()
{
var games = new List<Game>();
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM games ORDER BY date, time";
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
games.Add(ReadGame(reader));
return games;
}
public async Task<Game?> GetGameById(string id)
{
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM games WHERE id = @id";
cmd.Parameters.AddWithValue("@id", id);
await using var reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
return ReadGame(reader);
return null;
}
public async Task<(Game? Game, string Error)> CreateNewGame(string date, string time, string home, string away)
{
date = CleanDate(date);
time = CleanTime(time);
if (!DateOnly.TryParse(date, out _))
return (null, $"Invalide date format {date}");
if (!TimeOnly.TryParse(time, out _))
return (null, $"Invalide time format {time}");
if (!ValidTeams.Contains(away))
return (null, $"Invalide away format {away}");
if (!ValidTeams.Contains(home))
return (null, $"Invalide home format {home}");
if (home == away)
return (null, $"Home and away teams must be different (received {home} for both)");
var year = DateOnly.Parse(date).Year;
var game = new Game
{
Id = Guid.NewGuid().ToString(),
Date = date,
Time = time,
Home = home,
Away = away,
Year = year,
};
try
{
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
INSERT INTO games (id, date, time, home, away, year)
VALUES (@id, @date, @time, @home, @away, @year)
""";
cmd.Parameters.AddWithValue("@id", game.Id);
cmd.Parameters.AddWithValue("@date", game.Date);
cmd.Parameters.AddWithValue("@time", game.Time);
cmd.Parameters.AddWithValue("@home", game.Home);
cmd.Parameters.AddWithValue("@away", game.Away);
cmd.Parameters.AddWithValue("@year", game.Year);
await cmd.ExecuteNonQueryAsync();
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 19)
{
return (null, "game already exists at this date/time");
}
return (game, "");
}
public async Task<bool> UpdateGame(string id, int? homeScore, int? awayScore, bool? rain, bool? practice)
{
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
UPDATE games SET
home_score = COALESCE(@homeScore, home_score),
away_score = COALESCE(@awayScore, away_score),
rain = COALESCE(@rain, rain),
practice = COALESCE(@practice, practice)
WHERE id = @id
""";
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@homeScore", (object?)homeScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@awayScore", (object?)awayScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@rain", rain ?? (object?)DBNull.Value);
cmd.Parameters.AddWithValue("@practice", practice ?? (object?)DBNull.Value);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public async Task<bool> DeleteGameById(string id)
{
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "DELETE FROM games WHERE id = @id";
cmd.Parameters.AddWithValue("@id", id);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public async Task<bool> SwitchVisitors(string id)
{
var game = await GetGameById(id);
if (game is null) return false;
await using var conn = new SqliteConnection(_connectionString);
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = """
UPDATE games SET home = @home, away = @away,
home_score = @homeScore, away_score = @awayScore
WHERE id = @id
""";
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@home", game.Away);
cmd.Parameters.AddWithValue("@away", game.Home);
cmd.Parameters.AddWithValue("@homeScore", (object?)game.AwayScore ?? DBNull.Value);
cmd.Parameters.AddWithValue("@awayScore", (object?)game.HomeScore ?? DBNull.Value);
return await cmd.ExecuteNonQueryAsync() > 0;
}
private static Game ReadGame(DbDataReader r) => new()
{
Id = r.GetString(r.GetOrdinal("id")),
Date = r.GetString(r.GetOrdinal("date")),
Time = r.GetString(r.GetOrdinal("time")),
Home = r.GetString(r.GetOrdinal("home")),
Away = r.GetString(r.GetOrdinal("away")),
Year = r.GetInt32(r.GetOrdinal("year")),
HomeScore = r.IsDBNull(r.GetOrdinal("home_score")) ? null : r.GetInt32(r.GetOrdinal("home_score")),
AwayScore = r.IsDBNull(r.GetOrdinal("away_score")) ? null : r.GetInt32(r.GetOrdinal("away_score")),
Practice = r.GetBoolean(r.GetOrdinal("practice")),
Rain = r.GetBoolean(r.GetOrdinal("rain")),
};
private static readonly string[] ValidTeams =
["jaune", "rouge", "bleu", "gris", "vert", "noir", "orange", "mauve"];
private static string CleanDate(string raw)
{
var parts = raw.Split('-');
if (parts.Length != 3) return raw;
return $"{parts[0]}-{parts[1].PadLeft(2, '0')}-{parts[2].PadLeft(2, '0')}";
}
private static string CleanTime(string raw)
{
var sep = raw.Contains('h') ? 'h' : ':';
var parts = raw.Split(sep);
if (parts.Length != 2) return raw;
return $"{parts[0].PadLeft(2, '0')}:{parts[1].PadLeft(2, '0')}";
}
}