new admin-user api

This commit is contained in:
2025-12-08 07:10:10 -05:00
parent 0845443767
commit 7a8ec969b5
18 changed files with 1196 additions and 1004 deletions

View File

@@ -13,4 +13,6 @@ public interface UserRepository extends JpaRepository<User, Integer> {
boolean existsByEmailIgnoreCaseAndDeletedAtIsNull(String email);
Optional<User> findByUuid(UUID uuid);
boolean existsByRole(String role);
}

View File

@@ -0,0 +1,55 @@
package group.goforward.ballistic.services.admin;
import group.goforward.ballistic.model.User;
import group.goforward.ballistic.repos.UserRepository;
import group.goforward.ballistic.web.dto.admin.AdminUserDto;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@Service
public class AdminUserService {
private static final Set<String> ALLOWED_ROLES = Set.of("USER", "ADMIN");
private final UserRepository userRepository;
public AdminUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<AdminUserDto> getAllUsersForAdmin() {
return userRepository.findAll()
.stream()
.map(AdminUserDto::fromUser)
.toList();
}
@Transactional
public AdminUserDto updateUserRole(UUID userUuid, String newRole, Authentication auth) {
if (newRole == null || !ALLOWED_ROLES.contains(newRole.toUpperCase())) {
throw new IllegalArgumentException("Invalid role: " + newRole);
}
User user = userRepository.findByUuid(userUuid)
.orElseThrow(() -> new IllegalArgumentException("User not found"));
// Optional safety: do not allow demoting yourself (you can loosen this later)
String currentEmail = auth != null ? auth.getName() : null;
boolean isSelf = currentEmail != null
&& currentEmail.equalsIgnoreCase(user.getEmail());
if (isSelf && !"ADMIN".equalsIgnoreCase(newRole)) {
throw new IllegalStateException("You cannot change your own role to non-admin.");
}
user.setRole(newRole.toUpperCase());
// updatedAt will be handled by your entity / DB defaults
return AdminUserDto.fromUser(user);
}
}

View File

@@ -0,0 +1,37 @@
package group.goforward.ballistic.web.admin;
import group.goforward.ballistic.services.admin.AdminUserService;
import group.goforward.ballistic.web.dto.admin.AdminUserDto;
import group.goforward.ballistic.web.dto.admin.UpdateUserRoleRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public class AdminUserController {
private final AdminUserService adminUserService;
public AdminUserController(AdminUserService adminUserService) {
this.adminUserService = adminUserService;
}
@GetMapping
public List<AdminUserDto> listUsers() {
return adminUserService.getAllUsersForAdmin();
}
@PatchMapping("/{uuid}/role")
public AdminUserDto updateRole(
@PathVariable("uuid") UUID uuid,
@RequestBody UpdateUserRoleRequest request,
Authentication auth
) {
return adminUserService.updateUserRole(uuid, request.getRole(), auth);
}
}

View File

@@ -0,0 +1,76 @@
package group.goforward.ballistic.web.dto.admin;
import group.goforward.ballistic.model.User;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminUserDto {
// We'll expose the UUID as the "id" used by the frontend
private UUID id;
private String email;
private String displayName;
private String role;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
private OffsetDateTime lastLoginAt;
public AdminUserDto(UUID id,
String email,
String displayName,
String role,
OffsetDateTime createdAt,
OffsetDateTime updatedAt,
OffsetDateTime lastLoginAt) {
this.id = id;
this.email = email;
this.displayName = displayName;
this.role = role;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.lastLoginAt = lastLoginAt;
}
public static AdminUserDto fromUser(User user) {
return new AdminUserDto(
user.getUuid(), // use UUID here (stable id)
user.getEmail(),
user.getDisplayName(),
user.getRole(), // String: "USER" / "ADMIN"
user.getCreatedAt(),
user.getUpdatedAt(),
user.getLastLoginAt()
);
}
// Getters (and setters if you want Jackson to use them)
public UUID getId() {
return id;
}
public String getEmail() {
return email;
}
public String getDisplayName() {
return displayName;
}
public String getRole() {
return role;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public OffsetDateTime getLastLoginAt() {
return lastLoginAt;
}
}

View File

@@ -0,0 +1,21 @@
package group.goforward.ballistic.web.dto.admin;
public class UpdateUserRoleRequest {
private String role;
public UpdateUserRoleRequest() {
}
public UpdateUserRoleRequest(String role) {
this.role = role;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@@ -13,9 +13,10 @@ spring.datasource.driver-class-name=org.postgresql.Driver
security.jwt.secret=ballistic-test-secret-key-1234567890-ABCDEFGHIJKLNMOPQRST
security.jwt.access-token-minutes=2880
# Temp disabling logging to find what I fucked up
spring.jpa.show-sql=false
logging.level.org.hibernate.SQL=warn
# Logging
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=INFO
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=warn