diff --git a/BTCPayServer.Client/BTCPayServerClient.Apps.cs b/BTCPayServer.Client/BTCPayServerClient.Apps.cs index 71f0db681..159f32ea5 100644 --- a/BTCPayServer.Client/BTCPayServerClient.Apps.cs +++ b/BTCPayServer.Client/BTCPayServerClient.Apps.cs @@ -51,6 +51,24 @@ namespace BTCPayServer.Client method: HttpMethod.Get), token); return await HandleResponse(response); } + + public virtual async Task GetAllApps(string storeId, CancellationToken token = default) + { + if (storeId == null) + throw new ArgumentNullException(nameof(storeId)); + var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/stores/{storeId}/apps", + method: HttpMethod.Get), token); + return await HandleResponse(response); + } + + public virtual async Task GetAllApps(CancellationToken token = default) + { + var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/apps", + method: HttpMethod.Get), token); + return await HandleResponse(response); + } public virtual async Task DeleteApp(string appId, CancellationToken token = default) { diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 92a93e855..855f77e29 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -400,6 +400,64 @@ namespace BTCPayServer.Tests Assert.Equal("Crowdfund", app.AppType); } + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + public async Task CanGetAllApps() + { + using var tester = CreateServerTester(); + await tester.StartAsync(); + var user = tester.NewAccount(); + await user.RegisterDerivationSchemeAsync("BTC"); + var client = await user.CreateClient(); + + var posApp = await client.CreatePointOfSaleApp( + user.StoreId, + new CreatePointOfSaleAppRequest() + { + AppName = "test app from API", + Currency = "JPY" + } + ); + var crowdfundApp = await client.CreateCrowdfundApp(user.StoreId, new CreateCrowdfundAppRequest() { AppName = "test app from API" }); + + // Create another store and one app on it so we can get all apps from all stores for the user below + var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" }); + var newApp = await client.CreateCrowdfundApp(newStore.Id, new CreateCrowdfundAppRequest() { AppName = "new app" }); + + Assert.NotEqual(newApp.Id, user.StoreId); + + // Get all apps for a specific store first + var apps = await client.GetAllApps(user.StoreId); + + Assert.Equal(2, apps.Length); + + Assert.Equal(posApp.Name, apps[0].Name); + Assert.Equal(posApp.StoreId, apps[0].StoreId); + Assert.Equal(posApp.AppType, apps[0].AppType); + + Assert.Equal(crowdfundApp.Name, apps[1].Name); + Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId); + Assert.Equal(crowdfundApp.AppType, apps[1].AppType); + + // Get all apps for all store now + apps = await client.GetAllApps(); + + Assert.Equal(3, apps.Length); + + Assert.Equal(posApp.Name, apps[0].Name); + Assert.Equal(posApp.StoreId, apps[0].StoreId); + Assert.Equal(posApp.AppType, apps[0].AppType); + + Assert.Equal(crowdfundApp.Name, apps[1].Name); + Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId); + Assert.Equal(crowdfundApp.AppType, apps[1].AppType); + + Assert.Equal(newApp.Name, apps[2].Name); + Assert.Equal(newApp.StoreId, apps[2].StoreId); + Assert.Equal(newApp.AppType, apps[2].AppType); + + } + [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task CanDeleteUsersViaApi() diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs index 60c2c7b87..7a2a6b779 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs @@ -25,18 +25,20 @@ namespace BTCPayServer.Controllers.Greenfield private readonly AppService _appService; private readonly StoreRepository _storeRepository; private readonly CurrencyNameTable _currencies; + private readonly UserManager _userManager; public GreenfieldAppsController( AppService appService, StoreRepository storeRepository, - UserManager userManager, BTCPayNetworkProvider btcPayNetworkProvider, - CurrencyNameTable currencies + CurrencyNameTable currencies, + UserManager userManager ) { _appService = appService; _storeRepository = storeRepository; _currencies = currencies; + _userManager = userManager; } [HttpPost("~/api/v1/stores/{storeId}/apps/crowdfund")] @@ -143,6 +145,24 @@ namespace BTCPayServer.Controllers.Greenfield } } + [HttpGet("~/api/v1/apps")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + public async Task GetAllApps() + { + var apps = await _appService.GetAllApps(_userManager.GetUserId(User)); + + return Ok(apps.Select(ToModel).ToArray()); + } + + [HttpGet("~/api/v1/stores/{storeId}/apps")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + public async Task GetAllApps(string storeId) + { + var apps = await _appService.GetAllApps(_userManager.GetUserId(User), allowNoUser: false, storeId); + + return Ok(apps.Select(ToModel).ToArray()); + } + [HttpGet("~/api/v1/apps/{appId}")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task GetApp(string appId) @@ -250,6 +270,18 @@ namespace BTCPayServer.Controllers.Greenfield }; } + private AppDataBase ToModel(Models.AppViewModels.ListAppsViewModel.ListAppViewModel appData) + { + return new AppDataBase + { + Id = appData.Id, + AppType = appData.AppType, + Name = appData.AppName, + StoreId = appData.StoreId, + Created = appData.Created, + }; + } + private PointOfSaleAppData ToPointOfSaleModel(AppData appData) { return new PointOfSaleAppData diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index 65d342fe6..2b1964d4a 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -1196,6 +1196,18 @@ namespace BTCPayServer.Controllers.Greenfield await GetController().GetApp(appId)); } + public override async Task GetAllApps(string storeId, CancellationToken token = default) + { + return GetFromActionResult( + await GetController().GetAllApps(storeId)); + } + + public override async Task GetAllApps(CancellationToken token = default) + { + return GetFromActionResult( + await GetController().GetAllApps()); + } + public override async Task DeleteApp(string appId, CancellationToken token = default) { HandleActionResult(await GetController().DeleteApp(appId)); diff --git a/BTCPayServer/Models/AppViewModels/ListAppsViewModel.cs b/BTCPayServer/Models/AppViewModels/ListAppsViewModel.cs index ee9d0287d..b837422d3 100644 --- a/BTCPayServer/Models/AppViewModels/ListAppsViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ListAppsViewModel.cs @@ -1,3 +1,6 @@ +using System; + + namespace BTCPayServer.Models.AppViewModels { public class ListAppsViewModel @@ -14,6 +17,7 @@ namespace BTCPayServer.Models.AppViewModels public string UpdateAction { get { return "Update" + AppType; } } public string ViewAction { get { return "View" + AppType; } } + public DateTimeOffset Created { get; set; } } public ListAppViewModel[] Apps { get; set; } diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index 31d83e2b2..c1893a50c 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -443,7 +443,8 @@ namespace BTCPayServer.Services.Apps StoreName = us.StoreData.StoreName, AppName = app.Name, AppType = app.AppType, - Id = app.Id + Id = app.Id, + Created = app.Created, }) .ToArrayAsync(); diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.apps.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.apps.json index e6df8e73e..5902c521b 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.apps.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.apps.json @@ -267,7 +267,84 @@ } ] } - } + }, + "/api/v1/stores/{storeId}/apps": { + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store ID", + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "Apps" + ], + "operationId": "Apps_GetAllAppsForStore", + "summary": "Get basic app data for all apps for a store", + "description": "Returns basic app data for all apps for a store", + "responses": { + "200": { + "description": "Array of basic app data object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BasicAppData" + } + } + } + } + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + } + }, + "/api/v1/apps": { + "get": { + "tags": [ + "Apps" + ], + "operationId": "Apps_GetAllApps", + "summary": "Get basic app data for all apps for all stores for a user", + "description": "Returns basic app data for all apps for all stores", + "responses": { + "200": { + "description": "Array of basic app data object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BasicAppData" + } + } + } + } + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + } + } }, "components": { "schemas": {