From d098cd0dd263a44b466456f2ae511339bc3bd6b2 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Fri, 4 Oct 2024 13:39:55 +0530 Subject: [PATCH] Add header path to shortlinks per new TRAI SMS rules from Oct 1 (#2123) --- funnel/views/helpers.py | 24 ++++++++++++++++++- .../notifications/comment_notification.py | 4 ++-- .../project_starting_notification.py | 17 ++++++------- .../notifications/proposal_notification.py | 12 ++++------ .../views/notifications/rsvp_notification.py | 6 ++--- .../notifications/update_notification.py | 12 ++++------ funnel/views/shortlink.py | 13 ++++++---- sample.env | 1 + 8 files changed, 54 insertions(+), 35 deletions(-) diff --git a/funnel/views/helpers.py b/funnel/views/helpers.py index aa9651a60..28faa7eaa 100644 --- a/funnel/views/helpers.py +++ b/funnel/views/helpers.py @@ -718,20 +718,42 @@ def cleanurl_filter(url: str | furl) -> str: @app.template_filter('shortlink') -def shortlink(url: str, actor: Account | None = None, shorter: bool = True) -> str: +def shortlink( + url: str, + actor: Account | None = None, + shorter: bool = True, + header: str | None = None, +) -> str: """ Return a short link suitable for sharing, in a template filter. Caller must perform a database commit. + :param url: URL to shorten + :param actor: Optional actor to save against the shortlink :param shorter: Use a shorter shortlink, ideal for SMS or a small database + :param header: Insert a brand header into the shortlink, for SMS DLT """ sl = Shortlink.new(url, reuse=True, shorter=shorter, actor=actor) db.session.add(sl) g.require_db_commit = True + if header is not None: + return app_url_for( + shortlinkapp, 'link', name=sl.name, header=header, _external=True + ) return app_url_for(shortlinkapp, 'link', name=sl.name, _external=True) +def sms_shortlink(url: str) -> str: + """SMS version of :func:`shortlink`. + + :param url: URL to shorten + """ + return shortlink( + url, shorter=True, header=app.config.get('SMS_DLT_SHORTURL_HEADER') + ) + + # MARK: Request/response handlers ------------------------------------------------------ diff --git a/funnel/views/notifications/comment_notification.py b/funnel/views/notifications/comment_notification.py index b8ea0e719..1c1032879 100644 --- a/funnel/views/notifications/comment_notification.py +++ b/funnel/views/notifications/comment_notification.py @@ -22,7 +22,7 @@ Proposal, ) from ...transports.sms import SmsPriority, SmsTemplate -from ..helpers import shortlink +from ..helpers import sms_shortlink from ..notification import RenderNotification from .mixins import TemplateVarMixin @@ -252,7 +252,7 @@ def email_content(self) -> str: ) def sms(self) -> SmsTemplate: - url = shortlink( + url = sms_shortlink( self.comment.url_for(_external=True, **self.tracking_tags('sms')) ) if self.document_type == 'comment': diff --git a/funnel/views/notifications/project_starting_notification.py b/funnel/views/notifications/project_starting_notification.py index 07eacd46f..1fa2ba269 100644 --- a/funnel/views/notifications/project_starting_notification.py +++ b/funnel/views/notifications/project_starting_notification.py @@ -14,7 +14,7 @@ Session, ) from ...transports.sms import SmsPriority, SmsTemplate -from ..helpers import shortlink +from ..helpers import sms_shortlink from ..notification import RenderNotification from .mixins import TemplateVarMixin @@ -117,9 +117,8 @@ def email_content(self) -> str: def sms(self) -> SmsTemplate: return ProjectStartingTemplate( project=self.project, - url=shortlink( - self.project.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.project.url_for(_external=True, **self.tracking_tags('sms')) ), ) @@ -156,16 +155,14 @@ def sms(self) -> SmsTemplate: return ProjectStartingTomorrowVenueTemplate( account=self.project.account, venue=venue, - url=shortlink( - self.project.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.project.url_for(_external=True, **self.tracking_tags('sms')) ), ) return ProjectStartingTomorrowLocationTemplate( account=self.project.account, location=self.project.location, - url=shortlink( - self.project.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.project.url_for(_external=True, **self.tracking_tags('sms')) ), ) diff --git a/funnel/views/notifications/proposal_notification.py b/funnel/views/notifications/proposal_notification.py index 9a13ec1c9..b5519eb94 100644 --- a/funnel/views/notifications/proposal_notification.py +++ b/funnel/views/notifications/proposal_notification.py @@ -18,7 +18,7 @@ sa_orm, ) from ...transports.sms import SmsPriority, SmsTemplate -from ..helpers import shortlink +from ..helpers import sms_shortlink from ..notification import RenderNotification from .mixins import TemplateVarMixin @@ -110,9 +110,8 @@ def sms(self) -> ProposalReceivedTemplate: return ProposalReceivedTemplate( project=self.project, actor=self.proposal.first_user, - url=shortlink( - self.proposal.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.proposal.url_for(_external=True, **self.tracking_tags('sms')) ), ) @@ -152,8 +151,7 @@ def email_content(self) -> str: def sms(self) -> ProposalSubmittedTemplate: return ProposalSubmittedTemplate( project=self.proposal.project, - url=shortlink( - self.proposal.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.proposal.url_for(_external=True, **self.tracking_tags('sms')) ), ) diff --git a/funnel/views/notifications/rsvp_notification.py b/funnel/views/notifications/rsvp_notification.py index 3021f8627..4a4028c2b 100644 --- a/funnel/views/notifications/rsvp_notification.py +++ b/funnel/views/notifications/rsvp_notification.py @@ -15,7 +15,7 @@ ) from ...transports import email from ...transports.sms import SmsPriority, SmsTemplate -from ..helpers import shortlink +from ..helpers import sms_shortlink from ..notification import RenderNotification from ..schedule import schedule_ical from .mixins import TemplateVarMixin @@ -128,8 +128,8 @@ def sms( ) -> RegistrationConfirmationTemplate | RegistrationConfirmationWithNextTemplate: project = self.rsvp.project next_at = project.next_starting_at() - url = shortlink( - project.url_for(_external=True, **self.tracking_tags('sms')), shorter=True + url = sms_shortlink( + project.url_for(_external=True, **self.tracking_tags('sms')) ) if next_at: return RegistrationConfirmationWithNextTemplate( diff --git a/funnel/views/notifications/update_notification.py b/funnel/views/notifications/update_notification.py index 5294ba650..396796920 100644 --- a/funnel/views/notifications/update_notification.py +++ b/funnel/views/notifications/update_notification.py @@ -8,7 +8,7 @@ from ...models import Account, Project, ProjectUpdateNotification, Update from ...transports.sms import SmsPriority, SmsTemplate -from ..helpers import shortlink +from ..helpers import sms_shortlink from ..notification import RenderNotification from .mixins import TemplateVarMixin @@ -90,16 +90,14 @@ def sms(self) -> UpdateMergedTitleTemplate | UpdateSplitTitleTemplate: if len(self.update.project.title_parts) == 1: return UpdateMergedTitleTemplate( project=self.update.project, - url=shortlink( - self.update.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.update.url_for(_external=True, **self.tracking_tags('sms')) ), ) return UpdateSplitTitleTemplate( project_title=self.update.project, account_title=self.update.project.account, - url=shortlink( - self.update.url_for(_external=True, **self.tracking_tags('sms')), - shorter=True, + url=sms_shortlink( + self.update.url_for(_external=True, **self.tracking_tags('sms')) ), ) diff --git a/funnel/views/shortlink.py b/funnel/views/shortlink.py index bb3f12cef..71d908ae0 100644 --- a/funnel/views/shortlink.py +++ b/funnel/views/shortlink.py @@ -2,10 +2,12 @@ from __future__ import annotations -from datetime import datetime, timedelta +from datetime import timedelta from flask import abort, redirect +from coaster.utils import utcnow + from .. import app, shortlinkapp, unsubscribeapp from ..models import Shortlink from ..typing import Response @@ -18,8 +20,9 @@ def shortlink_index() -> Response: return redirect(app_url_for(app, 'index'), 301) -@shortlinkapp.route('/') -def link(name: str) -> Response: +@shortlinkapp.route('/
/') # 'header' is required for SMS DLT URLs +@shortlinkapp.route('/', defaults={'header': None}) +def link(name: str, header: str | None = None) -> Response: # noqa: ARG001 """Redirect from a shortlink to the full link.""" sl = Shortlink.get(name, True) if sl is None: @@ -29,10 +32,10 @@ def link(name: str) -> Response: response = redirect(str(sl.url), 301) response.cache_control.private = True response.cache_control.max_age = 90 - response.expires = datetime.utcnow() + timedelta(seconds=90) + response.expires = utcnow() + timedelta(seconds=90) # These two borrowed from Bitly and TinyURL's response headers. They tell the - # browser to reproduce the HTTP Referer header that was sent to this endpoint, to + # browser to reproduce the HTTP `Referer` header that was sent to this endpoint, to # send it again to the destination URL # Needs Werkzeug >= 2.0.2 diff --git a/sample.env b/sample.env index 8c2ea66a2..878856b69 100644 --- a/sample.env +++ b/sample.env @@ -239,6 +239,7 @@ FLASK_SMS_TWILIO_FROM=null #: DLT registered entity id and template ids (required for SMS to Indian numbers) FLASK_SMS_DLT_ENTITY_ID=null +FLASK_SMS_DLT_SHORTURL_HEADER=null FLASK_SMS_DLT_TEMPLATE_IDS__web_otp_template=null FLASK_SMS_DLT_TEMPLATE_IDS__project_starting_template=null FLASK_SMS_DLT_TEMPLATE_IDS__project_starting_tomorrow_venue_template=null