"use strict";
/*!
 * Connect - TypeORM
 * Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
 * Copyright(c) 2017, 2018 makepost <makepost@firemail.cc>
 * Copyright(c) 2018 Nathan Phillip Brink <ohnobinki@ohnopublishing.net>
 * MIT Licensed
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeormStore = void 0;
const Debug = require("debug");
const express_session_1 = require("express-session");
const typeorm_1 = require("typeorm");
/**
 * One day in seconds.
 */
const oneDay = 86400;
class TypeormStore extends express_session_1.Store {
    /**
     * Initializes TypeormStore with the given `options`.
     */
    constructor(options = {}) {
        super(options);
        this.debug = Debug("connect:typeorm");
        this.limitSubquery = true;
        /**
         * Attempts to fetch session by the given `sid`.
         */
        this.get = (sid, fn) => {
            this.debug('GET "%s"', sid);
            this.createQueryBuilder()
                .andWhere("session.id = :id", { id: sid })
                .getOne()
                .then((session) => {
                if (!session) {
                    return fn();
                }
                let result;
                this.debug("GOT %s", session.json);
                result = JSON.parse(session.json);
                fn(undefined, result);
            })
                .catch((er) => {
                fn(er);
                this.handleError(er);
            });
        };
        /**
         * Commits the given `sess` object associated with the given `sid`.
         */
        this.set = (sid, sess, fn) => {
            const args = [sid];
            let json;
            try {
                json = JSON.stringify(sess);
            }
            catch (er) {
                return fn ? fn(er) : undefined;
            }
            args.push(json);
            const ttl = this.getTTL(sess, sid);
            args.push("EX", ttl.toString());
            this.debug('SET "%s" %s ttl:%s', sid, json, ttl);
            (this.cleanupLimit
                ? (() => {
                    const $ = this.repository
                        .createQueryBuilder("session")
                        .withDeleted()
                        .select("session.id")
                        .where(`session.expiredAt <= ${Date.now()}`)
                        .limit(this.cleanupLimit);
                    return this.limitSubquery
                        ? Promise.resolve($.getQuery())
                        : $.getMany().then((xs) => xs.length
                            ? xs
                                .map((x) => typeof x.id === "string"
                                ? `'${x.id
                                    .replace(/\\/g, "\\\\")
                                    .replace(/'/g, "\\'")}'`
                                : `${x.id}`)
                                .join(", ")
                            : "NULL");
                })().then((ids) => this.repository
                    .createQueryBuilder()
                    .delete()
                    .where(`id IN (${ids})`)
                    .execute())
                : Promise.resolve())
                // @ts-ignore
                .then(async () => {
                try {
                    // If a session with the given SID is already present (even deleted), renew it.
                    // Else, create a new row/session.
                    await this.repository.findOneOrFail({ where: { id: sid }, withDeleted: true });
                    await this.repository.update({
                        destroyedAt: (0, typeorm_1.IsNull)(),
                        id: sid,
                    }, {
                        expiredAt: Date.now() + (ttl * 1000),
                        json,
                    });
                }
                catch (_) {
                    await this.repository.insert({
                        expiredAt: Date.now() + (ttl * 1000),
                        id: sid,
                        json,
                    });
                }
            })
                .then(() => {
                this.debug("SET complete");
                if (fn) {
                    fn();
                }
            })
                .catch((er) => {
                if (fn) {
                    fn(er);
                }
                this.handleError(er);
            });
        };
        /**
         * Destroys the session associated with the given `sid`.
         */
        this.destroy = (sid, fn) => {
            this.debug('DEL "%s"', sid);
            Promise.all((Array.isArray(sid) ? sid : [sid]).map((x) => {
                this.repository.softDelete({ id: x });
            }))
                .then(() => {
                if (fn) {
                    fn();
                }
            })
                .catch((er) => {
                if (fn) {
                    fn(er);
                }
                this.handleError(er);
            });
        };
        /**
         * Refreshes the time-to-live for the session with the given `sid`.
         */
        this.touch = (sid, sess, fn) => {
            var _a;
            const ttl = this.getTTL(sess);
            if ((_a = sess === null || sess === void 0 ? void 0 : sess.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
                this.debug('Skip updating session "%s" expiration', sid);
                if (fn) {
                    fn();
                }
            }
            this.debug('EXPIRE "%s" ttl:%s', sid, ttl);
            this.repository
                .createQueryBuilder()
                .update({ expiredAt: Date.now() + ttl * 1000 })
                .whereInIds([sid])
                .execute()
                .then(() => {
                this.debug("EXPIRE complete");
                if (fn) {
                    fn();
                }
            })
                .catch((er) => {
                if (fn) {
                    fn(er);
                }
                this.handleError(er);
            });
        };
        /**
         * Fetches all sessions.
         */
        this.all = (fn) => {
            let result = [];
            this.createQueryBuilder()
                .getMany()
                .then((sessions) => {
                result = sessions.map((session) => {
                    const sess = JSON.parse(session.json);
                    sess.id = session.id;
                    return sess;
                });
                fn(undefined, result);
            })
                .catch((er) => {
                fn(er, result);
                this.handleError(er);
            });
        };
        this.cleanupLimit = options.cleanupLimit;
        if (options.limitSubquery !== undefined) {
            this.limitSubquery = options.limitSubquery;
        }
        this.onError = options.onError;
        this.ttl = options.ttl;
    }
    connect(repository) {
        this.repository = repository;
        this.emit("connect");
        return this;
    }
    createQueryBuilder() {
        return this.repository.createQueryBuilder("session")
            .where("session.expiredAt > :expiredAt", { expiredAt: Date.now() });
    }
    getTTL(sess, sid) {
        if (typeof this.ttl === "number") {
            return this.ttl;
        }
        if (typeof this.ttl === "function") {
            return this.ttl(this, sess, sid);
        }
        const maxAge = sess.cookie.maxAge;
        return (typeof maxAge === "number"
            ? Math.floor(maxAge / 1000)
            : oneDay);
    }
    handleError(er) {
        this.debug("Typeorm returned err", er);
        if (this.onError) {
            this.onError(this, er);
        }
        else {
            this.emit("disconnect", er);
        }
    }
}
exports.TypeormStore = TypeormStore;
//# sourceMappingURL=TypeormStore.js.map