NaisConsoleClient.java

package no.nav.data.team.naisteam;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import no.nav.data.common.utils.MetricUtils;
import no.nav.data.common.utils.StreamUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.client.GraphQlClient;
import org.springframework.graphql.client.HttpGraphQlClient;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static java.time.temporal.ChronoUnit.MINUTES;
import static java.util.Collections.singletonList;
import static java.util.Comparator.comparing;
import static no.nav.data.common.utils.StartsWithComparator.startsWith;
import static no.nav.data.common.utils.StreamUtils.safeStream;

@Slf4j
@Service
@EnableConfigurationProperties(NaisConsoleProperties.class)
public class NaisConsoleClient {

    private final GraphQlClient client;

    private final LoadingCache<String, List<NaisTeam>> allTeamsCache;
    private final LoadingCache<String, NaisTeam> teamCache;

    public NaisConsoleClient(WebClient.Builder builder, NaisConsoleProperties consoleProperties) {
        client = HttpGraphQlClient.builder(builder)
                .webClient(client -> {
                    client.baseUrl(consoleProperties.baseUrl());
                    client.defaultHeaders(headers -> {
                        headers.put(HttpHeaders.AUTHORIZATION, singletonList("Bearer " + consoleProperties.auth().token()));
                        headers.put(HttpHeaders.CONTENT_TYPE, singletonList("application/json"));

                    });
                })
                .build();

        this.allTeamsCache = Caffeine.newBuilder().recordStats()
                .expireAfterWrite(Duration.of(10, MINUTES))
                .maximumSize(1)
                .build(k -> fetchAllNaisTeams());

        this.teamCache = Caffeine.newBuilder()
                .recordStats()
                .expireAfterWrite(Duration.of(10, MINUTES))
                .maximumSize(100)
                .build(this::fetchNaisTeam);

        MetricUtils.register("NaisConsoleTeamsCache", allTeamsCache);
        MetricUtils.register("NaisConsoleTeamCache", teamCache);
    }

    public List<NaisTeam> getAllNaisTeams() {
        List<NaisTeam> naisTeams = allTeamsCache.get("singleton");
        return safeStream(naisTeams)
                .distinct()
                .filter(team -> StringUtils.isNotBlank(team.slug()))
                .sorted(comparing(NaisTeam::slug))
                .toList();
    }

    public Optional<NaisTeam> getNaisteam(String teamId) {
        return Optional.ofNullable(teamCache.get(teamId));
    }

    public List<NaisTeam> searchForNaisTeams(String name) {
        var teams = StreamUtils.filter(getAllNaisTeams(), team -> Strings.CI.contains(team.slug(), name));
        teams.sort(comparing(NaisTeam::slug, startsWith(name)));
        return teams;
    }

    public boolean naisTeamExists(String teamId) {
        return getAllNaisTeams().stream().anyMatch(team -> team.slug().equals(teamId));
    }

    private List<NaisTeam> fetchAllNaisTeams() {
        List<NaisTeam> allTeams = new ArrayList<>();
        String cursor = "";
        boolean hasNextPage = true;

        while (hasNextPage) {
            var response = client.document(NaisTeam.TEAMS_QUERY)
                .variable("first", 100)
                .variable("after", cursor)
                .execute()
                .block();

            if (response != null && response.isValid()) {
                var teams = response
                    .field("teams.nodes")
                    .toEntity(new ParameterizedTypeReference<List<NaisTeam>>() {});

                if (teams != null) {
                    allTeams.addAll(teams);
                }

                cursor = response.field("teams.pageInfo.endCursor").toEntity(String.class);
                hasNextPage = Boolean.TRUE.equals(response.field("teams.pageInfo.hasNextPage").toEntity(Boolean.class));
            } else {
                log.error("fetchAllNaisTeams: Received a null response from the GraphQL query.");
                break;
            }
        }

        log.info("fetchAllNaisTeams: Fetched {} nais-teams in total.", allTeams.size());
        return allTeams;
    }

    private NaisTeam fetchNaisTeam(String slug) {
        var response = client.document(NaisTeam.TEAM_QUERY)
                .variable("slug", slug)
                .execute()
                .block();

        return response != null && response.isValid() ? response.field("team").toEntity(NaisTeam.class) : null;
    }
}