"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Redis = void 0;
const tslib_1 = require("tslib");
const redis_1 = require("redis");
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");
const redisClientWrapperForType = () => (0, redis_1.createClient)();
class Redis {
    constructor() {
        this.initialized = false;
        this.connected = false;
    }
    init() {
        if (this.initialized === true)
            return;
        this.initialized = true;
        this.client = (0, redis_1.createClient)(Redis.getRedisClientOptions());
        logger_1.logger.info('Connecting to redis...');
        this.client.connect()
            .then(() => {
            logger_1.logger.info('Connected to redis.');
            this.connected = true;
        }).catch(err => {
            logger_1.logger.error('Cannot connect to redis', { err });
            process.exit(-1);
        });
        this.prefix = 'redis-' + constants_1.WEBSERVER.HOST + '-';
    }
    static getRedisClientOptions() {
        let config = {
            socket: {
                connectTimeout: 20000
            }
        };
        if (config_1.CONFIG.REDIS.AUTH) {
            config = Object.assign(Object.assign({}, config), { password: config_1.CONFIG.REDIS.AUTH });
        }
        if (config_1.CONFIG.REDIS.DB) {
            config = Object.assign(Object.assign({}, config), { database: config_1.CONFIG.REDIS.DB });
        }
        if (config_1.CONFIG.REDIS.HOSTNAME && config_1.CONFIG.REDIS.PORT) {
            config.socket = Object.assign(Object.assign({}, config.socket), { host: config_1.CONFIG.REDIS.HOSTNAME, port: config_1.CONFIG.REDIS.PORT });
        }
        else {
            config.socket = Object.assign(Object.assign({}, config.socket), { path: config_1.CONFIG.REDIS.SOCKET });
        }
        return config;
    }
    getClient() {
        return this.client;
    }
    getPrefix() {
        return this.prefix;
    }
    isConnected() {
        return this.connected;
    }
    setResetPasswordVerificationString(userId) {
        return (0, 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 (0, 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 (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.removeValue(this.generateResetPasswordKey(userId));
        });
    }
    getResetPasswordLink(userId) {
        return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.getValue(this.generateResetPasswordKey(userId));
        });
    }
    setVerifyEmailVerificationString(userId) {
        return (0, 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 (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.getValue(this.generateVerifyEmailKey(userId));
        });
    }
    setContactFormIp(ip) {
        return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.setValue(this.generateContactFormKey(ip), '1', constants_1.CONTACT_FORM_LIFETIME);
        });
    }
    doesContactFormIpExist(ip) {
        return (0, 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);
    }
    setIPVideoViewer(ip, videoUUID) {
        return this.setValue(this.generateIPViewerKey(ip, videoUUID), '1', constants_1.VIEW_LIFETIME.VIEWER);
    }
    doesVideoIPViewExist(ip, videoUUID) {
        return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.exists(this.generateIPViewKey(ip, videoUUID));
        });
    }
    doesVideoIPViewerExist(ip, videoUUID) {
        return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            return this.exists(this.generateIPViewerKey(ip, videoUUID));
        });
    }
    setTrackerBlockIP(ip) {
        return this.setValue(this.generateTrackerBlockIPKey(ip), '1', constants_1.TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME);
    }
    doesTrackerBlockIPExist(ip) {
        return (0, 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 (0, 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 (0, 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 (0, 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 (0, 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)
        ]);
    }
    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 (0, 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 (0, 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}` };
    }
    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;
    }
    generateVerifyEmailKey(userId) {
        return 'verify-email-' + userId;
    }
    generateIPViewKey(ip, videoUUID) {
        return `views-${videoUUID}-${ip}`;
    }
    generateIPViewerKey(ip, videoUUID) {
        return `viewer-${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);
    }
    setValue(key, value, expirationMilliseconds) {
        return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            const result = yield this.client.set(this.prefix + key, value, { PX: expirationMilliseconds });
            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 (0, 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