Пример #1
0
    def move_election_to_voting(self, election_id: str):
        db_session = DbSession()
        partial_election = Election.get_election_by_id(
            db_session,
            election_id,
            query_modifier=lambda x: x.options(
                load_only(Election.id, Election.election_status)),
        )
        if partial_election is None:
            raise HttpException(
                message=f"Election with election id: {election_id} not found",
                status_code=404,
            )
        # TODO: Refactor election status "is already status" check (not changing) for both move_election_to_voting and
        #  mark_as_complete into one function that fetches the partial electoin
        if partial_election.election_status == ElectionStatus.VOTING:
            return
        if partial_election.election_status != ElectionStatus.IN_CREATION:
            raise InvalidElectionStateException(
                message=
                f"Cannot move an election to status {ElectionStatus.VOTING.name} when status is {partial_election.election_status.value}"
            )

        # TODO: validate change made by using WHERE
        partial_election.election_status = ElectionStatus.VOTING
        db_session.commit()
        self.__push_election_status_change_to_update_stream(
            election_id, ElectionStatus.VOTING)
Пример #2
0
    def mark_election_as_complete(self, election_id: str,
                                  complete_reason: ElectionStatus):
        if (complete_reason != ElectionStatus.MANUALLY_COMPLETE
                or complete_reason != ElectionStatus.MARKED_COMPLETE):
            raise ValueError(
                f"Invalid complete reason: {complete_reason.value}")

        db_session = DbSession()
        # could lead to race conditions, but exact completed_at not support important
        partial_election = Election.get_election_by_id(
            db_session,
            election_id,
            query_modifier=lambda x: x.options(
                load_only(Election.id, Election.election_status)),
        )
        if partial_election == complete_reason:
            return

        if partial_election.election_status != ElectionStatus.VOTING:
            raise InvalidElectionStateException(
                message=
                f"Cannot move an election to complete with status: {partial_election.election_status.value}"
            )
        partial_election.election_status = complete_reason
        partial_election.election_completed_at = datetime.now()
        db_session.commit()
        self.__push_election_status_change_to_update_stream(
            election_id, complete_reason)
Пример #3
0
 def __write_round_to_database(
     self,
     db_session: DbSession,
     election_id: str,
     votes: List[Tuple[str, int]],
     round: int,
     affected_candidate: str,
     candidate_round_action: RoundAction,
 ):
     db_session.add(Round(election_id=election_id, round_number=round))
     db_session.flush()
     db_session.add_all(
         [
             CandidateRoundResult(
                 election_id=election_id,
                 round_number=round,
                 business_id=candidate_id,
                 number_of_rank_one_votes=number_of_rank_one_votes_for_candidate,
                 round_action=None
                 if candidate_id != affected_candidate
                 else candidate_round_action,
             )
             for candidate_id, number_of_rank_one_votes_for_candidate in votes
         ]
     )
     db_session.flush()
Пример #4
0
 def get_nickname_by_user_id(self, id: str) -> Optional[str]:
     if id == "Admin":
         return "Admin"
     else:
         return BasicUser.get_user_by_id(
             DbSession(), id,
             lambda x: x.options(load_only(BasicUser.nickname))).nickname
Пример #5
0
def is_user_authorized(user: Optional[SerializableBasicUser], session_id: str):
    db_session = DbSession()
    partial_session = SearchSession.get_session_by_id(
        db_session, session_id, lambda x: x.options(
            load_only(SearchSession.id, SearchSession.created_by_id)))
    if partial_session.created_by_id is not None and user.id != partial_session.created_by_id:
        raise AuthorizationException(f"search_session--{session_id}")
Пример #6
0
    def get_displayable_session(self,
                                session_id: str) -> DisplayableSearchSession:
        db_session = DbSession()
        search_session = SearchSession.get_session_by_id(
            db_session, session_id)
        # parallelize
        displayable_recommendations_dict = {}
        for recommendation_key in self.__RECOMMENDATION_KEYS:
            recommendation_or_recommendations: Union[
                Recommendation,
                List[Recommendation]] = getattr(search_session,
                                                recommendation_key)
            if not isinstance(recommendation_or_recommendations, list):
                if recommendation_or_recommendations is None:
                    continue
                displayable_recommendations_dict[
                    recommendation_key] = self.__recommendation_manager.get_displayable_recommendation_from_recommendation(
                        recommendation_or_recommendations)
            else:
                displayable_recommendations = [
                    self.__recommendation_manager.
                    get_displayable_recommendation_from_recommendation(
                        recommendation)
                    for recommendation in recommendation_or_recommendations
                ]
                displayable_recommendations_dict[
                    recommendation_key] = displayable_recommendations

        return DisplayableSearchSession(
            id=session_id,
            search_request=search_session.search_request,
            session_status=search_session.session_status,
            dinner_party_id=search_session.dinner_party_id,
            **displayable_recommendations_dict,
        )
Пример #7
0
    def get_election_update_stream(self,
                                   election_id: str) -> ElectionUpdateStream:
        db_session = DbSession()
        if (Election.get_election_by_id(
                db_session, election_id,
                lambda x: x.options(load_only(Election.id))) is None):
            raise HttpException(
                message=f"Election with id: {election_id} not found",
                status_code=404)

        return ElectionUpdateStream.for_election(election_id)
Пример #8
0
    def apply_action_to_maybe(self, session_id: str, recommendation_id: str,
                              recommendation_action: RecommendationAction):
        db_session = DbSession()
        current_session: SearchSession = SearchSession.get_session_by_id(
            db_session, session_id)

        if recommendation_action == RecommendationAction.MAYBE:
            raise ValueError(
                f"Cannot maybe a recommendation that is already maybed")

        if current_session.is_dinner_party:
            raise ValueError(
                "Cannot apply maybe recommendation to dinner party search session"
            )

        if recommendation_id not in current_session.maybe_recommendation_ids:
            raise ValueError(
                f"Recommendation of id {recommendation_id} not in maybe recommendations"
            )

        recommendation = Recommendation.get_recommendation_by_key(
            db_session, current_session.id, recommendation_id)

        recommendation.status = recommendation_action
        current_session.maybe_recommendations = [
            rec for rec in current_session.maybe_recommendations
            if rec.business_id != recommendation_id
        ]

        if recommendation_action == RecommendationAction.ACCEPT:
            current_session.accepted_recommendations.append(recommendation)
            recommendation.session_id = session_id  # ORM workaround
            self.__complete_session(db_session=db_session,
                                    current_session=current_session)
        elif recommendation_action == RecommendationAction.REJECT:
            current_session.rejected_recommendations.append(recommendation)
        else:
            raise ValueError(
                f"Unsupported operation to a maybe operation: {recommendation_action}"
            )
        db_session.commit()
Пример #9
0
    def new_session(
        self,
        search_request: BusinessSearchRequest,
        dinner_party_active_id: Optional[str],
        user_maybe: Optional[SerializableBasicUser],
    ) -> SearchSession:
        session_id = str(uuid4())

        dinner_party_id = None
        if dinner_party_active_id is not None:
            dinner_party = self.__rcv_manager.get_active_election_by_active_id(
                dinner_party_active_id)
            if dinner_party is None:
                raise HttpException(
                    message=
                    f"Election with active id {dinner_party_active_id} not found",
                    error_code=ErrorCode.NO_ELECTION_FOUND)
            dinner_party_id = dinner_party.id

        new_session = SearchSession(
            id=session_id,
            search_request=search_request,
            dinner_party_id=dinner_party_id,
            created_by_id=None if user_maybe is None else user_maybe.id)
        db_session = DbSession()
        db_session.add(new_session)
        db_session.commit()
        return new_session
Пример #10
0
    def create_anonymous_user(self, nickname: str) -> BasicUser:
        user_id = str(uuid4())
        user: BasicUser = BasicUser(id=user_id, nickname=nickname)
        db_session = DbSession()
        db_session.add(user)
        db_session.commit()

        return user
Пример #11
0
    def get_candidates(self, active_id: str) -> [Candidate]:
        db_session = DbSession()
        partial_election = Election.get_active_election_by_active_id(
            db_session,
            active_id,
            query_modifier=lambda x: x.options(load_only(Election.id)),
        )

        if partial_election is None:
            raise HttpException(
                message=f"Election with active id: {active_id} not found",
                status_code=404,
            )
        return partial_election.candidates
Пример #12
0
 def consume(self, election_id: str):
     db_session = DbSession()
     Election.delete_election_results_for_election(db_session, election_id)
     db_session.flush()
     db_session.commit()
     rankings_by_voter: Dict[
         str, List[str]
     ] = Election.get_rankings_by_user_for_election(db_session, election_id)
     self.__run_election_round(
         db_session=db_session,
         election_id=election_id,
         rankings=list(rankings_by_voter.values()),
     )
     db_session.commit()
     ElectionUpdateStream.for_election(election_id).publish_message(
         ServerSentEvent(
             type=ElectionUpdateEventType.RESULTS_UPDATED, payload=None
         )
     )
Пример #13
0
 def get_displayable_election_by_id(
         self, id: str) -> Optional[DisplayableElection]:
     db_session = DbSession()
     election = Election.get_election_by_id(db_session=db_session, id=id)
     if election is None:
         return None
     return DisplayableElection(
         id=election.id,
         active_id=election.active_id,
         election_status=election.election_status,
         candidates=[
             DisplayableCandidate(
                 business_id=candidate.business_id,
                 name=self.__business_manager.get_displayable_business(
                     candidate.business_id).name,
                 nominator_nickname=candidate.nominator.nickname)
             for candidate in election.candidates
         ])
Пример #14
0
    def vote(self, user_id: str, election_id: str, votes: [str]):
        db_session = DbSession()
        partial_election = Election.get_election_by_id(
            db_session,
            election_id,
            query_modifier=lambda x: x.options(
                load_only(Election.id, Election.election_status)),
        )

        if partial_election is None:
            raise HttpException(
                message=f"Election with id: {election_id} not found",
                status_code=404)

        if partial_election.election_status != ElectionStatus.VOTING:
            raise InvalidElectionStateException(
                f"Cannot vote unless election is in voting status. Current status: {partial_election.election_status.name}"
            )

        candidate_ids = set([
            candidate.business_id for candidate in partial_election.candidates
        ])
        for business_id in votes:
            if business_id not in candidate_ids:
                raise HttpException(
                    message=f"Unknown or duplicate candidate id {business_id}",
                    status_code=404,
                )
            candidate_ids.remove(business_id)

        if len(candidate_ids) > 0:
            raise HttpException(
                message=
                f"Missing votes for candidates: {', '.join(candidate_ids)}",
                status_code=400,
            )

        rankings = [
            Ranking(
                user_id=user_id,
                election_id=election_id,
                business_id=business_id,
                rank=index,
            ) for index, business_id in enumerate(votes)
        ]
        Ranking.delete_users_rankings_for_election(db_session,
                                                   user_id=user_id,
                                                   election_id=election_id)
        db_session.add_all(rankings)
        db_session.commit()

        self.__queue_election_for_result_update(election=partial_election)
Пример #15
0
 def create_election(self, user: SerializableBasicUser) -> Election:
     db_session = DbSession()
     election_id = str(uuid4())
     while True:
         try:
             election = Election(
                 id=election_id,
                 active_id=self.__generate_election_active_id(),
                 election_creator_id=user.id,
             )
             db_session.add(election)
             db_session.commit()
             return election
         except BaseException as error:
             raise error
Пример #16
0
    def add_candidate(self, active_id: str, business_id: str,
                      user_id: str) -> bool:
        db_session = DbSession()

        partial_election = Election.get_active_election_by_active_id(
            db_session=db_session,
            active_id=active_id,
            query_modifier=lambda x: x.options(
                load_only(Election.id, Election.election_status)),
        )
        if partial_election is None:
            raise HttpException(
                message=f"Election with active id: {active_id} not found",
                status_code=404,
            )
        if partial_election.election_status != ElectionStatus.IN_CREATION:
            raise InvalidElectionStateException(
                message=
                f"Cannot add a candidate to an election with status: {partial_election.election_status.value}"
            )

        # Verify business exists? Cache business
        # Calculate distance

        candidate = Candidate(
            election_id=partial_election.id,
            business_id=business_id,
            distance=0,
            nominator_id=user_id,
        )
        db_session.add(candidate)
        try:
            db_session.commit()
        except IntegrityError as error:
            if is_unique_key_error(error):
                return False
            raise error
        # TODO: Accelerate fetching process here
        business: DisplayableBusiness = self.__business_manager.get_displayable_business(
            business_id=business_id)
        # TODO: Maybe just pass the name stored within the cookie instead of refetching from the database
        nickname = self.__user_manager.get_nickname_by_user_id(user_id)
        ElectionUpdateStream.for_election(partial_election.id).publish_message(
            CandidateAddedEvent(business_id=business_id,
                                name=business.name,
                                nominator_nickname=nickname
                                if nickname is not None else "Unknown"))
        return True
Пример #17
0
    def apply_recommendation_action_to_current(
        self,
        session_id: str,
        current_recommendation_id: str,
        recommendation_action: RecommendationAction,
    ):
        db_session = DbSession()
        current_session: SearchSession = SearchSession.get_session_by_id(
            db_session, session_id)

        if current_recommendation_id != current_session.current_recommendation_id:
            raise ValueError(
                f"Attempting to {recommendation_action} recommendation of id {current_recommendation_id}"
                f" but current recommendation is {current_session.current_recommendation_id} for "
                f"session {current_session.id}")
        current_recommendation: Recommendation = Recommendation.get_recommendation_by_key(
            db_session, session_id, current_recommendation_id)
        if current_recommendation is None:
            return ValueError(
                f"Attempting to {recommendation_action} recommendation of id {current_recommendation_id},"
                f"but current recommendation is None. This indicates the state of the session is incorrect."
            )
        if recommendation_action == RecommendationAction.MAYBE:
            current_session.maybe_recommendations.append(
                current_recommendation)
            current_recommendation.status = RecommendationAction.MAYBE
        elif recommendation_action == RecommendationAction.REJECT:
            current_session.rejected_recommendations.append(
                current_recommendation)
            current_recommendation.status = RecommendationAction.REJECT
        else:
            current_session.accepted_recommendations.append(
                current_recommendation)
            current_recommendation.status = RecommendationAction.ACCEPT
            db_session.commit(
            )  # complete transaction so rcv_manager can add candidate for SqlLite
            if current_session.is_dinner_party:
                self.__rcv_manager.add_candidate(
                    active_id=current_session.dinner_party.active_id,
                    business_id=current_recommendation_id,
                    user_id=current_session.created_by_id,
                )
            else:
                self.__complete_session(db_session=db_session,
                                        current_session=current_session)

        db_session.commit()
        current_session.current_recommendation = None
Пример #18
0
    def get_next_recommendation_for_session(
            self, session_id: str) -> DisplayableRecommendation:
        db_session = DbSession()
        search_session: SearchSession = SearchSession.get_session_by_id(
            db_session, session_id)
        if search_session.current_recommendation is not None:
            raise ValueError(
                "Cannot get a new recommendation. The search session has a current recommendation"
            )

        if search_session.is_complete:
            return None

        recommendation: Recommendation = self.__recommendation_manager.generate_new_recommendation_for_session(
            search_session)
        db_session.add(recommendation)
        db_session.commit()
        return self.__recommendation_manager.get_displayable_recommendation_from_recommendation(
            recommendation)
Пример #19
0
 def get_active_election_by_active_id(self, active_id: str) -> Election:
     db_session = DbSession()
     return Election.get_active_election_by_active_id(db_session, active_id)