Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ repositories {

dependencies {
// Spring core
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-web:3.5.3")
implementation("org.springframework.boot:spring-boot-starter-webflux:3.5.3")

implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3")
implementation("org.postgresql:postgresql:42.7.3")

runtimeOnly("org.postgresql:postgresql")

// Development tools
developmentOnly("org.springframework.boot:spring-boot-devtools")
compileOnly("org.projectlombok:lombok")

annotationProcessor("org.projectlombok:lombok")

// DB
// implementation("org.springframework.boot:spring-boot-starter-jdbc")
// implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3")
// runtimeOnly("org.postgresql:postgresql")
// implementation("org.liquibase:liquibase-core")


// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/io/paradaux/api/controllers/IfumController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.paradaux.api.controllers;

import io.paradaux.api.services.IfumService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/ifum")
public class IfumController {

private final IfumService ifumService;

public IfumController(IfumService ifumService) {
this.ifumService = ifumService;
}

@PostMapping("/visit")
public void insertVisit() {
ifumService.insertVisit();
}

@GetMapping("/visits")
public Integer getVisitCount() {
return ifumService.getVisitCount();
}
}
14 changes: 14 additions & 0 deletions src/main/java/io/paradaux/api/mappers/ContactFormMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.paradaux.api.mappers;

import io.paradaux.api.models.ContactFormEntry;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface ContactFormMapper {
void insert(ContactFormEntry response);
ContactFormEntry findById(int id);
List<ContactFormEntry> findAll();
void deleteById(int id);
}
10 changes: 10 additions & 0 deletions src/main/java/io/paradaux/api/mappers/IfumMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.paradaux.api.mappers;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface IfumMapper {

void insertVisit();
Integer getVisitCount();
}
26 changes: 26 additions & 0 deletions src/main/java/io/paradaux/api/models/ContactFormEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.paradaux.api.models;

import lombok.Data;

import java.sql.Timestamp;

@Data
public class ContactFormEntry {
private int id;
private String name;
private String email;
private String subject;
private String message;
private String ipAddress;
private Timestamp createdAt;

public static ContactFormEntry of(ContactFormRequest request) {
ContactFormEntry entry = new ContactFormEntry();
entry.setName(request.getName());
entry.setEmail(request.getEmail());
entry.setSubject(request.getSubject());
entry.setMessage(request.getMessage());
entry.setCreatedAt(new Timestamp(System.currentTimeMillis()));
return entry;
}
}
11 changes: 11 additions & 0 deletions src/main/java/io/paradaux/api/models/IfumVisit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.paradaux.api.models;

import lombok.Data;

import java.sql.Timestamp;

@Data
public class IfumVisit {
private int id;
private Timestamp createdAt;
}
15 changes: 15 additions & 0 deletions src/main/java/io/paradaux/api/services/IfumService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.paradaux.api.services;

public interface IfumService {
/**
* Inserts a new IfumVisit record into the database.
*/
void insertVisit();

/**
* Retrieves the total count of IfumVisit records.
*
* @return the count of IfumVisit records
*/
Integer getVisitCount();
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
package io.paradaux.api.services.impl;

import io.paradaux.api.mappers.ContactFormMapper;
import io.paradaux.api.models.ContactFormEntry;
import io.paradaux.api.models.ContactFormRequest;
import io.paradaux.api.services.CloudflareTurnstileService;
import io.paradaux.api.services.ContactService;
import io.paradaux.api.services.DiscordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

@Service
public class ContactServiceImpl implements ContactService {

private final CloudflareTurnstileService turnstileService;
private final DiscordService discordService;
private final ContactFormMapper contactFormMapper;

@Autowired
public ContactServiceImpl(CloudflareTurnstileService turnstileService, DiscordService discordService) {
public ContactServiceImpl(CloudflareTurnstileService turnstileService, DiscordService discordService, ContactFormMapper contactFormMapper) {
this.turnstileService = turnstileService;
this.discordService = discordService;
this.contactFormMapper = contactFormMapper;
}

@Override
public Mono<ResponseEntity<String>> processContactForm(ContactFormRequest request) {
ContactFormEntry entry = ContactFormEntry.of(request);

return turnstileService.verifyToken(request.getCfTurnstileResponse())
.flatMap(valid -> {
if (!valid) {
return Mono.just(ResponseEntity.badRequest().body("Turnstile validation failed."));
}

return discordService.sendContactForm(request)
return Mono.fromRunnable(() -> contactFormMapper.insert(entry))
.subscribeOn(Schedulers.boundedElastic())
.then(discordService.sendContactForm(request))
.then(Mono.just(ResponseEntity.ok("Message received.")));
});
}
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/io/paradaux/api/services/impl/IfumServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.paradaux.api.services.impl;

import io.paradaux.api.mappers.IfumMapper;
import io.paradaux.api.services.IfumService;
import org.springframework.stereotype.Service;

@Service
public class IfumServiceImpl implements IfumService {

private final IfumMapper ifumMapper;

public IfumServiceImpl(IfumMapper ifumMapper) {
this.ifumMapper = ifumMapper;
}

@Override
public void insertVisit() {
ifumMapper.insertVisit();
}

@Override
public Integer getVisitCount() {
return ifumMapper.getVisitCount();
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
6 changes: 6 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: example
driver-class-name: org.postgresql.Driver
5 changes: 5 additions & 0 deletions src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
6 changes: 5 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ cloudflare:
secret: ${CLOUDFLARE_TURNSTILE_SECRET}

discord:
webhook: ${DISCORD_WEBHOOK}
webhook: ${DISCORD_WEBHOOK}

mybatis:
type-aliases-package: io.paradaux.api.models
mapper-locations: classpath:mappers/*.xml
17 changes: 17 additions & 0 deletions src/main/resources/db/1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE SCHEMA IF NOT EXISTS api;
CREATE TABLE IF NOT EXISTS api.contact_form_responses
(
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
subject VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE SCHEMA IF NOT EXISTS ifum;
CREATE TABLE IF NOT EXISTS ifum.visits (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
33 changes: 33 additions & 0 deletions src/main/resources/mappers/ContactFormMapper.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="io.paradaux.api.mappers.ContactFormMapper">
<resultMap id="ContactFormEntryResultMap" type="ContactFormEntry">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="subject" column="subject" />
<result property="message" column="message" />
<result property="ipAddress" column="ip_address" />
<result property="createdAt" column="created_at" />
</resultMap>

<insert id="insert" parameterType="ContactFormEntry" useGeneratedKeys="true" keyProperty="id">
INSERT INTO api.contact_form_responses (name, email, subject, message, ip_address)
VALUES (#{name}, #{email}, #{subject}, #{message}, #{ipAddress})
</insert>

<select id="findById" parameterType="int" resultMap="ContactFormEntryResultMap">
SELECT * FROM api.contact_form_responses WHERE id = #{id}
</select>

<select id="findAll" resultMap="ContactFormEntryResultMap">
SELECT * FROM api.contact_form_responses ORDER BY created_at DESC
</select>

<delete id="deleteById" parameterType="int">
DELETE FROM api.contact_form_responses WHERE id = #{id}
</delete>
</mapper>
19 changes: 19 additions & 0 deletions src/main/resources/mappers/IfumMapper.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="io.paradaux.api.mappers.IfumMapper">
<resultMap id="IfumVisitResultMap" type="IfumVisit">
<id property="id" column="id"/>
<result property="createdAt" column="created_at"/>
</resultMap>

<insert id="insertVisit">
INSERT INTO ifum.visits DEFAULT VALUES
</insert>

<select id="getVisitCount" parameterType="int" resultType="Integer">
SELECT COUNT(*) AS count FROM ifum.visits
</select>
</mapper>