external plan user invitation fixes

This commit is contained in:
CITE\amentis 2024-10-08 14:05:48 +03:00
parent 078f5d0df1
commit d0cd1719a9
12 changed files with 113 additions and 35 deletions

View File

@ -18,6 +18,9 @@ public class PlanInvitationEntity {
@XmlAttribute(name = "plan-role") @XmlAttribute(name = "plan-role")
private PlanUserRole role; private PlanUserRole role;
@XmlAttribute(name = "section")
private UUID sectionId;
public String getEmail() { public String getEmail() {
return email; return email;
} }
@ -41,4 +44,12 @@ public class PlanInvitationEntity {
public void setRole(PlanUserRole role) { public void setRole(PlanUserRole role) {
this.role = role; this.role = role;
} }
public UUID getSectionId() {
return sectionId;
}
public void setSectionId(UUID sectionId) {
this.sectionId = sectionId;
}
} }

View File

@ -0,0 +1,26 @@
package org.opencdmp.model;
import java.util.UUID;
public class PlanInvitationResult {
private UUID planId;
private Boolean isAlreadyAccepted;
public UUID getPlanId() {
return planId;
}
public void setPlanId(UUID planId) {
this.planId = planId;
}
public Boolean getIsAlreadyAccepted() {
return isAlreadyAccepted;
}
public void setIsAlreadyAccepted(Boolean alreadyAccepted) {
isAlreadyAccepted = alreadyAccepted;
}
}

View File

@ -12,6 +12,9 @@ public class PlanInvitation {
private UUID planId; private UUID planId;
public static final String _planId = "planId"; public static final String _planId = "planId";
private UUID sectionId;
public static final String _sectionId = "sectionId";
private PlanUserRole role; private PlanUserRole role;
public static final String _role = "role"; public static final String _role = "role";
@ -32,6 +35,14 @@ public class PlanInvitation {
this.planId = planId; this.planId = planId;
} }
public UUID getSectionId() {
return sectionId;
}
public void setSectionId(UUID sectionId) {
this.sectionId = sectionId;
}
public PlanUserRole getRole() { public PlanUserRole getRole() {
return role; return role;
} }

View File

@ -47,6 +47,7 @@ public class PlanInvitationBuilder extends BaseBuilder<PlanInvitation, PlanInvit
PlanInvitation m = new PlanInvitation(); PlanInvitation m = new PlanInvitation();
if (fields.hasField(this.asIndexer(PlanInvitation._email))) m.setEmail(d.getEmail()); if (fields.hasField(this.asIndexer(PlanInvitation._email))) m.setEmail(d.getEmail());
if (fields.hasField(this.asIndexer(PlanInvitation._planId))) m.setPlanId(d.getPlanId()); if (fields.hasField(this.asIndexer(PlanInvitation._planId))) m.setPlanId(d.getPlanId());
if (fields.hasField(this.asIndexer(PlanInvitation._sectionId))) m.setSectionId(d.getSectionId());
if (fields.hasField(this.asIndexer(PlanInvitation._role))) m.setRole(d.getRole()); if (fields.hasField(this.asIndexer(PlanInvitation._role))) m.setRole(d.getRole());
models.add(m); models.add(m);

View File

@ -25,13 +25,18 @@ public class PlanInvitationPersist {
public static final String _planId = "planId"; public static final String _planId = "planId";
private UUID sectionId;
public static final String _sectionId = "sectionId";
private PlanUserRole role; private PlanUserRole role;
public static final String _role = "role"; public static final String _role = "role";
public PlanInvitationPersist(String email, UUID planId, PlanUserRole role) { public PlanInvitationPersist(String email, UUID planId, UUID sectionId, PlanUserRole role) {
this.email = email; this.email = email;
this.planId = planId; this.planId = planId;
this.sectionId = sectionId;
this.role = role; this.role = role;
} }
@ -51,6 +56,14 @@ public class PlanInvitationPersist {
this.planId = planId; this.planId = planId;
} }
public UUID getSectionId() {
return sectionId;
}
public void setSectionId(UUID sectionId) {
this.sectionId = sectionId;
}
public PlanUserRole getRole() { public PlanUserRole getRole() {
return role; return role;
} }

View File

@ -126,6 +126,7 @@ public class ActionConfirmationServiceImpl implements ActionConfirmationService
data.setEmail(persist.getEmail()); data.setEmail(persist.getEmail());
data.setRole(persist.getRole()); data.setRole(persist.getRole());
data.setPlanId(persist.getPlanId()); data.setPlanId(persist.getPlanId());
data.setSectionId(persist.getSectionId());
return data; return data;
} }

View File

@ -8,6 +8,7 @@ import gr.cite.tools.fieldset.FieldSet;
import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.JAXBException;
import org.opencdmp.commons.types.plan.importexport.PlanImportExport; import org.opencdmp.commons.types.plan.importexport.PlanImportExport;
import org.opencdmp.filetransformerbase.models.misc.PreprocessingPlanModel; import org.opencdmp.filetransformerbase.models.misc.PreprocessingPlanModel;
import org.opencdmp.model.PlanInvitationResult;
import org.opencdmp.model.PlanUser; import org.opencdmp.model.PlanUser;
import org.opencdmp.model.PlanValidationResult; import org.opencdmp.model.PlanValidationResult;
import org.opencdmp.model.plan.Plan; import org.opencdmp.model.plan.Plan;
@ -52,7 +53,7 @@ public interface PlanService {
void inviteUserOrAssignUsers(UUID id, List<PlanUserPersist> users) throws InvalidApplicationException, JAXBException, IOException; void inviteUserOrAssignUsers(UUID id, List<PlanUserPersist> users) throws InvalidApplicationException, JAXBException, IOException;
UUID planInvitationAccept(String token) throws InvalidApplicationException, IOException; PlanInvitationResult planInvitationAccept(String token) throws InvalidApplicationException, IOException;
PlanImportExport exportXmlEntity(UUID id, boolean ignoreAuthorize, boolean isPublic) throws MyForbiddenException, MyNotFoundException, JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException; PlanImportExport exportXmlEntity(UUID id, boolean ignoreAuthorize, boolean isPublic) throws MyForbiddenException, MyNotFoundException, JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException;

View File

@ -69,10 +69,7 @@ import org.opencdmp.integrationevent.outbox.annotationentityremoval.AnnotationEn
import org.opencdmp.integrationevent.outbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler; import org.opencdmp.integrationevent.outbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler;
import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEvent; import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEvent;
import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEventHandler; import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEventHandler;
import org.opencdmp.model.PlanDescriptionTemplate; import org.opencdmp.model.*;
import org.opencdmp.model.PlanUser;
import org.opencdmp.model.PlanValidationResult;
import org.opencdmp.model.PublicPlan;
import org.opencdmp.model.builder.PlanUserBuilder; import org.opencdmp.model.builder.PlanUserBuilder;
import org.opencdmp.model.builder.description.DescriptionBuilder; import org.opencdmp.model.builder.description.DescriptionBuilder;
import org.opencdmp.model.builder.plan.PlanBuilder; import org.opencdmp.model.builder.plan.PlanBuilder;
@ -1903,7 +1900,7 @@ public class PlanServiceImpl implements PlanService {
this.sendPlanInvitationExistingUser(user.getUser(), plan, user.getRole()); this.sendPlanInvitationExistingUser(user.getUser(), plan, user.getRole());
} }
}else if (user.getEmail() != null) { }else if (user.getEmail() != null) {
this.sendPlanInvitationExternalUser(user.getEmail(),plan, user.getRole()); this.sendPlanInvitationExternalUser(user.getEmail(),plan, user.getRole(), user.getSectionId());
} }
} }
@ -1941,8 +1938,8 @@ public class PlanServiceImpl implements PlanService {
this.eventHandler.handle(event); this.eventHandler.handle(event);
} }
private void sendPlanInvitationExternalUser(String email, PlanEntity plan, PlanUserRole role) throws JAXBException, InvalidApplicationException { private void sendPlanInvitationExternalUser(String email, PlanEntity plan, PlanUserRole role, UUID sectionId) throws JAXBException, InvalidApplicationException {
String token = this.createActionConfirmation(email, plan, role); String token = this.createActionConfirmation(email, plan, role, sectionId);
NotifyIntegrationEvent event = new NotifyIntegrationEvent(); NotifyIntegrationEvent event = new NotifyIntegrationEvent();
@ -1966,12 +1963,12 @@ public class PlanServiceImpl implements PlanService {
this.eventHandler.handle(event); this.eventHandler.handle(event);
} }
private String createActionConfirmation(String email, PlanEntity plan, PlanUserRole role) throws JAXBException, InvalidApplicationException { private String createActionConfirmation(String email, PlanEntity plan, PlanUserRole role, UUID sectionId) throws JAXBException, InvalidApplicationException {
ActionConfirmationPersist persist = new ActionConfirmationPersist(); ActionConfirmationPersist persist = new ActionConfirmationPersist();
persist.setType(ActionConfirmationType.PlanInvitation); persist.setType(ActionConfirmationType.PlanInvitation);
persist.setStatus(ActionConfirmationStatus.Requested); persist.setStatus(ActionConfirmationStatus.Requested);
persist.setToken(UUID.randomUUID().toString()); persist.setToken(UUID.randomUUID().toString());
persist.setPlanInvitation(new PlanInvitationPersist(email, plan.getId(), role)); persist.setPlanInvitation(new PlanInvitationPersist(email, plan.getId(), sectionId, role));
persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds())); persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds()));
this.validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist); this.validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist);
this.actionConfirmationService.persist(persist, null); this.actionConfirmationService.persist(persist, null);
@ -1979,23 +1976,29 @@ public class PlanServiceImpl implements PlanService {
return persist.getToken(); return persist.getToken();
} }
public UUID planInvitationAccept(String token) throws InvalidApplicationException { public PlanInvitationResult planInvitationAccept(String token) throws InvalidApplicationException {
PlanInvitationResult planInvitationResult = new PlanInvitationResult();
ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.PlanInvitation).isActive(IsActive.Active).first(); ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.PlanInvitation).isActive(IsActive.Active).first();
if (action == null){ if (action == null){
throw new MyValidationException(this.errors.getTokenNotExist().getCode(), this.errors.getTokenNotExist().getMessage()); throw new MyValidationException(this.errors.getTokenNotExist().getCode(), this.errors.getTokenNotExist().getMessage());
} }
if (action.getStatus().equals(ActionConfirmationStatus.Accepted)){
throw new MyValidationException(this.errors.getPlanInvitationAlreadyConfirmed().getCode(), this.errors.getPlanInvitationAlreadyConfirmed().getMessage());
}
if (action.getExpiresAt().compareTo(Instant.now()) < 0){
throw new MyValidationException(this.errors.getRequestHasExpired().getCode(), this.errors.getRequestHasExpired().getMessage());
}
PlanInvitationEntity planInvitation = this.xmlHandlingService.fromXmlSafe(PlanInvitationEntity.class, action.getData()); PlanInvitationEntity planInvitation = this.xmlHandlingService.fromXmlSafe(PlanInvitationEntity.class, action.getData());
if (planInvitation == null) { if (planInvitation == null) {
throw new MyApplicationException("plan invitation don't exist"); throw new MyApplicationException("plan invitation don't exist");
} }
if (action.getStatus().equals(ActionConfirmationStatus.Accepted)){
UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).disableTracking().userIds(this.userScope.getUserId()).values(planInvitation.getEmail()).types(ContactInfoType.Email).first();
if (contactInfoEntity == null){
throw new MyValidationException(this.errors.getAnotherUserToken().getCode(), this.errors.getAnotherUserToken().getMessage());
}
planInvitationResult.setPlanId(planInvitation.getPlanId());
planInvitationResult.setIsAlreadyAccepted(true);
return planInvitationResult;
}
if (action.getExpiresAt().compareTo(Instant.now()) < 0){
throw new MyValidationException(this.errors.getRequestHasExpired().getCode(), this.errors.getRequestHasExpired().getMessage());
}
UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).disableTracking().userIds(this.userScope.getUserId()).values(planInvitation.getEmail()).types(ContactInfoType.Email).first(); UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).disableTracking().userIds(this.userScope.getUserId()).values(planInvitation.getEmail()).types(ContactInfoType.Email).first();
if (contactInfoEntity == null){ if (contactInfoEntity == null){
@ -2009,13 +2012,16 @@ public class PlanServiceImpl implements PlanService {
data.setRole(planInvitation.getRole()); data.setRole(planInvitation.getRole());
data.setUserId(this.userScope.getUserIdSafe()); data.setUserId(this.userScope.getUserIdSafe());
data.setPlanId(planInvitation.getPlanId()); data.setPlanId(planInvitation.getPlanId());
data.setSectionId(planInvitation.getSectionId());
this.entityManager.persist(data); this.entityManager.persist(data);
action.setStatus(ActionConfirmationStatus.Accepted); action.setStatus(ActionConfirmationStatus.Accepted);
this.entityManager.merge(action); this.entityManager.merge(action);
this.annotationEntityTouchedIntegrationEventHandler.handlePlan(planInvitation.getPlanId()); this.annotationEntityTouchedIntegrationEventHandler.handlePlan(planInvitation.getPlanId());
return planInvitation.getPlanId(); planInvitationResult.setPlanId(planInvitation.getPlanId());
planInvitationResult.setIsAlreadyAccepted(false);
return planInvitationResult;
} }
//region Export //region Export

View File

@ -31,10 +31,7 @@ import org.opencdmp.controllers.swagger.annotation.Swagger400;
import org.opencdmp.controllers.swagger.annotation.Swagger404; import org.opencdmp.controllers.swagger.annotation.Swagger404;
import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses;
import org.opencdmp.filetransformerbase.models.misc.PreprocessingPlanModel; import org.opencdmp.filetransformerbase.models.misc.PreprocessingPlanModel;
import org.opencdmp.model.DescriptionsToBeFinalized; import org.opencdmp.model.*;
import org.opencdmp.model.PlanUser;
import org.opencdmp.model.PlanValidationResult;
import org.opencdmp.model.PublicPlan;
import org.opencdmp.model.builder.PublicPlanBuilder; import org.opencdmp.model.builder.PublicPlanBuilder;
import org.opencdmp.model.builder.plan.PlanBuilder; import org.opencdmp.model.builder.plan.PlanBuilder;
import org.opencdmp.model.censorship.PublicPlanCensor; import org.opencdmp.model.censorship.PublicPlanCensor;
@ -487,16 +484,16 @@ public class PlanController {
@OperationWithTenantHeader(summary = "Accept an invitation token for a plan by token") @OperationWithTenantHeader(summary = "Accept an invitation token for a plan by token")
@Transactional @Transactional
@Hidden @Hidden
public UUID acceptInvitation(@PathVariable("token") String token) throws InvalidApplicationException, JAXBException, IOException { public PlanInvitationResult acceptInvitation(@PathVariable("token") String token) throws InvalidApplicationException, JAXBException, IOException {
logger.debug(new MapLogEntry("inviting users to plan").And("token", token)); logger.debug(new MapLogEntry("inviting users to plan").And("token", token));
UUID planId = this.planService.planInvitationAccept(token); PlanInvitationResult planInvitationResult = this.planService.planInvitationAccept(token);
this.auditService.track(AuditableAction.Plan_Invite_Accept, Map.ofEntries( this.auditService.track(AuditableAction.Plan_Invite_Accept, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("token", token) new AbstractMap.SimpleEntry<String, Object>("token", token)
)); ));
return planId; return planInvitationResult;
} }
@RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml")

View File

@ -179,5 +179,10 @@ export interface PlanUserInvitePersist {
} }
// //
// Public // Invitation
// //
export interface PlanInvitationResult {
planId: Guid;
isAlreadyAccepted: boolean;
}

View File

@ -15,7 +15,7 @@ import { catchError, map } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof'; import { nameof } from 'ts-simple-nameof';
import { BaseHttpParams } from '../../../../common/http/base-http-params'; import { BaseHttpParams } from '../../../../common/http/base-http-params';
import { InterceptorType } from '../../../../common/http/interceptors/interceptor-type'; import { InterceptorType } from '../../../../common/http/interceptors/interceptor-type';
import { ClonePlanPersist, Plan, PlanPersist, PlanUser, PlanUserInvitePersist, PlanUserPersist, PlanUserRemovePersist, NewVersionPlanPersist, PublicPlan } from '../../model/plan/plan'; import { ClonePlanPersist, Plan, PlanPersist, PlanUser, PlanUserInvitePersist, PlanUserPersist, PlanUserRemovePersist, NewVersionPlanPersist, PublicPlan, PlanInvitationResult } from '../../model/plan/plan';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
import { ConfigurationService } from '../configuration/configuration.service'; import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service'; import { BaseHttpV2Service } from '../http/base-http-v2.service';
@ -172,10 +172,10 @@ export class PlanService {
catchError((error: any) => throwError(error))); catchError((error: any) => throwError(error)));
} }
acceptInvitation(token: string): Observable<Guid> { acceptInvitation(token: string): Observable<PlanInvitationResult> {
const url = `${this.apiBase}/token/${token}/invite-accept`; const url = `${this.apiBase}/token/${token}/invite-accept`;
return this.http.get<Guid>(url).pipe(catchError((error: any) => throwError(error))); return this.http.get<PlanInvitationResult>(url).pipe(catchError((error: any) => throwError(error)));
} }
downloadXML(id: Guid): Observable<HttpResponse<Blob>> { downloadXML(id: Guid): Observable<HttpResponse<Blob>> {

View File

@ -2,11 +2,12 @@ import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from '@app/core/services/auth/auth.service'; import { AuthService } from '@app/core/services/auth/auth.service';
import { SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { PlanService } from '@app/core/services/plan/plan.service'; import { PlanService } from '@app/core/services/plan/plan.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
@ -21,7 +22,9 @@ export class InvitationAcceptedComponent extends BaseComponent implements OnInit
private router: Router, private router: Router,
private authentication: AuthService, private authentication: AuthService,
protected routerUtils: RouterUtilsService, protected routerUtils: RouterUtilsService,
private httpErrorHandlingService: HttpErrorHandlingService protected language: TranslateService,
private httpErrorHandlingService: HttpErrorHandlingService,
private uiNotificationService: UiNotificationService
) { super(); } ) { super(); }
ngOnInit(): void { ngOnInit(): void {
@ -35,7 +38,10 @@ export class InvitationAcceptedComponent extends BaseComponent implements OnInit
this.planService.acceptInvitation(token) this.planService.acceptInvitation(token)
.pipe(takeUntil(this._destroyed)) .pipe(takeUntil(this._destroyed))
.subscribe(result => { .subscribe(result => {
this.router.navigate([this.routerUtils.generateUrl('plans/edit/' + result)]); if (result?.isAlreadyAccepted == false) {
this.uiNotificationService.snackBarNotification(this.language.instant('PLAN-USER-INVITATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success);
}
this.router.navigate([this.routerUtils.generateUrl('plans/overview/' + result?.planId)]);
}, },
error => this.onCallbackError(error)); error => this.onCallbackError(error));
}else{ }else{