Add staff application support with database integration and submission flow
This commit is contained in:
parent
f886609a0e
commit
643b15f2e0
|
|
@ -1,15 +1,92 @@
|
||||||
package com.alttd.altitudeweb.controllers.forms;
|
package com.alttd.altitudeweb.controllers.forms;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.api.ApplicationsApi;
|
import com.alttd.altitudeweb.api.ApplicationsApi;
|
||||||
|
import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.forms.StaffApplicationMapper;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
|
||||||
|
import com.alttd.altitudeweb.mappers.StaffApplicationDataMapper;
|
||||||
import com.alttd.altitudeweb.model.FormResponseDto;
|
import com.alttd.altitudeweb.model.FormResponseDto;
|
||||||
import com.alttd.altitudeweb.model.StaffApplicationDto;
|
import com.alttd.altitudeweb.model.StaffApplicationDto;
|
||||||
import org.springframework.http.HttpStatusCode;
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.HOURS)
|
||||||
public class ApplicationController implements ApplicationsApi {
|
public class ApplicationController implements ApplicationsApi {
|
||||||
|
|
||||||
|
private final StaffApplicationDataMapper staffApplicationDataMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
|
public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
|
||||||
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Staff applications are not yet supported");
|
UUID userUuid = AuthenticatedUuid.getAuthenticatedUserUuid();
|
||||||
|
|
||||||
|
StaffApplication application = staffApplicationDataMapper.map(userUuid, staffApplicationDto);
|
||||||
|
saveApplication(application);
|
||||||
|
|
||||||
|
Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, application.email());
|
||||||
|
boolean verified = optionalEmail.map(EmailVerification::verified).orElse(false);
|
||||||
|
|
||||||
|
if (verified) {
|
||||||
|
markAsSent(application.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
FormResponseDto response = buildResponse(application, verified);
|
||||||
|
return ResponseEntity.status(201).body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveApplication(StaffApplication application) {
|
||||||
|
CompletableFuture<Void> saveFuture = new CompletableFuture<>();
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
try {
|
||||||
|
sqlSession.getMapper(StaffApplicationMapper.class).insert(application);
|
||||||
|
saveFuture.complete(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to insert staff application", e);
|
||||||
|
saveFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
saveFuture.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<EmailVerification> fetchEmailVerification(UUID userUuid, String email) {
|
||||||
|
CompletableFuture<Optional<EmailVerification>> emailVerificationFuture = new CompletableFuture<>();
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
|
||||||
|
.findByUserAndEmail(userUuid, email);
|
||||||
|
emailVerificationFuture.complete(Optional.ofNullable(verifiedMail));
|
||||||
|
});
|
||||||
|
return emailVerificationFuture.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markAsSent(UUID applicationId) {
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> sqlSession.getMapper(StaffApplicationMapper.class).markAsSent(applicationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormResponseDto buildResponse(StaffApplication application, boolean verified) {
|
||||||
|
String message = verified
|
||||||
|
? "Your staff application has been submitted. You will be notified when it has been reviewed."
|
||||||
|
: "Application created. Please verify your email to complete submission.";
|
||||||
|
return new FormResponseDto(
|
||||||
|
application.id().toString(),
|
||||||
|
message,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.alttd.altitudeweb.mappers;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
|
||||||
|
import com.alttd.altitudeweb.model.StaffApplicationDto;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StaffApplicationDataMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the incoming DTO and the authenticated user's UUID to a StaffApplication entity.
|
||||||
|
* Normalizes and prepares fields as needed (lowercase email, join availableDays, timestamps, ids).
|
||||||
|
*/
|
||||||
|
public StaffApplication map(UUID userUuid, StaffApplicationDto dto) {
|
||||||
|
String email = dto.getEmail() == null ? null : dto.getEmail().toLowerCase();
|
||||||
|
String availableDaysJoined = joinList(dto.getAvailableDays());
|
||||||
|
|
||||||
|
return new StaffApplication(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
userUuid,
|
||||||
|
email,
|
||||||
|
dto.getAge(),
|
||||||
|
dto.getDiscordUsername(),
|
||||||
|
Boolean.TRUE.equals(dto.getMeetsRequirements()),
|
||||||
|
dto.getPronouns(),
|
||||||
|
dto.getJoinDate(),
|
||||||
|
dto.getWeeklyPlaytime(),
|
||||||
|
availableDaysJoined,
|
||||||
|
dto.getAvailableTimes(),
|
||||||
|
dto.getPreviousExperience(),
|
||||||
|
dto.getPluginExperience(),
|
||||||
|
dto.getModeratorExpectations(),
|
||||||
|
dto.getAdditionalInfo(),
|
||||||
|
Instant.now(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinList(List<String> list) {
|
||||||
|
if (list == null) return null;
|
||||||
|
// Avoid NPEs and trim entries
|
||||||
|
return list.stream()
|
||||||
|
.filter(s -> s != null && !s.isBlank())
|
||||||
|
.map(String::trim)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.alttd.altitudeweb.database.web_db.forms;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record StaffApplication(
|
||||||
|
UUID id,
|
||||||
|
UUID uuid,
|
||||||
|
String email,
|
||||||
|
Integer age,
|
||||||
|
String discordUsername,
|
||||||
|
Boolean meetsRequirements,
|
||||||
|
String pronouns,
|
||||||
|
LocalDate joinDate,
|
||||||
|
Integer weeklyPlaytime,
|
||||||
|
String availableDays,
|
||||||
|
String availableTimes,
|
||||||
|
String previousExperience,
|
||||||
|
String pluginExperience,
|
||||||
|
String moderatorExpectations,
|
||||||
|
String additionalInfo,
|
||||||
|
Instant createdAt,
|
||||||
|
Instant sendAt,
|
||||||
|
Long assignedTo
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.alttd.altitudeweb.database.web_db.forms;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface StaffApplicationMapper {
|
||||||
|
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO staff_applications (
|
||||||
|
id, uuid, email, age, discord_username, meets_requirements, pronouns, join_date,
|
||||||
|
weekly_playtime, available_days, available_times, previous_experience, plugin_experience,
|
||||||
|
moderator_expectations, additional_info, created_at, send_at, assigned_to
|
||||||
|
) VALUES (
|
||||||
|
#{id}, #{uuid}, #{email}, #{age}, #{discordUsername}, #{meetsRequirements}, #{pronouns}, #{joinDate},
|
||||||
|
#{weeklyPlaytime},
|
||||||
|
#{availableDays},
|
||||||
|
#{availableTimes}, #{previousExperience}, #{pluginExperience},
|
||||||
|
#{moderatorExpectations}, #{additionalInfo}, #{createdAt}, #{sendAt}, #{assignedTo}
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
void insert(StaffApplication application);
|
||||||
|
|
||||||
|
@Update("""
|
||||||
|
UPDATE staff_applications SET send_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
""")
|
||||||
|
void markAsSent(@Param("id") UUID id);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.PrivilegedUserMapper;
|
import com.alttd.altitudeweb.database.web_db.PrivilegedUserMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.forms.AppealMapper;
|
import com.alttd.altitudeweb.database.web_db.forms.AppealMapper;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.forms.StaffApplicationMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
|
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
|
|
@ -23,6 +24,7 @@ public class InitializeWebDb {
|
||||||
configuration.addMapper(KeyPairMapper.class);
|
configuration.addMapper(KeyPairMapper.class);
|
||||||
configuration.addMapper(PrivilegedUserMapper.class);
|
configuration.addMapper(PrivilegedUserMapper.class);
|
||||||
configuration.addMapper(AppealMapper.class);
|
configuration.addMapper(AppealMapper.class);
|
||||||
|
configuration.addMapper(StaffApplicationMapper.class);
|
||||||
configuration.addMapper(EmailVerificationMapper.class);
|
configuration.addMapper(EmailVerificationMapper.class);
|
||||||
}).join()
|
}).join()
|
||||||
.runQuery(sqlSession -> {
|
.runQuery(sqlSession -> {
|
||||||
|
|
@ -31,6 +33,7 @@ public class InitializeWebDb {
|
||||||
createPrivilegedUsersTable(sqlSession);
|
createPrivilegedUsersTable(sqlSession);
|
||||||
createPrivilegesTable(sqlSession);
|
createPrivilegesTable(sqlSession);
|
||||||
createAppealTable(sqlSession);
|
createAppealTable(sqlSession);
|
||||||
|
createStaffApplicationsTable(sqlSession);
|
||||||
createUserEmailsTable(sqlSession);
|
createUserEmailsTable(sqlSession);
|
||||||
});
|
});
|
||||||
log.debug("Initialized WebDb");
|
log.debug("Initialized WebDb");
|
||||||
|
|
@ -126,6 +129,37 @@ public class InitializeWebDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void createStaffApplicationsTable(@NotNull SqlSession sqlSession) {
|
||||||
|
String query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS staff_applications (
|
||||||
|
id UUID NOT NULL DEFAULT (UUID()) PRIMARY KEY,
|
||||||
|
uuid UUID NOT NULL,
|
||||||
|
email VARCHAR(320) NOT NULL,
|
||||||
|
age INT NOT NULL,
|
||||||
|
discord_username VARCHAR(32) NOT NULL,
|
||||||
|
meets_requirements BOOLEAN NOT NULL,
|
||||||
|
pronouns VARCHAR(32) NULL,
|
||||||
|
join_date DATE NOT NULL,
|
||||||
|
weekly_playtime INT NOT NULL,
|
||||||
|
available_days TEXT NOT NULL,
|
||||||
|
available_times TEXT NOT NULL,
|
||||||
|
previous_experience TEXT NOT NULL,
|
||||||
|
plugin_experience TEXT NOT NULL,
|
||||||
|
moderator_expectations TEXT NOT NULL,
|
||||||
|
additional_info TEXT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
send_at TIMESTAMP NULL,
|
||||||
|
assigned_to BIGINT UNSIGNED NULL,
|
||||||
|
FOREIGN KEY (uuid) REFERENCES privileged_users(uuid) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
try (Statement statement = sqlSession.getConnection().createStatement()) {
|
||||||
|
statement.execute(query);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void createAppealTable(@NotNull SqlSession sqlSession) {
|
private static void createAppealTable(@NotNull SqlSession sqlSession) {
|
||||||
String query = """
|
String query = """
|
||||||
CREATE TABLE IF NOT EXISTS appeals (
|
CREATE TABLE IF NOT EXISTS appeals (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user