Merge pull request #659 from GSA/ADMIN-97_Resend_Expired_Invites

Admin 97 resend expired invites
This commit is contained in:
Carlo Costino
2023-12-20 14:05:41 -05:00
committed by GitHub
303 changed files with 450 additions and 460 deletions

View File

@@ -117,9 +117,7 @@ def purge_functional_test_data(user_email_prefix):
current_app.logger.error("Can only be run in development")
return
users = User.query.filter(
User.email_address.like("{}%".format(user_email_prefix))
).all()
users = User.query.filter(User.email_address.like(f"{user_email_prefix}%")).all()
for usr in users:
# Make sure the full email includes a uuid in it
# Just in case someone decides to use a similar email address.
@@ -127,9 +125,7 @@ def purge_functional_test_data(user_email_prefix):
uuid.UUID(usr.email_address.split("@")[0].split("+")[1])
except ValueError:
print(
"Skipping {} as the user email doesn't contain a UUID.".format(
usr.email_address
)
f"Skipping {usr.email_address} as the user email doesn't contain a UUID."
)
else:
services = dao_fetch_all_services_by_user(usr.id)
@@ -164,7 +160,7 @@ def purge_functional_test_data(user_email_prefix):
def insert_inbound_numbers_from_file(file_name):
# TODO maintainability what is the purpose of this command? Who would use it and why?
print("Inserting inbound numbers from {}".format(file_name))
print(f"Inserting inbound numbers from {file_name}")
with open(file_name) as file:
sql = "insert into inbound_numbers values('{}', '{}', 'sns', null, True, now(), null);"
@@ -199,9 +195,7 @@ def rebuild_ft_billing_for_day(service_id, day):
def rebuild_ft_data(process_day, service):
deleted_rows = delete_billing_data_for_service_for_day(process_day, service)
current_app.logger.info(
"deleted {} existing billing rows for {} on {}".format(
deleted_rows, service, process_day
)
f"deleted {deleted_rows} existing billing rows for {service} on {process_day}"
)
transit_data = fetch_billing_data_for_day(
process_day=process_day, service_id=service
@@ -211,9 +205,7 @@ def rebuild_ft_billing_for_day(service_id, day):
# upsert existing rows
update_fact_billing(data, process_day)
current_app.logger.info(
"added/updated {} billing rows for {} on {}".format(
len(transit_data), service, process_day
)
f"added/updated {len(transit_data)} billing rows for {service} on {process_day}"
)
if service_id:
@@ -276,7 +268,7 @@ def bulk_invite_user_to_service(file_name, service_id, user_id, auth_type, permi
}
current_app.logger.info(f"DATA = {data}")
with current_app.test_request_context(
path="/service/{}/invite/".format(service_id),
path=f"/service/{service_id}/invite/",
method="POST",
data=json.dumps(data),
headers={"Content-Type": "application/json"},
@@ -286,16 +278,12 @@ def bulk_invite_user_to_service(file_name, service_id, user_id, auth_type, permi
current_app.logger.info(f"RESPONSE {response[1]}")
if response[1] != 201:
print(
"*** ERROR occurred for email address: {}".format(
email_address.strip()
)
f"*** ERROR occurred for email address: {email_address.strip()}"
)
print(response[0].get_data(as_text=True))
except Exception as e:
print(
"*** ERROR occurred for email address: {}. \n{}".format(
email_address.strip(), e
)
f"*** ERROR occurred for email address: {email_address.strip()}. \n{e}"
)
file.close()
@@ -318,7 +306,7 @@ def bulk_invite_user_to_service(file_name, service_id, user_id, auth_type, permi
)
def update_jobs_archived_flag(start_date, end_date):
current_app.logger.info(
"Archiving jobs created between {} to {}".format(start_date, end_date)
f"Archiving jobs created between {start_date} to {end_date}"
)
process_date = start_date
@@ -331,21 +319,20 @@ def update_jobs_archived_flag(start_date, end_date):
where
created_at >= (date :start + time '00:00:00')
and created_at < (date :end + time '00:00:00')
"""
"""
result = db.session.execute(
sql, {"start": process_date, "end": process_date + timedelta(days=1)}
)
db.session.commit()
current_app.logger.info(
"jobs: --- Completed took {}ms. Archived {} jobs for {}".format(
datetime.now() - start_time, result.rowcount, process_date
)
f"jobs: --- Completed took {datetime.now() - start_time}ms. Archived "
f"{result.rowcount} jobs for {process_date}"
)
process_date += timedelta(days=1)
total_updated += result.rowcount
current_app.logger.info("Total archived jobs = {}".format(total_updated))
current_app.logger.info(f"Total archived jobs = {total_updated}")
@notify_command(name="populate-organizations-from-file")
@@ -551,9 +538,7 @@ def fix_billable_units():
show_prefix=notification.service.prefix_sms,
)
print(
"Updating notification: {} with {} billable_units".format(
notification.id, template.fragment_count
)
f"Updating notification: {notification.id} with {template.fragment_count} billable_units"
)
Notification.query.filter(Notification.id == notification.id).update(
@@ -586,9 +571,7 @@ def process_row_from_job(job_id, job_row_number):
if row.index == job_row_number:
notification_id = process_row(row, template, job, job.service)
current_app.logger.info(
"Process row {} for job {} created notification_id: {}".format(
job_row_number, job_id, notification_id
)
f"Process row {job_row_number} for job {job_id} created notification_id: {notification_id}"
)
@@ -623,9 +606,7 @@ def populate_annual_billing_with_the_previous_years_allowance(year):
latest_annual_billing, {"service_id": row.id}
)
free_allowance = [x[0] for x in free_allowance_rows]
print(
"create free limit of {} for service: {}".format(free_allowance[0], row.id)
)
print(f"create free limit of {free_allowance[0]} for service: {row.id}")
dao_create_or_update_annual_billing_for_year(
service_id=row.id,
free_sms_fragment_limit=free_allowance[0],

View File

@@ -1,7 +1,7 @@
from datetime import datetime, timedelta
from app import db
from app.models import INVITE_EXPIRED, InvitedUser
from app.models import INVITE_EXPIRED, INVITE_PENDING, InvitedUser
def save_invited_user(invited_user):
@@ -13,32 +13,36 @@ def get_invited_user_by_service_and_id(service_id, invited_user_id):
return InvitedUser.query.filter(
InvitedUser.service_id == service_id,
InvitedUser.id == invited_user_id,
InvitedUser.status != INVITE_EXPIRED,
).one()
def get_expired_invite_by_service_and_id(service_id, invited_user_id):
return InvitedUser.query.filter(
InvitedUser.service_id == service_id,
InvitedUser.id == invited_user_id,
InvitedUser.status == INVITE_EXPIRED,
).one()
def get_invited_user_by_id(invited_user_id):
return InvitedUser.query.filter(
InvitedUser.id == invited_user_id, InvitedUser.status != INVITE_EXPIRED
).one()
return InvitedUser.query.filter(InvitedUser.id == invited_user_id).one()
def get_expired_invited_users_for_service(service_id):
return InvitedUser.query.filter(
InvitedUser.service_id == service_id, InvitedUser.status == INVITE_EXPIRED
).all()
return InvitedUser.query.filter(InvitedUser.service_id == service_id).all()
def get_invited_users_for_service(service_id):
return InvitedUser.query.filter(
InvitedUser.service_id == service_id, InvitedUser.status != INVITE_EXPIRED
).all()
return InvitedUser.query.filter(InvitedUser.service_id == service_id).all()
def expire_invitations_created_more_than_two_days_ago():
expired = (
db.session.query(InvitedUser)
.filter(InvitedUser.created_at <= datetime.utcnow() - timedelta(days=2))
.filter(
InvitedUser.created_at <= datetime.utcnow() - timedelta(days=2),
InvitedUser.status.in_((INVITE_PENDING,)),
)
.update({InvitedUser.status: INVITE_EXPIRED})
)
db.session.commit()

View File

@@ -87,9 +87,7 @@ def persist_notification(
if not notification_id:
notification_id = uuid.uuid4()
current_app.logger.info(
"Persisting notification with id {}".format(notification_id)
)
current_app.logger.info(f"Persisting notification with id {notification_id}")
notification = Notification(
id=notification_id,
@@ -124,9 +122,7 @@ def persist_notification(
notification.phone_prefix = recipient_info.country_prefix
notification.rate_multiplier = recipient_info.billable_units
elif notification_type == EMAIL_TYPE:
current_app.logger.info(
"Persisting notification with type: {}".format(EMAIL_TYPE)
)
current_app.logger.info(f"Persisting notification with type: {EMAIL_TYPE}")
notification.normalised_to = format_email_address(notification.to)
# if simulated create a Notification model to return but do not persist the Notification to the dB
@@ -141,9 +137,7 @@ def persist_notification(
)
current_app.logger.info(
"{} {} created at {}".format(
notification_type, notification_id, notification_created_at
)
f"{notification_type} {notification_id} created at {notification_created_at}"
)
return notification
@@ -170,15 +164,16 @@ def send_notification_to_queue_detached(
raise
current_app.logger.debug(
"{} {} sent to the {} queue for delivery".format(
notification_type, notification_id, queue
)
f"{notification_type} {notification_id} sent to the {queue} queue for delivery"
)
def send_notification_to_queue(notification, queue=None):
send_notification_to_queue_detached(
notification.key_type, notification.notification_type, notification.id, queue
notification.key_type,
notification.notification_type,
notification.id,
queue,
)

View File

@@ -1,9 +1,12 @@
from datetime import datetime
from flask import Blueprint, current_app, jsonify, request
from itsdangerous import BadData, SignatureExpired
from notifications_utils.url_safe_token import check_token, generate_token
from app.config import QueueNames
from app.dao.invited_user_dao import (
get_expired_invite_by_service_and_id,
get_expired_invited_users_for_service,
get_invited_user_by_id,
get_invited_user_by_service_and_id,
@@ -12,7 +15,7 @@ from app.dao.invited_user_dao import (
)
from app.dao.templates_dao import dao_get_template_by_id
from app.errors import InvalidRequest, register_errors
from app.models import EMAIL_TYPE, KEY_TYPE_NORMAL, Service
from app.models import EMAIL_TYPE, INVITE_PENDING, KEY_TYPE_NORMAL, Service
from app.notifications.process_notifications import (
persist_notification,
send_notification_to_queue,
@@ -24,15 +27,11 @@ service_invite = Blueprint("service_invite", __name__)
register_errors(service_invite)
@service_invite.route("/service/<service_id>/invite", methods=["POST"])
def create_invited_user(service_id):
request_json = request.get_json()
invited_user = invited_user_schema.load(request_json)
save_invited_user(invited_user)
def _create_service_invite(invited_user, invite_link_host):
template_id = current_app.config["INVITATION_EMAIL_TEMPLATE_ID"]
template = dao_get_template_by_id(template_id)
service = Service.query.get(current_app.config["NOTIFY_SERVICE_ID"])
saved_notification = persist_notification(
@@ -43,10 +42,7 @@ def create_invited_user(service_id):
personalisation={
"user_name": invited_user.from_user.name,
"service_name": invited_user.service.name,
"url": invited_user_url(
invited_user.id,
request_json.get("invite_link_host"),
),
"url": invited_user_url(invited_user.id, invite_link_host),
},
notification_type=EMAIL_TYPE,
api_key_id=None,
@@ -56,6 +52,15 @@ def create_invited_user(service_id):
send_notification_to_queue(saved_notification, queue=QueueNames.NOTIFY)
@service_invite.route("/service/<service_id>/invite", methods=["POST"])
def create_invited_user(service_id):
request_json = request.get_json()
invited_user = invited_user_schema.load(request_json)
save_invited_user(invited_user)
_create_service_invite(invited_user, request_json.get("invite_link_host"))
return jsonify(data=invited_user_schema.dump(invited_user)), 201
@@ -92,6 +97,36 @@ def update_invited_user(service_id, invited_user_id):
return jsonify(data=invited_user_schema.dump(fetched)), 200
@service_invite.route(
"/service/<service_id>/invite/<invited_user_id>/resend", methods=["POST"]
)
def resend_service_invite(service_id, invited_user_id):
"""Resend an expired invite.
This resets the invited user's created date and status to make it a new invite, and
sends the new invite out to the user.
Note:
This ignores the POST data entirely.
"""
fetched = get_expired_invite_by_service_and_id(
service_id=service_id,
invited_user_id=invited_user_id,
)
fetched.created_at = datetime.utcnow()
fetched.status = INVITE_PENDING
current_data = {k: v for k, v in invited_user_schema.dump(fetched).items()}
update_dict = invited_user_schema.load(current_data)
save_invited_user(update_dict)
_create_service_invite(fetched, current_app.config["ADMIN_BASE_URL"])
return jsonify(data=invited_user_schema.dump(fetched)), 200
def invited_user_url(invited_user_id, invite_link_host=None):
token = generate_token(
str(invited_user_id),