Merge branch 'dev' into NCBACK-13

This commit is contained in:
vladp
2025-12-18 17:43:35 +07:00
8 changed files with 103 additions and 92 deletions
+7 -1
View File
@@ -29,13 +29,19 @@ dependencies {
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 'org.springframework.security:spring-security-crypto:6.5.3'
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc' runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc'
runtimeOnly 'com.mysql:mysql-connector-j' runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+11 -8
View File
@@ -96,7 +96,7 @@ pipeline {
elif [ -f 'docker-compose.yml' ]; then elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build docker-compose -f docker-compose.yml build
else else
echo 'Exception:don't found docker-compose' echo 'Exception: docker-compose file not found'
exit 1 exit 1
fi fi
" "
@@ -120,6 +120,9 @@ pipeline {
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH} cd /opt/deployments/frontend/${params.BRANCH}
# Проверяем и подключаем к сети app-network если нужно
docker network inspect app-network 2>/dev/null || echo 'Warning: app-network not found'
if [ -f 'docker-compose.yaml' ]; then if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then elif [ -f 'docker-compose.yml' ]; then
@@ -150,16 +153,16 @@ pipeline {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container start' echo 'Container started'
sleep 5 sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000') HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP код: ' \$HTTP_CODE echo 'HTTP code: \$HTTP_CODE'
echo 'Frong start on http://92.242.61.23:2998' echo 'Frontend available on http://92.242.61.23:2998'
else else
echo 'Exception: container don't start' echo 'Exception: container did not start'
docker ps -a | grep frontend docker ps -a | grep frontend
exit 1 exit 1
fi fi
@@ -173,11 +176,11 @@ pipeline {
post { post {
success { success {
echo "Front branch ${params.BRANCH} completed" echo "Front branch ${params.BRANCH} deployment completed"
echo "Use on http://92.242.61.23:2998" echo "Frontend available on http://92.242.61.23:2998"
} }
failure { failure {
echo "Failed" echo "Deployment failed"
} }
} }
} }
+64 -11
View File
@@ -54,15 +54,26 @@ pipeline {
echo 'Step 1: Checking current state...' echo 'Step 1: Checking current state...'
# Смотрим что запущено # Смотрим что запущено
docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}' docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}'
echo 'Step 2: Stopping only application (keeping DB)...' echo 'Step 2: Force cleanup old application...'
# Останавливаем только приложение, БД оставляем работать # Принудительно удаляем старый контейнер и образ
docker stop app-backend 2>/dev/null || echo 'App not running' docker stop app-backend 2>/dev/null || echo 'App not running'
docker rm app-backend 2>/dev/null || echo 'App not found' docker rm app-backend 2>/dev/null || echo 'App not found'
docker rmi app-backend:latest 2>/dev/null || echo 'Image not found'
# Проверяем что БД работает, если нет - запускаем # Удаляем все образы app-backend
docker images --filter 'reference=app-backend*' -q | xargs -r docker rmi 2>/dev/null || echo 'No images to remove'
echo 'Step 3: Verify copied files...'
echo 'Files in directory:'
ls -la
echo ''
echo 'Checking Java sources:'
find . -name '*.java' | head -2
echo 'Step 4: Check if PostgreSQL is running...'
if ! docker ps | grep -q postgres; then if ! docker ps | grep -q postgres; then
echo 'Starting PostgreSQL...' echo 'Starting PostgreSQL...'
docker-compose up -d db docker-compose up -d db
@@ -71,13 +82,37 @@ pipeline {
echo 'PostgreSQL already running' echo 'PostgreSQL already running'
fi fi
echo 'Step 3: Building new application image...' echo 'Step 5: Build application with forced rebuild...'
docker build --no-cache -t app-backend:latest . echo 'Checking Dockerfile exists:'
ls -la Dockerfile 2>/dev/null || echo 'Dockerfile not found, creating simple one'
echo 'Step 4: Starting application...' # Если нет Dockerfile, создаем простой
if [ ! -f Dockerfile ]; then
echo 'Creating simple Dockerfile...'
cat > Dockerfile << 'EOF'
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY . /app/
CMD ["java", "-jar", "app.jar"]
EOF
fi
# Собираем с подробным выводом
echo 'Building Docker image...'
docker build --no-cache --progress=plain -t app-backend:latest . 2>&1 | tail -50
echo 'Step 6: Verify new image...'
echo 'Current app-backend images:'
docker images | grep app-backend
echo 'Image creation time:'
docker inspect app-backend:latest --format='{{.Created}}' 2>/dev/null || echo 'Cannot inspect image'
echo 'Step 7: Starting application with alias...'
docker run -d \\ docker run -d \\
--name app-backend \\ --name app-backend \\
--network app-network \\ --network app-network \\
--network-alias app \\
-p 80:8080 \\ -p 80:8080 \\
-e POSTGRES_DB=no_copy_ \\ -e POSTGRES_DB=no_copy_ \\
-e POSTGRES_USER=postgres \\ -e POSTGRES_USER=postgres \\
@@ -87,18 +122,36 @@ pipeline {
--restart unless-stopped \\ --restart unless-stopped \\
app-backend:latest app-backend:latest
echo 'Step 5: Checking deployment...' echo 'Step 8: Checking deployment...'
sleep 10 sleep 10
echo 'Containers status:' echo 'Containers status:'
docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}' docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.CreatedAt}}'
echo 'Application logs (last 5 lines):' echo 'Checking image inside container:'
docker logs --tail=5 app-backend 2>/dev/null || echo 'Logs not available yet' docker exec app-backend ls -la /app/ 2>/dev/null || echo 'Cannot check container files'
echo 'Application logs (last 10 lines):'
docker logs --tail=10 app-backend 2>/dev/null || echo 'Logs not available yet'
echo 'Step 9: Health check...'
if docker ps | grep -q app-backend; then if docker ps | grep -q app-backend; then
echo 'Container is running'
echo 'Testing application health...'
for i in {1..5}; do
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then
echo 'Health check passed'
echo 'Deployment successful' echo 'Deployment successful'
echo 'Application URL: http://${params.SERVER}:80' echo 'Application URL: http://${params.SERVER}:80'
exit 0
fi
echo 'Waiting for application to start... attempt ' \$i
sleep 5
done
echo 'Health check failed, but container is running'
echo 'Application might be starting slowly'
echo 'Application URL: http://${params.SERVER}:80'
else else
echo 'Application failed to start' echo 'Application failed to start'
docker logs app-backend docker logs app-backend
@@ -3,6 +3,7 @@ package ru.soune.nocopy.dto;
public enum MessageCode { public enum MessageCode {
SUCCESS(0, "Operation successful"), SUCCESS(0, "Operation successful"),
REG_EMAIL_EXISTS(1, "Email already registered"), REG_EMAIL_EXISTS(1, "Email already registered"),
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
INVALID_FIELD(2, "Invalid field"), INVALID_FIELD(2, "Invalid field"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
MSG_ID_NOT_FOUND(4, "Message id not found"), MSG_ID_NOT_FOUND(4, "Message id not found"),
@@ -42,6 +42,7 @@ public class User {
private String password; private String password;
@Size(min = 11, max = 14) @Size(min = 11, max = 14)
@Column(unique = true)
private String phone; private String phone;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@@ -33,13 +33,15 @@ public class RegRequestHandler implements RequestHandler {
public BaseResponse handle(BaseRequest request) throws ValidationException { public BaseResponse handle(BaseRequest request) throws ValidationException {
RegRequest regRequest = objectMapper.convertValue(request.getMessageBody(), RegRequest.class); RegRequest regRequest = objectMapper.convertValue(request.getMessageBody(), RegRequest.class);
if (userRepository.existsByEmail(regRequest.getEmail())) { if (userRepository.existsByEmail(regRequest.getEmail()) || userRepository.existsByPhone(regRequest.getPhone())) {
RegAnswer regAnswer = new RegAnswer(); RegAnswer regAnswer = new RegAnswer();
regAnswer.setFieldErrors(Arrays.asList(Map.of("email", regRequest.getEmail()))); regAnswer.setFieldErrors(Arrays.asList(Map.of("email", regRequest.getEmail())));
regAnswer.setFieldErrors(Arrays.asList(Map.of("phone", regRequest.getPhone())));
throw new NotValidFieldException("User already exists with email: " + regRequest.getEmail(), throw new NotValidFieldException("User already exists with email:" + regRequest.getEmail() + " or phone: " +
new BaseResponse(request.getMsgId(), MessageCode.REG_EMAIL_EXISTS.getCode(), regRequest.getPhone(), new BaseResponse(request.getMsgId(),
MessageCode.REG_EMAIL_EXISTS.getDescription(), regAnswer)); MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getCode(),
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
} }
BindingResult bindingResult = new BeanPropertyBindingResult(regRequest, "regRequest"); BindingResult bindingResult = new BeanPropertyBindingResult(regRequest, "regRequest");
@@ -1,10 +1,14 @@
package ru.soune.nocopy.handler.validator; package ru.soune.nocopy.handler.validator;
import org.apache.commons.validator.routines.DomainValidator;
import org.apache.commons.validator.routines.EmailValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import ru.soune.nocopy.dto.RegRequest; import ru.soune.nocopy.dto.RegRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*; import java.util.*;
@Component @Component
@@ -52,27 +56,6 @@ public class RegRequestValidator implements Validator {
errors.rejectValue("fullName", "fullName.invalid.chars", errors.rejectValue("fullName", "fullName.invalid.chars",
"Name can only contain letters, spaces, hyphens and apostrophes"); "Name can only contain letters, spaces, hyphens and apostrophes");
} }
String[] nameParts = trimmedName.split("\\s+");
if (nameParts.length < 2) {
errors.rejectValue("fullName", "fullName.missing.parts",
"Please enter both first and last name");
}
for (int i = 0; i < nameParts.length; i++) {
String part = nameParts[i];
if (part.length() < 2 && part.length() > 0) {
errors.rejectValue("fullName", "fullName.part.too.short",
"Each name part must be at least 2 characters");
break;
}
if (!Character.isUpperCase(part.charAt(0))) {
errors.rejectValue("fullName", "fullName.capitalization",
"Each name part should start with capital letter");
break;
}
}
} }
private void validateCompanyName(String companyName, Errors errors) { private void validateCompanyName(String companyName, Errors errors) {
@@ -110,64 +93,24 @@ public class RegRequestValidator implements Validator {
private void validateEmail(String email, Errors errors) { private void validateEmail(String email, Errors errors) {
if (email == null || email.trim().isEmpty()) { if (email == null || email.trim().isEmpty()) {
errors.rejectValue("email", "email.is.empty", errors.rejectValue("email", "email.is.empty", "Email must not be empty");
"Email must not be empty");
return; return;
} }
String trimmedEmail = email.trim().toLowerCase(); String trimmedEmail = email.trim().toLowerCase();
if (trimmedEmail.length() > 128) { if (trimmedEmail.length() > 254) {
errors.rejectValue("email", "email.too.long", errors.rejectValue("email", "email.too.long", "Email is too long");
"Email must be less than 128 characters");
return; return;
} }
if (!trimmedEmail.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) { EmailValidator emailValidator = EmailValidator.getInstance(true, true);
errors.rejectValue("email", "email.invalid.format", if (!emailValidator.isValid(trimmedEmail)) {
"Invalid email format"); errors.rejectValue("email", "email.invalid", "Invalid email address");
return; return;
} }
String[] parts = trimmedEmail.split("@");
if (parts.length != 2) {
return;
} }
String localPart = parts[0];
String domain = parts[1];
if (localPart.length() > 64) {
errors.rejectValue("email", "email.local.too.long",
"Email username too long");
}
if (!localPart.matches("^[a-z0-9+_.-]+$")) {
errors.rejectValue("email", "email.local.invalid.chars",
"Email contains invalid characters");
}
validateEmailDomain(domain, errors);
}
private void validateEmailDomain(String domain, Errors errors) {
if (domain.length() > 255) {
errors.rejectValue("email", "email.domain.too.long",
"Email domain too long");
}
String[] domainParts = domain.split("\\.");
if (domainParts.length < 2) {
errors.rejectValue("email", "email.domain.invalid",
"Invalid domain name");
}
String tld = domainParts[domainParts.length - 1];
if (tld.length() < 2) {
errors.rejectValue("email", "email.tld.invalid",
"Invalid domain extension");
}
}
private void validatePhone(String phone, Errors errors) { private void validatePhone(String phone, Errors errors) {
if (phone == null || phone.trim().isEmpty()) { if (phone == null || phone.trim().isEmpty()) {
@@ -1,5 +1,6 @@
package ru.soune.nocopy.repository; package ru.soune.nocopy.repository;
import jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import ru.soune.nocopy.entity.User; import ru.soune.nocopy.entity.User;
@@ -8,4 +9,5 @@ import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
boolean existsByEmail(String email); boolean existsByEmail(String email);
boolean existsByPhone(@Size(min = 11, max = 14) String phone);
} }