forked from em-shea/vocab
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
292 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import os | ||
import boto3 | ||
from boto3.dynamodb.conditions import Key | ||
|
||
import sys | ||
sys.path.append('../tests/') | ||
|
||
import format_user_data_service | ||
|
||
table = boto3.resource('dynamodb', region_name=os.environ['AWS_REGION']).Table(os.environ['DYNAMODB_TABLE_NAME']) | ||
|
||
def get_all_users(): | ||
|
||
dynamo_response = pull_all_users() | ||
grouped_user_and_subs = group_users_and_subs(dynamo_response) | ||
response = [] | ||
for user_id, user in grouped_user_and_subs.items(): | ||
response.append(format_user_data_service.format_user_data(user)) | ||
|
||
return response | ||
|
||
def pull_all_users(): | ||
|
||
response = table.query( | ||
IndexName='GSI1', | ||
KeyConditionExpression=Key('GSI1PK').eq('USER') | ||
) | ||
# print(response['Items']) | ||
return response['Items'] | ||
|
||
def group_users_and_subs(dynamo_response): | ||
|
||
grouped_user_and_subs = {} | ||
for item in dynamo_response: | ||
if item['PK'] not in grouped_user_and_subs: | ||
grouped_user_and_subs[item['PK']] = [] | ||
grouped_user_and_subs[item['PK']].append(item) | ||
|
||
# print('grouped: ', grouped_user_and_subs) | ||
return grouped_user_and_subs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import datetime | ||
from typing import List | ||
from dataclasses import dataclass | ||
|
||
@dataclass | ||
class Subscription: | ||
list_name: str | ||
unique_list_id: str | ||
list_id: str | ||
character_set: str | ||
status: str | ||
date_subscribed: str | ||
# def to_dict(self): | ||
# return { | ||
# 'list_name': self.list_name | ||
# } | ||
|
||
@dataclass | ||
class User: | ||
email_address: str | ||
user_id: str | ||
character_set_preference: str | ||
date_created: datetime.datetime | ||
user_alias: str | ||
user_alias_pinyin: str | ||
user_alias_emoji: str | ||
subscriptions: List[Subscription] | ||
|
||
def format_user_data(user_data): | ||
|
||
user = None | ||
subscription_list = [] | ||
|
||
# Loop through all users and subs | ||
for item in user_data: | ||
# print(item) | ||
|
||
# If Dynamo item is user metadata, create User class | ||
if 'Email address' in item: | ||
print('user', item['Email address']) | ||
user = User( | ||
email_address = item['Email address'], | ||
user_id = item['PK'][5:], | ||
character_set_preference = item['Character set preference'], | ||
date_created = item['Date created'], | ||
user_alias = item['User alias'], | ||
user_alias_pinyin = item['User alias pinyin'], | ||
user_alias_emoji = item['User alias emoji'], | ||
subscriptions = [] | ||
) | ||
|
||
# If Dynamo item is a list subscription, add the list to the user's lists dict | ||
if 'List name' in item: | ||
print('list', item['List name']) | ||
# Shortening list id from unique id (ex, LIST#1ebcad40-bb9e-6ece-a366-acde48001122#SIMPLIFIED) | ||
if 'SIMPLIFIED' in item['SK']: | ||
list_id = item['SK'][5:-11] | ||
if 'TRADITIONAL' in item['SK']: | ||
list_id = item['SK'][5:-12] | ||
|
||
sub = Subscription( | ||
list_name = item['List name'], | ||
unique_list_id = item['SK'][5:], | ||
list_id = list_id, | ||
character_set = item['Character set'], | ||
status = item['Status'], | ||
date_subscribed = item['Date subscribed'] | ||
) | ||
subscription_list.append(sub) | ||
|
||
# Sort lists by list id to appear in order (Level 1, Level 2, etc.) | ||
subscription_list = sorted(subscription_list, key=lambda k: k.list_id, reverse=False) | ||
|
||
user.subscriptions = subscription_list | ||
|
||
print('formatted user ', user) | ||
return user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import sys | ||
sys.path.append('../tests/') | ||
|
||
# Temporary service until vocab lists are dynamic and stored in DynamoDB | ||
|
||
def get_vocab_lists(): | ||
return [ | ||
{ | ||
"list_name": "HSK Level 1", | ||
"list_id": "1ebcad3f-5dfd-6bfe-bda4-acde48001122", | ||
"list_difficulty_level": "Beginner", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
}, | ||
{ | ||
"list_name": "HSK Level 2", | ||
"list_id": "1ebcad3f-adc0-6f42-b8b1-acde48001122", | ||
"list_difficulty_level": "Beginner", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
}, | ||
{ | ||
"list_name": "HSK Level 3", | ||
"list_id": "1ebcad3f-f815-6b92-b3e8-acde48001122", | ||
"list_difficulty_level": "Intermediate", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
}, | ||
{ | ||
"list_name": "HSK Level 4", | ||
"list_id": "1ebcad40-414f-6bc8-859d-acde48001122", | ||
"list_difficulty_level": "Intermediate", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
}, | ||
{ | ||
"list_name": "HSK Level 5", | ||
"list_id": "1ebcad40-bb9e-6ece-a366-acde48001122", | ||
"list_difficulty_level": "Advanced", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
}, | ||
{ | ||
"list_name": "HSK Level 6", | ||
"list_id": "1ebcad41-197a-6700-95a3-acde48001122", | ||
"list_difficulty_level": "Advanced", | ||
"date_created": "2018-12-16T23:06:48.467526", | ||
"created_by": "admin" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import os | ||
import json | ||
import boto3 | ||
from random import randint | ||
from datetime import datetime | ||
from boto3.dynamodb.conditions import Key | ||
from botocore.exceptions import ClientError | ||
|
||
# TODO: Add cognito id to unsub URL params | ||
|
||
import random_word_service | ||
import vocab_list_service | ||
import list_word_service | ||
import all_users_service | ||
|
||
# region_name specified in order to mock in unit tests | ||
ses_client = boto3.client('ses', region_name=os.environ['AWS_REGION']) | ||
|
@@ -28,15 +29,15 @@ def lambda_handler(event, context): | |
|
||
todays_announcement = get_announcement() | ||
|
||
# Scan the contacts table for a list of all contacts | ||
users_and_subscriptions = get_users_and_subscriptions() | ||
|
||
users_and_subscriptions_grouped = process_users_and_subscriptions(users_and_subscriptions) | ||
user_list = all_users_service.get_all_users() | ||
|
||
email_counter = 0 | ||
for user_id, user in users_and_subscriptions_grouped.items(): | ||
# print('loop through users') | ||
if len(user['lists'])>0: | ||
for user in user_list: | ||
active_subscription_count = 0 | ||
for subscription in user.subscriptions: | ||
if subscription.status == 'subscribed': | ||
active_subscription_count += 1 | ||
if active_subscription_count>0: | ||
email_content = assemble_html_content(user, todays_words, todays_announcement) | ||
try: | ||
# print('send emails') | ||
|
@@ -77,23 +78,23 @@ def get_announcement(): | |
return announcement_html | ||
|
||
def get_daily_words(): | ||
print('getting daily words...') | ||
|
||
todays_words = {} | ||
|
||
# Hard coding list names for now before refactoring list database | ||
list_names = ['HSK Level 1', 'HSK Level 2', 'HSK Level 3', 'HSK Level 4', 'HSK Level 5', 'HSK Level 6'] | ||
all_lists = vocab_list_service.get_vocab_lists() | ||
|
||
# Loop through all lists and select word | ||
for list in list_names: | ||
level = str(list_names.index(list)+1) | ||
for list in all_lists: | ||
try: | ||
word = random_word_service.select_random_word(level) | ||
todays_words[list] = word | ||
all_words = list_word_service.get_words_in_list(list['list_id']) | ||
random_number = randint(0,len(all_words)-1) | ||
random_word = all_words[random_number] | ||
todays_words[list['list_id']] = random_word | ||
except Exception as e: | ||
# Appending None to the list as a placeholder for the level's word. Emails will not send for this level. | ||
todays_words[list] = None | ||
todays_words[list['list_id']] = None | ||
print(e) | ||
|
||
print('daily words: ', todays_words) | ||
return todays_words | ||
|
||
def store_words(todays_words): | ||
|
@@ -111,58 +112,14 @@ def store_words(todays_words): | |
) | ||
print(response) | ||
|
||
def get_users_and_subscriptions(): | ||
|
||
response = table.query( | ||
IndexName='GSI1', | ||
KeyConditionExpression=Key('GSI1PK').eq('USER') | ||
) | ||
# print(response['Items']) | ||
return response['Items'] | ||
|
||
def process_users_and_subscriptions(users_and_subscriptions): | ||
|
||
users_and_subscriptions_grouped = {} | ||
# users_and_subscriptions_grouped = { | ||
# user_id: { | ||
# 'user_data': {user metadata}, | ||
# 'lists': [ | ||
# {sub metadata} | ||
# ] | ||
# } | ||
# } | ||
|
||
# Loop through all users and subs | ||
for item in users_and_subscriptions: | ||
# If user is not yet in the grouped dict, add a new dict for the user | ||
if item['PK'] not in users_and_subscriptions_grouped: | ||
users_and_subscriptions_grouped[item['PK']] = {'user_data': None, 'lists': []} | ||
|
||
# If Dynamo item is a user, add user metadata to the user's dict | ||
if 'Email address' in item: | ||
print('user metadata', item['Email address']) | ||
users_and_subscriptions_grouped[item['PK']]['user_data'] = item | ||
|
||
# If Dynamo item is a list subscription and the status is subscribed, add the list to the user's dict | ||
if 'List name' in item: | ||
print('user list', item['List name']) | ||
if item['Status'] == 'subscribed': | ||
users_and_subscriptions_grouped[item['PK']]['lists'].append(item) | ||
print('subscribed') | ||
else: | ||
print('unsubscribed') | ||
|
||
print(users_and_subscriptions_grouped) | ||
|
||
return users_and_subscriptions_grouped | ||
|
||
def assemble_html_content(user, todays_words, todays_announcement): | ||
|
||
# Appends multiple words into the same email | ||
# TODO: Order the lists in some way? | ||
word_content = "" | ||
for list in user['lists']: | ||
word_content = word_content + assemble_word_html_content(user['user_data']['Email address'], list, todays_words) | ||
for subscription in user.subscriptions: | ||
if subscription.status == 'subscribed': | ||
word_content = word_content + assemble_word_html_content(user.email_address, subscription, todays_words) | ||
|
||
# Open HTML template file | ||
# To run unit tests, we need to specify an absolute file path | ||
|
@@ -172,9 +129,7 @@ def assemble_html_content(user, todays_words, todays_announcement): | |
|
||
# Hard coding HSK level before list database refactor | ||
# Get first list user is subscribed to and use in unsub link | ||
list_id_substring = user['lists'][0]['SK'].split('#') | ||
print('substring, ', list_id_substring) | ||
email_contents = email_template.replace("{unsubscribe_link}", "https://haohaotiantian.com/unsub?list=" + list_id_substring[1] + "&char=" + list_id_substring[2] + "&email=" + user['user_data']['Email address']) | ||
email_contents = email_template.replace("{unsubscribe_link}", "https://haohaotiantian.com/unsub?list=" + user.subscriptions[0].list_id + "&char=" + user.subscriptions[0].character_set + "&email=" + user.email_address) | ||
|
||
email_contents = email_contents.replace("{word_contents}", word_content) | ||
|
||
|
@@ -185,20 +140,23 @@ def assemble_html_content(user, todays_words, todays_announcement): | |
|
||
return email_contents | ||
|
||
def assemble_word_html_content(user_email, list, todays_words): | ||
def assemble_word_html_content(user_email, subscription, todays_words): | ||
print('assembling word content...') | ||
print('list subscription: ', subscription) | ||
|
||
word = todays_words[list['List name']] | ||
word = todays_words[subscription.list_id]['word'] | ||
print('selected word, ', word) | ||
if word is None: | ||
return "" | ||
else: | ||
# Select simplified or traditional character | ||
if list['Character set'] == "simplified": | ||
selected_word = word["Word"] | ||
if subscription.character_set == "simplified": | ||
selected_word = word["Simplified"] | ||
else: | ||
selected_word = word["Word-Traditional"] | ||
selected_word = word["Traditional"] | ||
|
||
# Hard coding list names and sentence URLs before list database refactor | ||
if list['List name'] in ['HSK Level 1', 'HSK Level 2', 'HSK Level 3']: | ||
if subscription.list_name in ['HSK Level 1', 'HSK Level 2', 'HSK Level 3']: | ||
example_link = "https://www.yellowbridge.com/chinese/sentsearch.php?word=" + selected_word | ||
else: | ||
example_link = "https://fanyi.baidu.com/#zh/en/" + selected_word | ||
|
@@ -208,16 +166,16 @@ def assemble_word_html_content(user_email, list, todays_words): | |
word_template = fh.read() | ||
|
||
# Hard coding HSK level before list database refactor | ||
hsk_level = list['List name'][-1] | ||
hsk_level = subscription.list_name[-1] | ||
|
||
word_contents = word_template.replace("{word}", selected_word) | ||
word_contents = word_contents.replace("{pronunciation}", word["Pronunciation"]) | ||
word_contents = word_contents.replace("{pronunciation}", word["Pinyin"]) | ||
word_contents = word_contents.replace("{definition}", word["Definition"]) | ||
word_contents = word_contents.replace("{link}", example_link) | ||
word_contents = word_contents.replace("{list}", list['List name']) | ||
word_contents = word_contents.replace("{quiz_link}", "https://haohaotiantian.com/quiz?list=HSKLevel" + hsk_level + "&days=14&ques=10&char=" + list['Character set']) | ||
word_contents = word_contents.replace("{list}", subscription.list_name) | ||
word_contents = word_contents.replace("{quiz_link}", "https://haohaotiantian.com/quiz?list=HSKLevel" + hsk_level + "&days=14&ques=10&char=" + subscription.character_set) | ||
word_contents = word_contents.replace("{signin_link}", "https://haohaotiantian.com/quiz?email=" + user_email) | ||
word_contents = word_contents.replace("{history_link}", "https://haohaotiantian.com/history?list=HSKLevel" + hsk_level + "&dates=30&char=" + list['Character set']) | ||
word_contents = word_contents.replace("{history_link}", "https://haohaotiantian.com/history?list=HSKLevel" + hsk_level + "&dates=30&char=" + subscription.character_set) | ||
|
||
return word_contents | ||
|
||
|
@@ -227,7 +185,7 @@ def send_email(user, email_content): | |
Source = "Haohaotiantian <[email protected]>", | ||
Destination = { | ||
"ToAddresses" : [ | ||
user['user_data']['Email address'] | ||
user.email_address | ||
] | ||
}, | ||
Message = { | ||
|
Oops, something went wrong.