diff --git a/build.gradle b/build.gradle index 91ba0e0..dd9bf78 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,4 @@ plugins { - id 'java' - id 'application' - id 'org.springframework.boot' version '3.0.2' id 'io.spring.dependency-management' version '1.1.4' } @@ -10,7 +7,6 @@ repositories { mavenCentral() } - def harmonistModule = project(':modules:harmonist') configure([harmonistModule]) { @@ -35,19 +31,10 @@ configure([harmonistModule]) { } } -dependencies { - implementation harmonistModule - -// implementation 'org.springframework.boot:spring-boot-starter' - implementation 'org.springframework.boot:spring-boot-starter-web' -// implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' -} - -application { - mainClass = 'com.RPA.RPAMain' -} - tasks.create("unitTests").configure { dependsOn(harmonistModule.test) +} + +tasks.create("run").configure { + harmonistModule.run } \ No newline at end of file diff --git a/modules/harmonist/build.gradle b/modules/harmonist/build.gradle index 1f36b19..b8583b4 100644 --- a/modules/harmonist/build.gradle +++ b/modules/harmonist/build.gradle @@ -6,16 +6,14 @@ buildscript { dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' - compileOnly 'javax.servlet:javax.servlet-api:4.0.1' annotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'org.jetbrains:annotations:23.0.0' - implementation 'javax.validation:validation-api:2.0.1.Final' - implementation 'javax.persistence:javax.persistence-api:2.2' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/modules/harmonist/src/main/java/com/RPA/config/JwtAuthenticationFilter.java b/modules/harmonist/src/main/java/com/RPA/config/JwtAuthenticationFilter.java index 54dfa0b..873ca96 100644 --- a/modules/harmonist/src/main/java/com/RPA/config/JwtAuthenticationFilter.java +++ b/modules/harmonist/src/main/java/com/RPA/config/JwtAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.RPA.config; +import com.RPA.entity.User; import com.RPA.service.JwtService; import com.RPA.service.UserService; import jakarta.servlet.FilterChain; @@ -45,9 +46,7 @@ protected void doFilterInternal( String username = jwtService.extractUserName(jwt); if (StringUtils.isNotEmpty(username) && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = userService - .userDetailsService() - .loadUserByUsername(username); + User userDetails = userService.getByUsername(username); if (jwtService.isTokenValid(jwt, userDetails)) { SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -61,6 +60,8 @@ protected void doFilterInternal( authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); context.setAuthentication(authToken); SecurityContextHolder.setContext(context); + + request.setAttribute("uid", userDetails.getId()); } } filterChain.doFilter(request, response); diff --git a/modules/harmonist/src/main/java/com/RPA/config/SecurityConfig.java b/modules/harmonist/src/main/java/com/RPA/config/SecurityConfig.java index a58bc0c..3186a04 100644 --- a/modules/harmonist/src/main/java/com/RPA/config/SecurityConfig.java +++ b/modules/harmonist/src/main/java/com/RPA/config/SecurityConfig.java @@ -42,9 +42,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return corsConfiguration; })) .authorizeHttpRequests(request -> request - .requestMatchers("/api/user/**").permitAll() - .requestMatchers(HttpMethod.GET, "/api/robot/admin").hasRole("admin") - .requestMatchers(HttpMethod.GET, "/api/robot/**").authenticated() + .requestMatchers(HttpMethod.POST, "/api/user/login", "/api/user", "/api/robot").permitAll() + .requestMatchers(HttpMethod.DELETE, "/api/robot").permitAll() .requestMatchers("/swagger/**").permitAll() .anyRequest().authenticated()) .sessionManagement(manager -> manager.sessionCreationPolicy(STATELESS)) diff --git a/modules/harmonist/src/main/java/com/RPA/controller/ExceptionHandleController.java b/modules/harmonist/src/main/java/com/RPA/controller/ExceptionHandleController.java new file mode 100644 index 0000000..5e9c0df --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/controller/ExceptionHandleController.java @@ -0,0 +1,51 @@ +package com.RPA.controller; + +import com.RPA.exception.ConflictException; +import com.RPA.exception.ForbiddenException; +import com.RPA.exception.NotFoundException; +import io.swagger.v3.oas.annotations.Hidden; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Hidden +@RestControllerAdvice +public class ExceptionHandleController { + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ResponseEntity handleConflictException(ConflictException ex) { + return createBody(ex.getMessage(), HttpStatus.CONFLICT); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ResponseEntity handleNotFoundException(NotFoundException ex) { + return createBody(ex.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ResponseEntity handleRuntimeException(ForbiddenException ex) { + return createBody(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ResponseEntity handleRuntimeException(RuntimeException ex) { + return createBody("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR); + } + + private ResponseEntity createBody(String error, HttpStatus status) { + Map map = new HashMap<>(); + map.put("timestamp", new Date().toString()); + map.put("status", status.toString()); + map.put("error", error); + return new ResponseEntity(map, status); + } +} diff --git a/modules/harmonist/src/main/java/com/RPA/controller/GroupController.java b/modules/harmonist/src/main/java/com/RPA/controller/GroupController.java new file mode 100644 index 0000000..9e67913 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/controller/GroupController.java @@ -0,0 +1,312 @@ +package com.RPA.controller; + +import com.RPA.request.ChangeGroupDataRequest; +import com.RPA.request.CreateGroupRequest; +import com.RPA.request.UsernameRequest; +import com.RPA.response.GroupResponse; +import com.RPA.response.GroupWithRoleResponse; +import com.RPA.response.MemberWithRoleResponse; +import com.RPA.service.GroupService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/group") +public class GroupController { + private final GroupService groupService; + + @Operation(summary = "Create Group") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Group successfully created", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = GroupResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Group with that name already exists", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "", produces = {"application/json"}) + public ResponseEntity createGroup( + @RequestBody CreateGroupRequest request, + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(groupService.createGroup(userId, request)); + } + + @Operation(summary = "Update Group Info") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Group successfully created", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = GroupResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Group with that name already exists", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PutMapping(value = "/{groupId}", produces = {"application/json"}) + public ResponseEntity updateGroupInfo( + @RequestBody ChangeGroupDataRequest request, + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + return ResponseEntity.ok(groupService.updateGroup(userId, groupId, request)); + } + + @Operation(summary = "Delete Group") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "Group deleted", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @DeleteMapping(value = "/{groupId}", produces = {"application/json"}) + public ResponseEntity deleteGroup( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + groupService.deleteGroup(userId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Returns User's Groups") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Group deleted", + content = {@Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = GroupWithRoleResponse.class))) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "", produces = {"application/json"}) + public ResponseEntity getUserGroups( + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(groupService.getUsersGroups(userId)); + } + + @Operation(summary = "Returns Group's members") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Group deleted", + content = {@Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = MemberWithRoleResponse.class))) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "/{groupId}", produces = {"application/json"}) + public ResponseEntity getGroupsMembers( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + return ResponseEntity.ok(groupService.getGroupsMembers(groupId, userId)); + } + + @Operation(summary = "Invite user to the group") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "User invited", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User Or Group Not Found", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "User already invited", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{groupId}/invite", produces = {"application/json"}) + public ResponseEntity inviteUser( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId, + @RequestBody UsernameRequest request + ) { + groupService.inviteUser(userId, groupId, request); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Accept invitation") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "Invitation accepted", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User Or Group Not Found", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{groupId}/accept", produces = {"application/json"}) + public ResponseEntity acceptInvitation( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + groupService.acceptInvitation(userId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Decline invitation") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "Invitation declined", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User Or Group Not Found", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{groupId}/decline", produces = {"application/json"}) + public ResponseEntity declineInvitation( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + groupService.declineInvitation(userId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Leave the group") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "Group left", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User Or Group Not Found", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{groupId}/leave", produces = {"application/json"}) + public ResponseEntity leaveGroupEndPoint( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId + ) { + groupService.leaveGroup(userId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Remove the member from group") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "Member removed", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User Or Group Not Found", + content = @Content), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{groupId}/remove", produces = {"application/json"}) + public ResponseEntity removeMemberEndPoint( + @RequestAttribute("uid") Long userId, + @PathVariable Long groupId, + @RequestBody UsernameRequest request + ) { + groupService.excludeUser(userId, groupId, request); + return ResponseEntity.noContent().build(); + } +} diff --git a/modules/harmonist/src/main/java/com/RPA/controller/ScriptController.java b/modules/harmonist/src/main/java/com/RPA/controller/ScriptController.java new file mode 100644 index 0000000..e4acf2b --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/controller/ScriptController.java @@ -0,0 +1,313 @@ +package com.RPA.controller; + +import com.RPA.request.CreateScriptRequest; +import com.RPA.request.UpdateScriptRequest; +import com.RPA.request.UsernameRequest; +import com.RPA.response.GroupsAndAuthorsResponse; +import com.RPA.response.ScriptResponse; +import com.RPA.response.ScriptWithRoleResponse; +import com.RPA.service.ScriptService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/script") +public class ScriptController { + private final ScriptService service; + + @Operation(summary = "Create Script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Script successfully created", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = ScriptResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Unauthorized", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Script with that name already exists", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "", produces = {"application/json"}) + public ResponseEntity createScriptEp( + @RequestBody CreateScriptRequest request, + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(service.createScript(userId, request)); + } + + @Operation(summary = "Update Script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Script successfully Updated", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = ScriptResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Unauthorized", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Script with that name already exists", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PutMapping(value = "/{script_id}", produces = {"application/json"}) + public ResponseEntity updateScriptEp( + @RequestBody UpdateScriptRequest request, + @RequestAttribute("uid") Long userId, + @PathVariable Long script_id + ) { + return ResponseEntity.ok(service.updateScript(userId, script_id, request)); + } + + @Operation(summary = "Delete Script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "Script successfully deleted", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "Script not found", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @DeleteMapping(value = "/{script_id}", produces = {"application/json"}) + public ResponseEntity deleteScriptEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long script_id + ) { + service.deleteScript(userId, script_id); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Allow group to execute script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "Group successfully received script", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{scriptId}/group/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity grantScriptEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long scriptId, + @PathVariable Long groupId + ) { + service.grantScriptToGroup(userId, scriptId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Revoke rights to execute script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "Grant revoked", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @DeleteMapping(value = "/{scriptId}/group/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity revokeScriptEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long scriptId, + @PathVariable Long groupId + ) { + service.revokeScriptFromGroup(userId, scriptId, groupId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Allow user to manage script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "User is added to authors", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @PostMapping(value = "/{scriptId}/author", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity addAuthorEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long scriptId, + @RequestBody UsernameRequest request + ) { + service.addAuthorToScript(userId, scriptId, request); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Revoke rights from user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "Rights revoked", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Conflict", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @DeleteMapping(value = "/{scriptId}/author/{username}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity removeAuthorEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long scriptId, + @PathVariable String username + ) { + service.removeAuthorFromScript(userId, scriptId, username); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Get managing scripts") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Scripts where user is author", + content = {@Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ScriptWithRoleResponse.class))) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "/manage", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getManagingScriptsEp( + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(service.getManagingScripts(userId)); + } + + @Operation(summary = "Get executing scripts") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Scripts user can execute", + content = {@Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ScriptResponse.class))) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "/execute", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getExecutingScriptsEp( + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(service.getExecutingScripts(userId)); + } + + @Operation(summary = "Returns information about authors and executors of script") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Authors and executors found", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = GroupsAndAuthorsResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "Forbidden", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Conflict", + content = @Content) + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "/{scriptId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getInfoAboutScriptEp( + @RequestAttribute("uid") Long userId, + @PathVariable Long scriptId + ) { + return ResponseEntity.ok(service.getScriptInfo(userId, scriptId)); + } +} diff --git a/modules/harmonist/src/main/java/com/RPA/controller/UserController.java b/modules/harmonist/src/main/java/com/RPA/controller/UserController.java index 4fe46ce..d5ea372 100644 --- a/modules/harmonist/src/main/java/com/RPA/controller/UserController.java +++ b/modules/harmonist/src/main/java/com/RPA/controller/UserController.java @@ -1,28 +1,40 @@ package com.RPA.controller; +import com.RPA.entity.Group; import com.RPA.request.AuthorizeUserRequest; +import com.RPA.request.ChangeUserDataRequest; +import com.RPA.request.CreateGroupRequest; import com.RPA.request.RegisterUserRequest; +import com.RPA.response.GroupResponse; +import com.RPA.response.UserInfoResponse; import com.RPA.response.UserTokenResponse; import com.RPA.service.AuthenticationService; +import com.RPA.service.GroupService; +import com.RPA.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("api/user") public class UserController { private final AuthenticationService authService; + private final UserService userService; + @Operation(summary = "Register user") @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "Found Terms", + description = "User successfully create", content = {@Content( mediaType = "application/json", schema = @Schema(implementation = UserTokenResponse.class)) @@ -30,10 +42,14 @@ public class UserController { @ApiResponse( responseCode = "400", description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "409", + description = "Data already used", content = @Content) }) @PostMapping(value = "", produces = {"application/json"}) - public ResponseEntity registerUser( + public ResponseEntity registerUser( @RequestBody RegisterUserRequest request ) { return ResponseEntity.ok(authService.signUp(request)); @@ -43,7 +59,7 @@ public ResponseEntity registerUser( @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "Found Terms", + description = "Create JWT token", content = {@Content( mediaType = "application/json", schema = @Schema(implementation = UserTokenResponse.class)) @@ -54,9 +70,91 @@ public ResponseEntity registerUser( content = @Content) }) @PostMapping(value = "/login", produces = {"application/json"}) - public ResponseEntity authorizeUser( + public ResponseEntity authorizeUser( @RequestBody AuthorizeUserRequest request ) { return ResponseEntity.ok(authService.signIn(request)); } + + @Operation(summary = "Get user by username") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "User information", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = UserInfoResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User not Found", + content = @Content + ) + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "/{userName}", produces = {"application/json"}) + public ResponseEntity findUser( + @PathVariable String userName + ) { + return ResponseEntity.ok(new UserInfoResponse(userService.getByUsername(userName))); + } + + @Operation(summary = "Find users by username") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "User information", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = UserInfoResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User not Found", + content = @Content + ) + }) + @SecurityRequirement(name = "Bearer Authentication") + @GetMapping(value = "", produces = {"application/json"}) + public ResponseEntity> findUsersByTemplate( + @RequestParam(name = "userName") String userName + ) { + return ResponseEntity.ok(userService.searchByUsername(userName)); + } + + @Operation(summary = "Update user info") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Updated user information", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = UserInfoResponse.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Wrong request", + content = @Content), + @ApiResponse( + responseCode = "404", + description = "User not Found", + content = @Content + ), + }) + @SecurityRequirement(name = "Bearer Authentication") + @PutMapping(value = "", produces = {"application/json"}) + public ResponseEntity updateUser( + @RequestBody ChangeUserDataRequest request, + @RequestAttribute("uid") Long userId + ) { + return ResponseEntity.ok(userService.updateUserInfo(userId, request)); + } } diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Author.java b/modules/harmonist/src/main/java/com/RPA/entity/Author.java new file mode 100644 index 0000000..48862d6 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/Author.java @@ -0,0 +1,25 @@ +package com.RPA.entity; + +import com.RPA.entity.id.UserScriptId; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "authors") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Author { + @EmbeddedId + private UserScriptId id; + + @ManyToOne + @JoinColumn(name = "user_id", insertable = false, updatable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "script_id", insertable = false, updatable = false) + private Script script; +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Executor.java b/modules/harmonist/src/main/java/com/RPA/entity/Executor.java new file mode 100644 index 0000000..3eacdde --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/Executor.java @@ -0,0 +1,26 @@ +package com.RPA.entity; + +import com.RPA.entity.id.GroupScriptId; +import com.RPA.entity.id.UserScriptId; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "executors") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Executor { + @EmbeddedId + private GroupScriptId id; + + @ManyToOne + @JoinColumn(name = "group_id", insertable = false, updatable = false) + private Group group; + + @ManyToOne + @JoinColumn(name = "script_id", insertable = false, updatable = false) + private Script script; +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Group.java b/modules/harmonist/src/main/java/com/RPA/entity/Group.java new file mode 100644 index 0000000..045ec3a --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/Group.java @@ -0,0 +1,29 @@ +package com.RPA.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="groups") +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Group { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "groups_id_seq") + @SequenceGenerator(name = "groups_id_seq", sequenceName = "groups_id_seq", allocationSize = 1) + private Long id; + + @Column(name = "name", unique = true, nullable = false) + private String name; + + @Column(name = "description", nullable = false) + private String description; + + @ManyToOne + @JoinColumn(name = "leader", referencedColumnName = "id") + private User leaderId; +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Member.java b/modules/harmonist/src/main/java/com/RPA/entity/Member.java new file mode 100644 index 0000000..00001cf --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/Member.java @@ -0,0 +1,30 @@ +package com.RPA.entity; + +import com.RPA.entity.id.UserGroupId; +import com.RPA.entity.num.GroupRole; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "members") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Member { + @EmbeddedId + private UserGroupId id; + + @ManyToOne + @JoinColumn(name = "user_id", insertable = false, updatable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "group_id", insertable = false, updatable = false) + private Group group; + + @Enumerated(EnumType.ORDINAL) + @Column(name = "status", nullable = false) + private GroupRole role; +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Role.java b/modules/harmonist/src/main/java/com/RPA/entity/Role.java deleted file mode 100644 index 1e48dc6..0000000 --- a/modules/harmonist/src/main/java/com/RPA/entity/Role.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.RPA.entity; -public enum Role { - ROLE_USER, - ROLE_ADMIN -} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/Script.java b/modules/harmonist/src/main/java/com/RPA/entity/Script.java new file mode 100644 index 0000000..2566f0b --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/Script.java @@ -0,0 +1,51 @@ +package com.RPA.entity; + +import com.RPA.entity.num.OperatingSystem; +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.sql.Timestamp; + +@Entity +@Table(name="scripts") +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Script { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "scripts_id_seq") + @SequenceGenerator(name = "scripts_id_seq", sequenceName = "scripts_id_seq", allocationSize = 1) + private Long id; + + @Column(name = "code", nullable = false) + private String code; + + @Column(name = "name", nullable = false, unique = true) + private String name; + + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "input_data", columnDefinition = "jsonb", nullable = false) + @JdbcTypeCode(SqlTypes.JSON) + private JsonNode inputData; + + @Column(name = "version", nullable = false) + private String version; + + @Enumerated(EnumType.STRING) + @Column(name = "os", nullable = false) + private OperatingSystem os; + + @Column(name = "created", nullable = false) + private Timestamp created; + + @ManyToOne + @JoinColumn(name = "creator", referencedColumnName = "id") + private User creator; +} \ No newline at end of file diff --git a/modules/harmonist/src/main/java/com/RPA/entity/User.java b/modules/harmonist/src/main/java/com/RPA/entity/User.java index 8faf488..3b688b7 100644 --- a/modules/harmonist/src/main/java/com/RPA/entity/User.java +++ b/modules/harmonist/src/main/java/com/RPA/entity/User.java @@ -1,5 +1,6 @@ package com.RPA.entity; +import com.RPA.entity.num.UserRole; import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; @@ -37,7 +38,7 @@ public class User implements UserDetails { @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) - private Role role; + private UserRole role; @Override public Collection getAuthorities() { diff --git a/modules/harmonist/src/main/java/com/RPA/entity/id/GroupScriptId.java b/modules/harmonist/src/main/java/com/RPA/entity/id/GroupScriptId.java new file mode 100644 index 0000000..f128700 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/id/GroupScriptId.java @@ -0,0 +1,36 @@ +package com.RPA.entity.id; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Objects; + +@Data +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +public class GroupScriptId implements Serializable { + @Column(name = "group_id") + private Long groupId; + + @Column(name = "script_id") + private Long scriptId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GroupScriptId groupScriptId = (GroupScriptId) o; + return Objects.equals(scriptId, groupScriptId.scriptId) && + Objects.equals(groupId, groupScriptId.groupId); + } + + @Override + public int hashCode() { + return Objects.hash(scriptId, groupId); + } +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/id/UserGroupId.java b/modules/harmonist/src/main/java/com/RPA/entity/id/UserGroupId.java new file mode 100644 index 0000000..128890b --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/id/UserGroupId.java @@ -0,0 +1,34 @@ +package com.RPA.entity.id; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.*; + +import java.io.Serializable; +import java.util.Objects; + +@Data +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +public class UserGroupId implements Serializable { + @Column(name = "user_id") + private Long userId; + + @Column(name = "group_id") + private Long groupId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserGroupId userGroupId = (UserGroupId) o; + return Objects.equals(userId, userGroupId.userId) && + Objects.equals(groupId, userGroupId.groupId); + } + + @Override + public int hashCode() { + return Objects.hash(userId, groupId); + } +} \ No newline at end of file diff --git a/modules/harmonist/src/main/java/com/RPA/entity/id/UserScriptId.java b/modules/harmonist/src/main/java/com/RPA/entity/id/UserScriptId.java new file mode 100644 index 0000000..dd6d328 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/id/UserScriptId.java @@ -0,0 +1,36 @@ +package com.RPA.entity.id; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Objects; + +@Data +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +public class UserScriptId implements Serializable { + @Column(name = "user_id") + private Long userId; + + @Column(name = "script_id") + private Long scriptId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserScriptId userScriptId = (UserScriptId) o; + return Objects.equals(userId, userScriptId.userId) && + Objects.equals(scriptId, userScriptId.scriptId); + } + + @Override + public int hashCode() { + return Objects.hash(userId, scriptId); + } +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/num/GroupRole.java b/modules/harmonist/src/main/java/com/RPA/entity/num/GroupRole.java new file mode 100644 index 0000000..1cb2fbc --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/num/GroupRole.java @@ -0,0 +1,7 @@ +package com.RPA.entity.num; + +public enum GroupRole { + LEADER, + MEMBER, + INVITED +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/num/OperatingSystem.java b/modules/harmonist/src/main/java/com/RPA/entity/num/OperatingSystem.java new file mode 100644 index 0000000..a372850 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/num/OperatingSystem.java @@ -0,0 +1,6 @@ +package com.RPA.entity.num; + +public enum OperatingSystem { + Windows, + Darwin +} diff --git a/modules/harmonist/src/main/java/com/RPA/entity/num/UserRole.java b/modules/harmonist/src/main/java/com/RPA/entity/num/UserRole.java new file mode 100644 index 0000000..13ba308 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/entity/num/UserRole.java @@ -0,0 +1,5 @@ +package com.RPA.entity.num; +public enum UserRole { + ROLE_USER, + ROLE_ADMIN +} diff --git a/modules/harmonist/src/main/java/com/RPA/exception/ConflictException.java b/modules/harmonist/src/main/java/com/RPA/exception/ConflictException.java new file mode 100644 index 0000000..487c2eb --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/exception/ConflictException.java @@ -0,0 +1,5 @@ +package com.RPA.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { super(message); } +} diff --git a/modules/harmonist/src/main/java/com/RPA/exception/ForbiddenException.java b/modules/harmonist/src/main/java/com/RPA/exception/ForbiddenException.java new file mode 100644 index 0000000..a3e1719 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/exception/ForbiddenException.java @@ -0,0 +1,5 @@ +package com.RPA.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { super(message); } +} diff --git a/modules/harmonist/src/main/java/com/RPA/exception/NotFoundException.java b/modules/harmonist/src/main/java/com/RPA/exception/NotFoundException.java new file mode 100644 index 0000000..9c27475 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/exception/NotFoundException.java @@ -0,0 +1,5 @@ +package com.RPA.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { super(message); } +} diff --git a/modules/harmonist/src/main/java/com/RPA/repository/AuthorRepository.java b/modules/harmonist/src/main/java/com/RPA/repository/AuthorRepository.java new file mode 100644 index 0000000..12d32c0 --- /dev/null +++ b/modules/harmonist/src/main/java/com/RPA/repository/AuthorRepository.java @@ -0,0 +1,26 @@ +package com.RPA.repository; + +import com.RPA.entity.Author; +import com.RPA.entity.Script; +import com.RPA.entity.User; +import com.RPA.entity.id.UserScriptId; +import jakarta.transaction.Transactional; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface AuthorRepository extends CrudRepository { + @Modifying + @Transactional + @Query("DELETE FROM Author a WHERE a.id.scriptId = :scriptId") + void deleteAuthorsByScriptId(@Param("scriptId") Long scriptId); + + @Query("SELECT a.script FROM Author a WHERE a.user.id = :userId") + List