Skip to content

Commit

Permalink
feat(subscriptionDecline): Ability for App or Service Provider to Dec…
Browse files Browse the repository at this point in the history
…line the Subscription request

Ref: eclipse-tractusx#1140

- /api/apps/subscription/{subscriptionId}/decline
- /api/services/subscription/{subscriptionId}/decline
  • Loading branch information
tfjanjua committed Nov 21, 2024
1 parent 3a07d78 commit caefc7d
Show file tree
Hide file tree
Showing 22 changed files with 11,173 additions and 24 deletions.
38 changes: 38 additions & 0 deletions docs/api/apps-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,44 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: The User is unauthorized
'/api/apps/subscription/{subscriptionId}/decline':
put:
tags:
- Apps
summary: 'Declines a pending app subscription. (Authorization required - Roles: decline_subscription)'
description: 'Example: PUT: /api/apps/subscription/{subscriptiondId}/decline'
parameters:
- name: subscriptionId
in: path
description: ID of the subscription to decline.
required: true
schema:
type: string
format: uuid
example: D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645
responses:
'204':
description: App subscription was successfully declined.
'400':
description: 'If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: 'If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal Server Error.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: The User is unauthorized
'/api/apps/{subscriptionId}/unsubscribe':
put:
tags:
Expand Down
2 changes: 2 additions & 0 deletions docs/api/notifications-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ components:
- CREDENTIAL_APPROVAL
- CREDENTIAL_REJECTED
- CREDENTIAL_EXPIRY
- APP_SUBSCRIPTION_DECLINE
- SERVICE_SUBSCRIPTION_DECLINE
type: string
SearchSemanticTypeId:
enum:
Expand Down
44 changes: 44 additions & 0 deletions docs/api/services-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,50 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: The User is unauthorized
'/api/services/subscription/{subscriptionId}/decline':
put:
tags:
- Services
summary: 'Declines a pending service subscription. (Authorization required - Roles: decline_subscription)'
description: 'Example: PUT: /api/services/supscription/{subscriptiondId}/decline'
parameters:
- name: subscriptionId
in: path
description: ID of the subscription to decline.
required: true
schema:
type: string
format: uuid
example: D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645
responses:
'204':
description: Service subscription was successfully declined.
'400':
description: 'If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: Conflict
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal Server Error.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: The User is unauthorized
components:
schemas:
AgreementConsentStatus:
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/mailing/Mailing.Template/Enums/EmailTemplateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public enum EmailTemplateType
[Path("appprovider_subscription_request.html")]
AppSubscriptionRequest,

/// <summary>
/// Email template for notifying requester (subscriber) of subscription declination.
/// </summary>
[Path("app_subscription_decline.html")]
AppSubscriptionDecline,

/// <summary>
/// Email template for notifying app providers of subscription activition.
/// </summary>
Expand All @@ -95,6 +101,12 @@ public enum EmailTemplateType
[Path("serviceprovider_subscription_request.html")]
ServiceSubscriptionRequest,

/// <summary>
/// Email template for notifying requester (subscriber) of subscription declination.
/// </summary>
[Path("service_subscription_decline.html")]
ServiceSubscriptionDecline,

/// <summary>
/// Email template for notifying requester of subscription activations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ public async Task AddFavouriteAppForUserAsync(Guid appId)
public Task<Guid> AddOwnCompanyAppSubscriptionAsync(Guid appId, IEnumerable<OfferAgreementConsentData> offerAgreementConsentData) =>
_offerSubscriptionService.AddOfferSubscriptionAsync(appId, offerAgreementConsentData, OfferTypeId.APP, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles, _settings.ServiceManagerRoles);

/// <inheritdoc/>
public Task DeclineAppSubscriptionAsync(Guid subscriptionId) =>
_offerSubscriptionService.RemoveOfferSubscriptionAsync(subscriptionId, OfferTypeId.APP, _settings.BasePortalAddress);

/// <inheritdoc/>
public Task TriggerActivateOfferSubscription(Guid subscriptionId) =>
_offerSetupService.TriggerActivateSubscription(subscriptionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ public interface IAppsBusinessLogic
/// <param name="offerAgreementConsentData">The agreement consent data</param>
public Task<Guid> AddOwnCompanyAppSubscriptionAsync(Guid appId, IEnumerable<OfferAgreementConsentData> offerAgreementConsentData);

/// <summary>
/// Declines a pending app subscription of an app, provided by the current user's company.
/// </summary>
/// <param name="subscriptionId">ID of the pending app to be declined.</param>
public Task DeclineAppSubscriptionAsync(Guid subscriptionId);

/// <summary>
/// Activates a pending app subscription for an app provided by the current user's company.
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions src/marketplace/Apps.Service/Controllers/AppsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,30 @@ public async Task<IActionResult> ActivateOfferSubscription([FromRoute] Guid subs
return NoContent();
}

/// <summary>
/// Declines a pending app subscription.
/// </summary>
/// <param name="subscriptionId" example="D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645">ID of the subscription to decline.</param>
/// <remarks>Example: PUT: /api/apps/subscription/{subscriptiondId}/decline</remarks>
/// <response code="204">App subscription was successfully declined.</response>
/// <response code="400">If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.</response>
/// <response code="404">If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.</response>
/// <response code="500">Internal Server Error.</response>
[HttpPut]
[Route("subscription/{subscriptionId}/decline")]
[Authorize(Roles = "decline_subscription")]
[Authorize(Policy = PolicyTypes.ValidIdentity)]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeclineAppSubscriptionAsync([FromRoute] Guid subscriptionId)
{
await _appsBusinessLogic.DeclineAppSubscriptionAsync(subscriptionId).ConfigureAwait(ConfigureAwaitOptions.None);
return NoContent();
}

/// <summary>
/// Unsubscribes an app from the current user's company's subscriptions.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service;
public interface IOfferSubscriptionService
{
Task<Guid> AddOfferSubscriptionAsync(Guid offerId, IEnumerable<OfferAgreementConsentData> offerAgreementConsentData, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable<UserRoleConfig> notificationRecipients, IEnumerable<UserRoleConfig> serviceManagerRoles);
Task<Guid> RemoveOfferSubscriptionAsync(Guid subscriptionId, OfferTypeId offerTypeId, string basePortalAddress);
}
56 changes: 56 additions & 0 deletions src/marketplace/Offers.Library/Service/OfferSubscriptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,62 @@ await _mailingProcessCreation.RoleBaseSendMail(
return offerSubscription.Id;
}

public async Task<Guid> RemoveOfferSubscriptionAsync(Guid subscriptionId, OfferTypeId offerTypeId, string basePortalAddress)
{
var offerSubscriptionsRepository = _portalRepositories.GetInstance<IOfferSubscriptionsRepository>();
var offerSubscriptionDetails = await offerSubscriptionsRepository.GetOfferDetailsAndCheckProviderCompany(subscriptionId, _identityData.CompanyId, offerTypeId) ?? throw new NotFoundException($"Subscription {subscriptionId} does not exist.");
var offerId = offerSubscriptionDetails.OfferId;

if (offerSubscriptionDetails.OfferName is null)
{
throw new NotFoundException($"Offer {offerId} does not exist");
}
if (!offerSubscriptionDetails.IsProviderCompany)
{
throw new ForbiddenException("Only the providing company can decline the subscription request.");
}
if (offerSubscriptionDetails.Status == OfferSubscriptionStatusId.INACTIVE)
{
throw new ConflictException($"Subscription of {offerSubscriptionDetails.OfferName} is already {offerSubscriptionDetails.Status}");
}

var offerSubscription = offerSubscriptionsRepository.DeleteOfferSubscription(subscriptionId, offerId, offerSubscriptionDetails.CompanyId, offerSubscriptionDetails.Status, offerSubscriptionDetails.RequesterId);
SendNotificationsToRequester(offerId, offerTypeId, basePortalAddress, offerSubscriptionDetails);
await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);

return offerSubscription.Id;
}

private void SendNotificationsToRequester(Guid offerId, OfferTypeId offerTypeId, string basePortalAddress, OfferSubscriptionTransferData offerSubscriptionDetails)
{
var content = JsonSerializer.Serialize(new
{
AppName = offerSubscriptionDetails.OfferName,
OfferId = offerId
});

var notificationTypeId = offerTypeId == OfferTypeId.SERVICE ? NotificationTypeId.SERVICE_SUBSCRIPTION_DECLINE : NotificationTypeId.APP_SUBSCRIPTION_DECLINE;
_portalRepositories.GetInstance<INotificationRepository>().CreateNotification(
offerSubscriptionDetails.RequesterId,
notificationTypeId,
false,
notification =>
{
notification.CreatorUserId = _identityData.IdentityId;
notification.Content = content;
});
var mailParameters = ImmutableDictionary.CreateRange(
[
KeyValuePair.Create("offerName", offerSubscriptionDetails.OfferName!),
KeyValuePair.Create("url", basePortalAddress),
KeyValuePair.Create("requesterName", string.Format("{0} {1}", offerSubscriptionDetails.RequesterFirstname, offerSubscriptionDetails.RequesterLastname))
]);
_mailingProcessCreation.CreateMailProcess(
offerSubscriptionDetails.RequesterEmail!,
$"{offerTypeId.ToString().ToLower()}-subscription-decline",
mailParameters);
}

private void CreateProcessSteps(OfferSubscription offerSubscription)
{
var processStepRepository = _portalRepositories.GetInstance<IProcessStepRepository>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public interface IServiceBusinessLogic
/// <returns></returns>
Task<Guid> AddServiceSubscription(Guid serviceId, IEnumerable<OfferAgreementConsentData> offerAgreementConsentData);

/// <summary>
/// Declines a pending service subscription of a service, provided by the current user's company.
/// </summary>
/// <param name="subscriptionId">ID of the pending service to be declined.</param>
public Task DeclineServiceSubscriptionAsync(Guid subscriptionId);

/// <summary>
/// Gets the service detail data for the given service
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public ServiceBusinessLogic(
public Task<Guid> AddServiceSubscription(Guid serviceId, IEnumerable<OfferAgreementConsentData> offerAgreementConsentData) =>
_offerSubscriptionService.AddOfferSubscriptionAsync(serviceId, offerAgreementConsentData, OfferTypeId.SERVICE, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles, _settings.ServiceManagerRoles);

/// <inheritdoc />
public Task DeclineServiceSubscriptionAsync(Guid subscriptionId) =>
_offerSubscriptionService.RemoveOfferSubscriptionAsync(subscriptionId, OfferTypeId.SERVICE, _settings.BasePortalAddress);

/// <inheritdoc />
public async Task<ServiceDetailResponse> GetServiceDetailsAsync(Guid serviceId, string lang)
{
Expand Down
24 changes: 24 additions & 0 deletions src/marketplace/Services.Service/Controllers/ServicesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,28 @@ public async Task<IActionResult> ActivateCompanyAppSubscriptionAsync([FromRoute]
await _serviceBusinessLogic.TriggerActivateOfferSubscription(subscriptionId).ConfigureAwait(ConfigureAwaitOptions.None);
return NoContent();
}

/// <summary>
/// Declines a pending service subscription.
/// </summary>
/// <param name="subscriptionId" example="D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645">ID of the subscription to decline.</param>
/// <remarks>Example: PUT: /api/services/supscription/{subscriptiondId}/decline</remarks>
/// <response code="204">Service subscription was successfully declined.</response>
/// <response code="400">If sub claim is empty/invalid or user does not exist, or any other parameters are invalid.</response>
/// <response code="500">Internal Server Error.</response>
[HttpPut]
[Route("/subscription/{subscriptionId}/decline")]
[Authorize(Roles = "decline_subscription")]
[Authorize(Policy = PolicyTypes.ValidIdentity)]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeclineServiceSubscriptionAsync([FromRoute] Guid subscriptionId)
{
await _serviceBusinessLogic.DeclineServiceSubscriptionAsync(subscriptionId).ConfigureAwait(ConfigureAwaitOptions.None);
return NoContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ public interface IOfferSubscriptionsRepository
/// <param name="creatorId">id of the creator</param>
OfferSubscription CreateOfferSubscription(Guid offerId, Guid companyId, OfferSubscriptionStatusId offerSubscriptionStatusId, Guid requesterId);

/// <summary>
/// Deletes the given company assigned offer's subscription from the database
/// </summary>
/// <param name="offerId">Id of the assigned offer</param>
/// <param name="companyId">Id of the company</param>
/// <param name="offerSubscriptionStatusId">id of the offer subscription</param>
/// <param name="requesterId">id of the user that requested the subscription of the offer</param>
/// <param name="creatorId">id of the creator</param>
OfferSubscription DeleteOfferSubscription(Guid subscriptionId, Guid offerId, Guid companyId, OfferSubscriptionStatusId offerSubscriptionStatusId, Guid requesterId);

/// <summary>
/// Gets the provided offer subscription statuses for the user and given company
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public class OfferSubscriptionsRepository(PortalDbContext dbContext) : IOfferSub
public OfferSubscription CreateOfferSubscription(Guid offerId, Guid companyId, OfferSubscriptionStatusId offerSubscriptionStatusId, Guid requesterId) =>
dbContext.OfferSubscriptions.Add(new OfferSubscription(Guid.NewGuid(), offerId, companyId, offerSubscriptionStatusId, requesterId, DateTimeOffset.UtcNow)).Entity;

/// <inheritdoc />
public OfferSubscription DeleteOfferSubscription(Guid subscriptionId, Guid offerId, Guid companyId, OfferSubscriptionStatusId offerSubscriptionStatusId, Guid requesterId) =>
dbContext.OfferSubscriptions.Remove(new OfferSubscription(subscriptionId, offerId, companyId, offerSubscriptionStatusId, requesterId, DateTimeOffset.UtcNow)).Entity;

/// <inheritdoc />
public Func<int, int, Task<Pagination.Source<OfferCompanySubscriptionStatusData>?>> GetOwnCompanyProvidedOfferSubscriptionStatusesUntrackedAsync(Guid userCompanyId, OfferTypeId offerTypeId, SubscriptionStatusSorting? sorting, IEnumerable<OfferSubscriptionStatusId> statusIds, Guid? offerId, string? companyName) =>
(skip, take) => Pagination.CreateSourceQueryAsync(
Expand Down
Loading

0 comments on commit caefc7d

Please sign in to comment.