Skip to content

Commit

Permalink
feat(app): add endpoint to change subscription url (#33)
Browse files Browse the repository at this point in the history
add endpoint: /api/apps/appchanges/{appId}/subscription/{subscriptionId}/tenantUrl
to change the subscription url for subscriptions that are active
Refs: CPLP-2656
---------
Co-authored-by: Norbert Truchsess <norbert.truchsess@t-online.de>
Reviewed-By: Norbert Truchsess <norbert.truchsess@t-online.de>
  • Loading branch information
Phil91 authored May 16, 2023
1 parent a7028c4 commit 4153baf
Show file tree
Hide file tree
Showing 19 changed files with 6,794 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Async;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.IO;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Web;
using Org.Eclipse.TractusX.Portal.Backend.Notifications.Library;
using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service;
Expand All @@ -41,6 +42,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.BusinessLogic;
/// </summary>
public class AppChangeBusinessLogic : IAppChangeBusinessLogic
{
private static readonly JsonSerializerOptions Options = new (){ PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
private readonly IPortalRepositories _portalRepositories;
private readonly AppsSettings _settings;
private readonly INotificationService _notificationService;
Expand Down Expand Up @@ -125,7 +127,7 @@ await ValidateAndGetAppDescription(appId, iamUserId, offerRepository),

await _portalRepositories.SaveAsync().ConfigureAwait(false);
}

private static async Task<IEnumerable<LocalizedDescription>> ValidateAndGetAppDescription(Guid appId, string iamUserId, IOfferRepository offerRepository)
{
var result = await offerRepository.GetActiveOfferDescriptionDataByIdAsync(appId, OfferTypeId.APP, iamUserId).ConfigureAwait(false);
Expand Down Expand Up @@ -197,4 +199,82 @@ public async Task UploadOfferAssignedAppLeadImageDocumentByIdAsync(Guid appId, s
/// <inheritdoc />
public Task DeactivateOfferByAppIdAsync(Guid appId, string iamUserId) =>
_offerService.DeactivateOfferIdAsync(appId, iamUserId, OfferTypeId.APP);

/// <inheritdoc />
public Task UpdateTenantUrlAsync(Guid offerId, Guid subscriptionId, UpdateTenantData data, string iamUserId)
{
data.Url.EnsureValidHttpUrl(() => nameof(data.Url));
return UpdateTenantUrlAsyncInternal(offerId, subscriptionId, data.Url, iamUserId);
}

private async Task UpdateTenantUrlAsyncInternal(Guid offerId, Guid subscriptionId, string url, string iamUserId)
{
var offerSubscriptionsRepository = _portalRepositories.GetInstance<IOfferSubscriptionsRepository>();
var result = await offerSubscriptionsRepository.GetUpdateUrlDataAsync(offerId, subscriptionId, iamUserId).ConfigureAwait(false);
if (result == null)
{
throw new NotFoundException($"Offer {offerId} or subscription {subscriptionId} do not exists");
}

var (offerName, isSingleInstance, isUserOfCompany, requesterId, subscribingCompanyId, offerSubscriptionStatusId, detailData) = result;
if (isSingleInstance)
{
throw new ConflictException("Subscription url of single instance apps can't be changed");
}

if (!isUserOfCompany)
{
throw new ForbiddenException($"User {iamUserId} is not part of the app's providing company");
}

if (offerSubscriptionStatusId != OfferSubscriptionStatusId.ACTIVE)
{
throw new ConflictException($"Subscription {subscriptionId} must be in status {OfferSubscriptionStatusId.ACTIVE}");
}

if (detailData == null)
{
throw new ConflictException($"There is no subscription detail data configured for subscription {subscriptionId}");
}

if (url == detailData.SubscriptionUrl)
return;

offerSubscriptionsRepository.AttachAndModifyAppSubscriptionDetail(detailData.DetailId, subscriptionId,
os =>
{
os.AppSubscriptionUrl = detailData.SubscriptionUrl;
},
os =>
{
os.AppSubscriptionUrl = url;
});

if (!string.IsNullOrEmpty(detailData.ClientClientId))
{
await _provisioningManager.UpdateClient(detailData.ClientClientId, url, url.AppendToPathEncoded("*")).ConfigureAwait(false);
}

var notificationContent = JsonSerializer.Serialize(new
{
AppId = offerId,
AppName = offerName,
OldUrl = detailData.SubscriptionUrl,
NewUrl = url
}, Options);
if (requesterId != Guid.Empty)
{
_portalRepositories.GetInstance<INotificationRepository>().CreateNotification(requesterId, NotificationTypeId.SUBSCRIPTION_URL_UPDATE, false,
n =>
{
n.Content = notificationContent;
});
}
else
{
await _notificationService.CreateNotifications(_settings.CompanyAdminRoles, null, new (string?,NotificationTypeId)[] {(notificationContent, NotificationTypeId.SUBSCRIPTION_URL_UPDATE)}, subscribingCompanyId).AwaitAll().ConfigureAwait(false);
}

await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,13 @@ public interface IAppChangeBusinessLogic
/// <param name="appId">Id of the app</param>
/// <param name="iamUserId">Id of the iamUser</param>
public Task DeactivateOfferByAppIdAsync(Guid appId, string iamUserId);

/// <summary>
/// Updates the url of the subscription
/// </summary>
/// <param name="offerId">Id of the offer</param>
/// <param name="subscriptionId">If of the subscription</param>
/// <param name="data">the data to update the url</param>
/// <param name="iamUserId">id of the iamuser</param>
Task UpdateTenantUrlAsync(Guid offerId, Guid subscriptionId, UpdateTenantData data, string iamUserId);
}
25 changes: 23 additions & 2 deletions src/marketplace/Apps.Service/Controllers/AppChangeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task<IEnumerable<LocalizedDescription>> GetAppUpdateDescriptionsAsy
/// </summary>
/// <param name="appId" example="092bdae3-a044-4314-94f4-85c65a09e31b">Id for the app description to create or update.</param>
/// <param name="offerDescriptionDatas">app description data to create or update.</param>
/// <remarks>Example: Put: /api/apps/appchanges/092bdae3-a044-4314-94f4-85c65a09e31b/appupdate/description</remarks>
/// <remarks>Example: Put: /api/apps/appchange/092bdae3-a044-4314-94f4-85c65a09e31b/appupdate/description</remarks>
/// <response code="204">The app description succesFully created or updated</response>
[HttpPut]
[Route("{appId}/appupdate/description")]
Expand All @@ -112,7 +112,7 @@ public async Task<NoContentResult> CreateOrUpdateAppDescriptionsByIdAsync([FromR
/// <param name="appId"></param>
/// <param name="document"></param>
/// <param name="cancellationToken"></param>
/// <remarks>Example: POST: /api/apps/appchanges/{appId}/appLeadImage</remarks>
/// <remarks>Example: POST: /api/apps/appchange/{appId}/appLeadImage</remarks>
/// <response code="204">Successfully uploaded the document</response>
/// <response code="400">If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.</response>
/// <response code="403">The user is not assigned with the app.</response>
Expand Down Expand Up @@ -155,4 +155,25 @@ public async Task<NoContentResult> DeactivateApp([FromRoute] Guid appId)
await this.WithIamUserId(userId => _businessLogic.DeactivateOfferByAppIdAsync(appId,userId)).ConfigureAwait(false);
return NoContent();
}

/// <summary>
/// Updates the url for a specific subscription
/// </summary>
/// <param name="appId" example="092bdae3-a044-4314-94f4-85c65a09e31b">Id of the app.</param>
/// <param name="subscriptionId" example="092bdae3-a044-4314-94f4-85c65a09e31b">Id of the subscription.</param>
/// <param name="data">new url for the subscription.</param>
/// <remarks>Example: Put: /api/apps/appchange/{appId}/subscription/{subscriptionId}/tenantUrl</remarks>
/// <response code="204">The app description succesFully created or updated</response>
[HttpPut]
[Route("{appId}/subscription/{subscriptionId}/tenantUrl")]
[Authorize(Roles = "edit_apps")]
[ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)]
public async Task<NoContentResult> UpdateTenantUrl([FromRoute] Guid appId, [FromRoute] Guid subscriptionId, [FromBody] UpdateTenantData data)
{
await this.WithIamUserId(iamUserId => _businessLogic.UpdateTenantUrlAsync(appId, subscriptionId, data, iamUserId)).ConfigureAwait(false);
return NoContent();
}
}
27 changes: 27 additions & 0 deletions src/marketplace/Apps.Service/ViewModels/UpdateTenantData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/********************************************************************************
* Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;

/// <summary>
/// Data to update the offer subscription url
/// </summary>
/// <param name="Url">The new url</param>
public record UpdateTenantData(string Url);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/********************************************************************************
* Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;

namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;

public record OfferUpdateUrlData(
string? OfferName,
bool IsSingleInstance,
bool IsUserOfCompany,
Guid RequesterId,
Guid SubscribingCompanyId,
OfferSubscriptionStatusId OfferSubscriptionStatusId,
OfferUpdateUrlSubscriptionDetailData? SubscriptionDetailData);

public record OfferUpdateUrlSubscriptionDetailData (
Guid DetailId,
string? ClientClientId,
string? SubscriptionUrl
);
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,22 @@ public interface IOfferSubscriptionsRepository
/// <param name="userRoleIds">Ids of the user roles the contacts should be in</param>
/// <returns>Returns details for the offer subscription</returns>
Task<(bool Exists, bool IsUserOfCompany, OfferSubscriptionDetailData Details)> GetSubscriptionDetailsAsync(Guid offerId, Guid subscriptionId, string iamUserId, OfferTypeId offerTypeId, IEnumerable<Guid> userRoleIds, bool forProvider);

/// <summary>
/// Get the data to update the subscription url
/// </summary>
/// <param name="offerId">Id of the offer</param>
/// <param name="subscriptionId">Id of the subscription</param>
/// <param name="iamUserId">Id of the iam user</param>
/// <returns>Returns the data needed to update the subscription url</returns>
Task<OfferUpdateUrlData?> GetUpdateUrlDataAsync(Guid offerId, Guid subscriptionId, string iamUserId);

/// <summary>
/// The subscription details
/// </summary>
/// <param name="detailId">Id of the detail to update</param>
/// <param name="subscriptionId">Id of the subscription</param>
/// <param name="initialize">Initializes the entity</param>
/// <param name="setParameters">Updates the fields</param>
void AttachAndModifyAppSubscriptionDetail(Guid detailId, Guid subscriptionId, Action<AppSubscriptionDetail>? initialize, Action<AppSubscriptionDetail> setParameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,33 @@ public void AttachAndModifyOfferSubscription(Guid offerSubscriptionId, Action<Of
x.OtherCompany.CompanyUsers.Where(cu => cu.Email != null && cu.UserRoles.Any(ur => userRoleIds.Contains(ur.Id))).Select(cu => cu.Email!),
x.CompanyServiceAccounts.Select(sa => new SubscriptionTechnicalUserData(sa.Id, sa.Name, sa.UserRoles.Select(x => x.UserRoleText))))))
.SingleOrDefaultAsync();

/// <inheritdoc />
public Task<OfferUpdateUrlData?> GetUpdateUrlDataAsync(Guid offerId, Guid subscriptionId, string iamUserId) =>
_context.OfferSubscriptions
.Where(os => os.Id == subscriptionId && os.OfferId == offerId)
.Select(os => new OfferUpdateUrlData(
os.Offer!.Name,
(os.Offer.AppInstanceSetup != null && os.Offer.AppInstanceSetup!.IsSingleInstance),
os.Offer.ProviderCompany!.CompanyUsers.Any(x => x.IamUser!.UserEntityId == iamUserId),
os.RequesterId,
os.CompanyId,
os.OfferSubscriptionStatusId,
os.AppSubscriptionDetail == null ?
null :
new OfferUpdateUrlSubscriptionDetailData(
os.AppSubscriptionDetail.Id,
os.AppSubscriptionDetail.AppInstance!.IamClient!.ClientClientId,
os.AppSubscriptionDetail.AppSubscriptionUrl)
))
.SingleOrDefaultAsync();

/// <inheritdoc />
public void AttachAndModifyAppSubscriptionDetail(Guid detailId, Guid subscriptionId, Action<AppSubscriptionDetail>? initialize, Action<AppSubscriptionDetail> setParameters)
{
var appSubscriptionDetail = new AppSubscriptionDetail(detailId, subscriptionId);
initialize?.Invoke(appSubscriptionDetail);
_context.Attach(appSubscriptionDetail);
setParameters.Invoke(appSubscriptionDetail);
}
}
Loading

0 comments on commit 4153baf

Please sign in to comment.