"use strict";
/*
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
 * User storage format:
 * {
 *   type: "matrix|remote",
 *   id: "user_id|remote_id",
 *   data: {
 *     .. matrix-specific info e.g. display name ..
 *     .. remote specific info e.g. IRC username ..
 *   }
 * }
 * Examples:
 * {
 *   type: "matrix",
 *   id: "@foo:bar",
 *   data: {
 *     localpart: "foo",      // Required.
 *     displayName: "Foo Bar" // Optional.
 *   }
 * }
 *
 * {
 *   type: "remote",
 *   id: "foobar@irc.freenode.net",
 *   data: {
 *     nickChoices: ["foobar", "foobar_", "foobar__"]
 *   }
 * }
 *
 * There is also a third type, the "union" type. This binds together a single
 * matrix <--> remote pairing. A single remote ID can have many matrix_id and
 * vice versa, via mutliple union entries.
 *
 * {
 *   type: "union",
 *   remote_id: "foobar@irc.freenode.net",
 *   matrix_id: "@foo:bar"
 * }
 */
var BridgeStore = require("./bridge-store");
var MatrixUser = require("../models/users/matrix");
var RemoteUser = require("../models/users/remote");
var util = require("util");
/**
 * Construct a store suitable for user bridging information.
 * @constructor
 * @param {Datastore} db The connected NEDB database instance
 * @param {Object} opts Options for this store.
 */
function UserBridgeStore(db, opts) {
    this.db = db;
}
util.inherits(UserBridgeStore, BridgeStore);
/**
 * Retrieve a list of corresponding remote users for the given matrix user ID.
 * @param {string} userId The Matrix user ID
 * @return {Promise<RemoteUser[], Error>} Resolves to a list of Remote users.
 */
UserBridgeStore.prototype.getRemoteUsersFromMatrixId = function (userId) {
    var self = this;
    return this.select({
        type: "union",
        matrix_id: userId
    }, self.convertTo(function (doc) {
        return doc.remote_id;
    })).then(function (remoteIds) {
        return self.select({
            type: "remote",
            id: { $in: remoteIds }
        }, self.convertTo(function (doc) {
            return new RemoteUser(doc.id, doc.data);
        }));
    });
};
/**
 * Retrieve a list of corresponding matrix users for the given remote ID.
 * @param {string} remoteId The Remote ID
 * @return {Promise<MatrixUser[], Error>} Resolves to a list of Matrix users.
 */
UserBridgeStore.prototype.getMatrixUsersFromRemoteId = function (remoteId) {
    var self = this;
    return this.select({
        type: "union",
        remote_id: remoteId
    }, self.convertTo(function (doc) {
        return doc.matrix_id;
    })).then(function (matrixUserIds) {
        return self.select({
            type: "matrix",
            id: { $in: matrixUserIds }
        }, self.convertTo(function (doc) {
            return new MatrixUser(doc.id, doc.data);
        }));
    });
};
/**
 * Retrieve a MatrixUser based on their user ID localpart. If there is more than
 * one match (e.g. same localpart, different domains) then this will return an
 * arbitrary matching user.
 * @param {string} localpart The user localpart
 * @return {Promise<?MatrixUser, Error>} Resolves to a MatrixUser or null.
 */
UserBridgeStore.prototype.getByMatrixLocalpart = function (localpart) {
    return this.selectOne({
        type: "matrix",
        "data.localpart": localpart
    }, this.convertTo(function (doc) {
        return new MatrixUser(doc.id, doc.data);
    }));
};
/**
 * Get a matrix user by their user ID.
 * @param {string} userId The user_id
 * @return {Promise<?MatrixUser, Error>} Resolves to the user or null if they
 * do not exist. Rejects with an error if there was a problem querying the store.
 */
UserBridgeStore.prototype.getMatrixUser = function (userId) {
    return this.selectOne({
        type: "matrix",
        id: userId
    }, this.convertTo(function (doc) {
        return new MatrixUser(doc.id, doc.data);
    }));
};
/**
 * Store a Matrix user. If they already exist, they will be updated. Equivalence
 * is determined by their user ID.
 * @param {MatrixUser} matrixUser The matrix user
 * @return {Promise}
 */
UserBridgeStore.prototype.setMatrixUser = function (matrixUser) {
    return this.upsert({
        type: "matrix",
        id: matrixUser.getId()
    }, {
        type: "matrix",
        id: matrixUser.getId(),
        data: matrixUser.serialize()
    });
};
/**
 * Get a remote user by their remote ID.
 * @param {string} id The remote ID
 * @return {Promise<?RemoteUser, Error>} Resolves to the user or null if they
 * do not exist. Rejects with an error if there was a problem querying the store.
 */
UserBridgeStore.prototype.getRemoteUser = function (id) {
    return this.selectOne({
        type: "remote",
        id: id
    }, this.convertTo(function (doc) {
        return new RemoteUser(doc.id, doc.data);
    }));
};
/**
 * Get remote users by some data about them, previously stored via the set
 * method on the Remote user.
 * @param {Object} dataQuery The keys and matching values the remote users share.
 * This should use dot notation for nested types. For example:
 * <code> { "topLevel.midLevel.leaf": 42, "otherTopLevel": "foo" } </code>
 * @return {Promise<RemoteUser[], Error>} Resolves to a possibly empty list of
 * RemoteUsers. Rejects with an error if there was a problem querying the store.
 * @throws If dataQuery isn't an object.
 * @example
 * remoteUser.set({
 *   toplevel: "foo",
 *   nested: {
 *     bar: {
 *       baz: 43
 *     }
 *   }
 * });
 * store.setRemoteUser(remoteUser).then(function() {
 *   store.getByRemoteData({
 *     "toplevel": "foo",
 *     "nested.bar.baz": 43
 *   })
 * });
 */
UserBridgeStore.prototype.getByRemoteData = function (dataQuery) {
    if (typeof dataQuery !== "object") {
        throw new Error("Data query must be an object.");
    }
    var query = {};
    Object.keys(dataQuery).forEach(function (key) {
        query["data." + key] = dataQuery[key];
    });
    query.type = "remote";
    return this.select(query, this.convertTo(function (doc) {
        return new RemoteUser(doc.id, doc.data);
    }));
};
/**
 * Get Matrix users by some data about them, previously stored via the set
 * method on the Matrix user.
 * @param {Object} dataQuery The keys and matching values the remote users share.
 * This should use dot notation for nested types. For example:
 * <code> { "topLevel.midLevel.leaf": 42, "otherTopLevel": "foo" } </code>
 * @return {Promise<MatrixUser[], Error>} Resolves to a possibly empty list of
 * MatrixUsers. Rejects with an error if there was a problem querying the store.
 * @throws If dataQuery isn't an object.
 * @example
 * matrixUser.set({
 *   toplevel: "foo",
 *   nested: {
 *     bar: {
 *       baz: 43
 *     }
 *   }
 * });
 * store.setMatrixUser(matrixUser).then(function() {
 *   store.getByMatrixData({
 *     "toplevel": "foo",
 *     "nested.bar.baz": 43
 *   })
 * });
 */
UserBridgeStore.prototype.getByMatrixData = function (dataQuery) {
    if (typeof dataQuery !== "object") {
        throw new Error("Data query must be an object.");
    }
    var query = {};
    Object.keys(dataQuery).forEach(function (key) {
        query["data." + key] = dataQuery[key];
    });
    query.type = "matrix";
    return this.select(query, this.convertTo(function (doc) {
        return new MatrixUser(doc.id, doc.data);
    }));
};
/**
 * Store a Remote user. If they already exist, they will be updated. Equivalence
 * is determined by the Remote ID.
 * @param {RemoteUser} remoteUser The remote user
 * @return {Promise}
 */
UserBridgeStore.prototype.setRemoteUser = function (remoteUser) {
    return this.upsert({
        type: "remote",
        id: remoteUser.getId()
    }, {
        type: "remote",
        id: remoteUser.getId(),
        data: remoteUser.serialize()
    });
};
/**
 * Create a link between a matrix and remote user. If either user does not exist,
 * they will be inserted prior to linking. This is done to ensure foreign key
 * constraints are satisfied (so you cannot have a mapping to a user ID which
 * does not exist).
 * @param {MatrixUser} matrixUser The matrix user
 * @param {RemoteUser} remoteUser The remote user
 * @return {Promise}
 */
UserBridgeStore.prototype.linkUsers = function (matrixUser, remoteUser) {
    var self = this;
    return self.insertIfNotExists({
        type: "remote",
        id: remoteUser.getId()
    }, {
        type: "remote",
        id: remoteUser.getId(),
        data: remoteUser.serialize()
    }).then(function () {
        return self.insertIfNotExists({
            type: "matrix",
            id: matrixUser.getId()
        }, {
            type: "matrix",
            id: matrixUser.getId(),
            data: matrixUser.serialize()
        });
    }).then(function () {
        return self.upsert({
            type: "union",
            remote_id: remoteUser.getId(),
            matrix_id: matrixUser.getId()
        }, {
            type: "union",
            remote_id: remoteUser.getId(),
            matrix_id: matrixUser.getId()
        });
    });
};
/**
 * Delete a link between a matrix user and a remote user.
 * @param {MatrixUser} matrixUser The matrix user
 * @param {RemoteUser} remoteUser The remote user
 * @return {Promise<Number, Error>} Resolves to the number of entries removed.
 */
UserBridgeStore.prototype.unlinkUsers = function (matrixUser, remoteUser) {
    return this.unlinkUserIds(matrixUser.getId(), remoteUser.getId());
};
/**
 * Delete a link between a matrix user ID and a remote user ID.
 * @param {string} matrixUserId The matrix user ID
 * @param {string} remoteUserId The remote user ID
 * @return {Promise<Number, Error>} Resolves to the number of entries removed.
 */
UserBridgeStore.prototype.unlinkUserIds = function (matrixUserId, remoteUserId) {
    return this.delete({
        type: "union",
        remote_id: remoteUserId,
        matrix_id: matrixUserId
    });
};
/**
 * Retrieve a list of matrix user IDs linked to this remote ID.
 * @param {string} remoteId The remote ID
 * @return {Promise<String[], Error>} A list of user IDs.
 */
UserBridgeStore.prototype.getMatrixLinks = function (remoteId) {
    return this.select({
        type: "union",
        remote_id: remoteId
    }, this.convertTo(function (doc) {
        return doc.matrix_id;
    }));
};
/**
 * Retrieve a list of remote IDs linked to this matrix user ID.
 * @param {string} matrixId The matrix user ID
 * @return {Promise<String[], Error>} A list of remote IDs.
 */
UserBridgeStore.prototype.getRemoteLinks = function (matrixId) {
    return this.select({
        type: "union",
        matrix_id: matrixId
    }, this.convertTo(function (doc) {
        return doc.remote_id;
    }));
};
/** The UserBridgeStore class. */
module.exports = UserBridgeStore;
//# sourceMappingURL=user-bridge-store.js.map