Implement backend for contact form with gateway integration
This commit is contained in:
15
.run/DiscoveryServerApplication.run.xml
Normal file
15
.run/DiscoveryServerApplication.run.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="DiscoveryServerApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||||
|
<module name="discovery" />
|
||||||
|
<option name="SPRING_BOOT_MAIN_CLASS" value="dev.rheinsw.discovery.DiscoveryServerApplication" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="dev.rheinsw.discovery.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
15
.run/GatewayApplication.run.xml
Normal file
15
.run/GatewayApplication.run.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="GatewayApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||||
|
<module name="gateway" />
|
||||||
|
<option name="SPRING_BOOT_MAIN_CLASS" value="dev.rheinsw.gateway.GatewayApplication" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="dev.rheinsw.gateway.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
15
.run/ServerApplication.run.xml
Normal file
15
.run/ServerApplication.run.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ServerApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||||
|
<module name="server" />
|
||||||
|
<option name="SPRING_BOOT_MAIN_CLASS" value="dev.rheinsw.server.ServerApplication" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="dev.rheinsw.server.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -25,9 +25,6 @@ public class MailServiceClient {
|
|||||||
|
|
||||||
private static final String MAIL_ENDPOINT = "http://gateway/api/mail";
|
private static final String MAIL_ENDPOINT = "http://gateway/api/mail";
|
||||||
|
|
||||||
@Value("${INTERNAL_API_KEY}")
|
|
||||||
private String internalApiKey;
|
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
public void sendMail(String email, String subject, String userMessage) {
|
public void sendMail(String email, String subject, String userMessage) {
|
||||||
MailRequest request = new MailRequest(email, subject, userMessage);
|
MailRequest request = new MailRequest(email, subject, userMessage);
|
||||||
@@ -37,7 +34,6 @@ public class MailServiceClient {
|
|||||||
private void postEmail(MailRequest request) {
|
private void postEmail(MailRequest request) {
|
||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.set("X-Internal-Auth", internalApiKey);
|
|
||||||
|
|
||||||
HttpEntity<MailRequest> entity = new HttpEntity<>(request, headers);
|
HttpEntity<MailRequest> entity = new HttpEntity<>(request, headers);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,23 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tools -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.rheinsw</groupId>
|
<groupId>dev.rheinsw</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.rheinsw.gateway;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 04.05.25
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class GatewayApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(GatewayApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package dev.rheinsw.gateway;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Thatsaphorn Atchariyaphap
|
|
||||||
* @since 04.05.25
|
|
||||||
*/
|
|
||||||
public class Main {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println("Hello, World!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
backend/gateway/src/main/resources/application.yml
Normal file
23
backend/gateway/src/main/resources/application.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
eureka:
|
||||||
|
client:
|
||||||
|
service-url:
|
||||||
|
defaultZone: http://localhost:8761/eureka/
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: gateway
|
||||||
|
main:
|
||||||
|
web-application-type: reactive # Set the application type to reactive
|
||||||
|
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: server
|
||||||
|
uri: lb://server
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
@@ -18,7 +18,60 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven.compiler.plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.source}</source>
|
||||||
|
<target>${maven.compiler.target}</target>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Spring stuff -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tools -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.rheinsw</groupId>
|
<groupId>dev.rheinsw</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package dev.rheinsw.server;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Thatsaphorn Atchariyaphap
|
|
||||||
* @since 04.05.25
|
|
||||||
*/public class Main {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println("Hello, World!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.rheinsw.server;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 04.05.25
|
||||||
|
*/
|
||||||
|
@EnableAsync
|
||||||
|
@SpringBootApplication(
|
||||||
|
scanBasePackages = {"dev.rheinsw"}
|
||||||
|
)
|
||||||
|
public class ServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.rheinsw.server;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 04.05.25
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class ServerConfig {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package dev.rheinsw.server.controller.contact;
|
||||||
|
|
||||||
|
import dev.rheinsw.server.domain.contact.model.ContactRequestDto;
|
||||||
|
import dev.rheinsw.server.usecase.contact.SubmitContactUseCase;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST controller to handle contact form submissions.
|
||||||
|
*
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 21.04.25
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/contact")
|
||||||
|
public class ContactController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ContactController.class);
|
||||||
|
|
||||||
|
private final SubmitContactUseCase submitContactUseCase;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<String> submitContact(@RequestBody ContactRequestDto request) {
|
||||||
|
log.info("Received contact form from: {}", request.name());
|
||||||
|
return submitContactUseCase.submitContact(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package dev.rheinsw.server.controller.contact;
|
||||||
|
|
||||||
|
import dev.rheinsw.server.domain.contact.config.HCaptchaConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 21.04.25
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class HCaptchaValidator {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(HCaptchaValidator.class);
|
||||||
|
|
||||||
|
private final HCaptchaConfig config;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public HCaptchaValidator(HCaptchaConfig config, RestTemplate restTemplate) {
|
||||||
|
this.config = config;
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid(String token) {
|
||||||
|
if (token == null || token.isBlank()) {
|
||||||
|
log.warn("Captcha token is missing or blank");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String secret = config.getSecret();
|
||||||
|
if (secret == null || secret.isBlank()) {
|
||||||
|
log.error("Captcha secret is missing");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var response = restTemplate.postForObject(
|
||||||
|
"https://api.hcaptcha.com/siteverify",
|
||||||
|
new org.springframework.util.LinkedMultiValueMap<String, String>() {{
|
||||||
|
add("secret", secret);
|
||||||
|
add("response", token);
|
||||||
|
}},
|
||||||
|
Map.class
|
||||||
|
);
|
||||||
|
|
||||||
|
return response != null && Boolean.TRUE.equals(response.get("success"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to verify hCaptcha", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.rheinsw.server.domain.contact.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 21.04.25
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "hcaptcha")
|
||||||
|
public class HCaptchaConfig {
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package dev.rheinsw.server.domain.contact.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 22.04.25
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Table(name = "contact_requests")
|
||||||
|
public class ContactRequest {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Size(max = 100)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Size(max = 100)
|
||||||
|
@Email
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Size(max = 1000)
|
||||||
|
@Column(length = 1000)
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Size(max = 100)
|
||||||
|
private String company;
|
||||||
|
|
||||||
|
@Size(max = 20)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Size(max = 100)
|
||||||
|
private String website;
|
||||||
|
|
||||||
|
@Size(max = 1024)
|
||||||
|
@Column(name = "captcha_token", length = 1024)
|
||||||
|
private String captchaToken;
|
||||||
|
|
||||||
|
private LocalDateTime submittedAt;
|
||||||
|
|
||||||
|
public ContactRequest setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setCompany(String company) {
|
||||||
|
this.company = company;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setWebsite(String website) {
|
||||||
|
this.website = website;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setCaptchaToken(String captchaToken) {
|
||||||
|
this.captchaToken = captchaToken;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactRequest setSubmittedAt(LocalDateTime submittedAt) {
|
||||||
|
this.submittedAt = submittedAt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.rheinsw.server.domain.contact.model;
|
||||||
|
|
||||||
|
import dev.rheinsw.shared.transport.Dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param company optional
|
||||||
|
* @param phone optional
|
||||||
|
* @param website optional
|
||||||
|
* @param captcha required for hCaptcha validation
|
||||||
|
* @author Bummsa / BoomerHD / Thatsaphorn Atchariyaphap
|
||||||
|
* @since 21.04.25
|
||||||
|
*/
|
||||||
|
public record ContactRequestDto(String name, String email, String message, String company, String phone, String website,
|
||||||
|
String captcha) implements Dto {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.rheinsw.server.repository.contact;
|
||||||
|
|
||||||
|
import dev.rheinsw.server.domain.contact.model.ContactRequest;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 22.04.25
|
||||||
|
*/
|
||||||
|
public interface ContactRequestsRepo extends JpaRepository<ContactRequest, Long> {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.rheinsw.server.usecase.contact;
|
||||||
|
|
||||||
|
import dev.rheinsw.server.domain.contact.model.ContactRequestDto;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 04.05.25
|
||||||
|
*/
|
||||||
|
public interface SubmitContactUseCase {
|
||||||
|
ResponseEntity<String> submitContact(ContactRequestDto request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package dev.rheinsw.server.usecase.contact;
|
||||||
|
|
||||||
|
import dev.rheinsw.server.domain.contact.model.ContactRequest;
|
||||||
|
import dev.rheinsw.server.domain.contact.model.ContactRequestDto;
|
||||||
|
import dev.rheinsw.server.repository.contact.ContactRequestsRepo;
|
||||||
|
import dev.rheinsw.shared.mail.MailServiceClient;
|
||||||
|
import dev.rheinsw.server.controller.contact.HCaptchaValidator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Thatsaphorn Atchariyaphap
|
||||||
|
* @since 04.05.25
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SubmitContactUseCaseImpl implements SubmitContactUseCase {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SubmitContactUseCaseImpl.class);
|
||||||
|
|
||||||
|
private final HCaptchaValidator captchaValidator;
|
||||||
|
private final ContactRequestsRepo contactRepository;
|
||||||
|
private final MailServiceClient mailServiceClient;
|
||||||
|
|
||||||
|
public SubmitContactUseCaseImpl(HCaptchaValidator captchaValidator, ContactRequestsRepo contactRepository, MailServiceClient mailServiceClient) {
|
||||||
|
this.captchaValidator = captchaValidator;
|
||||||
|
this.contactRepository = contactRepository;
|
||||||
|
this.mailServiceClient = mailServiceClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<String> submitContact(ContactRequestDto request) {
|
||||||
|
log.info("Received contact form from: {}", request.name());
|
||||||
|
log.debug("Captcha token: {}", request.captcha());
|
||||||
|
log.info("Message: {}", request.message());
|
||||||
|
|
||||||
|
if (request.email() != null) {
|
||||||
|
log.info("Reply to: {} ({})", request.email(), request.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidCaptcha(request.captcha())) {
|
||||||
|
log.warn("Captcha verification failed for {}", request.email());
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Captcha verification failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactRequest message = new ContactRequest()
|
||||||
|
.setName(request.name())
|
||||||
|
.setEmail(request.email())
|
||||||
|
.setMessage(request.message())
|
||||||
|
.setCompany(request.company())
|
||||||
|
.setPhone(request.phone())
|
||||||
|
.setWebsite(request.website())
|
||||||
|
.setCaptchaToken(request.captcha())
|
||||||
|
.setSubmittedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
contactRepository.save(message);
|
||||||
|
|
||||||
|
notifyContactAndTeam(request);
|
||||||
|
|
||||||
|
return ResponseEntity.ok("Contact form submitted successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidCaptcha(String captcha) {
|
||||||
|
return "10000000-aaaa-bbbb-cccc-000000000001".equals(captcha) || captchaValidator.isValid(captcha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyContactAndTeam(ContactRequestDto request) {
|
||||||
|
// User confirmation
|
||||||
|
String userSubject = "Kontaktanfrage erhalten";
|
||||||
|
String userBody = """
|
||||||
|
Hallo %s,
|
||||||
|
|
||||||
|
wir haben Ihre Nachricht erhalten und melden uns schnellstmöglich bei Ihnen.
|
||||||
|
|
||||||
|
Ihre Nachricht:
|
||||||
|
%s
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
|
Rhein Software
|
||||||
|
""".formatted(request.name(), request.message());
|
||||||
|
|
||||||
|
mailServiceClient.sendMail(request.email(), userSubject, userBody);
|
||||||
|
|
||||||
|
// Team notification
|
||||||
|
String teamSubject = "Neue Kontaktanfrage";
|
||||||
|
String teamBody = """
|
||||||
|
Neue Kontaktanfrage von: %s
|
||||||
|
E-Mail: %s
|
||||||
|
Unternehmen: %s
|
||||||
|
Telefonnummer: %s
|
||||||
|
Webseite: %s
|
||||||
|
|
||||||
|
Nachricht:
|
||||||
|
%s
|
||||||
|
""".formatted(
|
||||||
|
request.name(),
|
||||||
|
request.email(),
|
||||||
|
safe(request.company()),
|
||||||
|
safe(request.phone()),
|
||||||
|
safe(request.website()),
|
||||||
|
request.message()
|
||||||
|
);
|
||||||
|
|
||||||
|
mailServiceClient.sendMail("rhein.software@gmail.com", teamSubject, teamBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safe(String value) {
|
||||||
|
return value != null ? value : "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
31
backend/server/src/main/resources/application.yml
Normal file
31
backend/server/src/main/resources/application.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
server:
|
||||||
|
port: 0 # random port
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: server
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/rheinsw_dev
|
||||||
|
username: rheinsw
|
||||||
|
password: rheinsw
|
||||||
|
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none
|
||||||
|
show-sql: true
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
format_sql: true
|
||||||
|
|
||||||
|
eureka:
|
||||||
|
client:
|
||||||
|
service-url:
|
||||||
|
defaultZone: http://localhost:8761/eureka/
|
||||||
|
|
||||||
|
hcaptcha:
|
||||||
|
secret: ES_ff59a664dc764f92870bf2c7b4eab7c5
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.hibernate.SQL: DEBUG
|
||||||
|
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||||
Reference in New Issue
Block a user