1 Commits
Author SHA1 Message Date
backdev 47cde9caa5 Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2025-11-25 13:32:46 +08:00
58 changed files with 61 additions and 1961 deletions
-36
View File
@@ -1,36 +0,0 @@
### MODULES
-api
- register +
- logut +
- login +
- check-demo-status
- check-token-balance
- content
- dismiss-notification
- download
- download-certificate
- download-pdf-tracked
- export-report
- forgor-password
- generate-certificate
- get-pdf-tracking-token
- get-user-data
- login ?
- logaut ?
- notifications-api
- preview-certificate
- register
- reports
- reset-password
- token-manager
- track-pdf-open
- upload
- validate-upload
- verify-video
- violations
infra
-grafana/prom +
- ci/cd
View File
-2
View File
@@ -28,8 +28,6 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.security:spring-security-crypto:6.5.3'
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc'
+3 -119
View File
@@ -13,14 +13,10 @@ services:
volumes:
- pgdata:/var/lib/postgresql/data
container_name: postgres
networks:
app-network:
aliases:
- database
app:
build: .
container_name: app-backend
image: popovtsev/ncp
container_name: no_copy_app
environment:
POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
@@ -30,119 +26,7 @@ services:
depends_on:
- db
ports:
- "80:8080"
networks:
app-network:
aliases:
- app
- backend
- api
grafana:
image: grafana/grafana:10.3.1
container_name: grafana
ports:
- "3000:3000"
depends_on:
prometheus:
condition: service_healthy
loki:
condition: service_healthy
tempo:
condition: service_started
alloy:
condition: service_started
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
GF_METRICS_ENABLED: "true"
volumes:
- ./infrastructure/grafana/provisioning:/etc/grafana/provisioning
- ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards
healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:3000/api/health" ]
interval: 10s
timeout: 5s
retries: 10
networks:
- app-network
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
loki:
image: grafana/loki:2.9.2
container_name: loki
ports:
- "3100:3100"
volumes:
- ./infrastructure/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
tempo:
image: grafana/tempo:2.4.1
container_name: tempo
command: [ "-config.file=/etc/tempo/tempo.yaml" ]
volumes:
- ./infrastructure/tempo/tempo.yaml:/etc/tempo/tempo.yaml
- tempo_data:/var/tempo
ports:
- "3200:3200"
networks:
- app-network
alloy:
image: grafana/alloy:latest
container_name: alloy
user: root
ports:
- "9080:9080"
- "4317:4317"
- "4318:4318"
volumes:
- ./infrastructure/alloy/config.alloy:/etc/alloy/config.alloy:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock
- /var/log:/var/log:ro
environment:
GRAFANA_LOKI_URL: http://loki:3100/loki/api/v1/push
command:
- run
- --server.http.listen-addr=0.0.0.0:9080
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
networks:
- app-network
- "8080:8080"
volumes:
pgdata:
artifacts:
tempo_data:
loki_data:
loki_chunks:
loki_index:
loki_rules:
networks:
app-network:
external: true
driver: bridge
-74
View File
@@ -1,74 +0,0 @@
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
refresh_interval = "5s"
}
discovery.relabel "containers" {
targets = discovery.docker.containers.targets
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container"
}
rule {
target_label = "job"
replacement = "docker"
}
rule {
target_label = "host"
replacement = constants.hostname
}
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "service"
}
rule {
source_labels = ["__meta_docker_container_log_stream"]
target_label = "stream"
}
}
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.containers.targets
relabel_rules = discovery.relabel.containers.rules
forward_to = [loki.write.local.receiver]
}
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
output {
traces = [otelcol.exporter.otlp.tempo.input]
metrics = [otelcol.exporter.prometheus.default.input]
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
tls {
insecure = true
}
}
}
otelcol.exporter.prometheus "default" {
forward_to = []
}
@@ -1,43 +0,0 @@
{
"title": "Postgres Overview",
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "10s",
"panels": [
{
"type": "gauge",
"title": "pg_up",
"targets": [{ "expr": "pg_up", "refId": "A" }],
"gridPos": { "x": 0, "y": 0, "w": 4, "h": 6 }
},
{
"type": "timeseries",
"title": "Active sessions by state",
"targets": [
{ "expr": "sum by (state,instance) (pg_stat_activity_count)", "refId": "A" }
],
"gridPos": { "x": 4, "y": 0, "w": 20, "h": 6 }
},
{
"type": "timeseries",
"title": "Transactions per second (commits+rollbacks)",
"targets": [
{ "expr": "sum(rate(pg_stat_database_xact_commit[1m])) + sum(rate(pg_stat_database_xact_rollback[1m]))", "refId": "A" }
],
"gridPos": { "x": 0, "y": 6, "w": 12, "h": 8 }
},
{
"type": "timeseries",
"title": "Tuples fetched/returned/inserted/updated/deleted",
"targets": [
{ "expr": "sum(rate(pg_stat_database_tup_fetched[1m]))", "legendFormat": "fetched", "refId": "A" },
{ "expr": "sum(rate(pg_stat_database_tup_returned[1m]))", "legendFormat": "returned", "refId": "B" },
{ "expr": "sum(rate(pg_stat_database_tup_inserted[1m]))", "legendFormat": "inserted", "refId": "C" },
{ "expr": "sum(rate(pg_stat_database_tup_updated[1m]))", "legendFormat": "updated", "refId": "D" },
{ "expr": "sum(rate(pg_stat_database_tup_deleted[1m]))", "legendFormat": "deleted", "refId": "E" }
],
"gridPos": { "x": 12, "y": 6, "w": 12, "h": 8 }
}
]
}
@@ -1,20 +0,0 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: false
editable: true
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
isDefault: false
editable: true
@@ -1,11 +0,0 @@
apiVersion: 1
providers:
- name: 'default'
folder: 'Individuals-api'
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: true
@@ -1,20 +0,0 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: false
editable: true
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
isDefault: false
editable: true
-183
View File
@@ -1,183 +0,0 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception:don't found docker-compose'
exit 1
fi
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container start'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP код: ' \$HTTP_CODE
echo 'Frong start on http://92.242.61.23:2998'
else
echo 'Exception: container don't start'
docker ps -a | grep frontend
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Front branch ${params.BRANCH} completed"
echo "Use on http://92.242.61.23:2998"
}
failure {
echo "Failed"
}
}
}
-218
View File
@@ -1,218 +0,0 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/backdev/no-copy.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old container') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
echo "Stop old container by branch ${params.BRANCH}..."
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/${params.BRANCH}
# Stop old container if have docker-compose
if [ -f 'docker-compose.yaml' ] || [ -f 'docker-compose.yml' ]; then
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
fi
# Stop and delete container by name
docker stop app-${params.BRANCH} 2>/dev/null || true
docker rm app-${params.BRANCH} 2>/dev/null || true
echo "Old containder deleted"
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
echo "Copy files on server..."
# Clean mkdir
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "
rm -rf /opt/deployments/${params.BRANCH}/*
mkdir -p /opt/deployments/${params.BRANCH}
"
# Copy all files
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/ 2>/dev/null || true
echo "Files copied"
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
echo "Build on server..."
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
echo "Build Docker for branch ${params.BRANCH}..."
# delete old docker image
docker rmi app:${params.BRANCH} 2>/dev/null || true
# build from branch
if [ -f 'Dockerfile' ]; then
docker build -t app:${params.BRANCH} .
echo "Docker образ app:${params.BRANCH} собран"
fi
# or from docker-compose
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
echo "Build from docker-compose.yaml completed"
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
echo "Build from docker-compose.yml completed"
fi
echo 'Build completed'
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
echo "Start container..."
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
# If have docker-compose - use them
if [ -f 'docker-compose.yaml' ]; then
echo "Start docker-compose.yaml..."
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
echo "Start docker-compose.yml..."
docker-compose -f docker-compose.yml up -d
# If have Dockerfile - start
elif [ -f 'Dockerfile' ]; then
echo "Start app-${params.BRANCH}..."
docker run -d \\
--name app-${params.BRANCH} \\
-p 8080:8080 \\
app:${params.BRANCH}
else
echo "Error:Dockerfile NOT FOUND, docker-compose.yaml or docker-compose.yml"
exit 1
fi
echo 'Container start'
# Wait start
sleep 10
# Check status
echo 'status container:'
if [ -f 'docker-compose.yaml' ] || [ -f 'docker-compose.yml' ]; then
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml ps
else
docker-compose -f docker-compose.yml ps
fi
else
docker ps --filter "name=app-${params.BRANCH}"
fi
echo ''
echo 'Apps start on:'
echo 'http://$SERVER:80'
"
"""
}
}
}
}
}
post {
success {
echo "Deploy branch ${params.BRANCH} completed"
echo "App : http://$SERVER:80"
}
failure {
echo "Deploy failed"
}
}
}
-56
View File
@@ -1,56 +0,0 @@
auth_enabled: false
server:
http_listen_post: 3100
ingester:
lifecycler:
address: 0.0.0.0
ring:
kvstore:
store: summary
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 5m
chunk_retain_period: 30s
max_transfer_retries: 0
schema_config:
configs:
- from: 2018-04-15
store: boltdb
object_store: filesystem
schema: v11
index:
prefix: index_
period: 168h
storage_config:
boltdb:
directory: /loki/index
filesystem:
directory: /loki/chunks
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 8
ingestion_burst_size_md: 10
chunk_storage_config:
max_look_back_period: 0s
table_manager:
chunk_tables_provisioning:
inactive_read_throughput: 0
inactive_write_throughput: 0
provisioned_read_throughput: 0
provisioned_write_throughput: 0
index_tables_provisioning:
inactive_read_throughput: 0
inactive_write_throughput: 0
provisioned_read_throughput: 0
provisioned_write_throughput: 0
retention_deletes_enabled: true
retention_period: 672h
-53
View File
@@ -1,53 +0,0 @@
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: [ "prometheus:9090" ]
- job_name: api
metrics_path: /actuator/prometheus
static_configs:
- targets: [ 'api:8091' ]
- job_name: persons-api
metrics_path: /actuator/prometheus
static_configs:
- targets: [ 'persons-api:8092' ]
- job_name: keycloak
metrics_path: /metrics
static_configs:
- targets: [ 'keycloak:9000' ]
- job_name: grafana
metrics_path: /metrics
static_configs:
- targets: [ 'grafana:3000' ]
- job_name: alloy
metrics_path: /metrics
static_configs:
- targets: [ 'alloy:9080' ]
- job_name: loki
metrics_path: /metrics
static_configs:
- targets: [ 'loki:3100' ]
- job_name: tempo
metrics_path: /metrics
static_configs:
- targets: [ 'tempo:3200' ]
- job_name: person-postgres
static_configs:
- targets: [ 'person-postgres-exporter:9187' ]
- job_name: keycloak-postgres
static_configs:
- targets: [ 'keycloak-postgres-exporter:9187' ]
-22
View File
@@ -1,22 +0,0 @@
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
http:
ingester:
trace_idle_period: 10s
compactor:
compaction:
compacted_block_retention: 48h
storage:
trace:
backend: local
local:
path: /var/tempo/traces
@@ -1,41 +0,0 @@
package ru.soune.no_copy.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(
DateTimeFormatter.ofPattern("dd-MM-yyyy")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(
DateTimeFormatter.ofPattern("dd-MM-yyyy")));
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")));
mapper.registerModule(javaTimeModule);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
@@ -1,35 +0,0 @@
package ru.soune.no_copy.configuration;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "subscription")
@Getter
public class SubscriptionConfig {
private final Map<SubscriptionType, SubscriptionLimits> limits = Map.of(
SubscriptionType.START, new SubscriptionLimits(50, 5.0),
SubscriptionType.BASIC, new SubscriptionLimits(200, 20.0),
SubscriptionType.PRO, new SubscriptionLimits(1000, 100.0),
SubscriptionType.ENTERPRISE, new SubscriptionLimits(-1, 500.0)
);
@Getter
public static class SubscriptionLimits {
private final int monthlyFiles;
private final double storageGB;
public SubscriptionLimits(int monthlyFiles, double storageGB) {
this.monthlyFiles = monthlyFiles;
this.storageGB = storageGB;
}
}
public enum SubscriptionType {
START, BASIC, PRO, ENTERPRISE
}
}
@@ -3,28 +3,24 @@ package ru.soune.no_copy.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.soune.no_copy.dto.AuthResponse;
import ru.soune.no_copy.dto.LoginRequest;
import ru.soune.no_copy.dto.LoginResponse;
import ru.soune.no_copy.dto.RegisterRequest;
import ru.soune.no_copy.entity.AuthToken;
import ru.soune.no_copy.exception.TokenNotFoundException;
import ru.soune.no_copy.repository.AuthTokenRepository;
import ru.soune.no_copy.service.AuthService;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("v1/api/auth")
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
private final AuthTokenRepository authTokenRepository;
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest registerRequest) {
AuthToken authToken = authService.register(registerRequest);
@@ -40,19 +36,4 @@ public class AuthController {
return ResponseEntity.ok(new LoginResponse(true, login.getUser().getEmail(),
login.getToken(),login.getExpiresAt().toString()));
}
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> opToken = authTokenRepository.findByToken(token);
if (opToken.isEmpty()) {
throw new TokenNotFoundException("Token not found");
}
authService.logout(token);
return ResponseEntity.ok(Map.of("success", true));
}
}
@@ -1,11 +0,0 @@
package ru.soune.no_copy.controller;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("v1/api/content")
@AllArgsConstructor
public class UserContentController {
}
@@ -1,111 +0,0 @@
package ru.soune.no_copy.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.no_copy.dto.ChangePasswordRequest;
import ru.soune.no_copy.dto.UserDTO;
import ru.soune.no_copy.dto.UserRequest;
import ru.soune.no_copy.entity.AuthToken;
import ru.soune.no_copy.entity.User;
import ru.soune.no_copy.exception.InvalidUserEmail;
import ru.soune.no_copy.exception.NotFoundAuthToken;
import ru.soune.no_copy.mapper.UserMapper;
import ru.soune.no_copy.repository.AuthTokenRepository;
import ru.soune.no_copy.repository.UserRepository;
import ru.soune.no_copy.service.UserService;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("v1/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
private final UserService userService;
private final AuthTokenRepository authTokenRepository;
private final UserMapper userMapper;
@GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(),
u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType()))
.toList();
return ResponseEntity.ok(allUsers);
}
@GetMapping
public ResponseEntity<UserDTO> getUser(@RequestParam("email") String email,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
if (authToken != null) {
Optional<User> user = userRepository.findByEmail(email);
//TODO add mapper
if (user.isPresent()) {
UserDTO userDTO = userMapper.toDTO(user.get());
userDTO.setEmail(email);
userDTO.setFullName(user.get().getFullName());
userDTO.setCompany(user.get().getCompany());
userDTO.setPhone(user.get().getPhone());
userDTO.setGenderType(user.get().getGenderType());
userDTO.setBirthday(user.get().getBirthday());
userDTO.setCreatedAt(user.get().getCreatedAt());
userDTO.setSubscriptionType(user.get().getSubscriptionType());
userDTO.setActive(user.get().getIsActive());
return ResponseEntity.ok(userDTO);
}
}
return ResponseEntity.notFound().build();
}
//TODO fix mapper,doesnot exist all fields
@PostMapping("/change-password")
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
if (!changePasswordRequest.getEmail().equals(user.getEmail())) {
throw new InvalidUserEmail("Email is not valid: " + changePasswordRequest.getEmail() + "not found");
}
User updateUser = userService.changePassword(user, changePasswordRequest);
return ResponseEntity.ok(userMapper.toDTO(updateUser));
}
@PatchMapping("/user-update")
public ResponseEntity<UserDTO> updateUser(@RequestBody UserRequest userRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
return ResponseEntity.ok(userService.updateUser(userRequest, user));
}
}
@@ -2,9 +2,4 @@ package ru.soune.no_copy.dto;
import java.time.LocalDateTime;
public record AuthResponse (
boolean success,
String message,
String token,
LocalDateTime expiresAt
) {}
public record AuthResponse (boolean success, String message, String token, LocalDateTime expiresAt) {}
@@ -1,14 +0,0 @@
package ru.soune.no_copy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@Data
@AllArgsConstructor
@Getter
public class ChangePasswordRequest {
private String email;
private String currentPassword;
private String newPassword;
}
@@ -5,11 +5,9 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginRequest {
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128)
@@ -1,40 +1,13 @@
package ru.soune.no_copy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import ru.soune.no_copy.entity.GenderType;
import ru.soune.no_copy.entity.SubscriptionType;
import java.time.LocalDate;
public record RegisterRequest(
@NotBlank(message = "error.name.length")
String fullName,
String companyName,
@Size(min = 11, max = 14)
String phone,
@NotBlank(message = "error.not.blank")
@Email(message = "error.not.email")
@Size(max = 128)
String email,
@DateTimeFormat(pattern = "dd-MM-yyyy")
@JsonFormat(pattern = "dd-MM-yyyy")
@Past
LocalDate birthday,
@NotBlank(message = "error.not.blank")
@Size(min = 8)
String password,
SubscriptionType subscriptionType,
GenderType genderType
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String firstName,
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String secondName,
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String lastName,
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128) String email,
@NotBlank(message = "error.not.blank") @Size(min = 8) String password
) {}
@@ -1,15 +0,0 @@
package ru.soune.no_copy.dto;
import lombok.Data;
import ru.soune.no_copy.entity.FileType;
@Data
public class UserContentDTO {
String fileName;
String originalFilename;
String fileType;
String fileExtension;
Long fileSize;
String filePath;
Integer protectionLevel;
}
@@ -1,15 +0,0 @@
package ru.soune.no_copy.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record UserContentRequest(
@NotBlank @Email String userEmail,
@NotBlank String fileName,
@NotBlank String originalFilename,
@NotBlank String fileType,
@NotBlank String fileExtension,
@NotBlank Long fileSize,
@NotBlank String filePath,
Integer protectionLevel
) {}
@@ -1,17 +0,0 @@
package ru.soune.no_copy.dto;
import ru.soune.no_copy.entity.ContentStatus;
import ru.soune.no_copy.entity.FileType;
import java.time.LocalDateTime;
public record UserContentResponse(
Long id,
String filename,
String originalFilename,
FileType fileType,
long fileSize,
String filePath,
ContentStatus status,
LocalDateTime uploadDate
) {}
@@ -1,16 +0,0 @@
package ru.soune.no_copy.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record UserContentUpdateRequest(
@NotBlank @Email String userEmail,
@NotBlank String fileName,
@NotBlank String originalFilename,
@NotBlank String fileType,
@NotBlank String fileExtension,
@NotBlank Long fileSize,
@NotBlank String filePath,
Integer protectionLevel,
Long id
) {}
@@ -1,27 +0,0 @@
package ru.soune.no_copy.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.no_copy.entity.GenderType;
import ru.soune.no_copy.entity.SubscriptionType;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDTO {
private String fullName;
private String company;
private String email;
private boolean active;
private String phone;
private GenderType genderType;
private LocalDate birthday;
private LocalDateTime createdAt;
private SubscriptionType subscriptionType;
}
@@ -1,25 +0,0 @@
package ru.soune.no_copy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import lombok.Data;
import ru.soune.no_copy.entity.GenderType;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class UserRequest {
@Size(min = 2)
private String fullName;
private String company;
@Email
private String email;
private String phone;
private GenderType genderType;
@Past
@JsonFormat(pattern = "dd-MM-yyyy")
private LocalDate birthday;
}
@@ -1,30 +1,25 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "auth_tokens")
@NoArgsConstructor
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
@Table(name = "auth_tokens")
public class AuthToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long tokenId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
private Long id;
@Column(nullable = false, unique = true, length = 64)
private String token;
@@ -36,13 +31,7 @@ public class AuthToken {
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@Column(name = "last_used_at")
private LocalDateTime lastUsedAt;
@Column(name = "is_active")
private Boolean isActive = true;
public boolean isValid() {
return Boolean.TRUE.equals(isActive) && expiresAt.isAfter(LocalDateTime.now());
}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
@@ -1,29 +0,0 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@@ -1,20 +0,0 @@
package ru.soune.no_copy.entity;
import lombok.Getter;
@Getter
public enum ContentStatus {
PROCESSING("processing"),
COMPLETED("completed"),
ERROR("error");
private final String code;
ContentStatus(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
@@ -1,21 +0,0 @@
package ru.soune.no_copy.entity;
import lombok.Getter;
@Getter
public enum FileType {
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
DOCUMENT("document");
private final String code;
FileType(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
@@ -1,5 +0,0 @@
package ru.soune.no_copy.entity;
public enum GenderType {
MALE, FEMALE
}
@@ -1,49 +0,0 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "image_protection")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class ImageProtection {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long protectionId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "content_id", nullable = false)
@ToString.Exclude
private UserContent content;
@Column(name = "protection_method", nullable = false, length = 50)
private String protectionMethod;
@Column(name = "protection_level", nullable = false)
private Integer protectionLevel;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
@CreatedDate
@Column(name = "applied_at", nullable = false, updatable = false)
private LocalDateTime appliedAt;
@Column(name = "metadata", columnDefinition = "JSON")
private String metadata;
}
@@ -1,8 +0,0 @@
package ru.soune.no_copy.entity;
import lombok.Getter;
@Getter
public enum SubscriptionType {
START, BASIC, PRO, ENTERPRISE, DEMO;
}
+19 -53
View File
@@ -7,86 +7,52 @@ import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
@Getter @Setter
@EntityListeners(AuditingEntityListener.class)
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private Long userId;
@Column(name = "full_name", nullable = false)
private String fullName;
@Size(max = 64)
@Column(name = "firstName", nullable = false, length = 64)
private String firstName;
@Size(max = 64)
@Column(name = "lastName", nullable = false, length = 64)
private String lastName;
@Size(max = 64)
@Column(name = "secondName", length = 64)
private String secondName;
@Size(max = 1024)
@Column(name = "email", nullable = false, length = 1024, unique = true)
private String email;
@Column(name = "company")
private String company;
@Column(nullable = false)
@Size(min = 6)
@JsonIgnore
private String password;
@Size(min = 11, max = 14)
private String phone;
@Enumerated(EnumType.STRING)
@Column(name = "subscription_type", nullable = false, length = 20)
private SubscriptionType subscriptionType = SubscriptionType.DEMO;
@Enumerated(EnumType.STRING)
@Column(name = "gender")
private GenderType genderType = GenderType.MALE;
@Column(name = "birthday")
private LocalDate birthday;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AuthToken> tokens = new ArrayList<>();
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@Column(name = "last_login_at")
private LocalDateTime lastLoginAt;
@Column(name = "is_active")
private Boolean isActive = true;
private Boolean isActive;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<AuthToken> tokens = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<UserContent> userContents = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<UserAction> userActions = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<Violation> violations = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<ImageProtection> imageProtections = new ArrayList<>();
}
@@ -1,44 +0,0 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_actions")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class UserAction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long actionId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "action", nullable = false, length = 100)
private String action;
@Column(name = "details", columnDefinition = "TEXT")
private String details;
@Column(name = "ip_address", length = 45)
private String ipAddress;
@Column(name = "user_agent", columnDefinition = "TEXT")
private String userAgent;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}
@@ -1,80 +0,0 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_content")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class UserContent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long contentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "filename", nullable = false, length = 255)
private String filename;
@Column(name = "original_filename", nullable = false, length = 255)
private String originalFilename;
@Enumerated(EnumType.STRING)
@Column(name = "file_type", nullable = false, length = 20)
private FileType fileType;
@Column(name = "file_extension", nullable = false, length = 10)
private String fileExtension;
@Column(name = "file_size", nullable = false)
private Long fileSize;
@Column(name = "file_path", nullable = false, columnDefinition = "TEXT")
private String filePath;
@Column(name = "protection_level")
private Integer protectionLevel = 0;
@Column(name = "geometric_resistant")
private Boolean geometricResistant = false;
@Column(name = "watermark_applied")
private Boolean watermarkApplied = false;
@Column(name = "protection_hash", length = 64)
private String protectionHash;
@Column(name = "watermark_id", length = 50)
private String watermarkId;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private ContentStatus status = ContentStatus.PROCESSING;
@CreatedDate
@Column(name = "upload_date", nullable = false, updatable = false)
private LocalDateTime uploadDate;
public Double getFileSizeInGB() {
return fileSize != null ? fileSize / (1024.0 * 1024.0 * 1024.0) : 0.0;
}
public Boolean isProtected() {
return protectionLevel != null && protectionLevel > 0;
}
}
@@ -1,56 +0,0 @@
package ru.soune.no_copy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "violations")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class Violation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long violationId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "violation_type", nullable = false, length = 50)
private String violationType;
@Column(name = "description", columnDefinition = "TEXT")
private String description;
@Column(name = "content_id")
private Long contentId;
@Column(name = "status", nullable = false, length = 20)
private String status = "new";
@Column(name = "severity", length = 20)
private String severity;
@Column(name = "resolved_at")
private LocalDateTime resolvedAt;
@Column(name = "resolved_by")
private Long resolvedBy;
@Column(name = "resolution_notes", columnDefinition = "TEXT")
private String resolutionNotes;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.exception;
public class ContentNotFoundException extends RuntimeException {
public ContentNotFoundException(String message) {
super(message);
}
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.exception;
public class InvalidPasswordException extends RuntimeException {
public InvalidPasswordException(String message) {
super(message);
}
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.exception;
public class InvalidUserEmail extends RuntimeException {
public InvalidUserEmail(String message) {
super(message);
}
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.exception;
public class NotFoundAuthToken extends RuntimeException {
public NotFoundAuthToken(String message) {
super(message);
}
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.exception;
public class TokenNotFoundException extends RuntimeException {
public TokenNotFoundException(String message) {
super(message);
}
}
@@ -8,7 +8,9 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.soune.no_copy.exception.*;
import ru.soune.no_copy.exception.NotValidationPasswordException;
import ru.soune.no_copy.exception.UserAlreadyExistsException;
import ru.soune.no_copy.exception.UserNotFoundException;
import java.util.Map;
@@ -51,17 +53,6 @@ public class GlobalExceptionHandler {
));
}
@ExceptionHandler(ContentNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<?> handleUserNotFoundException(ContentNotFoundException ex) {
return ResponseEntity.
badRequest()
.body(Map.of(
"success", false,
"message" ,ex.getMessage()
));
}
@ExceptionHandler(NotValidationPasswordException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ResponseEntity<?> handleNotValidationPasswordException(NotValidationPasswordException ex) {
@@ -72,15 +63,4 @@ public class GlobalExceptionHandler {
"message" ,ex.getMessage()
));
}
@ExceptionHandler(TokenNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<?> handleNotFoundTokenException(TokenNotFoundException ex) {
return ResponseEntity.
badRequest()
.body(Map.of(
"success", false,
"message" ,ex.getMessage()
));
}
}
@@ -1,7 +0,0 @@
package ru.soune.no_copy.handler;
import org.springframework.stereotype.Component;
@Component
public class JwtTokenProvider {
}
@@ -1,18 +0,0 @@
package ru.soune.no_copy.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
import ru.soune.no_copy.dto.UserContentUpdateRequest;
import ru.soune.no_copy.entity.UserContent;
@Mapper(componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface UserContentMapper {
UserContentMapper INSTANCE = Mappers.getMapper(UserContentMapper.class);
void updateEntityFromDto(UserContentUpdateRequest dto, @MappingTarget UserContent entity);
}
@@ -1,16 +0,0 @@
package ru.soune.no_copy.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
import ru.soune.no_copy.dto.UserDTO;
import ru.soune.no_copy.entity.User;
@Mapper(componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO toDTO(User user);
}
@@ -10,5 +10,4 @@ import java.util.Optional;
@Repository
public interface AuthTokenRepository extends JpaRepository<AuthToken, Long> {
Optional<AuthToken> findByTokenAndExpiresAtAfter(String token, LocalDate expiresAtAfter);
Optional<AuthToken> findByToken(String token);
}
@@ -1,20 +0,0 @@
package ru.soune.no_copy.repository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.soune.no_copy.entity.FileType;
import ru.soune.no_copy.entity.UserContent;
import java.util.List;
public interface UserContentRepository extends JpaRepository<UserContent, Long> {
List<UserContent> findByUserIdOrderByUploadDateDesc(Long userId);
List<UserContent> findByUserId(Long userId);
List<UserContent> findByUserIdAndFileTypeOrderByUploadDateDesc(Long userId, FileType type, Pageable pageable);
long countByUserIdAndFileType(Long userId, FileType type);
long countByUserId(Long userId);
}
@@ -7,5 +7,6 @@ import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
@@ -16,7 +16,6 @@ import ru.soune.no_copy.repository.AuthTokenRepository;
import ru.soune.no_copy.repository.UserRepository;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Locale;
import java.util.Optional;
@@ -38,22 +37,17 @@ public class AuthService {
@Transactional
public AuthToken register(RegisterRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.email())) {
throw new UserAlreadyExistsException("User already exists with email: " + registerRequest.email());
throw new UserAlreadyExistsException(messageSource.getMessage("error.user.exists", null,
Locale.getDefault()));
}
User user = new User();
user.setFullName(registerRequest.fullName());
user.setFirstName(registerRequest.firstName());
user.setSecondName(registerRequest.secondName());
user.setLastName(registerRequest.lastName());
user.setEmail(registerRequest.email());
user.setPassword(passwordEncoder.encode(registerRequest.password()));
if (registerRequest.companyName() != null) {
user.setCompany(registerRequest.companyName());
}
if (registerRequest.phone() != null) {
user.setPhone(registerRequest.phone());
}
User savedUser = userRepository.save(user);
AuthToken authToken = new AuthToken();
@@ -77,21 +71,13 @@ public class AuthService {
throw new NotValidationPasswordException("Invalid password");
}
user.setLastLoginAt(LocalDateTime.now());
User savedUser = userRepository.save(user);
AuthToken authToken = new AuthToken();
authToken.setToken(generateAuthToken());
authToken.setUser(savedUser);
authToken.setUser(user);
return authTokenRepository.save(authToken);
}
@Transactional
public void logout(String token) {
authTokenRepository.findByToken(token)
.ifPresent(authTokenRepository::delete);
}
private String generateAuthToken() {
byte[] bytes = new byte[32];
@@ -1,72 +0,0 @@
package ru.soune.no_copy.service;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.no_copy.dto.UserContentRequest;
import ru.soune.no_copy.dto.UserContentUpdateRequest;
import ru.soune.no_copy.entity.FileType;
import ru.soune.no_copy.entity.User;
import ru.soune.no_copy.entity.UserContent;
import ru.soune.no_copy.exception.ContentNotFoundException;
import ru.soune.no_copy.exception.UserNotFoundException;
import ru.soune.no_copy.mapper.UserContentMapper;
import ru.soune.no_copy.repository.UserContentRepository;
import ru.soune.no_copy.repository.UserRepository;
import java.util.List;
import java.util.Optional;
@Service
@AllArgsConstructor
public class UserContentService {
private final UserContentRepository userContentRepository;
private final UserRepository userRepository;
private final UserContentMapper userContentMapper;
public List<UserContent> findByUser(long userId) {
return userContentRepository.findByUserId(userId);
}
@Transactional
public UserContent addUserContent(UserContentRequest userContentRequest) {
UserContent userContent = new UserContent();
Optional<User> byEmail = userRepository.findByEmail(userContentRequest.userEmail());
User user = byEmail.orElseThrow(() -> new UserNotFoundException(userContentRequest.userEmail()));
userContent.setUser(user);
userContent.setFilename(userContentRequest.fileName());
userContent.setFileExtension(userContentRequest.fileExtension());
userContent.setFilePath(userContentRequest.filePath());
userContent.setFileType(FileType.valueOf(userContentRequest.fileType()));
userContent.setOriginalFilename(userContentRequest.originalFilename());
userContent.setProtectionLevel(userContentRequest.protectionLevel());
userContent.setFileSize(userContentRequest.fileSize());
//TODO add
// @Column(name = "protection_hash", length = 64)
// private String protectionHash;
// @Column(name = "watermark_id", length = 50)
// private String watermarkId;
return userContentRepository.save(userContent);
}
@Transactional
public UserContent updateUserContent(UserContentUpdateRequest userContentRequest) {
UserContent entity = userContentRepository.findById(userContentRequest.id())
.orElseThrow(() -> new ContentNotFoundException("Content not found with: " + userContentRequest.id()));
userContentMapper.updateEntityFromDto(userContentRequest, entity);
return userContentRepository.save(entity);
}
public void deleteUserContent(long userContentId) {
userContentRepository.deleteById(userContentId);
}
}
@@ -1,86 +0,0 @@
package ru.soune.no_copy.service;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.no_copy.dto.ChangePasswordRequest;
import ru.soune.no_copy.dto.UserDTO;
import ru.soune.no_copy.dto.UserRequest;
import ru.soune.no_copy.entity.User;
import ru.soune.no_copy.exception.InvalidUserEmail;
import ru.soune.no_copy.exception.NotValidationPasswordException;
import ru.soune.no_copy.exception.UserNotFoundException;
import ru.soune.no_copy.repository.UserRepository;
@Service
@AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Transactional
public User changePassword(User user, ChangePasswordRequest request) {
if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
throw new NotValidationPasswordException("Current password is incorrect");
}
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
return userRepository.save(user);
}
@Transactional
public UserDTO updateUser(UserRequest userRequest, User currentUser) {
User user = userRepository.findById(currentUser.getId())
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + currentUser.getId()));
updateUserFromRequest(user, userRequest);
User updatedUser = userRepository.save(user);
return mapToDTO(updatedUser);
}
private void updateUserFromRequest(User user, UserRequest request) {
if (request.getFullName() != null) {
user.setFullName(request.getFullName());
}
if (request.getCompany() != null) {
user.setCompany(request.getCompany());
}
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new InvalidUserEmail("Email already exists: " + request.getEmail());
}
user.setEmail(request.getEmail());
}
if (request.getPhone() != null) {
user.setPhone(request.getPhone());
}
if (request.getGenderType() != null) {
user.setGenderType(request.getGenderType());
}
if (request.getBirthday() != null) {
user.setBirthday(request.getBirthday());
}
}
private UserDTO mapToDTO(User user) {
return UserDTO.builder()
.fullName(user.getFullName())
.email(user.getEmail())
.company(user.getCompany())
.phone(user.getPhone())
.genderType(user.getGenderType())
.birthday(user.getBirthday())
.subscriptionType(user.getSubscriptionType())
.createdAt(user.getCreatedAt())
.build();
}
}
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration scan="true">
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>{"timestamp":"%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}", "trace_id": "%X{trace_id}",
"span_id":"%X{span_id}", "level":"%level", "thread":"%thread",
"logger":"%logger{36}","message":"%msg"}%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>
@@ -51,8 +51,8 @@ public class AuthServiceTest {
@Test
void registerSuccess() {
RegisterRequest request = new RegisterRequest("John","A.","124124",
"john@example.com", null,"password123", null, null);
RegisterRequest request = new RegisterRequest("John","A.","Doe",
"john@example.com", "password123");
when(userRepository.existsByEmail(request.email())).thenReturn(false);
when(passwordEncoder.encode(request.password())).thenReturn("hashed");
@@ -72,8 +72,8 @@ public class AuthServiceTest {
@Test
void registerExistingEmailThrows() {
RegisterRequest request = new RegisterRequest("John","A.","124124",
"john@example.com", null,"password123", null, null);
RegisterRequest request = new RegisterRequest("John","A.","Doe",
"john@example.com", "password123");
when(userRepository.existsByEmail(request.email()))
.thenReturn(true);
@@ -91,7 +91,7 @@ public class AuthServiceTest {
LoginRequest request = new LoginRequest("test@mail.com", "password");
User user = new User();
user.setId(1L);
user.setUserId(1L);
user.setEmail("test@mail.com");
user.setPassword("encoded_pass");
@@ -106,12 +106,12 @@ public class AuthServiceTest {
assertNotNull(token);
assertNotNull(token.getToken());
// assertEquals(user.getTokens().get(0), token.getUser());
assertEquals(user, token.getUser());
ArgumentCaptor<AuthToken> captor = ArgumentCaptor.forClass(AuthToken.class);
verify(authTokenRepository).save(captor.capture());
// assertEquals(user, captor.getValue().getUser());
assertEquals(user, captor.getValue().getUser());
assertNotNull(captor.getValue().getToken());
}