Skip to content

Commit

Permalink
Merge pull request #29 from the-deep/feature/simple-deployment
Browse files Browse the repository at this point in the history
Add basic script for deployment
  • Loading branch information
AdityaKhatri authored Jul 18, 2024
2 parents 635021f + 5f81a2b commit 6736857
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/questionnaire/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def bulk_update_questionnaire_question_groups_leaf_order(
_data = {
int(d['id']): d['order']
for d in process_input_data(data)
if d['id']
}
queryset = QuestionLeafGroupType.get_queryset(None, None, info).filter(
questionnaire=questionnaire_id,
Expand Down
1 change: 1 addition & 0 deletions apps/user/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CustomUserAdmin(UserAdmin):
(_('Misc'), {
'fields': (
'email_opt_outs',
'invalid_email',
)
}),
)
Expand Down
9 changes: 9 additions & 0 deletions deploy/run_web.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/bash

# /code/deploy/ -> /code/
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT_DIR=$(dirname "$BASE_DIR")

cd $ROOT_DIR

uwsgi --ini ./deploy/uwsgi.ini # Start uwsgi server
15 changes: 15 additions & 0 deletions deploy/run_worker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /bin/bash

# /code/deploy/ -> /code/
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT_DIR=$(dirname "$BASE_DIR")

cd $ROOT_DIR

echo '>> [Running] Django Collectstatic and Migrate'

./manage.py collectstatic --no-input
./manage.py migrate --no-input

# Start celery
celery -A main worker -Q CELERY-DEFAULT-QUEUE,CELERY-EXPORT-HEAVY-QUEUE -E --concurrency=2 -l info
12 changes: 12 additions & 0 deletions deploy/uwsgi.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[uwsgi]

chdir = /code/
module = main.wsgi

master = true
processes = 10
http = 0.0.0.0:80
chmod = 666

vacuum = true
harakiri = 60
14 changes: 14 additions & 0 deletions main/sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@
]
IGNORED_LOGGERS = [
"graphql.execution.utils",
"CSSUTILS",
]

for _logger in IGNORED_LOGGERS:
ignore_logger(_logger)


def before_send(event, hint):
if 'exc_info' in hint:
exc_type, exc_value, _ = hint['exc_info']
# Ignore 'User is not authenticated' error message
if (
issubclass(exc_type, PermissionError) and
str(exc_value) == 'User is not authenticated'
):
return
return event


def init_sentry(app_type, tags={}, **config):
integrations = [
DjangoIntegration(),
Expand All @@ -28,6 +41,7 @@ def init_sentry(app_type, tags={}, **config):
traces_sample_rate=settings.SENTRY_SAMPLE_RATE,
ignore_errors=IGNORED_ERRORS,
integrations=integrations,
before_send=before_send,
)
with sentry_sdk.configure_scope() as scope:
scope.set_tag("app_type", app_type)
Expand Down
71 changes: 71 additions & 0 deletions main/ses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import json
import logging

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

from sns_message_validator import (
InvalidMessageTypeException,
InvalidCertURLException,
InvalidSignatureVersionException,
SignatureVerificationFailureException,
SNSMessageValidator,
)

from apps.user.models import User


logger = logging.getLogger(__name__)


sns_message_validator = SNSMessageValidator()


def verify_sns_payload(request) -> tuple[str, int]:
# Validate message type from header without having to parse the request body.
message_type = request.headers.get('x-amz-sns-message-type')
try:
sns_message_validator.validate_message_type(message_type)
message = json.loads(request.body.decode('utf-8'))
sns_message_validator.validate_message(message=message)
except InvalidMessageTypeException:
return 'Invalid message type.', 400
except json.decoder.JSONDecodeError:
return 'Request body is not in json format.', 400
except InvalidCertURLException:
return 'Invalid certificate URL.', 400
except InvalidSignatureVersionException:
return 'Unexpected signature version.', 400
except SignatureVerificationFailureException:
return 'Failed to verify signature.', 400
return 'Success', 200


@csrf_exempt
def bounce_handler_view(request):
if request.method != 'POST':
# Return empty error
return JsonResponse({'message': f'{request.method} Method not allowed'}, status=405)

error_message, status_code = verify_sns_payload(request)
if status_code != 200:
logger.warning(f'Failed to handle bounce request: {error_message}')
return JsonResponse({'message': error_message}, status=status_code)

body = json.loads(request.body.decode('utf-8'))
if 'SubscribeURL' in body:
logger.warning(f'Verify subscription using this url: {body["SubscribeURL"]}')
return JsonResponse({'message': 'Logged'}, status=200)

# Do something with the data
message = json.loads(body['Message'])
notification_type = message['notificationType']
if notification_type == 'Bounce':
recipients = message['bounce']['bouncedRecipients']
bounce_type = message['bounce']['bounceType']
if bounce_type == 'Permanent':
for recipient in recipients:
email_address = recipient['emailAddress']
User.objects.filter(email__iexact=email_address).update(invalid_email=True)
logger.warning(f'Flagged {email_address} as invalid email')
return JsonResponse({'message': 'Success'}, status=200)
2 changes: 1 addition & 1 deletion main/storages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class S3StaticStorage(S3Boto3Storage):
location = 'static'
default_acl = 'public-read'
# NOTE: We need to set 'public-read' in s3 policy
querystring_auth = False

def get_default_settings(self):
Expand Down
5 changes: 4 additions & 1 deletion main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, re_path
from django.conf.urls.static import static
from django.conf import settings

from main.graphql.schema import CustomAsyncGraphQLView, schema as graphql_schema
from main.ses import bounce_handler_view
from apps.user.views import unsubscribe_email


Expand All @@ -39,6 +40,8 @@
unsubscribe_email,
name='unsubscribe_email'
),

re_path('ses-bounce/?$', bounce_handler_view, name='ses_bounce'),
]


Expand Down
Loading

0 comments on commit 6736857

Please sign in to comment.