<commit_before>from .payment import *
from .totp import *
from tapiriik.database import db
from tapiriik.sync import Sync
from datetime import datetime, timedelta
from bson.objectid import ObjectId

class User:
    def Get(id):
        return db.users.find_one({"_id": ObjectId(id)})
    def Ensure(req):
        if req.user == None:
            req.user = User.Create()
            User.Login(req.user, req)
        return req.user

    def Login(user, req):
        req.session["userid"] = str(user["_id"])
        req.user = user

    def Create():
        uid = db.users.insert({"Created": datetime.utcnow()})  # will mongodb insert an almost empty doc, i.e. _id?
        return db.users.find_one({"_id": uid})

    def GetConnectionRecordsByUser(user):
        return list(db.connections.find({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"]]}}))

    def GetConnectionRecord(user, svcId):
        return db.connections.find_one({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"] if x["Service"] == svcId]}})

    def SetEmail(user, email):
        db.users.update({"_id": ObjectId(user["_id"])}, {"$set": {"Email": email}})

    def AssociatePayment(user, payment):
        db.users.update({"_id": {'$ne': ObjectId(user["_id"])}}, {"$pull": {"Payments": payment}}, multi=True)  # deassociate payment ids from other accounts that may be using them
        db.users.update({"_id": ObjectId(user["_id"])}, {"$addToSet": {"Payments": payment}})
        Sync.ScheduleImmediateSync(user)

    def HasActivePayment(user):
        if "Payments" not in user:
            return False
        for payment in user["Payments"]:
            if payment["Timestamp"] > (datetime.utcnow() - timedelta(days=365.25)):
                return True
        return False

    def ConnectService(user, serviceRecord):
        existingUser = db.users.find_one({"_id": {'$ne': ObjectId(user["_id"])}, "ConnectedServices.ID": ObjectId(serviceRecord["_id"])})
        if "ConnectedServices" not in user:
            user["ConnectedServices"] = []
        delta = False
        if existingUser is not None:
            # merge merge merge
            user["ConnectedServices"] += existingUser["ConnectedServices"]
            if "Payments" in existingUser:
                user["Payments"] += existingUser["Payments"]
            if "FlowExceptions" in existingUser:
                user["FlowExceptions"] += existingUser["FlowExceptions"]
            user["Email"] = user["Email"] if "Email" in user and user["Email"] is not None else (existingUser["Email"] if "Email" in existingUser else None)
            user["SyncErrorCount"] = max(user["SyncErrorCount"] if "SyncErrorCount" in user and user["SyncErrorCount"] is not None else 0, existingUser["SyncErrorCount"] if "SyncErrorCount" in existingUser and existingUser["SyncErrorCount"] is not None else 0)
            delta = True
            db.users.remove({"_id": existingUser["_id"]})
        else:
            if serviceRecord["_id"] not in [x["ID"] for x in user["ConnectedServices"]]:
                user["ConnectedServices"].append({"Service": serviceRecord["Service"], "ID": serviceRecord["_id"]})
                delta = True
        db.users.update({"_id": user["_id"]}, user)
        if delta or ("SyncErrors" in serviceRecord and len(serviceRecord["SyncErrors"]) > 0):  # also schedule an immediate sync if there is an outstanding error (i.e. user reconnected)
            Sync.SetNextSyncIsExhaustive(user, True)  # exhaustive, so it'll pick up activities from newly added services / ones lost during an error
            if "SyncErrors" in serviceRecord and len(serviceRecord["SyncErrors"]) > 0:
                Sync.ScheduleImmediateSync(user)

    def DisconnectService(serviceRecord):
        # not that >1 user should have this connection
        activeUsers = list(db.users.find({"ConnectedServices.ID": serviceRecord["_id"]}))
        if len(activeUsers) == 0:
            raise Exception("No users found with service " + serviceRecord["_id"])
        db.users.update({}, {"$pull": {"ConnectedServices": {"ID": serviceRecord["_id"]}}}, multi=True)
        for user in activeUsers:
            if len(user["ConnectedServices"]) - 1 == 0:
                # I guess we're done here?
                db.users.remove({"_id": user["_id"]})

    def AuthByService(serviceRecord):
        return db.users.find_one({"ConnectedServices.ID": serviceRecord["_id"]})

    def SetFlowException(user, sourceServiceRecord, targetServiceRecord, flowToTarget=True, flowtoSource=True):
        if "FlowExceptions" not in user:
            user["FlowExceptions"] = []

        # flow exceptions are stored in "forward" direction - service-account X will not send activities to service-account Y
        forwardException = {"Target": {"Service": targetServiceRecord["Service"], "ExternalID": targetServiceRecord["ExternalID"]}, "Source": {"Service": sourceServiceRecord["Service"], "ExternalID": sourceServiceRecord["ExternalID"]}}
        backwardsException = {"Target": forwardException["Source"], "Source": forwardException["Target"]}
        if flowToTarget is not None:
            if flowToTarget:
                user["FlowExceptions"][:] = [x for x in user["FlowExceptions"] if x != forwardException]
            elif not flowToTarget and forwardException not in user["FlowExceptions"]:
                user["FlowExceptions"].append(forwardException)<commit_msg>Officially prevent connecting more than one account from the same provider<commit_after>from .payment import *
from .totp import *
from tapiriik.database import db
from tapiriik.sync import Sync
from datetime import datetime, timedelta
from bson.objectid import ObjectId

class User:
    def Get(id):
        return db.users.find_one({"_id": ObjectId(id)})
    def Ensure(req):
        if req.user == None:
            req.user = User.Create()
            User.Login(req.user, req)
        return req.user

    def Login(user, req):
        req.session["userid"] = str(user["_id"])
        req.user = user

    def Create():
        uid = db.users.insert({"Created": datetime.utcnow()})  # will mongodb insert an almost empty doc, i.e. _id?
        return db.users.find_one({"_id": uid})

    def GetConnectionRecordsByUser(user):
        return list(db.connections.find({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"]]}}))

    def GetConnectionRecord(user, svcId):
        return db.connections.find_one({"_id": {"$in": [x["ID"] for x in user["ConnectedServices"] if x["Service"] == svcId]}})

    def SetEmail(user, email):
        db.users.update({"_id": ObjectId(user["_id"])}, {"$set": {"Email": email}})

    def AssociatePayment(user, payment):
        db.users.update({"_id": {'$ne': ObjectId(user["_id"])}}, {"$pull": {"Payments": payment}}, multi=True)  # deassociate payment ids from other accounts that may be using them
        db.users.update({"_id": ObjectId(user["_id"])}, {"$addToSet": {"Payments": payment}})
        Sync.ScheduleImmediateSync(user)

    def HasActivePayment(user):
        if "Payments" not in user:
            return False
        for payment in user["Payments"]:
            if payment["Timestamp"] > (datetime.utcnow() - timedelta(days=365.25)):
                return True
        return False

    def ConnectService(user, serviceRecord):
        existingUser = db.users.find_one({"_id": {'$ne': ObjectId(user["_id"])}, "ConnectedServices.ID": ObjectId(serviceRecord["_id"])})
        if "ConnectedServices" not in user:
            user["ConnectedServices"] = []
        delta = False
        if existingUser is not None:
            # merge merge merge
            user["ConnectedServices"] += existingUser["ConnectedServices"]
            if "Payments" in existingUser:
                user["Payments"] += existingUser["Payments"]
            if "FlowExceptions" in existingUser:
                user["FlowExceptions"] += existingUser["FlowExceptions"]
            user["Email"] = user["Email"] if "Email" in user and user["Email"] is not None else (existingUser["Email"] if "Email" in existingUser else None)
            user["SyncErrorCount"] = max(user["SyncErrorCount"] if "SyncErrorCount" in user and user["SyncErrorCount"] is not None else 0, existingUser["SyncErrorCount"] if "SyncErrorCount" in existingUser and existingUser["SyncErrorCount"] is not None else 0)
            delta = True
            db.users.remove({"_id": existingUser["_id"]})
        else:
            if serviceRecord["_id"] not in [x["ID"] for x in user["ConnectedServices"]]:
                # we might be connecting a second account for the same service
                for duplicateConn in [x for x in user["ConnectedServices"] if x["Service"] == serviceRecord["Service"]]:
                    dupeRecord = User.GetConnectionRecord(user, serviceRecord["Service"])  # this'll just pick the first connection of type, but we repeat the right # of times anyways
                    Service.DeleteServiceRecord(dupeRecord)
                    User.DisconnectService(dupeRecord, preserveUser=True)  # preserveUser prevents GCing the user immediately before it's needed

                user["ConnectedServices"].append({"Service": serviceRecord["Service"], "ID": serviceRecord["_id"]})
                delta = True

        db.users.update({"_id": user["_id"]}, user)
        if delta or ("SyncErrors" in serviceRecord and len(serviceRecord["SyncErrors"]) > 0):  # also schedule an immediate sync if there is an outstanding error (i.e. user reconnected)
            Sync.SetNextSyncIsExhaustive(user, True)  # exhaustive, so it'll pick up activities from newly added services / ones lost during an error
            if "SyncErrors" in serviceRecord and len(serviceRecord["SyncErrors"]) > 0:
                Sync.ScheduleImmediateSync(user)

    def DisconnectService(serviceRecord, preserveUser=False):
        # not that >1 user should have this connection
        activeUsers = list(db.users.find({"ConnectedServices.ID": serviceRecord["_id"]}))
        if len(activeUsers) == 0:
            raise Exception("No users found with service " + serviceRecord["_id"])
        db.users.update({}, {"$pull": {"ConnectedServices": {"ID": serviceRecord["_id"]}}}, multi=True)
        if not preserveUser:
            for user in activeUsers:
                if len(user["ConnectedServices"]) - 1 == 0:
                    # I guess we're done here?
                    db.users.remove({"_id": user["_id"]})

    def AuthByService(serviceRecord):
        return db.users.find_one({"ConnectedServices.ID": serviceRecord["_id"]})

    def SetFlowException(user, sourceServiceRecord, targetServiceRecord, flowToTarget=True, flowtoSource=True):
        if "FlowExceptions" not in user:
            user["FlowExceptions"] = []

        # flow exceptions are stored in "forward" direction - service-account X will not send activities to service-account Y
        forwardException = {"Target": {"Service": targetServiceRecord["Service"], "ExternalID": targetServiceRecord["ExternalID"]}, "Source": {"Service": sourceServiceRecord["Service"], "ExternalID": sourceServiceRecord["ExternalID"]}}
        backwardsException = {"Target": forwardException["Source"], "Source": forwardException["Target"]}
        if flowToTarget is not None:
            if flowToTarget:
                user["FlowExceptions"][:] = [x for x in user["FlowExceptions"] if x != forwardException]
            elif not flowToTarget and forwardException not in user["FlowExceptions"]:
                user["FlowExceptions"].append(forwardException)
{'fraction_non_alphanumeric': 0.13444069556189983, 'fraction_numerical': 0.0022493295267756726, 'mean_word_length': 3.199055575735561, 'pattern_counts': {'":': 72, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2819320', 'n_tokens_mistral': 3202, 'n_tokens_neox': 3077, 'n_words': 900}