dev #1

Open
backdev wants to merge 47 commits from dev into main
44 changed files with 2488 additions and 60 deletions
+5 -5
View File
@@ -1,8 +1,8 @@
POSTGRES_DB=no_copy_ POSTGRES_DB=admin_db
POSTGRES_USER=ncp_db POSTGRES_USER=admin
POSTGRES_PASSWORD=ncpDbApp POSTGRES_PASSWORD=adminDbApp
POSTGRES_PORT=5432 POSTGRES_PORT=5454
POSTGRES_HOST=postgres POSTGRES_HOST=db
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PORT=6379 REDIS_PORT=6379
+1 -1
View File
@@ -9,6 +9,6 @@ FROM eclipse-temurin:21-jre
WORKDIR /app WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080 EXPOSE 8082
CMD ["java", "-jar", "app.jar"] CMD ["java", "-jar", "app.jar"]
+11
View File
@@ -20,6 +20,11 @@ configurations {
} }
} }
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.0"
}
}
repositories { repositories {
mavenCentral() mavenCentral()
@@ -32,6 +37,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.security:spring-security-crypto:6.5.3'
implementation("io.jsonwebtoken:jjwt:0.13.0")
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
@@ -39,5 +46,9 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 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")
} }
+32 -11
View File
@@ -1,22 +1,43 @@
version: '3.9' version: '3.9'
services: services:
admin: db:
build: . image: postgres:17
container_name: admin-backend container_name: admin-db
ports:
- "8082:8080"
environment: environment:
SPRING_PROFILES_ACTIVE: prod POSTGRES_DB: admin_db
POSTGRES_HOST: db POSTGRES_USER: admin
POSTGRES_PORT: 5432 POSTGRES_PASSWORD: adminDbApp
POSTGRES_DB: no_copy_ ports:
POSTGRES_USER: ${POSTGRES_USER} - "5454:5432"
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes:
- postgres_data:/var/lib/postgresql/data
networks: networks:
- app-network - app-network
restart: unless-stopped 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: networks:
app-network: app-network:
external: true external: true
+169 -38
View File
@@ -2,68 +2,199 @@ pipeline {
agent any agent any
parameters { parameters {
string(name: 'BRANCH', defaultValue: 'dev', description: 'Ветка для деплоя') string(
string(name: 'SERVER', defaultValue: '92.242.61.23', description: 'Сервер для деплоя') 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 { stages {
stage('Git pull') { stage('Git pull') {
steps { steps {
cleanWs() 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 { steps {
script { script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim() def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([ withCredentials([
usernamePassword(credentialsId: 'server-root-password', usernameVariable: 'SSH_USER', passwordVariable: 'SSH_PASS'), usernamePassword(
string(credentialsId: 'DB_USER', variable: 'DB_USER'), credentialsId: 'server-root-password',
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD') usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER ' sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
cd /opt/deployments/admin-panel || mkdir -p /opt/deployments/admin-panel && cd /opt/deployments/admin-panel cd /opt/deployments/admin-panel-${params.PORT}
echo "1. Остановка старого контейнера..." echo '1. Проверяем/создаем сеть ${params.NETWORK}...'
docker stop admin-backend 2>/dev/null || echo "Контейнер не найден" if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker rm admin-backend 2>/dev/null || echo "Контейнер не найден" docker network create ${params.NETWORK}
echo 'Сеть ${params.NETWORK} создана'
else
echo 'Сеть ${params.NETWORK} уже существует'
fi
echo "2. Копирование файлов..." echo '2. Останавливаем старые контейнеры...'
rsync -av --delete --exclude=.git --exclude=build . $SSH_USER@$SERVER:/opt/deployments/admin-panel/ docker stop admin-panel-${params.PORT} 2>/dev/null || echo 'Контейнер приложения не найден'
docker rm admin-panel-${params.PORT} 2>/dev/null || echo 'Контейнер приложения не найден'
echo "3. Сборка образа..." docker stop admin-db-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker build --no-cache -t admin-backend:latest . docker rm admin-db-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
echo "4. Запуск контейнера..." echo '3. Запускаем базу данных...'
docker run -d \ docker run -d \\
--name admin-backend \ --name admin-db-${params.PORT} \\
--network app-network \ --network ${params.NETWORK} \\
--network-alias admin \ --network-alias db \\
-p 8082:8082 \ -p ${params.DB_PORT}:5432 \\
-v uploads_data:/data/uploads:rw \ -v admin_db_data_${params.PORT}:/var/lib/postgresql/data \\
-e POSTGRES_DB=no_copy_ \ -e POSTGRES_DB=admin_db \\
-e POSTGRES_USER=$DB_USER \ -e POSTGRES_USER=admin \\
-e POSTGRES_PASSWORD=$DB_PASSWORD \ -e POSTGRES_PASSWORD=adminDbApp \\
-e POSTGRES_PORT=5432 \ --restart unless-stopped \\
-e POSTGRES_HOST=db \ postgres:16
-e BUILD_TIME='${buildTime}' \
--restart unless-stopped \
admin-backend:latest
echo "5. Проверка health..." echo '4. Ждем готовности БД...'
if curl -s -f http://localhost:8082/actuator/health; then sleep 5
echo "Admin backend работает" until docker exec admin-db-${params.PORT} pg_isready -U admin; do
else echo 'Waiting for database...'
echo "Проблема с запуском admin-backend" sleep 2
fi done
' echo 'База данных готова'
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"
}
}
} }
@@ -1,9 +1,11 @@
package ru.soune.nocopy.adminpanel; package ru.soune.nocopy.adminpanel;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
@EnableFeignClients
public class AdminPanelApplication { public class AdminPanelApplication {
public static void main(String[] args) { public static void main(String[] args) {
@@ -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<String, Object> getUsersWithPagination(@RequestBody BaseRequest request);
@PostMapping("api/internal/data-info")
Map<String, Object> internalDataInfo(@RequestBody BaseRequest request);
@PostMapping("api/internal/data-control")
Map<String, Object> internalDataControl(@RequestBody BaseRequest request);
@GetMapping("api/internal/data-download/{fileId}")
ResponseEntity<byte[]> download(@PathVariable String fileId);
}
@@ -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<Integer, RequestHandler> handlers(AdminUserHandler adminUserHandler, CreateAdminTemplateHandler createAdminTemplateHandler) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(100, adminUserHandler);
map.put(200, createAdminTemplateHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> loginHandlers(LoginHandler loginHandler) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(101, loginHandler);
return map;
}
}
@@ -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);
};
}
}
@@ -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();
}
}
@@ -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<Integer, RequestHandler> handlers;
private final Map<Integer, RequestHandler> loginHandlers;
private final DashBoardAppClient dashBoardAppClient;
private final AdminSectionPermissionService adminSectionPermissionService;
private final HttpServletRequest httpServletRequest;
@PostMapping
public ResponseEntity<BaseResponse> handleRequest(@RequestBody BaseRequest request) {
log.info("Received request: msg_id={}, version={}", request.getMsgId(), request.getVersion());
try {
RequestHandler handler = handlers.get(request.getMsgId());
if (handler == null) {
log.warn("No handler found for msg_id: {}", request.getMsgId());
return ResponseEntity.ok(
BaseResponse.builder()
.msgId(request.getMsgId())
.messageCode(MessageCode.INVALID_ACTION.getCode())
.messageDesc("No handler for msg_id: " + request.getMsgId())
.build()
);
}
BaseResponse response = handler.handle(request);
log.info("Request processed successfully: msg_id={}", request.getMsgId());
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("Error processing request: msg_id={}", request.getMsgId(), e);
return ResponseEntity.ok(
BaseResponse.builder()
.msgId(request.getMsgId())
.messageCode(MessageCode.INVALID_ACTION.getCode())
.messageDesc("Internal server error: " + e.getMessage())
.build()
);
}
}
@PostMapping("/login")
public ResponseEntity<BaseResponse> 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<BaseResponse> 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<String, Object> 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<BaseResponse> 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<String, Object> 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<byte[]> 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();
}
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.adminpanel.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class ActionResponse {
@JsonProperty("action")
private String action;
@JsonProperty("available_actions")
private List<String> availableActions;
}
@@ -0,0 +1,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<String, Integer> permissions;
@JsonProperty("change_password")
private Boolean changePassword;
@JsonProperty("action")
private String action;
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String name;
}
@@ -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<String, Integer> permissions;
@JsonProperty("change_password")
private Boolean changePassword;
@JsonProperty("name")
private String name;
}
@@ -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<String, Integer> permissions;
}
@@ -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<String, Integer> permissions;
}
@@ -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;
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.adminpanel.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
@Builder
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse {
@JsonProperty("msg_id")
private Integer msgId;
@JsonProperty("message_code")
private Integer messageCode;
@JsonProperty("message_desc")
private String messageDesc;
@JsonProperty("message_body")
private Object messageBody;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.adminpanel.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class LoginResponse {
@JsonProperty("success")
private Boolean success;
@JsonProperty("token")
private String token;
@JsonProperty("admin")
private AdminUserResponse admin;
}
@@ -0,0 +1,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;
}
@@ -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
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.adminpanel.exceptions;
public class AdminUserNotFoundException extends RuntimeException {
public AdminUserNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.adminpanel.exceptions;
public class EmailAlreadyExistsException extends RuntimeException {
public EmailAlreadyExistsException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.adminpanel.exceptions;
public class WrongPassword extends RuntimeException {
public WrongPassword(String message) {
super(message);
}
}
@@ -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<String, Integer> permissions = adminRequest.getPermissions();
Map<AdminSection, Integer> 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<String, Integer> 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<AdminUserResponse> admins = adminUserService.findAll().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return BaseResponse.builder()
.msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageDesc(MessageCode.SUCCESS.getDescription())
.messageBody(admins)
.build();
}
private BaseResponse handleGetById(BaseRequest request, AdminUserRequest adminRequest) {
try {
AdminUser admin = adminUserService.findById(adminRequest.getAdminId());
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<AdminSection, Integer> 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<AdminSection, Integer> sectionPermissions = permissionService.getAllPermissions(userId);
Map<String, Integer> 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<String, Integer> 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<AdminSection, Integer> permissions = new HashMap<>();
for (Map.Entry<String, Integer> 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<AdminSection, Integer> updatedPermissions = permissionService.getAllPermissions(userId);
Map<String, Integer> 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<AdminSection, Integer> sectionPermissions = permissionService.getAllPermissions(admin.getId());
Map<String, Integer> 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();
}
}
@@ -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<String, Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> 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<AdminCreateTemplateResponse> 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<String, Integer> permissionsMap = null;
try {
permissionsMap = objectMapper.readValue(
adminTemplate.getPermission(),
new TypeReference<Map<String, Integer>>() {}
);
} 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();
}
}
@@ -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<AdminSection, Integer> sectionPermissions = permissionService.getAllPermissions(admin.getId());
Map<String, Integer> 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();
}
}
@@ -0,0 +1,8 @@
package ru.soune.nocopy.adminpanel.handler;
import ru.soune.nocopy.adminpanel.dto.BaseRequest;
import ru.soune.nocopy.adminpanel.dto.BaseResponse;
public interface RequestHandler {
BaseResponse handle(BaseRequest request) throws Exception;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.adminpanel.permission;
public class AdminPermission {
public static final long ADMIN_BIT = 1L;
public static final long SUPER_ADMIN_BIT = 1L << 1;
public static final long DEFAULT_PERMISSIONS = ADMIN_BIT;
public static final long SUPER_ADMIN_PERMISSIONS = ADMIN_BIT | SUPER_ADMIN_BIT;
public static boolean hasPermission(long userPermissions, long permissionBit) {
return (userPermissions & permissionBit) != 0;
}
public static boolean isAdmin(long permissions) {
return hasPermission(permissions, ADMIN_BIT);
}
public static boolean isSuperAdmin(long permissions) {
return hasPermission(permissions, SUPER_ADMIN_BIT);
}
}
@@ -0,0 +1,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);
}
}
@@ -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;
}
}
@@ -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<AdminCreateTemplate, Long> {
boolean existsById(Long id);
}
@@ -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<AdminSectionPermission, Long> {
Optional<AdminSectionPermission> findByUserId(Long userId);
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.adminpanel.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.adminpanel.entity.AdminUser;
import java.util.Optional;
@Repository
public interface AdminUserRepository extends JpaRepository<AdminUser, Long> {
Optional<AdminUser> findByEmail(String email);
boolean existsByEmail(String email);
}
@@ -0,0 +1,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<AdminCreateTemplate> getAllTemplates() {
return adminCreateTemplateRepository.findAll();
}
public AdminCreateTemplate getTemplateById(Long id) {
return adminCreateTemplateRepository.findById(id).orElse(null);
}
}
@@ -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<AdminSection, Integer> permissions) {
AdminSectionPermission permission = getOrCreate(userId);
permissions.forEach((section, value) -> setSectionValue(permission, section, value));
repository.save(permission);
}
public Map<AdminSection, Integer> getAllPermissions(Long userId) {
AdminSectionPermission permission = getOrCreate(userId);
Map<AdminSection, Integer> 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);
}
}
}
@@ -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<AdminUser> 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);
}
}
@@ -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);
}
}
@@ -0,0 +1,64 @@
package ru.soune.nocopy.adminpanel.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtUtil {
@Value("${jwt.secret:Z29nb3Bvd2VycmFuZ2VydHVtYmF5YW1iZjMyNDIyMjh3aW5lcndpbmVy}")
private String secret;
@Value("${jwt.expiration:3600000}")
private Long expiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(String email, Long userId) {
return Jwts.builder()
.setSubject(email)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public String extractEmail(String token) {
return extractClaims(token).getSubject();
}
public Long extractUserId(String token) {
return extractClaims(token).get("userId", Long.class);
}
public boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
public boolean validateToken(String token) {
try {
return !isTokenExpired(token);
} catch (Exception e) {
return false;
}
}
}
@@ -0,0 +1,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;
}
}
+22 -5
View File
@@ -1,12 +1,29 @@
spring: spring:
cloud:
compatibility-verifier:
enabled: false
datasource: datasource:
url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} url: jdbc:postgresql://${POSTGRES_HOST:db}:${POSTGRES_PORT:5432}/${POSTGRES_DB:admin_db}
username: ${POSTGRES_USER} username: ${POSTGRES_USER:admin}
password: ${POSTGRES_PASSWORD} password: ${POSTGRES_PASSWORD:adminDbApp}
jpa: jpa:
hibernate: hibernate:
ddl-auto: validate ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
server: server:
port: 8080 port: ${SERVER_PORT:8082}
app:
internal-api-key: "tljzkXiEYF1klSHuG2hPZKjx6EsBX8RQP6UrzMdQanSKbRYuHuOBwrXejZkn7V4FICvIahoDmYD2hjNBPw61NFbGIt4scOzwZpyCiXEa1YKLAeJSPso4S43LIZlKjO4S"
jwt:
secret: Z29nb3Bvd2VycmFuZ2VydHVtYmF5YW1iZjMyNDIyMjh3aW5lcndpbmVy
expiration: 3600000
nocopy-dashboard:
url: http://172.17.0.1:3001