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)
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}")
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
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)
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
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, )
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
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)
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)
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
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
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
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
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 ])
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 ) )
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)
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()
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)