<commit_before>import { Requests } from "./Requests";
import { AsyncLoadingCache, Caches, CacheStats, ICacheBase, LoadingCache, SimpleCache } from "@inventivetalent/loading-cache";
import * as Sentry from "@sentry/node";
import { Severity } from "@sentry/node";
import { Maybe, sha256, sha512, stripUuid } from "../util";
import { IPoint } from "influx";
import { Skin, Traffic } from "../database/schemas";
import { BasicMojangProfile } from "./Authentication";
import { getConfig } from "../typings/Configs";
import { SkinData } from "../typings/SkinData";
import { User } from "../typings/User";
import { ISkinDocument } from "../typings";
import { ProfileResponse } from "../typings/ProfileResponse";
import { metrics } from "../util/metrics";
import { Bread } from "../typings/Bread";
import { IApiKeyDocument } from "../typings/db/IApiKeyDocument";
import { ApiKey } from "../database/schemas/ApiKey";
import { IPendingDiscordLink } from "../typings/DiscordAccountLink";
import { Time } from "@inventivetalent/time";

const config = getConfig();

export class Caching {

    //// REQUESTS

    protected static readonly skinDataCache: AsyncLoadingCache<string, SkinData> = Caches.builder()
        .expireAfterWrite(Time.minutes(1))
        .expirationInterval(Time.seconds(10))
        .buildAsync<string, SkinData>(uuid => {
            return Requests.mojangSessionRequest({
                url: "/session/minecraft/profile/" + uuid + "?unsigned=false"
            }).then(response => {
                if (!Requests.isOk(response) || !response.data.hasOwnProperty("properties")) {
                    return undefined;
                }
                const body = response.data as ProfileResponse;
                return body.properties[0] as SkinData;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "skinData"
                    }
                });
                return undefined;
            });
        });

    protected static readonly userByNameCache: AsyncLoadingCache<string, User> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.minutes(1))
        .buildAsync<string, User>(name => {
            return Requests.mojangApiRequest({
                url: "/users/profiles/minecraft/" + name,

            }).then(response => {
                let d = {
                    valid: false,
                    uuid: undefined,
                    name: name
                } as User;
                if (Requests.isOk(response) && (response.data && response.data.hasOwnProperty("id"))) {
                    const body = response.data;
                    d = {
                        valid: true,
                        uuid: body["id"],
                        name: body["name"]
                    } as User;
                    // update other cache
                    Caching.userByUuidCache.put(d.uuid!, d);
                }
                return d;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "userByName"
                    }
                });
                return {
                    valid: false,
                    uuid: undefined,
                    name: name
                } as User;
            });
        });
    protected static readonly userByUuidCache: AsyncLoadingCache<string, User> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.minutes(1))
        .buildAsync<string, User>(uuid => {
            uuid = stripUuid(uuid);
            return Requests.mojangApiRequest({
                url: "/user/profiles/" + uuid + "/names"
            }).then(response => {
                let d = {
                    valid: false,
                    uuid: uuid,
                    name: undefined
                } as User;
                if (Requests.isOk(response) && (response.data && response.data.length > 0)) {
                    const body = response.data;
                    d = {
                        valid: true,
                        uuid: uuid,
                        name: body[body.length - 1]["name"]
                    } as User;
                    // update other cache
                    Caching.userByNameCache.put(d.name!.toLowerCase(), d);
                }
                return d;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "userByUuid"
                    }
                });
                return {
                    valid: false,
                    uuid: uuid,
                    name: undefined
                } as User;
            });
        });

    protected static readonly profileByAccessTokenCache: AsyncLoadingCache<string, BasicMojangProfile> = Caches.builder()
        .expireAfterWrite(Time.minutes(2))
        .expirationInterval(Time.seconds(30))
        .buildAsync<string, BasicMojangProfile>(accessToken => {
            return Requests.minecraftServicesRequest({
                method: "GET",
                url: "/minecraft/profile",
                headers: {
                    "Authorization": `Bearer ${ accessToken }`
                }
            }).then(response => {
                return response.data as BasicMojangProfile
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "profileByAccessToken"
                    }
                });
                return undefined;
            });
        })

    //// DATABASE

    protected static readonly trafficByIpCache: AsyncLoadingCache<string, Date> = Caches.builder()
        .expireAfterWrite(Time.seconds(5))
        .expirationInterval(Time.seconds(1))
        .buildAsync<string, Date>(async (ip) => {
            const traffic = await Traffic.findForIp(ip);
            return traffic?.lastRequest;
        });

    protected static readonly skinByIdCache: AsyncLoadingCache<number, ISkinDocument> = Caches.builder()
        .expireAfterWrite(Time.seconds(20))
        .expirationInterval(Time.seconds(5))
        .buildAsync<number, ISkinDocument>(id => Skin.findForId(id));

    protected static readonly apiKeyCache: AsyncLoadingCache<string, IApiKeyDocument> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expireAfterAccess(Time.minutes(1))
        .expirationInterval(Time.seconds(20))
        .buildAsync<string, IApiKeyDocument>(key => ApiKey.findKey(key));

    protected static readonly skinDocumentCounts: AsyncLoadingCache<string, number> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expireAfterAccess(Time.minutes(1))
        .expirationInterval(Time.seconds(10))
        .buildAsync<string, number>(key => Skin.countDocuments(JSON.parse(key)).exec());

    //// OTHER

    protected static readonly pendingDiscordLinkByStateCache: SimpleCache<string, IPendingDiscordLink> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.seconds(30))
        .build();

    protected static readonly recentAccountsLock: SimpleCache<number, string> = Caches.builder()
        .expireAfterWrite(Time.minutes(1))
        .expirationInterval(Time.seconds(20))
        .build();

    protected static readonly hashCache: LoadingCache<string, string> = Caches.builder()
        .expireAfterAccess(Time.seconds(40))
        .expirationInterval(Time.seconds(10))
        .build(key => sha512(key));

    ////

    protected static metricsCollector = setInterval(() => {
        const caches = new Map<string, ICacheBase<any, any>>([
            ["skinData", Caching.skinDataCache],<commit_msg>cache longer<commit_after>import { Requests } from "./Requests";
import { AsyncLoadingCache, Caches, CacheStats, ICacheBase, LoadingCache, SimpleCache } from "@inventivetalent/loading-cache";
import * as Sentry from "@sentry/node";
import { Severity } from "@sentry/node";
import { Maybe, sha256, sha512, stripUuid } from "../util";
import { IPoint } from "influx";
import { Skin, Traffic } from "../database/schemas";
import { BasicMojangProfile } from "./Authentication";
import { getConfig } from "../typings/Configs";
import { SkinData } from "../typings/SkinData";
import { User } from "../typings/User";
import { ISkinDocument } from "../typings";
import { ProfileResponse } from "../typings/ProfileResponse";
import { metrics } from "../util/metrics";
import { Bread } from "../typings/Bread";
import { IApiKeyDocument } from "../typings/db/IApiKeyDocument";
import { ApiKey } from "../database/schemas/ApiKey";
import { IPendingDiscordLink } from "../typings/DiscordAccountLink";
import { Time } from "@inventivetalent/time";

const config = getConfig();

export class Caching {

    //// REQUESTS

    protected static readonly skinDataCache: AsyncLoadingCache<string, SkinData> = Caches.builder()
        .expireAfterWrite(Time.minutes(1))
        .expirationInterval(Time.seconds(10))
        .buildAsync<string, SkinData>(uuid => {
            return Requests.mojangSessionRequest({
                url: "/session/minecraft/profile/" + uuid + "?unsigned=false"
            }).then(response => {
                if (!Requests.isOk(response) || !response.data.hasOwnProperty("properties")) {
                    return undefined;
                }
                const body = response.data as ProfileResponse;
                return body.properties[0] as SkinData;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "skinData"
                    }
                });
                return undefined;
            });
        });

    protected static readonly userByNameCache: AsyncLoadingCache<string, User> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.minutes(1))
        .buildAsync<string, User>(name => {
            return Requests.mojangApiRequest({
                url: "/users/profiles/minecraft/" + name,

            }).then(response => {
                let d = {
                    valid: false,
                    uuid: undefined,
                    name: name
                } as User;
                if (Requests.isOk(response) && (response.data && response.data.hasOwnProperty("id"))) {
                    const body = response.data;
                    d = {
                        valid: true,
                        uuid: body["id"],
                        name: body["name"]
                    } as User;
                    // update other cache
                    Caching.userByUuidCache.put(d.uuid!, d);
                }
                return d;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "userByName"
                    }
                });
                return {
                    valid: false,
                    uuid: undefined,
                    name: name
                } as User;
            });
        });
    protected static readonly userByUuidCache: AsyncLoadingCache<string, User> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.minutes(1))
        .buildAsync<string, User>(uuid => {
            uuid = stripUuid(uuid);
            return Requests.mojangApiRequest({
                url: "/user/profiles/" + uuid + "/names"
            }).then(response => {
                let d = {
                    valid: false,
                    uuid: uuid,
                    name: undefined
                } as User;
                if (Requests.isOk(response) && (response.data && response.data.length > 0)) {
                    const body = response.data;
                    d = {
                        valid: true,
                        uuid: uuid,
                        name: body[body.length - 1]["name"]
                    } as User;
                    // update other cache
                    Caching.userByNameCache.put(d.name!.toLowerCase(), d);
                }
                return d;
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "userByUuid"
                    }
                });
                return {
                    valid: false,
                    uuid: uuid,
                    name: undefined
                } as User;
            });
        });

    protected static readonly profileByAccessTokenCache: AsyncLoadingCache<string, BasicMojangProfile> = Caches.builder()
        .expireAfterWrite(Time.minutes(2))
        .expirationInterval(Time.seconds(30))
        .buildAsync<string, BasicMojangProfile>(accessToken => {
            return Requests.minecraftServicesRequest({
                method: "GET",
                url: "/minecraft/profile",
                headers: {
                    "Authorization": `Bearer ${ accessToken }`
                }
            }).then(response => {
                return response.data as BasicMojangProfile
            }).catch(err => {
                Sentry.captureException(err, {
                    level: Severity.Warning,
                    tags: {
                        cache: "profileByAccessToken"
                    }
                });
                return undefined;
            });
        })

    //// DATABASE

    protected static readonly trafficByIpCache: AsyncLoadingCache<string, Date> = Caches.builder()
        .expireAfterWrite(Time.seconds(5))
        .expirationInterval(Time.seconds(1))
        .buildAsync<string, Date>(async (ip) => {
            const traffic = await Traffic.findForIp(ip);
            return traffic?.lastRequest;
        });

    protected static readonly skinByIdCache: AsyncLoadingCache<number, ISkinDocument> = Caches.builder()
        .expireAfterWrite(Time.seconds(20))
        .expirationInterval(Time.seconds(5))
        .buildAsync<number, ISkinDocument>(id => Skin.findForId(id));

    protected static readonly apiKeyCache: AsyncLoadingCache<string, IApiKeyDocument> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expireAfterAccess(Time.minutes(1))
        .expirationInterval(Time.seconds(20))
        .buildAsync<string, IApiKeyDocument>(key => ApiKey.findKey(key));

    protected static readonly skinDocumentCounts: AsyncLoadingCache<string, number> = Caches.builder()
        .expireAfterWrite(Time.minutes(10))
        .expireAfterAccess(Time.minutes(5))
        .expirationInterval(Time.seconds(30))
        .buildAsync<string, number>(key => Skin.countDocuments(JSON.parse(key)).exec());

    //// OTHER

    protected static readonly pendingDiscordLinkByStateCache: SimpleCache<string, IPendingDiscordLink> = Caches.builder()
        .expireAfterWrite(Time.minutes(5))
        .expirationInterval(Time.seconds(30))
        .build();

    protected static readonly recentAccountsLock: SimpleCache<number, string> = Caches.builder()
        .expireAfterWrite(Time.minutes(1))
        .expirationInterval(Time.seconds(20))
        .build();

    protected static readonly hashCache: LoadingCache<string, string> = Caches.builder()
        .expireAfterAccess(Time.seconds(40))
        .expirationInterval(Time.seconds(10))
        .build(key => sha512(key));

    ////

    protected static metricsCollector = setInterval(() => {
        const caches = new Map<string, ICacheBase<any, any>>([
            ["skinData", Caching.skinDataCache],
{'fraction_non_alphanumeric': 0.116375, 'fraction_numerical': 0.0056875, 'mean_word_length': 1.6983136593591905, 'pattern_counts': {'":': 2, '<': 45, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '85733', 'n_tokens_mistral': 4221, 'n_tokens_neox': 3957, 'n_words': 814}