dev #1
@@ -32,6 +32,8 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.security:spring-security-crypto:6.5.3'
|
||||||
|
implementation("io.jsonwebtoken:jjwt:0.13.0")
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import ru.soune.nocopy.adminpanel.handler.AdminUserHandler;
|
||||||
|
import ru.soune.nocopy.adminpanel.handler.RequestHandler;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AppConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.addAllowedOrigin("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Map<Integer, RequestHandler> handlers(AdminUserHandler adminUserHandler) {
|
||||||
|
Map<Integer, RequestHandler> map = new HashMap<>();
|
||||||
|
map.put(100, adminUserHandler);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.BaseRequest;
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.BaseResponse;
|
||||||
|
import ru.soune.nocopy.adminpanel.handler.RequestHandler;
|
||||||
|
import ru.soune.nocopy.adminpanel.util.MessageCode;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class AdminController {
|
||||||
|
|
||||||
|
private final Map<Integer, RequestHandler> handlers;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<BaseResponse> handleRequest(@RequestBody BaseRequest request) {
|
||||||
|
log.info("Received request: msg_id={}, version={}", request.getMsgId(), request.getVersion());
|
||||||
|
|
||||||
|
try {
|
||||||
|
RequestHandler handler = handlers.get(request.getMsgId());
|
||||||
|
|
||||||
|
if (handler == null) {
|
||||||
|
log.warn("No handler found for msg_id: {}", request.getMsgId());
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.INVALID_ACTION.getCode())
|
||||||
|
.messageDesc("No handler for msg_id: " + request.getMsgId())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseResponse response = handler.handle(request);
|
||||||
|
log.info("Request processed successfully: msg_id={}", request.getMsgId());
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error processing request: msg_id={}", request.getMsgId(), e);
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.INVALID_ACTION.getCode())
|
||||||
|
.messageDesc("Internal server error: " + e.getMessage())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ActionResponse {
|
||||||
|
@JsonProperty("action")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
@JsonProperty("available_actions")
|
||||||
|
private List<String> availableActions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AdminUserRequest {
|
||||||
|
@JsonProperty("action")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
@JsonProperty("admin_id")
|
||||||
|
private Long adminId;
|
||||||
|
|
||||||
|
@JsonProperty("full_name")
|
||||||
|
private String fullName;
|
||||||
|
|
||||||
|
@JsonProperty("email")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@JsonProperty("password")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@JsonProperty("is_super_admin")
|
||||||
|
private Boolean isSuperAdmin;
|
||||||
|
|
||||||
|
@JsonProperty("is_active")
|
||||||
|
private Boolean isActive;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class AdminUserResponse {
|
||||||
|
@JsonProperty("id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@JsonProperty("full_name")
|
||||||
|
private String fullName;
|
||||||
|
|
||||||
|
@JsonProperty("email")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@JsonProperty("is_active")
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@JsonProperty("is_super_admin")
|
||||||
|
private Boolean isSuperAdmin;
|
||||||
|
|
||||||
|
@JsonProperty("created_at")
|
||||||
|
private String createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
|
||||||
|
public class BaseRequest {
|
||||||
|
@JsonProperty("version")
|
||||||
|
Integer version;
|
||||||
|
|
||||||
|
@JsonProperty("msg_id")
|
||||||
|
Integer msgId;
|
||||||
|
|
||||||
|
@JsonProperty("token")
|
||||||
|
String token;
|
||||||
|
|
||||||
|
@JsonProperty("message_body")
|
||||||
|
Object messageBody;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
|
||||||
|
public class BaseResponse {
|
||||||
|
@JsonProperty("msg_id")
|
||||||
|
private Integer msgId;
|
||||||
|
|
||||||
|
@JsonProperty("message_code")
|
||||||
|
private Integer messageCode;
|
||||||
|
|
||||||
|
@JsonProperty("message_desc")
|
||||||
|
private String messageDesc;
|
||||||
|
|
||||||
|
@JsonProperty("message_body")
|
||||||
|
private Object messageBody;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class LoginResponse {
|
||||||
|
@JsonProperty("success")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@JsonProperty("token")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@JsonProperty("admin")
|
||||||
|
private AdminUserResponse admin;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.*;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
import ru.soune.nocopy.adminpanel.permission.AdminPermission;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "admin_users")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString(exclude = {"password", "permissions"})
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class AdminUser {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "full_name")
|
||||||
|
private String fullName;
|
||||||
|
|
||||||
|
@Size(max = 1024)
|
||||||
|
@Column(name = "email", length = 1024, unique = true)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Size(min = 6)
|
||||||
|
@JsonIgnore
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
@Column(name = "created_at", updatable = false, nullable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private Boolean isActive = true;
|
||||||
|
|
||||||
|
@Column(name = "is_super_admin", nullable = false)
|
||||||
|
private Boolean isSuperAdmin = false;
|
||||||
|
|
||||||
|
@Column(name = "permissions", nullable = false)
|
||||||
|
private Long permissions = AdminPermission.DEFAULT_PERMISSIONS;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.exceptions;
|
||||||
|
|
||||||
|
public class AdminUserNotFoundException extends RuntimeException {
|
||||||
|
public AdminUserNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.exceptions;
|
||||||
|
|
||||||
|
public class EmailAlreadyExistsException extends RuntimeException {
|
||||||
|
public EmailAlreadyExistsException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.handler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.*;
|
||||||
|
import ru.soune.nocopy.adminpanel.entity.AdminUser;
|
||||||
|
import ru.soune.nocopy.adminpanel.exceptions.AdminUserNotFoundException;
|
||||||
|
import ru.soune.nocopy.adminpanel.exceptions.EmailAlreadyExistsException;
|
||||||
|
import ru.soune.nocopy.adminpanel.permission.AdminPermission;
|
||||||
|
import ru.soune.nocopy.adminpanel.service.AdminUserService;
|
||||||
|
import ru.soune.nocopy.adminpanel.util.JwtUtil;
|
||||||
|
import ru.soune.nocopy.adminpanel.util.MessageCode;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class AdminUserHandler implements RequestHandler {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
private final AdminUserService adminUserService;
|
||||||
|
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||||
|
AdminUserRequest adminRequest = objectMapper.convertValue(request.getMessageBody(), AdminUserRequest.class);
|
||||||
|
String action = adminRequest.getAction();
|
||||||
|
|
||||||
|
return switch (action) {
|
||||||
|
case "login" -> handleLogin(request, adminRequest);
|
||||||
|
case "create" -> handleCreate(request, adminRequest);
|
||||||
|
case "get_all" -> handleGetAll(request);
|
||||||
|
case "get_by_id" -> handleGetById(request, adminRequest);
|
||||||
|
case "update" -> handleUpdate(request, adminRequest);
|
||||||
|
case "set_super_admin" -> handleSetSuperAdmin(request, adminRequest);
|
||||||
|
case "toggle_active" -> handleToggleActive(request, adminRequest);
|
||||||
|
case "delete" -> handleDelete(request, adminRequest);
|
||||||
|
default -> handleInvalidAction(request, action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleLogin(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
String email = adminRequest.getEmail();
|
||||||
|
String password = adminRequest.getPassword();
|
||||||
|
|
||||||
|
boolean authenticated = adminUserService.authenticate(email, password);
|
||||||
|
|
||||||
|
if (!authenticated) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.INVALID_CREDENTIALS.getCode())
|
||||||
|
.messageDesc(MessageCode.INVALID_CREDENTIALS.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AdminUser admin = adminUserService.findByEmail(email);
|
||||||
|
|
||||||
|
if (!admin.getIsActive()) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_INACTIVE.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_INACTIVE.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = jwtUtil.generateToken(admin.getEmail(), admin.getId());
|
||||||
|
|
||||||
|
LoginResponse loginResponse = LoginResponse.builder()
|
||||||
|
.success(true)
|
||||||
|
.token(token)
|
||||||
|
.admin(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(loginResponse)
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleCreate(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
boolean superAdmin = adminRequest.getIsSuperAdmin() != null && adminRequest.getIsSuperAdmin();
|
||||||
|
AdminUser admin = adminUserService.create(
|
||||||
|
adminRequest.getFullName(),
|
||||||
|
adminRequest.getEmail(),
|
||||||
|
adminRequest.getPassword(),
|
||||||
|
superAdmin
|
||||||
|
);
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
} catch (EmailAlreadyExistsException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.EMAIL_ALREADY_EXISTS.getCode())
|
||||||
|
.messageDesc(MessageCode.EMAIL_ALREADY_EXISTS.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleGetAll(BaseRequest request) {
|
||||||
|
List<AdminUserResponse> admins = adminUserService.findAll().stream()
|
||||||
|
.map(this::convertToResponse)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(admins)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleGetById(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
AdminUser admin = adminUserService.findById(adminRequest.getAdminId());
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleUpdate(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
AdminUser admin = adminUserService.update(
|
||||||
|
adminRequest.getAdminId(),
|
||||||
|
adminRequest.getFullName(),
|
||||||
|
adminRequest.getEmail(),
|
||||||
|
adminRequest.getPassword()
|
||||||
|
);
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
} catch (EmailAlreadyExistsException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.EMAIL_ALREADY_EXISTS.getCode())
|
||||||
|
.messageDesc(MessageCode.EMAIL_ALREADY_EXISTS.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleSetSuperAdmin(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
AdminUser admin = adminUserService.setSuperAdmin(adminRequest.getAdminId(), adminRequest.getIsSuperAdmin());
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleToggleActive(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
adminUserService.toggleActive(adminRequest.getAdminId(), adminRequest.getIsActive());
|
||||||
|
AdminUser admin = adminUserService.findById(adminRequest.getAdminId());
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody(convertToResponse(admin))
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleDelete(BaseRequest request, AdminUserRequest adminRequest) {
|
||||||
|
try {
|
||||||
|
adminUserService.delete(adminRequest.getAdminId());
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.SUCCESS.getCode())
|
||||||
|
.messageDesc(MessageCode.SUCCESS.getDescription())
|
||||||
|
.messageBody("Admin deleted successfully")
|
||||||
|
.build();
|
||||||
|
} catch (AdminUserNotFoundException e) {
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.ADMIN_NOT_FOUND.getCode())
|
||||||
|
.messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleInvalidAction(BaseRequest request, String action) {
|
||||||
|
ActionResponse response = ActionResponse.builder()
|
||||||
|
.action(action)
|
||||||
|
.availableActions(Arrays.asList(
|
||||||
|
"login", "create", "get_all", "get_by_id",
|
||||||
|
"update", "set_super_admin", "toggle_active", "delete"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageCode(MessageCode.INVALID_ACTION.getCode())
|
||||||
|
.messageDesc(MessageCode.INVALID_ACTION.getDescription())
|
||||||
|
.messageBody(response)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminUserResponse convertToResponse(AdminUser admin) {
|
||||||
|
return AdminUserResponse.builder()
|
||||||
|
.id(admin.getId())
|
||||||
|
.fullName(admin.getFullName())
|
||||||
|
.email(admin.getEmail())
|
||||||
|
.isActive(admin.getIsActive())
|
||||||
|
.isSuperAdmin(AdminPermission.isSuperAdmin(admin.getPermissions()))
|
||||||
|
.createdAt(admin.getCreatedAt().toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.handler;
|
||||||
|
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.BaseRequest;
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.BaseResponse;
|
||||||
|
|
||||||
|
public interface RequestHandler {
|
||||||
|
BaseResponse handle(BaseRequest request) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.permission;
|
||||||
|
|
||||||
|
public class AdminPermission {
|
||||||
|
public static final long ADMIN_BIT = 1L;
|
||||||
|
public static final long SUPER_ADMIN_BIT = 1L << 1;
|
||||||
|
|
||||||
|
public static final long DEFAULT_PERMISSIONS = ADMIN_BIT;
|
||||||
|
public static final long SUPER_ADMIN_PERMISSIONS = ADMIN_BIT | SUPER_ADMIN_BIT;
|
||||||
|
|
||||||
|
public static boolean hasPermission(long userPermissions, long permissionBit) {
|
||||||
|
return (userPermissions & permissionBit) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAdmin(long permissions) {
|
||||||
|
return hasPermission(permissions, ADMIN_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSuperAdmin(long permissions) {
|
||||||
|
return hasPermission(permissions, SUPER_ADMIN_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.permission;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.soune.nocopy.adminpanel.entity.AdminUser;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PermissionChecker {
|
||||||
|
|
||||||
|
public boolean isSuperAdmin(AdminUser admin) {
|
||||||
|
if (admin == null || !admin.getIsActive()) return false;
|
||||||
|
return AdminPermission.isSuperAdmin(admin.getPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkSuperAdmin(AdminUser admin) {
|
||||||
|
if (!isSuperAdmin(admin)) {
|
||||||
|
throw new SecurityException("Super admin permission required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.soune.nocopy.adminpanel.entity.AdminUser;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface AdminUserRepository extends JpaRepository<AdminUser, Long> {
|
||||||
|
Optional<AdminUser> findByEmail(String email);
|
||||||
|
|
||||||
|
boolean existsByEmail(String email);
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.service;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import ru.soune.nocopy.adminpanel.entity.AdminUser;
|
||||||
|
import ru.soune.nocopy.adminpanel.exceptions.AdminUserNotFoundException;
|
||||||
|
import ru.soune.nocopy.adminpanel.exceptions.EmailAlreadyExistsException;
|
||||||
|
import ru.soune.nocopy.adminpanel.permission.AdminPermission;
|
||||||
|
import ru.soune.nocopy.adminpanel.repository.AdminUserRepository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminUserService {
|
||||||
|
|
||||||
|
private final AdminUserRepository adminUserRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public AdminUser create(String fullName, String email, String password, boolean superAdmin) {
|
||||||
|
if (adminUserRepository.existsByEmail(email)) {
|
||||||
|
throw new EmailAlreadyExistsException("Email already exists: " + email);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminUser admin = new AdminUser();
|
||||||
|
admin.setFullName(fullName);
|
||||||
|
admin.setEmail(email);
|
||||||
|
admin.setPassword(passwordEncoder.encode(password));
|
||||||
|
admin.setCreatedAt(LocalDateTime.now());
|
||||||
|
admin.setIsActive(true);
|
||||||
|
admin.setPermissions(superAdmin ? AdminPermission.SUPER_ADMIN_PERMISSIONS : AdminPermission.DEFAULT_PERMISSIONS);
|
||||||
|
|
||||||
|
return adminUserRepository.save(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminUser findById(Long id) {
|
||||||
|
return adminUserRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new AdminUserNotFoundException("Admin not found: " + id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminUser findByEmail(String email) {
|
||||||
|
return adminUserRepository.findByEmail(email)
|
||||||
|
.orElseThrow(() -> new AdminUserNotFoundException("Admin not found: " + email));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AdminUser> findAll() {
|
||||||
|
return adminUserRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public AdminUser update(Long id, String fullName, String email, String password) {
|
||||||
|
AdminUser admin = findById(id);
|
||||||
|
|
||||||
|
if (fullName != null) admin.setFullName(fullName);
|
||||||
|
|
||||||
|
if (email != null && !email.equals(admin.getEmail())) {
|
||||||
|
if (adminUserRepository.existsByEmail(email)) {
|
||||||
|
throw new EmailAlreadyExistsException("Email already exists: " + email);
|
||||||
|
}
|
||||||
|
admin.setEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password != null && !password.isEmpty()) {
|
||||||
|
admin.setPassword(passwordEncoder.encode(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
return adminUserRepository.save(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public AdminUser setSuperAdmin(Long id, boolean superAdmin) {
|
||||||
|
AdminUser admin = findById(id);
|
||||||
|
admin.setPermissions(superAdmin ? AdminPermission.SUPER_ADMIN_PERMISSIONS : AdminPermission.DEFAULT_PERMISSIONS);
|
||||||
|
return adminUserRepository.save(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void toggleActive(Long id, Boolean isActive) {
|
||||||
|
AdminUser admin = findById(id);
|
||||||
|
admin.setIsActive(isActive);
|
||||||
|
adminUserRepository.save(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void delete(Long id) {
|
||||||
|
adminUserRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean authenticate(String email, String password) {
|
||||||
|
return adminUserRepository.findByEmail(email)
|
||||||
|
.filter(AdminUser::getIsActive)
|
||||||
|
.map(admin -> passwordEncoder.matches(password, admin.getPassword()))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import ru.soune.nocopy.adminpanel.dto.BaseResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtAuthFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
String authHeader = request.getHeader("Authorization");
|
||||||
|
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = authHeader.substring(7);
|
||||||
|
|
||||||
|
if (!jwtUtil.validateToken(token)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json");
|
||||||
|
|
||||||
|
BaseResponse errorResponse = BaseResponse.builder()
|
||||||
|
.messageCode(MessageCode.INVALID_CREDENTIALS.getCode())
|
||||||
|
.messageDesc("Invalid or expired token")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.setAttribute("userId", jwtUtil.extractUserId(token));
|
||||||
|
request.setAttribute("email", jwtUtil.extractEmail(token));
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.util;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtUtil {
|
||||||
|
|
||||||
|
@Value("${jwt.secret:Z29nb3Bvd2VycmFuZ2VydHVtYmF5YW1iZjMyNDIyMjh3aW5lcndpbmVy}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${jwt.expiration:3600000}")
|
||||||
|
private Long expiration;
|
||||||
|
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(String email, Long userId) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setSubject(email)
|
||||||
|
.claim("userId", userId)
|
||||||
|
.setIssuedAt(new Date())
|
||||||
|
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||||
|
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Claims extractClaims(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(getSigningKey())
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractEmail(String token) {
|
||||||
|
return extractClaims(token).getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long extractUserId(String token) {
|
||||||
|
return extractClaims(token).get("userId", Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTokenExpired(String token) {
|
||||||
|
return extractClaims(token).getExpiration().before(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
return !isTokenExpired(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ru.soune.nocopy.adminpanel.util;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum MessageCode {
|
||||||
|
SUCCESS(0, "Success"),
|
||||||
|
INVALID_ACTION(2, "Invalid action"),
|
||||||
|
ADMIN_NOT_FOUND(4, "Admin not found"),
|
||||||
|
EMAIL_ALREADY_EXISTS(2, "Email already exists"),
|
||||||
|
INVALID_CREDENTIALS(2, "Invalid credentials"),
|
||||||
|
PERMISSION_DENIED(2, "Permission denied"),
|
||||||
|
ADMIN_INACTIVE(2, "Admin is inactive");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
MessageCode(int code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,3 +10,7 @@ spring:
|
|||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8082
|
port: 8082
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: Z29nb3Bvd2VycmFuZ2VydHVtYmF5YW1iZjMyNDIyMjh3aW5lcndpbmVy
|
||||||
|
expiration: 3600000
|
||||||
Reference in New Issue
Block a user