NCBACK-33 #16

Merged
backdev merged 13 commits from NCBACK-33 into dev 2026-01-27 09:11:31 +08:00
6 changed files with 62 additions and 258 deletions
Showing only changes of commit 4a8e561258 - Show all commits
+10 -1
View File
@@ -1,10 +1,19 @@
# Postgres
POSTGRES_DB=no_copy_ POSTGRES_DB=no_copy_
POSTGRES_USER=ncp_db POSTGRES_USER=ncp_db
POSTGRES_PASSWORD=ncpDbApp POSTGRES_PASSWORD=ncpDbApp
POSTGRES_PORT=5432 POSTGRES_PORT=5432
POSTGRES_HOST=postgres POSTGRES_HOST=postgres
# Redis
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PORT=6379 REDIS_PORT=6379
SERVER_PORT=8080 # Backend server
SERVER_PORT=8080
# Postfix
MAIL_HOST_PROD=postfix-production
MAIL_PORT_PROD=25
MAIL_USERNAME_PROD=noreply@nocopy.com
SMTP_PASSWORD_PROD=nocopy!nocopy!
+37 -182
View File
@@ -1,15 +1,32 @@
version: '3.9' version: '3.9'
services: services:
postfix:
image: boky/postfix:latest
container_name: postfix-production
hostname: ${DOMAIN:-no-copy.ru}
environment:
- DOMAIN=${DOMAIN:-no-copy.ru}
- ALLOWED_SENDER_DOMAINS=*
- RELAYHOST=
- DISABLE_SMTP_AUTH=
- SMTP_USERNAME=${MAIL_USERNAME:-noreply@no-copy.ru}
- SMTP_PASSWORD=${SMTP_PASSWORD:-nocopy!nocopy!}
- RELAY_NETWORKS=172.16.0.0/12
- POSTFIX_mynetworks=127.0.0.0/8 172.17.0.0/16 172.16.0.0/12
- POSTFIX_smtpd_relay_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
- POSTFIX_smtpd_recipient_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
- DISABLE_SIGNING=true
ports:
- "25:25"
- "587:587"
networks:
- app-network
restart: unless-stopped
storage: storage:
image: alpine:latest image: alpine:latest
container_name: file-storage container_name: file-storage
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 64M
networks: networks:
- app-network - app-network
volumes: volumes:
@@ -19,19 +36,9 @@ services:
db: db:
image: postgres:17.7 image: postgres:17.7
restart: always container_name: postgres
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
environment: environment:
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_SHARED_BUFFERS: 512MB POSTGRES_SHARED_BUFFERS: 512MB
@@ -41,42 +48,36 @@ services:
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d:ro - ./init-scripts:/docker-entrypoint-initdb.d:ro
container_name: postgres
networks: networks:
app-network: app-network:
aliases: aliases:
- database - database
restart: unless-stopped
app: app:
build: . build: .
container_name: app-backend container_name: app-backend
deploy:
resources:
limits:
cpus: '1.5'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
environment: environment:
FILE_STORAGE_PATH: /data/uploads FILE_STORAGE_PATH: /data/uploads
MAX_FILE_SIZE: 10737418240 MAX_FILE_SIZE: 10737418240
# FILE_CHUNK_SIZE: 1048576
FILE_CHUNK_SIZE: 1000000 FILE_CHUNK_SIZE: 1000000
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: 5432 POSTGRES_PORT: 5432
POSTGRES_HOST: db POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081 STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: docker SPRING_PROFILES_ACTIVE: docker
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q MAIL_HOST: postfix
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd MAIL_PORT: 25
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" MAIL_USERNAME: noreply@no-copy.ru
SMTP_PASSWORD: nocopy!nocopy!
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "false"
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "false"
SPRING_MAIL_PROPERTIES_MAIL_DEBUG: "true"
depends_on: depends_on:
- db - db
- postfix
ports: ports:
- "80:8080" - "80:8080"
networks: networks:
@@ -88,163 +89,17 @@ services:
volumes: volumes:
- uploads_data:/data/uploads:rw - uploads_data:/data/uploads:rw
healthcheck: healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_" ] test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_"]
interval: 10s interval: 10s
retries: 5 retries: 5
restart: unless-stopped
grafana:
image: grafana/grafana:10.3.1
container_name: grafana
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
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"
GF_DATABASE_MAX_IDLE_CONN: "2"
GF_DATABASE_MAX_OPEN_CONN: "10"
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
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
reservations:
memory: 512M
ports:
- "9090:9090"
volumes:
- ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=15d' # Удерживать 15 дней
- '--web.enable-lifecycle'
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
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
reservations:
memory: 1G
ports:
- "3100:3100"
volumes:
- ./infrastructure/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro
- loki_data:/loki
command:
- -config.file=/etc/loki/loki-config.yaml
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
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
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
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
memory: 128M
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
volumes: volumes:
pgdata: pgdata:
artifacts:
tempo_data:
loki_data:
loki_chunks:
loki_index:
loki_rules:
uploads_data: uploads_data:
prometheus_data: postfix-data:
networks: networks:
app-network: app-network:
external: true external: true
driver: bridge driver: bridge
@@ -36,8 +36,8 @@ public class User {
@Column(name = "email", nullable = false, length = 1024, unique = true) @Column(name = "email", nullable = false, length = 1024, unique = true)
private String email; private String email;
@Column(name = "email_verified", nullable = false) @Column(name = "email_verified")
private Boolean emailVerified = false; private boolean emailVerified = false;
@Column(name = "company") @Column(name = "company")
private String company; private String company;
@@ -82,11 +82,6 @@ public class User {
@ToString.Exclude @ToString.Exclude
private List<UserContent> userContents = new ArrayList<>(); 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) @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore @JsonIgnore
@ToString.Exclude @ToString.Exclude
@@ -1,44 +0,0 @@
package ru.soune.nocopy.entity.user;
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;
}
@@ -4,6 +4,7 @@ import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
@@ -35,16 +36,17 @@ public class EmailService {
helper.setFrom("NO COPY <noreply@nocopy.com>"); helper.setFrom("NO COPY <noreply@nocopy.com>");
helper.setTo(user.getEmail()); helper.setTo(user.getEmail());
helper.setSubject("NO COPY - Подтверждение email адреса"); helper.setSubject("NO COPY - Подтверждение email");
String htmlContent = loadAndProcessTemplate(user.getFullName(), code); String htmlContent = loadAndProcessTemplate(user.getFullName(), code);
helper.setText(htmlContent, true); helper.setText(htmlContent, true);
mailSender.send(message); mailSender.send(message);
} catch (MessagingException | IOException e) { log.info("Email sent to: {}", user.getEmail());
log.error("Fail send email: " + e.getMessage());
} catch (Exception e) {
log.error("Failed to send email: {}", e.getMessage());
} }
} }
+7 -20
View File
@@ -1,15 +1,13 @@
spring: spring:
mail: mail:
host: smtp.gmail.com host: postfix
port: 587 port: 25
username: vlad.popovtsev@gmail.com username: noreply@nocopy.com
password: aylgtjcxdtjvohrk password: nocopy!nocopy!
properties: properties:
mail: mail.smtp.auth: false
smtp: mail.smtp.starttls.enable: false
auth: true mail.debug: true
starttls:
enable: true
datasource: datasource:
url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
@@ -36,8 +34,6 @@ spring:
file: file:
storage: storage:
base-path: ${FILE_STORAGE_PATH:/data/uploads} base-path: ${FILE_STORAGE_PATH:/data/uploads}
# chunk-size: ${FILE_CHUNK_SIZE:5242880} # 5MB
# chunk-size: ${FILE_CHUNK_SIZE:1048576} # 1MB
chunk-size: ${FILE_CHUNK_SIZE:1000000} # 1MB chunk-size: ${FILE_CHUNK_SIZE:1000000} # 1MB
max-file-size: ${MAX_FILE_SIZE:10737418240} # 10GB max-file-size: ${MAX_FILE_SIZE:10737418240} # 10GB
max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3} max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3}
@@ -69,12 +65,3 @@ yandex:
api-key: ${YANDEX_API_KEY:AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q} api-key: ${YANDEX_API_KEY:AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q}
folder-id: ${YANDEX_FOLDER_ID:b1gokpdbm6qfpsou8pcd} folder-id: ${YANDEX_FOLDER_ID:b1gokpdbm6qfpsou8pcd}
search-url: ${YANDEX_SEARCH_URL:https://searchapi.api.cloud.yandex.net/v2/image/search_by_image} search-url: ${YANDEX_SEARCH_URL:https://searchapi.api.cloud.yandex.net/v2/image/search_by_image}
app:
email:
verification:
expiration-hours: ${EMAIL_VERIFICATION_EXPIRATION:24}
base-url: ${EMAIL_BASE_URL:http://localhost:80}
from-email: ${EMAIL_FROM:noreply@yourdomain.com}
templates:
verification: verification-email.html