Пример #1
0
class UserService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    def create_if_not_exists(
        self, email, display_image_url, full_name, provider_user_id, is_buy, auth_token
    ):
        with session_scope() as session:
            user = (
                session.query(User)
                .filter_by(provider_user_id=provider_user_id)
                .one_or_none()
            )
            if user is None:
                user = User(
                    email=email,
                    full_name=full_name,
                    display_image_url=display_image_url,
                    provider="linkedin",
                    can_buy=False,
                    can_sell=False,
                    provider_user_id=provider_user_id,
                    auth_token=auth_token,
                )
                session.add(user)
                session.flush()

                req = UserRequest(user_id=str(user.id), is_buy=is_buy)
                session.add(req)

                email_template = "register_buyer" if is_buy else "register_seller"
                self.email_service.send_email(emails=[email], template=email_template)

                committee_emails = [
                    u.email
                    for u in session.query(User).filter_by(is_committee=True).all()
                ]
                self.email_service.send_email(
                    emails=committee_emails, template="new_user_review"
                )
            else:
                user.email = email
                user.full_name = full_name
                user.display_image_url = display_image_url
                user.auth_token = auth_token

            session.commit()
            return user.asdict()

    def get_user_by_linkedin_id(self, provider_user_id):
        with session_scope() as session:
            user = (
                session.query(User)
                .filter_by(provider_user_id=provider_user_id)
                .one_or_none()
            )
            if user is None:
                raise ResourceNotFoundException()
            user_dict = user.asdict()
        return user_dict
Пример #2
0
class UserRequestService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    @validate_input({"subject_id": UUID_RULE})
    def get_requests(self, subject_id):
        with session_scope() as session:
            if not session.query(User).get(subject_id).is_committee:
                raise InvisibleUnauthorizedException("Not committee")

            buy_requests = (
                session.query(UserRequest, User)
                .join(User, User.id == UserRequest.user_id)
                .filter(
                    UserRequest.is_buy == True, UserRequest.closed_by_user_id == None
                )
                .all()
            )
            sell_requests = (
                session.query(UserRequest, User)
                .join(User, User.id == UserRequest.user_id)
                .filter(
                    UserRequest.is_buy == False, UserRequest.closed_by_user_id == None
                )
                .all()
            )
            return {
                "buyers": [
                    {
                        **r[0].asdict(),
                        **{
                            k: v
                            for k, v in r[1].asdict().items()
                            if k not in ["id", "created_at", "updated_at"]
                        },
                    }
                    for r in buy_requests
                ],
                "sellers": [
                    {
                        **r[0].asdict(),
                        **{
                            k: v
                            for k, v in r[1].asdict().items()
                            if k not in ["id", "created_at", "updated_at"]
                        },
                    }
                    for r in sell_requests
                ],
            }

    @validate_input({"request_id": UUID_RULE, "subject_id": UUID_RULE})
    def approve_request(self, request_id, subject_id):
        with session_scope() as session:
            if not session.query(User).get(subject_id).is_committee:
                raise InvisibleUnauthorizedException("Not committee")

            request = session.query(UserRequest).get(request_id)
            request.closed_by_user_id = subject_id

            user = session.query(User).get(request.user_id)
            if request.is_buy:
                user.can_buy = True
                self.email_service.send_email(
                    emails=[user.email], template="approved_buyer"
                )
            else:
                user.can_sell = True
                self.email_service.send_email(
                    emails=[user.email], template="approved_seller"
                )

    @validate_input({"request_id": UUID_RULE, "subject_id": UUID_RULE})
    def reject_request(self, request_id, subject_id):
        with session_scope() as session:
            if not session.query(User).get(subject_id).is_committee:
                raise InvisibleUnauthorizedException("Not committee")

            request = session.query(UserRequest).get(request_id)
            request.closed_by_user_id = subject_id

            user = session.query(User).get(request.user_id)
            email_template = "rejected_buyer" if request.is_buy else "rejected_seller"
            self.email_service.send_email(emails=[user.email], template=email_template)
Пример #3
0
class MatchService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    def run_matches(self):
        round_id = RoundService(self.config).get_active()["id"]
        buy_orders, sell_orders, banned_pairs = self._get_matching_params(round_id)

        match_results = match_buyers_and_sellers(buy_orders, sell_orders, banned_pairs)

        buy_order_to_buyer_dict = {
            order["id"]: order["user_id"] for order in buy_orders
        }
        sell_order_to_seller_dict = {
            order["id"]: order["user_id"] for order in sell_orders
        }

        self._add_db_objects(
            round_id, match_results, sell_order_to_seller_dict, buy_order_to_buyer_dict
        )
        self._send_emails(buy_orders, sell_orders, match_results)

    def _get_matching_params(self, round_id):
        with session_scope() as session:
            buy_orders = [
                b.asdict()
                for b in session.query(BuyOrder)
                .join(User, User.id == BuyOrder.user_id)
                .filter(BuyOrder.round_id == round_id, User.can_buy)
                .all()
            ]
            sell_orders = [
                s.asdict()
                for s in session.query(SellOrder)
                .join(User, User.id == SellOrder.user_id)
                .filter(SellOrder.round_id == round_id, User.can_sell)
                .all()
            ]
            banned_pairs = [
                (bp.buyer_id, bp.seller_id) for bp in session.query(BannedPair).all()
            ]

        return buy_orders, self._double_sell_orders(sell_orders), banned_pairs

    def _double_sell_orders(self, sell_orders):
        seller_counts = defaultdict(lambda: 0)
        for sell_order in sell_orders:
            seller_counts[sell_order["user_id"]] += 1

        new_sell_orders = []
        for sell_order in sell_orders:
            new_sell_orders.append(sell_order)
            if seller_counts[sell_order["user_id"]] == 1:
                new_sell_orders.append(sell_order)

        return new_sell_orders

    def _add_db_objects(
        self,
        round_id,
        match_results,
        sell_order_to_seller_dict,
        buy_order_to_buyer_dict,
    ):
        with session_scope() as session:
            for buy_order_id, sell_order_id in match_results:
                match = Match(buy_order_id=buy_order_id, sell_order_id=sell_order_id)
                chat_room = ChatRoom(
                    seller_id=sell_order_to_seller_dict[sell_order_id],
                    buyer_id=buy_order_to_buyer_dict[buy_order_id],
                )
                session.add_all([match, chat_room])

            session.query(Round).get(round_id).is_concluded = True

    def _send_emails(self, buy_orders, sell_orders, match_results):
        matched_uuids = set()
        for buy_order_uuid, sell_order_uuid in match_results:
            matched_uuids.add(buy_order_uuid)
            matched_uuids.add(sell_order_uuid)

        all_user_ids = set()
        matched_user_ids = set()
        for buy_order in buy_orders:
            all_user_ids.add(buy_order["user_id"])
            if buy_order["id"] in matched_uuids:
                matched_user_ids.add(buy_order["user_id"])
        for sell_order in sell_orders:
            all_user_ids.add(sell_order["user_id"])
            if sell_order["id"] in matched_uuids:
                matched_user_ids.add(sell_order["user_id"])

        with session_scope() as session:
            matched_emails = [
                user.email
                for user in session.query(User)
                .filter(User.id.in_(matched_user_ids))
                .all()
            ]
            self.email_service.send_email(
                matched_emails, template="match_done_has_match"
            )

            unmatched_emails = [
                user.email
                for user in session.query(User)
                .filter(User.id.in_(all_user_ids - matched_user_ids))
                .all()
            ]
            self.email_service.send_email(
                unmatched_emails, template="match_done_no_match"
            )
Пример #4
0
class RoundService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    def get_all(self):
        with session_scope() as session:
            return [r.asdict() for r in session.query(Round).all()]

    def get_active(self):
        with session_scope() as session:
            active_round = (
                session.query(Round)
                .filter(Round.end_time >= datetime.now(), Round.is_concluded == False)
                .one_or_none()
            )
            return active_round and active_round.asdict()

    def should_round_start(self):
        with session_scope() as session:
            unique_sellers = (
                session.query(SellOrder.user_id)
                .filter_by(round_id=None)
                .distinct()
                .count()
            )
            if (
                unique_sellers
                >= self.config["ACQUITY_ROUND_START_NUMBER_OF_SELLERS_CUTOFF"]
            ):
                return True

            total_shares = (
                session.query(func.sum(SellOrder.number_of_shares))
                .filter_by(round_id=None)
                .scalar()
                or 0
            )
            return (
                total_shares
                >= self.config["ACQUITY_ROUND_START_TOTAL_SELL_SHARES_CUTOFF"]
            )

    def create_new_round_and_set_orders(self, scheduler):
        with session_scope() as session:
            end_time = datetime.now(timezone.utc) + self.config["ACQUITY_ROUND_LENGTH"]
            new_round = Round(end_time=end_time, is_concluded=False)
            session.add(new_round)
            session.flush()

            for sell_order in session.query(SellOrder).filter_by(round_id=None):
                sell_order.round_id = str(new_round.id)
            for buy_order in session.query(BuyOrder).filter_by(round_id=None):
                buy_order.round_id = str(new_round.id)

            emails = [user.email for user in session.query(User).all()]
            self.email_service.send_email(emails, template="round_opened")

        if scheduler is not None:
            scheduler.add_job(
                MatchService(self.config).run_matches, "date", run_date=end_time
            )

    @validate_input({"security_id": UUID_RULE})
    def get_previous_round_statistics(self, security_id):
        return None
Пример #5
0
class BuyOrderService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    @validate_input(CREATE_BUY_ORDER_SCHEMA)
    def create_order(self, user_id, number_of_shares, price, security_id):
        with session_scope() as session:
            user = session.query(User).get(user_id)
            if user is None:
                raise ResourceNotFoundException()
            if user.asdict()["can_buy"] == "NO":
                raise UnauthorizedException("User cannot place buy orders.")

            buy_order_count = session.query(BuyOrder).filter_by(user_id=user_id).count()
            if buy_order_count >= self.config["ACQUITY_BUY_ORDER_PER_ROUND_LIMIT"]:
                raise UnauthorizedException("Limit of buy orders reached.")

            active_round = RoundService(self.config).get_active()

            buy_order = BuyOrder(
                user_id=user_id,
                number_of_shares=number_of_shares,
                price=price,
                security_id=security_id,
                round_id=(active_round and active_round["id"]),
            )

            session.add(buy_order)
            session.commit()

            self.email_service.send_email(
                emails=[user.email], template="create_buy_order"
            )

            return buy_order.asdict()

    @validate_input({"user_id": UUID_RULE})
    def get_orders_by_user(self, user_id):
        with session_scope() as session:
            buy_orders = session.query(BuyOrder).filter_by(user_id=user_id).all()
            return [buy_order.asdict() for buy_order in buy_orders]

    @validate_input({"id": UUID_RULE, "user_id": UUID_RULE})
    def get_order_by_id(self, id, user_id):
        with session_scope() as session:
            order = session.query(BuyOrder).get(id)
            if order is None:
                raise ResourceNotFoundException()
            if order.user_id != user_id:
                raise ResourceNotOwnedException()
            return order.asdict()

    @validate_input(EDIT_ORDER_SCHEMA)
    def edit_order(self, id, subject_id, new_number_of_shares=None, new_price=None):
        with session_scope() as session:
            buy_order = session.query(BuyOrder).get(id)
            if buy_order is None:
                raise ResourceNotFoundException()
            if buy_order.user_id != subject_id:
                raise ResourceNotOwnedException("You need to own this order.")

            if new_number_of_shares is not None:
                buy_order.number_of_shares = new_number_of_shares
            if new_price is not None:
                buy_order.price = new_price

            session.commit()

            user = session.query(User).get(buy_order.user_id)
            self.email_service.send_email(
                emails=[user.email], template="edit_buy_order"
            )

            return buy_order.asdict()

    @validate_input(DELETE_ORDER_SCHEMA)
    def delete_order(self, id, subject_id):
        with session_scope() as session:
            buy_order = session.query(BuyOrder).get(id)
            if buy_order is None:
                raise ResourceNotFoundException()
            if buy_order.user_id != subject_id:
                raise ResourceNotOwnedException("You need to own this order.")

            session.delete(buy_order)
        return {}
Пример #6
0
class SellOrderService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config)

    @validate_input(CREATE_SELL_ORDER_SCHEMA)
    def create_order(self, user_id, number_of_shares, price, security_id, scheduler):
        with session_scope() as session:
            user = session.query(User).get(user_id)
            if user is None:
                raise ResourceNotFoundException()
            if user.asdict()["can_sell"] == "NO":
                raise UnauthorizedException("User cannot place sell orders.")

            sell_order_count = (
                session.query(SellOrder).filter_by(user_id=user_id).count()
            )
            if sell_order_count >= self.config["ACQUITY_SELL_ORDER_PER_ROUND_LIMIT"]:
                raise UnauthorizedException("Limit of sell orders reached.")

            sell_order = SellOrder(
                user_id=user_id,
                number_of_shares=number_of_shares,
                price=price,
                security_id=security_id,
            )

            active_round = RoundService(self.config).get_active()
            if active_round is None:
                session.add(sell_order)
                session.commit()
                if RoundService(self.config).should_round_start():
                    RoundService(self.config).create_new_round_and_set_orders(scheduler)
            else:
                sell_order.round_id = active_round["id"]
                session.add(sell_order)

            session.commit()

            self.email_service.send_email(
                emails=[user.email], template="create_sell_order"
            )

            return sell_order.asdict()

    @validate_input({"user_id": UUID_RULE})
    def get_orders_by_user(self, user_id):
        with session_scope() as session:
            sell_orders = session.query(SellOrder).filter_by(user_id=user_id).all()
            return [sell_order.asdict() for sell_order in sell_orders]

    @validate_input({"id": UUID_RULE, "user_id": UUID_RULE})
    def get_order_by_id(self, id, user_id):
        with session_scope() as session:
            order = session.query(SellOrder).get(id)
            if order is None:
                raise ResourceNotFoundException()
            if order.user_id != user_id:
                raise ResourceNotOwnedException()
            return order.asdict()

    @validate_input(EDIT_ORDER_SCHEMA)
    def edit_order(self, id, subject_id, new_number_of_shares=None, new_price=None):
        with session_scope() as session:
            sell_order = session.query(SellOrder).get(id)
            if sell_order is None:
                raise ResourceNotFoundException()
            if sell_order.user_id != subject_id:
                raise ResourceNotOwnedException("You need to own this order.")

            if new_number_of_shares is not None:
                sell_order.number_of_shares = new_number_of_shares
            if new_price is not None:
                sell_order.price = new_price

            session.commit()

            user = session.query(User).get(sell_order.user_id)
            self.email_service.send_email(
                emails=[user.email], template="edit_sell_order"
            )

            return sell_order.asdict()

    @validate_input(DELETE_ORDER_SCHEMA)
    def delete_order(self, id, subject_id):
        with session_scope() as session:
            sell_order = session.query(SellOrder).get(id)
            if sell_order is None:
                raise ResourceNotFoundException()
            if sell_order.user_id != subject_id:
                raise ResourceNotOwnedException("You need to own this order.")

            session.delete(sell_order)
        return {}
Пример #7
0
class ChatService:
    def __init__(self, config):
        self.config = config
        self.email_service = EmailService(config=config)

    @validate_input(GET_CHATS_BY_USER_ID_SCHEMA)
    def get_chats_by_user_id(self, user_id, as_buyer, as_seller):
        roles = []
        if as_buyer:
            roles.append("BUYER")
        if as_seller:
            roles.append("SELLER")

        with session_scope() as session:
            user = session.query(User).get(user_id)
            if (as_buyer and (not user.can_buy)) or (as_seller and
                                                     (not user.can_sell)):
                raise UnauthorizedException("Too much permissions requested.")

            chat_room_queries = (session.query(
                ChatRoom, UserChatRoomAssociation, Match, BuyOrder,
                SellOrder).join(Match, ChatRoom.match_id == Match.id).join(
                    UserChatRoomAssociation,
                    UserChatRoomAssociation.chat_room_id == ChatRoom.id,
                ).join(BuyOrder, Match.buy_order_id == BuyOrder.id).join(
                    SellOrder, Match.sell_order_id == SellOrder.id).filter(
                        UserChatRoomAssociation.user_id == user_id).filter(
                            UserChatRoomAssociation.role.in_(roles)).all())
            chats = session.query(Chat).all()
            offers = session.query(Offer).all()
            offer_responses = session.query(OfferResponse).all()

            whitelist_chat_rooms = None
            if not as_seller:
                whitelist_chat_rooms = set(
                    str(r[0].id) for r in session.query(ChatRoom, Chat).
                    join(Chat, ChatRoom.id == Chat.chat_room_id).all()) | set(
                        str(r[0].id)
                        for r in session.query(ChatRoom, Offer).join(
                            Offer, ChatRoom.id == Offer.chat_room_id).all())

            res = {}

            for chat_room, assoc, match, buy_order, sell_order in chat_room_queries:
                chat_room_id = str(chat_room.id)
                if (chat_room_id in res) or (
                    (whitelist_chat_rooms is not None) and
                    (chat_room_id not in whitelist_chat_rooms)):
                    continue

                chat_room_repr = ChatRoomService(
                    self.config)._serialize_chat_room(chat_room, user_id)
                res[chat_room_id] = chat_room_repr
                res[chat_room_id]["buy_order"] = buy_order.asdict()
                res[chat_room_id]["sell_order"] = (sell_order.asdict()
                                                   if as_seller else None)

                res[chat_room_id]["chats"] = []
                res[chat_room_id]["latest_offer"] = None

            for chat in chats:
                if chat.chat_room_id in res:
                    res[chat.chat_room_id]["chats"].append({
                        "type": "chat",
                        **chat.asdict()
                    })

            offer_d = {}
            for offer in offers:
                if offer.chat_room_id in res:
                    offer_d[str(offer.id)] = offer

                    res[offer.chat_room_id]["chats"].append({
                        "type": "offer",
                        **offer.asdict()
                    })

                    if offer.offer_status != "REJECTED":
                        res[offer.chat_room_id]["latest_offer"] = offer.asdict(
                        )

            for offer_resp in offer_responses:
                offer = offer_d.get(offer_resp.offer_id)
                if offer is None:
                    continue

                if offer.offer_status == "CANCELED":
                    author_id = offer.author_id
                else:
                    author_id = ChatRoomService._get_other_party_id(
                        chat_room_id=offer.chat_room_id,
                        user_id=offer.author_id)
                res[offer.chat_room_id]["chats"].append(
                    OfferService._serialize_chat_offer(
                        offer=offer.asdict(),
                        is_deal_closed=chat_room.is_deal_closed,
                        offer_response=offer_resp.asdict(),
                        author_id=author_id,
                    ))

            for v in res.values():
                v["chats"].sort(key=lambda x: x["created_at"])

            archived_room_ids = set(q[1].chat_room_id
                                    for q in chat_room_queries
                                    if q[1].is_archived)

        unarchived_res = {}
        archived_res = {}
        for chat_room_id, room in res.items():
            if chat_room_id in archived_room_ids:
                archived_res[chat_room_id] = room
            else:
                unarchived_res[chat_room_id] = room

        return {"archived": archived_res, "unarchived": unarchived_res}

    @validate_input(CREATE_NEW_MESSAGE_SCHEMA)
    def create_new_message(self, chat_room_id, message, author_id):
        with session_scope() as session:
            chat_room = session.query(ChatRoom).get(chat_room_id)
            if chat_room is None:
                raise ResourceNotFoundException("Chat room not found")
            if ChatRoomService.is_disbanded(chat_room):
                raise ResourceNotFoundException("Chat room is disbanded")

            if (session.query(UserChatRoomAssociation).filter_by(
                    user_id=author_id,
                    chat_room_id=chat_room_id).count() == 0):
                raise ResourceNotOwnedException(
                    "User is not in this chat room")

            first_chat = (session.query(Chat).filter_by(
                chat_room_id=chat_room_id).count() == 0)

            message = Chat(
                chat_room_id=str(chat_room_id),
                message=message,
                author_id=str(author_id),
            )
            session.add(message)
            session.flush()
            chat_room.updated_at = message.created_at

            if first_chat:
                other_party_id = ChatRoomService._get_other_party_id(
                    chat_room_id=str(chat_room.id), user_id=author_id)
                other_party_email = session.query(User).get(
                    other_party_id).email
                self.email_service.send_email(emails=[other_party_email],
                                              template="new_chat_message")

            return {"type": "chat", **message.asdict()}