예제 #1
0
def get_min_distance_house_travel_distance(task: ExternalTask) -> TaskResult:
    """
    Gets the distance between the user's house and a travel company.
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("get_min_distance_house_travel_distance")

    GEOGRAPHICAL_DISTACE_SERVICE = environ.get(
        "GEOGRAPHICAL_DISTACE_SERVICE", "http://geographical_distances:8080")

    travel_company = str(task.get_variable("travel_company"))
    travel_company_url = travel_company.split(';')[0]
    travel_company_address = travel_company.split(';')[1]
    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))

    distances = json.loads(str(task.get_variable("distances")))

    request = {
        "address_1": travel_company_address,
        "address_2": str(offer_purchase_data.address)
    }

    distance_request = requests.post(GEOGRAPHICAL_DISTACE_SERVICE +
                                     "/distance",
                                     json=request)

    distances.get("distances").append({
        "company": travel_company_url,
        "distance": distance_request.text
    })
    return task.complete(global_variables={"distances": json.dumps(distances)})
예제 #2
0
def rehabilitation_offer_code(task: ExternalTask) -> TaskResult:
    """
    Compensation task: if the verification offer code and payment sub process fail, this task rehabilitates the offer
    code for another try.
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("rehabilitation_offer_code")

    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))

    offer_code = offer_purchase_data.offer_code

    # Connects to postgreSQL
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()
    """
    Gets the offer match that has to be rehabilitated from the DB.
    The offer match is the one blocked and with offer_code equal to the process offer_code variable.
    """
    affected_rows = session.query(OfferMatch).filter(
        OfferMatch.offer_code == offer_code).update(
            {"blocked": False}, synchronize_session="fetch")
    if affected_rows < 1:
        session.rollback()
        logger.error(
            f"{affected_rows} matches were found for the given offer code. The offer code will not be rehabilitated."
        )
        return task.complete()

    logger.info(f"{affected_rows} match was found for the given offer code.")
    session.commit()
    return task.complete()
def verify_offer_code_validity(task: ExternalTask) -> TaskResult:
    """
    Verifies that the offer code is valid, not expired and not already in use by another user.
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("verify_offer_code_validity")

    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))

    offer_code = offer_purchase_data.offer_code

    # Connects to PostgreSQL
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()

    user_communication_code = str(hash(offer_purchase_data))

    # Checks if the offer matched is blocked
    matches = session.query(OfferMatch).filter(
        OfferMatch.offer_code == offer_code, OfferMatch.blocked == True).all()
    if len(matches) == 1:
        logger.error(f"Offer code is BLOCKED.")
        return task.complete(
            global_variables={
                'offer_code_validity': False,
                'user_communication_code': user_communication_code
            })

    # Checks if the offer match is not expired and sets it to blocked=True.
    affected_rows = session.query(OfferMatch).filter(
        OfferMatch.offer_code == offer_code, OfferMatch.creation_date >=
        datetime.datetime.now(tz=datetime.timezone.utc) -
        datetime.timedelta(hours=24)).update({"blocked": True},
                                             synchronize_session="fetch")
    if affected_rows < 1:
        session.rollback()
        logger.error(
            f"{affected_rows} matches were found for the given offer code.")
        return task.complete(
            global_variables={
                'offer_code_validity': False,
                'user_communication_code': user_communication_code
            })

    logger.info(f"{affected_rows} match was found for the given offer code.")
    session.commit()
    return task.complete(
        global_variables={
            'offer_code_validity': True,
            'user_communication_code': user_communication_code
        })
예제 #4
0
def book_transfer(task: ExternalTask) -> TaskResult:
    """
    Contacts the chosen Travel Company and requests to book a travel using the SOAP protocol
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("get_min_distance_house_travel_distance")

    distances = json.loads(str(task.get_variable("distances")))
    distances = distances.get("distances")
    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))
    tickets = json.loads(str(task.get_variable("tickets")))

    # Connects to PostgreSQL to get the offer match information
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()
    offer_match: OfferMatch = session.query(OfferMatch).get(
        {"offer_code": offer_purchase_data.offer_code})

    # Identifies the Travel Company to contact, choosing the one nearer to the user's address.
    travel_company_to_contact = min(distances,
                                    key=lambda tc: tc.get("distance"))

    # Creates the SOAP Client and the datetime when then transfer will be booked for.
    # We need to replace the port for accessing the web server serving the WSDL interface.
    wsdl_url = travel_company_to_contact.get("company").replace(
        ":8080", ":8000") + "/travel_company.wsdl"
    soap_client = Client(wsdl=wsdl_url)

    outbound_departure_transfer_datetime = offer_match.outbound_flight.departure_datetime - timedelta(
        hours=4)
    comeback_arrival_transfer_datetime = offer_match.comeback_flight.arrival_datetime + timedelta(
        minutes=10)

    try:
        soap_response = soap_client.service.buyTransfers(
            departure_transfer_datetime=outbound_departure_transfer_datetime.
            strftime("%Y-%m-%dT%H:%M:%S"),
            customer_address=str(offer_purchase_data.address),
            airport_code=offer_match.outbound_flight.departure_airport_code,
            customer_name=
            f"{offer_purchase_data.name} {offer_purchase_data.surname}",
            arrival_transfer_datetime=comeback_arrival_transfer_datetime.
            strftime("%Y-%m-%dT%H:%M:%S"))
        tickets["transfers"] = [soap_response]
        return task.complete(global_variables={"tickets": json.dumps(tickets)})
    except Fault:
        return task.failure("Book ticket",
                            "Failure in booking ticket from travel company",
                            max_retries=5,
                            retry_timeout=10)
예제 #5
0
def check_distance_house_airport(task: ExternalTask) -> TaskResult:
    """
    Checks if the distance (that we got when contacting the Geographical Distance service)
    is congruent with the transfer bundle.
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("check_distance_house_airport")

    GEOGRAPHICAL_DISTACE_SERVICE = environ.get(
        "GEOGRAPHICAL_DISTACE_SERVICE", "http://geographical_distances:8080")

    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))

    # Connects to postgreSQL and get the offer purchased
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()
    offer_match: OfferMatch = session.query(OfferMatch).get(
        {"offer_code": offer_purchase_data.offer_code})

    # Finds the name (used for the airport) of the departure airport
    airports_file = open("./camundaworkers/airports.csv", 'r')
    airports = csv.reader(airports_file)
    airport_address = None
    for row in airports:
        if row[4] == offer_match.outbound_flight.departure_airport_code:
            airport_address = row[1]
    airports_file.close()

    # Failure case: the airport cannot be found in the CSV.
    if not airport_address:
        logger.error(
            f"Cannot find airport associated with: {offer_match.outbound_flight.departure_airport_code}"
        )
        return task.complete(global_variables={
            "distance": "35"
        })  # 35 > 30, then the transfer won't be booked.

    request = {
        "address_1": airport_address,
        "address_2": str(offer_purchase_data.address)
    }

    distance_request = requests.post(GEOGRAPHICAL_DISTACE_SERVICE +
                                     "/distance",
                                     json=request)
    return task.complete(global_variables={"distance": distance_request.text})
예제 #6
0
def send_tickets(task: ExternalTask) -> TaskResult:
    """
    Sends the tickets to the user
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("send_tickets")

    user_communication_code = str(task.get_variable("user_communication_code"))
    tickets = str(task.get_variable("tickets"))
    logger.info(f"Tickets: {tickets}")

    # Connects to RabbitMQ and publish the ticket
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host="acmesky_mq"))
    channel = connection.channel()
    channel.queue_declare(queue=user_communication_code, durable=True)

    channel.basic_publish(
        exchange="",
        routing_key=user_communication_code,
        body=bytes(tickets, "utf-8"),
        properties=pika.BasicProperties(delivery_mode=2),
    )

    connection.close()

    # Connects to PostgreSQL and deletes the purchased offer.
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()
    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))
    to_delete = session.query(OfferMatch).get(
        {"offer_code": offer_purchase_data.offer_code})
    session.delete(to_delete)
    # The following lines are commented (uncomment them and comment the previous two not to delete the offermatch from the database).
    # session.query(OfferMatch)\
    #     .filter(OfferMatch.offer_code == offer_purchase_data.offer_code)\
    #     .update({"blocked": False}, synchronize_session="fetch")
    session.commit()
    return task.complete()
예제 #7
0
def payment_request(task: ExternalTask) -> TaskResult:
    """
    Requests for a new payment session to the Payment Provider.
    :param task: the current task instance
    :return: the task result
    """
    logger = get_logger()
    logger.info("payment_request")

    user_communication_code = str(task.get_variable("user_communication_code"))

    offer_purchase_data = OfferPurchaseData.from_dict(
        json.loads(task.get_variable("offer_purchase_data")))

    offer_code = offer_purchase_data.offer_code

    # Connecting to postgreSQL and getting the offer the user wants to purchase
    Session = sessionmaker(bind=create_sql_engine())
    session = Session()
    offer_match = session.query(OfferMatch).filter(
        OfferMatch.offer_code == offer_code,
        OfferMatch.blocked == True).first()

    # affected_rows == 1 by hypothesis.
    outbound_flight_id = offer_match.outbound_flight_id
    comeback_flight_id = offer_match.comeback_flight_id

    outbound_flight = session.query(Flight).filter(
        Flight.id == outbound_flight_id).first()
    comeback_flight = session.query(Flight).filter(
        Flight.id == comeback_flight_id).first()

    # Sends the payment request generation to the Payment Provider and get back the URL to send to the user.
    payment_request_to_send = {
        "amount":
        outbound_flight.cost + comeback_flight.cost,
        "payment_receiver":
        "ACMESky",
        "description":
        f"Il costo totale dell'offerta è: € {outbound_flight.cost + comeback_flight.cost}. I biglietti verranno acquistati dalla compagnia {outbound_flight.flight_company_name}.",
    }
    payment_provider_url = environ.get("PAYMENT_PROVIDER_URL",
                                       "http://payment_provider_backend:8080")
    payment_creation_response = requests.post(
        payment_provider_url + "/payments/request",
        json=payment_request_to_send).json()

    # Creates a payment transaction
    payment_tx = PaymentTransaction(
        transaction_id=payment_creation_response.get('transaction_id'))
    session.add(payment_tx)
    session.commit()

    # Connects to Redis and relate the transaction id to the process instance id
    redis_connection = Redis(host="acmesky_redis", port=6379, db=0)
    redis_connection.set(payment_creation_response.get('transaction_id'),
                         task.get_process_instance_id())
    redis_connection.close()

    # Connects to RabbitMQ and communicate to the user the payment URL
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host="acmesky_mq"))
    channel = connection.channel()
    channel.queue_declare(queue=user_communication_code, durable=True)

    purchase_url = PurchaseProcessInformation(
        message=str(payment_creation_response.get('redirect_page')),
        communication_code=user_communication_code)

    channel.basic_publish(
        exchange="",
        routing_key=user_communication_code,
        body=bytes(json.dumps(purchase_url.to_dict()), "utf-8"),
        properties=pika.BasicProperties(delivery_mode=2),
    )

    connection.close()

    return task.complete()