tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
ChatController.cs
Go to the documentation of this file.
1using System;
4using System.Linq;
6using System.Net;
10
14
28
30{
35#pragma warning disable CA1506 // TODO: Decomplexify
37 {
47 IDatabaseContext databaseContext,
48 IAuthenticationContext authenticationContext,
51 IApiHeadersProvider apiHeaders)
52 : base(
53 databaseContext,
54 authenticationContext,
55 logger,
57 apiHeaders)
58 {
59 }
60
68 {
70 {
71 IsAdminChannel = api.IsAdminChannel ?? false,
72 IsWatchdogChannel = api.IsWatchdogChannel ?? false,
73 IsUpdatesChannel = api.IsUpdatesChannel ?? false,
74 IsSystemChannel = api.IsSystemChannel ?? false,
75 Tag = api.Tag,
76 };
77
78 if (api.ChannelData != null)
79 {
80 switch (chatProvider)
81 {
82 case ChatProvider.Discord:
83 result.DiscordChannelId = ulong.Parse(api.ChannelData, CultureInfo.InvariantCulture);
84 break;
85 case ChatProvider.Irc:
86 result.IrcChannel = api.ChannelData;
87 break;
88 default:
89 throw new InvalidOperationException($"Invalid chat provider: {chatProvider}");
90 }
91 }
92
93 return result;
94 }
95
103 [HttpPut]
107 {
108 ArgumentNullException.ThrowIfNull(model);
109
111 if (earlyOut != null)
112 return earlyOut;
113
115 .ChatBots
116 .AsQueryable()
117 .Where(x => x.InstanceId == Instance.Id)
118 .CountAsync(cancellationToken);
119
121 return Conflict(new ErrorMessageResponse(ErrorCode.ChatBotMax));
122
123 model.Enabled ??= false;
125
126 // try to update das db first
127 var newChannels = model.Channels?.Select(x => ConvertApiChatChannel(x, model.Provider!.Value)).ToList() ?? new List<Models.ChatChannel>(); // important that this isn't null
129 {
130 Name = model.Name,
131 ConnectionString = model.ConnectionString,
132 Enabled = model.Enabled,
133 InstanceId = Instance.Id!.Value,
134 Provider = model.Provider,
135 ReconnectionInterval = model.ReconnectionInterval,
136 ChannelLimit = model.ChannelLimit,
137 };
138
140
143 async instance =>
144 {
145 try
146 {
147 // try to create it
148 await instance.Chat.ChangeSettings(dbModel, cancellationToken);
149
150 if (dbModel.Channels.Count > 0)
151 await instance.Chat.ChangeChannels(dbModel.Id!.Value, dbModel.Channels, cancellationToken);
152 }
153 catch
154 {
155 // undo the add
157
158 // DCTx2: Operations must always run
159 await DatabaseContext.Save(default);
160 await instance.Chat.DeleteConnection(dbModel.Id!.Value, default);
161 throw;
162 }
163
164 return null;
165 })
166
167 ?? this.StatusCode(HttpStatusCode.Created, dbModel.ToApi());
168 }
169
177 [HttpDelete("{id}")]
180 public async ValueTask<IActionResult> Delete(long id, CancellationToken cancellationToken)
182 async instance =>
183 {
184 await Task.WhenAll(
185 instance.Chat.DeleteConnection(id, cancellationToken),
187 .ChatBots
188 .AsQueryable()
189 .Where(x => x.Id == id)
190 .ExecuteDeleteAsync(cancellationToken));
191 return null;
192 })
193
194 ?? NoContent();
195
208 {
209 var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatBots) & (ulong)ChatBotRights.ReadConnectionString) != 0;
211 () => ValueTask.FromResult(
214 .ChatBots
215 .AsQueryable()
216 .Where(x => x.InstanceId == Instance.Id)
217 .Include(x => x.Channels)
218 .OrderBy(x => x.Id))),
219 chatBot =>
220 {
221 if (!connectionStrings)
222 chatBot.ConnectionString = null;
223
224 return ValueTask.CompletedTask;
225 },
226 page,
227 pageSize,
229 }
230
239 [HttpGet("{id}")]
243 public async ValueTask<IActionResult> GetId(long id, CancellationToken cancellationToken)
244 {
246 .AsQueryable()
247 .Where(x => x.Id == id && x.InstanceId == Instance.Id)
248 .Include(x => x.Channels);
249
250 var results = await query.FirstOrDefaultAsync(cancellationToken);
251 if (results == default)
252 return this.Gone();
253
254 var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatBots) & (ulong)ChatBotRights.ReadConnectionString) != 0;
255
258
259 return Json(results.ToApi());
260 }
261
271 [HttpPost]
272 [TgsAuthorize(ChatBotRights.WriteChannels | ChatBotRights.WriteConnectionString | ChatBotRights.WriteEnabled | ChatBotRights.WriteName | ChatBotRights.WriteProvider)]
276#pragma warning disable CA1502, CA1506 // TODO: Decomplexify
278#pragma warning restore CA1502, CA1506
279 {
280 ArgumentNullException.ThrowIfNull(model);
281
283 if (earlyOut != null)
284 return earlyOut;
285
287 .ChatBots
288 .AsQueryable()
289 .Where(x => x.InstanceId == Instance.Id && x.Id == model.Id)
290 .Include(x => x.Channels);
291
292 var current = await query.FirstOrDefaultAsync(cancellationToken);
293
294 if (current == default)
295 return this.Gone();
296
297 if ((model.Channels?.Count ?? current.Channels!.Count) > (model.ChannelLimit ?? current.ChannelLimit!.Value))
298 {
299 // 400 or 409 depends on if the client sent both
300 var errorMessage = new ErrorMessageResponse(ErrorCode.ChatBotMaxChannels);
301 if (model.Channels != null && model.ChannelLimit.HasValue)
302 return BadRequest(errorMessage);
303 return Conflict(errorMessage);
304 }
305
307
308 bool anySettingsModified = false;
309
311 {
313 var property = (PropertyInfo)memberSelectorExpression.Member;
314
315 var newVal = property.GetValue(model);
316 if (newVal == null)
317 return false;
318 if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal)
319 return true;
320
321 property.SetValue(current, newVal);
322 anySettingsModified = true;
323 return false;
324 }
325
326 var oldProvider = current.Provider;
327
328 if (CheckModified(x => x.ConnectionString, ChatBotRights.WriteConnectionString)
329 || CheckModified(x => x.Enabled, ChatBotRights.WriteEnabled)
330 || CheckModified(x => x.Name, ChatBotRights.WriteName)
331 || CheckModified(x => x.Provider, ChatBotRights.WriteProvider)
332 || CheckModified(x => x.ReconnectionInterval, ChatBotRights.WriteReconnectionInterval)
333 || CheckModified(x => x.ChannelLimit, ChatBotRights.WriteChannelLimit)
334 || (model.Channels != null && !userRights.HasFlag(ChatBotRights.WriteChannels)))
335 return Forbid();
336
338 if (hasChannels || (model.Provider.HasValue && model.Provider != oldProvider))
339 {
340 DatabaseContext.ChatChannels.RemoveRange(current.Channels!);
341 if (hasChannels)
342 {
343 var dbChannels = model.Channels!.Select(x => ConvertApiChatChannel(x, model.Provider ?? current.Provider!.Value)).ToList();
346 }
347 else
348 current.Channels!.Clear();
349 }
350
352
354 async instance =>
355 {
356 var chat = instance.Chat;
358 await chat.ChangeSettings(current, cancellationToken); // have to rebuild the thing first
359
360 if ((model.Channels != null || anySettingsModified) && current.Enabled!.Value)
361 await chat.ChangeChannels(current.Id!.Value, current.Channels, cancellationToken);
362
363 return null;
364 });
365 if (earlyOut != null)
366 return earlyOut;
367
368 if (userRights.HasFlag(ChatBotRights.Read))
369 {
370 if (!userRights.HasFlag(ChatBotRights.ReadConnectionString))
371 current.ConnectionString = null;
372 return Json(current.ToApi());
373 }
374
375 return NoContent();
376 }
377
385 {
386 if (model.ReconnectionInterval == 0)
387 throw new InvalidOperationException("RecconnectionInterval cannot be zero!");
388
389 if (forCreation && !model.Provider.HasValue)
390 return BadRequest(new ErrorMessageResponse(ErrorCode.ChatBotProviderMissing));
391
392 if (model.Name != null && String.IsNullOrWhiteSpace(model.Name))
393 return BadRequest(new ErrorMessageResponse(ErrorCode.ChatBotWhitespaceName));
394
395 if (model.ConnectionString != null && String.IsNullOrWhiteSpace(model.ConnectionString))
396 return BadRequest(new ErrorMessageResponse(ErrorCode.ChatBotWhitespaceConnectionString));
397
398 if (!model.ValidateProviderChannelTypes())
399 return BadRequest(new ErrorMessageResponse(ErrorCode.ChatBotWrongChannelType));
400
401 var defaultMaxChannels = (ulong)Math.Max(ChatBot.DefaultChannelLimit, model.Channels?.Count ?? 0);
402 if (defaultMaxChannels > UInt16.MaxValue)
403 return BadRequest(new ErrorMessageResponse(ErrorCode.ChatBotMaxChannels));
404
405 if (forCreation)
407
408 return null;
409 }
410 }
411#pragma warning restore CA1506
412}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:13
Metadata about a server instance.
Definition Instance.cs:9
ushort? ChatBotLimit
The maximum number of chat bots the Instance may contain.
Definition Instance.cs:50
string? Tag
A custom tag users can define to group channels together.
virtual ? string Name
The name of the entity represented by the NamedEntity.
Represents an error message returned by the server.
Routes to a server actions.
Definition Routes.cs:9
const string List
The postfix for list operations.
Definition Routes.cs:113
const string Chat
The chat bot controller.
Definition Routes.cs:93
StatusCodeResult StatusCode(HttpStatusCode statusCode)
Strongly type calls to ControllerBase.StatusCode(int).
ApiController for managing ChatBots.
static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api, ChatProvider chatProvider)
Converts api to a ChatChannel.
async ValueTask< IActionResult > Delete(long id, CancellationToken cancellationToken)
Delete a ChatBot.
async ValueTask< IActionResult > GetId(long id, CancellationToken cancellationToken)
Get a specific ChatBot.
BadRequestObjectResult? StandardModelChecks(ChatBotApiBase model, bool forCreation)
Perform some basic validation of a given model .
async ValueTask< IActionResult > Update([FromBody] ChatBotUpdateRequest model, CancellationToken cancellationToken)
Updates a chat bot model .
async ValueTask< IActionResult > Create([FromBody] ChatBotCreateRequest model, CancellationToken cancellationToken)
Create a new chat bot model .
ValueTask< IActionResult > List([FromQuery] int? page, [FromQuery] int? pageSize, CancellationToken cancellationToken)
List ChatBots.
ChatController(IDatabaseContext databaseContext, IAuthenticationContext authenticationContext, ILogger< ChatController > logger, IInstanceManager instanceManager, IApiHeadersProvider apiHeaders)
Initializes a new instance of the ChatController class.
async ValueTask< IActionResult?> WithComponentInstanceNullable(Func< IInstanceCore, ValueTask< IActionResult?> > action, Models.Instance? instance=null)
Run a given action with the relevant IInstance.
readonly IInstanceManager instanceManager
The IInstanceManager for the ComponentInterfacingController.
ComponentInterfacingController for operations that require an instance.
Backend abstract implementation of IDatabaseContext.
DbSet< ChatChannel > ChatChannels
The ChatChannels in the DatabaseContext.
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext.A Task representing the running operation.
DbSet< ChatBot > ChatBots
The ChatBots in the DatabaseContext.
const ushort DefaultChannelLimit
Default for Api.Models.Internal.ChatBotSettings.ChannelLimit.
Definition ChatBot.cs:16
ulong GetRight(RightsType rightsType)
Get the value of a given rightsType .The value of rightsType . Note that if InstancePermissionSet is ...
For creating and accessing authentication contexts.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
ChatProvider
Represents a chat service provider.
ChatBotRights
Rights for chat bots.
@ List
User may list files if the Models.Instance allows it.
RightsType
The type of rights a model uses.
Definition RightsType.cs:7
@ Api
The ApiHeaders.ApiVersionHeader header is missing or invalid.
@ Enabled
The OAuth Gateway is enabled.