Skip to content

Commit 6a3dcb9

Browse files
Case2 backend (#9)
1 parent f3e5250 commit 6a3dcb9

40 files changed

Lines changed: 127 additions & 1248 deletions

File tree

DocuSign.Workspaces/DocuSign.Workspaces.sln.DotSettings.user

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACreateWorkspaceUploadRequestAssignment_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F618474b3a7a24c07a172752f30df361467000_003Fe4_003F72813357_003FCreateWorkspaceUploadRequestAssignment_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
88
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACreateWorkspaceUploadRequestBody_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F618474b3a7a24c07a172752f30df361467000_003F5d_003Ff0d879c2_003FCreateWorkspaceUploadRequestBody_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
99
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADocuSignClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003F4c_003F0d171731_003FDocuSignClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
10+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelopeDefinition_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003Fee_003Fe9fd97a6_003FEnvelopeDefinition_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1011
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelopeDocumentsResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003F1e_003Fc6579aed_003FEnvelopeDocumentsResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1112
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelopeDocument_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003Fc8_003F4c34a961_003FEnvelopeDocument_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
13+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelopeNotificationRequest_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003Fd3_003Fb337f57b_003FEnvelopeNotificationRequest_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1214
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelopesApi_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003F4a_003F2f4cb457_003FEnvelopesApi_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1315
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvelope_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003F4f_003F4a01c6f8_003FEnvelope_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
16+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEventNotification_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003F59_003Feaeedd8a_003FEventNotification_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1417
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc8f7d45dcd2c48e78402c5a517034592f83200_003Fb1_003F3ca91393_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1518
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1619
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
20+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F50a88c0ea42f4d46a834d193cbf6e9051d8a00_003F19_003F66bb6d07_003FHttpClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1721
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEnvelopesApi_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5a0acb18b7542e28324415a109391b15bd200_003Fca_003F2e84465e_003FIEnvelopesApi_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1822
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AISpeakeasyHttpClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F618474b3a7a24c07a172752f30df361467000_003F2c_003F2a764133_003FISpeakeasyHttpClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1923
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIWorkspacesApi_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F76a122f643e648e8acfd46d3c83cc6b6592800_003Fbf_003F01a9b829_003FIWorkspacesApi_002Ecs_002Fz_003A3_002D2/@EntryIndexedValue">ForceIncluded</s:String>

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/Admin/AdminController.cs

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using DocuSign.eSign.Model;
2-
using DocuSign.Workspaces.Controllers.Common.Models;
1+
using DocuSign.Workspaces.Controllers.Common.Models;
32
using DocuSign.Workspaces.Domain.Admin.Models;
43
using DocuSign.Workspaces.Domain.Admin.Services.Interfaces;
54
using Microsoft.AspNetCore.Authentication;
@@ -8,7 +7,6 @@
87
using Microsoft.AspNetCore.Mvc;
98
using System.Collections.Generic;
109
using System.Linq;
11-
using System.Security.Claims;
1210
using System.Threading.Tasks;
1311
using DocuSign.Workspaces.Controllers.Admin.Models;
1412
using DocuSign.Workspaces.Infrastructure.Exceptions;
@@ -48,15 +46,12 @@ public IActionResult ObtainConsent([FromBody] RequestAccountAuthorizeModel model
4846
settings.BasePath = model.BasePath;
4947
_settingsRepository.Save(settings);
5048

51-
switch (model.ConsentType)
49+
return model.ConsentType switch
5250
{
53-
case ConsentType.Admin:
54-
return Ok(new ResponseAccountAuthorizeModel(_authenticationService.CreateAdminConsentUrl(model.BasePath, $"api/consentcallback")));
55-
case ConsentType.Individual:
56-
return Ok(new ResponseAccountAuthorizeModel(_authenticationService.CreateUserConsentUrl(model.BasePath, $"api/consentcallback")));
57-
default:
58-
return BadRequest("Unknown consent type");
59-
}
51+
ConsentType.Admin => Ok(new ResponseAccountAuthorizeModel(_authenticationService.CreateAdminConsentUrl(model.BasePath, $"api/consentcallback"))),
52+
ConsentType.Individual => Ok(new ResponseAccountAuthorizeModel(_authenticationService.CreateUserConsentUrl(model.BasePath, $"api/consentcallback"))),
53+
_ => BadRequest("Unknown consent type")
54+
};
6055
}
6156

6257
[HttpGet]
@@ -77,7 +72,7 @@ public IActionResult ConsentCallback(string code)
7772
settings.IsConsentGranted = true;
7873
settings.UserId = _authenticationService.PrePopulateUserId(settings.BasePath, code);
7974
_settingsRepository.Save(settings);
80-
return LocalRedirect("/admin");
75+
return LocalRedirect("/");
8176
}
8277

8378
[HttpGet]
@@ -99,10 +94,10 @@ public IActionResult GetAccounts(string basePath, string userId)
9994
[Route("/api/account/connect")]
10095
public async Task<IActionResult> Connect([FromBody] RequestAccountConnectModel model)
10196
{
102-
AccountConnectionSettings connectionSettings = CreateConnectionSettings(model);
97+
var connectionSettings = CreateConnectionSettings(model);
10398
try
10499
{
105-
ClaimsPrincipal principal =
100+
var principal =
106101
_authenticationService.AuthenticateFromJwt(connectionSettings);
107102

108103
await HttpContext.SignInAsync(
@@ -137,12 +132,12 @@ public IActionResult GetStatus()
137132
{
138133
ConnectedUser = new ConnectedUserModel
139134
{
140-
Name = HttpContext.User.Identity.Name ?? string.Empty,
135+
Name = HttpContext.User.Identity?.Name ?? string.Empty,
141136
Email = _accountRepository.Email,
142137
AccountName = _accountRepository.AccountName
143138
},
144139
IsConsentGranted = _settingsRepository.Get().IsConsentGranted,
145-
IsConnected = HttpContext.User.Identity.IsAuthenticated
140+
IsConnected = HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated
146141
};
147142
return Ok(model);
148143
}
@@ -177,7 +172,7 @@ public async Task<IActionResult> Logout()
177172
[Route("/api/settings")]
178173
public IActionResult GetSetting()
179174
{
180-
Settings settings = _settingsRepository.Get();
175+
var settings = _settingsRepository.Get();
181176
return Ok(new SettingsModel
182177
{
183178
BasePath = settings.BasePath,
@@ -194,7 +189,7 @@ public IActionResult GetSetting()
194189
[Route("/api/settings/datasource")]
195190
public IActionResult GetDatasource()
196191
{
197-
Settings settings = _settingsRepository.Get();
192+
var settings = _settingsRepository.Get();
198193
return Ok(new DataSourceModel
199194
{
200195
SignatureTypes = settings.SignatureTypesDataSource,
@@ -220,51 +215,40 @@ public IActionResult SetSettings([FromBody] SettingsModel model)
220215

221216
private AccountConnectionSettings CreateConnectionSettings(RequestAccountConnectModel model)
222217
{
223-
AccountConnectionSettings connectionSettings = null;
224-
switch (model.AuthenticationType)
218+
var connectionSettings = model.AuthenticationType switch
225219
{
226-
case AuthenticationType.UserAccount:
227-
connectionSettings = new AccountConnectionSettings
228-
{
229-
BasePath = model.BasePath,
230-
BaseUri = model.BaseUri,
231-
AccountId = model.AccountId,
232-
UserId = model.UserId
233-
};
234-
break;
235-
case AuthenticationType.TestAccount:
236-
connectionSettings = _testAccountConnectionSettingsRepository.Get();
237-
break;
238-
default:
239-
break;
240-
}
220+
AuthenticationType.UserAccount => new AccountConnectionSettings
221+
{
222+
BasePath = model.BasePath,
223+
BaseUri = model.BaseUri,
224+
AccountId = model.AccountId,
225+
UserId = model.UserId
226+
},
227+
AuthenticationType.TestAccount => _testAccountConnectionSettingsRepository.Get(),
228+
_ => null
229+
};
241230

242231
return connectionSettings;
243232
}
244233

245234
private ErrorDetailsModel CreateErrorDetails(ApiErrorDetails error, RequestAccountConnectModel model)
246235
{
247-
switch (error.Error)
236+
return error.Error switch
248237
{
249-
case ApiErrorDetails.InvalidBasePath:
250-
return ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, model => model.BasePath);
251-
case ApiErrorDetails.InvalidBaseUri:
252-
return ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, model => model.BaseUri);
253-
case ApiErrorDetails.InvalidUserId:
254-
return ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, model => model.UserId);
255-
case ApiErrorDetails.InvalidAccountId:
256-
return ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, model => model.AccountId);
257-
default:
258-
return ErrorDetailsModel.CreateGeneralErrorDetails(error.Error);
259-
}
238+
ApiErrorDetails.InvalidBasePath => ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, connectModel => connectModel.BasePath),
239+
ApiErrorDetails.InvalidBaseUri => ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, connectModel => connectModel.BaseUri),
240+
ApiErrorDetails.InvalidUserId => ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, connectModel => connectModel.UserId),
241+
ApiErrorDetails.InvalidAccountId => ErrorDetailsModel.CreateErrorDetailsForOneModelProperty(error.Error, model, connectModel => connectModel.AccountId),
242+
_ => ErrorDetailsModel.CreateGeneralErrorDetails(error.Error)
243+
};
260244
}
261245

262246
private IEnumerable<DataSourceItem> GetSignatureTypesDataSource(string accountId)
263247
{
264248
var signatureProviders = _docuSignApiProvider.AccountsApi.ListSignatureProviders(accountId);
265249
var result = new List<DataSourceItem>
266250
{
267-
new DataSourceItem
251+
new()
268252
{
269253
Key = SignatureInfo.DefaultProviderName,
270254
Value = SignatureInfo.DefaultProviderDisplayName
@@ -277,10 +261,10 @@ private IEnumerable<DataSourceItem> GetSignatureTypesDataSource(string accountId
277261

278262
private IEnumerable<DataSourceItem> GetTemplatesDataSource(string accountId)
279263
{
280-
EnvelopeTemplateResults userTemplates = _docuSignApiProvider.TemplatesApi.ListTemplates(accountId);
264+
var userTemplates = _docuSignApiProvider.TemplatesApi.ListTemplates(accountId);
281265
var result = new List<DataSourceItem>
282266
{
283-
new DataSourceItem
267+
new()
284268
{
285269
Key = TemplateNames.DefaultTemplateId,
286270
Value = TemplateNames.DefaultTemplateName

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/Admin/Models/ConnectedUserModel.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@
22
{
33
public class ConnectedUserModel
44
{
5-
public ConnectedUserModel()
6-
{
7-
}
8-
9-
public ConnectedUserModel(string name, string email)
10-
{
11-
Name = name;
12-
Email = email;
13-
}
14-
155
public string Name { get; set; }
166
public string Email { get; set; }
177
public string AccountName { get; set; }

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/Admin/Models/ResponseGetAccountsModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public class ResponseGetAccountsModel
44
{
55
public string BaseUri { get; set; }
66
public string AccountId { get; set; }
7-
public string AccountName { get; set; }
7+
public string AccountName { get; set; }
88
public bool IsDefault { get; set; }
99
}
1010
}

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/CarePlans/CarePlansController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Threading.Tasks;
34
using DocuSign.Workspaces.Domain.CarePlans;

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/Events/DocuSignEventsController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ public DocuSignEventsController(IHubContext<EventsHub> hubContext, IEventsReposi
2424
[HttpGet]
2525
public async Task<IActionResult> ReceiveEvent([FromBody] DocuSignEventModel docusignEvent)
2626
{
27-
string evnvelopId = docusignEvent?.Data?.EnvelopeId;
27+
var evnvelopId = docusignEvent?.Data?.EnvelopeId;
2828
if (_eventsRepository.IsEnvelopRegistered(evnvelopId))
2929
{
30-
(string connectionId, string useCaseType) = _eventsRepository.GetEnvelopDetails(evnvelopId);
30+
var (connectionId, useCaseType) = _eventsRepository.GetEnvelopDetails(evnvelopId);
3131
var eventModel = CreateEventModel(useCaseType, docusignEvent);
3232
await _hubContext.Clients.Client(connectionId).SendAsync("ReceivedEvent", eventModel);
3333
}

DocuSign.Workspaces/DocuSign.Workspaces/Controllers/WealthManagement/WealthManagementClientController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Threading.Tasks;
43
using DocuSign.Workspaces.Domain.Workspaces;

0 commit comments

Comments
 (0)