diff --git a/Lab2-rest/.vscode/launch.json b/Lab2-rest/.vscode/launch.json
new file mode 100644
index 0000000..54b90be
--- /dev/null
+++ b/Lab2-rest/.vscode/launch.json
@@ -0,0 +1,38 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "ChatRoom",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build-ChatRoom",
+ "program": "${workspaceFolder}/ChatRoom/bin/Debug/net6.0/ChatRoom.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/ChatRoom",
+ "console": "externalTerminal",
+ "stopAtEntry": false
+ },
+ {
+ "name": "Moderator",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build-Moderator",
+ "program": "${workspaceFolder}/Moderator/bin/Debug/net6.0/Moderator.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/Moderator",
+ "console": "externalTerminal",
+ "stopAtEntry": false
+ },
+ {
+ "name": "Participant",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build-Participant",
+ "program": "${workspaceFolder}/Participant/bin/Debug/net6.0/Participant.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/Participant",
+ "console": "externalTerminal",
+ "stopAtEntry": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Lab2-rest/.vscode/tasks.json b/Lab2-rest/.vscode/tasks.json
new file mode 100644
index 0000000..cf312c1
--- /dev/null
+++ b/Lab2-rest/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build-ChatRoom",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/ChatRoom/ChatRoom.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build-Moderator",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/Moderator/Moderator.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build-Participant",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/Participant/Participant.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Lab2-rest/ChatRoom/ChatRoom.csproj b/Lab2-rest/ChatRoom/ChatRoom.csproj
new file mode 100644
index 0000000..2127933
--- /dev/null
+++ b/Lab2-rest/ChatRoom/ChatRoom.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net6.0
+ enable
+ enable
+ True
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Lab2-rest/ChatRoom/ChatRoomLogic.cs b/Lab2-rest/ChatRoom/ChatRoomLogic.cs
new file mode 100644
index 0000000..6481d92
--- /dev/null
+++ b/Lab2-rest/ChatRoom/ChatRoomLogic.cs
@@ -0,0 +1,295 @@
+using NLog;
+
+namespace ChatRoom;
+
+///
+/// Chat room service logic
+///
+public class ChatRoomLogic
+{
+ ///
+ /// Background thread of deleting approved or rejected messages
+ ///
+ private Thread thread;
+ ///
+ /// Chat Room state
+ ///
+ private ChatRoomState state = new ChatRoomState();
+ ///
+ /// Logger
+ ///
+ private Logger log = LogManager.GetCurrentClassLogger();
+
+ ///
+ /// Chat room logic constructor
+ ///
+ public ChatRoomLogic()
+ {
+ thread = new Thread(BackgroundTask);
+ thread.Start();
+ }
+
+ ///
+ /// Generate the next incrementing ID
+ ///
+ /// Unique ID
+ int NextId()
+ {
+ int id = state.lastUniqueId;
+ state.lastUniqueId++;
+ return id;
+ }
+
+ ///
+ /// Register a client
+ ///
+ /// Client name
+ /// Client ID
+ public int RegisterClient(string name)
+ {
+ lock (state.accessLock)
+ {
+ int clientId = NextId();
+ state.clients.Add(new Client
+ {
+ id = clientId,
+ name = name
+ });
+ log.Info($"Registered with client '{name}' with id {clientId}");
+ return clientId;
+ }
+ }
+
+ ///
+ /// Find a client by ID, can return NULL if not found
+ ///
+ /// Client ID
+ /// Optional client object
+ Client? FindClientById(int clientId)
+ {
+ foreach (var client in state.clients)
+ {
+ if (client.id == clientId)
+ {
+ return client;
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Find message by ID, can return NULL if not found
+ ///
+ /// Message ID
+ /// Optional message object
+ Message? FindMessageById(int messageId)
+ {
+ foreach (var message in state.messages)
+ {
+ if (message.id == messageId)
+ {
+ return message;
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Check if client is still blocked, will clear `blockedUntil` if client became unblocked
+ ///
+ /// Client object
+ /// Is client blocked?
+ bool GetAndUpdateBlockedState(Client client)
+ {
+ if (client.blockedUntil != null && DateTime.UtcNow >= client.blockedUntil)
+ {
+ client.blockedUntil = null;
+ }
+
+ return client.blockedUntil != null;
+ }
+
+ ///
+ /// Send a message
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Was sending the message successful, can fail if client is blocked
+ public bool SendMessage(int clientId, string contents, bool needsToBeCensored)
+ {
+ lock (state.accessLock)
+ {
+ var client = FindClientById(clientId);
+ if (client == null)
+ {
+ return false;
+ }
+
+ if (GetAndUpdateBlockedState(client))
+ {
+ return false;
+ }
+
+ var message = new Message
+ {
+ id = NextId(),
+ clientId = clientId,
+ contents = contents,
+ needsToBeCensored = needsToBeCensored,
+ status = MessageStatus.WaitingForModerator,
+ sentAt = DateTime.UtcNow
+ };
+ state.messages.Add(message);
+ log.Info($"Client '{client.name}' ({client.id}) sent message '{contents}' ({message.id}). Needs to censored: {needsToBeCensored}");
+ }
+
+ return true;
+ }
+
+ ///
+ /// Get next message which isin't approved or rejected
+ ///
+ /// Optional message object
+ public ChatRoomContract.Message? GetNewMessage()
+ {
+ lock (state.accessLock)
+ {
+ foreach (var message in state.messages)
+ {
+ if (message.status != MessageStatus.WaitingForModerator) continue;
+
+ log.Info($"Message '{message.id}' given to moderator");
+ message.status = MessageStatus.GivenToModerator;
+ return new ChatRoomContract.Message{
+ id = message.id,
+ contents = message.contents,
+ needsToBeCensored = message.needsToBeCensored
+ };
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ public void ApproveMessage(int messageId)
+ {
+ lock (state.accessLock)
+ {
+ var message = FindMessageById(messageId);
+ if (message == null)
+ {
+ return;
+ }
+
+ if (message.status != MessageStatus.GivenToModerator)
+ {
+ return;
+ }
+
+ message.status = MessageStatus.Approved;
+ log.Info($"Message {message.id} was approved");
+ }
+ }
+
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ public void RejectMessage(int messageId)
+ {
+ lock (state.accessLock)
+ {
+ var message = FindMessageById(messageId);
+ if (message == null)
+ {
+ return;
+ }
+
+ if (message.status != MessageStatus.GivenToModerator)
+ {
+ return;
+ }
+
+ message.status = MessageStatus.Rejected;
+ log.Info($"Message {message.id} was rejected");
+
+ var client = FindClientById(message.clientId);
+ if (client != null && !GetAndUpdateBlockedState(client))
+ {
+ client.strikes++;
+
+ var rnd = new Random();
+ if (client.strikes > rnd.Next(0, 10))
+ {
+ log.Info($"Client '{client.name}' ({client.id}) was blocked for {client.strikes}s");
+ client.blockedUntil = DateTime.UtcNow.AddSeconds(client.strikes);
+ client.strikes = 0;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get number of strikes a client has
+ ///
+ /// Client ID
+ /// Number of strikes
+ public int GetStrikes(int clientId)
+ {
+ lock (state.accessLock)
+ {
+ var client = FindClientById(clientId);
+ if (client == null)
+ {
+ return 0;
+ }
+
+ return client.strikes;
+ }
+ }
+
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Optional datetime object
+ public DateTime? GetBlockedUntil(int clientId)
+ {
+ lock (state.accessLock)
+ {
+ var client = FindClientById(clientId);
+ if (client == null)
+ {
+ return null;
+ }
+
+ return client.blockedUntil;
+ }
+ }
+
+ ///
+ /// Main loop for background thread. Used for deleting approved or rejected messages.
+ ///
+ public void BackgroundTask()
+ {
+ while (true)
+ {
+ lock (state.accessLock)
+ {
+ var now = DateTime.UtcNow;
+ int count = state.messages.RemoveAll(msg =>
+ (now - msg.sentAt).TotalSeconds > 5 &&
+ (msg.status == MessageStatus.Approved || msg.status == MessageStatus.Rejected)
+ );
+ log.Info($"Running periodic cleanup, removed {count} messages");
+ }
+
+ Thread.Sleep(10 * 1000);
+ }
+ }
+}
diff --git a/Lab2-rest/ChatRoom/ChatRoomState.cs b/Lab2-rest/ChatRoom/ChatRoomState.cs
new file mode 100644
index 0000000..4e6f3c2
--- /dev/null
+++ b/Lab2-rest/ChatRoom/ChatRoomState.cs
@@ -0,0 +1,90 @@
+namespace ChatRoom;
+
+///
+/// Client information
+///
+public class Client
+{
+ ///
+ /// Client ID
+ ///
+ public int id;
+ ///
+ /// Client name, can be the same between multiple clients
+ ///
+ public string name;
+ ///
+ /// Number of strikes
+ ///
+ public int strikes = 0;
+ ///
+ /// Until when is this client blocked from sending messages
+ ///
+ public DateTime? blockedUntil = null;
+}
+
+///
+/// Describes the messages status/stage
+///
+public enum MessageStatus
+{
+ WaitingForModerator,
+ GivenToModerator,
+ Approved,
+ Rejected
+}
+
+///
+/// Message information
+///
+public class Message
+{
+ ///
+ /// Message ID
+ ///
+ public int id;
+ ///
+ /// Client ID
+ ///
+ public int clientId;
+ ///
+ /// Message contents
+ ///
+ public string contents;
+ ///
+ /// Does this message need to be censored?
+ ///
+ public bool needsToBeCensored;
+ ///
+ /// Message status/stage
+ ///
+ public MessageStatus status = MessageStatus.WaitingForModerator;
+
+ ///
+ /// When was this message sent
+ ///
+ public DateTime sentAt;
+}
+
+public class ChatRoomState
+{
+ ///
+ /// Access lock.
+ ///
+ public readonly object accessLock = new object();
+
+ ///
+ /// Last unique ID value generated.
+ ///
+ public int lastUniqueId;
+
+ ///
+ /// List of all registered clients
+ ///
+ public List clients = new List();
+
+ ///
+ /// List of messages
+ ///
+ public List messages = new List();
+}
diff --git a/Lab2-rest/ChatRoom/Controllers/ChatRoomController.cs b/Lab2-rest/ChatRoom/Controllers/ChatRoomController.cs
new file mode 100644
index 0000000..bc1d1ca
--- /dev/null
+++ b/Lab2-rest/ChatRoom/Controllers/ChatRoomController.cs
@@ -0,0 +1,103 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace ChatRoom;
+
+///
+/// Service. Class must be marked public, otherwise ASP.NET core runtime will not find it.
+///
+/// Look into FromXXX attributes if you need to map inputs to custom parts of HTTP request.
+///
+[Route("chatRoom")]
+[ApiController]
+public class ChatRoomController : ControllerBase
+{
+ ///
+ /// Service logic. This is created in Server.StartServer() and received through DI in constructor.
+ ///
+ private readonly ChatRoomLogic logic;
+
+ ///
+ /// Constructor
+ ///
+ /// Logic to use. This will get passed through DI.
+ public ChatRoomController(ChatRoomLogic logic)
+ {
+ this.logic = logic;
+ }
+
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Client ID
+ [HttpPost("/registerClient")]
+ public ActionResult RegisterClient(string name)
+ {
+ return logic.RegisterClient(name);
+ }
+
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Number of strikes
+ [HttpGet("/getStrikes")]
+ public ActionResult GetStrikes(int clientId)
+ {
+ return logic.GetStrikes(clientId);
+ }
+
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Optional datetime object
+ [HttpGet("/getBlockedUntil")]
+ public ActionResult GetBlockedUntil(int clientId)
+ {
+ return logic.GetBlockedUntil(clientId);
+ }
+
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Was sending successful, can fail if user is blocked
+ [HttpPost("/sendMessage")]
+ public ActionResult SendMessage(int clientId, string contents, bool needsToBeCensored)
+ {
+ return logic.SendMessage(clientId, contents, needsToBeCensored);
+ }
+
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Message object. Returns null if there is no message
+ [HttpGet("/getNewMessage")]
+ public ActionResult GetNewMessage()
+ {
+ return logic.GetNewMessage();
+ }
+
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ [HttpPost("/rejectMessage")]
+ public void RejectMessage(int messageId)
+ {
+ logic.RejectMessage(messageId);
+ }
+
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ [HttpPost("/approveMessage")]
+ public void ApproveMessage(int messageId)
+ {
+ logic.ApproveMessage(messageId);
+ }
+}
diff --git a/Lab2-rest/ChatRoom/Properties/launchSettings.json b/Lab2-rest/ChatRoom/Properties/launchSettings.json
new file mode 100644
index 0000000..c20165d
--- /dev/null
+++ b/Lab2-rest/ChatRoom/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:64988",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5125",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Lab2-rest/ChatRoom/Server.cs b/Lab2-rest/ChatRoom/Server.cs
new file mode 100644
index 0000000..ebed60c
--- /dev/null
+++ b/Lab2-rest/ChatRoom/Server.cs
@@ -0,0 +1,126 @@
+
+using NLog;
+using System.Net;
+using System.Reflection;
+using System.Text.Json.Serialization;
+
+namespace ChatRoom;
+
+public class Server
+{
+ ///
+ /// Logger for this class.
+ ///
+ Logger log = LogManager.GetCurrentClassLogger();
+
+ ///
+ /// Program entry point.
+ ///
+ /// Command line arguments.
+ public static void Main(string[] args)
+ {
+ var self = new Server();
+ self.Run(args);
+ }
+
+ ///
+ /// Program body.
+ ///
+ /// Command line arguments.
+ private void Run(string[] args)
+ {
+ //configure logging
+ ConfigureLogging();
+
+ //indicate server is about to start
+ log.Info("Server is about to start");
+
+ //start the server
+ StartServer(args);
+ }
+
+ ///
+ /// Configure loggin subsystem.
+ ///
+ private void ConfigureLogging()
+ {
+ var config = new NLog.Config.LoggingConfiguration();
+
+ var console =
+ new NLog.Targets.ConsoleTarget("console")
+ {
+ Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
+ };
+ config.AddTarget(console);
+ config.AddRuleForAllLevels(console);
+
+ LogManager.Configuration = config;
+ }
+
+ ///
+ /// Starts integrated server.
+ ///
+ /// Command line arguments.
+ public void StartServer(string[] args)
+ {
+ //create web app builder
+ var builder = WebApplication.CreateBuilder(args);
+
+ //configure integrated server
+ builder.WebHost.ConfigureKestrel(opts => {
+ opts.Listen(IPAddress.Loopback, 5000);
+ });
+
+ //add and configure swagger documentation generator (http://127.0.0.1:5000/swagger/)
+ builder.Services.AddSwaggerGen(opts => {
+ //include code comments in swagger documentation
+ var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+ opts.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
+ });
+
+ //turn on support for web api controllers
+ builder.Services
+ .AddControllers()
+ .AddJsonOptions(opts => {
+ //this makes enumeration values to be strings instead of integers in opeanapi doc
+ opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ });
+
+ //add CORS policies
+ builder.Services.AddCors(cr => {
+ //allow everything from everywhere
+ cr.AddPolicy("allowAll", cp => {
+ cp.AllowAnyOrigin();
+ cp.AllowAnyMethod();
+ cp.AllowAnyHeader();
+ });
+ });
+
+ //publish the background logic as (an internal) service through dependency injection,
+ //otherwise it will not start until the first client calls into controller
+ builder.Services.AddSingleton(new ChatRoomLogic());
+
+ //build the server
+ var app = builder.Build();
+
+ //turn CORS policy on
+ app.UseCors("allowAll");
+
+ //turn on support for swagger doc web page
+ app.UseSwagger();
+ app.UseSwaggerUI();
+
+ //turn on request routing
+ app.UseRouting();
+
+ //configure routes
+ app.MapControllerRoute(
+ name: "default",
+ pattern: "{controller}/{action=Index}/{id?}"
+ );
+
+ //run the server
+ app.Run();
+ // app.RunAsync(); //use this if you need to implement background processing in the main thread
+ }
+}
diff --git a/Lab2-rest/ChatRoom/appsettings.json b/Lab2-rest/ChatRoom/appsettings.json
new file mode 100644
index 0000000..ae459eb
--- /dev/null
+++ b/Lab2-rest/ChatRoom/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information",
+ "Microsoft.AspNetCore.Hosting.Diagnostics": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/Lab2-rest/ChatRoomContract/ChatRoomClient.cs b/Lab2-rest/ChatRoomContract/ChatRoomClient.cs
new file mode 100644
index 0000000..6f3f927
--- /dev/null
+++ b/Lab2-rest/ChatRoomContract/ChatRoomClient.cs
@@ -0,0 +1,961 @@
+//----------------------
+//
+// Generated using the NSwag toolchain v14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
+//
+//----------------------
+
+#nullable enable
+
+#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
+#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
+#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
+#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
+#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null"
+#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
+#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
+#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
+#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
+#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
+#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
+#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
+#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)."
+
+namespace ChatRoomContract
+{
+ using System = global::System;
+
+ [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class ChatRoomClient : IChatRoomService
+ {
+#pragma warning disable 8618
+ private string _baseUrl;
+#pragma warning restore 8618
+
+ private System.Net.Http.HttpClient _httpClient;
+ private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true);
+ private Newtonsoft.Json.JsonSerializerSettings _instanceSettings;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ public ChatRoomClient(string baseUrl, System.Net.Http.HttpClient httpClient)
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ {
+ BaseUrl = baseUrl;
+ _httpClient = httpClient;
+ Initialize();
+ }
+
+ private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
+ {
+ var settings = new Newtonsoft.Json.JsonSerializerSettings();
+ UpdateJsonSerializerSettings(settings);
+ return settings;
+ }
+
+ public string BaseUrl
+ {
+ get { return _baseUrl; }
+ set
+ {
+ _baseUrl = value;
+ if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/"))
+ _baseUrl += '/';
+ }
+ }
+
+ protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } }
+
+ static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);
+
+ partial void Initialize();
+
+ partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
+ partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
+ partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
+
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task RegisterClientAsync(string? name)
+ {
+ return RegisterClientAsync(name, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Success
+ /// A server side error occurred.
+ public virtual int RegisterClient(string? name)
+ {
+ return System.Threading.Tasks.Task.Run(async () => await RegisterClientAsync(name, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task RegisterClientAsync(string? name, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "text/plain");
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "registerClient"
+ urlBuilder_.Append("registerClient");
+ urlBuilder_.Append('?');
+ if (name != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("name")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(name, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task GetStrikesAsync(int? clientId)
+ {
+ return GetStrikesAsync(clientId, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual int GetStrikes(int clientId)
+ {
+ return System.Threading.Tasks.Task.Run(async () => await GetStrikesAsync(clientId, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetStrikesAsync(int? clientId, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "getStrikes"
+ urlBuilder_.Append("getStrikes");
+ urlBuilder_.Append('?');
+ if (clientId != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("clientId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(clientId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task GetBlockedUntilAsync(int? clientId)
+ {
+ return GetBlockedUntilAsync(clientId, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual System.DateTime? GetBlockedUntil(int clientId)
+ {
+ return System.Threading.Tasks.Task.Run(async () => await GetBlockedUntilAsync(clientId, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetBlockedUntilAsync(int? clientId, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "getBlockedUntil"
+ urlBuilder_.Append("getBlockedUntil");
+ urlBuilder_.Append('?');
+ if (clientId != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("clientId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(clientId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object.DateTime;
+ }
+ else if (status_ == 204)
+ {
+ return null;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task SendMessageAsync(int? clientId, string? contents, bool? needsToBeCensored)
+ {
+ return SendMessageAsync(clientId, contents, needsToBeCensored, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Success
+ /// A server side error occurred.
+ public virtual bool SendMessage(int clientId, string contents, bool needsToBeCensored)
+ {
+ return System.Threading.Tasks.Task.Run(async () => await SendMessageAsync(clientId, contents, needsToBeCensored, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task SendMessageAsync(int? clientId, string? contents, bool? needsToBeCensored, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "text/plain");
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "sendMessage"
+ urlBuilder_.Append("sendMessage");
+ urlBuilder_.Append('?');
+ if (clientId != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("clientId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(clientId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ if (contents != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("contents")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(contents, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ if (needsToBeCensored != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("needsToBeCensored")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(needsToBeCensored, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task GetNewMessageAsync()
+ {
+ return GetNewMessageAsync(System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Success
+ /// A server side error occurred.
+ public virtual Message? GetNewMessage()
+ {
+ return System.Threading.Tasks.Task.Run(async () => await GetNewMessageAsync(System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetNewMessageAsync(System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "getNewMessage"
+ urlBuilder_.Append("getNewMessage");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else if (status_ == 204)
+ {
+ return null;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task RejectMessageAsync(int? messageId)
+ {
+ return RejectMessageAsync(messageId, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual void RejectMessage(int messageId)
+ {
+ System.Threading.Tasks.Task.Run(async () => await RejectMessageAsync(messageId, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task RejectMessageAsync(int? messageId, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "rejectMessage"
+ urlBuilder_.Append("rejectMessage");
+ urlBuilder_.Append('?');
+ if (messageId != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("messageId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(messageId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ return;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual System.Threading.Tasks.Task ApproveMessageAsync(int? messageId)
+ {
+ return ApproveMessageAsync(messageId, System.Threading.CancellationToken.None);
+ }
+
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual void ApproveMessage(int messageId)
+ {
+ System.Threading.Tasks.Task.Run(async () => await ApproveMessageAsync(messageId, System.Threading.CancellationToken.None)).GetAwaiter().GetResult();
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ /// Success
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task ApproveMessageAsync(int? messageId, System.Threading.CancellationToken cancellationToken)
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "approveMessage"
+ urlBuilder_.Append("approveMessage");
+ urlBuilder_.Append('?');
+ if (messageId != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("messageId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(messageId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ return;
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
+ throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ protected struct ObjectResponseResult
+ {
+ public ObjectResponseResult(T responseObject, string responseText)
+ {
+ this.Object = responseObject;
+ this.Text = responseText;
+ }
+
+ public T Object { get; }
+
+ public string Text { get; }
+ }
+
+ public bool ReadResponseAsString { get; set; }
+
+ protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken)
+ {
+ if (response == null || response.Content == null)
+ {
+ return new ObjectResponseResult(default(T)!, string.Empty);
+ }
+
+ if (ReadResponseAsString)
+ {
+ var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ try
+ {
+ var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings);
+ return new ObjectResponseResult(typedBody!, responseText);
+ }
+ catch (Newtonsoft.Json.JsonException exception)
+ {
+ var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
+ throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
+ }
+ }
+ else
+ {
+ try
+ {
+ using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ using (var streamReader = new System.IO.StreamReader(responseStream))
+ using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
+ {
+ var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
+ var typedBody = serializer.Deserialize(jsonTextReader);
+ return new ObjectResponseResult(typedBody!, string.Empty);
+ }
+ }
+ catch (Newtonsoft.Json.JsonException exception)
+ {
+ var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
+ throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
+ }
+ }
+ }
+
+ private string ConvertToString(object? value, System.Globalization.CultureInfo cultureInfo)
+ {
+ if (value == null)
+ {
+ return "";
+ }
+
+ if (value is System.Enum)
+ {
+ var name = System.Enum.GetName(value.GetType(), value);
+ if (name != null)
+ {
+ var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
+ if (field != null)
+ {
+ var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
+ as System.Runtime.Serialization.EnumMemberAttribute;
+ if (attribute != null)
+ {
+ return attribute.Value != null ? attribute.Value : name;
+ }
+ }
+
+ var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
+ return converted == null ? string.Empty : converted;
+ }
+ }
+ else if (value is bool)
+ {
+ return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
+ }
+ else if (value is byte[])
+ {
+ return System.Convert.ToBase64String((byte[])value);
+ }
+ else if (value is string[])
+ {
+ return string.Join(",", (string[])value);
+ }
+ else if (value.GetType().IsArray)
+ {
+ var valueArray = (System.Array)value;
+ var valueTextArray = new string[valueArray.Length];
+ for (var i = 0; i < valueArray.Length; i++)
+ {
+ valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo);
+ }
+ return string.Join(",", valueTextArray);
+ }
+
+ var result = System.Convert.ToString(value, cultureInfo);
+ return result == null ? "" : result;
+ }
+ }
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class ApiException : System.Exception
+ {
+ public int StatusCode { get; private set; }
+
+ public string? Response { get; private set; }
+
+ public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; }
+
+ public ApiException(string message, int statusCode, string? response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception? innerException)
+ : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException)
+ {
+ StatusCode = statusCode;
+ Response = response;
+ Headers = headers;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
+ }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class ApiException : ApiException
+ {
+ public TResult Result { get; private set; }
+
+ public ApiException(string message, int statusCode, string? response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception? innerException)
+ : base(message, statusCode, response, headers, innerException)
+ {
+ Result = result;
+ }
+ }
+
+}
+
+#pragma warning restore 108
+#pragma warning restore 114
+#pragma warning restore 472
+#pragma warning restore 612
+#pragma warning restore 1573
+#pragma warning restore 1591
+#pragma warning restore 8073
+#pragma warning restore 3016
+#pragma warning restore 8603
+#pragma warning restore 8604
+#pragma warning restore 8625
\ No newline at end of file
diff --git a/Lab2-rest/ChatRoomContract/ChatRoomContract.csproj b/Lab2-rest/ChatRoomContract/ChatRoomContract.csproj
new file mode 100644
index 0000000..ac5be2b
--- /dev/null
+++ b/Lab2-rest/ChatRoomContract/ChatRoomContract.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Lab2-rest/ChatRoomContract/IChatRoomService.cs b/Lab2-rest/ChatRoomContract/IChatRoomService.cs
new file mode 100644
index 0000000..c7405cd
--- /dev/null
+++ b/Lab2-rest/ChatRoomContract/IChatRoomService.cs
@@ -0,0 +1,74 @@
+namespace ChatRoomContract;
+
+///
+/// Minimal message description
+///
+public class Message
+{
+ ///
+ /// Message ID
+ ///
+ public int id { get; set; }
+ ///
+ /// Message contents
+ ///
+ public string contents { get; set; }
+ ///
+ /// Does this message need to be censored?
+ ///
+ public bool needsToBeCensored { get; set; }
+}
+
+///
+/// Chat room service contract
+///
+public interface IChatRoomService
+{
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Client ID
+ int RegisterClient(string name);
+
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Number of strikes
+ int GetStrikes(int clientId);
+
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Optional datetime object
+ DateTime? GetBlockedUntil(int clientId);
+
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Was sending successful, can fail if user is blocked
+ bool SendMessage(int clientId, string contents, bool needsToBeCensored);
+
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Message object. Returns null if there is no message
+ Message? GetNewMessage();
+
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
+ void RejectMessage(int messageId);
+
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
+ void ApproveMessage(int messageId);
+}
diff --git a/Lab2-rest/Lab2-rest.sln b/Lab2-rest/Lab2-rest.sln
new file mode 100644
index 0000000..1012147
--- /dev/null
+++ b/Lab2-rest/Lab2-rest.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35208.52
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRoom", "ChatRoom\ChatRoom.csproj", "{5F24CB9F-866F-4104-A6FB-A60371E6A85F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRoomContract", "ChatRoomContract\ChatRoomContract.csproj", "{CC5134F2-1127-4AF2-8CAF-1EFEAF31F5C0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moderator", "Moderator\Moderator.csproj", "{572058F8-3C35-463C-95E9-A056A872FE36}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Participant", "Participant\Participant.csproj", "{E2193712-FABC-4447-A291-AE0C29E80DD8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5F24CB9F-866F-4104-A6FB-A60371E6A85F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5F24CB9F-866F-4104-A6FB-A60371E6A85F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5F24CB9F-866F-4104-A6FB-A60371E6A85F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5F24CB9F-866F-4104-A6FB-A60371E6A85F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC5134F2-1127-4AF2-8CAF-1EFEAF31F5C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC5134F2-1127-4AF2-8CAF-1EFEAF31F5C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC5134F2-1127-4AF2-8CAF-1EFEAF31F5C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC5134F2-1127-4AF2-8CAF-1EFEAF31F5C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {572058F8-3C35-463C-95E9-A056A872FE36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {572058F8-3C35-463C-95E9-A056A872FE36}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {572058F8-3C35-463C-95E9-A056A872FE36}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {572058F8-3C35-463C-95E9-A056A872FE36}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2193712-FABC-4447-A291-AE0C29E80DD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2193712-FABC-4447-A291-AE0C29E80DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2193712-FABC-4447-A291-AE0C29E80DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2193712-FABC-4447-A291-AE0C29E80DD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {39207399-CA34-4BBB-BE63-E7BBCD0B52C5}
+ EndGlobalSection
+EndGlobal
diff --git a/Lab2-rest/Moderator/Moderator.cs b/Lab2-rest/Moderator/Moderator.cs
new file mode 100644
index 0000000..d9b0c4c
--- /dev/null
+++ b/Lab2-rest/Moderator/Moderator.cs
@@ -0,0 +1,92 @@
+using ChatRoomContract;
+using NLog;
+using Bogus;
+
+namespace Moderator;
+
+internal class Moderator
+{
+ ///
+ /// Logger for this class.
+ ///
+ Logger log = LogManager.GetCurrentClassLogger();
+
+ ///
+ /// Configures logging subsystem.
+ ///
+ private void ConfigureLogging()
+ {
+ var config = new NLog.Config.LoggingConfiguration();
+
+ var console =
+ new NLog.Targets.ConsoleTarget("console")
+ {
+ Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
+ };
+ config.AddTarget(console);
+ config.AddRuleForAllLevels(console);
+
+ LogManager.Configuration = config;
+ }
+
+ private void RunConnection(IChatRoomService chatRoom)
+ {
+ var faker = new Faker("en");
+
+ var name = faker.Name.FullName();
+ int clientId = chatRoom.RegisterClient(name);
+ log.Info($"Registered with client id {clientId}");
+
+ Console.Title = $"Moderator | {name} | {clientId}";
+
+ while (true)
+ {
+ var message = chatRoom.GetNewMessage();
+ if (message != null)
+ {
+ log.Info($"Checking message ({message.id}): {message.contents}");
+ Thread.Sleep(500);
+
+ if (message.needsToBeCensored)
+ {
+ chatRoom.RejectMessage(message.id);
+ }
+ else
+ {
+ chatRoom.ApproveMessage(message.id);
+ }
+ }
+
+ Thread.Sleep(1 * 1000);
+ }
+ }
+
+ private void Run()
+ {
+ ConfigureLogging();
+
+ while (true)
+ {
+ var client = new ChatRoomClient("http://127.0.0.1:5000", new HttpClient());
+
+ try
+ {
+ RunConnection(client);
+ }
+ catch (Exception e)
+ {
+ //log whatever exception to console
+ log.Warn(e, "Unhandled exception caught. Will restart main loop.");
+
+ //prevent console spamming
+ Thread.Sleep(2000);
+ }
+ }
+ }
+
+ static void Main(string[] args)
+ {
+ var self = new Moderator();
+ self.Run();
+ }
+}
diff --git a/Lab2-rest/Moderator/Moderator.csproj b/Lab2-rest/Moderator/Moderator.csproj
new file mode 100644
index 0000000..a10c28d
--- /dev/null
+++ b/Lab2-rest/Moderator/Moderator.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Lab2-rest/Participant/Participant.cs b/Lab2-rest/Participant/Participant.cs
new file mode 100644
index 0000000..870d5d5
--- /dev/null
+++ b/Lab2-rest/Participant/Participant.cs
@@ -0,0 +1,86 @@
+using ChatRoomContract;
+using NLog;
+using Bogus;
+
+namespace Participant;
+
+internal class Participant
+{
+ ///
+ /// Logger for this class.
+ ///
+ Logger log = LogManager.GetCurrentClassLogger();
+
+ ///
+ /// Configures logging subsystem.
+ ///
+ private void ConfigureLogging()
+ {
+ var config = new NLog.Config.LoggingConfiguration();
+
+ var console =
+ new NLog.Targets.ConsoleTarget("console")
+ {
+ Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
+ };
+ config.AddTarget(console);
+ config.AddRuleForAllLevels(console);
+
+ LogManager.Configuration = config;
+ }
+
+ private void RunConnection(IChatRoomService chatRoom)
+ {
+ var faker = new Faker("en");
+ var rnd = new Random();
+
+ var name = faker.Name.FullName();
+ int clientId = chatRoom.RegisterClient(name);
+ log.Info($"Registered with client id {clientId}");
+
+ while (true)
+ {
+ int strikes = chatRoom.GetStrikes(clientId);
+ Console.Title = $"Participant | {name} | {clientId} | {strikes} Strikes";
+
+ var message = string.Join(" ", faker.Lorem.Words(5));
+ bool needsToBeCensored = rnd.Next(0, 100) > 50;
+ if (chatRoom.SendMessage(clientId, message, needsToBeCensored)) {
+ log.Info("Sent message");
+ } else {
+ log.Info("Failed to send message, blocked");
+ }
+
+ Thread.Sleep(2 * 1000);
+ }
+ }
+
+ private void Run()
+ {
+ ConfigureLogging();
+
+ while (true)
+ {
+ var client = new ChatRoomClient("http://127.0.0.1:5000", new HttpClient());
+
+ try
+ {
+ RunConnection(client);
+ }
+ catch (Exception e)
+ {
+ //log whatever exception to console
+ log.Warn(e, "Unhandled exception caught. Will restart main loop.");
+
+ //prevent console spamming
+ Thread.Sleep(2000);
+ }
+ }
+ }
+
+ static void Main(string[] args)
+ {
+ var self = new Participant();
+ self.Run();
+ }
+}
diff --git a/Lab2-rest/Participant/Participant.csproj b/Lab2-rest/Participant/Participant.csproj
new file mode 100644
index 0000000..a10c28d
--- /dev/null
+++ b/Lab2-rest/Participant/Participant.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+