diff --git a/.env b/.env index 07be7fb..b1dc353 100644 --- a/.env +++ b/.env @@ -1,8 +1,8 @@ -POSTGRES_DB=no_copy_ -POSTGRES_USER=ncp_db -POSTGRES_PASSWORD=ncpDbApp -POSTGRES_PORT=5432 -POSTGRES_HOST=postgres +POSTGRES_DB=admin_db +POSTGRES_USER=admin +POSTGRES_PASSWORD=adminDbApp +POSTGRES_PORT=5454 +POSTGRES_HOST=db REDIS_HOST=redis REDIS_PORT=6379 diff --git a/Dockerfile b/Dockerfile index 35301ca..f64e7bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,6 @@ FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /app/build/libs/*.jar app.jar -EXPOSE 8080 +EXPOSE 8082 CMD ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index bd00b16..cbeb567 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,11 @@ configurations { } } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.0" + } +} repositories { mavenCentral() @@ -32,6 +37,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' 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' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' @@ -39,5 +46,9 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0' + //security + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation("io.jsonwebtoken:jjwt:0.13.0") } diff --git a/docker-compose.yaml b/docker-compose.yaml index 8160e9f..fa5b8b8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,22 +1,43 @@ version: '3.9' services: - admin: - build: . - container_name: admin-backend - ports: - - "8082:8080" + db: + image: postgres:17 + container_name: admin-db environment: - SPRING_PROFILES_ACTIVE: prod - POSTGRES_HOST: db - POSTGRES_PORT: 5432 - POSTGRES_DB: no_copy_ - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: admin_db + POSTGRES_USER: admin + POSTGRES_PASSWORD: adminDbApp + ports: + - "5454:5432" + volumes: + - postgres_data:/var/lib/postgresql/data networks: - app-network restart: unless-stopped + admin: + build: . + container_name: admin-backend + ports: + - "${SERVER_PORT:-8082}:${SERVER_PORT:-8082}" + environment: + SPRING_PROFILES_ACTIVE: prod + POSTGRES_HOST: db + POSTGRES_PORT: 5432 + POSTGRES_DB: admin_db + POSTGRES_USER: admin + POSTGRES_PASSWORD: adminDbApp + SERVER_PORT: ${SERVER_PORT:-8082} + depends_on: + - db + networks: + - app-network + restart: unless-stopped + +volumes: + postgres_data: + networks: app-network: external: true \ No newline at end of file diff --git a/infrastrucure/deploy.groovy b/infrastrucure/deploy.groovy index c4cbd17..46850ae 100644 --- a/infrastrucure/deploy.groovy +++ b/infrastrucure/deploy.groovy @@ -2,68 +2,199 @@ pipeline { agent any parameters { - string(name: 'BRANCH', defaultValue: 'dev', description: 'Ветка для деплоя') - string(name: 'SERVER', defaultValue: '92.242.61.23', description: 'Сервер для деплоя') + string( + name: 'BRANCH', + defaultValue: 'dev', + description: 'Ветка для деплоя' + ) + string( + name: 'PORT', + defaultValue: '8082', + description: 'Порт для запуска admin-panel' + ) + string( + name: 'SERVER', + defaultValue: '92.242.61.23', + description: 'Сервер для деплоя' + ) + string( + name: 'NETWORK', + defaultValue: 'app-network', + description: 'Имя сети (будет создана если не существует)' + ) + string( + name: 'DB_PORT', + defaultValue: '5454', + description: 'Порт для базы данных' + ) } stages { stage('Git pull') { steps { cleanWs() - git branch: params.BRANCH, url: 'https://code.3err0.ru/backdev/admin-panel.git', credentialsId: 'nx-jen' + script { + checkout([ + $class: 'GitSCM', + branches: [[name: params.BRANCH]], + userRemoteConfigs: [[ + url: 'https://code.3err0.ru/backdev/admin-panel.git', + credentialsId: 'nx-jen' + ]] + ]) + } } } - stage('Deploy with docker-compose') { + stage('Copy to server') { + steps { + script { + withCredentials([ + usernamePassword( + credentialsId: 'server-root-password', + usernameVariable: 'SSH_USER', + passwordVariable: 'SSH_PASS' + ) + ]) { + sh """ + sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} ' + mkdir -p /opt/deployments/admin-panel-${params.PORT} && + rm -rf /opt/deployments/admin-panel-${params.PORT}/* + ' + + sshpass -p '${SSH_PASS}' scp -r -o StrictHostKeyChecking=no \ + . \ + ${SSH_USER}@${params.SERVER}:/opt/deployments/admin-panel-${params.PORT}/ + """ + } + } + } + } + + stage('Deploy admin-panel') { steps { script { def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim() withCredentials([ - usernamePassword(credentialsId: 'server-root-password', usernameVariable: 'SSH_USER', passwordVariable: 'SSH_PASS'), - string(credentialsId: 'DB_USER', variable: 'DB_USER'), - string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD') + usernamePassword( + credentialsId: 'server-root-password', + usernameVariable: 'SSH_USER', + passwordVariable: 'SSH_PASS' + ) ]) { sh """ - sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER ' - cd /opt/deployments/admin-panel || mkdir -p /opt/deployments/admin-panel && cd /opt/deployments/admin-panel + sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} " + cd /opt/deployments/admin-panel-${params.PORT} - echo "1. Остановка старого контейнера..." - docker stop admin-backend 2>/dev/null || echo "Контейнер не найден" - docker rm admin-backend 2>/dev/null || echo "Контейнер не найден" + echo '1. Проверяем/создаем сеть ${params.NETWORK}...' + if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then + docker network create ${params.NETWORK} + echo 'Сеть ${params.NETWORK} создана' + else + echo 'Сеть ${params.NETWORK} уже существует' + fi - echo "2. Копирование файлов..." - rsync -av --delete --exclude=.git --exclude=build . $SSH_USER@$SERVER:/opt/deployments/admin-panel/ + echo '2. Останавливаем старые контейнеры...' + docker stop admin-panel-${params.PORT} 2>/dev/null || echo 'Контейнер приложения не найден' + docker rm admin-panel-${params.PORT} 2>/dev/null || echo 'Контейнер приложения не найден' + + docker stop admin-db-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден' + docker rm admin-db-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден' - echo "3. Сборка образа..." - docker build --no-cache -t admin-backend:latest . + echo '3. Запускаем базу данных...' + docker run -d \\ + --name admin-db-${params.PORT} \\ + --network ${params.NETWORK} \\ + --network-alias db \\ + -p ${params.DB_PORT}:5432 \\ + -v admin_db_data_${params.PORT}:/var/lib/postgresql/data \\ + -e POSTGRES_DB=admin_db \\ + -e POSTGRES_USER=admin \\ + -e POSTGRES_PASSWORD=adminDbApp \\ + --restart unless-stopped \\ + postgres:16 - echo "4. Запуск контейнера..." - docker run -d \ - --name admin-backend \ - --network app-network \ - --network-alias admin \ - -p 8082:8082 \ - -v uploads_data:/data/uploads:rw \ - -e POSTGRES_DB=no_copy_ \ - -e POSTGRES_USER=$DB_USER \ - -e POSTGRES_PASSWORD=$DB_PASSWORD \ - -e POSTGRES_PORT=5432 \ - -e POSTGRES_HOST=db \ - -e BUILD_TIME='${buildTime}' \ - --restart unless-stopped \ - admin-backend:latest + echo '4. Ждем готовности БД...' + sleep 5 + until docker exec admin-db-${params.PORT} pg_isready -U admin; do + echo 'Waiting for database...' + sleep 2 + done + echo 'База данных готова' - echo "5. Проверка health..." - if curl -s -f http://localhost:8082/actuator/health; then - echo "Admin backend работает" - else - echo "Проблема с запуском admin-backend" - fi - ' + echo '5. Собираем образ приложения...' + docker build --no-cache -t admin-panel-${params.PORT}:latest . + + echo '6. Запускаем приложение...' + docker run -d \\ + --name admin-panel-${params.PORT} \\ + --network ${params.NETWORK} \\ + --network-alias admin \\ + --network-alias admin-${params.PORT} \\ + -p ${params.PORT}:${params.PORT} \\ + -v uploads_data:/data/uploads:rw \\ + -e POSTGRES_DB=admin_db \\ + -e POSTGRES_USER=admin \\ + -e POSTGRES_PASSWORD=adminDbApp \\ + -e POSTGRES_PORT=5432 \\ + -e POSTGRES_HOST=db \\ + -e SERVER_PORT=${params.PORT} \\ + -e BUILD_TIME='${buildTime}' \\ + --restart unless-stopped \\ + admin-panel-${params.PORT}:latest + + echo '7. Проверка...' + sleep 10 + + if curl -s -f http://localhost:${params.PORT}/actuator/health > /dev/null 2>&1; then + echo 'Admin-panel работает на порту ${params.PORT}' + echo 'URL: http://${params.SERVER}:${params.PORT}' + else + echo 'Проверка health не удалась' + echo '=== Логи приложения: ===' + docker logs admin-panel-${params.PORT} --tail=30 + echo '=== Логи БД: ===' + docker logs admin-db-${params.PORT} --tail=10 + fi + " + """ + } + } + } + } + + stage('Verify network') { + steps { + script { + withCredentials([ + usernamePassword( + credentialsId: 'server-root-password', + usernameVariable: 'SSH_USER', + passwordVariable: 'SSH_PASS' + ) + ]) { + sh """ + sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} " + echo '=== Контейнеры в сети ${params.NETWORK} ===' + docker network inspect ${params.NETWORK} --format='{{range .Containers}}{{.Name}} ({{.IPv4Address}}){{println}}{{end}}' + " """ } } } } } + + post { + success { + echo "Deployment successful" + echo "Admin panel URL: http://${params.SERVER}:${params.PORT}" + } + failure { + echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}" + } + always { + echo "Deployment process finished" + } + } } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/adminpanel/AdminPanelApplication.java b/src/main/java/ru/soune/nocopy/adminpanel/AdminPanelApplication.java index 0fc77f6..a998898 100644 --- a/src/main/java/ru/soune/nocopy/adminpanel/AdminPanelApplication.java +++ b/src/main/java/ru/soune/nocopy/adminpanel/AdminPanelApplication.java @@ -1,9 +1,11 @@ package ru.soune.nocopy.adminpanel; import org.springframework.boot.SpringApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@EnableFeignClients public class AdminPanelApplication { public static void main(String[] args) { diff --git a/src/main/java/ru/soune/nocopy/adminpanel/client/DashBoardAppClient.java b/src/main/java/ru/soune/nocopy/adminpanel/client/DashBoardAppClient.java new file mode 100644 index 0000000..1dbab82 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/client/DashBoardAppClient.java @@ -0,0 +1,29 @@ +package ru.soune.nocopy.adminpanel.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; +import ru.soune.nocopy.adminpanel.dto.BaseRequest; + +import java.util.Map; + +@FeignClient(name = "dashboard", url = "${nocopy-dashboard.url}") +public interface DashBoardAppClient { + + @PostMapping("/api/v1/data") + Map getUsersWithPagination(@RequestBody BaseRequest request); + + @PostMapping("api/internal/data-info") + Map internalDataInfo(@RequestBody BaseRequest request); + + @PostMapping("api/internal/data-control") + Map internalDataControl(@RequestBody BaseRequest request); + + @GetMapping("api/internal/data-download/{fileId}") + ResponseEntity download(@PathVariable String fileId); +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/config/AppConfig.java b/src/main/java/ru/soune/nocopy/adminpanel/config/AppConfig.java new file mode 100644 index 0000000..2c6be73 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/config/AppConfig.java @@ -0,0 +1,55 @@ +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.CreateAdminTemplateHandler; +import ru.soune.nocopy.adminpanel.handler.LoginHandler; +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 handlers(AdminUserHandler adminUserHandler, CreateAdminTemplateHandler createAdminTemplateHandler) { + Map map = new HashMap<>(); + map.put(100, adminUserHandler); + map.put(200, createAdminTemplateHandler); + + return map; + } + + @Bean + public Map loginHandlers(LoginHandler loginHandler) { + Map map = new HashMap<>(); + map.put(101, loginHandler); + + return map; + } + +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/config/FeignConfig.java b/src/main/java/ru/soune/nocopy/adminpanel/config/FeignConfig.java new file mode 100644 index 0000000..74d0512 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/config/FeignConfig.java @@ -0,0 +1,20 @@ +package ru.soune.nocopy.adminpanel.config; + +import feign.RequestInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignConfig { + + @Value("${app.internal-api-key}") + private String internalApiKey; + + @Bean + public RequestInterceptor internalKeyInterceptor() { + return requestTemplate -> { + requestTemplate.header("X-Internal-Key", internalApiKey); + }; + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/config/SecurityConfig.java b/src/main/java/ru/soune/nocopy/adminpanel/config/SecurityConfig.java new file mode 100644 index 0000000..24f7a59 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/config/SecurityConfig.java @@ -0,0 +1,36 @@ +package ru.soune.nocopy.adminpanel.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import ru.soune.nocopy.adminpanel.util.JwtAuthFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtAuthFilter jwtAuthFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/admin/login").permitAll() +// .requestMatchers("/api/admin").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/adminpanel/controller/AdminController.java b/src/main/java/ru/soune/nocopy/adminpanel/controller/AdminController.java new file mode 100644 index 0000000..810aeb2 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/controller/AdminController.java @@ -0,0 +1,173 @@ +package ru.soune.nocopy.adminpanel.controller; + +import feign.FeignException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.soune.nocopy.adminpanel.client.DashBoardAppClient; +import ru.soune.nocopy.adminpanel.dto.BaseRequest; +import ru.soune.nocopy.adminpanel.dto.BaseResponse; +import ru.soune.nocopy.adminpanel.entity.AdminSection; +import ru.soune.nocopy.adminpanel.handler.RequestHandler; +import ru.soune.nocopy.adminpanel.service.AdminSectionPermissionService; +import ru.soune.nocopy.adminpanel.util.MessageCode; + +import java.util.Map; + +@RestController +@RequestMapping("/api/admin") +@RequiredArgsConstructor +@Slf4j +public class AdminController { + + private final Map handlers; + + private final Map loginHandlers; + + private final DashBoardAppClient dashBoardAppClient; + + private final AdminSectionPermissionService adminSectionPermissionService; + + private final HttpServletRequest httpServletRequest; + + @PostMapping + public ResponseEntity 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() + ); + } + } + + + @PostMapping("/login") + public ResponseEntity handleLoginRequest(@RequestBody BaseRequest request) { + log.info("Received request: msg_id={}, version={}", request.getMsgId(), request.getVersion()); + + try { + RequestHandler handler = loginHandlers.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() + ); + } + } + + @PostMapping("/info") + public ResponseEntity handleInfo(@RequestBody BaseRequest request) { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.USERS)) { + return ResponseEntity.ok(BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't reads") + .build()); + } + + request.setAdminId(currentUserId); + + Map response = dashBoardAppClient.internalDataInfo(request); + + return ResponseEntity.ok( + BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(response) + .build()); + } + + @PostMapping("/control") + public ResponseEntity handleControl(@RequestBody BaseRequest request) { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.USERS)) { + return ResponseEntity.ok(BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't control") + .build()); + } + + request.setAdminId(currentUserId); + + Map response = dashBoardAppClient.internalDataControl(request); + + return ResponseEntity.ok( + BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(response) + .build()); + } + + @GetMapping("/download/{fileId}") + public ResponseEntity downloadFile(@PathVariable String fileId) { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canRead(currentUserId, AdminSection.CONTENT_MODERATION)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + try { + return dashBoardAppClient.download(fileId); + } catch (FeignException.NotFound e) { + return ResponseEntity.notFound().build(); + } catch (FeignException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/ActionResponse.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/ActionResponse.java new file mode 100644 index 0000000..be5d535 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/ActionResponse.java @@ -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 availableActions; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateRequest.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateRequest.java new file mode 100644 index 0000000..d6cc955 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateRequest.java @@ -0,0 +1,25 @@ +package ru.soune.nocopy.adminpanel.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Map; + +@Data +public class AdminCreateTemplateRequest { + + @JsonProperty("permissions") + private Map permissions; + + @JsonProperty("change_password") + private Boolean changePassword; + + @JsonProperty("action") + private String action; + + @JsonProperty("id") + private Long id; + + @JsonProperty("name") + private String name; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateResponse.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateResponse.java new file mode 100644 index 0000000..1a1aaed --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminCreateTemplateResponse.java @@ -0,0 +1,23 @@ +package ru.soune.nocopy.adminpanel.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Data +@Builder +public class AdminCreateTemplateResponse { + @JsonProperty("id") + private Long id; + + @JsonProperty("permissions") + private Map permissions; + + @JsonProperty("change_password") + private Boolean changePassword; + + @JsonProperty("name") + private String name; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserRequest.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserRequest.java new file mode 100644 index 0000000..dab02f1 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserRequest.java @@ -0,0 +1,43 @@ +package ru.soune.nocopy.adminpanel.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Map; + +@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("new_password") + private String newPassword; + + @JsonProperty("old_password") + private String oldPassword; + + @JsonProperty("is_super_admin") + private Boolean isSuperAdmin; + + @JsonProperty("is_active") + private Boolean isActive; + + @JsonProperty("change_password") + private Boolean changePassword; + + @JsonProperty("permissions") + private Map permissions; + +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserResponse.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserResponse.java new file mode 100644 index 0000000..a0061d1 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/AdminUserResponse.java @@ -0,0 +1,35 @@ +package ru.soune.nocopy.adminpanel.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@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; + + @JsonProperty("active_password") + private Boolean activePassword; + + private Map permissions; +} + diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseRequest.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseRequest.java new file mode 100644 index 0000000..baae892 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseRequest.java @@ -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("message_body") + Object messageBody; + + @JsonProperty(value = "admin_id", required = false) + Long adminId; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseResponse.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseResponse.java new file mode 100644 index 0000000..54b84e3 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/BaseResponse.java @@ -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; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/dto/LoginResponse.java b/src/main/java/ru/soune/nocopy/adminpanel/dto/LoginResponse.java new file mode 100644 index 0000000..c1f66ed --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/dto/LoginResponse.java @@ -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; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminCreateTemplate.java b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminCreateTemplate.java new file mode 100644 index 0000000..9cd5fe8 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminCreateTemplate.java @@ -0,0 +1,27 @@ +package ru.soune.nocopy.adminpanel.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "admin_create_template") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AdminCreateTemplate { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "permission", columnDefinition = "TEXT") + String permission; + + @Column(name = "change_password") + Boolean changePassword; + + @Column(name = "name") + String name; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSection.java b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSection.java new file mode 100644 index 0000000..13b43fa --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSection.java @@ -0,0 +1,17 @@ +package ru.soune.nocopy.adminpanel.entity; + +public enum AdminSection { + DASH, + USERS, + STAFF, + SUBSCRIPTIONS, + TARIFFS, + CONTENT_MODERATION, + COMPLAINTS, + CLAIMS, + MAILINGS, + API, + MONEY, + KYC, + AGREEMENTS +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSectionPermission.java b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSectionPermission.java new file mode 100644 index 0000000..0d661e3 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminSectionPermission.java @@ -0,0 +1,74 @@ +package ru.soune.nocopy.adminpanel.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "admin_section_permissions") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EntityListeners(AuditingEntityListener.class) +public class AdminSectionPermission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "user_id", nullable = false, unique = true) + private Long userId; + + @Column(name = "dash", nullable = false) + private Integer dash = 0b00; + + @Column(name = "users", nullable = false) + private Integer users = 0b00; + + @Column(name = "staff", nullable = false) + private Integer staff = 0b00; + + @Column(name = "subscriptions", nullable = false) + private Integer subscriptions = 0b00; + + @Column(name = "tariffs", nullable = false) + private Integer tariffs = 0b00; + + @Column(name = "content_moderation", nullable = false) + private Integer contentModeration = 0b00; + + @Column(name = "complaints", nullable = false) + private Integer complaints = 0b00; + + @Column(name = "claims", nullable = false) + private Integer claims = 0b00; + + @Column(name = "mailings", nullable = false) + private Integer mailings = 0b00; + + @Column(name = "api", nullable = false) + private Integer api = 0b00; + + @Column(name = "money", nullable = false) + private Integer money = 0b00; + + @Column(name = "kyc", nullable = false) + private Integer kyc = 0b00; + + @Column(name = "agreements", nullable = false) + private Integer agreements = 0b00; + + @CreatedDate + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminUser.java b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminUser.java new file mode 100644 index 0000000..cde4803 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/entity/AdminUser.java @@ -0,0 +1,62 @@ +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 = "active_password", nullable = false) + private Boolean isActivePassword = false; + + @Column(name = "permissions", nullable = false) + private Long permissions = AdminPermission.DEFAULT_PERMISSIONS; + +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/entity/PermissionType.java b/src/main/java/ru/soune/nocopy/adminpanel/entity/PermissionType.java new file mode 100644 index 0000000..dd6b983 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/entity/PermissionType.java @@ -0,0 +1,14 @@ +package ru.soune.nocopy.adminpanel.entity; + +import lombok.Getter; + +@Getter +public enum PermissionType { + READ(0b01), NONE(0b00), WRITE_AND_READ(0b11); + + private final Integer value; + + PermissionType(Integer value) { + this.value = value; + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/exceptions/AdminUserNotFoundException.java b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/AdminUserNotFoundException.java new file mode 100644 index 0000000..89c02cd --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/AdminUserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.soune.nocopy.adminpanel.exceptions; + +public class AdminUserNotFoundException extends RuntimeException { + public AdminUserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/exceptions/EmailAlreadyExistsException.java b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/EmailAlreadyExistsException.java new file mode 100644 index 0000000..38395dc --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/EmailAlreadyExistsException.java @@ -0,0 +1,7 @@ +package ru.soune.nocopy.adminpanel.exceptions; + +public class EmailAlreadyExistsException extends RuntimeException { + public EmailAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/exceptions/WrongPassword.java b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/WrongPassword.java new file mode 100644 index 0000000..f87bf33 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/exceptions/WrongPassword.java @@ -0,0 +1,7 @@ +package ru.soune.nocopy.adminpanel.exceptions; + +public class WrongPassword extends RuntimeException { + public WrongPassword(String message) { + super(message); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/handler/AdminUserHandler.java b/src/main/java/ru/soune/nocopy/adminpanel/handler/AdminUserHandler.java new file mode 100644 index 0000000..9fda54c --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/handler/AdminUserHandler.java @@ -0,0 +1,551 @@ +package ru.soune.nocopy.adminpanel.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +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.AdminSection; +import ru.soune.nocopy.adminpanel.entity.AdminUser; +import ru.soune.nocopy.adminpanel.entity.PermissionType; +import ru.soune.nocopy.adminpanel.exceptions.AdminUserNotFoundException; +import ru.soune.nocopy.adminpanel.exceptions.EmailAlreadyExistsException; +import ru.soune.nocopy.adminpanel.exceptions.WrongPassword; +import ru.soune.nocopy.adminpanel.permission.AdminPermission; +import ru.soune.nocopy.adminpanel.service.AdminSectionPermissionService; +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.HashMap; +import java.util.List; +import java.util.Map; +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; + + private final AdminSectionPermissionService permissionService; + + private final HttpServletRequest httpServletRequest; + + private final AdminSectionPermissionService adminSectionPermissionService; + + @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, adminRequest); + 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); + case "get_permissions_by_id" -> handleGetPermissionsById(request, adminRequest); + case "update_permissions" -> handleUpdatePermissions(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 { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.STAFF)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't create profile") + .build(); + } + + boolean superAdmin = adminRequest.getIsSuperAdmin() != null && adminRequest.getIsSuperAdmin(); + boolean changePassword = adminRequest.getChangePassword() != null ? adminRequest.getChangePassword(): + !superAdmin; + + AdminUser admin = adminUserService.create( + adminRequest.getFullName(), + adminRequest.getEmail(), + adminRequest.getPassword(), + changePassword, superAdmin); + + Map permissions = adminRequest.getPermissions(); + Map sectionPermissons = new HashMap<>(); + + if (superAdmin) { + sectionPermissons.put(AdminSection.DASH, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.USERS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.STAFF, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.SUBSCRIPTIONS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.TARIFFS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.CONTENT_MODERATION, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.COMPLAINTS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.CLAIMS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.MAILINGS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.API, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.MONEY, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.KYC, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.AGREEMENTS, PermissionType.WRITE_AND_READ.getValue()); + } else { + for (Map.Entry entry: permissions.entrySet()) { + String name = entry.getKey(); + AdminSection adminSection = AdminSection.valueOf(name.toUpperCase()); + + sectionPermissons.put(adminSection, entry.getValue()); + } + } + + permissionService.setAllPermissions(admin.getId(), sectionPermissons); + + 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, AdminUserRequest adminRequest) { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't reads") + .build(); + } + + List 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()); + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!currentUserId.equals(adminRequest.getAdminId())) { + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't read") + .build(); + } + } + + 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 { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!currentUserId.equals(adminRequest.getAdminId())) { + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't update profile") + .build(); + } + } + + AdminUser admin = adminUserService.update( + adminRequest.getAdminId(), + adminRequest.getFullName(), + adminRequest.getEmail(), + adminRequest.getOldPassword(), + adminRequest.getNewPassword()); + + 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(); + } catch (WrongPassword e) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.WRONG_PASSWORD.getCode()) + .messageDesc(MessageCode.WRONG_PASSWORD.getDescription()) + .build(); + } + } + + private BaseResponse handleSetSuperAdmin(BaseRequest request, AdminUserRequest adminRequest) { + try { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't update profile") + .build(); + } + + AdminUser admin = adminUserService.setSuperAdmin(adminRequest.getAdminId(), adminRequest.getIsSuperAdmin()); + + Map sectionPermissons = new HashMap<>(); + + sectionPermissons.put(AdminSection.DASH, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.USERS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.STAFF, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.SUBSCRIPTIONS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.TARIFFS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.CONTENT_MODERATION, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.COMPLAINTS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.CLAIMS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.MAILINGS, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.API, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.MONEY, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.KYC, PermissionType.WRITE_AND_READ.getValue()); + sectionPermissons.put(AdminSection.AGREEMENTS, PermissionType.WRITE_AND_READ.getValue()); + + permissionService.setAllPermissions(admin.getId(), sectionPermissons); + + 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 { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!currentUserId.equals(adminRequest.getAdminId())) { + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't update profile") + .build(); + } + } + + 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 { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't delete profile") + .build(); + } + + 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", + "get_permissions_by_id", "update_permissions")) + .build(); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.INVALID_ACTION.getCode()) + .messageDesc(MessageCode.INVALID_ACTION.getDescription()) + .messageBody(response) + .build(); + } + + private BaseResponse handleGetPermissionsById(BaseRequest request, AdminUserRequest adminRequest) { + try { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!currentUserId.equals(adminRequest.getAdminId())) { + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't update profile") + .build(); + } + } + + Long userId = adminRequest.getAdminId(); + AdminUser admin = adminUserService.findById(userId); + + Map sectionPermissions = permissionService.getAllPermissions(userId); + + Map permissionsMap = new HashMap<>(); + sectionPermissions.forEach((section, value) -> + permissionsMap.put(section.name().toLowerCase(), value)); + + AdminUserResponse response = AdminUserResponse.builder() + .id(admin.getId()) + .fullName(admin.getFullName()) + .email(admin.getEmail()) + .isActive(admin.getIsActive()) + .isSuperAdmin(AdminPermission.isSuperAdmin(admin.getPermissions())) + .createdAt(admin.getCreatedAt().toString()) + .permissions(permissionsMap) + .build(); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(response) + .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 handleUpdatePermissions(BaseRequest request, AdminUserRequest adminRequest) { + try { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.USERS)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc("You can't update profile") + .build(); + } + + Long userId = adminRequest.getAdminId(); + Map permissionsMap = adminRequest.getPermissions(); + + if (permissionsMap == null || permissionsMap.isEmpty()) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_IS_EMPTY.getCode()) + .messageDesc("Permissions map is required") + .build(); + } + + Map permissions = new HashMap<>(); + + for (Map.Entry entry : permissionsMap.entrySet()) { + try { + AdminSection section = AdminSection.valueOf(entry.getKey().toUpperCase()); + permissions.put(section, entry.getValue()); + } catch (IllegalArgumentException e) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.INVALID_ACTION.getCode()) + .messageDesc("Invalid section: " + entry.getKey() + + ". Valid sections: " + Arrays.toString(AdminSection.values())) + .build(); + } + } + + permissionService.setAllPermissions(userId, permissions); + + AdminUser admin = adminUserService.findById(userId); + Map updatedPermissions = permissionService.getAllPermissions(userId); + + Map updatedPermissionsMap = new HashMap<>(); + updatedPermissions.forEach((section, value) -> + updatedPermissionsMap.put(section.name().toLowerCase(), value)); + + AdminUserResponse response = AdminUserResponse.builder() + .id(admin.getId()) + .fullName(admin.getFullName()) + .email(admin.getEmail()) + .isActive(admin.getIsActive()) + .isSuperAdmin(AdminPermission.isSuperAdmin(admin.getPermissions())) + .createdAt(admin.getCreatedAt().toString()) + .permissions(updatedPermissionsMap) + .build(); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc("Permissions updated successfully") + .messageBody(response) + .build(); + } catch (AdminUserNotFoundException e) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.ADMIN_NOT_FOUND.getCode()) + .messageDesc(MessageCode.ADMIN_NOT_FOUND.getDescription()) + .build(); + } + } + + private AdminUserResponse convertToResponse(AdminUser admin) { + Map sectionPermissions = permissionService.getAllPermissions(admin.getId()); + + Map permissionsMap = new HashMap<>(); + sectionPermissions.forEach((section, value) -> + permissionsMap.put(section.name().toLowerCase(), value)); + + return AdminUserResponse.builder() + .id(admin.getId()) + .fullName(admin.getFullName()) + .email(admin.getEmail()) + .isActive(admin.getIsActive()) + .isSuperAdmin(AdminPermission.isSuperAdmin(admin.getPermissions())) + .createdAt(admin.getCreatedAt().toString()) + .permissions(permissionsMap) + .activePassword(admin.getIsActivePassword()) + .build(); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/handler/CreateAdminTemplateHandler.java b/src/main/java/ru/soune/nocopy/adminpanel/handler/CreateAdminTemplateHandler.java new file mode 100644 index 0000000..e8839ec --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/handler/CreateAdminTemplateHandler.java @@ -0,0 +1,278 @@ +package ru.soune.nocopy.adminpanel.handler; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.adminpanel.dto.*; +import ru.soune.nocopy.adminpanel.entity.AdminCreateTemplate; +import ru.soune.nocopy.adminpanel.entity.AdminSection; +import ru.soune.nocopy.adminpanel.service.AdminCreateTemplateService; +import ru.soune.nocopy.adminpanel.service.AdminSectionPermissionService; +import ru.soune.nocopy.adminpanel.util.MessageCode; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +@Slf4j +public class CreateAdminTemplateHandler implements RequestHandler { + + private final ObjectMapper objectMapper; + + private final AdminCreateTemplateService adminCreateTemplateService; + + private final AdminSectionPermissionService adminSectionPermissionService; + + private final HttpServletRequest httpServletRequest; + + @Override + public BaseResponse handle(BaseRequest request) { + AdminCreateTemplateRequest adminCreateTemplateRequest = objectMapper.convertValue(request.getMessageBody(), + AdminCreateTemplateRequest.class); + String action = adminCreateTemplateRequest.getAction(); + + return switch (action) { + case "create" -> handleCreate(request, adminCreateTemplateRequest); + case "update" -> handleUpdate(request, adminCreateTemplateRequest); + case "delete" -> handleDelete(request, adminCreateTemplateRequest); + case "get_all" -> handleGetAll(request); + case "get_by_id" -> handleGetById(request, adminCreateTemplateRequest); + default -> handleInvalidAction(request, action); + }; + } + + private BaseResponse handleDelete(BaseRequest request, AdminCreateTemplateRequest adminCreateTemplateRequest) { + Long adminCreateTemplateRequestId = adminCreateTemplateRequest.getId(); + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (adminCreateTemplateRequestId == null || !adminCreateTemplateService.existById(adminCreateTemplateRequestId)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.TEMPLATE_NOT_FOUND.getCode()) + .messageDesc(MessageCode.TEMPLATE_NOT_FOUND.getDescription()) + .messageBody(Map.of("id", adminCreateTemplateRequest.getId())) + .build(); + } + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.STAFF)) { + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc(MessageCode.PERMISSION_DENIED.getDescription()) + .messageBody(Map.of("currentUserId", currentUserId)) + .build(); + } + + adminCreateTemplateService.deleteTemplate(adminCreateTemplateRequestId); + + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(Map.of("id", adminCreateTemplateRequestId)) + .build(); + } + + private BaseResponse handleUpdate(BaseRequest request, AdminCreateTemplateRequest adminCreateTemplateRequest) { + Map permissionsMap = adminCreateTemplateRequest.getPermissions(); + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (permissionsMap == null || permissionsMap.isEmpty()) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.TEMPLATE_NOT_FOUND.getCode()) + .messageDesc(MessageCode.TEMPLATE_NOT_FOUND.getDescription()) + .messageBody(Map.of("id", adminCreateTemplateRequest.getId())) + .build(); + } + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.STAFF)) { + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc(MessageCode.PERMISSION_DENIED.getDescription()) + .messageBody(Map.of("currentUserId", currentUserId)) + .build(); + } + + ObjectNode node = objectMapper.createObjectNode(); + + for (Map.Entry entry: permissionsMap.entrySet()) { + node.put(entry.getKey(), entry.getValue()); + } + + Boolean changePassword = adminCreateTemplateRequest.getChangePassword(); + + AdminCreateTemplate adminTemplate = + adminCreateTemplateService.updateCreateTemplate(node.toString(), changePassword, + adminCreateTemplateRequest.getId(), adminCreateTemplateRequest.getName()); + + if (adminTemplate == null) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.TEMPLATE_NOT_FOUND.getCode()) + .messageDesc(MessageCode.TEMPLATE_NOT_FOUND.getDescription()) + .messageBody(Map.of("id", adminCreateTemplateRequest.getId())) + .build(); + } + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(convertToResponse(adminTemplate)) + .build(); + } + + private BaseResponse handleCreate(BaseRequest request, AdminCreateTemplateRequest adminCreateTemplateRequest) { + Map permissionsMap = adminCreateTemplateRequest.getPermissions(); + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (permissionsMap == null || permissionsMap.isEmpty()) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_IS_EMPTY.getCode()) + .messageDesc("Permissions map is required") + .build(); + } + + + if (!adminSectionPermissionService.canWrite(currentUserId, + AdminSection.STAFF)) { + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc(MessageCode.PERMISSION_DENIED.getDescription()) + .messageBody(Map.of("currentUserId", currentUserId)) + .build(); + } + + ObjectNode node = objectMapper.createObjectNode(); + + for (Map.Entry entry: permissionsMap.entrySet()) { + node.put(entry.getKey(), entry.getValue()); + } + + Boolean changePassword = adminCreateTemplateRequest.getChangePassword(); + + AdminCreateTemplate adminTemplate = + adminCreateTemplateService.createAdminTemplate(node.toString(), changePassword, + adminCreateTemplateRequest.getName()); + + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(convertToResponse(adminTemplate)) + .build(); + } + + private BaseResponse handleGetAll(BaseRequest request) { + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.STAFF)) { + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc(MessageCode.PERMISSION_DENIED.getDescription()) + .messageBody(Map.of("currentUserId", currentUserId)) + .build(); + } + + List templateResponses = adminCreateTemplateService.getAllTemplates() + .stream() + .map(this::convertToResponse) + .toList(); + + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(templateResponses) + .build(); + } + + private BaseResponse handleGetById(BaseRequest request, AdminCreateTemplateRequest adminCreateTemplateRequest) { + Long id = adminCreateTemplateRequest.getId(); + Long currentUserId = (Long) httpServletRequest.getAttribute("userId"); + + if (id == null || !adminCreateTemplateService.existById(id)) { + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.TEMPLATE_NOT_FOUND.getCode()) + .messageDesc(MessageCode.TEMPLATE_NOT_FOUND.getDescription()) + .messageBody(Map.of("id", adminCreateTemplateRequest.getId())) + .build(); + } + + if (!adminSectionPermissionService.canRead(currentUserId, + AdminSection.STAFF)) { + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.PERMISSION_DENIED.getCode()) + .messageDesc(MessageCode.PERMISSION_DENIED.getDescription()) + .messageBody(Map.of("currentUserId", currentUserId)) + .build(); + } + + AdminCreateTemplate template = adminCreateTemplateService.getTemplateById(adminCreateTemplateRequest.getId()); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.SUCCESS.getCode()) + .messageDesc(MessageCode.SUCCESS.getDescription()) + .messageBody(convertToResponse(template)) + .build(); + } + + private BaseResponse handleInvalidAction(BaseRequest request, String action) { + ActionResponse response = ActionResponse.builder() + .action(action) + .availableActions(Arrays.asList("create", "update", "delete", "get_all", "get_by_id")) + .build(); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.INVALID_ACTION.getCode()) + .messageDesc(MessageCode.INVALID_ACTION.getDescription()) + .messageBody(response) + .build(); + } + + private AdminCreateTemplateResponse convertToResponse(AdminCreateTemplate adminTemplate) { + Map permissionsMap = null; + try { + permissionsMap = objectMapper.readValue( + adminTemplate.getPermission(), + new TypeReference>() {} + ); + } catch (Exception e) { + log.error("Failed to parse permissions", e); + } + + return AdminCreateTemplateResponse.builder() + .id(adminTemplate.getId()) + .changePassword(adminTemplate.getChangePassword()) + .permissions(permissionsMap) + .name(adminTemplate.getName()) + .build(); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/handler/LoginHandler.java b/src/main/java/ru/soune/nocopy/adminpanel/handler/LoginHandler.java new file mode 100644 index 0000000..26a2ffa --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/handler/LoginHandler.java @@ -0,0 +1,127 @@ +package ru.soune.nocopy.adminpanel.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +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.AdminSection; +import ru.soune.nocopy.adminpanel.entity.AdminUser; +import ru.soune.nocopy.adminpanel.exceptions.AdminUserNotFoundException; +import ru.soune.nocopy.adminpanel.permission.AdminPermission; +import ru.soune.nocopy.adminpanel.service.AdminSectionPermissionService; +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.HashMap; +import java.util.Map; + +@Component +@Slf4j +@RequiredArgsConstructor +public class LoginHandler implements RequestHandler { + private final ObjectMapper objectMapper; + + private final AdminUserService adminUserService; + + private final JwtUtil jwtUtil; + + private final AdminSectionPermissionService permissionService; + + @Override + public BaseResponse handle(BaseRequest request) { + AdminUserRequest adminRequest = objectMapper.convertValue(request.getMessageBody(), AdminUserRequest.class); + String action = adminRequest.getAction(); + + return switch (action) { + case "login" -> handleLogin(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 AdminUserResponse convertToResponse(AdminUser admin) { + Map sectionPermissions = permissionService.getAllPermissions(admin.getId()); + + Map permissionsMap = new HashMap<>(); + sectionPermissions.forEach((section, value) -> + permissionsMap.put(section.name().toLowerCase(), value)); + + return AdminUserResponse.builder() + .id(admin.getId()) + .fullName(admin.getFullName()) + .email(admin.getEmail()) + .isActive(admin.getIsActive()) + .isSuperAdmin(AdminPermission.isSuperAdmin(admin.getPermissions())) + .createdAt(admin.getCreatedAt().toString()) + .permissions(permissionsMap) + .activePassword(admin.getIsActivePassword()) + .build(); + } + + private BaseResponse handleInvalidAction(BaseRequest request, String action) { + ActionResponse response = ActionResponse.builder() + .action(action) + .availableActions(Arrays.asList( + "login")) + .build(); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageCode(MessageCode.INVALID_ACTION.getCode()) + .messageDesc(MessageCode.INVALID_ACTION.getDescription()) + .messageBody(response) + .build(); + } + +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/handler/RequestHandler.java b/src/main/java/ru/soune/nocopy/adminpanel/handler/RequestHandler.java new file mode 100644 index 0000000..260fdbc --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/handler/RequestHandler.java @@ -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; +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/permission/AdminPermission.java b/src/main/java/ru/soune/nocopy/adminpanel/permission/AdminPermission.java new file mode 100644 index 0000000..e2728b3 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/permission/AdminPermission.java @@ -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); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/permission/PermissionChecker.java b/src/main/java/ru/soune/nocopy/adminpanel/permission/PermissionChecker.java new file mode 100644 index 0000000..29d600d --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/permission/PermissionChecker.java @@ -0,0 +1,41 @@ +package ru.soune.nocopy.adminpanel.permission; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.adminpanel.entity.AdminSection; +import ru.soune.nocopy.adminpanel.entity.AdminUser; + +import ru.soune.nocopy.adminpanel.service.AdminSectionPermissionService; + +@Component +@RequiredArgsConstructor +public class PermissionChecker { + + private final AdminSectionPermissionService sectionPermissionService; + + 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"); + } + } + + public boolean canReadSection(AdminUser admin, AdminSection section) { + if (admin == null || !admin.getIsActive()) return false; + if (isSuperAdmin(admin)) return true; + + return sectionPermissionService.canRead(admin.getId(), section); + } + + public boolean canWriteSection(AdminUser admin, AdminSection section) { + if (admin == null || !admin.getIsActive()) return false; + if (isSuperAdmin(admin)) return true; + + return sectionPermissionService.canWrite(admin.getId(), section); + } +} + diff --git a/src/main/java/ru/soune/nocopy/adminpanel/permission/SectionPermissions.java b/src/main/java/ru/soune/nocopy/adminpanel/permission/SectionPermissions.java new file mode 100644 index 0000000..a42c186 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/permission/SectionPermissions.java @@ -0,0 +1,15 @@ +package ru.soune.nocopy.adminpanel.permission; + +public class SectionPermissions { + public static final int NONE = 0b00; + public static final int READ = 0b01; + public static final int WRITE = 0b11; + + public static boolean canRead(int permission) { + return (permission & READ) == READ; + } + + public static boolean canWrite(int permission) { + return (permission & WRITE) == WRITE; + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminCreateTemplateRepository.java b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminCreateTemplateRepository.java new file mode 100644 index 0000000..91a5040 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminCreateTemplateRepository.java @@ -0,0 +1,11 @@ +package ru.soune.nocopy.adminpanel.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.soune.nocopy.adminpanel.entity.AdminCreateTemplate; + +@Repository +public interface AdminCreateTemplateRepository extends JpaRepository { + + boolean existsById(Long id); +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminSectionPermissionRepository.java b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminSectionPermissionRepository.java new file mode 100644 index 0000000..0debab7 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminSectionPermissionRepository.java @@ -0,0 +1,12 @@ +package ru.soune.nocopy.adminpanel.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.soune.nocopy.adminpanel.entity.AdminSectionPermission; + +import java.util.Optional; + +@Repository +public interface AdminSectionPermissionRepository extends JpaRepository { + Optional findByUserId(Long userId); +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminUserRepository.java b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminUserRepository.java new file mode 100644 index 0000000..b382e9f --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/repository/AdminUserRepository.java @@ -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 { + Optional findByEmail(String email); + + boolean existsByEmail(String email); +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/service/AdminCreateTemplateService.java b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminCreateTemplateService.java new file mode 100644 index 0000000..c996aec --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminCreateTemplateService.java @@ -0,0 +1,65 @@ +package ru.soune.nocopy.adminpanel.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.adminpanel.entity.AdminCreateTemplate; +import ru.soune.nocopy.adminpanel.repository.AdminCreateTemplateRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AdminCreateTemplateService { + + private final AdminCreateTemplateRepository adminCreateTemplateRepository; + + public AdminCreateTemplate createAdminTemplate(String permissionsMap, Boolean changePassword, String name) { + AdminCreateTemplate adminCreateTemplate = AdminCreateTemplate.builder() + .permission(permissionsMap) + .changePassword(changePassword) + .name(name) + .build(); + + return adminCreateTemplateRepository.save(adminCreateTemplate); + } + + public AdminCreateTemplate updateCreateTemplate(String permissionsMap, Boolean changePassword, Long id, String name) { + AdminCreateTemplate adminCreateTemplate = adminCreateTemplateRepository.findById(id).orElse(null); + + if (adminCreateTemplate == null) { + return null; + } + + if (!permissionsMap.isEmpty()) { + adminCreateTemplate.setPermission(permissionsMap); + } + + if (changePassword != null) { + adminCreateTemplate.setChangePassword(changePassword); + } + + if (name != null) { + adminCreateTemplate.setName(name); + } + + return adminCreateTemplateRepository.save(adminCreateTemplate); + } + + public void deleteTemplate(Long id) { + adminCreateTemplateRepository.deleteById(id); + } + + public boolean existById(Long id) { + return adminCreateTemplateRepository.existsById(id); + } + + public List getAllTemplates() { + return adminCreateTemplateRepository.findAll(); + } + + public AdminCreateTemplate getTemplateById(Long id) { + return adminCreateTemplateRepository.findById(id).orElse(null); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/service/AdminSectionPermissionService.java b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminSectionPermissionService.java new file mode 100644 index 0000000..2d0ee24 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminSectionPermissionService.java @@ -0,0 +1,108 @@ +package ru.soune.nocopy.adminpanel.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.soune.nocopy.adminpanel.entity.AdminSectionPermission; +import ru.soune.nocopy.adminpanel.entity.AdminSection; +import ru.soune.nocopy.adminpanel.permission.SectionPermissions; +import ru.soune.nocopy.adminpanel.repository.AdminSectionPermissionRepository; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class AdminSectionPermissionService { + + private final AdminSectionPermissionRepository repository; + + @Transactional + public AdminSectionPermission getOrCreate(Long userId) { + return repository.findByUserId(userId) + .orElseGet(() -> { + AdminSectionPermission permission = new AdminSectionPermission(); + permission.setUserId(userId); + return repository.save(permission); + }); + } + + public boolean canRead(Long userId, AdminSection section) { + AdminSectionPermission permission = getOrCreate(userId); + int value = getSectionValue(permission, section); + return SectionPermissions.canRead(value); + } + + public boolean canWrite(Long userId, AdminSection section) { + AdminSectionPermission permission = getOrCreate(userId); + int value = getSectionValue(permission, section); + return SectionPermissions.canWrite(value); + } + + @Transactional + public void setPermission(Long userId, AdminSection section, int permissionValue) { + AdminSectionPermission permission = getOrCreate(userId); + setSectionValue(permission, section, permissionValue); + repository.save(permission); + } + + @Transactional + public void setAllPermissions(Long userId, Map permissions) { + AdminSectionPermission permission = getOrCreate(userId); + permissions.forEach((section, value) -> setSectionValue(permission, section, value)); + + repository.save(permission); + } + + public Map getAllPermissions(Long userId) { + AdminSectionPermission permission = getOrCreate(userId); + Map result = new HashMap<>(); + + for (AdminSection section : AdminSection.values()) { + result.put(section, getSectionValue(permission, section)); + } + + return result; + } + + private int getSectionValue(AdminSectionPermission permission, AdminSection section) { + return switch (section) { + case DASH -> permission.getDash(); + case USERS -> permission.getUsers(); + case STAFF -> permission.getStaff(); + case SUBSCRIPTIONS -> permission.getSubscriptions(); + case TARIFFS -> permission.getTariffs(); + case CONTENT_MODERATION -> permission.getContentModeration(); + case COMPLAINTS -> permission.getComplaints(); + case CLAIMS -> permission.getClaims(); + case MAILINGS -> permission.getMailings(); + case API -> permission.getApi(); + case MONEY -> permission.getMoney(); + case KYC -> permission.getKyc(); + case AGREEMENTS -> permission.getAgreements(); + }; + } + + private void setSectionValue(AdminSectionPermission permission, AdminSection section, int value) { + if (value == SectionPermissions.WRITE) { + value = SectionPermissions.WRITE; + } + + switch (section) { + case DASH -> permission.setDash(value); + case USERS -> permission.setUsers(value); + case STAFF -> permission.setStaff(value); + case SUBSCRIPTIONS -> permission.setSubscriptions(value); + case TARIFFS -> permission.setTariffs(value); + case CONTENT_MODERATION -> permission.setContentModeration(value); + case COMPLAINTS -> permission.setComplaints(value); + case CLAIMS -> permission.setClaims(value); + case MAILINGS -> permission.setMailings(value); + case API -> permission.setApi(value); + case MONEY -> permission.setMoney(value); + case KYC -> permission.setKyc(value); + case AGREEMENTS -> permission.setAgreements(value); + } + } +} + diff --git a/src/main/java/ru/soune/nocopy/adminpanel/service/AdminUserService.java b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminUserService.java new file mode 100644 index 0000000..bb43bee --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/service/AdminUserService.java @@ -0,0 +1,106 @@ +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.exceptions.WrongPassword; +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 changePassword, 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.setIsActivePassword(!changePassword); + 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 findAll() { + return adminUserRepository.findAll(); + } + + @Transactional + public AdminUser update(Long id, String fullName, String email, String oldPassword, String newPassword) { + 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 (newPassword != null && oldPassword != null) { + if (passwordEncoder.matches(oldPassword, admin.getPassword())) { + admin.setPassword(passwordEncoder.encode(newPassword)); + admin.setIsActivePassword(true); + } else { + throw new WrongPassword("Old password not equal current"); + } + } + + 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); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/util/JwtAuthFilter.java b/src/main/java/ru/soune/nocopy/adminpanel/util/JwtAuthFilter.java new file mode 100644 index 0000000..5b78e9b --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/util/JwtAuthFilter.java @@ -0,0 +1,64 @@ +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.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import ru.soune.nocopy.adminpanel.dto.BaseResponse; + +import java.io.IOException; +import java.util.List; + +@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; + } + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(jwtUtil.extractEmail(token), null, + List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))); + SecurityContextHolder.getContext().setAuthentication(authentication); + + request.setAttribute("userId", jwtUtil.extractUserId(token)); + request.setAttribute("email", jwtUtil.extractEmail(token)); + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/util/JwtUtil.java b/src/main/java/ru/soune/nocopy/adminpanel/util/JwtUtil.java new file mode 100644 index 0000000..129be81 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/util/JwtUtil.java @@ -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; + } + } +} diff --git a/src/main/java/ru/soune/nocopy/adminpanel/util/MessageCode.java b/src/main/java/ru/soune/nocopy/adminpanel/util/MessageCode.java new file mode 100644 index 0000000..eee9f6b --- /dev/null +++ b/src/main/java/ru/soune/nocopy/adminpanel/util/MessageCode.java @@ -0,0 +1,26 @@ +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"), + TEMPLATE_NOT_FOUND(4, "Template not found or id is null"), + EMAIL_ALREADY_EXISTS(2, "Email already exists"), + WRONG_PASSWORD(2, "Wrong password"), + INVALID_CREDENTIALS(2, "Invalid credentials"), + PERMISSION_DENIED(2, "Permission denied"), + ADMIN_INACTIVE(2, "Admin is inactive"), + PERMISSION_IS_EMPTY(2, "Permission is empty"); + + private final int code; + private final String description; + + MessageCode(int code, String description) { + this.code = code; + this.description = description; + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4a86184..a8ecf31 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,12 +1,29 @@ spring: + cloud: + compatibility-verifier: + enabled: false datasource: - url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} - username: ${POSTGRES_USER} - password: ${POSTGRES_PASSWORD} + url: jdbc:postgresql://${POSTGRES_HOST:db}:${POSTGRES_PORT:5432}/${POSTGRES_DB:admin_db} + username: ${POSTGRES_USER:admin} + password: ${POSTGRES_PASSWORD:adminDbApp} jpa: hibernate: - ddl-auto: validate + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + format_sql: true server: - port: 8080 \ No newline at end of file + port: ${SERVER_PORT:8082} + +app: + internal-api-key: "tljzkXiEYF1klSHuG2hPZKjx6EsBX8RQP6UrzMdQanSKbRYuHuOBwrXejZkn7V4FICvIahoDmYD2hjNBPw61NFbGIt4scOzwZpyCiXEa1YKLAeJSPso4S43LIZlKjO4S" + +jwt: + secret: Z29nb3Bvd2VycmFuZ2VydHVtYmF5YW1iZjMyNDIyMjh3aW5lcndpbmVy + expiration: 3600000 + +nocopy-dashboard: + url: http://172.17.0.1:3001 \ No newline at end of file