diff --git a/README.md b/README.md index b3b10b8..062ad25 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ # TimeManager +Template für einen Spring-Angular-Service +## Generate Angular Client Code with OpenAPI Generator +1. Install the latest version of "openapi-generator-cli" +```bash +npm install @openapitools/openapi-generator-cli -g +``` +2. Generate Code +```bash +npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-angular -o frontend/src/api/ +``` \ No newline at end of file diff --git a/backend/src/main/java/core/api/controller/TaskgroupController.java b/backend/src/main/java/core/api/controller/TaskgroupController.java new file mode 100644 index 0000000..d6fedf4 --- /dev/null +++ b/backend/src/main/java/core/api/controller/TaskgroupController.java @@ -0,0 +1,81 @@ +package core.api.controller; + +import core.api.models.auth.SimpleStatusResponse; +import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; +import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo; +import core.entities.timemanager.Taskgroup; +import core.services.PermissionResult; +import core.services.ServiceExitCode; +import core.services.ServiceResult; +import core.services.TaskgroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/api") +public class TaskgroupController { + + private final TaskgroupService taskgroupService; + + public TaskgroupController(@Autowired TaskgroupService taskgroupService) { + this.taskgroupService = taskgroupService; + } + + @PutMapping("/taskgroups") + public ResponseEntity createTaskgroup(@Valid @RequestBody TaskgroupFieldInfo fieldInfo) { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + ServiceResult creationResult = taskgroupService.addTaskgroup(fieldInfo, username); + if(creationResult.getExitCode() == ServiceExitCode.OK) { + return ResponseEntity.ok(new TaskgroupEntityInfo(creationResult.getResult())); + } else { + return ResponseEntity.status(409).body(new SimpleStatusResponse("failed")); + } + } + + @PostMapping("/taskgroups/{taskgroupID}") + public ResponseEntity editTaskgroup(@PathVariable long taskgroupID, @Valid @RequestBody TaskgroupFieldInfo taskgroupFieldInfo) { + PermissionResult taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); + if(!taskgroupPermissionResult.isHasPermissions()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { + return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); + } + + ServiceExitCode serviceExitCode = taskgroupService.editTaskgroup(taskgroupPermissionResult.getResult(), taskgroupFieldInfo); + if(serviceExitCode == ServiceExitCode.OK) { + return ResponseEntity.ok(new SimpleStatusResponse("success")); + } else { + return ResponseEntity.status(409).body(new SimpleStatusResponse("failed")); + } + } + + @DeleteMapping("/taskgroups/{taskgroupID}") + public ResponseEntity deleteTaskgroup(@PathVariable long taskgroupID) { + PermissionResult taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); + if (!taskgroupPermissionResult.isHasPermissions()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { + return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); + } + + taskgroupService.deleteTaskgroup(taskgroupPermissionResult.getResult()); + return ResponseEntity.ok(new SimpleStatusResponse("success")); + } + + @GetMapping("/taskgroups") + public ResponseEntity> listTaskgroupsOfUser() { + List taskgroups = taskgroupService.getTaskgroupsByUser(SecurityContextHolder.getContext().getAuthentication().getName()); + List taskgroupEntityInfos = taskgroups.stream().map(TaskgroupEntityInfo::new).toList(); + return ResponseEntity.ok(taskgroupEntityInfos); + } +} diff --git a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java new file mode 100644 index 0000000..99fd360 --- /dev/null +++ b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java @@ -0,0 +1,30 @@ +package core.api.models.timemanager.taskgroup; + +import core.entities.timemanager.Taskgroup; + +public class TaskgroupEntityInfo { + + private long taskgroupID; + private String taskgroupName; + + public TaskgroupEntityInfo(Taskgroup taskgroup) { + this.taskgroupID = taskgroup.getTaskgroupID(); + this.taskgroupName = taskgroup.getTaskgroupName(); + } + + public long getTaskgroupID() { + return taskgroupID; + } + + public void setTaskgroupID(long taskgroupID) { + this.taskgroupID = taskgroupID; + } + + public String getTaskgroupName() { + return taskgroupName; + } + + public void setTaskgroupName(String taskgroupName) { + this.taskgroupName = taskgroupName; + } +} diff --git a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java new file mode 100644 index 0000000..0c3116d --- /dev/null +++ b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java @@ -0,0 +1,23 @@ +package core.api.models.timemanager.taskgroup; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.Max; +import javax.validation.constraints.NotBlank; + +public class TaskgroupFieldInfo { + + @JsonProperty + @NotBlank + @Length(max = 255) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/backend/src/main/java/core/entities/timemanager/Taskgroup.java b/backend/src/main/java/core/entities/timemanager/Taskgroup.java new file mode 100644 index 0000000..4043f4d --- /dev/null +++ b/backend/src/main/java/core/entities/timemanager/Taskgroup.java @@ -0,0 +1,55 @@ +package core.entities.timemanager; + +import core.entities.User; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; + +@Entity +@Table(name= "taskgroups") +public class Taskgroup { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long taskgroupID; + + @NotBlank + @Column(name = "name", length = 255) + private String taskgroupName; + + @OneToOne + @JoinColumn(name = "taskgroupuser", nullable = false) + private User user; + + public Taskgroup(String taskgroupName, User user) { + this.taskgroupName = taskgroupName; + this.user = user; + } + + public Taskgroup() { + } + + public long getTaskgroupID() { + return taskgroupID; + } + + public void setTaskgroupID(long taskgroupID) { + this.taskgroupID = taskgroupID; + } + + public String getTaskgroupName() { + return taskgroupName; + } + + public void setTaskgroupName(String taskgroupName) { + this.taskgroupName = taskgroupName; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } +} diff --git a/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java b/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java new file mode 100644 index 0000000..4c17014 --- /dev/null +++ b/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java @@ -0,0 +1,24 @@ +package core.repositories.timemanager; + +import core.entities.User; +import core.entities.timemanager.Taskgroup; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import javax.transaction.Transactional; +import java.util.List; + +@Repository +public interface TaskgroupRepository extends CrudRepository { + + boolean existsByTaskgroupNameAndUser(String name, User user); + + @Query("SELECT tg FROM Taskgroup tg WHERE tg.user.username = ?1") + List findAllByUser(String username); + + @Modifying + @Transactional + void deleteAllByUser(User user); +} diff --git a/backend/src/main/java/core/services/InvalidConfigurationException.java b/backend/src/main/java/core/services/InvalidConfigurationException.java deleted file mode 100644 index 501c4c7..0000000 --- a/backend/src/main/java/core/services/InvalidConfigurationException.java +++ /dev/null @@ -1,2 +0,0 @@ -package core.services;public class InvalidConfigurationException { -} diff --git a/backend/src/main/java/core/services/PermissionResult.java b/backend/src/main/java/core/services/PermissionResult.java new file mode 100644 index 0000000..aafe4ef --- /dev/null +++ b/backend/src/main/java/core/services/PermissionResult.java @@ -0,0 +1,40 @@ +package core.services; + +public class PermissionResult extends ServiceResult { + + private boolean hasPermissions; + public PermissionResult(ServiceExitCode exitCode, T result) { + super(exitCode, result); + } + + public PermissionResult(T result) { + super(result); + } + + public PermissionResult(ServiceExitCode exitCode) { + super(exitCode); + } + + public PermissionResult(ServiceExitCode exitCode, T result, boolean hasPermissions) { + super(exitCode, result); + this.hasPermissions = hasPermissions; + } + + public PermissionResult(T result, boolean hasPermissions) { + super(result); + this.hasPermissions = hasPermissions; + } + + public PermissionResult(ServiceExitCode exitCode, boolean hasPermissions) { + super(exitCode); + this.hasPermissions = hasPermissions; + } + + public boolean isHasPermissions() { + return hasPermissions; + } + + public void setHasPermissions(boolean hasPermissions) { + this.hasPermissions = hasPermissions; + } +} diff --git a/backend/src/main/java/core/services/PropertyService.java b/backend/src/main/java/core/services/PropertyService.java index 7f9dfa3..fda0d25 100644 --- a/backend/src/main/java/core/services/PropertyService.java +++ b/backend/src/main/java/core/services/PropertyService.java @@ -1,14 +1,54 @@ package core.services; import core.api.models.properties.PropertyInfo; +import core.entities.PropertiesEntity; import core.repositories.PropertyRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.parsing.PropertyEntry; import org.springframework.stereotype.Service; import java.util.Optional; - +@Service public class PropertyService { - + @Autowired + private PropertyRepository propertyRepository; + + + + public void init() { + PropertiesEntity registrationEntity = new PropertiesEntity(PropertyName.REGISTRATION_PROPERTY_NAME.name(), true); + if(!propertyRepository.existsByName(registrationEntity.getName())) { + propertyRepository.save(registrationEntity); + } + } + + public Optional getProperty(PropertyName propertyName) { + Optional property = propertyRepository.findByName(propertyName.name()); + return property.map(PropertyInfo::new); + } + + public Optional getPropertyEntityByName(String name) { + return propertyRepository.findByName(name); + } + + public boolean isPropertyEnabled(PropertyName propertyName) { + Optional property = propertyRepository.findByName(propertyName.name()); + return property.map(PropertiesEntity::isStatus).orElseGet(() -> getDefaultBehaivior(propertyName)); + } + + public boolean getDefaultBehaivior(PropertyName propertyName) { + return switch (propertyName) { + case REGISTRATION_PROPERTY_NAME -> true; + }; + } + + public void savePropertyEntity(PropertiesEntity property) { + propertyRepository.save(property); + } + + public enum PropertyName { + REGISTRATION_PROPERTY_NAME + } } diff --git a/backend/src/main/java/core/services/ServiceExitCode.java b/backend/src/main/java/core/services/ServiceExitCode.java new file mode 100644 index 0000000..6105f94 --- /dev/null +++ b/backend/src/main/java/core/services/ServiceExitCode.java @@ -0,0 +1,8 @@ +package core.services; + +public enum ServiceExitCode { + + OK, + ENTITY_ALREADY_EXIST, + MISSING_ENTITY; +} diff --git a/backend/src/main/java/core/services/ServiceResult.java b/backend/src/main/java/core/services/ServiceResult.java new file mode 100644 index 0000000..da9e03d --- /dev/null +++ b/backend/src/main/java/core/services/ServiceResult.java @@ -0,0 +1,37 @@ +package core.services; + +public class ServiceResult { + + private ServiceExitCode exitCode; + private T result; + + public ServiceResult(ServiceExitCode exitCode, T result) { + this.exitCode = exitCode; + this.result = result; + } + + public ServiceResult(T result) { + this.result = result; + this.exitCode = ServiceExitCode.OK; + } + + public ServiceResult(ServiceExitCode exitCode) { + this.exitCode = exitCode; + } + + public ServiceExitCode getExitCode() { + return exitCode; + } + + public void setExitCode(ServiceExitCode exitCode) { + this.exitCode = exitCode; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } +} diff --git a/backend/src/main/java/core/services/TaskgroupService.java b/backend/src/main/java/core/services/TaskgroupService.java new file mode 100644 index 0000000..d5031c8 --- /dev/null +++ b/backend/src/main/java/core/services/TaskgroupService.java @@ -0,0 +1,78 @@ +package core.services; + +import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; +import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo; +import core.entities.User; +import core.entities.UserRole; +import core.entities.timemanager.Taskgroup; +import core.repositories.UserRepository; +import core.repositories.timemanager.TaskgroupRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.config.Task; +import org.springframework.security.core.parameters.P; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +@Service +public class TaskgroupService { + + private final TaskgroupRepository taskgroupRepository; + private final UserRepository userRepository; + public TaskgroupService(@Autowired TaskgroupRepository taskgroupRepository, + @Autowired UserRepository userRepository) { + this.taskgroupRepository = taskgroupRepository; + this.userRepository = userRepository; + } + + public PermissionResult getTaskgroupByIDAndUsername(long taskgroupID, String username) { + Optional taskgroup = taskgroupRepository.findById(taskgroupID); + return taskgroup.map(value -> new PermissionResult<>(value, value.getUser().getUsername().equals(username))).orElseGet(() -> + new PermissionResult<>(ServiceExitCode.MISSING_ENTITY)); + } + + public ServiceResult addTaskgroup(TaskgroupFieldInfo taskData, String username) { + Optional user = userRepository.findByUsername(username); + if(user.isEmpty()) { + throw new NoSuchElementException(); + } + + if(!taskgroupRepository.existsByTaskgroupNameAndUser(taskData.getName(), user.get())) { + + Taskgroup taskgroup = new Taskgroup(taskData.getName(), user.get()); + taskgroupRepository.save(taskgroup); + return new ServiceResult<>(taskgroup); + } else { + return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST); + } + } + + public ServiceExitCode editTaskgroup(Taskgroup taskgroup, TaskgroupFieldInfo taskgroupFieldInfo) { + if(!taskgroup.getTaskgroupName().equals(taskgroupFieldInfo.getName())) { + //Check if another taskgroup with the new name is already existent for the user of the taskgroup + if(!taskgroupRepository.existsByTaskgroupNameAndUser(taskgroupFieldInfo.getName(), taskgroup.getUser())) { + taskgroup.setTaskgroupName(taskgroupFieldInfo.getName()); + taskgroupRepository.save(taskgroup); + return ServiceExitCode.OK; + } else { + return ServiceExitCode.ENTITY_ALREADY_EXIST; + } + } else { + return ServiceExitCode.OK; + } + } + + public void deleteTaskgroup(Taskgroup taskgroup) { + taskgroupRepository.delete(taskgroup); + } + + public List getTaskgroupsByUser(String username) { + return taskgroupRepository.findAllByUser(username); + } + + public void deleteTaskgroupByUser(User user) { + taskgroupRepository.deleteAllByUser(user); + } +} diff --git a/backend/src/main/java/core/services/UserService.java b/backend/src/main/java/core/services/UserService.java index 2ed47f9..e149fbe 100644 --- a/backend/src/main/java/core/services/UserService.java +++ b/backend/src/main/java/core/services/UserService.java @@ -9,6 +9,7 @@ import core.api.models.users.UserUpdateInfo; import core.entities.RoleEntity; import core.entities.User; import core.entities.UserRole; +import core.entities.timemanager.Taskgroup; import core.repositories.RoleRepository; import core.repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +30,7 @@ public class UserService { @Autowired private RoleRepository roleRepository; + @Autowired private TaskgroupService taskgroupService; public List getAllUserInfos() { Iterable users = userRepository.findAll(); @@ -146,6 +148,7 @@ public class UserService { return 2; } + taskgroupService.deleteTaskgroupByUser(requestedUser.get()); userRepository.deleteByUsername(username); return 0; } diff --git a/frontend/src/api/.openapi-generator/FILES b/frontend/src/api/.openapi-generator/FILES index 25f6fa7..8f617f7 100644 --- a/frontend/src/api/.openapi-generator/FILES +++ b/frontend/src/api/.openapi-generator/FILES @@ -5,6 +5,7 @@ api/account.service.ts api/api.ts api/login.service.ts api/properties.service.ts +api/taskgroup.service.ts api/users.service.ts configuration.ts encoder.ts @@ -24,6 +25,8 @@ model/propertiesInfo.ts model/propertyInfo.ts model/propertyUpdateRequest.ts model/signUpRequest.ts +model/taskgroupEntityInfo.ts +model/taskgroupFieldInfo.ts model/userAddInfo.ts model/userInfo.ts model/userUpdateInfo.ts diff --git a/frontend/src/api/api.module.ts b/frontend/src/api/api.module.ts index 187ee74..a695643 100644 --- a/frontend/src/api/api.module.ts +++ b/frontend/src/api/api.module.ts @@ -5,6 +5,7 @@ import { HttpClient } from '@angular/common/http'; import { AccountService } from './api/account.service'; import { LoginService } from './api/login.service'; import { PropertiesService } from './api/properties.service'; +import { TaskgroupService } from './api/taskgroup.service'; import { UsersService } from './api/users.service'; @NgModule({ diff --git a/frontend/src/api/api/api.ts b/frontend/src/api/api/api.ts index af6d936..e3102eb 100644 --- a/frontend/src/api/api/api.ts +++ b/frontend/src/api/api/api.ts @@ -4,6 +4,8 @@ export * from './login.service'; import { LoginService } from './login.service'; export * from './properties.service'; import { PropertiesService } from './properties.service'; +export * from './taskgroup.service'; +import { TaskgroupService } from './taskgroup.service'; export * from './users.service'; import { UsersService } from './users.service'; -export const APIS = [AccountService, LoginService, PropertiesService, UsersService]; +export const APIS = [AccountService, LoginService, PropertiesService, TaskgroupService, UsersService]; diff --git a/frontend/src/api/api/taskgroup.service.ts b/frontend/src/api/api/taskgroup.service.ts new file mode 100644 index 0000000..2d00598 --- /dev/null +++ b/frontend/src/api/api/taskgroup.service.ts @@ -0,0 +1,341 @@ +/** + * API Title + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +import { InlineResponse200 } from '../model/models'; +import { InlineResponse403 } from '../model/models'; +import { TaskgroupEntityInfo } from '../model/models'; +import { TaskgroupFieldInfo } from '../model/models'; + +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class TaskgroupService { + + protected basePath = 'http://localhost:8080/api'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, + (value as Date).toISOString().substr(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * list all taskgroups of authorized user + * list all taskgroups of authorized user + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public taskgroupsGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/taskgroups`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * creates taskgroup + * creates taskgroup + * @param taskgroupFieldInfo + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public taskgroupsPut(taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public taskgroupsPut(taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsPut(taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsPut(taskgroupFieldInfo?: TaskgroupFieldInfo, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.put(`${this.configuration.basePath}/taskgroups`, + taskgroupFieldInfo, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * deletes taskgroup + * deletes taskgroup + * @param taskgroupID internal id of taskgroup + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public taskgroupsTaskgroupIDDelete(taskgroupID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public taskgroupsTaskgroupIDDelete(taskgroupID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsTaskgroupIDDelete(taskgroupID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsTaskgroupIDDelete(taskgroupID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (taskgroupID === null || taskgroupID === undefined) { + throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDDelete.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.delete(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * edits taskgroup + * edits taskgroup + * @param taskgroupID internal id of taskgroup + * @param taskgroupFieldInfo + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public taskgroupsTaskgroupIDPost(taskgroupID: number, taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public taskgroupsTaskgroupIDPost(taskgroupID: number, taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsTaskgroupIDPost(taskgroupID: number, taskgroupFieldInfo?: TaskgroupFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsTaskgroupIDPost(taskgroupID: number, taskgroupFieldInfo?: TaskgroupFieldInfo, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (taskgroupID === null || taskgroupID === undefined) { + throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDPost.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.post(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}`, + taskgroupFieldInfo, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/api/model/models.ts b/frontend/src/api/model/models.ts index 653f5dc..adcbb18 100644 --- a/frontend/src/api/model/models.ts +++ b/frontend/src/api/model/models.ts @@ -11,6 +11,8 @@ export * from './propertiesInfo'; export * from './propertyInfo'; export * from './propertyUpdateRequest'; export * from './signUpRequest'; +export * from './taskgroupEntityInfo'; +export * from './taskgroupFieldInfo'; export * from './userAddInfo'; export * from './userInfo'; export * from './userUpdateInfo'; diff --git a/frontend/src/api/model/taskgroupEntityInfo.ts b/frontend/src/api/model/taskgroupEntityInfo.ts new file mode 100644 index 0000000..bbec6ed --- /dev/null +++ b/frontend/src/api/model/taskgroupEntityInfo.ts @@ -0,0 +1,24 @@ +/** + * API Title + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface TaskgroupEntityInfo { + /** + * internal id of taskgroup + */ + taskgroupID: number; + /** + * name of taskgroup + */ + taskgroupName: string; +} + diff --git a/frontend/src/api/model/taskgroupFieldInfo.ts b/frontend/src/api/model/taskgroupFieldInfo.ts new file mode 100644 index 0000000..66c2b15 --- /dev/null +++ b/frontend/src/api/model/taskgroupFieldInfo.ts @@ -0,0 +1,20 @@ +/** + * API Title + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface TaskgroupFieldInfo { + /** + * name of taskgroup + */ + name: string; +} + diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index b03ec72..47103b6 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -4,11 +4,13 @@ import {AppComponent} from "./app.component"; import {AdminDashboardComponent} from "./admin-dashboard/admin-dashboard.component"; import {MainComponent} from "./main/main.component"; import {UserSettingsComponent} from "./user-settings/user-settings.component"; +import {TaskgroupDashboardComponent} from "./taskgroups/taskgroup-dashboard/taskgroup-dashboard.component"; const routes: Routes = [ {path: '', component: MainComponent}, {path: 'admin', component: AdminDashboardComponent}, - {path: 'user/settings', component: UserSettingsComponent} + {path: 'user/settings', component: UserSettingsComponent}, + {path: 'taskgroups', component: TaskgroupDashboardComponent} ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index f32869d..b4f5177 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,5 +1,10 @@ - Spring Angular Demo + TimeManager + + + + + diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index a2b3d4e..0b23766 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -36,6 +36,9 @@ import { DeleteAccountComponent } from './user-settings/account-settings/delete- import { RegistrationComponent } from './auth/registration/registration.component'; import { SettingsComponent } from './admin-dashboard/settings/settings.component'; import {MatListModule} from "@angular/material/list"; +import { TaskgroupDashboardComponent } from './taskgroups/taskgroup-dashboard/taskgroup-dashboard.component'; +import { TaskgroupCreationComponent } from './taskgroups/taskgroup-creation/taskgroup-creation.component'; +import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/taskgroup-deletion.component'; @NgModule({ declarations: [ @@ -52,7 +55,10 @@ import {MatListModule} from "@angular/material/list"; ManageEmailComponent, DeleteAccountComponent, RegistrationComponent, - SettingsComponent + SettingsComponent, + TaskgroupDashboardComponent, + TaskgroupCreationComponent, + TaskgroupDeletionComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.css b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.css new file mode 100644 index 0000000..820cdcf --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.css @@ -0,0 +1,3 @@ +.mat-form-field { + width: 100%; +} diff --git a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.html b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.html new file mode 100644 index 0000000..266d467 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.html @@ -0,0 +1,13 @@ +

Create New Taskgroup

+
+ + Name + + {{nameCtrl.value.length}} / 255 + +
+ +
+ + +
diff --git a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.spec.ts b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.spec.ts new file mode 100644 index 0000000..52708f0 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskgroupCreationComponent } from './taskgroup-creation.component'; + +describe('TaskgroupCreationComponent', () => { + let component: TaskgroupCreationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TaskgroupCreationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskgroupCreationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts new file mode 100644 index 0000000..8b299b7 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts @@ -0,0 +1,76 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {FormControl, Validators} from "@angular/forms"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; +import {error} from "@angular/compiler/src/util"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-taskgroup-creation', + templateUrl: './taskgroup-creation.component.html', + styleUrls: ['./taskgroup-creation.component.css'] +}) +export class TaskgroupCreationComponent implements OnInit { + + nameCtrl = new FormControl('', [Validators.required, Validators.maxLength(255)]) + pending: boolean = false + + constructor(private dialogRef: MatDialogRef, + private taskgroupService: TaskgroupService, + private snackbar: MatSnackBar, + @Inject(MAT_DIALOG_DATA) public data: TaskgroupEntityInfo | undefined) { } + + ngOnInit(): void { + if(this.data != undefined) { + this.nameCtrl.setValue(this.data!.taskgroupName) + } + } + + cancel() { + this.dialogRef.close(); + } + + save() { + this.pending = true; + if(this.data == undefined) { + this.taskgroupService.taskgroupsPut({ + name: this.nameCtrl.value + }).subscribe({ + next: resp => { + this.pending = false; + this.dialogRef.close(resp); + }, + error: err => { + this.pending = false; + if(err.status == 409) { + this.snackbar.open("Taskgroup already exists", "", {duration: 2000}); + } else { + this.snackbar.open("An unexpected error occured", ""); + } + } + }) + } else { + this.taskgroupService.taskgroupsTaskgroupIDPost(this.data.taskgroupID, { + name: this.nameCtrl.value + }).subscribe({ + next: resp => { + this.pending = false; + this.data!.taskgroupName = this.nameCtrl.value + this.dialogRef.close(true); + }, + error: err => { + this.pending = false; + if(err.status == 409) { + this.snackbar.open("Taskgroup already exists", "", {duration: 2000}); + } else if(err.status == 403) { + this.snackbar.open("No permission", "", {duration: 2000}) + } else if(err.status == 404) { + this.snackbar.open("Not found", "", {duration: 2000}); + } else { + this.snackbar.open("An unexpected error occured", ""); + } + } + }) + } + } +} diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.css b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.css new file mode 100644 index 0000000..a1970b1 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.css @@ -0,0 +1,41 @@ +.container { + margin: 20px auto; + width: 60%; +} + +.spacer { + margin-bottom: 2.5%; +} + + +@media screen and (max-width: 600px) { + .container { + width: 100%; + margin: 20px 10px; + } +} + +.navLink { + text-decoration: underline; + color: black; + margin-right: 5px; +} + +.navLink-disabled { + text-decoration: none; + color: grey; + +} + +.navLink-bar { + margin-bottom: 20px; +} + +#addTaskgroupBtn { + margin-top: 20px; +} + +.edit-btn { + background-color: #6e6e6e; + color: white; +} diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html new file mode 100644 index 0000000..747ee96 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html @@ -0,0 +1,19 @@ +
+ + + Dashboard + / Taskgroups + + + + + +

{{taskgroup.taskgroupName}}

+ + + +
+ +
+ +
diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.spec.ts b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.spec.ts new file mode 100644 index 0000000..4ec2ce3 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskgroupDashboardComponent } from './taskgroup-dashboard.component'; + +describe('TaskgroupDashboardComponent', () => { + let component: TaskgroupDashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TaskgroupDashboardComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskgroupDashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts new file mode 100644 index 0000000..fd65051 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from '@angular/core'; +import {MatDialog} from "@angular/material/dialog"; +import {TaskgroupCreationComponent} from "../taskgroup-creation/taskgroup-creation.component"; +import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; +import {TaskgroupDeletionComponent} from "../taskgroup-deletion/taskgroup-deletion.component"; + +@Component({ + selector: 'app-taskgroup-dashboard', + templateUrl: './taskgroup-dashboard.component.html', + styleUrls: ['./taskgroup-dashboard.component.css'] +}) +export class TaskgroupDashboardComponent implements OnInit { + + constructor(private dialog: MatDialog, + private taskgroupService: TaskgroupService) { } + + taskgroups: TaskgroupEntityInfo[] = [] + + ngOnInit(): void { + this.taskgroupService.taskgroupsGet().subscribe({ + next: resp => { + this.taskgroups = resp; + } + }) + } + + openTaskgroupCreation() { + const dialogRef = this.dialog.open(TaskgroupCreationComponent, {minWidth: "400px"}) + dialogRef.afterClosed().subscribe(res => { + if(res != undefined) { + this.taskgroups.push(res); + } + }) + } + + openTaskgroupEditor(taskgroup: TaskgroupEntityInfo) { + const dialogRef = this.dialog.open(TaskgroupCreationComponent, {data: taskgroup, minWidth: "400px"}); + dialogRef.afterClosed().subscribe(res => { + if(res) { + const data = this.taskgroups + } + }) + } + + openTaskgroupDeletion(taskgroup: TaskgroupEntityInfo) { + const dialogRef = this.dialog.open(TaskgroupDeletionComponent, {data: taskgroup, minWidth: "400px"}); + dialogRef.afterClosed().subscribe(res => { + if(res) { + this.taskgroups = this.taskgroups.filter(ctg => ctg.taskgroupID != taskgroup.taskgroupID) + } + }) + } +} diff --git a/backend/configuration.properties b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.css similarity index 100% rename from backend/configuration.properties rename to frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.css diff --git a/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.html b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.html new file mode 100644 index 0000000..71015c2 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.html @@ -0,0 +1,8 @@ +

Delete Taskgroup

+ +

Are you sure, you want to delete your taskgroup {{data.taskgroupName}}? This cannot be undone!

+ +
+ + +
diff --git a/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.spec.ts b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.spec.ts new file mode 100644 index 0000000..4f7d1d5 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskgroupDeletionComponent } from './taskgroup-deletion.component'; + +describe('TaskgroupDeletionComponent', () => { + let component: TaskgroupDeletionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TaskgroupDeletionComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskgroupDeletionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.ts b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.ts new file mode 100644 index 0000000..d19a62d --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-deletion/taskgroup-deletion.component.ts @@ -0,0 +1,46 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-taskgroup-deletion', + templateUrl: './taskgroup-deletion.component.html', + styleUrls: ['./taskgroup-deletion.component.css'] +}) +export class TaskgroupDeletionComponent implements OnInit { + + constructor(@Inject(MAT_DIALOG_DATA) public data: TaskgroupEntityInfo, + private dialogRef: MatDialogRef, + private taskgroupService: TaskgroupService, + private snackbar: MatSnackBar) { } + + pending: boolean = false; + + ngOnInit(): void { + } + + cancel() { + this.dialogRef.close(false); + } + + confirm() { + this.pending = true; + this.taskgroupService.taskgroupsTaskgroupIDDelete(this.data.taskgroupID).subscribe({ + next: resp => { + this.pending = false; + this.dialogRef.close(true); + }, + error: err => { + this.pending = false; + if(err.stats == 403) { + this.snackbar.open("No permission", "", {duration: 2000}); + } else if(err.status == 404) { + this.snackbar.open("Not found", "", {duration: 2000}); + } else { + this.snackbar.open("Unexpected error", ""); + } + } + }) + } +} diff --git a/openapi.yaml b/openapi.yaml index fd9e9a6..67f719f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -564,6 +564,200 @@ paths: example: "failed" enum: - "failed" + /taskgroups: + get: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: list all taskgroups of authorized user + description: list all taskgroups of authorized user + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/TaskgroupEntityInfo' + put: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: creates taskgroup + description: creates taskgroup + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TaskgroupFieldInfo' + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + $ref: '#/components/schemas/TaskgroupEntityInfo' + 409: + description: Taskgroup already exists + content: + application/json: + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + /taskgroups/{taskgroupID}: + post: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: edits taskgroup + description: edits taskgroup + parameters: + - name: taskgroupID + in: path + description: internal id of taskgroup + required: true + schema: + type: number + example: 1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TaskgroupFieldInfo' + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "success" + enum: + - "success" + 403: + description: No permission + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + 404: + description: Taskgroup does not exist + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + 409: + description: Taskgroup with that new name already exists + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + delete: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: deletes taskgroup + description: deletes taskgroup + parameters: + - name: taskgroupID + in: path + description: internal id of taskgroup + required: true + schema: + type: number + example: 1 + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "success" + enum: + - "success" + 403: + description: No permission + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + 404: + description: Taskgroup does not exist + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" components: securitySchemes: API_TOKEN: @@ -749,4 +943,31 @@ components: settings: type: array items: - $ref: '#/components/schemas/PropertyInfo' \ No newline at end of file + $ref: '#/components/schemas/PropertyInfo' + TaskgroupEntityInfo: + required: + - taskgroupID + - taskgroupName + additionalProperties: false + properties: + taskgroupID: + type: number + description: internal id of taskgroup + example: 1 + taskgroupName: + type: string + description: name of taskgroup + example: Taskgroup 1 + maxLength: 255 + minLength: 1 + TaskgroupFieldInfo: + required: + - name + additionalProperties: false + properties: + name: + type: string + description: name of taskgroup + example: Taskgroup 1 + maxLength: 255 + minLength: 1 \ No newline at end of file