"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Redis = void 0;
const tslib_1 = require("tslib");
const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
const misc_1 = require("../helpers/custom-validators/misc");
const extra_utils_1 = require("../../shared/extra-utils");
const logger_1 = require("../helpers/logger");
const utils_1 = require("../helpers/utils");
const config_1 = require("../initializers/config");
const constants_1 = require("../initializers/constants");
class Redis {
    constructor() {
        this.initialized = false;
        this.connected = false;
    }
    init() {
        if (this.initialized === true)
            return;
        this.initialized = true;
        logger_1.logger.info('Connecting to redis...');
        this.client = new ioredis_1.default(Redis.getRedisClientOptions('', { enableAutoPipelining: true }));
        this.client.on('error', err => logger_1.logger.error('Redis failed to connect', { err }));
        this.client.on('connect', () => {
            logger_1.logger.info('Connected to redis.');
            this.connected = true;
        });
        this.client.on('reconnecting', (ms) => {
            logger_1.logger.error(`Reconnecting to redis in ${ms}.`);
        });
        this.client.on('close', () => {
            logger_1.logger.error('Connection to redis has closed.');
            this.connected = false;
        });
        this.client.on('end', () => {
            logger_1.logger.error('Connection to redis has closed and no more reconnects will be done.');
        });
        this.prefix = 'redis-' + constants_1.WEBSERVER.HOST + '-';
    }
    static getRedisClientOptions(connectionName, options = {}) {
        return Object.assign({ connectionName: ['PeerTube', connectionName].join(''), connectTimeout: 20000, password: config_1.CONFIG.REDIS.AUTH, db: config_1.CONFIG.REDIS.DB, host: config_1.CONFIG.REDIS.HOSTNAME, port: config_1.CONFIG.REDIS.PORT, path: config_1.CONFIG.REDIS.SOCKET, showFriendlyErrorStack: true }, options);
    }
    getClient() {
        return this.client;
    }
    getPrefix() {
        return this.prefix;
    }
    isConnected() {
        return this.connected;
    }
    setResetPasswordVerificationString(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const generatedString = yield (0, utils_1.generateRandomString)(32);
            yield this.setValue(this.generateResetPasswordKey(userId), generatedString, constants_1.USER_PASSWORD_RESET_LIFETIME);
            return generatedString;
        });
    }
    setCreatePasswordVerificationString(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const generatedString = yield (0, utils_1.generateRandomString)(32);
            yield this.setValue(this.generateResetPasswordKey(userId), generatedString, constants_1.USER_PASSWORD_CREATE_LIFETIME);
            return generatedString;
        });
    }
    removePasswordVerificationString(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.removeValue(this.generateResetPasswordKey(userId));
        });
    }
    getResetPasswordVerificationString(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.getValue(this.generateResetPasswordKey(userId));
        });
    }
    setTwoFactorRequest(userId, otpSecret) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const requestToken = yield (0, utils_1.generateRandomString)(32);
            yield this.setValue(this.generateTwoFactorRequestKey(userId, requestToken), otpSecret, constants_1.TWO_FACTOR_AUTH_REQUEST_TOKEN_LIFETIME);
            return requestToken;
        });
    }
    getTwoFactorRequestToken(userId, requestToken) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.getValue(this.generateTwoFactorRequestKey(userId, requestToken));
        });
    }
    setVerifyEmailVerificationString(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const generatedString = yield (0, utils_1.generateRandomString)(32);
            yield this.setValue(this.generateVerifyEmailKey(userId), generatedString, constants_1.USER_EMAIL_VERIFY_LIFETIME);
            return generatedString;
        });
    }
    getVerifyEmailLink(userId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.getValue(this.generateVerifyEmailKey(userId));
        });
    }
    setContactFormIp(ip) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.setValue(this.generateContactFormKey(ip), '1', constants_1.CONTACT_FORM_LIFETIME);
        });
    }
    doesContactFormIpExist(ip) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.exists(this.generateContactFormKey(ip));
        });
    }
    setIPVideoView(ip, videoUUID) {
        return this.setValue(this.generateIPViewKey(ip, videoUUID), '1', constants_1.VIEW_LIFETIME.VIEW);
    }
    doesVideoIPViewExist(ip, videoUUID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.exists(this.generateIPViewKey(ip, videoUUID));
        });
    }
    setTrackerBlockIP(ip) {
        return this.setValue(this.generateTrackerBlockIPKey(ip), '1', constants_1.TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME);
    }
    doesTrackerBlockIPExist(ip) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.exists(this.generateTrackerBlockIPKey(ip));
        });
    }
    addVideoViewStats(videoId) {
        const { videoKey, setKey } = this.generateVideoViewStatsKeys({ videoId });
        return Promise.all([
            this.addToSet(setKey, videoId.toString()),
            this.increment(videoKey)
        ]);
    }
    getVideoViewsStats(videoId, hour) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { videoKey } = this.generateVideoViewStatsKeys({ videoId, hour });
            const valueString = yield this.getValue(videoKey);
            const valueInt = parseInt(valueString, 10);
            if (isNaN(valueInt)) {
                logger_1.logger.error('Cannot get videos views stats of video %d in hour %d: views number is NaN (%s).', videoId, hour, valueString);
                return undefined;
            }
            return valueInt;
        });
    }
    listVideosViewedForStats(hour) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { setKey } = this.generateVideoViewStatsKeys({ hour });
            const stringIds = yield this.getSet(setKey);
            return stringIds.map(s => parseInt(s, 10));
        });
    }
    deleteVideoViewsStats(videoId, hour) {
        const { setKey, videoKey } = this.generateVideoViewStatsKeys({ videoId, hour });
        return Promise.all([
            this.deleteFromSet(setKey, videoId.toString()),
            this.deleteKey(videoKey)
        ]);
    }
    addLocalVideoView(videoId) {
        const { videoKey, setKey } = this.generateLocalVideoViewsKeys(videoId);
        return Promise.all([
            this.addToSet(setKey, videoId.toString()),
            this.increment(videoKey)
        ]);
    }
    getLocalVideoViews(videoId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { videoKey } = this.generateLocalVideoViewsKeys(videoId);
            const valueString = yield this.getValue(videoKey);
            const valueInt = parseInt(valueString, 10);
            if (isNaN(valueInt)) {
                logger_1.logger.error('Cannot get videos views of video %d: views number is NaN (%s).', videoId, valueString);
                return undefined;
            }
            return valueInt;
        });
    }
    listLocalVideosViewed() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { setKey } = this.generateLocalVideoViewsKeys();
            const stringIds = yield this.getSet(setKey);
            return stringIds.map(s => parseInt(s, 10));
        });
    }
    deleteLocalVideoViews(videoId) {
        const { setKey, videoKey } = this.generateLocalVideoViewsKeys(videoId);
        return Promise.all([
            this.deleteFromSet(setKey, videoId.toString()),
            this.deleteKey(videoKey)
        ]);
    }
    getLocalVideoViewer(options) {
        if (options.key)
            return this.getObject(options.key);
        const { viewerKey } = this.generateLocalVideoViewerKeys(options.ip, options.videoId);
        return this.getObject(viewerKey);
    }
    setLocalVideoViewer(ip, videoId, object) {
        const { setKey, viewerKey } = this.generateLocalVideoViewerKeys(ip, videoId);
        return Promise.all([
            this.addToSet(setKey, viewerKey),
            this.setObject(viewerKey, object)
        ]);
    }
    listLocalVideoViewerKeys() {
        const { setKey } = this.generateLocalVideoViewerKeys();
        return this.getSet(setKey);
    }
    deleteLocalVideoViewersKeys(key) {
        const { setKey } = this.generateLocalVideoViewerKeys();
        return Promise.all([
            this.deleteFromSet(setKey, key),
            this.deleteKey(key)
        ]);
    }
    setUploadSession(uploadId, response) {
        return this.setValue('resumable-upload-' + uploadId, response
            ? JSON.stringify(response)
            : '', constants_1.RESUMABLE_UPLOAD_SESSION_LIFETIME);
    }
    doesUploadSessionExist(uploadId) {
        return this.exists('resumable-upload-' + uploadId);
    }
    getUploadSession(uploadId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const value = yield this.getValue('resumable-upload-' + uploadId);
            return value
                ? JSON.parse(value)
                : '';
        });
    }
    deleteUploadSession(uploadId) {
        return this.deleteKey('resumable-upload-' + uploadId);
    }
    addAPUnavailability(url) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const key = this.generateAPUnavailabilityKey(url);
            const value = yield this.increment(key);
            yield this.setExpiration(key, constants_1.AP_CLEANER.PERIOD * 2);
            return value;
        });
    }
    generateLocalVideoViewsKeys(videoId) {
        return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` };
    }
    generateLocalVideoViewerKeys(ip, videoId) {
        return { setKey: `local-video-viewer-stats-keys`, viewerKey: `local-video-viewer-stats-${ip}-${videoId}` };
    }
    generateVideoViewStatsKeys(options) {
        const hour = (0, misc_1.exists)(options.hour)
            ? options.hour
            : new Date().getHours();
        return { setKey: `videos-view-h${hour}`, videoKey: `video-view-${options.videoId}-h${hour}` };
    }
    generateResetPasswordKey(userId) {
        return 'reset-password-' + userId;
    }
    generateTwoFactorRequestKey(userId, token) {
        return 'two-factor-request-' + userId + '-' + token;
    }
    generateVerifyEmailKey(userId) {
        return 'verify-email-' + userId;
    }
    generateIPViewKey(ip, videoUUID) {
        return `views-${videoUUID}-${ip}`;
    }
    generateTrackerBlockIPKey(ip) {
        return `tracker-block-ip-${ip}`;
    }
    generateContactFormKey(ip) {
        return 'contact-form-' + ip;
    }
    generateAPUnavailabilityKey(url) {
        return 'ap-unavailability-' + (0, extra_utils_1.sha256)(url);
    }
    getValue(key) {
        return this.client.get(this.prefix + key);
    }
    getSet(key) {
        return this.client.smembers(this.prefix + key);
    }
    addToSet(key, value) {
        return this.client.sadd(this.prefix + key, value);
    }
    deleteFromSet(key, value) {
        return this.client.srem(this.prefix + key, value);
    }
    deleteKey(key) {
        return this.client.del(this.prefix + key);
    }
    getObject(key) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const value = yield this.getValue(key);
            if (!value)
                return null;
            return JSON.parse(value);
        });
    }
    setObject(key, value, expirationMilliseconds) {
        return this.setValue(key, JSON.stringify(value), expirationMilliseconds);
    }
    setValue(key, value, expirationMilliseconds) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const result = expirationMilliseconds !== undefined
                ? yield this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds)
                : yield this.client.set(this.prefix + key, value);
            if (result !== 'OK')
                throw new Error('Redis set result is not OK.');
        });
    }
    removeValue(key) {
        return this.client.del(this.prefix + key);
    }
    increment(key) {
        return this.client.incr(this.prefix + key);
    }
    exists(key) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const result = yield this.client.exists(this.prefix + key);
            return result !== 0;
        });
    }
    setExpiration(key, ms) {
        return this.client.expire(this.prefix + key, ms / 1000);
    }
    static get Instance() {
        return this.instance || (this.instance = new this());
    }
}
exports.Redis = Redis;
//# sourceMappingURL=redis.js.map