text:
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Server.Database.Collections;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database
{
    public class MongoConnector : IDatabaseCollectionProvider
    {
        private static IMongoDatabase _database;
        private ILogger? _logger;
        private MongoConfiguration _config;
        private MongoClient client;

        private static string DATABASE_NAME = "subterfugeDb";

        public MongoConnector(IMongoConfigurationProvider config, ILogger<MongoConnector>? logger)
        {
            _logger = logger;
            _config = config.GetConfiguration();

            _logger?.LogInformation("Configuring MongoDB...");
            _database = ConnectToMongo();

            AddComponent(new UserModelCollection(GetDbCollection<DbUserModel>()));
            AddComponent(new GameConfigurationCollection(GetDbCollection<DbGameLobbyConfiguration>()));
            AddComponent(new RelationshipCollection(GetDbCollection<DbPlayerRelationship>()));
            AddComponent(new GameEventCollection(GetDbCollection<DbGameEvent>()));
            AddComponent(new ChatMessageCollection(GetDbCollection<DbChatMessage>()));
            AddComponent(new GroupCollection(GetDbCollection<DbMessageGroup>()));
            AddComponent(new ServerActionLogCollection(GetDbCollection<DbServerAction>()));
            AddComponent(new ServerExceptionLogCollection(GetDbCollection<DbServerException>()));
            AddComponent(new IpBanCollection(GetDbCollection<DbIpBan>()));
            AddComponent(new GameAnnouncementCollection(GetDbCollection<DbGameAnnouncement>()));

            _logger?.LogInformation("Connected to database.");
            try
            {
                SetupDatabase();
            }
            catch (Exception e)
            {
                _logger.LogError($"Error initializing database connection. {e.Message}: {e.StackTrace}");
                throw;
            }
        }

        public async Task SetupDatabase()
        {
            if (_config.FlushDatabase)
            {
                _logger?.LogInformation("Flushing database");
                await client.DropDatabaseAsync(DATABASE_NAME);
                FlushAll();
            }

            _logger?.LogInformation("Creating indexes");
            await CreateAllIndexes(_logger);

            if (_config.CreateSuperUser)
            {
                _logger?.LogInformation("Creating Super user");
                await CreateSuperUser();
            }

            _logger?.LogInformation("Database configuration complete");
        }

        private IMongoDatabase ConnectToMongo()
        {
            string username = "user";
            string password = "password";
            string mongoDbAuthMechanism = "SCRAM-SHA-1";
            MongoInternalIdentity internalIdentity =
                new MongoInternalIdentity("admin", username);
            PasswordEvidence passwordEvidence = new PasswordEvidence(password);
            MongoCredential mongoCredential =
                new MongoCredential(mongoDbAuthMechanism,
                    internalIdentity, passwordEvidence);

            MongoClientSettings settings = new MongoClientSettings();
            settings.Server = new MongoServerAddress(_config.Host, _config.Port);
            settings.Credential = mongoCredential;
            settings.ApplicationName = "SubterfugeServer";

            client = new MongoClient(settings);
            return client.GetDatabase(DATABASE_NAME);
        }

        private IMongoCollection<T> GetDbCollection<T>()
        {
            return _database.GetCollection<T>(typeof(T).ToString());
        }

        public async Task<DbUserModel> CreateSuperUser()
        {
            DbUserModel dbUserModel = new DbUserModel()
            {
                Id = "1",
                Username = _config.SuperUserUsername,
                PasswordHash = JwtManager.HashString(_config.SuperUserPassword),
                PhoneNumber = "9999999999",
                Claims = new [] { UserClaim.User, UserClaim.Administrator, UserClaim.PhoneVerified }
            };
            await GetDbCollection<DbUserModel>().ReplaceOneAsync(
                it => it.Id == dbUserModel.Id, dbUserModel, new UpdateOptions(){ IsUpsert = true});
            _logger?.LogInformation("SuperUser Username: {username}", _config.SuperUserUsername);
            _logger?.LogInformation("SuperUser Password: {password}", _config.SuperUserPassword);
            return dbUserModel;
        }
    }
}


text:
namespace Subterfuge.Remake.Server.Database;

public class MongoConfiguration
{
    public string Host { get; set; }
    public int Port { get; set; }
    public bool CreateSuperUser { get; set; }
    public string SuperUserUsername { get; set; }
    public string SuperUserPassword { get; set; }
    public bool FlushDatabase { get; set; }
}

public interface IMongoConfigurationProvider
{
    public MongoConfiguration GetConfiguration();
}

public class DefaultMongoConfigurationProviderImpl : IMongoConfigurationProvider
{

    public MongoConfiguration Config { get; set; }

    public MongoConfiguration GetConfiguration()
    {
        return Config;
    }
}

repo_name: Subterfuge-Revived/Remake-Core

repo_language: C#

mime_type: text/plain


text:
using Microsoft.Extensions.Logging;
using MongoDB.Driver.Linq;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace Subterfuge.Remake.Server.Database;

public interface IGenericDatabaseCollection
{
    void Flush();
    Task CreateIndexes();
}

public interface IDatabaseCollection<T> : IGenericDatabaseCollection
{
    Task Upsert(T item);
    Task Delete(T item);
    IMongoQueryable<T> Query();
}

public abstract class IDatabaseCollectionProvider {

    private readonly Dictionary<Type, IGenericDatabaseCollection> _componentMap = new Dictionary<Type, IGenericDatabaseCollection>();

    public void AddComponent<T>(IDatabaseCollection<T> component)
    {
        _componentMap[typeof(T)] = component;
    }

    public IDatabaseCollection<T> GetCollection<T>()
    {
        if (_componentMap.ContainsKey(typeof(T)))
        {
            return _componentMap[typeof(T)] as IDatabaseCollection<T>;
        }
        return null;
    }

    protected async Task CreateAllIndexes(ILogger logger)
    {
        var tasks = new List<Task>();
        foreach (var collection in _componentMap.Values)
        {
            tasks.Add(collection.CreateIndexes());
        }

        var task = Task.WhenAll(tasks);

        try
        {
            await task;
        }
        catch (Exception)
        {
            if (task.Exception != null)
            {
                logger.LogError($"Failed to create index. {task.Exception.Message} {task.Exception.StackTrace}");
                throw task.Exception;
            }
        }
    }

    public void FlushAll()
    {
        foreach (var collection in _componentMap.Values)
        {
            Console.WriteLine("Flushing collection " + collection.GetType());
            // _logger.LogInformation("Flushing collection {collection}", collection.GetType().ToString());
            collection.Flush();
        }
    }
}


text:
using System.Security.Cryptography;

namespace Subterfuge.Remake.Server.Database
{
    public class JwtManager
    {
        public static String HashString(string password)
        {
            // Create salt
            byte[] salt;
            new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

            // Create the Rfc2898DeriveBytes and get the hash value:
            var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
            byte[] hash = pbkdf2.GetBytes(20);

            // Combine salt and password bytes
            byte[] hashBytes = new byte[36];
            Array.Copy(salt, 0, hashBytes, 0, 16);
            Array.Copy(hash, 0, hashBytes, 16, 20);

            // Return value
            return Convert.ToBase64String(hashBytes);
        }

        public static Boolean VerifyHashedStringMatches(string rawString, string expectedHash)
        {
            /* Fetch the stored value */
            string savedPasswordHash = expectedHash;

            /* Extract the bytes */
            byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);

            /* Get the salt */
            byte[] salt = new byte[16];
            Array.Copy(hashBytes, 0, salt, 0, 16);

            /* Compute the hash on the entered password the user entered using the extracted salt */
            var pbkdf2 = new Rfc2898DeriveBytes(rawString, salt, 100000);
            byte[] hash = pbkdf2.GetBytes(20);

            /* Compare the results */
            for (int i=0; i < 20; i++)
                if (hashBytes[i + 16] != hash[i])
                    return false;
            return true;
        }
    }
}


text:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>
        <RootNamespace>Subterfuge.Remake.Server.Database</RootNamespace>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="MongoDB.Driver" Version="2.19.0" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\Core\Models\Models.csproj" />
    </ItemGroup>

</Project>


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class ChatMessageCollection : IDatabaseCollection<DbChatMessage>
{
    private IMongoCollection<DbChatMessage> _collection;

    public ChatMessageCollection(IMongoCollection<DbChatMessage> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbChatMessage item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbChatMessage item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbChatMessage> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync(new List<CreateIndexModel<DbChatMessage>>()
        {
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Hashed(message => message.RoomId)),
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Hashed(message => message.GroupId)),
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Hashed(message => message.SentBy.Id)),
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Ascending(message => message.SentAt)),
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Text(message => message.Message)),
            new CreateIndexModel<DbChatMessage>(Builders<DbChatMessage>.IndexKeys.Ascending(message => message.ExpiresAt), new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbChatMessage>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class IpBanCollection : IDatabaseCollection<DbIpBan>
{
    private IMongoCollection<DbIpBan> _collection;

    public IpBanCollection(IMongoCollection<DbIpBan> collection)
    {
        this._collection = collection;
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbIpBan>.Empty);
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbIpBan>>()
        {
            new CreateIndexModel<DbIpBan>(Builders<DbIpBan>.IndexKeys.Ascending(ban => ban.BannedUntil)),
            new CreateIndexModel<DbIpBan>(keys: Builders<DbIpBan>.IndexKeys.Text(ban => ban.IpAddressOrRegex)),
        });
    }

    public async Task Upsert(DbIpBan item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbIpBan item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbIpBan> Query()
    {
        return _collection.AsQueryable();
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class ServerActionLogCollection : IDatabaseCollection<DbServerAction>
{
    private IMongoCollection<DbServerAction> _collection;

    public ServerActionLogCollection(IMongoCollection<DbServerAction> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbServerAction item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbServerAction item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbServerAction> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbServerAction>>()
        {
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.Id)),
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.Username)),
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.UserId)),
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.RemoteIpAddress)),
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.RequestUrl)),
            new CreateIndexModel<DbServerAction>(Builders<DbServerAction>.IndexKeys.Ascending(action => action.StatusCode)),
            new CreateIndexModel<DbServerAction>(keys: Builders<DbServerAction>.IndexKeys.Ascending(room => room.ExpiresAt), options: new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbServerAction>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class GameConfigurationCollection : IDatabaseCollection<DbGameLobbyConfiguration>
{
    private IMongoCollection<DbGameLobbyConfiguration> _collection;

    public GameConfigurationCollection(IMongoCollection<DbGameLobbyConfiguration> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbGameLobbyConfiguration item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbGameLobbyConfiguration item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbGameLobbyConfiguration> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbGameLobbyConfiguration>>()
        {
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Text(room => room.RoomName)),
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Ascending(room => room.TimeCreated)),
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Hashed(room => room.Creator.Id)),
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Ascending(room => room.GameVersion)),
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Ascending(room => room.RoomStatus)),
            new CreateIndexModel<DbGameLobbyConfiguration>(Builders<DbGameLobbyConfiguration>.IndexKeys.Ascending(room => room.ExpiresAt), options: new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbGameLobbyConfiguration>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class GroupCollection : IDatabaseCollection<DbMessageGroup>
{
    private IMongoCollection<DbMessageGroup> _collection;

    public GroupCollection(IMongoCollection<DbMessageGroup> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbMessageGroup item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbMessageGroup item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbMessageGroup> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbMessageGroup>>()
        {
            new CreateIndexModel<DbMessageGroup>(Builders<DbMessageGroup>.IndexKeys.Hashed(group => group.RoomId)),
            new CreateIndexModel<DbMessageGroup>(keys: Builders<DbMessageGroup>.IndexKeys.Ascending(room => room.ExpiresAt), options: new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbMessageGroup>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class UserModelCollection : IDatabaseCollection<DbUserModel>
{
    private IMongoCollection<DbUserModel> _collection;

    public UserModelCollection(IMongoCollection<DbUserModel> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbUserModel item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbUserModel item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbUserModel> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbUserModel>>()
        {
            new (Builders<DbUserModel>.IndexKeys.Hashed(user => user.DeviceIdentifier)),
            new (Builders<DbUserModel>.IndexKeys.Ascending(user => user.Username)),
            new (Builders<DbUserModel>.IndexKeys.Hashed(user => user.PhoneNumber)),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbUserModel>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class RelationshipCollection : IDatabaseCollection<DbPlayerRelationship>
{
    private IMongoCollection<DbPlayerRelationship> _collection;

    public RelationshipCollection(IMongoCollection<DbPlayerRelationship> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbPlayerRelationship item)
    {
        item.UnixTimeLastUpdated = DateTime.UtcNow;
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbPlayerRelationship item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbPlayerRelationship> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbPlayerRelationship>>()
        {
            new CreateIndexModel<DbPlayerRelationship>(Builders<DbPlayerRelationship>.IndexKeys.Hashed(relation => relation.Player.Id)),
            new CreateIndexModel<DbPlayerRelationship>(Builders<DbPlayerRelationship>.IndexKeys.Hashed(relation => relation.Friend.Id)),
            new CreateIndexModel<DbPlayerRelationship>(Builders<DbPlayerRelationship>.IndexKeys.Ascending(relation => relation.RelationshipStatus)),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbPlayerRelationship>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class GameEventCollection : IDatabaseCollection<DbGameEvent>
{
    private IMongoCollection<DbGameEvent> _collection;

    public GameEventCollection(IMongoCollection<DbGameEvent> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbGameEvent item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbGameEvent item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbGameEvent> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbGameEvent>>()
        {
            new CreateIndexModel<DbGameEvent>(Builders<DbGameEvent>.IndexKeys.Hashed(gameEvent => gameEvent.IssuedBy.Id)),
            new CreateIndexModel<DbGameEvent>(Builders<DbGameEvent>.IndexKeys.Ascending(gameEvent => gameEvent.TimeIssued)),
            new CreateIndexModel<DbGameEvent>(Builders<DbGameEvent>.IndexKeys.Ascending(gameEvent => gameEvent.OccursAtTick)),
            new CreateIndexModel<DbGameEvent>(keys: Builders<DbGameEvent>.IndexKeys.Ascending(room => room.ExpiresAt), options: new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbGameEvent>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class ServerExceptionLogCollection: IDatabaseCollection<DbServerException>
{
    private IMongoCollection<DbServerException> _collection;

    public ServerExceptionLogCollection(IMongoCollection<DbServerException> collection)
    {
        this._collection = collection;
    }

    public async Task Upsert(DbServerException item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbServerException item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbServerException> Query()
    {
        return _collection.AsQueryable();
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync( new List<CreateIndexModel<DbServerException>>()
        {
                new CreateIndexModel<DbServerException>(Builders<DbServerException>.IndexKeys.Hashed(action => action.UserId)),
                new CreateIndexModel<DbServerException>(Builders<DbServerException>.IndexKeys.Ascending(action => action.RequestUri)),
                new CreateIndexModel<DbServerException>(Builders<DbServerException>.IndexKeys.Text(action => action.RemoteIpAddress)),
                new CreateIndexModel<DbServerException>(keys: Builders<DbServerException>.IndexKeys.Ascending(room => room.ExpireAt), options: new CreateIndexOptions()
                {
                    ExpireAfter = TimeSpan.FromSeconds(0),
                }),
        });
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbServerException>.Empty);
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Database.Collections;

public class GameAnnouncementCollection : IDatabaseCollection<DbGameAnnouncement>
{

    private IMongoCollection<DbGameAnnouncement> _collection;

    public GameAnnouncementCollection(IMongoCollection<DbGameAnnouncement> collection)
    {
        this._collection = collection;
    }

    public void Flush()
    {
        _collection.DeleteMany(FilterDefinition<DbGameAnnouncement>.Empty);
    }

    public async Task CreateIndexes()
    {
        await _collection.Indexes.CreateManyAsync(new List<CreateIndexModel<DbGameAnnouncement>>()
        {
            new CreateIndexModel<DbGameAnnouncement>(Builders<DbGameAnnouncement>.IndexKeys.Hashed(announcement => announcement.Id)),
            new CreateIndexModel<DbGameAnnouncement>(Builders<DbGameAnnouncement>.IndexKeys.Text(announcement => announcement.BroadcastTo)),
            new CreateIndexModel<DbGameAnnouncement>(Builders<DbGameAnnouncement>.IndexKeys.Ascending(announcement => announcement.StartsAt)),
            new CreateIndexModel<DbGameAnnouncement>(Builders<DbGameAnnouncement>.IndexKeys.Ascending(announcement => announcement.ExpiresAt), new CreateIndexOptions()
            {
                ExpireAfter = TimeSpan.FromSeconds(0),
            }),
        });
    }

    public async Task Upsert(DbGameAnnouncement item)
    {
        await _collection.ReplaceOneAsync(
            it => it.Id == item.Id,
            item,
            new ReplaceOptions() { IsUpsert = true}
        );
    }

    public async Task Delete(DbGameAnnouncement item)
    {
        await _collection.DeleteOneAsync(it => it.Id == item.Id);
    }

    public IMongoQueryable<DbGameAnnouncement> Query()
    {
        return _collection.AsQueryable();
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbGameEvent
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime TimeIssued { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; } = DateTime.MaxValue;
    public string RoomId { get; set; }
    public SimpleUser IssuedBy { get; set; }
    public int OccursAtTick { get; set; }
    public EventDataType GameEventType { get; set; }

    public String SerializedEventData { get; set; }

    public GameRoomEvent ToGameEventData()
    {
        return new GameRoomEvent()
        {
            Id = Id,
            RoomId = RoomId,
            TimeIssued = TimeIssued,
            IssuedBy = IssuedBy,
            GameEventData = new GameEventData()
            {
                EventDataType = GameEventType,
                OccursAtTick = OccursAtTick,
                SerializedEventData = SerializedEventData
            },
        };
    }

    public static DbGameEvent FromGameEventRequest(SubmitGameEventRequest request, SimpleUser issuer, string roomId)
    {
        return new DbGameEvent()
        {
            RoomId = roomId,
            IssuedBy = issuer,
            OccursAtTick = request.GameEventData.OccursAtTick,
            GameEventType = request.GameEventData.EventDataType,
            SerializedEventData = request.GameEventData.SerializedEventData,
        };
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbMessageGroup
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; } = DateTime.MaxValue;
    public string RoomId { get; set; }
    public List<User> MembersInGroup { get; set; }

    public static DbMessageGroup CreateGroup(string roomId, List<User> membersInGroup)
    {
        return new DbMessageGroup()
        {
            RoomId = roomId,
            MembersInGroup = membersInGroup
        };
    }

    public async Task<MessageGroup> ToMessageGroup(IDatabaseCollection<DbChatMessage> _dbChatMessages)
    {
        return new MessageGroup()
        {
            Id = Id,
            RoomId = RoomId,
            GroupMembers = MembersInGroup,
            Messages = (await _dbChatMessages.Query()
                .Where(message => message.GroupId == Id)
                .Where(message => message.RoomId == RoomId)
                .OrderByDescending(message => message.SentAt)
                .Take(50)
                .ToListAsync())
                .Select(it => it.ToChatMessage())
                .ToList()
        };
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbPlayerRelationship
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime UnixTimeLastUpdated { get; set; } = DateTime.UtcNow;
    public User Player { get; set; }
    public User Friend { get; set; }
    public RelationshipStatus RelationshipStatus { get; set; }

    public User GetOtherUser(string userId)
    {
        return Player.Id == userId ? Friend : Player;
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbChatMessage
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime SentAt { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; } = DateTime.MaxValue;
    public string RoomId { get; set; }
    public string GroupId { get; set; }
    public User SentBy { get; set; }
    public string Message { get; set; }

    public ChatMessage ToChatMessage()
    {
        return new ChatMessage()
        {
            Id = Id,
            SentAt = SentAt,
            RoomId = RoomId,
            GroupId = GroupId,
            SentBy = SentBy.ToSimpleUser(),
            Message = Message,
        };
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbUserModel
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    // Administrative
    public DateTime DateCreated { get; set; } = DateTime.UtcNow;
    public DateTime BannedUntil { get; set; } = DateTime.MinValue;

    public string Username { get; set; } = "";
    public string PasswordHash { get; set; } = "";
    public string DeviceIdentifier { get; set; } = "";
    public string PhoneNumber { get; set; } = "";
    public string DiscordUsername { get; set; } = "";
    public string DiscordVerificationCode { get; set; } = "";
    public UserClaim[] Claims { get; set; } = new[] { UserClaim.User };
    public string PushNotificationIdentifier { get; set; } = "";

    public string DeviceType { get; set; } = "";
    // A list of other accounts that this user has associated as.
    public List<MultiboxAccount> MultiboxAccounts { get; set; } = new List<MultiboxAccount>() { };

    public List<AccountBan> BanHistory { get; set; } = new List<AccountBan>();

    public User ToUser()
    {
        return new User()
        {

            Id = Id,
            Username = Username,
            Claims = Claims,
            DateCreated = DateCreated,
            BannedUntil = BannedUntil,
            Pseudonyms = MultiboxAccounts.Select(it => it.User).ToList(),
        };
    }

    public SimpleUser ToSimpleUser()
    {
        return new SimpleUser()
        {
            Id = Id,
            Username = Username,
        };
    }

    /**
     * This method should only be used to return user data to Administrator level accounts.
     */
    public DetailedUser ToDetailedUser()
    {
        return new DetailedUser()
        {
            Id = Id,
            Username = Username,
            Claims = Claims,
            DateCreated = DateCreated,
            BannedUntil = BannedUntil,
            DeviceIdentifier = JwtManager.HashString(DeviceIdentifier),
            PhoneNumberHash = JwtManager.HashString(PhoneNumber),
            DiscordUsername = DiscordUsername,
            PushNotificationIdentifier = PushNotificationIdentifier,
            DeviceType = DeviceType,
            MultiboxAccounts = MultiboxAccounts,
            BanHistory = BanHistory,
        };
    }

    public static DbUserModel FromRegistrationRequest(AccountRegistrationRequest request)
    {
        return new DbUserModel()
        {
            Username = request.Username,
            PasswordHash = JwtManager.HashString(request.Password),
            DeviceIdentifier = request.DeviceIdentifier,
            PhoneNumber = request.PhoneNumber,
        };
    }

    public bool HasClaim(UserClaim claim)
    {
        return Claims.Contains(claim);
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbGameAnnouncement
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public string Title { get; set; }
    public string Message { get; set; }
    public SimpleUser PostedBy { get; set; }
    public DateTime StartsAt { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(21);
    public string BroadcastTo { get; set; } = "global";

    public static DbGameAnnouncement fromGameAnnouncementRequest(CreateAnnouncementRequest request, SimpleUser PostedBy)
    {
        return new DbGameAnnouncement()
        {
            Title = request.Title,
            Message = request.Message,
            PostedBy = PostedBy,
            BroadcastTo = request.BroadcastTo,
            StartsAt = request.StartsAt,
            ExpiresAt = request.ExpiresAt
        };
    }

    public GameAnnouncement toGameAnnouncement()
    {
        return new GameAnnouncement()
        {
            Id = Id,
            ExpiresAt = ExpiresAt,
            Message = Message,
            Title = Title,
            PostedBy = PostedBy,
            StartsAt = StartsAt,
        };
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbServerException
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime UnixTimeProcessed { get; set; } = DateTime.UtcNow;
    public DateTime ExpireAt { get; set; } = DateTime.UtcNow.AddMonths(12);
    public string? Username { get; set; }
    public string? UserId { get; set; }
    public string? RemoteIpAddress { get; set; }
    public string? HttpMethod { get; set; }
    public string? RequestUri { get; set; }
    public string? RequestBody { get; set; }
    public string? QueryString { get; set; }
    public string? UserAgent { get; set; }
    public string? ExceptionMessage { get; set; }
    public string? ExceptionSource { get; set; }
    public string? StackTrace { get; set; }

    public ServerException ToServerException()
    {
        return new ServerException()
        {
            Id = Id,
            Username = Username,
            UserId = UserId,
            RemoteIpAddress = RemoteIpAddress,
            HttpMethod = HttpMethod,
            RequestUri = RequestUri,
            RequestBody = RequestBody,
            QueryString = QueryString,
            ExceptionMessage = ExceptionMessage,
            ExceptionSource = ExceptionSource,
            StackTrace = StackTrace,
            UnixTimeProcessed = UnixTimeProcessed,
        };
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbIpBan
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime DateApplied { get; set; } = DateTime.UtcNow;
    public string IpAddressOrRegex { get; set; }
    public DateTime BannedUntil { get; set; }
    public string Reason { get; set; }
    public string AdminNotes { get; set; }

    public IpBans ToIpBan()
    {
        return new IpBans()
        {
            Reason = Reason,
            AdminNotes = AdminNotes,
            BannedUntil = BannedUntil,
            DateApplied = DateApplied,
            IpOrRegex = IpAddressOrRegex
        };
    }
}


text:
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbServerAction
{
    public string Id { get; set;  } = Guid.NewGuid().ToString();
    public DateTime TimesAccessed { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddMonths(2);
    public string? Username { get; set; }
    public string? UserId { get; set; }
    public string? RemoteIpAddress { get; set; }
    public string? HttpMethod { get; set; }
    public string? RequestUrl { get; set; }
    public string? UserAgent { get; set; }
    public int? StatusCode { get; set; }

    public ServerAction ToServerAction()
    {
        return new ServerAction()
        {
            Username = Username,
            UserId = UserId,
            RemoteIpAddress = RemoteIpAddress,
            HttpMethod = HttpMethod,
            RequestUrl = RequestUrl,
            StatusCode = StatusCode,
            TimeProcessed = TimesAccessed,
        };
    }
}


text:
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Database.Models;

public class DbGameLobbyConfiguration
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public DateTime TimeCreated { get; set; } = DateTime.UtcNow;
    public DateTime TimeStarted { get; set; }
    public DateTime TimeEnded { get; set; }
    public DateTime ExpiresAt { get; set; } = DateTime.MaxValue;
    public RoomStatus RoomStatus { get; set; } = RoomStatus.Open;
    public User Creator { get; set; }
    public GameSettings GameSettings { get; set; }
    public MapConfiguration MapConfiguration { get; set; }
    public string RoomName { get; set; }
    public GameVersion GameVersion { get; set; } = GameVersion.ALPHA01;
    public List<string> PlayerIdsInLobby { get; set; }

    public Dictionary<string, List<SpecialistTypeId>> PlayerSpecialistDecks { get; set; } = new Dictionary<string, List<SpecialistTypeId>>();

    public static DbGameLobbyConfiguration FromRequest(CreateRoomRequest request, User creator)
    {
        var lobby = new DbGameLobbyConfiguration()
        {
            Creator = creator,
            GameSettings = request.GameSettings,
            MapConfiguration = request.MapConfiguration,
            RoomName = request.RoomName,
            PlayerIdsInLobby = new List<string>() { creator.Id },
        };
        lobby.PlayerSpecialistDecks.Add(creator.Id, request.CreatorSpecialistDeck);
        return lobby;
    }

    public async Task<List<User>> GetPlayersInLobby(IDatabaseCollection<DbUserModel> _dbUserCollection)
    {
        return (await _dbUserCollection.Query()
                .Where(user => PlayerIdsInLobby.Contains(user.Id))
                .ToListAsync())
            .Select(it => it.ToUser())
            .ToList();
    }

    public async Task<GameConfiguration> ToGameConfiguration(IDatabaseCollection<DbUserModel> _dbUserCollection)
    {
        var usersInGame = (await GetPlayersInLobby(_dbUserCollection));

        return new GameConfiguration()
        {
            Id = Id,
            RoomName = RoomName,
            RoomStatus = RoomStatus,
            Creator = Creator,
            GameSettings = GameSettings,
            GameVersion = GameVersion,
            PlayersInLobby = usersInGame,
            TimeCreated = TimeCreated,
            TimeStarted = TimeStarted,
            ExpiresAt = ExpiresAt,
            MapConfiguration = MapConfiguration
        };
    }

}


text:
using Subterfuge.Remake.Api.Client.controllers.account;
using Subterfuge.Remake.Api.Client.controllers.admin;
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Client.controllers.game;
using Subterfuge.Remake.Api.Client.controllers.health;
using Subterfuge.Remake.Api.Client.controllers.social;

namespace Subterfuge.Remake.Api.Client
{
    public class SubterfugeClient
    {
        private SubterfugeHttpClient subsHttpClient;

        // APIs
        public UserControllerClient UserApi;
        public UserRoleClient UserRoles;
        public GameEventClient GameEventClient;
        public GroupClient GroupClient;
        public LobbyClient LobbyClient;
        public HealthClient HealthClient;
        public SocialClient SocialClient;
        public AdminClient AdminClient;
        public GameAnnouncementClient AnnouncementClient;

        /// <summary>
        /// The client constructor.
        ///
        /// By default, this constructor works for local development as well as docker container environments.
        /// The client will look for the `ASPNETCORE_ENVIRONMENT` environment variable.
        /// If the environment variable is `Docker`, the client connects to: "server:5295", otherwise it will connect to "localhost:5295".
        /// If you need to connect to a custom server URL, use the secondary constructor which accepts a base URL.
        /// </summary>
        public SubterfugeClient()
        {
            subsHttpClient = new SubterfugeHttpClient();
            UserApi = new UserControllerClient(subsHttpClient);
            UserRoles = new UserRoleClient(subsHttpClient);
            GameEventClient = new GameEventClient(subsHttpClient);
            GroupClient = new GroupClient(subsHttpClient);
            LobbyClient = new LobbyClient(subsHttpClient);
            HealthClient = new HealthClient(subsHttpClient);
            SocialClient = new SocialClient(subsHttpClient);
            AdminClient = new AdminClient(subsHttpClient);
            AnnouncementClient = new GameAnnouncementClient(subsHttpClient);
        }

        /// <summary>
        /// Creates an instance of a subterfuge client using the specified baseURL.
        /// </summary>
        /// <param name="baseUrl">The server URL to connect to. Should include the protocol, hostname, and port. ex. http://localhost:5295</param>
        public SubterfugeClient(string baseUrl)
        {
            subsHttpClient = new SubterfugeHttpClient(baseUrl);
            UserApi = new UserControllerClient(subsHttpClient);
            UserRoles = new UserRoleClient(subsHttpClient);
            GameEventClient = new GameEventClient(subsHttpClient);
            GroupClient = new GroupClient(subsHttpClient);
            LobbyClient = new LobbyClient(subsHttpClient);
            HealthClient = new HealthClient(subsHttpClient);
            SocialClient = new SocialClient(subsHttpClient);
            AdminClient = new AdminClient(subsHttpClient);
            AnnouncementClient = new GameAnnouncementClient(subsHttpClient);
        }
    }
}


text:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>
        <TargetFramework>netstandard2.0</TargetFramework>
        <RootNamespace>Subterfuge.Remake.Api.Client</RootNamespace>
		<AssemblyVersion>1.0.0</AssemblyVersion>
		<FileVersion>1.0.0</FileVersion>
		<NeutralLanguage>en-CA</NeutralLanguage>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.0.0" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\Core\Models\Models.csproj" />
    </ItemGroup>

</Project>


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.health;

public class HealthClient : ISubterfugeHealthApi
{
    private SubterfugeHttpClient client;

    public HealthClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<PingResponse>> Ping()
    {
        return await client.Get<PingResponse>($"api/Health/Ping", null);
    }

    public async Task<SubterfugeResponse<AuthorizedPingResponse>> AuthorizedPing()
    {
        return await client.Get<AuthorizedPingResponse>($"api/Health/AuthorizedPing", null);
    }
}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.game;

public class GroupClient : ISubterfugeGroupChatApi
{
    private SubterfugeHttpClient client;

    public GroupClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<CreateMessageGroupResponse>> CreateMessageGroup(CreateMessageGroupRequest request, string roomId)
    {
        return await client.Post<CreateMessageGroupRequest, CreateMessageGroupResponse>($"api/room/{roomId}/group/create", request);
    }

    public async Task<SubterfugeResponse<SendMessageResponse>> SendMessage(SendMessageRequest request, string roomId, string groupId)
    {
        return await client.Post<SendMessageRequest, SendMessageResponse>($"api/room/{roomId}/group/{groupId}/send", request);
    }

    public async Task<SubterfugeResponse<GetMessageGroupsResponse>> GetMessageGroups(string roomId)
    {
        return await client.Get<GetMessageGroupsResponse>($"api/room/{roomId}/groups", null);
    }

    public async Task<SubterfugeResponse<GetGroupMessagesResponse>> GetMessages(GetGroupMessagesRequest request, string roomId, string groupId)
    {
        return await client.Get<GetGroupMessagesResponse>($"api/room/{roomId}/group/{groupId}/messages", null);
    }

}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.game;

public class GameAnnouncementClient : ISubterfugeAnnouncementApi
{
    private SubterfugeHttpClient client;

    public GameAnnouncementClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<CreateAnnouncementResponse>> CreateAnnouncement(CreateAnnouncementRequest announcementRequest)
    {
        return await client.Post<CreateAnnouncementRequest, CreateAnnouncementResponse>($"api/announcements/create", announcementRequest);
    }

    public async Task<SubterfugeResponse<CreateAnnouncementResponse>> UpdateAnnouncement(string id, CreateAnnouncementRequest announcementRequest)
    {
        return await client.Put<CreateAnnouncementRequest, CreateAnnouncementResponse>($"api/announcements/{id}", announcementRequest);
    }

    public async Task<SubterfugeResponse<GetAnnouncementsResponse>> GetAnnouncements()
    {
        return await client.Get<GetAnnouncementsResponse>($"api/announcements", null);
    }
}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.game;

public class GameEventClient : ISubterfugeGameEventApi
{
    private SubterfugeHttpClient client;

    public GameEventClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<GetGameRoomEventsResponse>> GetGameRoomEvents(string roomId)
    {
        return await client.Get<GetGameRoomEventsResponse>($"api/room/{roomId}/events", null);
    }

    public async Task<SubterfugeResponse<SubmitGameEventResponse>> SubmitGameEvent(SubmitGameEventRequest request, string roomId)
    {
        return await client.Post<SubmitGameEventRequest, SubmitGameEventResponse>($"api/room/{roomId}/events", request);
    }

    public async Task<SubterfugeResponse<SubmitGameEventResponse>> UpdateGameEvent(UpdateGameEventRequest request, string roomId, string eventGuid)
    {
        return await client.Put<UpdateGameEventRequest, SubmitGameEventResponse>($"api/room/{roomId}/events/{eventGuid}", request);
    }

    public async Task<SubterfugeResponse<DeleteGameEventResponse>> DeleteGameEvent(string roomId, string eventGuid)
    {
        return await client.Delete<DeleteGameEventResponse>($"api/room/{roomId}/events/{eventGuid}", null);
    }
}


text:
#nullable enable
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.game;

public class LobbyClient : ISubterfugeGameLobbyApi
{
    private SubterfugeHttpClient client;

    public LobbyClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<GetLobbyResponse>> GetLobbies(GetLobbyRequest lobbyRequest)
    {
        return await client.Get<GetLobbyResponse>($"api/lobby", new Dictionary<string, string>()
        {
            {"Pagination", lobbyRequest.Pagination.ToString() },
            {"CreatedByUserId", lobbyRequest.CreatedByUserId },
            {"RoomStatus", lobbyRequest.RoomStatus.ToString() },
            {"UserIdInRoom", lobbyRequest.UserIdInRoom },
            {"RoomId", lobbyRequest.RoomId },
            {"Goal", lobbyRequest.Goal.ToString() },
            {"MinPlayers", lobbyRequest.MinPlayers.ToString() },
            {"MaxPlayers", lobbyRequest.MaxPlayers.ToString() },
            {"IsAnonymous", lobbyRequest.IsAnonymous.ToString() },
            {"IsRanked", lobbyRequest.IsRanked.ToString() },
        });
    }

    public async Task<SubterfugeResponse<CreateRoomResponse>> CreateNewRoom(CreateRoomRequest request)
    {
        return await client.Post<CreateRoomRequest, CreateRoomResponse>($"api/lobby/create", request);
    }

    public async Task<SubterfugeResponse<JoinRoomResponse>> JoinRoom(JoinRoomRequest request, string guid)
    {
        return await client.Post<JoinRoomRequest, JoinRoomResponse>($"api/lobby/{guid}/join", request);
    }

    public async Task<SubterfugeResponse<LeaveRoomResponse>> LeaveRoom(string guid)
    {
        return await client.Get<LeaveRoomResponse>($"api/lobby/{guid}/leave", null);
    }

    public async Task<SubterfugeResponse<StartGameEarlyResponse>> StartGameEarly(string guid)
    {
        return await client.Get<StartGameEarlyResponse>($"api/lobby/{guid}/start", null);
    }
}


text:
using System.Net;
using System.Net.Http.Headers;
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Api.Client.controllers.Client;

public class SubterfugeHttpClient
{
    private HttpClient Client = new HttpClient();

    /// <summary>
    /// The client constructor.
    ///
    /// By default, this constructor works for local development as well as docker container environments.
    /// The client will look for the `ASPNETCORE_ENVIRONMENT` environment variable.
    /// If the environment variable is `Docker`, the client connects to: "server:5295", otherwise it will connect to "localhost:5295".
    /// If you need to connect to a custom server URL, use the secondary constructor which accepts a base URL.
    /// </summary>
    public SubterfugeHttpClient()
    {
        string hostname = "localhost";
        int port = 5295;

        // Get environment
        string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (environment == "Docker")
        {
            hostname = "server";
        }
        setupClient($"http://{hostname}:{port}/");
    }

    /// <summary>
    /// Creates an instance of a subterfuge client using the specified baseURL.
    /// </summary>
    /// <param name="baseUrl">The server URL to connect to. Should include the protocol, hostname, and port. ex. http://localhost:5295</param>
    public SubterfugeHttpClient(string baseUrl)
    {
        setupClient(baseUrl);
    }

    private void setupClient(string baseUrl)
    {
        Client.BaseAddress = new Uri(baseUrl);
        Client.DefaultRequestHeaders.Accept.Clear();
        Client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json")
        );
    }

    public async Task<SubterfugeResponse<TResponse>> Post<TRequest, TResponse>(string url, TRequest data)
    {
        HttpResponseMessage response = await Client.PostAsJsonAsync(url, data);
        return await ReturnSerializedResponse<TResponse>(response);
    }

    public async Task<SubterfugeResponse<TResponse>> Get<TResponse>(string url, IDictionary<string, string> getParams)
    {
        HttpResponseMessage response = await Client.GetAsync(GetUrlFromParams(url, getParams));
        return await ReturnSerializedResponse<TResponse>(response);
    }

    public async Task<SubterfugeResponse<TResponse>> Put<TRequest, TResponse>(string url, TRequest data)
    {
        HttpResponseMessage response = await Client.PutAsJsonAsync(url, data);
        return await ReturnSerializedResponse<TResponse>(response);
    }

    public async Task<SubterfugeResponse<TResponse>> Delete<TResponse>(string url, IDictionary<string, string> deleteParams)
    {
        HttpResponseMessage response = await Client.DeleteAsync(GetUrlFromParams(url, deleteParams));
        return await ReturnSerializedResponse<TResponse>(response);
    }

    public void SetAuthHeader(string Token)
    {
        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
    }

    private string GetUrlFromParams(string baseUrl, IDictionary<string, string> queryParams)
    {
        string queryUrl = baseUrl;
        if (queryParams != null)
        {
            var nonNullParams = queryParams
                .Where(it => it.Value != null)
                .ToDictionary(it => it.Key, it => it.Value)
                .Select(kvPair => kvPair.Key + "=" + kvPair.Value )
                .ToList();

            queryUrl = baseUrl + "?" + String.Join("&", nonNullParams);
        }

        return queryUrl;
    }

    private async Task<SubterfugeResponse<TResponse>> ReturnSerializedResponse<TResponse>(HttpResponseMessage rawResponse)
    {
        if (!rawResponse.IsSuccessStatusCode)
        {
            if (rawResponse.StatusCode == HttpStatusCode.Forbidden)
            {
                return SubterfugeResponse<TResponse>.OfFailure(ResponseType.PERMISSION_DENIED, "Permission denied.");
            }
            if (rawResponse.StatusCode == HttpStatusCode.Unauthorized)
            {
                return SubterfugeResponse<TResponse>.OfFailure(ResponseType.UNAUTHORIZED, "You are not logged in.");
            }
            // TODO: parse the failure content into a response
            return SubterfugeResponse<TResponse>.OfFailure(ResponseType.INTERNAL_SERVER_ERROR, rawResponse.ReasonPhrase);
        }
        return await rawResponse.Content.ReadAsAsync<SubterfugeResponse<TResponse>>();
    }
}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.admin;

public class AdminClient : ISubterfugeAdminApi
{
    private SubterfugeHttpClient client;

    public AdminClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<ServerActionLogResponse>> GetActionLog(ServerActionLogRequeset request)
    {
        return await client.Get<ServerActionLogResponse>($"api/admin/serverLog", new Dictionary<string, string>()
        {
            { "Pagination", request.Pagination.ToString() },
            { "Username", request.Username.ToString() },
            { "UserId", request.UserId.ToString() },
            { "HttpMethod", request.HttpMethod.ToString() },
            { "RequestUrl", request.RequestUrl.ToString() },
        });
    }

    public async Task<SubterfugeResponse<ServerExceptionLogResponse>> GetServerExceptions(ServerExceptionLogRequest request)
    {
        return await client.Get<ServerExceptionLogResponse>($"api/admin/exceptions", new Dictionary<string, string>()
        {
            { "Pagination", request.Pagination.ToString() },
            { "Username", request.Username.ToString() },
            { "UserId", request.UserId.ToString() },
            { "HttpMethod", request.HttpMethod.ToString() },
            { "RequestUrl", request.RequestUrl.ToString() },
            { "ExceptionSource", request.ExceptionSource.ToString() },
            { "RemoteIpAddress", request.RemoteIpAddress.ToString() },
        });
    }

    public async Task<SubterfugeResponse<BanPlayerResponse>> BanPlayer(BanPlayerRequest banPlayerRequest)
    {
        return await client.Post<BanPlayerRequest, BanPlayerResponse>($"api/admin/banPlayer", banPlayerRequest);
    }

    public async Task<SubterfugeResponse<BanIpResponse>> BanIp(BanIpRequest banIpRequest)
    {
        return await client.Post<BanIpRequest, BanIpResponse>($"api/admin/banIp", banIpRequest);
    }

    public async Task<SubterfugeResponse<GetIpBansResponse>> GetIpBans(int pagination)
    {
        return await client.Get<GetIpBansResponse>($"api/admin/ipBans", new Dictionary<string, string>()
        {
            { "Pagination", pagination.ToString() }
        });
    }

    public async Task<SubterfugeResponse<GetBannedPlayerResponse>> GetBannedPlayers(int pagination)
    {
        return await client.Get<GetBannedPlayerResponse>($"api/admin/playerBans", new Dictionary<string, string>()
        {
            { "Pagination", pagination.ToString() }
        });
    }

    public async Task<SubterfugeResponse<Echo>> EchoRequest(Echo request)
    {
        return await client.Post<Echo, Echo>($"api/admin/echo", request);
    }
}


text:
#nullable enable
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.account;

public class UserControllerClient : ISubterfugeAccountApi
{
    private SubterfugeHttpClient client;

    public UserControllerClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<AuthorizationResponse>> Login(AuthorizationRequest request)
    {
        var response = await client.Post<AuthorizationRequest, AuthorizationResponse>("api/user/login", request);
        if (response.IsSuccess())
        {
            client.SetAuthHeader(response.GetOrThrow().Token);
        }

        return response;
    }

    public void Logout()
    {
        client.SetAuthHeader(null);
    }

    public void SetToken(string token)
    {
        client.SetAuthHeader(token);
    }

    public async Task<SubterfugeResponse<AccountRegistrationResponse>> RegisterAccount(AccountRegistrationRequest request)
    {
        var response = await client.Post<AccountRegistrationRequest, AccountRegistrationResponse>("api/user/register", request);
        if (response.IsSuccess())
        {
            client.SetAuthHeader(response.GetOrThrow().Token);
        }
        return response;
    }

    public async Task<SubterfugeResponse<AccountVadliationResponse>> VerifyPhone(AccountValidationRequest validationRequest)
    {
        return await client.Post<AccountValidationRequest, AccountVadliationResponse>("api/user/verifyPhone", validationRequest);
    }

    public async Task<SubterfugeResponse<GetUserResponse>> GetUser(string userId)
    {
        return await client.Get<GetUserResponse>($"api/user/query", new Dictionary<string, string>()
        {
            { "userId", userId },
        });
    }

    public async Task<SubterfugeResponse<GetDetailedUsersResponse>> GetUsers(GetUserRequest request)
    {
        return await client.Get<GetDetailedUsersResponse>($"api/user/query", new Dictionary<string, string>()
        {
            { "pagination", request.pagination.ToString() },
            { "UsernameSearch", request.UsernameSearch },
            { "DeviceIdentifierSearch", request.DeviceIdentifierSearch },
            { "UserIdSearch", request.UserIdSearch },
            { "RequireUserClaims", request.RequireUserClaim.ToString() },
            { "isBanned", request.isBanned.ToString() },
        });
    }

    public async Task<SubterfugeResponse<GetPlayerChatMessagesResponse>> GetPlayerChatMessages(string playerId, int pagination = 1)
    {
        return await client.Get<GetPlayerChatMessagesResponse>($"api/user/messages", new Dictionary<string, string>()
        {
            { "playerId", playerId },
            { "pagination", pagination.ToString() },
        });
    }
}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.account;

public class UserRoleClient : ISubterfugeUserRoleApi
{
    private SubterfugeHttpClient client;

    public UserRoleClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<GetRolesResponse>> GetRoles(string userId)
    {
        return await client.Get<GetRolesResponse>($"api/user/{userId}/roles", null);
    }

    public async Task<SubterfugeResponse<GetRolesResponse>> SetRoles(string userId, UpdateRolesRequest request)
    {
        return await client.Post<UpdateRolesRequest, GetRolesResponse>($"api/user/{userId}/roles", request);
    }
}


text:
using Subterfuge.Remake.Api.Client.controllers.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Api.Network.Api;

namespace Subterfuge.Remake.Api.Client.controllers.social;

public class SocialClient : ISubterfugeSocialApi
{
    private SubterfugeHttpClient client;

    public SocialClient(SubterfugeHttpClient client)
    {
        this.client = client;
    }

    public async Task<SubterfugeResponse<UnblockPlayerResponse>> UnblockPlayer(UnblockPlayerRequest request, string userId)
    {
        return await client.Post<UnblockPlayerRequest, UnblockPlayerResponse>($"api/user/{userId}/unblock", request);
    }

    public async Task<SubterfugeResponse<ViewBlockedPlayersResponse>> ViewBlockedPlayers(string userId)
    {
        return await client.Get<ViewBlockedPlayersResponse>($"api/user/{userId}/blocks", null);
    }

    public async Task<SubterfugeResponse<AddAcceptFriendResponse>> AddAcceptFriendRequest(string userId)
    {
        return await client.Get<AddAcceptFriendResponse>($"api/user/{userId}/addFriend", null);
    }

    public async Task<SubterfugeResponse<ViewFriendRequestsResponse>> ViewFriendRequests(string userId)
    {
        return await client.Get<ViewFriendRequestsResponse>($"api/user/{userId}/friendRequests", null);
    }

    public async Task<SubterfugeResponse<BlockPlayerResponse>> BlockPlayer(BlockPlayerRequest request, string userId)
    {
        return await client.Post<BlockPlayerRequest, BlockPlayerResponse>($"api/user/{userId}/block", request);
    }

    public async Task<SubterfugeResponse<DenyFriendRequestResponse>> RemoveRejectFriend(string userId)
    {
        return await client.Get<DenyFriendRequestResponse>($"api/user/{userId}/removeFriend", null);
    }

    public async Task<SubterfugeResponse<ViewFriendsResponse>> GetFriendList(string userId)
    {
        return await client.Get<ViewFriendsResponse>($"api/user/{userId}/friends", null);
    }
}


text:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>disable</Nullable>
        <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
        <LangVersion>10</LangVersion>
        <RootNamespace>Subterfuge.Remake.Server.Test</RootNamespace>
    </PropertyGroup>

    <ItemGroup>
      <ProjectReference Include="..\SubterfugeDatabaseProvider\SubterfugeDatabaseProvider.csproj" />
      <ProjectReference Include="..\SubterfugeRestApiClient\SubterfugeRestApiClient.csproj" />
    </ItemGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
      <PackageReference Include="NUnit" Version="3.13.3" />
      <PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
    </ItemGroup>

</Project>


text:
FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["Server/SubterfugeRestApiServerTest/SubterfugeRestApiServerTest.csproj", "SubterfugeRestApiServerTest/"]
RUN dotnet restore "Server/SubterfugeRestApiServerTest/SubterfugeRestApiServerTest.csproj"
COPY . .
WORKDIR "/src/SubterfugeRestApiServerTest"
RUN dotnet build "SubterfugeRestApiServerTest.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "SubterfugeRestApiServerTest.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "SubterfugeRestApiServerTest.dll"]


text:
using System.Security.Cryptography;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Server.Database;
using Subterfuge.Remake.Server.Database.Models;

namespace Subterfuge.Remake.Server.Test.util
{
    public class MongoIntegrationTestConnector
    {
        public MongoConnector Mongo;

        public MongoIntegrationTestConnector()
        {
            string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var mongoConfig = new MongoConfiguration()
            {
                CreateSuperUser = false,
                FlushDatabase = true,
                Host = environment == "Docker" ? "db" : "localhost",
                Port = 27017,
                SuperUserUsername = "admin",
                SuperUserPassword = "admin",
            };

            Mongo = new MongoConnector(new DefaultMongoConfigurationProviderImpl() { Config = mongoConfig}, null);
        }

        public async Task<DbUserModel> CreateTestingSuperUser()
        {
            var userModel = new DbUserModel()
            {
                Id = "1",
                Username = "admin",
                PasswordHash = HashPassword("admin"),
                PhoneNumber = "9999999999",
                Claims = new[] { UserClaim.User, UserClaim.Administrator, UserClaim.PhoneVerified }
            };

            await Mongo.GetCollection<DbUserModel>().Upsert(userModel);
            return userModel;
        }

        private String HashPassword(string password)
        {
            // Create salt
            byte[] salt;
            new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

            // Create the Rfc2898DeriveBytes and get the hash value:
            var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
            byte[] hash = pbkdf2.GetBytes(20);

            // Combine salt and password bytes
            byte[] hashBytes = new byte[36];
            Array.Copy(salt, 0, hashBytes, 0, 16);
            Array.Copy(hash, 0, hashBytes, 16, 20);

            // Return value
            return Convert.ToBase64String(hashBytes);
        }
    }
}


text:
#nullable enable
using Subterfuge.Remake.Api.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Server.Database;

namespace Subterfuge.Remake.Server.Test.util;

public class TestUtils
{
    public static MongoConnector Mongo = new MongoIntegrationTestConnector().Mongo;
    private static SubterfugeClient? _client = null;
    public static SubterfugeClient GetClient()
    {
        if (_client == null)
        {
            string hostname = "localhost";
            int port = 5295;

            // Get environment
            string? environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            if (environment == "Docker")
            {
                hostname = "server";
            }
            _client = new SubterfugeClient($"http://{hostname}:{port}");
            return _client;
        }

        return _client;
    }

    public static async Task<AuthorizationResponse> CreateSuperUserAndLogin()
    {
        var account = await Mongo.CreateSuperUser();
        return (await TestUtils.GetClient().UserApi.Login(new AuthorizationRequest() { Username = "admin", Password = "admin" })).GetOrThrow();
    }

    public static JoinRoomRequest CreateJoinRequest()
    {
        return new JoinRoomRequest()
        {
            SpecialistDeck = new List<SpecialistTypeId>()
            {
                SpecialistTypeId.Advisor,
                SpecialistTypeId.Amnesiac,
                SpecialistTypeId.Assasin,
                SpecialistTypeId.Automation,
                SpecialistTypeId.Dispatcher,
                SpecialistTypeId.Economist,
                SpecialistTypeId.Enforcer,
                SpecialistTypeId.Engineer,
                SpecialistTypeId.Escort,
                SpecialistTypeId.Foreman,
                SpecialistTypeId.Helmsman,
                SpecialistTypeId.Hypnotist,
                SpecialistTypeId.Icicle,
                SpecialistTypeId.Industrialist,
                SpecialistTypeId.Infiltrator
            }
        };
    }
}


text:
using NUnit.Framework;
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Test.util.account;

public class AccountUtils
{
    public static async Task<AccountRegistrationResponse> AssertRegisterAccountAndAuthorized(
        string username,
        string email = "someEmail@email.com",
        string deviceId = null,
        string phone = null
    ) {
        var accountRegistrationResponse = await TestUtils.GetClient().UserApi.RegisterAccount(new AccountRegistrationRequest()
        {
            DeviceIdentifier = deviceId ?? Guid.NewGuid().ToString(),
            Password = username,
            PhoneNumber = phone ?? Guid.NewGuid().ToString(),
            Username = username
        });
        Assert.True(accountRegistrationResponse.IsSuccess());
        Assert.IsNotNull(accountRegistrationResponse.GetOrThrow().Token);
        Assert.IsNotNull(accountRegistrationResponse.GetOrThrow().User);
        Assert.AreEqual(accountRegistrationResponse.GetOrThrow().User.Username, username);

        await AssertUserAuthorized();
        return accountRegistrationResponse.GetOrThrow();
    }

    public static async Task<AuthorizationResponse> AssertLogin(string username)
    {
        var loginResponse = await TestUtils.GetClient().UserApi.Login(new AuthorizationRequest()
        {
            Password = username,
            Username = username
        });
        Assert.True(loginResponse.IsSuccess());
        Assert.IsNotNull(loginResponse.GetOrThrow().Token);
        Assert.IsNotNull(loginResponse.GetOrThrow().User);
        Assert.AreEqual(loginResponse.GetOrThrow().User.Username, username);

        await AccountUtils.AssertUserAuthorized();
        return loginResponse.GetOrThrow();
    }

    public static async Task AssertUserAuthorized()
    {
        var pingResponse = await TestUtils.GetClient().HealthClient.AuthorizedPing();
        Assert.True(pingResponse.IsSuccess());
    }
}


text:
using NUnit.Framework;
using Subterfuge.Remake.Api.Network;

namespace Subterfuge.Remake.Server.Test.util.account;

public class LobbyUtils
{
    public static async Task<CreateRoomResponse> CreateLobby(
        string roomName = "My room!",
        int maxPlayers = 5,
        bool isRanked = false,
        Goal goal = Goal.Domination
    )
    {
        var roomResponse = await TestUtils.GetClient().LobbyClient.CreateNewRoom(CreateRoomRequest(roomName, maxPlayers: maxPlayers, isRanked: isRanked, goal: goal));
        CreateRoomResponse room = roomResponse.GetOrThrow();
        Assert.AreEqual(roomResponse.IsSuccess(), true);
        Assert.IsTrue(room.GameConfiguration.Id != null);
        return room;
    }

    public static async Task<GetLobbyResponse> AssertPlayerInLobby(string playerId, bool isInLobby = true)
    {
        var roomResponse = await TestUtils.GetClient().LobbyClient.GetLobbies(new GetLobbyRequest());
        GetLobbyResponse lobbyResponse = roomResponse.GetOrThrow();
        Assert.AreEqual(roomResponse.IsSuccess(), true);
        if (isInLobby)
        {
            Assert.AreEqual(isInLobby, lobbyResponse.Lobbies[0].PlayersInLobby.Any(it => it.Id == playerId));
        }
        else
        {
            if (lobbyResponse.Lobbies.Length > 0)
            {
                Assert.IsFalse(lobbyResponse.Lobbies[0].PlayersInLobby.Any(it => it.Id == playerId));
            }
        }

        return lobbyResponse;
    }

    private static CreateRoomRequest CreateRoomRequest(
        string roomName,
        bool anon = false,
        bool isRanked = false,
        Goal goal = Goal.Domination,
        int maxPlayers = 5
    ) {
        return new CreateRoomRequest()
        {
            GameSettings = new GameSettings()
            {

                IsAnonymous = anon,
                Goal = goal,
                IsRanked = isRanked,
                MaxPlayers = maxPlayers,
                MinutesPerTick = 15,
            },
            IsPrivate = false,
            MapConfiguration = CreateMapConfiguration(),
            RoomName = roomName,
            CreatorSpecialistDeck = new List<SpecialistTypeId>()
            {
                SpecialistTypeId.Advisor,
                SpecialistTypeId.Amnesiac,
                SpecialistTypeId.Assasin,
                SpecialistTypeId.Automation,
                SpecialistTypeId.Dispatcher,
                SpecialistTypeId.Economist,
                SpecialistTypeId.Enforcer,
                SpecialistTypeId.Engineer,
                SpecialistTypeId.Escort,
                SpecialistTypeId.Foreman,
                SpecialistTypeId.Helmsman,
                SpecialistTypeId.Hypnotist,
                SpecialistTypeId.Icicle,
                SpecialistTypeId.Industrialist,
                SpecialistTypeId.Infiltrator
            }
        };
    }

    private static MapConfiguration CreateMapConfiguration()
    {
        return new MapConfiguration()
        {
            DormantsPerPlayer = 5,
            MaximumOutpostDistance = 100,
            MinimumOutpostDistance = 5,
            OutpostDistribution = new OutpostDistribution()
            {
                FactoryWeight = 0.33f,
                GeneratorWeight = 0.33f,
                WatchtowerWeight = 0.33f,
            },
            OutpostsPerPlayer = 3,
            Seed = 123123,
        };
    }
}


text:
using NUnit.Framework;
using Subterfuge.Remake.Api.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Server.Test.util;
using Subterfuge.Remake.Server.Test.util.account;

namespace Subterfuge.Remake.Server.Test.test;

public class DatabaseSeeder
{
    private SubterfugeClient client = TestUtils.GetClient();

    [SetUp]
    public void Setup()
    {
        TestUtils.Mongo.FlushAll();
        TestUtils.GetClient().UserApi.Logout();
    }

    // [Ignore("Ignore until the dev enables it. This will seed an entire database with some sample data everywhere")]
    [Test]
    public async Task SeedDatabase()
    {
        // Create an admin
        var admin = await TestUtils.CreateSuperUserAndLogin();
        TestUtils.GetClient().UserApi.Logout();

        var userThree = await AccountUtils.AssertRegisterAccountAndAuthorized("UserThree");
        TestUtils.GetClient().UserApi.Logout();
        var userTwo = await AccountUtils.AssertRegisterAccountAndAuthorized("UserTwo");
        TestUtils.GetClient().UserApi.Logout();
        var userOne = await AccountUtils.AssertRegisterAccountAndAuthorized("UserOne");

        var roomOne = await LobbyUtils.CreateLobby(
            "Room 1",
            maxPlayers: 2,
            isRanked: false,
            goal: Goal.Domination
        );

        // Login to a different account to test searching for players in a room.
        TestUtils.GetClient().UserApi.SetToken(userTwo.Token);
        await TestUtils.GetClient().LobbyClient.JoinRoom(TestUtils.CreateJoinRequest(), roomOne.GameConfiguration.Id);

        var groupOne = await TestUtils.GetClient().GroupClient.CreateMessageGroup(new CreateMessageGroupRequest()
        {
            UserIdsInGroup = new List<string>() { userOne.User.Id, userTwo.User.Id }
        }, roomOne.GameConfiguration.Id);

        await TestUtils.GetClient().GroupClient.SendMessage(new SendMessageRequest()
        {
            Message = "Hello there friend!"
        }, roomOne.GameConfiguration.Id, groupOne.GetOrThrow().GroupId);

        await TestUtils.GetClient().GameEventClient.SubmitGameEvent(new SubmitGameEventRequest()
        {
            GameEventData = new GameEventData()
            {
                EventDataType = EventDataType.DrillMineEventData,
                SerializedEventData = Newtonsoft.Json.JsonConvert.SerializeObject(new DrillMineEventData()
                {
                    SourceId = "MyOtherOutpost"
                }),
                OccursAtTick = 15,
            }
        }, roomOne.GameConfiguration.Id);

        await TestUtils.GetClient().GameEventClient.SubmitGameEvent(new SubmitGameEventRequest()
        {
            GameEventData = new GameEventData()
            {
                EventDataType = EventDataType.DrillMineEventData,
                SerializedEventData = Newtonsoft.Json.JsonConvert.SerializeObject(new DrillMineEventData()
                {
                    SourceId = "MyOutpost"
                }),
                OccursAtTick = 23,
            }
        }, roomOne.GameConfiguration.Id);

        await TestUtils.GetClient().SocialClient.AddAcceptFriendRequest(userOne.User.Id);
        await TestUtils.GetClient().SocialClient.AddAcceptFriendRequest(userThree.User.Id); // User two has an outgoing friend request to user three
        TestUtils.GetClient().UserApi.SetToken(userOne.Token);
        await TestUtils.GetClient().SocialClient.AddAcceptFriendRequest(userTwo.User.Id); // User one and two are friends.
        await TestUtils.GetClient().SocialClient.BlockPlayer(new BlockPlayerRequest(), userThree.User.Id); // User one has blocked user three

        TestUtils.GetClient().UserApi.SetToken(admin.Token);

        await TestUtils.GetClient().AdminClient.BanPlayer(new BanPlayerRequest()
            {
                AdminNotes = "Generated from database seed",
                Reason = "Seeded ban",
                Until = DateTime.Now.AddHours(1),
                UserId = userThree.User.Id,
            });
    }
}


text:
using NUnit.Framework;
using Subterfuge.Remake.Api.Client;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Server.Test.util;
using Subterfuge.Remake.Server.Test.util.account;

namespace Subterfuge.Remake.Server.Test.test.health;

public class HealthControllerTest
{
    private SubterfugeClient client = TestUtils.GetClient();

    [SetUp]
    public void Setup()
    {
        TestUtils.Mongo.FlushAll();
        TestUtils.GetClient().UserApi.Logout();
    }

    [Test]
    public async Task AnyoneCanPing()
    {
        var response = await client.HealthClient.Ping();
        Assert.IsTrue(response.ResponseDetail.IsSuccess);
    }

    [Test]
    public async Task UnauthenticatedUsersCannotUseAuthorizedPing()
    {
        var response = await client.HealthClient.AuthorizedPing();
        Assert.IsFalse(response.IsSuccess());
        Assert.AreEqual(ResponseType.UNAUTHORIZED, response.ResponseDetail.ResponseType);
    }

    [Test]
    public async Task AuthorizedUserCanUseAuthorziedPing()
    {
        var user = await AccountUtils.AssertRegisterAccountAndAuthorized("NewUser");
        var response = await client.HealthClient.AuthorizedPing();
        Assert.IsTrue(response.ResponseDetail.IsSuccess);
    }

    [Test]
    public async Task AuthorizedPingReturnsTheLoggedInUserInfo()
    {
        var user = await AccountUtils.AssertRegisterAccountAndAuthorized("NewUser");
        var response = await client.HealthClient.AuthorizedPing();
        Assert.IsTrue(response.ResponseDetail.IsSuccess);
        Assert.AreEqual(response.GetOrThrow().LoggedInUser.Id, user.User.Id);
    }

}