RoleService.java

package fr.tiogars.domaintemplate.domains.auth.role.services;

import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import fr.tiogars.domaintemplate.domains.auth.role.entities.Role;
import fr.tiogars.domaintemplate.domains.auth.role.entities.RoleEntity;
import fr.tiogars.domaintemplate.domains.auth.role.models.CreateRoleRequest;
import fr.tiogars.domaintemplate.domains.auth.role.models.UpdateRoleRequest;
import fr.tiogars.domaintemplate.domains.auth.role.repositories.RoleRepository;

/** Coordinates role persistence and default role seeding. */
@Service
public class RoleService {

    public static final String ADMIN = "ADMIN";
    public static final String USER = "USER";

    private static final Set<String> SYSTEM_ROLES = Set.of(ADMIN, USER);

    private final Clock clock;
    private final RoleRepository repository;

    @Autowired
    public RoleService(RoleRepository repository) {
        this(repository, Clock.systemUTC());
    }

    RoleService(RoleRepository repository, Clock clock) {
        this.repository = repository;
        this.clock = clock;
    }

    @Transactional
    public Optional<Role> create(CreateRoleRequest request) {
        if (repository.findByCode(request.code()).isPresent()) {
            return Optional.empty();
        }
        Instant timestamp = currentTimestamp();
        return Optional.of(toRecord(repository.save(new RoleEntity(request.code(), request.label(), timestamp, timestamp))));
    }

    @Transactional
    public Optional<Role> update(Long id, UpdateRoleRequest request) {
        return repository.findById(id)
                .map(existing -> {
                    existing.update(request.label(), currentTimestamp());
                    return toRecord(repository.save(existing));
                });
    }

    @Transactional
    public boolean delete(Long id) {
        return repository.findById(id)
                .filter(existing -> !SYSTEM_ROLES.contains(existing.getCode()))
                .map(existing -> {
                    repository.delete(existing);
                    return true;
                })
                .orElse(false);
    }

    public List<Role> findAll() {
        return repository.findAllByOrderByCodeAsc().stream()
                .map(RoleService::toRecord)
                .toList();
    }

    public Optional<Role> findById(Long id) {
        return repository.findById(id).map(RoleService::toRecord);
    }

    public Optional<RoleEntity> findEntityByCode(String code) {
        return repository.findByCode(code);
    }

    @Transactional
    public List<Role> seedDefaultRoles() {
        return List.of(
                seedSystemRole(ADMIN, "Administrator"),
                seedSystemRole(USER, "User"));
    }

    private Role seedSystemRole(String code, String label) {
        Instant timestamp = currentTimestamp();
        RoleEntity entity = repository.findByCode(code)
                .map(existing -> {
                    existing.update(label, timestamp);
                    return existing;
                })
                .orElseGet(() -> new RoleEntity(code, label, timestamp, timestamp));
        return toRecord(repository.save(entity));
    }

    private Instant currentTimestamp() {
        return Instant.now(clock).truncatedTo(ChronoUnit.MICROS);
    }

    public static Role toRecord(RoleEntity entity) {
        return new Role(entity.getId(), entity.getCode(), entity.getLabel(), entity.getCreatedDate(), entity.getLastModifiedDate());
    }
}