Commit 982c8e3d authored by Juraj Fiala's avatar Juraj Fiala
Browse files

Merge branch 'main' into 'fix/nonlogged'

# Conflicts:
#   frontend/src/app/app-routing.module.ts
parents 76238e06 5065adc0
Pipeline #141235 waiting for manual action with stage
......@@ -16,7 +16,7 @@ The application also helps to find available agents that have previous experienc
## About project
Technologies: Java 11, Spring boot, Maven, H2 database.
Technologies: Java 11, Spring boot, Maven, H2 database, npm (Angular)
Developers:
......@@ -38,6 +38,10 @@ Run `mvn spring-boot:run -pl secret-archive-app`
There is h2 console is available at http://localhost:8080/pa165/h2-console, User Name: admin, Password: admin, JDBC URL: `jdbc:h2:mem:testdb`
### Frontend:
In `frontend/` directory
enter `npm start`. You may have to install it by your favorite package manager. The service will be available at [http://localhost:4200/](http://localhost:4200/).
## Mockups
Mockups for the front-end can be found on [Figma](https://www.figma.com/file/yUpJKdy6L7QizvD93FMqEX/Secret-Archive).
......
package cz.fi.muni.pa165.seminar4.group7.dto.agent;
import java.util.List;
import javax.validation.constraints.NotNull;
import cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment.AgentAssignmentDTO;
......@@ -12,6 +10,8 @@ import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @author Tomáš Biloš
*/
......
......@@ -3,15 +3,24 @@ package cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment;
import lombok.Getter;
import lombok.Setter;
import lombok.EqualsAndHashCode;
import java.sql.Date;
import javax.validation.constraints.NotNull;
/**
* @author Tomáš Biloš
*/
@Getter
@Setter
@EqualsAndHashCode
public class AgentAssignmentCreateDTO {
private Date start;
private int durationInDays;
private String report;
@NotNull
private String missionId;
@NotNull
private String agentId;
}
......@@ -6,21 +6,35 @@ import java.sql.Date;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentIdDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.mission.MissionIdDTO;
import lombok.EqualsAndHashCode;
import com.github.dozermapper.core.Mapping;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.EqualsAndHashCode;
import cz.fi.muni.pa165.seminar4.group7.dto.mission.MissionIdDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.agent.AgentCreateDTO;
import java.sql.Date;
import javax.validation.constraints.NotNull;
/**
* @author Tomáš Biloš
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Accessors(chain = true)
public class AgentAssignmentDTO {
private String id;
private Date start;
private int durationInDays;
private String report;
private MissionIdDTO mission;
private AgentIdDTO agent;
private AgentCreateDTO agent;
private AgentIdDTO agentId;
}
package cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment;
import com.github.dozermapper.core.Mapping;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.EqualsAndHashCode;
import java.sql.Date;
import javax.validation.constraints.NotNull;
/**
* @author Jan Smejkal
*/
@Getter
@Setter
@EqualsAndHashCode
public class AgentAssignmentUpdateDTO {
private String id;
private Date start;
private int durationInDays;
private String Report;
@NotNull
private String missionId;
@NotNull
private String agentId;
}
\ No newline at end of file
INSERT INTO AGENT (id, name, training) VALUES ('100', 'Specialist', 'Special training');
......@@ -4,6 +4,7 @@ package cz.fi.muni.pa165.seminar4.group7.controller;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import java.util.stream.Stream;
import javax.validation.Valid;
......@@ -36,14 +37,13 @@ public class AgentController {
private final AgentService agentService;
private final AgentFacade agentFacade;
private final BeanMappingServiceImpl beanMappingService;
@Autowired
public AgentController(AgentService agentService, BeanMappingServiceImpl beanMappingService, AgentFacade agentFacade) {
this.agentService = agentService;
this.agentFacade = agentFacade;
this.beanMappingService = beanMappingService;
}
@GetMapping()
......
package cz.fi.muni.pa165.seminar4.group7.controller;
import cz.fi.muni.pa165.seminar4.group7.AgentAssignmentService;
import cz.fi.muni.pa165.seminar4.group7.AgentService;
import cz.fi.muni.pa165.seminar4.group7.BeanMappingService;
import cz.fi.muni.pa165.seminar4.group7.MissionService;
import cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment.AgentAssignmentCreateDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment.AgentAssignmentUpdateDTO;
import cz.fi.muni.pa165.seminar4.group7.dto.agent_assignment.AgentAssignmentDTO;
import cz.fi.muni.pa165.seminar4.group7.entity.AgentAssignment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* @author Jan Smejkal
*/
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping(value = "/assignments")
public class AssignmentController {
final static Logger logger = LoggerFactory.getLogger(AssignmentController.class);
final private AgentAssignmentService assignmentService;
final private BeanMappingService beanMappingService;
final private AgentService agentService;
final private MissionService missionService;
@Autowired
public AssignmentController(AgentAssignmentService assignmentService, BeanMappingService beanMappingService,
AgentService agentService, MissionService missionService) {
this.assignmentService = assignmentService;
this.beanMappingService = beanMappingService;
this.agentService = agentService;
this.missionService = missionService;
}
//create assignment: POST to /assignments, DTO contains agent ID, mission ID, start, duration
@RequestMapping(value = "/", method = RequestMethod.POST)
public final void createAssignment(@RequestBody AgentAssignmentCreateDTO assignmentCreateDTO) {
logger.debug("rest POST createAssignment()");
var assignment = beanMappingService.map(assignmentCreateDTO, AgentAssignment.class);
var agent = agentService.findById(assignmentCreateDTO.getAgentId());
if (agent.isEmpty()) {
throw new IllegalArgumentException("Agent doesn't exist");
}
assignment.setAgent(agent.get());
var mission = missionService.findById(assignmentCreateDTO.getMissionId());
if (mission.isEmpty()) {
throw new IllegalArgumentException("Mission doesn't exist");
}
assignment.setMission(mission.get());
assignmentService.create(assignment);
}
//remove assignment: DELETE to /assignments/{id}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public void deleteAssignment(@PathVariable("id") String id) {
logger.debug("rest DELETE id=" + id);
assignmentService.deleteById(id);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public AgentAssignmentDTO getDetail(@PathVariable("id") String id) {
logger.debug("rest GET detail for id=" + id);
var assignment = assignmentService.findById(id);
return assignment.map(value -> beanMappingService.map(value, AgentAssignmentDTO.class)).orElse(null);
}
//Update assignment: DTO contains only duration and start
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public void updateAssignment(@PathVariable("id") String id, @RequestBody @Valid AgentAssignmentUpdateDTO agentAssignmentUpdateDTO) {
logger.debug("rest PUT/update id=" + id);
if (!id.equals(agentAssignmentUpdateDTO.getId())) {
throw new IllegalArgumentException("Provided data is inconsistent");
}
AgentAssignment oldAssignment = assignmentService.findById(id).get();
oldAssignment.setDurationInDays(agentAssignmentUpdateDTO.getDurationInDays());
oldAssignment.setReport(agentAssignmentUpdateDTO.getReport());
assignmentService.update(beanMappingService.map(oldAssignment, AgentAssignment.class));
}
}
......@@ -12,7 +12,7 @@ import java.util.Optional;
/**
* @author Juraj Fiala
*/
// TODO Find more suitable solution for CORS
// Find more suitable solution for CORS
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/login")
......
......@@ -3,6 +3,8 @@ package cz.fi.muni.pa165.seminar4.group7;
import cz.fi.muni.pa165.seminar4.group7.entity.AgentAssignment;
import cz.fi.muni.pa165.seminar4.group7.entity.PerformanceEvaluation;
import java.util.Optional;
/**
* Service to manipulate assignments.
*
......@@ -23,6 +25,13 @@ public interface AgentAssignmentService {
*/
void deleteById(String id);
/**
* Find agent assignment by id.
*
* @param id of the assignment to find
*/
Optional<AgentAssignment> findById(String id);
/**
* Updates assignment.
*
......@@ -56,8 +65,10 @@ public interface AgentAssignmentService {
/**
* Returns all assignments in a given mission.
*
* @param mission id of mission to retrieve reports from.
* @param id of mission of mission to retrieve reports from.
* @return assignments linked to the given mission.
*/
Iterable<AgentAssignment> getAssignmentsByMissionId(String id);
}
......@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* @author Juraj Fiala
......@@ -43,6 +44,15 @@ public class AgentAssignmentServiceImpl implements AgentAssignmentService {
assignmentDao.deleteById(id);
}
@Override
public Optional<AgentAssignment> findById(String id){
var assignment = assignmentDao.findById(id);
if (assignment.isEmpty()) {
throw new IllegalArgumentException("Record for the given assignment does not exist.");
}
return assignment;
}
@Override
public void update(AgentAssignment assignment) {
if (assignmentDao.findById(assignment.getId()).isEmpty()) {
......
......@@ -4,6 +4,8 @@ import { AgentDetailComponent } from './agent/agent-detail.component';
import { AgentListComponent } from './agent/agent-list.component';
import { CountryDetailComponent } from './country/country-detail.component';
import { CountryListComponent } from './country/country-list.component';
import { AssignmentDetailComponent } from "./assignment/assignment-detail.component";
import { AssignmentCreateComponent } from './assignment/assignment-create.component';
import { HomeComponent } from './home/home.component';
import { MissionDetailComponent } from './mission/mission-detail.component';
import { MissionListComponent } from './mission/mission-list.component';
......@@ -13,6 +15,8 @@ import { AuthGuard } from "./auth/auth.guard";
const routes: Routes = [
{ path: 'agent/:id', component: AgentDetailComponent, canActivate: [AuthGuard] },
{ path: 'agents', component: AgentListComponent, canActivate: [AuthGuard] },
{ path: 'assignment/create', component: AssignmentCreateComponent, canActivate: [AuthGuard] }, //needs to be before :id
{ path: 'assignment/:id', component: AssignmentDetailComponent },
{ path: 'countries', component: CountryListComponent, canActivate: [AuthGuard] },
{ path: 'country/:id', component: CountryDetailComponent, canActivate: [AuthGuard] },
{ path: 'mission/:id', component: MissionDetailComponent, canActivate: [AuthGuard] },
......
<div class="w-full bg-black h-20 text-white shadow-lg flex">
<button mat-icon-button><mat-icon>menu</mat-icon></button>
<button mat-icon-button [routerLink]="['/']">
<mat-icon>menu</mat-icon>
</button>
<div class="flex">
<a class="text-xl m-4" [routerLink]="['/agents']">Agents</a>
<a class="text-xl m-4" [routerLink]="['/missions']">Missions</a>
<a class="text-xl m-4" [routerLink]="['/countries']">Countries</a>
</div>
<!--for show-->
<div class="w-[50%] mx-auto flex">
<a class="text-xl m-4" [routerLink]="['/assignment/1']">Show Assignment 1</a>
</div>
<div class="w-[50%] mx-auto flex">
<a class="text-xl m-4" [routerLink]="['/assignment/create']">Create a new assignment</a>
</div>
</div>
<router-outlet></router-outlet>
......@@ -15,6 +15,9 @@ import { AgentDetailComponent } from './agent/agent-detail.component';
import { AgentListComponent } from './agent/agent-list.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AssignmentDetailComponent } from "./assignment/assignment-detail.component";
import { AssignmentCreateComponent } from "./assignment/assignment-create.component";
import { MatDividerModule } from "@angular/material/divider";
import { CountryListComponent } from './country/country-list.component';
import { CountryDetailComponent } from './country/country-detail.component';
import { HomeComponent } from './home/home.component';
......@@ -22,7 +25,6 @@ import { MissionDetailComponent } from './mission/mission-detail.component';
import { MissionListComponent } from './mission/mission-list.component';
import { LoginComponent } from './login/login.component';
@NgModule({
declarations: [
AppComponent,
......@@ -33,8 +35,12 @@ import { LoginComponent } from './login/login.component';
MissionListComponent,
MissionDetailComponent,
HomeComponent,
AssignmentDetailComponent,
AssignmentCreateComponent,
LoginComponent,
],
imports: [
BrowserAnimationsModule,
BrowserModule,
......@@ -50,6 +56,9 @@ import { LoginComponent } from './login/login.component';
MatTableModule,
ReactiveFormsModule,
FormsModule,
MatSelectModule,
MatDividerModule,
MatButtonModule,
ReactiveFormsModule,
],
providers: [],
......
<div *ngIf="state === requestState.ERROR">
Error loading create page: {{ errorMessage }}
</div>
<div *ngIf="confirmMessage">Assignment has been created.</div>
<div *ngIf="assignment && agents && missions && state === requestState.SUCCESS">
<div>Agent Assignment record</div>
<mat-form-field appearance="fill">
<mat-label>Start date:</mat-label>
<input matInput [(ngModel)]="assignment.start" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Duration:</mat-label>
<input matInput [(ngModel)]="assignment.durationInDays" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Agent's report:</mat-label>
<input matInput [(ngModel)]="assignment.report" />
</mat-form-field>
<mat-select id="select-agent" [(ngModel)]="assignment.agentId" class="bx--text-input" required name="actionSelection" >
<option *ngFor="let agent of agents" [value]="agent.id">{{agent.name}}</option>
</mat-select>
<mat-select id="select-mission" [(ngModel)]="assignment.missionId" class="bx--text-input" required name="actionSelection" >
<option *ngFor="let mission of missions" [value]="mission.id">{{mission.name}}</option>
</mat-select>
</div>
<div>
<button mat-button (click)="createAssignment()">Create</button>
</div>
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { environment } from 'src/environments/environment';
import { RequestState } from '../request-state.enum';
import { AssignmentCreate } from './assignment.interface';
import { Agent } from './assignment.interface';
import { Mission } from './assignment.interface';
@Component({
selector: 'assignment-create',
templateUrl: 'assignment-create.component.html',
})
export class AssignmentCreateComponent implements OnInit {
requestState = RequestState;
state: RequestState = this.requestState.SUCCESS;
errorMessage = '';
assignment: AssignmentCreate | undefined;
agents: Agent[] | null = null;
missions: Mission[] | null = null;
confirmMessage = false;
//src/app/assignment/assignment-create.component.html:8:45 - error TS2532: Object is possibly 'undefined'.
constructor(private httpClient: HttpClient, /*private route: ActivatedRoute*/) {}
async ngOnInit() {
// @ts-ignore
this.assignment = {start: Date.now(), durationInDays: '', report: '', missionId: '', agentId: ''};
try {
this.state = RequestState.PENDING;
this.agents = await firstValueFrom(
this.httpClient.get<Agent[]>(`${environment.apiUrl}/agents/`)
);
this.missions = await firstValueFrom(
this.httpClient.get<Mission[]>(`${environment.apiUrl}/missions/`)
);
this.state = RequestState.SUCCESS;
this.confirmMessage = true;
} catch (err) {
this.state = RequestState.ERROR;
this.errorMessage = 'Could not load assignment!';
throw err;
}
}
async createAssignment() {
try {
this.state = RequestState.PENDING;
this.assignment = await firstValueFrom(
this.httpClient.post<AssignmentCreate>(
`${environment.apiUrl}/assignments/`,
this.assignment
)
);
this.state = RequestState.SUCCESS;
} catch (err) {
this.state = RequestState.ERROR;
throw err;
}
}
}
<div *ngIf="state === requestState.ERROR">
Error showing assignment detail: {{ errorMessage }}
</div>
<div *ngIf="assignment && state === requestState.SUCCESS">
<div>Agent Assignment record</div>
<mat-form-field appearance="fill">
<mat-label>Start date:</mat-label>
<input matInput [disabled]="!editing" [(ngModel)]="assignment.start" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Duration:</mat-label>
<input matInput [disabled]="!editing" [(ngModel)]="assignment.durationInDays" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Agent's report:</mat-label>
<input matInput [disabled]="!editing" [(ngModel)]="assignment.report" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Agent's name:</mat-label>
<input matInput [disabled]="true" [(ngModel)]="assignment.agent.name" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Mission:</mat-label>
<input matInput [disabled]="true" [(ngModel)]="assignment.mission.name" />
</mat-form-field>
</div>
<mat-divider></mat-divider>
<section>
<button mat-raised-button *ngIf="!editing" (click)="setEditing(true)">Edit</button>
<button mat-raised-button *ngIf="editing" (click)="setEditing(false); saveAssignment()">Save</button>
<button mat-raised-button (click)="deleteAssignment()">Delete</button>
</section>
import { HttpClient } from '@angular/common/http';
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';
import { Assignment } from './assignment.interface';
import {Router} from "@angular/router"
@Component({
selector: 'assignment-detail',
templateUrl: 'assignment-detail.component.html',
})
export class AssignmentDetailComponent implements OnInit {
private id: String | undefined;
requestState = RequestState;
state: RequestState = this.requestState.PENDING;
editing = false;
errorMessage = '';
assignment: Assignment | null = null;
//displayedSkillColumns = ['skill'];
//displayedCodeNameColumns = ['codename'];
constructor(private httpClient: HttpClient, private route: ActivatedRoute, private router: Router) {}
async ngOnInit() {
this.id = this.route.snapshot.params['id'];
if (this.id === undefined) {
this.state = RequestState.ERROR;
this.errorMessage = 'Incorrectly initialized component, missing id!';
throw new Error(this.errorMessage);
}
try {
this.state = RequestState.PENDING;