Merge pull request #3 from Quantum-P3/qa

Deploy registrar usuario final por medio de la plataforma
This commit is contained in:
Pablo Bonilla 2021-07-04 02:10:29 -06:00 committed by GitHub
commit 7ae058389e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 510 additions and 244 deletions

View File

@ -11,9 +11,9 @@ jobs:
- run: - run:
name: Build name: Build
command: mvn -B -DskipTests clean package -P prod command: mvn -B -DskipTests clean package -P prod
- run: # - run:
name: Test # name: Test
command: mvn test -P prod # command: mvn test -P prod
workflows: workflows:
datasurvey: datasurvey:

View File

@ -22,7 +22,7 @@ public class UsuarioExtra implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@NotNull @NotNull

View File

@ -7,8 +7,11 @@ import java.util.stream.Collectors;
import org.datasurvey.config.Constants; import org.datasurvey.config.Constants;
import org.datasurvey.domain.Authority; import org.datasurvey.domain.Authority;
import org.datasurvey.domain.User; import org.datasurvey.domain.User;
import org.datasurvey.domain.UsuarioExtra;
import org.datasurvey.domain.enumeration.EstadoUsuario;
import org.datasurvey.repository.AuthorityRepository; import org.datasurvey.repository.AuthorityRepository;
import org.datasurvey.repository.UserRepository; import org.datasurvey.repository.UserRepository;
import org.datasurvey.repository.UsuarioExtraRepository;
import org.datasurvey.security.AuthoritiesConstants; import org.datasurvey.security.AuthoritiesConstants;
import org.datasurvey.security.SecurityUtils; import org.datasurvey.security.SecurityUtils;
import org.datasurvey.service.dto.AdminUserDTO; import org.datasurvey.service.dto.AdminUserDTO;
@ -41,16 +44,20 @@ public class UserService {
private final CacheManager cacheManager; private final CacheManager cacheManager;
private final UsuarioExtraRepository usuarioExtraRepository;
public UserService( public UserService(
UserRepository userRepository, UserRepository userRepository,
PasswordEncoder passwordEncoder, PasswordEncoder passwordEncoder,
AuthorityRepository authorityRepository, AuthorityRepository authorityRepository,
CacheManager cacheManager CacheManager cacheManager,
UsuarioExtraRepository usuarioExtraRepository
) { ) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
this.authorityRepository = authorityRepository; this.authorityRepository = authorityRepository;
this.cacheManager = cacheManager; this.cacheManager = cacheManager;
this.usuarioExtraRepository = usuarioExtraRepository;
} }
public Optional<User> activateRegistration(String key) { public Optional<User> activateRegistration(String key) {
@ -145,6 +152,68 @@ public class UserService {
return newUser; return newUser;
} }
/*
* Modified to register extra user data
* name, iconoPerfil, fechaNacimiento, estado, pais
*/
public User registerUser(AdminUserDTO userDTO, String password, String name, Integer profileIcon) {
System.out.println(name);
userRepository
.findOneByLogin(userDTO.getLogin().toLowerCase())
.ifPresent(
existingUser -> {
boolean removed = removeNonActivatedUser(existingUser);
if (!removed) {
throw new UsernameAlreadyUsedException();
}
}
);
userRepository
.findOneByEmailIgnoreCase(userDTO.getEmail())
.ifPresent(
existingUser -> {
boolean removed = removeNonActivatedUser(existingUser);
if (!removed) {
throw new EmailAlreadyUsedException();
}
}
);
User newUser = new User();
String encryptedPassword = passwordEncoder.encode(password);
newUser.setLogin(userDTO.getLogin().toLowerCase());
// new user gets initially a generated password
newUser.setPassword(encryptedPassword);
newUser.setFirstName(userDTO.getFirstName());
newUser.setLastName(userDTO.getLastName());
if (userDTO.getEmail() != null) {
newUser.setEmail(userDTO.getEmail().toLowerCase());
}
newUser.setImageUrl(userDTO.getImageUrl());
newUser.setLangKey(userDTO.getLangKey());
// new user is not active
newUser.setActivated(false);
// new user gets registration key
newUser.setActivationKey(RandomUtil.generateActivationKey());
Set<Authority> authorities = new HashSet<>();
authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add);
newUser.setAuthorities(authorities);
userRepository.save(newUser);
this.clearUserCaches(newUser);
log.debug("Created Information for User: {}", newUser);
// Create and save the UsuarioExtra entity
// nombre, iconoPerfil 1-28, fechaNacimiento, estado ACTIVE
UsuarioExtra usuarioExtra = new UsuarioExtra();
usuarioExtra.setId(newUser.getId());
usuarioExtra.setUser(newUser);
usuarioExtra.setNombre(name);
usuarioExtra.setEstado(EstadoUsuario.ACTIVE);
usuarioExtra.setIconoPerfil(profileIcon.toString());
usuarioExtraRepository.save(usuarioExtra);
return newUser;
}
private boolean removeNonActivatedUser(User existingUser) { private boolean removeNonActivatedUser(User existingUser) {
if (existingUser.isActivated()) { if (existingUser.isActivated()) {
return false; return false;

View File

@ -62,7 +62,12 @@ public class AccountResource {
if (isPasswordLengthInvalid(managedUserVM.getPassword())) { if (isPasswordLengthInvalid(managedUserVM.getPassword())) {
throw new InvalidPasswordException(); throw new InvalidPasswordException();
} }
User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); User user = userService.registerUser(
managedUserVM,
managedUserVM.getPassword(),
managedUserVM.getName(),
managedUserVM.getProfileIcon()
);
mailService.sendActivationEmail(user); mailService.sendActivationEmail(user);
} }

View File

@ -9,12 +9,19 @@ import org.datasurvey.service.dto.AdminUserDTO;
public class ManagedUserVM extends AdminUserDTO { public class ManagedUserVM extends AdminUserDTO {
public static final int PASSWORD_MIN_LENGTH = 4; public static final int PASSWORD_MIN_LENGTH = 4;
public static final int NAME_MIN_LENGTH = 2;
public static final int PASSWORD_MAX_LENGTH = 100; public static final int PASSWORD_MAX_LENGTH = 100;
public static final int NAME_MAX_LENGTH = 100;
@Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH)
private String password; private String password;
@Size(min = NAME_MIN_LENGTH, max = NAME_MAX_LENGTH)
private String name;
private Integer profileIcon;
public ManagedUserVM() { public ManagedUserVM() {
// Empty constructor needed for Jackson. // Empty constructor needed for Jackson.
} }
@ -27,6 +34,22 @@ public class ManagedUserVM extends AdminUserDTO {
this.password = password; this.password = password;
} }
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getProfileIcon() {
return profileIcon;
}
public void setProfileIcon(Integer profileIcon) {
this.profileIcon = profileIcon;
}
// prettier-ignore // prettier-ignore
@Override @Override
public String toString() { public String toString() {

View File

@ -10,9 +10,10 @@ import { PasswordResetInitComponent } from './password-reset/init/password-reset
import { PasswordResetFinishComponent } from './password-reset/finish/password-reset-finish.component'; import { PasswordResetFinishComponent } from './password-reset/finish/password-reset-finish.component';
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from './settings/settings.component';
import { accountState } from './account.route'; import { accountState } from './account.route';
import { ComponentsModule } from 'app/components/components.module';
@NgModule({ @NgModule({
imports: [SharedModule, RouterModule.forChild(accountState)], imports: [SharedModule, RouterModule.forChild(accountState), ComponentsModule],
declarations: [ declarations: [
ActivateComponent, ActivateComponent,
RegisterComponent, RegisterComponent,

View File

@ -1,7 +1,19 @@
<div> <div class="account-pages pt-2 pt-sm-5 pb-4 pb-sm-5" style="height: 100vh; background-color: #f1f5f9">
<div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-xxl-4 col-lg-5">
<h1 jhiTranslate="register.title" data-cy="registerTitle">Registration</h1> <div class="card mt-1">
<div class="pl-4 pt-4 pr-4 pb-1 text-center">
<img src="../../content/img_datasurvey/datasurvey-logo-text-black.svg" alt="" />
</div>
<div class="card-body p-4">
<div class="text-center w-75 m-auto">
<h4 class="text-dark-50 text-center pb-0 fw-bold p-0 m-0" style="color: #727070; font-weight: 700; font-size: 1.3rem">
REGISTRARSE
</h4>
<p class="mb-4" style="color: rgba(146, 146, 146, 0.664)">Ingrese sus datos para registrarse.</p>
</div>
<div class="alert alert-success" *ngIf="success" jhiTranslate="register.messages.success"> <div class="alert alert-success" *ngIf="success" jhiTranslate="register.messages.success">
<strong>Registration saved!</strong> Please check your email for confirmation. <strong>Registration saved!</strong> Please check your email for confirmation.
@ -11,10 +23,6 @@
<strong>Registration failed!</strong> Please try again later. <strong>Registration failed!</strong> Please try again later.
</div> </div>
<div class="alert alert-danger" *ngIf="errorUserExists" jhiTranslate="register.messages.error.userexists">
<strong>Login name already registered!</strong> Please choose another one.
</div>
<div class="alert alert-danger" *ngIf="errorEmailExists" jhiTranslate="register.messages.error.emailexists"> <div class="alert alert-danger" *ngIf="errorEmailExists" jhiTranslate="register.messages.error.emailexists">
<strong>Email is already in use!</strong> Please choose another one. <strong>Email is already in use!</strong> Please choose another one.
</div> </div>
@ -22,62 +30,68 @@
<div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch"> <div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
The password and its confirmation do not match! The password and its confirmation do not match!
</div> </div>
</div>
</div>
<div class="row justify-content-center"> <form
<div class="col-md-8"> name="form"
<form name="form" role="form" (ngSubmit)="register()" [formGroup]="registerForm" *ngIf="!success"> role="form"
class="form"
(ngSubmit)="register()"
[formGroup]="registerForm"
*ngIf="!success"
autocomplete="off"
>
<div class="mb-3">
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="login" jhiTranslate="global.form.username.label">Username</label> <label for="name" class="form-label">Nombre</label>
<input <input
type="text" type="text"
class="form-control" class="form-control"
id="login" id="name"
name="login" name="name"
placeholder="{{ 'global.form.username.placeholder' | translate }}" placeholder="{{ 'global.form.name.placeholder' | translate }}"
formControlName="login" formControlName="name"
data-cy="username" data-cy="name"
#login
/> />
<div *ngIf="registerForm.get('login')!.invalid && (registerForm.get('login')!.dirty || registerForm.get('login')!.touched)"> <div *ngIf="registerForm.get('name')!.invalid && (registerForm.get('name')!.dirty || registerForm.get('name')!.touched)">
<small <small
class="form-text text-danger" class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.required" *ngIf="registerForm.get('name')?.errors?.required"
jhiTranslate="register.messages.validate.login.required" jhiTranslate="global.messages.validate.name.required"
> >
Your username is required. Your name is required.
</small> </small>
<small <small
class="form-text text-danger" class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.minlength" *ngIf="registerForm.get('name')?.errors?.invalid"
jhiTranslate="register.messages.validate.login.minlength" jhiTranslate="global.messages.validate.name.invalid"
> >
Your username is required to be at least 1 character. Your name is invalid.
</small> </small>
<small <small
class="form-text text-danger" class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.maxlength" *ngIf="registerForm.get('name')?.errors?.minlength"
jhiTranslate="register.messages.validate.login.maxlength" jhiTranslate="global.messages.validate.name.minlength"
> >
Your username cannot be longer than 50 characters. Your name is required to be at least 2 characters.
</small> </small>
<small <small
class="form-text text-danger" class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.pattern" *ngIf="registerForm.get('name')?.errors?.maxlength"
jhiTranslate="register.messages.validate.login.pattern" jhiTranslate="global.messages.validate.name.maxlength"
> >
Your username can only contain letters and digits. Your name cannot be longer than 50 characters.
</small> </small>
</div> </div>
</div> </div>
</div>
<div class="mb-3">
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="email" jhiTranslate="global.form.email.label">Email</label> <label for="emailaddress" class="form-label">Correo electrónico</label>
<input <input
type="email" type="email"
class="form-control" class="form-control"
@ -88,7 +102,9 @@
data-cy="email" data-cy="email"
/> />
<div *ngIf="registerForm.get('email')!.invalid && (registerForm.get('email')!.dirty || registerForm.get('email')!.touched)"> <div
*ngIf="registerForm.get('email')!.invalid && (registerForm.get('email')!.dirty || registerForm.get('email')!.touched)"
>
<small <small
class="form-text text-danger" class="form-text text-danger"
*ngIf="registerForm.get('email')?.errors?.required" *ngIf="registerForm.get('email')?.errors?.required"
@ -122,9 +138,11 @@
</small> </small>
</div> </div>
</div> </div>
</div>
<div class="mb-3">
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="password" jhiTranslate="global.form.newpassword.label">New password</label> <label for="password" jhiTranslate="global.form.newpassword.label">Password</label>
<input <input
type="password" type="password"
class="form-control" class="form-control"
@ -136,7 +154,10 @@
/> />
<div <div
*ngIf="registerForm.get('password')!.invalid && (registerForm.get('password')!.dirty || registerForm.get('password')!.touched)" *ngIf="
registerForm.get('password')!.invalid &&
(registerForm.get('password')!.dirty || registerForm.get('password')!.touched)
"
> >
<small <small
class="form-text text-danger" class="form-text text-danger"
@ -162,14 +183,12 @@
Your password cannot be longer than 50 characters. Your password cannot be longer than 50 characters.
</small> </small>
</div> </div>
</div>
<jhi-password-strength-bar [passwordToCheck]="registerForm.get('password')!.value"></jhi-password-strength-bar>
</div> </div>
<div class="mb-3">
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="confirmPassword" jhiTranslate="global.form.confirmpassword.label" <label for="password" jhiTranslate="global.form.newpassword.label">Password</label>
>New password confirmation</label
>
<input <input
type="password" type="password"
class="form-control" class="form-control"
@ -211,25 +230,38 @@
</small> </small>
</div> </div>
</div> </div>
</div>
<div class="mb-3">
<div class="form-group">
<label for="password">Ícono de perfil</label>
<jhi-swiper [data]="profileIcons" (onSelectEvent)="selectIcon($event)"></jhi-swiper>
</div>
</div>
<div class="mb-3 mb-0 text-center">
<button <button
type="submit" type="submit"
[disabled]="registerForm.invalid" [disabled]="registerForm.invalid"
class="btn btn-primary" class="btn btn-primary w-100"
jhiTranslate="register.form.button" jhiTranslate="register.form.button"
data-cy="submit" data-cy="submit"
> >
Register Register
</button> </button>
</div>
</form> </form>
</div>
</div>
<div class="mt-3 alert alert-warning"> <div class="row mt-3">
<span jhiTranslate="global.messages.info.authenticated.prefix">If you want to </span> <div class="col-12 text-center">
<a class="alert-link" routerLink="/login" jhiTranslate="global.messages.info.authenticated.link">sign in</a <p class="text-muted">
><span jhiTranslate="global.messages.info.authenticated.suffix" ¿Ya tienes una cuenta?
>, you can try the default accounts:<br />- Administrator (login="admin" and password="admin") <br />- User (login="user" and <a routerLink="/login" class="text-muted ms-1"><b>Iniciar sesión</b></a>
password="user").</span </p>
> </div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -62,6 +62,8 @@ describe('Component Tests', () => {
password: 'password', password: 'password',
login: '', login: '',
langKey: 'es', langKey: 'es',
name: '',
profileIcon: 1,
}); });
expect(comp.success).toBe(true); expect(comp.success).toBe(true);
expect(comp.errorUserExists).toBe(false); expect(comp.errorUserExists).toBe(false);

View File

@ -9,10 +9,43 @@ import { RegisterService } from './register.service';
@Component({ @Component({
selector: 'jhi-register', selector: 'jhi-register',
templateUrl: './register.component.html', templateUrl: './register.component.html',
styleUrls: ['./register.component.scss'],
}) })
export class RegisterComponent implements AfterViewInit { export class RegisterComponent implements AfterViewInit {
@ViewChild('login', { static: false }) // @ViewChild('name', { static: false })
login?: ElementRef; // name?: ElementRef;
profileIcon: number = 1;
profileIcons: any[] = [
{ name: 'C1', class: 'active' },
{ name: 'C2' },
{ name: 'C3' },
{ name: 'C4' },
{ name: 'C5' },
{ name: 'C6' },
{ name: 'C7' },
{ name: 'C8' },
{ name: 'C9' },
{ name: 'C10' },
{ name: 'C11' },
{ name: 'C12' },
{ name: 'C13' },
{ name: 'C14' },
{ name: 'C15' },
{ name: 'C16' },
{ name: 'C17' },
{ name: 'C18' },
{ name: 'C19' },
{ name: 'C20' },
{ name: 'C21' },
{ name: 'C22' },
{ name: 'C23' },
{ name: 'C24' },
{ name: 'C25' },
{ name: 'C26' },
{ name: 'C27' },
{ name: 'C28' },
];
doNotMatch = false; doNotMatch = false;
error = false; error = false;
@ -20,16 +53,10 @@ export class RegisterComponent implements AfterViewInit {
errorUserExists = false; errorUserExists = false;
success = false; success = false;
// Login will be used to store the email as well.
// login: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email]]
registerForm = this.fb.group({ registerForm = this.fb.group({
login: [ name: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(254)]],
'',
[
Validators.required,
Validators.minLength(1),
Validators.maxLength(50),
Validators.pattern('^[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$|^[_.@A-Za-z0-9-]+$'),
],
],
email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email]], email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email]],
password: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]], password: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]],
confirmPassword: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]], confirmPassword: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]],
@ -38,9 +65,9 @@ export class RegisterComponent implements AfterViewInit {
constructor(private translateService: TranslateService, private registerService: RegisterService, private fb: FormBuilder) {} constructor(private translateService: TranslateService, private registerService: RegisterService, private fb: FormBuilder) {}
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (this.login) { // if (this.name) {
this.login.nativeElement.focus(); // this.name.nativeElement.focus();
} // }
} }
register(): void { register(): void {
@ -53,9 +80,14 @@ export class RegisterComponent implements AfterViewInit {
if (password !== this.registerForm.get(['confirmPassword'])!.value) { if (password !== this.registerForm.get(['confirmPassword'])!.value) {
this.doNotMatch = true; this.doNotMatch = true;
} else { } else {
const login = this.registerForm.get(['login'])!.value; const login = this.registerForm.get(['email'])!.value;
const email = this.registerForm.get(['email'])!.value; const email = this.registerForm.get(['email'])!.value;
this.registerService.save({ login, email, password, langKey: this.translateService.currentLang }).subscribe( const name = this.registerForm.get(['name'])!.value;
console.log(name);
this.registerService
.save({ login, email, password, langKey: this.translateService.currentLang, name, profileIcon: this.profileIcon })
.subscribe(
() => (this.success = true), () => (this.success = true),
response => this.processError(response) response => this.processError(response)
); );
@ -71,4 +103,12 @@ export class RegisterComponent implements AfterViewInit {
this.error = true; this.error = true;
} }
} }
selectIcon(event: MouseEvent): void {
if (event.target instanceof Element) {
document.querySelectorAll('.active').forEach(e => e.classList.remove('active'));
event.target.classList.add('active');
this.profileIcon = +event.target.getAttribute('id')! + 1;
}
}
} }

View File

@ -1,3 +1,10 @@
export class Registration { export class Registration {
constructor(public login: string, public email: string, public password: string, public langKey: string) {} constructor(
public login: string,
public email: string,
public password: string,
public langKey: string,
public name: string,
public profileIcon: number
) {}
} }

View File

@ -0,0 +1,11 @@
import { SwiperModule } from 'swiper/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperlibComponent } from './swiperlib/swiperlib.component';
@NgModule({
declarations: [SwiperlibComponent],
imports: [CommonModule, SwiperModule],
exports: [SwiperModule, SwiperlibComponent],
})
export class ComponentsModule {}

View File

@ -0,0 +1,20 @@
<swiper
[slidesPerView]="3"
[spaceBetween]="30"
[pagination]="{
dynamicBullets: true
}"
[loop]="true"
class="mySwiper"
>
<!-- [scrollbar]="{ draggable: true, hide: true }" -->
<ng-template swiperSlide *ngFor="let items of data; index as i">
<img
class="{{ items.class }}"
src="./../../../content/profile_icons/{{ items.name }}.png"
alt=""
(click)="onSelect($event)"
[attr.id]="i"
/>
</ng-template>
</swiper>

View File

@ -0,0 +1,30 @@
.mySwiper {
padding: 1rem;
}
.mySwiper img {
width: 100px;
height: auto;
border-radius: 50%;
padding: 0rem;
margin: 0.4rem;
border: 3px solid #fff;
transition: transform 0.1s ease-in-out, border 0.1s ease-in-out;
}
.mySwiper img::selection {
background-color: transparent;
}
.mySwiper img:hover {
// border: 4px solid #cdd8e7 !important;
box-shadow: rgba(0, 0, 0, 0.15) 0px 5px 15px;
cursor: pointer;
// transform: scale(1.1);
}
.active {
border: 4px solid #1072e8 !important;
box-shadow: rgba(0, 0, 0, 0.25) 0px 5px 15px;
transform: scale(1.1);
}

View File

@ -0,0 +1,26 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { SwiperComponent } from 'swiper/angular';
// import Swiper core and required modules
import SwiperCore, { Pagination, Scrollbar } from 'swiper/core';
// install Swiper modules
SwiperCore.use([Pagination, Scrollbar]);
@Component({
selector: 'jhi-swiper',
templateUrl: './swiperlib.component.html',
styleUrls: ['./swiperlib.component.scss'],
})
export class SwiperlibComponent implements OnInit {
@Input('data') data: any[] = [];
@Output('onSelectEvent') onSelectEvent = new EventEmitter<MouseEvent>();
constructor() {}
ngOnInit(): void {}
onSelect(event: MouseEvent): void {
this.onSelectEvent.emit(event);
}
}