Commit 65a79326 authored by Tomáš Biloš's avatar Tomáš Biloš
Browse files

Merge branch 'xbilos/create-agent' into 'main'

feat: create agent

See merge request !79
parents e75508ee a7b490f5
Pipeline #142442 failed with stage
in 8 seconds
package cz.fi.muni.pa165.seminar4.group7.dto.agent;
import cz.fi.muni.pa165.seminar4.group7.dto.codename.CodenameDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.skill.SkillDTO;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
......@@ -12,8 +15,14 @@ import java.util.List;
@Getter
@Setter
@EqualsAndHashCode
public class AgentCreateDTO {
public class CreateAgentDTO {
@NotNull
private String name;
@NotNull
private String password;
@NotNull
private String agentRole;
private String training;
private List<String> codeNames;
private List<CodenameDTO> codeNames;
private List<SkillDTO> skills;
}
......@@ -10,7 +10,6 @@ import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentCreateDTO;
/**
* @author Tomáš Biloš
......@@ -27,6 +26,5 @@ public class AgentAssignmentDTO {
private int durationInDays;
private String report;
private MissionIdDTO mission;
private AgentCreateDTO agent;
private AgentIdDTO agentId;
private AgentIdDTO agent;
}
......@@ -2,6 +2,7 @@ package cz.fi.muni.pa165.seminar4.group7.facade;
import java.util.List;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.CreateAgentDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentDTO;
public interface AgentFacade {
......
package cz.fi.muni.pa165.seminar4.group7.controller;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.validation.Valid;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.CreateAgentDTO;
import org.apache.commons.lang3.NotImplementedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
......@@ -21,7 +24,6 @@ import org.springframework.web.bind.annotation.RestController;
import cz.fi.muni.pa165.seminar4.group7.agent.AgentService;
import cz.fi.muni.pa165.seminar4.group7.mapping.BeanMappingServiceImpl;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentCreateDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentDTO;
import cz.fi.muni.pa165.seminar4.group7.entity.Agent;
import cz.fi.muni.pa165.seminar4.group7.facade.AgentFacade;
......@@ -35,13 +37,15 @@ public class AgentController {
private final AgentService agentService;
private final AgentFacade agentFacade;
private final BeanMappingServiceImpl beanMappingService;
private final PasswordEncoder passwordEncoder;
@Autowired
public AgentController(AgentService agentService, BeanMappingServiceImpl beanMappingService,
AgentFacade agentFacade) {
AgentFacade agentFacade, PasswordEncoder passwordEncoder) {
this.agentService = agentService;
this.agentFacade = agentFacade;
this.beanMappingService = beanMappingService;
this.passwordEncoder = passwordEncoder;
}
@GetMapping()
......@@ -81,7 +85,10 @@ public class AgentController {
}
@PostMapping()
public void createAgent(@RequestBody @Valid AgentCreateDTO agentCreateDTO) {
agentService.create(beanMappingService.map(agentCreateDTO, Agent.class));
public void createAgent(@RequestBody @Valid CreateAgentDTO createAgentDTO) {
var agent = beanMappingService.map(createAgentDTO, Agent.class);
agent.setId(UUID.randomUUID().toString());
agent.setPasswordHash(passwordEncoder.encode(createAgentDTO.getPassword()));
agentService.create(agent);
}
}
......@@ -7,6 +7,7 @@ import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import cz.fi.muni.pa165.seminar4.group7.country.CountryService;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.CreateAgentDTO;
import cz.fi.muni.pa165.seminar4.group7.entity.CodeName;
import cz.fi.muni.pa165.seminar4.group7.entity.Skill;
import cz.fi.muni.pa165.seminar4.group7.mapping.BeanMappingService;
......
......@@ -2,55 +2,22 @@
Error occured: {{ errorMessage }}
</div>
<div *ngIf="agent && state === requestState.SUCCESS" class="px-4">
<div *ngIf="!editing" class="text-gray-600">
<div class="text-xl text-black">{{ agent.name }}</div>
<span class="text-md">Training</span>
<p class="mb-4 border-b-[1xp] border-gray-200">
{{ agent.training }}
</p>
<button mat-icon-button *ngIf="!editing" (click)="startEditing()">
<mat-icon>edit</mat-icon>
</button>
</div>
<div *ngIf="editing">
<mat-form-field>
<mat-label>Agent name</mat-label>
<input matInput [(ngModel)]="agent.name" />
</mat-form-field>
<mat-form-field>
<mat-label>Training</mat-label>
<textarea matInput [(ngModel)]="agent.training"></textarea>
</mat-form-field>
</div>
<div *ngIf="editing">
<mat-form-field>
<mat-label>New codename</mat-label>
<textarea matInput #newCodename></textarea>
</mat-form-field>
<div class="flex">
<span class="text-4xl text-black mb-4">{{ agent.name }}</span>
<button
mat-icon-button
(click)="addCodeName(newCodename.value); newCodename.value = ''"
routerLink="../edit/{{ agent.id }}"
class="justify-end"
>
<mat-icon>add</mat-icon>
<mat-icon>edit</mat-icon>
</button>
</div>
<div *ngIf="editing">
<mat-form-field>
<mat-label>New skill</mat-label>
<textarea matInput #newSkill></textarea>
</mat-form-field>
<button
mat-icon-button
(click)="addSkill(newSkill.value); newSkill.value = ''"
>
<mat-icon>add</mat-icon>
</button>
</div>
<br />
<span class="text-md">Training</span>
<p class="mb-4 border-b-[1xp] border-gray-200 text-lg">
{{ agent.training }}
</p>
<table mat-table [dataSource]="agent.codeNames" class="pb-4 w-full">
<ng-container matColumnDef="codename">
......@@ -69,7 +36,7 @@
<table
mat-table
[dataSource]="agent.skills"
class="pb-6 w-full text-gray-700"
class="pb-6 text-gray-700 w-full"
>
<ng-container matColumnDef="skill">
<th mat-header-cell *matHeaderCellDef class="text-lg text-black">
......@@ -108,11 +75,4 @@
class="hover:bg-gray-200 hover:cursor-pointer"
></tr>
</table>
<div *ngIf="editing" class="w-full h-14 fixed bottom-0 bg-cyan-600 flex">
<button class="ml-4" mat-raised-button (click)="cancelEditing()">
Cancel
</button>
<button class="mr-4" mat-raised-button (click)="saveAgent()">Save</button>
</div>
</div>
import { HttpClient } from '@angular/common/http';
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { environment } from 'src/environments/environment';
import { RequestState } from '../request-state.enum';
......@@ -14,21 +14,14 @@ export class AgentDetailComponent implements OnInit {
private id: String | undefined;
requestState = RequestState;
state: RequestState = this.requestState.PENDING;
editing = false;
errorMessage = '';
agent: Agent | null = null;
private oldAgent: Agent | null = null;
skillColumns = ['skill'];
codenameColumns = ['codename'];
assignmentColumns = ['missionName', 'start'];
constructor(
private httpClient: HttpClient,
private route: ActivatedRoute,
private router: Router,
private changeDetectorRefs: ChangeDetectorRef
) {}
constructor(private httpClient: HttpClient, private route: ActivatedRoute) {}
async ngOnInit() {
this.id = this.route.snapshot.params['id'];
......@@ -42,68 +35,13 @@ export class AgentDetailComponent implements OnInit {
this.agent = await firstValueFrom(
this.httpClient.get<Agent>(`${environment.apiUrl}/agents/${this.id}`)
);
this.state = RequestState.SUCCESS;
} catch (err) {
this.state = RequestState.ERROR;
this.errorMessage = 'Could not load agent!';
throw err;
}
}
console.log(this.agent);
async saveAgent() {
this.endEditing();
try {
this.state = RequestState.PENDING;
this.agent = await firstValueFrom(
this.httpClient.put<Agent>(
`${environment.apiUrl}/agents/${this.id}`,
this.agent
)
);
this.state = RequestState.SUCCESS;
} catch (err) {
this.state = RequestState.ERROR;
this.errorMessage = 'Could not load agent!';
throw err;
}
}
addCodeName(newCodename: string) {
if (!newCodename || newCodename.length === 0) {
return;
}
if (!this.agent?.codeNames) {
this.agent!.codeNames = [];
}
this.agent!.codeNames.push({ codeName: newCodename });
this.agent!.codeNames = [...this.agent!.codeNames];
}
addSkill(newSkill: string) {
if (!newSkill || newSkill.length === 0) {
return;
}
if (!this.agent?.skills) {
this.agent!.skills = [];
}
this.agent!.skills.push({ name: newSkill });
this.agent!.skills = [...this.agent!.skills];
}
goToAssignmentDetail(assignmentId: string) {
this.router.navigate(['assignment', assignmentId]);
}
startEditing() {
this.editing = true;
this.oldAgent = JSON.parse(JSON.stringify(this.agent));
}
endEditing() {
this.editing = false;
}
cancelEditing() {
this.agent = this.oldAgent;
this.endEditing();
}
}
......@@ -11,6 +11,8 @@
</mat-select>
</mat-form-field>
<button mat-raised-button routerLink="../agent/create">Create agent</button>
<mat-form-field class="example-chip-list" appearance="fill">
<mat-label>Filter by skills</mat-label>
<mat-chip-list #chipList aria-label="Fruit selection">
......
......@@ -19,10 +19,12 @@ export interface Codename {
}
export interface Agent {
id: string;
id?: string;
name: string;
training?: string;
password?: string;
skills: Skill[];
agentRole?: string;
codeNames: Codename[];
agentAssignments: Assignment[];
}
<div *ngIf="agent">
<form [formGroup]="edit">
<mat-form-field>
<mat-label>Agent name</mat-label>
<input matInput formControlName="name" />
<mat-error>Must match {{ "[a-zA-Z]{3,}" }}</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Training</mat-label>
<textarea matInput formControlName="training"></textarea>
</mat-form-field>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput formControlName="password" />
<mat-error>Must be at least 6 characters long</mat-error>
</mat-form-field>
</form>
<mat-form-field>
<mat-label>New skill</mat-label>
<input matInput #newSkill />
</mat-form-field>
<button mat-icon-button (click)="addSkill(newSkill.value)">
<mat-icon>add</mat-icon>
</button>
<mat-form-field>
<mat-label>New codename</mat-label>
<input matInput #newCodename />
</mat-form-field>
<button mat-icon-button (click)="addCodename(newCodename.value)">
<mat-icon>add</mat-icon>
</button>
<mat-form-field appearance="fill">
<mat-label>Favorite food</mat-label>
<mat-select [(value)]="selectedRole">
<mat-option *ngFor="let role of roles" [value]="role">
{{ role }}
</mat-option>
</mat-select>
</mat-form-field>
<table mat-table [dataSource]="agent.codeNames" class="pb-4 w-full">
<ng-container matColumnDef="codename">
<th mat-header-cell *matHeaderCellDef class="text-lg text-black">
Codenames
</th>
<td mat-cell *matCellDef="let codename" class="text-gray-700">
{{ codename.codeName }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="codenameColumns"></tr>
<tr mat-row *matRowDef="let row; columns: codenameColumns"></tr>
</table>
<table
mat-table
[dataSource]="agent.skills"
class="pb-6 text-gray-700 w-full"
>
<ng-container matColumnDef="skill">
<th mat-header-cell *matHeaderCellDef class="text-lg text-black">
Skills
</th>
<td mat-cell *matCellDef="let skill">{{ skill.name }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="skillColumns"></tr>
<tr mat-row *matRowDef="let row; columns: skillColumns"></tr>
</table>
<button class="ml-4" mat-raised-button routerLink="auth/agents">
Cancel
</button>
<button
class="mr-4"
mat-raised-button
disabled="{{ !edit.valid }}"
(click)="saveAgent()"
>
Save
</button>
</div>
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { environment } from 'src/environments/environment';
import { RequestState } from '../request-state.enum';
import { Agent } from './agent.interface';
@Component({
selector: 'edit-agent',
templateUrl: 'edit-agent.component.html',
})
export class EditAgentComponent implements OnInit {
agent: Agent = {
name: '',
training: '',
skills: [],
codeNames: [],
agentAssignments: [],
};
roles = ['REGULAR', 'ADMIN', 'SUPERVISOR'];
selectedRole = 'REGULAR';
state: RequestState = RequestState.SUCCESS;
errorMessage: String = '';
edit = new FormGroup({
name: new FormControl('', [
Validators.pattern(/[a-zA-Z]{3,}/),
Validators.required,
]),
training: new FormControl(''),
password: new FormControl('', [
Validators.required,
Validators.pattern(/.{6,}/),
]),
});
creatingNewAgent = false;
skillColumns = ['skill'];
codenameColumns = ['codename'];
constructor(
private httpClient: HttpClient,
private route: ActivatedRoute,
private router: Router
) {}
async ngOnInit() {
const id = this.route.snapshot.params['id'];
if (id) {
this.creatingNewAgent = false;
try {
this.state = RequestState.PENDING;
this.agent = await firstValueFrom(
this.httpClient.get<Agent>(`${environment.apiUrl}/agents/${id}`)
);
this.edit.patchValue({
name: this.agent.name,
training: this.agent.training ?? '',
});
this.state = RequestState.SUCCESS;
} catch (error) {
this.errorMessage = 'Loading agent failed';
this.state = RequestState.ERROR;
}
} else {
this.creatingNewAgent = true;
}
}
addCodename(newCodename: string) {
if (!newCodename || newCodename.length === 0) {
return;
}
if (!this.agent?.codeNames) {
this.agent!.codeNames = [];
}
this.agent!.codeNames.push({ codeName: newCodename });
this.agent!.codeNames = [...this.agent!.codeNames];
}
addSkill(newSkill: string) {
if (!newSkill || newSkill.length === 0) {
return;
}
if (!this.agent?.skills) {
this.agent!.skills = [];
}
this.agent!.skills.push({ name: newSkill });
this.agent!.skills = [...this.agent!.skills];
}
async saveAgent() {
this.agent.name = this.edit.get('name')?.value;
this.agent.training = this.edit.get('training')?.value;
this.agent.agentRole = this.selectedRole;
this.agent.password = this.edit.get('password')?.value;
if (this.creatingNewAgent) {
this.agent = await firstValueFrom(
this.httpClient.post<Agent>(`${environment.apiUrl}/agents`, this.agent)
);
this.router.navigate(['auth', 'agents']);
} else {
this.agent = await firstValueFrom(
this.httpClient.put<Agent>(
`${environment.apiUrl}/agents/${this.agent.id}`,
this.agent
)
);
this.router.navigate(['auth', 'agent', this.agent.id]);
}
}
}
......@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AgentDetailComponent } from './agent/agent-detail.component';
import { AgentListComponent } from './agent/agent-list.component';
import { EditAgentComponent } from './agent/edit-agent.component';
import { AssignmentCreateComponent } from './assignment/assignment-create.component';
import { AssignmentDetailComponent } from './assignment/assignment-detail.component';
import { AgentDetailGuard } from './auth/agent-detail.guard';
......@@ -23,6 +24,15 @@ const routes: Routes = [
canActivate: [AuthGuard],
children: [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{
path: 'agent/edit/:id',
component: EditAgentComponent,
canActivate: [AgentDetailGuard],
},
{
path: 'agent/create',
component: EditAgentComponent,
},
{
path: 'agent/:id',
component: AgentDetailComponent,
......
......@@ -15,6 +15,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AgentDetailComponent } from './agent/agent-detail.component';
import { AgentListComponent } from './agent/agent-list.component';
import { EditAgentComponent } from './agent/edit-agent.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AssignmentCreateComponent } from './assignment/assignment-create.component';
......@@ -34,6 +35,7 @@ import { NotFoundComponent } from './not-found.component';
AgentListComponent,
AuthComponent,
AgentDetailComponent,
EditAgentComponent,
CountryListComponent,
CountryDetailComponent,
MissionListComponent,
......
......@@ -23,7 +23,8 @@ export class AgentDetailGuard implements CanActivate {
| UrlTree
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree> {
const id = state.url.split('/')[-1];
const id = state.url.split('/').pop();
return (
id === this.authService.getCurrentUser().id ||