From dd1ee357d6260852542029e224aa269f103e5f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Fri, 21 Sep 2018 17:12:11 +0100 Subject: [PATCH 1/9] Add list teams command handler, dispatcher and responder --- src/database.py | 27 +++++++++++++++++++++++++++ src/definitions.py | 1 + src/dispatcher.py | 20 ++++++++++++++++++++ src/handlers.py | 29 +++++++++++++++++++++++++++++ src/responder.py | 26 ++++++++++++++++++++++++++ src/server.py | 1 + 6 files changed, 104 insertions(+) diff --git a/src/database.py b/src/database.py index 45ba9a0..6bf2a17 100644 --- a/src/database.py +++ b/src/database.py @@ -653,6 +653,33 @@ def get_last_transactions(slack_user_id, max_quantity): cursor.close() db_connection.close() raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = [r for r in cursor.fetchall()] + cursor.close() + db_connection.close() + return result + +def get_teams(): + """ Gets the teams list.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get user slack details: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT team_id, team_name + FROM teams + """ + try: + cursor.execute(sql_string) + except Exception as ex: + log.error("Couldn't get teams list: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) else: result = [r for r in cursor.fetchall()] cursor.close() diff --git a/src/definitions.py b/src/definitions.py index 3792d94..14ada02 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -20,6 +20,7 @@ "CHECK_BALANCE": "/saldo", "BUY": "/compra", "LIST_TRANSACTIONS": "/movimentos", + "LIST_TEAMS": "/ver-equipas", } INITIAL_TEAM_BALANCE = 200 diff --git a/src/dispatcher.py b/src/dispatcher.py index 6666344..ddf1d9e 100644 --- a/src/dispatcher.py +++ b/src/dispatcher.py @@ -39,6 +39,8 @@ def general_dispatcher(): buy_dispatcher(request) elif request["command"] == SLACK_COMMANDS["LIST_TRANSACTIONS"]: list_transactions_dispatcher(request) + elif request["command"] == SLACK_COMMANDS["LIST_TEAMS"]: + list_teams_dispatcher(request) else: log.critical("Invalid request command.") @@ -482,6 +484,24 @@ def list_transactions_dispatcher(request): log.error("Failed to save request log on database.") responder.list_transactions_delayed_reply_success(request, transactions) +def list_teams_dispatcher(request): + """Dispatcher to list teams requests/commands.""" + log.debug("List teams request.") + + try: + log.debug("Getting teams") + teams = database.get_teams() + log.debug(teams) + responder.list_teams_delayed_reply_success(request, teams) + except exceptions.QueryDatabaseError as ex: + log.critical("List teams search failed: {}".format(ex)) + responder.default_error() + try: + database.save_request_log(request, False, "Could not perform teams list search.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + return + def add_request_to_queue(request): """ Add a request to the requests queue.""" try: diff --git a/src/handlers.py b/src/handlers.py index dcb8e19..b2f5139 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -155,6 +155,35 @@ def list_transactions(): log.error("Failed to save request log.") return responder.unverified_origin_error() +def list_teams(): + """Handler to list teams request.""" + log.debug("New list teams request.") + request_data = dict(request.POST) + + if check_request_origin(request): + if all_elements_on_request(request_data): + # Procceed with request. + log.debug("Request with correct fields, add to queue.") + if dispatcher.add_request_to_queue(request_data): + # Request was added to queue + return responder.confirm_list_teams_command_reception() + else: + # Request wasn't added to queue + return responder.overloaded_error() + else: + # Inform user of incomplete request. + log.warn("Request with invalid payload was sent.") + return responder.default_error() + else: + # Could not validate user request + log.error("Slack request origin verification failed.") + try: + database.save_request_log(request_data, False, "Unverified origin.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log.") + return responder.unverified_origin_error() + + def all_elements_on_request(request_data): """Check if all elements (keys) are present in the request dictionary""" if all(k in request_data for k in SLACK_REQUEST_DATA_KEYS): diff --git a/src/responder.py b/src/responder.py index b1306ff..31c5b55 100644 --- a/src/responder.py +++ b/src/responder.py @@ -58,6 +58,14 @@ def confirm_list_transactions_command_reception(): } return json.dumps(response_content, ensure_ascii=False).encode("utf-8") +def confirm_list_teams_command_reception(): + """Immediate response to a list teams command.""" + response.add_header("Content-Type", "application/json") + response_content = { + "text": "Vou tratar de ir buscar as equipas a participar!", + } + return json.dumps(response_content, ensure_ascii=False).encode("utf-8") + def create_team_delayed_reply_missing_arguments(request): """Delayed response to Slack reporting not enough arguments on create team command""" log.debug("Missing arguments on create team request.") @@ -348,6 +356,24 @@ def list_transactions_delayed_reply_success(request, transaction_list): except exceptions.POSTRequestError: log.critical("Failed to send delayed message to Slack.") +def list_teams_delayed_reply_success(request, teams_list): + """Delayed response to Slack reporting the teams list.""" + response_content = { + "text": "Aqui estão as {} equipas a participar:\n".format(len(teams_list)), + } + + for idx, team in enumerate(teams_list): + log.debug(team) + response_content["text"] += "_{}_: *Nome:* {} | *ID:* {}\n".format(idx + 1, team[1], team[0]) + + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + def default_error(): """Immediate default response to report an error.""" response.add_header("Content-Type", "application/json") diff --git a/src/server.py b/src/server.py index 283d90b..6179345 100644 --- a/src/server.py +++ b/src/server.py @@ -34,3 +34,4 @@ def define_routing(app): app.route(path="/check-balance", method=["POST"], callback=handlers.check_balance) app.route(path="/buy", method=["POST"], callback=handlers.buy) app.route(path="/list-transactions", method=["POST"], callback=handlers.list_transactions) + app.route(path="/list-teams", method=["POST"], callback=handlers.list_teams) From e4e106e4d2d0b4bd2e9c8041bfdbe90376abf3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Fri, 21 Sep 2018 17:29:23 +0100 Subject: [PATCH 2/9] Add list registration teams command handler, dispatcher and responder --- src/database.py | 27 +++++++++++++++++++++++++++ src/definitions.py | 1 + src/dispatcher.py | 20 ++++++++++++++++++++ src/handlers.py | 27 +++++++++++++++++++++++++++ src/responder.py | 26 ++++++++++++++++++++++++++ src/server.py | 1 + 6 files changed, 102 insertions(+) diff --git a/src/database.py b/src/database.py index 6bf2a17..0558203 100644 --- a/src/database.py +++ b/src/database.py @@ -673,6 +673,33 @@ def get_teams(): SELECT team_id, team_name FROM teams """ + try: + cursor.execute(sql_string) + except Exception as ex: + log.error("Couldn't get teams list: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = [r for r in cursor.fetchall()] + cursor.close() + db_connection.close() + return result + +def get_teams_registration(): + """ Gets the registration teams list.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get user slack details: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT team_id, team_name, entry_code + FROM team_registration + """ try: cursor.execute(sql_string) except Exception as ex: diff --git a/src/definitions.py b/src/definitions.py index 14ada02..fdb231b 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -21,6 +21,7 @@ "BUY": "/compra", "LIST_TRANSACTIONS": "/movimentos", "LIST_TEAMS": "/ver-equipas", + "LIST_TEAMS_REGISTRATION": "/ver-equipas-registo", } INITIAL_TEAM_BALANCE = 200 diff --git a/src/dispatcher.py b/src/dispatcher.py index ddf1d9e..ab68f2b 100644 --- a/src/dispatcher.py +++ b/src/dispatcher.py @@ -41,6 +41,8 @@ def general_dispatcher(): list_transactions_dispatcher(request) elif request["command"] == SLACK_COMMANDS["LIST_TEAMS"]: list_teams_dispatcher(request) + elif request["command"] == SLACK_COMMANDS["LIST_TEAMS_REGISTRATION"]: + list_teams_registration_dispatcher(request) else: log.critical("Invalid request command.") @@ -502,6 +504,24 @@ def list_teams_dispatcher(request): log.error("Failed to save request log on database.") return +def list_teams_registration_dispatcher(request): + """Dispatcher to list teams registrations requests/commands.""" + log.debug("List teams request.") + + try: + log.debug("Getting teams registrations.") + teams = database.get_teams_registration() + log.debug(teams) + responder.list_teams_registration_delayed_reply_success(request, teams) + except exceptions.QueryDatabaseError as ex: + log.critical("List teams search failed: {}".format(ex)) + responder.default_error() + try: + database.save_request_log(request, False, "Could not perform registration teams list search.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + return + def add_request_to_queue(request): """ Add a request to the requests queue.""" try: diff --git a/src/handlers.py b/src/handlers.py index b2f5139..fc42a20 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -183,6 +183,33 @@ def list_teams(): log.error("Failed to save request log.") return responder.unverified_origin_error() +def list_teams_registration(): + """Handler to list teams registration request.""" + log.debug("New list teams registration request.") + request_data = dict(request.POST) + + if check_request_origin(request): + if all_elements_on_request(request_data): + # Procceed with request. + log.debug("Request with correct fields, add to queue.") + if dispatcher.add_request_to_queue(request_data): + # Request was added to queue + return responder.confirm_list_teams_registration_command_reception() + else: + # Request wasn't added to queue + return responder.overloaded_error() + else: + # Inform user of incomplete request. + log.warn("Request with invalid payload was sent.") + return responder.default_error() + else: + # Could not validate user request + log.error("Slack request origin verification failed.") + try: + database.save_request_log(request_data, False, "Unverified origin.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log.") + return responder.unverified_origin_error() def all_elements_on_request(request_data): """Check if all elements (keys) are present in the request dictionary""" diff --git a/src/responder.py b/src/responder.py index 31c5b55..aeee0e6 100644 --- a/src/responder.py +++ b/src/responder.py @@ -66,6 +66,14 @@ def confirm_list_teams_command_reception(): } return json.dumps(response_content, ensure_ascii=False).encode("utf-8") +def confirm_list_teams_registration_command_reception(): + """Immediate response to a list teams registration command.""" + response.add_header("Content-Type", "application/json") + response_content = { + "text": "Vou tratar de ir buscar as equipas registadas!", + } + return json.dumps(response_content, ensure_ascii=False).encode("utf-8") + def create_team_delayed_reply_missing_arguments(request): """Delayed response to Slack reporting not enough arguments on create team command""" log.debug("Missing arguments on create team request.") @@ -374,6 +382,24 @@ def list_teams_delayed_reply_success(request, teams_list): except exceptions.POSTRequestError: log.critical("Failed to send delayed message to Slack.") +def list_teams_registration_delayed_reply_success(request, teams_list): + """Delayed response to Slack reporting the registration teams list.""" + response_content = { + "text": "Aqui estão as {} equipas registadas:\n".format(len(teams_list)), + } + + for idx, team in enumerate(teams_list): + log.debug(team) + response_content["text"] += "_{}_: *Nome:* {} | *ID:* {} | *Código:* {}\n".format(idx + 1, team[1], team[0], team[2]) + + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + def default_error(): """Immediate default response to report an error.""" response.add_header("Content-Type", "application/json") diff --git a/src/server.py b/src/server.py index 6179345..893ef7f 100644 --- a/src/server.py +++ b/src/server.py @@ -35,3 +35,4 @@ def define_routing(app): app.route(path="/buy", method=["POST"], callback=handlers.buy) app.route(path="/list-transactions", method=["POST"], callback=handlers.list_transactions) app.route(path="/list-teams", method=["POST"], callback=handlers.list_teams) + app.route(path="/list-teams-registration", method=["POST"], callback=handlers.list_teams_registration) From da1c67453cc7f1bb9c00840e5814d6f4840895f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Fri, 21 Sep 2018 19:31:33 +0100 Subject: [PATCH 3/9] Add TODO references --- src/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatcher.py b/src/dispatcher.py index ab68f2b..e923924 100644 --- a/src/dispatcher.py +++ b/src/dispatcher.py @@ -489,7 +489,7 @@ def list_transactions_dispatcher(request): def list_teams_dispatcher(request): """Dispatcher to list teams requests/commands.""" log.debug("List teams request.") - + # TODO: Change response to "No teams" if no teams were found. try: log.debug("Getting teams") teams = database.get_teams() @@ -507,7 +507,7 @@ def list_teams_dispatcher(request): def list_teams_registration_dispatcher(request): """Dispatcher to list teams registrations requests/commands.""" log.debug("List teams request.") - + # TODO: Change response to "No teams" if no teams were found. try: log.debug("Getting teams registrations.") teams = database.get_teams_registration() From bb989ea5501daf1a98eef3447469d4c44adabfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Fri, 21 Sep 2018 19:31:42 +0100 Subject: [PATCH 4/9] Update README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 900bf25..ad03930 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,19 @@ Allows to buy something from another user. It performs a transfer, between the c ### List last transactions `/movimentos ` List transactions. If the user has a team, list the last `qty` transactions of his team. If the current user doesn't have a team, an error message appears stating how to join a team. - +### List all teams +`/ver-equipas` +List all teams. Provides the team name and team id of each team participating. +### List all teams registered +`/ver-equipas-registo` +List all registered teams. Provides the team name and team id and entry code of each team registered. ## Current features: - Request origin verification/validation ## To be added -```./ver-equipas``` - List all teams. \ -Can only be performed by admins. Used to list all teams. - -```./detalhes-equipa ``` \ +```./detalhes-equipa ``` \ Can only be performed by admins. Used to list all details of a team. The `team-id` must be provided. From a21aecc5b9a3572eb124057e358bb4e3cb705a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Sat, 22 Sep 2018 12:46:53 +0100 Subject: [PATCH 5/9] Add team details command handler, dispatcher and responder --- src/database.py | 68 ++++++++++++++++++++++++++++++++++++++++++++-- src/definitions.py | 1 + src/dispatcher.py | 51 ++++++++++++++++++++++++++++++++-- src/handlers.py | 30 ++++++++++++++++++++ src/responder.py | 50 ++++++++++++++++++++++++++++++++++ src/server.py | 1 + 6 files changed, 196 insertions(+), 5 deletions(-) diff --git a/src/database.py b/src/database.py index 0558203..0625c81 100644 --- a/src/database.py +++ b/src/database.py @@ -602,7 +602,7 @@ def get_last_transactions(slack_user_id, max_quantity): try: db_connection = connect() except exceptions.DatabaseConnectionError as ex: - log.critical("Couldn't get user slack details: {}".format(ex)) + log.critical("Couldn't get last transactions: {}".format(ex)) raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) else: cursor = db_connection.cursor() @@ -664,7 +664,7 @@ def get_teams(): try: db_connection = connect() except exceptions.DatabaseConnectionError as ex: - log.critical("Couldn't get user slack details: {}".format(ex)) + log.critical("Couldn't get teams: {}".format(ex)) raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) else: cursor = db_connection.cursor() @@ -691,7 +691,7 @@ def get_teams_registration(): try: db_connection = connect() except exceptions.DatabaseConnectionError as ex: - log.critical("Couldn't get user slack details: {}".format(ex)) + log.critical("Couldn't get registration teams: {}".format(ex)) raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) else: cursor = db_connection.cursor() @@ -707,6 +707,68 @@ def get_teams_registration(): cursor.close() db_connection.close() raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = [r for r in cursor.fetchall()] + cursor.close() + db_connection.close() + return result + +def get_team_details(team_id): + """ Gets a team details.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get team details: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT team_id, team_name, balance + FROM teams + WHERE team_id = %s + """ + data = ( + team_id, + ) + try: + cursor.execute(sql_string, data) + except Exception as ex: + log.error("Couldn't get team details: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = cursor.fetchone() + cursor.close() + db_connection.close() + return result + +def get_team_users(team_id): + """ Gets a team users.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get team users: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT slack_id, slack_name, user_id + FROM users + WHERE team = %s + """ + data = ( + team_id, + ) + try: + cursor.execute(sql_string, data) + except Exception as ex: + log.error("Couldn't get team users: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) else: result = [r for r in cursor.fetchall()] cursor.close() diff --git a/src/definitions.py b/src/definitions.py index fdb231b..21b579b 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -22,6 +22,7 @@ "LIST_TRANSACTIONS": "/movimentos", "LIST_TEAMS": "/ver-equipas", "LIST_TEAMS_REGISTRATION": "/ver-equipas-registo", + "TEAM_DETAILS": "/detalhes-equipa", } INITIAL_TEAM_BALANCE = 200 diff --git a/src/dispatcher.py b/src/dispatcher.py index e923924..f4d239b 100644 --- a/src/dispatcher.py +++ b/src/dispatcher.py @@ -43,6 +43,8 @@ def general_dispatcher(): list_teams_dispatcher(request) elif request["command"] == SLACK_COMMANDS["LIST_TEAMS_REGISTRATION"]: list_teams_registration_dispatcher(request) + elif request["command"] == SLACK_COMMANDS["TEAM_DETAILS"]: + team_details_dispatcher(request) else: log.critical("Invalid request command.") @@ -494,7 +496,6 @@ def list_teams_dispatcher(request): log.debug("Getting teams") teams = database.get_teams() log.debug(teams) - responder.list_teams_delayed_reply_success(request, teams) except exceptions.QueryDatabaseError as ex: log.critical("List teams search failed: {}".format(ex)) responder.default_error() @@ -503,6 +504,13 @@ def list_teams_dispatcher(request): except exceptions.SaveRequestLogError: log.error("Failed to save request log on database.") return + else: + log.debug("Retrieved data.") + try: + database.save_request_log(request, True, "Team list collected.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.list_teams_delayed_reply_success(request, teams) def list_teams_registration_dispatcher(request): """Dispatcher to list teams registrations requests/commands.""" @@ -512,7 +520,6 @@ def list_teams_registration_dispatcher(request): log.debug("Getting teams registrations.") teams = database.get_teams_registration() log.debug(teams) - responder.list_teams_registration_delayed_reply_success(request, teams) except exceptions.QueryDatabaseError as ex: log.critical("List teams search failed: {}".format(ex)) responder.default_error() @@ -521,6 +528,46 @@ def list_teams_registration_dispatcher(request): except exceptions.SaveRequestLogError: log.error("Failed to save request log on database.") return + else: + log.debug("Retrieved data.") + try: + database.save_request_log(request, True, "Registration team list collected.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.list_teams_registration_delayed_reply_success(request, teams) + +def team_details_dispatcher(request): + """Dispatcher to team details requests/commands.""" + log.debug("Team details request.") + # Get team_id from args + team_id = request["text"] + if not team_id: + # Bad usage + log.warn("Bad format on command given.") + responder.team_details_delayed_reply_bad_usage(request) + return + + try: + log.debug("Getting team details.") + details = database.get_team_details(team_id) + log.debug(details) + users = database.get_team_users(team_id) + log.debug(users) + except exceptions.QueryDatabaseError as ex: + log.critical("Team details/users search failed: {}".format(ex)) + responder.default_error() + try: + database.save_request_log(request, False, "Could not fetch team details/users from the database.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + return + else: + log.debug("Retrieved data.") + try: + database.save_request_log(request, True, "Team details collected.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.team_details_delayed_reply_success(request, details, users) def add_request_to_queue(request): """ Add a request to the requests queue.""" diff --git a/src/handlers.py b/src/handlers.py index fc42a20..b58f861 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -211,6 +211,35 @@ def list_teams_registration(): log.error("Failed to save request log.") return responder.unverified_origin_error() +def team_details(): + """Handler to list a team details.""" + log.debug("New team details request.") + request_data = dict(request.POST) + + if check_request_origin(request): + if all_elements_on_request(request_data): + # Procceed with request. + log.debug("Request with correct fields, add to queue.") + if dispatcher.add_request_to_queue(request_data): + # Request was added to queue + return responder.confirm_team_details_command_reception() + else: + # Request wasn't added to queue + return responder.overloaded_error() + else: + # Inform user of incomplete request. + log.warn("Request with invalid payload was sent.") + return responder.default_error() + else: + # Could not validate user request + log.error("Slack request origin verification failed.") + try: + database.save_request_log(request_data, False, "Unverified origin.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log.") + return responder.unverified_origin_error() + + def all_elements_on_request(request_data): """Check if all elements (keys) are present in the request dictionary""" if all(k in request_data for k in SLACK_REQUEST_DATA_KEYS): @@ -238,6 +267,7 @@ def check_request_origin(request): return False else: log.critical("Header 'X-Slack-Request-Timestamp' value is different than handler server. Refusing request.") + log.debug("Header value: {} | Current timestamp: {}".format(request_timestamp, time.time())) return False else: # No header diff --git a/src/responder.py b/src/responder.py index aeee0e6..d616a18 100644 --- a/src/responder.py +++ b/src/responder.py @@ -74,6 +74,14 @@ def confirm_list_teams_registration_command_reception(): } return json.dumps(response_content, ensure_ascii=False).encode("utf-8") +def confirm_team_details_command_reception(): + """Immediate response to a team details command.""" + response.add_header("Content-Type", "application/json") + response_content = { + "text": "Vou tratar de ir buscar os detalhes dessa equipa!", + } + return json.dumps(response_content, ensure_ascii=False).encode("utf-8") + def create_team_delayed_reply_missing_arguments(request): """Delayed response to Slack reporting not enough arguments on create team command""" log.debug("Missing arguments on create team request.") @@ -400,6 +408,48 @@ def list_teams_registration_delayed_reply_success(request, teams_list): except exceptions.POSTRequestError: log.critical("Failed to send delayed message to Slack.") +def team_details_delayed_reply_bad_usage(request): + """Delayed response to Slack reporting a bad usage on team details command.""" + response_content = { + "text": "Má utilização do comando! Utilização: `/detalhes-equipa id-equipa`.", + } + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + +def team_details_delayed_reply_success(request, details, users): + """Delayed response to Slack reporting the results of team details command.""" + + response_content = { + "text": "", + } + + if len(details): + log.debug("Team exists.") + response_content["text"] += "Aqui estão os detalhes da equipa:\n" + response_content["text"] += "*Nome:* {} | *Saldo:* {:.2f} | *ID:* {}\n".format(details[1], details[2], details[0]) + if len(users): + log.debug("Team has users.") + for user in users: + response_content["text"] += "_Elemento:_ *Nome:* <@{}|{}> | *ID:* {}\n".format(user[0], user[1], user[2]) + else: + log.debug("Team has no users") + response_content["text"] += "Não foi encontrado nenhum jogador na equipa." + else: + log.debug("Team doesn't exist.") + response_content["text"] += "Não foi encontrada nenhuma equipa com esse ID." + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + def default_error(): """Immediate default response to report an error.""" response.add_header("Content-Type", "application/json") diff --git a/src/server.py b/src/server.py index 893ef7f..7ecb3db 100644 --- a/src/server.py +++ b/src/server.py @@ -36,3 +36,4 @@ def define_routing(app): app.route(path="/list-transactions", method=["POST"], callback=handlers.list_transactions) app.route(path="/list-teams", method=["POST"], callback=handlers.list_teams) app.route(path="/list-teams-registration", method=["POST"], callback=handlers.list_teams_registration) + app.route(path="/team-details", method=["POST"], callback=handlers.team_details) From 9132c43704031d159c4fa71152fa95b7aa7eb091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Sat, 22 Sep 2018 12:47:58 +0100 Subject: [PATCH 6/9] Fix request time difference gap to 1 minute --- src/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/definitions.py b/src/definitions.py index 21b579b..a9286f7 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -27,4 +27,4 @@ INITIAL_TEAM_BALANCE = 200 -SLACK_REQUEST_TIMESTAMP_MAX_GAP_MINUTES = 1.0 +SLACK_REQUEST_TIMESTAMP_MAX_GAP_MINUTES = 60.0 From f87bd8809c33b20fac68bcbe87e448f86bb41144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Sat, 22 Sep 2018 12:55:47 +0100 Subject: [PATCH 7/9] Update README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad03930..abcf3f5 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,16 @@ List all teams. Provides the team name and team id of each team participating. ### List all teams registered `/ver-equipas-registo` List all registered teams. Provides the team name and team id and entry code of each team registered. +### View team details +```./detalhes-equipa ``` \ +Used to list all details of a team. The `team-id` must be provided. ## Current features: - Request origin verification/validation ## To be added - -```./detalhes-equipa ``` \ -Can only be performed by admins. Used to list all details of a team. The `team-id` must be provided. - +```./detalhes-participante <@user>``` \ +Can only be performed by admins. Used to list all details of a participant. The `@user` must be provided. ```./bug ``` \ Can only be performed by admins. Used to change all teams balances. @@ -47,6 +48,7 @@ Can only be performed by admins. Used to make `@user` an admin. - Report money receival on buy operation - Permissions system - Error codes +- Single user transactions listing ## Problems found - How to create first admin. From 7b31d243412ad800e7852bbae03a7c2029f1892e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Sat, 22 Sep 2018 14:13:11 +0100 Subject: [PATCH 8/9] Add user details command handler, dispatcher and responder --- src/database.py | 62 ++++++++++++++++++++++++++++++++++++++ src/definitions.py | 1 + src/dispatcher.py | 74 +++++++++++++++++++++++++++++++++++++++++++++- src/handlers.py | 27 +++++++++++++++++ src/responder.py | 40 +++++++++++++++++++++++++ src/server.py | 1 + 6 files changed, 204 insertions(+), 1 deletion(-) diff --git a/src/database.py b/src/database.py index 0625c81..00b0f3e 100644 --- a/src/database.py +++ b/src/database.py @@ -773,4 +773,66 @@ def get_team_users(team_id): result = [r for r in cursor.fetchall()] cursor.close() db_connection.close() + return result + +def get_user_details_from_slack_id(slack_id): + """ Gets an user details from its slack id.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get user details: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT slack_id, slack_name, user_id, team + FROM users + WHERE slack_id = %s + """ + data = ( + slack_id, + ) + try: + cursor.execute(sql_string, data) + except Exception as ex: + log.error("Couldn't get user details: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = cursor.fetchone() + cursor.close() + db_connection.close() + return result + +def get_user_details_from_user_id(user_id): + """ Gets an user details from its user id.""" + try: + db_connection = connect() + except exceptions.DatabaseConnectionError as ex: + log.critical("Couldn't get user details: {}".format(ex)) + raise exceptions.QueryDatabaseError("Could not connect to database: {}".format(ex)) + else: + cursor = db_connection.cursor() + + sql_string = """ + SELECT slack_id, slack_name, user_id, team + FROM users + WHERE user_id = %s + """ + data = ( + user_id, + ) + try: + cursor.execute(sql_string, data) + except Exception as ex: + log.error("Couldn't get user details: {}".format(ex)) + cursor.close() + db_connection.close() + raise exceptions.QueryDatabaseError("Could not perform database select query: {}".format(ex)) + else: + result = cursor.fetchone() + cursor.close() + db_connection.close() return result \ No newline at end of file diff --git a/src/definitions.py b/src/definitions.py index a9286f7..a6b2d6b 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -23,6 +23,7 @@ "LIST_TEAMS": "/ver-equipas", "LIST_TEAMS_REGISTRATION": "/ver-equipas-registo", "TEAM_DETAILS": "/detalhes-equipa", + "USER_DETAILS": "/detalhes", } INITIAL_TEAM_BALANCE = 200 diff --git a/src/dispatcher.py b/src/dispatcher.py index f4d239b..14f203d 100644 --- a/src/dispatcher.py +++ b/src/dispatcher.py @@ -10,6 +10,7 @@ import random import string import re +import uuid common.setup_logger() @@ -45,6 +46,8 @@ def general_dispatcher(): list_teams_registration_dispatcher(request) elif request["command"] == SLACK_COMMANDS["TEAM_DETAILS"]: team_details_dispatcher(request) + elif request["command"] == SLACK_COMMANDS["USER_DETAILS"]: + user_details_dispatcher(request) else: log.critical("Invalid request command.") @@ -545,6 +548,10 @@ def team_details_dispatcher(request): # Bad usage log.warn("Bad format on command given.") responder.team_details_delayed_reply_bad_usage(request) + try: + database.save_request_log(request, False, "Not enough arguments.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") return try: @@ -569,6 +576,62 @@ def team_details_dispatcher(request): log.error("Failed to save request log on database.") responder.team_details_delayed_reply_success(request, details, users) +def user_details_dispatcher(request): + """Dispatcher to user details requests/commands.""" + log.debug("User details request.") + # Get user from args + args = get_request_args(request["text"]) + if not args or len(args) > 1: + # Bad usage + log.warn("Bad format on command given.") + responder.user_details_delayed_reply_bad_usage(request) + try: + database.save_request_log(request, False, "Not enough arguments.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + return + + user = args[0] + user_id = get_slack_user_id_from_arg(user) + if user_id: + try: + user_info = database.get_user_details_from_slack_id(user_id) + except exceptions.QueryDatabaseError as ex: + log.critical("User details search failed: {}".format(ex)) + try: + database.save_request_log(request, False, "Could not fetch user details from the database.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.default_error() + return + elif check_valid_uuid4(user): + try: + user_info = database.get_user_details_from_user_id(user) + except exceptions.QueryDatabaseError as ex: + log.critical("User details search failed: {}".format(ex)) + try: + database.save_request_log(request, False, "Could not fetch user details from the database.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.default_error() + return + else: + log.debug("Both formats invalid.") + try: + database.save_request_log(request, False, "Invalid user_id/slack name.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + responder.user_details_delayed_reply_bad_usage(request) + return + + log.debug(user_info) + try: + database.save_request_log(request, True, "User details fetched.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log on database.") + + responder.user_details_delayed_reply_success(request, user_info) + def add_request_to_queue(request): """ Add a request to the requests queue.""" try: @@ -618,4 +681,13 @@ def parse_transaction_quantity(amount_str): raise exceptions.IntegerParseError("Failed to convert string to int.") def parse_transaction_description(description_list): - return " ".join(description_list) \ No newline at end of file + return " ".join(description_list) + +def check_valid_uuid4(arg): + try: + uuid.UUID(arg, version=4) + except ValueError: + log.warn("Invalid uuid4 format.") + return False + else: + return True \ No newline at end of file diff --git a/src/handlers.py b/src/handlers.py index b58f861..de0a5da 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -239,6 +239,33 @@ def team_details(): log.error("Failed to save request log.") return responder.unverified_origin_error() +def user_details(): + """Handler to list a user details.""" + log.debug("New user details request.") + request_data = dict(request.POST) + + if check_request_origin(request): + if all_elements_on_request(request_data): + # Procceed with request. + log.debug("Request with correct fields, add to queue.") + if dispatcher.add_request_to_queue(request_data): + # Request was added to queue + return responder.confirm_user_details_command_reception() + else: + # Request wasn't added to queue + return responder.overloaded_error() + else: + # Inform user of incomplete request. + log.warn("Request with invalid payload was sent.") + return responder.default_error() + else: + # Could not validate user request + log.error("Slack request origin verification failed.") + try: + database.save_request_log(request_data, False, "Unverified origin.") + except exceptions.SaveRequestLogError: + log.error("Failed to save request log.") + return responder.unverified_origin_error() def all_elements_on_request(request_data): """Check if all elements (keys) are present in the request dictionary""" diff --git a/src/responder.py b/src/responder.py index d616a18..e1a5116 100644 --- a/src/responder.py +++ b/src/responder.py @@ -82,6 +82,14 @@ def confirm_team_details_command_reception(): } return json.dumps(response_content, ensure_ascii=False).encode("utf-8") +def confirm_user_details_command_reception(): + """Immediate response to a user details command.""" + response.add_header("Content-Type", "application/json") + response_content = { + "text": "Vou tratar de ir buscar os detalhes desse utilizador!", + } + return json.dumps(response_content, ensure_ascii=False).encode("utf-8") + def create_team_delayed_reply_missing_arguments(request): """Delayed response to Slack reporting not enough arguments on create team command""" log.debug("Missing arguments on create team request.") @@ -450,6 +458,38 @@ def team_details_delayed_reply_success(request, details, users): except exceptions.POSTRequestError: log.critical("Failed to send delayed message to Slack.") +def user_details_delayed_reply_bad_usage(request): + """Delayed response to Slack reporting a bad usage on user details command.""" + response_content = { + "text": "Má utilização do comando! Utilização: `/detalhes [@user|user-id]`.\n Podes fornecer tanto o user pelo seu ID, bem como pela @mention.", + } + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + +def user_details_delayed_reply_success(request, user_info): + """Delayed response to Slack reporting the results of user details command.""" + + if user_info: + response_content = { + "text": "*Informação:*\n*Nome:* <@{}|{}> | *ID:* {} | *Equipa:* {}".format(user_info[0], user_info[1], user_info[2], user_info[3]), + } + else: + response_content = { + "text": "*Informação:* Não foi encontrado nenhum utilizador com esse ID/nome.", + } + try: + if send_delayed_response(request['response_url'], response_content): + log.debug("Delayed message sent successfully.") + else: + log.critical("Delayed message not sent.") + except exceptions.POSTRequestError: + log.critical("Failed to send delayed message to Slack.") + def default_error(): """Immediate default response to report an error.""" response.add_header("Content-Type", "application/json") diff --git a/src/server.py b/src/server.py index 7ecb3db..5894b1c 100644 --- a/src/server.py +++ b/src/server.py @@ -37,3 +37,4 @@ def define_routing(app): app.route(path="/list-teams", method=["POST"], callback=handlers.list_teams) app.route(path="/list-teams-registration", method=["POST"], callback=handlers.list_teams_registration) app.route(path="/team-details", method=["POST"], callback=handlers.team_details) + app.route(path="/user-details", method=["POST"], callback=handlers.user_details) From a9df2579da5d7f998cf89e5f0185b5197e52d19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Coelho?= Date: Sat, 22 Sep 2018 14:14:49 +0100 Subject: [PATCH 9/9] Update README --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index abcf3f5..4568c7b 100644 --- a/README.md +++ b/README.md @@ -24,24 +24,22 @@ List all teams. Provides the team name and team id of each team participating. `/ver-equipas-registo` List all registered teams. Provides the team name and team id and entry code of each team registered. ### View team details -```./detalhes-equipa ``` \ +`/detalhes-equipa ` Used to list all details of a team. The `team-id` must be provided. +### View user details +`/detalhes-participante <@user|user-id>` +Used to list details of a participant. The `@user` or `user-id` must be provided. ## Current features: - Request origin verification/validation ## To be added -```./detalhes-participante <@user>``` \ -Can only be performed by admins. Used to list all details of a participant. The `@user` must be provided. - ```./bug ``` \ Can only be performed by admins. Used to change all teams balances. - ```./tornar-admin <@user>``` \ Can only be performed by admins. Used to make `@user` an admin. - ## Features to add - Auto add users to channels - Report logs to channel