"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DBHelper = void 0;
const Crypt_1 = require("./Crypt");
const openflow_api_1 = require("@openiap/openflow-api");
const Config_1 = require("./Config");
const Logger_1 = require("./Logger");
const Auth_1 = require("./Auth");
const LoginProvider_1 = require("./LoginProvider");
const cacheManager = require("cache-manager");
const amqpwrapper_1 = require("./amqpwrapper");
// var cacheManager = require('cache-manager');
var redisStore = require('cache-manager-ioredis');
var mongoStore = require('@skadefro/cache-manager-mongodb');
class DBHelper {
    constructor() {
        this.item_cache = null;
    }
    async init() {
        if (!openflow_api_1.NoderedUtil.IsNullUndefinded(this.memoryCache))
            return;
        this.mongoCache = cacheManager.caching({
            store: mongoStore,
            uri: Config_1.Config.mongodb_url,
            options: {
                collection: "_cache",
                compression: false,
                poolSize: 5
            }
        });
        if (Config_1.Config.cache_store_type == "mongodb") {
            this.memoryCache = this.mongoCache;
            this.ensureotel();
            return;
        }
        else if (Config_1.Config.cache_store_type == "redis") {
            this.memoryCache = cacheManager.caching({
                store: redisStore,
                host: Config_1.Config.cache_store_redis_host,
                port: Config_1.Config.cache_store_redis_port,
                password: Config_1.Config.cache_store_redis_password,
                ignoreCacheErrors: true,
                db: 0,
                ttl: Config_1.Config.cache_store_ttl_seconds,
                max: Config_1.Config.cache_store_max
            });
            // listen for redis connection error event
            var redisClient = this.memoryCache.store.getClient();
            redisClient.on('error', (error) => {
                Logger_1.Logger.instanse.error(error, null);
            });
            this.ensureotel();
            return;
        }
        this.memoryCache = cacheManager.caching({
            store: 'memory',
            ignoreCacheErrors: true,
            max: Config_1.Config.cache_store_max,
            ttl: Config_1.Config.cache_store_ttl_seconds
        });
        this.ensureotel();
    }
    async clearCache(reason, span) {
        await this.init();
        var keys;
        if (Config_1.Config.cache_store_type == "redis") {
            keys = await this.memoryCache.keys('*');
        }
        else {
            keys = await this.memoryCache.keys();
        }
        for (var i = 0; i < keys.length; i++) {
            if (keys[i] && !keys[i].startsWith("requesttoken")) {
                this.memoryCache.del(keys[i]);
            }
        }
        Logger_1.Logger.instanse.debug("clearCache called with reason: " + reason, span);
    }
    ensureotel() {
        var _a;
        if (!openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel) && !openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel.meter) && openflow_api_1.NoderedUtil.IsNullUndefinded(this.item_cache)) {
            this.item_cache = Logger_1.Logger.otel.meter.createObservableGauge("openflow_item_cache_count", {
                description: 'Total number of cached items'
            });
            (_a = this.item_cache) === null || _a === void 0 ? void 0 : _a.addCallback(async (res) => {
                var keys = null;
                try {
                    if (Config_1.Config.cache_store_type == "redis") {
                        keys = await this.memoryCache.keys('*');
                    }
                    else {
                        keys = await this.memoryCache.keys();
                    }
                }
                catch (error) {
                }
                if (keys != null) {
                    res.observe(keys.length, { ...Logger_1.Logger.otel.defaultlabels, type: Config_1.Config.cache_store_type });
                }
                else {
                    res.observe(0, { ...Logger_1.Logger.otel.defaultlabels, type: Config_1.Config.cache_store_type });
                }
            });
        }
    }
    async FindByIdWrap(_id, span) {
        Logger_1.Logger.instanse.debug("Add user to cache : " + _id, span);
        return Config_1.Config.db.getbyid(_id, "users", Crypt_1.Crypt.rootToken(), true, span);
    }
    async FindById(_id, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(_id))
                return null;
            var key = ("users_" + _id).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindByIdWrap(_id, span); });
            this.ensureotel();
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item)) {
                Logger_1.Logger.instanse.debug("No user matches " + _id, span);
                return null;
            }
            Logger_1.Logger.instanse.silly("Return user " + _id + " " + item.formvalidated, span);
            var res2 = await this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
            return res2;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    GetResourcesWrap(span) {
        Logger_1.Logger.instanse.debug("Add resources user to cache", span);
        return Config_1.Config.db.query({ query: { "_type": "resource" }, collectionname: "config", jwt: Crypt_1.Crypt.rootToken() }, span);
    }
    async GetResources(parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetResources", parent);
        try {
            let items = await this.memoryCache.wrap("resource", () => { return this.GetResourcesWrap(span); });
            Logger_1.Logger.instanse.silly("Return " + items.length + " resources", span);
            return items;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    GetResourceUsageByUserIDWrap(userid, span) {
        Logger_1.Logger.instanse.debug("Add user resources to cache : " + userid, span);
        return Config_1.Config.db.query({ query: { "_type": "resourceusage", userid }, collectionname: "config", jwt: Crypt_1.Crypt.rootToken() }, span);
    }
    async GetResourceUsageByUserID(userid, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetResourceUsageByUserID", parent);
        try {
            var key = ("resourceusage_" + userid).toString().toLowerCase();
            let items = await this.memoryCache.wrap(key, () => { return this.GetResourceUsageByUserIDWrap(userid, span); });
            Logger_1.Logger.instanse.silly("Return resources for user " + userid, span);
            return items;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    GetProvidersWrap(span) {
        return Config_1.Config.db.query({ query: { _type: "provider" }, top: 10, collectionname: "config", jwt: Crypt_1.Crypt.rootToken() }, span);
    }
    async GetProviders(parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetProviders", parent);
        try {
            let items = await this.memoryCache.wrap("providers", () => { return this.GetProvidersWrap(span); });
            // const result: Provider[] = [];
            // https://www.w3schools.com/icons/fontawesome5_icons_brands.asp
            items.forEach(provider => {
                // const item: any = { name: provider.name, id: provider.id, provider: provider.provider, logo: "fa-question-circle" };
                provider.logo = "fa-microsoft";
                if (provider.provider === "oidc") {
                    provider.logo = "fa-openid";
                }
                if (provider.provider === "google") {
                    provider.logo = "fa-google";
                }
                if (provider.provider === "saml") {
                    provider.logo = "fa-windows";
                }
                //result.push(item);
            });
            if (items.length === 0) {
                const item = { name: "Local", id: "local", provider: "local", logo: "fa-question-circle" };
                items.push(item);
            }
            return items;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async ClearProviders() {
        await this.memoryCache.del("providers");
    }
    async FindRequestTokenID(key, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindRequestTokenID", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(key))
                return null;
            if (Config_1.Config.cache_store_type == "redis") {
                return await this.memoryCache.get("requesttoken" + key);
            }
            else {
                return await this.mongoCache.get("requesttoken" + key);
            }
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async AdddRequestTokenID(key, data, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindRequestTokenID", parent);
        try {
            if (Config_1.Config.cache_store_type == "redis") {
                return await this.memoryCache.set("requesttoken" + key, data);
            }
            else {
                return await this.mongoCache.set("requesttoken" + key, data);
            }
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async RemoveRequestTokenID(key, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindRequestTokenID", parent);
        try {
            if (Config_1.Config.cache_store_type == "redis") {
                return await this.memoryCache.del("requesttoken" + key);
            }
            else {
                return await this.mongoCache.del("requesttoken" + key);
            }
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindByAuthorizationWrap(token, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add authentication header to cache", span);
        return LoginProvider_1.LoginProvider.validateToken(token, span);
    }
    FindByAuthorizationWrap2(login, password, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add basicauth header to cache", span);
        return Auth_1.Auth.ValidateByPassword(login, password, span);
    }
    async FindByAuthorization(authorization, jwt, span) {
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(authorization) && authorization.indexOf(" ") > 1 &&
            (authorization.toLocaleLowerCase().startsWith("bearer") || authorization.toLocaleLowerCase().startsWith("jwt"))) {
            const token = authorization.split(" ")[1].toString();
            let item = await this.memoryCache.wrap(token, () => { return this.FindByAuthorizationWrap(token, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        const b64auth = (authorization || '').split(' ')[1].toString() || '';
        // const [login, password] = new Buffer(b64auth, 'base64').toString().split(':')
        const [login, password] = Buffer.from(b64auth, "base64").toString().split(':');
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(login) && !openflow_api_1.NoderedUtil.IsNullEmpty(password)) {
            let item = await this.memoryCache.wrap(b64auth, () => { return this.FindByAuthorizationWrap2(login, password, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
    }
    FindQueueByIdWrap(_id, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add queue to cache : " + _id, span);
        return Config_1.Config.db.getbyid(_id, "mq", jwt, true, span);
    }
    async FindQueueById(_id, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(_id))
                return null;
            var key = ("mq_" + _id).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindQueueByIdWrap(_id, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindQueueByNameWrap(name, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add queue to cache : " + name, span);
        return Config_1.Config.db.GetOne({ query: { name }, collectionname: "mq", jwt }, span);
    }
    async FindQueueByName(name, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(name))
                return null;
            var key = ("queuename_" + name).toString();
            let item = await this.memoryCache.wrap(key, () => { return this.FindQueueByNameWrap(name, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindExchangeByIdWrap(_id, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add exchange to cache : " + _id, span);
        return Config_1.Config.db.getbyid(_id, "mq", jwt, true, span);
    }
    async FindExchangeById(_id, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(_id))
                return null;
            var key = ("mq_" + _id).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindExchangeByIdWrap(_id, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindExchangeByNameWrap(name, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add exchange to cache : " + name, span);
        return Config_1.Config.db.GetOne({ query: { name }, collectionname: "mq", jwt }, span);
    }
    async FindExchangeByName(name, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(name))
                return null;
            var key = ("exchangename_" + name).toString();
            let item = await this.memoryCache.wrap(key, () => { return this.FindExchangeByNameWrap(name, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindRoleByIdWrap(_id, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add role to cache : " + _id, span);
        return Config_1.Config.db.getbyid(_id, "users", jwt, true, span);
    }
    async FindRoleById(_id, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindById", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(_id))
                return null;
            var key = ("users_" + _id).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindRoleByIdWrap(_id, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return openflow_api_1.Role.assign(item);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindByUsernameWrap(username, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add user to cache by username : " + username, span);
        return Config_1.Config.db.getbyusername(username, null, jwt, true, span);
    }
    async FindByUsername(username, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindByUsername", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(username))
                return null;
            var key = ("username_" + username).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindByUsernameWrap(username, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    GetDisposableDomainWrap(domain, span) {
        const jwt = Crypt_1.Crypt.rootToken();
        Logger_1.Logger.instanse.debug("Add to cache : " + domain, span);
        const query = { name: domain, "_type": "disposable" };
        return Config_1.Config.db.GetOne({ query, collectionname: "domains", jwt }, span);
    }
    async GetDisposableDomain(domain, parent) {
        await this.init();
        if (domain.indexOf("@") > -1) {
            domain = domain.substring(domain.indexOf("@") + 1);
        }
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindByUsername", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(domain))
                return null;
            var key = ("disposable_" + domain).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.GetDisposableDomainWrap(domain, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return item;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    FindByUsernameOrFederationidWrap(username, issuer, span) {
        const jwt = Crypt_1.Crypt.rootToken();
        Logger_1.Logger.instanse.debug("Add federationid to cache : " + username, span);
        return Config_1.Config.db.getbyusername(username, issuer, jwt, true, span);
    }
    async FindByUsernameOrFederationid(username, issuer, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindByUsername", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullEmpty(username))
                return null;
            var key = ("federation_" + username).toString().toLowerCase();
            let item = await this.memoryCache.wrap(key, () => { return this.FindByUsernameOrFederationidWrap(username, issuer, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return this.DecorateWithRoles(openflow_api_1.User.assign(item), span);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    DecorateWithRolesWrap(user, span) {
        Logger_1.Logger.instanse.debug("Add userroles to cache : " + user._id + " " + user.name, span);
        const pipe = [{ "$match": { "_id": user._id } },
            {
                "$graphLookup": {
                    from: "users",
                    startWith: "$_id",
                    connectFromField: "_id",
                    connectToField: "members._id",
                    as: "roles",
                    maxDepth: Config_1.Config.max_recursive_group_depth,
                    depthField: "depth",
                    restrictSearchWithMatch: { "_type": "role" }
                    // , "_id": { $nin: Config.db.WellknownIdsArray }, "members._id": { $nin: Config.db.WellknownIdsArray }
                }
            }, {
                "$graphLookup": {
                    from: "users",
                    startWith: "$_id",
                    connectFromField: "members._id",
                    connectToField: "members._id",
                    as: "roles2",
                    maxDepth: 0,
                    depthField: "depth",
                    restrictSearchWithMatch: { "_type": "role" }
                }
            },
            {
                "$project": {
                    _id: 1,
                    name: 1,
                    username: 1,
                    roles: {
                        $map: {
                            input: "$roles",
                            as: "roles",
                            in: {
                                "name": "$$roles.name",
                                "_id": "$$roles._id"
                            }
                        }
                    },
                    roles2: {
                        $map: {
                            input: "$roles2",
                            as: "roles2",
                            in: {
                                "name": "$$roles2.name",
                                "_id": "$$roles2._id"
                            }
                        }
                    }
                }
            }
        ];
        return Config_1.Config.db.aggregate(pipe, "users", Crypt_1.Crypt.rootToken(), null, span);
    }
    DecorateWithRolesAllRolesWrap(span) {
        Logger_1.Logger.instanse.debug("Add all roles", span);
        return Config_1.Config.db.query({ query: { _type: "role" }, projection: { "name": 1, "members": 1 }, top: Config_1.Config.expected_max_roles, collectionname: "users", jwt: Crypt_1.Crypt.rootToken() }, span);
    }
    async DecorateWithRoles(user, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.DecorateWithRoles", parent);
        try {
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(user))
                return null;
            if (!Config_1.Config.decorate_roles_fetching_all_roles) {
                if (!user.roles)
                    user.roles = [];
                var key = ("userroles_" + user._id).toString().toLowerCase();
                const results = await this.memoryCache.wrap(key, () => { return this.DecorateWithRolesWrap(user, span); });
                var key = ("userroles_" + openflow_api_1.WellknownIds.users).toString().toLowerCase();
                const users_results = await this.memoryCache.wrap(key, () => { return this.DecorateWithRolesWrap({ "_id": openflow_api_1.WellknownIds.users, "name": "users" }, span); });
                if (results.length > 0) {
                    user.roles = [];
                    results[0].roles.forEach(r => {
                        const exists = user.roles.filter(x => x._id == r._id);
                        if (exists.length == 0) {
                            user.roles.push(r);
                            Logger_1.Logger.instanse.silly("adding (from roles) " + r.name + " " + r._id, span);
                        }
                    });
                    results[0].roles2.forEach(r => {
                        const exists = user.roles.filter(x => x._id == r._id);
                        if (exists.length == 0) {
                            user.roles.push(r);
                            Logger_1.Logger.instanse.silly("adding (from roles2) " + r.name + " " + r._id, span);
                        }
                    });
                    if (users_results.length > 0) {
                        users_results[0].roles.forEach(r => {
                            const exists = user.roles.filter(x => x._id == r._id);
                            if (exists.length == 0) {
                                user.roles.push(r);
                                Logger_1.Logger.instanse.silly("also adding (from users roles) " + r.name + " " + r._id, span);
                            }
                        });
                        users_results[0].roles2.forEach(r => {
                            const exists = user.roles.filter(x => x._id == r._id);
                            if (exists.length == 0) {
                                user.roles.push(r);
                                Logger_1.Logger.instanse.silly("also adding (from users roles2) " + r.name + " " + r._id, span);
                            }
                        });
                    }
                }
                let hasusers = user.roles.filter(x => x._id == openflow_api_1.WellknownIds.users);
                if (hasusers.length == 0) {
                    user.roles.push(new openflow_api_1.Rolemember("users", openflow_api_1.WellknownIds.users));
                    Logger_1.Logger.instanse.verbose("also adding user to users " + openflow_api_1.WellknownIds.users, span);
                    // Logger.instanse.debug(user.name + " missing from users, adding it", span);
                    // await Config.db.db.collection("users").updateOne(
                    //     { _id: WellknownIds.users },
                    //     { "$push": { members: new Rolemember(user.name, user._id) } }
                    // );
                }
                return user;
            }
            let cached_roles = await this.memoryCache.wrap("allroles", () => { return this.DecorateWithRolesAllRolesWrap(span); });
            if (cached_roles.length === 0 && user.username !== "root") {
                throw new Error("System has no roles !!!!!!");
            }
            user.roles = [];
            for (let role of cached_roles) {
                let isMember = -1;
                if (role.members !== undefined) {
                    isMember = role.members.map(function (e) { return e._id; }).indexOf(user._id);
                }
                if (isMember > -1) {
                    user.roles.push(new openflow_api_1.Rolemember(role.name, role._id));
                }
            }
            let hasusers = user.roles.filter(x => x._id == openflow_api_1.WellknownIds.users);
            if (hasusers.length == 0) {
                user.roles.push(new openflow_api_1.Rolemember("users", openflow_api_1.WellknownIds.users));
            }
            let updated = false;
            do {
                updated = false;
                for (let userrole of user.roles) {
                    for (let role of cached_roles) {
                        let isMember = -1;
                        if (role.members !== undefined) {
                            isMember = role.members.map(function (e) { return e._id; }).indexOf(userrole._id);
                        }
                        if (isMember > -1) {
                            const beenAdded = user.roles.map(function (e) { return e._id; }).indexOf(role._id);
                            if (beenAdded === -1) {
                                user.roles.push(new openflow_api_1.Rolemember(role.name, role._id));
                                updated = true;
                            }
                        }
                    }
                }
            } while (updated);
            user.roles.sort((a, b) => a.name.localeCompare(b.name));
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
        return user;
    }
    FindRoleByNameWrap(name, jwt, span) {
        if (jwt === null || jwt == undefined || jwt == "") {
            jwt = Crypt_1.Crypt.rootToken();
        }
        Logger_1.Logger.instanse.debug("Add role to cache : " + name, span);
        return Config_1.Config.db.GetOne({ query: { name: name, "_type": "role" }, collectionname: "users", jwt }, span);
    }
    async FindRoleByName(name, jwt, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.FindByUsername", parent);
        try {
            var key = ("rolename_" + name).toString();
            let item = await this.memoryCache.wrap(key, async () => { return this.FindRoleByNameWrap(name, jwt, span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(item))
                return null;
            return openflow_api_1.Role.assign(item);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async UserRoleUpdateId(id, watch, span) {
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(id))
            return;
        var u = new openflow_api_1.Base();
        u._id = id;
        return this.UserRoleUpdate(u, watch, span);
    }
    async DeleteKey(key, watch, frombroadcast, span) {
        if (!this._doClear(watch, span))
            return;
        // moght have more than one api node, but don't have shared cache, so broadcast to all
        if (Config_1.Config.enable_openflow_amqp && Config_1.Config.cache_store_type != "redis" && Config_1.Config.cache_store_type != "mongodb") {
            if (!Config_1.Config.unittesting && !frombroadcast) {
                Logger_1.Logger.instanse.debug("Send clearcache command for " + key, span);
                amqpwrapper_1.amqpwrapper.Instance().send("openflow", "", { "command": "clearcache", "key": key }, 20000, null, "", span, 1);
            }
        }
        else if (!Config_1.Config.enable_openflow_amqp) { // only one api node, since not using queue, so remove key
            await Logger_1.Logger.DBHelper.memoryCache.del(key);
        }
        else if (!watch) { // more than one api node, but using shared cache, so only need to clear once
            await Logger_1.Logger.DBHelper.memoryCache.del(key);
        }
    }
    _doClear(watch, span) {
        var doit = false;
        if (watch) {
            doit = false;
            if (Config_1.Config.cache_store_type != "redis" && Config_1.Config.cache_store_type != "mongodb") {
                doit = true;
            }
        }
        else {
            doit = true;
        }
        return doit;
    }
    async UserRoleUpdate(userrole, watch, span) {
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(userrole))
            return;
        if (!this._doClear(watch, span))
            return;
        if (userrole._type == "user") {
            Logger_1.Logger.instanse.debug("Remove user from cache : " + userrole._id, span);
            let u = userrole;
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(u._id))
                await this.DeleteKey(("users_" + u._id).toString(), watch, false, span);
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(u.username))
                await this.DeleteKey(("username_" + u.username).toString(), watch, false, span);
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(u.email))
                await this.DeleteKey(("username_" + u.email).toString(), watch, false, span);
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(u._id))
                await this.DeleteKey(("userroles_" + u._id).toString(), watch, false, span);
            if (u.federationids != null && Array.isArray(u.federationids)) {
                for (var i = 0; i < u.federationids.length; i++) {
                    var fed = u.federationids[i];
                    if (fed == null)
                        continue;
                    // has self property with value id
                    if (fed.hasOwnProperty("id")) {
                        await this.DeleteKey(("federation_" + fed.id).toString(), watch, false, span);
                    }
                    else {
                        await this.DeleteKey(("federation_" + fed).toString(), watch, false, span);
                    }
                }
            }
            await this.DeleteKey("allroles", watch, false, span);
        }
        else if (userrole._type == "role") {
            let r = userrole;
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(r._id))
                await this.DeleteKey(("users_" + r._id).toString(), watch, false, span);
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(r.name))
                await this.DeleteKey(("rolename_" + r.name).toString(), watch, false, span);
            if (userrole._id != openflow_api_1.WellknownIds.users) {
                if (r.members != null && Array.isArray(r.members)) {
                    for (var i = 0; i < r.members.length; i++) {
                        var member = r.members[i];
                        this.UserRoleUpdate(member, watch, span);
                    }
                }
            }
            await this.DeleteKey("allroles", watch, false, span);
        }
        else if (userrole._type == "customer") {
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(userrole._id))
                await this.DeleteKey(("users_" + userrole._id).toString(), watch, false, span);
        }
    }
    async QueueUpdate(_id, name, watch, span) {
        Logger_1.Logger.instanse.debug("Clear queue cache : " + name + " " + _id, span);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(name))
            await this.DeleteKey(("queuename_" + name).toString(), watch, false, span);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(_id))
            await this.DeleteKey(("mq_" + _id).toString(), watch, false, span);
    }
    async ExchangeUpdate(_id, name, watch, span) {
        Logger_1.Logger.instanse.debug("Clear exchange cache : " + name + " " + _id, span);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(name))
            await this.DeleteKey(("exchangename_" + name).toString(), watch, false, span);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(_id))
            await this.DeleteKey(("mq_" + _id).toString(), watch, false, span);
    }
    async WorkitemQueueUpdate(wiqid, watch, span) {
        Logger_1.Logger.instanse.debug("Clear workitem queue cache : " + wiqid, span);
        await this.DeleteKey("pushablequeues", watch, false, span);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(wiqid))
            await this.DeleteKey("pendingworkitems_" + wiqid, watch, false, span);
    }
    GetPushableQueuesWrap(span) {
        Logger_1.Logger.instanse.debug("Add pushable queues", span);
        return Config_1.Config.db.query({
            query: {
                "$or": [
                    { robotqueue: { "$exists": true, $nin: [null, "", "(empty)"] }, workflowid: { "$exists": true, $nin: [null, "", "(empty)"] } },
                    { amqpqueue: { "$exists": true, $nin: [null, "", "(empty)"] } }
                ]
            }, collectionname: "mq", jwt: Crypt_1.Crypt.rootToken()
        }, span);
    }
    async GetPushableQueues(parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetPushableQueues", parent);
        try {
            let items = await this.memoryCache.wrap("pushablequeues", () => { return this.GetPushableQueuesWrap(span); });
            return items;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    GetPendingWorkitemsCountWrap(wiqid, span) {
        Logger_1.Logger.instanse.debug("Saving pending workitems count for wiqid " + wiqid, span);
        // TODO: skip nextrun ? or accept neextrun will always be based of cache TTL or substract the TTL ?
        const query = { "wiqid": wiqid, state: "new", "_type": "workitem", "nextrun": { "$lte": new Date(new Date().toISOString()) } };
        return Config_1.Config.db.count({
            query, collectionname: "workitems", jwt: Crypt_1.Crypt.rootToken()
        }, span);
    }
    async GetPendingWorkitemsCount(wiqid, parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetPendingWorkitemsCount", parent);
        try {
            var key = ("pendingworkitems_" + wiqid).toString().toLowerCase();
            let count = await this.memoryCache.wrap(key, () => { return this.GetPendingWorkitemsCountWrap(wiqid, span); });
            return count;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async Save(item, jwt, parent) {
        await Config_1.Config.db._UpdateOne(null, item, "users", 2, false, jwt, parent);
    }
    async EnsureRole(jwt, name, id, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.EnsureRole", parent);
        try {
            Logger_1.Logger.instanse.verbose(`FindRoleByName ${name}`, span);
            let role = await this.FindRoleByName(name, jwt, span);
            if (role == null) {
                Logger_1.Logger.instanse.verbose(`EnsureRole FindRoleById ${name}`, span);
                role = await this.FindRoleById(id, null, span);
            }
            if (role !== null && (role._id === id || openflow_api_1.NoderedUtil.IsNullEmpty(id))) {
                return role;
            }
            if (role !== null && !openflow_api_1.NoderedUtil.IsNullEmpty(role._id)) {
                Logger_1.Logger.instanse.warn(`Deleting ${name} with ${role._id} not matcing expected id ${id}`, span);
                await Config_1.Config.db.DeleteOne(role._id, "users", false, jwt, span);
            }
            role = new openflow_api_1.Role();
            role.name = name;
            role._id = id;
            Logger_1.Logger.instanse.verbose(`Adding new role ${name}`, span);
            role = await Config_1.Config.db.InsertOne(role, "users", 0, false, jwt, span);
            role = openflow_api_1.Role.assign(role);
            if (Config_1.Config.force_add_admins)
                openflow_api_1.Base.addRight(role, openflow_api_1.WellknownIds.admins, "admins", [openflow_api_1.Rights.full_control]);
            openflow_api_1.Base.addRight(role, role._id, role.name, [openflow_api_1.Rights.full_control]);
            if (Config_1.Config.force_add_admins)
                openflow_api_1.Base.removeRight(role, role._id, [openflow_api_1.Rights.delete]);
            Logger_1.Logger.instanse.verbose(`Updating ACL for new role ${name}`, span);
            await this.Save(role, jwt, span);
            return openflow_api_1.Role.assign(role);
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async EnsureUser(jwt, name, username, id, password, extraoptions, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.ensureUser", parent);
        try {
            span === null || span === void 0 ? void 0 : span.addEvent("FindByUsernameOrId");
            Logger_1.Logger.instanse.verbose(`FindById ${name} ${id}`, span);
            let user = await this.FindById(id, span);
            if (user == null) {
                Logger_1.Logger.instanse.verbose(`FindByUsername ${username}`, span);
                user = await this.FindByUsername(username, null, span);
            }
            if (user !== null && (user._id === id || id === null)) {
                return user;
            }
            if (user !== null && id !== null) {
                span === null || span === void 0 ? void 0 : span.addEvent("Deleting");
                Logger_1.Logger.instanse.warn(`Deleting ${name} with ${user._id} not matcing expected id ${id}`, span);
                await Config_1.Config.db.DeleteOne(user._id, "users", false, jwt, span);
            }
            user = new openflow_api_1.User();
            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(extraoptions))
                user = Object.assign(user, extraoptions);
            user._id = id;
            user.name = name;
            user.username = username;
            if (password !== null && password !== undefined && password !== "") {
                span === null || span === void 0 ? void 0 : span.addEvent("SetPassword");
                await Crypt_1.Crypt.SetPassword(user, password, span);
            }
            else {
                span === null || span === void 0 ? void 0 : span.addEvent("SetPassword");
                await Crypt_1.Crypt.SetPassword(user, Math.random().toString(36).substr(2, 9), span);
            }
            span === null || span === void 0 ? void 0 : span.addEvent("Insert user");
            Logger_1.Logger.instanse.verbose(`Adding new user ${name}`, span);
            user = await Config_1.Config.db.InsertOne(user, "users", 0, false, jwt, span);
            user = openflow_api_1.User.assign(user);
            span === null || span === void 0 ? void 0 : span.addEvent("DecorateWithRoles");
            Logger_1.Logger.instanse.verbose(`Decorating new user ${name} with roles`, span);
            user = await this.DecorateWithRoles(user, span);
            span === null || span === void 0 ? void 0 : span.addEvent("return user");
            return user;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async EnsureNoderedRoles(user, jwt, force, parent) {
        if (Config_1.Config.auto_create_personal_nodered_group || force) {
            let name = user.username;
            // name = name.split("@").join("").split(".").join("");
            // name = name.toLowerCase();
            name = name.toLowerCase();
            name = name.replace(/([^a-z0-9]+){1,63}/gi, "");
            let noderedadmins = await this.FindRoleByName(name + "noderedadmins", jwt, parent);
            if (noderedadmins == null) {
                noderedadmins = await this.EnsureRole(jwt, name + "noderedadmins", null, parent);
                openflow_api_1.Base.addRight(noderedadmins, user._id, user.username, [openflow_api_1.Rights.full_control]);
                openflow_api_1.Base.removeRight(noderedadmins, user._id, [openflow_api_1.Rights.delete]);
                noderedadmins.AddMember(user);
                await this.Save(noderedadmins, jwt, parent);
            }
        }
        if (Config_1.Config.auto_create_personal_noderedapi_group || force) {
            let name = user.username;
            // name = name.split("@").join("").split(".").join("");
            // name = name.toLowerCase();
            name = name.toLowerCase();
            name = name.replace(/([^a-z0-9]+){1,63}/gi, "");
            let noderedadmins = await this.FindRoleByName(name + "nodered api users", jwt, parent);
            if (noderedadmins == null) {
                noderedadmins = await this.EnsureRole(jwt, name + "nodered api users", null, parent);
                openflow_api_1.Base.addRight(noderedadmins, user._id, user.username, [openflow_api_1.Rights.full_control]);
                openflow_api_1.Base.removeRight(noderedadmins, user._id, [openflow_api_1.Rights.delete]);
                noderedadmins.AddMember(user);
                await this.Save(noderedadmins, jwt, parent);
            }
        }
    }
    UpdateHeartbeat(cli) {
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(cli) || openflow_api_1.NoderedUtil.IsNullUndefinded(cli.user))
            return null;
        const dt = new Date(new Date().toISOString());
        const updatedoc = { _heartbeat: dt, lastseen: dt, clientagent: cli.clientagent, clientversion: cli.clientversion, remoteip: cli.remoteip };
        cli.user._heartbeat = dt;
        cli.user.lastseen = dt;
        if (cli.clientagent == "openrpa") {
            cli.user._rpaheartbeat = dt;
            return { $set: { ...updatedoc, _rpaheartbeat: new Date(new Date().toISOString()), _lastopenrpaclientversion: cli.clientversion } };
        }
        if (cli.clientagent == "nodered") {
            cli.user._noderedheartbeat = dt;
            return { $set: { ...updatedoc, _noderedheartbeat: new Date(new Date().toISOString()), _lastnoderedclientversion: cli.clientversion } };
        }
        if (cli.clientagent == "webapp" || cli.clientagent == "aiotwebapp") {
            cli.user._webheartbeat = dt;
            return { $set: { ...updatedoc, _webheartbeat: new Date(new Date().toISOString()), _lastwebappclientversion: cli.clientversion } };
        }
        if (cli.clientagent == "powershell") {
            cli.user._powershellheartbeat = dt;
            return { $set: { ...updatedoc, _powershellheartbeat: new Date(new Date().toISOString()), _lastpowershellclientversion: cli.clientversion } };
        }
        if (cli.clientagent == "mobileapp" || cli.clientagent == "aiotmobileapp") {
            cli.user._webheartbeat = dt;
            cli.user._mobilheartbeat = dt;
            return {
                $set: {
                    ...updatedoc, _webheartbeat: new Date(new Date().toISOString()), _lastwebappclientversion: cli.clientversion,
                    _mobilheartbeat: new Date(new Date().toISOString()), _lastmobilclientversion: cli.clientversion
                }
            };
        }
        else {
            return { $set: updatedoc };
        }
    }
    GetIPBlockListWrap(span) {
        return Config_1.Config.db.query({ query: { _type: "ipblock" }, projection: { "ips": 1 }, top: 10, collectionname: "config", jwt: Crypt_1.Crypt.rootToken() }, span);
        ;
    }
    async GetIPBlockList(parent) {
        await this.init();
        const span = Logger_1.Logger.otel.startSubSpan("dbhelper.GetIPBlockList", parent);
        try {
            let items = await this.memoryCache.wrap("ipblock", () => { return this.GetIPBlockListWrap(span); });
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(items))
                items = [];
            return items;
        }
        catch (error) {
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async ClearIPBlockList() {
        await this.memoryCache.del("ipblock");
    }
}
exports.DBHelper = DBHelper;
//# sourceMappingURL=DBHelper.js.map