All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.server.graphql.MessageResolver Maven / Gradle / Ivy

There is a newer version: 1.17.6
Show newest version
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))";
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy