com.xlrit.gears.server.graphql.MessageResolver Maven / Gradle / Ivy
package com.xlrit.gears.server.graphql;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import com.xlrit.gears.base.exception.AuthException;
import com.xlrit.gears.base.model.Message;
import com.xlrit.gears.base.repository.MessageRepository;
import com.xlrit.gears.base.repository.Repository;
import com.xlrit.gears.base.util.Range;
import com.xlrit.gears.base.util.StringUtils;
import com.xlrit.gears.engine.security.AuthManager;
import com.xlrit.gears.server.tmp.MessageEventService;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.DataFetchingFieldSelectionSet;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import static com.xlrit.gears.engine.util.EngineUtils.requireFound;
import static java.util.Objects.requireNonNull;
@Controller
@RequiredArgsConstructor
public class MessageResolver {
private static final Logger LOG = LoggerFactory.getLogger(MessageResolver.class);
private final MessageRepository messageRepository;
private final MessageEventService messageEventService;
private final EntityManager entityManager;
private final AuthManager authManager;
@QueryMapping
@PreAuthorize("isAuthenticated()")
public Message message(@Argument String id, DataFetchingEnvironment env) {
EntityGraph loadGraph = createEntityGraph(env.getSelectionSet());
Message message = messageRepository.findById(id, loadGraph);
requireFound(message, "message", "id", id);
String userId = StringUtils.trim(requireNonNull(authManager.getCurrentUserId()));
String recipientId = StringUtils.trim(message.getRecipient().getId());
if (!Objects.equals(userId, recipientId)) {
LOG.debug("userId='{}', recipientId='{}'", userId, recipientId);
throw new AuthException("Current user is not the intended message recipient");
}
return message;
}
@QueryMapping
@PreAuthorize("isAuthenticated()")
public List myMessages(@Argument Boolean read, @Argument Range range, DataFetchingEnvironment env) {
String userId = requireNonNull(authManager.getCurrentUserId());
return entityManager.createQuery("SELECT m FROM Message m WHERE " + myMessagesPredicate + " ORDER BY m.publishedAt DESC", Message.class)
.setParameter("userId", userId)
.setParameter("read", read)
.setFirstResult(range.getStart())
.setMaxResults(range.getCount())
.setHint(Repository.LOADGRAPH, createEntityGraph(env.getSelectionSet()))
.getResultList();
}
@QueryMapping
@PreAuthorize("isAuthenticated()")
public long myMessagesCount(@Argument Boolean read) {
String userId = requireNonNull(authManager.getCurrentUserId());
return entityManager.createQuery("SELECT count(m) FROM Message m WHERE " + myMessagesPredicate, Long.class)
.setParameter("userId", userId)
.setParameter("read", read)
.getSingleResult();
}
@MutationMapping
@PreAuthorize("isAuthenticated()")
@Transactional
public int deleteMessage(@Argument String id) {
String userId = requireNonNull(authManager.getCurrentUserId());
return entityManager.createQuery("DELETE FROM Message m WHERE m.id = :id AND m.recipient.id = :userId")
.setParameter("id", id)
.setParameter("userId", userId)
.executeUpdate();
}
@MutationMapping
@PreAuthorize("isAuthenticated()")
@Transactional
public int markMessageRead(@Argument String id, @Argument boolean read) {
String userId = requireNonNull(authManager.getCurrentUserId());
OffsetDateTime readAt = read ? OffsetDateTime.now() : null;
return entityManager.createQuery("UPDATE Message m SET m.readAt = :readAt WHERE m.id = :id AND m.recipient.id = :userId")
.setParameter("id", id)
.setParameter("userId", userId)
.setParameter("readAt", readAt)
.executeUpdate();
}
@SubscriptionMapping
@PreAuthorize("isAuthenticated()")
public Publisher messages() {
// see:
// https://www.graphql-java.com/documentation/subscriptions/
// https://stackoverflow.com/questions/44270248/graphql-java-how-to-use-subscriptions-with-spring-boot
// https://github.com/npalm/blog-graphql-spring-service/tree/master
String userId = requireNonNull(authManager.getCurrentUserId());
LOG.info("Returning message publisher for user {}", userId);
return messageEventService.subscribe(userId);
}
public EntityGraph createEntityGraph(DataFetchingFieldSelectionSet selectionSet) {
EntityGraph entityGraph = entityManager.createEntityGraph(Message.class);
if (selectionSet.contains("sender/*")) {
entityGraph.addAttributeNodes("sender");
}
if (selectionSet.contains("recipient/*")) {
entityGraph.addAttributeNodes("recipient");
}
return entityGraph;
}
private static final String myMessagesPredicate =
"m.recipient.id = :userId AND m.publishedAt IS NOT NULL AND " +
"((:read IS NULL) OR (:read = TRUE and m.readAt IS NOT NULL) OR (:read = FALSE and m.readAt IS NULL))";
}