Skip to content

Commit

Permalink
Add in sync_minibc_subscriptions_etl bit
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffwecan committed Mar 8, 2024
1 parent 63077b6 commit 5a0003d
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 108 deletions.
8 changes: 8 additions & 0 deletions member_card/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ def minibc():
pass


@minibc.command("sync-subscriptions")
def minibc_sync_subscriptions():
etl_results = worker.sync_minibc_subscriptions_etl(

Check warning on line 259 in member_card/commands.py

View check run for this annotation

Codecov / codecov/patch

member_card/commands.py#L259

Added line #L259 was not covered by tests
message=dict(type="cli-sync-subscriptions"),
)
logger.info(f"minibc_sync_subscriptions() => {etl_results=}")

Check warning on line 262 in member_card/commands.py

View check run for this annotation

Codecov / codecov/patch

member_card/commands.py#L262

Added line #L262 was not covered by tests


@minibc.command("find-missing-shipping")
def minibc_cmd_find_missing_shipping():
minibc_client = Minibc(api_key=app.config["MINIBC_API_KEY"])
Expand Down
166 changes: 58 additions & 108 deletions member_card/minibc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import logging
from datetime import timedelta, timezone
from datetime import timezone
from time import sleep
from typing import TYPE_CHECKING

import requests
from dateutil.parser import parse, ParserError
from dateutil.parser import ParserError, parse

from member_card.db import db, get_or_update
from member_card.models import table_metadata
from member_card.models.user import ensure_user

# from member_card.models import MinibcWebhook, table_metadata

Expand Down Expand Up @@ -162,109 +161,48 @@ def get_profile_by_email(self, email):
return self.get(path="profiles/", args=dict(filter=f"email,{email}"))


def insert_order_as_membership(order, skus):
from member_card.models import AnnualMembership

membership_orders = []
products = order.get("products", [])
subscription_line_items = [p for p in products if p["sku"] in skus]
ignored_line_items = [p for p in products if p["sku"] not in skus]
logger.debug(f"{ignored_line_items=}")
for subscription_line_item in subscription_line_items:
fulfilled_on = None
if fulfilled_on := order.get("fulfilledOn"):
fulfilled_on = parse(fulfilled_on).replace(tzinfo=timezone.utc)

customer_email = order["customer"]["email"]
# logger.debug(f"{order=}")

weird_dates_keys = [
"created_time",
"last_modified",
"signup_date",
"next_payment_date",
]
weird_dates = {}
for weird_dates_key in weird_dates_keys:
order[weird_dates_key] = order[weird_dates_key].strip("-")
if order[weird_dates_key] == "0":
weird_dates[weird_dates_key] = None
else:
try:
weird_dates[weird_dates_key] = parse(
order[weird_dates_key]
).replace(tzinfo=timezone.utc)
except ParserError as err:
logger.warning(
f"Unable to parse {weird_dates_key} for {customer_email}: {err=}"
)
weird_dates[weird_dates_key] = None

created_on = weird_dates["signup_date"]
if weird_dates["next_payment_date"] is not None:
created_on = weird_dates["next_payment_date"] - timedelta(days=365)

logger.debug(f"{weird_dates['next_payment_date']=} => {created_on=}")
membership_kwargs = dict(
order_id=f"minibc_{str(order['id'])}",
order_number=f"minibc_{order['order_id'] or order['id']}_{order['customer']['store_customer_id']}",
channel="minibc",
channel_name="minibc",
billing_address_first_name=order["customer"]["first_name"],
billing_address_last_name=order["customer"]["last_name"],
external_order_reference=order["customer"]["store_customer_id"],
created_on=created_on,
modified_on=weird_dates["last_modified"],
fulfilled_on=fulfilled_on,
customer_email=customer_email,
fulfillment_status=None,
test_mode=False,
line_item_id=subscription_line_item["order_product_id"],
sku=subscription_line_item["sku"],
variant_id=subscription_line_item["name"],
product_id=subscription_line_item["store_product_id"],
product_name=subscription_line_item["name"],
)
membership = get_or_update(
session=db.session,
model=AnnualMembership,
filters=["order_id"],
kwargs=membership_kwargs,
)
membership_orders.append(membership)

membership_user = ensure_user(
email=membership.customer_email,
first_name=membership.billing_address_first_name,
last_name=membership.billing_address_last_name,
)
membership_user_id = membership_user.id
if not membership.user_id:
logger.debug(
f"No user_id set for {membership=}! Setting to: {membership_user_id=}"
)
setattr(membership, "user_id", membership_user_id)
return membership_orders


def parse_subscriptions(skus, subscriptions):
def parse_subscriptions(subscriptions):
logger.info(f"{len(subscriptions)=} retrieved from Minibc...")

Check warning on line 165 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L165

Added line #L165 was not covered by tests

# Insert oldest orders first (so our internal membership ID generally aligns with order IDs...)
subscriptions.reverse()

Check warning on line 168 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L168

Added line #L168 was not covered by tests

# Loop over all the raw order data and do the ETL bits
memberships = []
subscription_objs = []
from member_card.models import Subscription

Check warning on line 172 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L171-L172

Added lines #L171 - L172 were not covered by tests

for subscription in subscriptions:
membership_orders = insert_order_as_membership(
order=subscription,
skus=skus,
product_name = ",".join([p["name"] for p in subscription["products"]])
shipping_address = " ".join(subscription["shipping_address"].values())
subscription_kwargs = dict(

Check warning on line 177 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L174-L177

Added lines #L174 - L177 were not covered by tests
subscription_id=subscription["id"],
order_id=subscription["order_id"],
customer_id=subscription["customer"]["id"],
customer_first_name=subscription["customer"]["first_name"],
customer_last_name=subscription["customer"]["last_name"],
customer_email=subscription["customer"]["email"],
product_name=product_name,
status=subscription["status"],
shipping_address=shipping_address,
signup_date=parse_weird_dates(subscription["signup_date"]),
pause_date=parse_weird_dates(subscription["pause_date"]),
cancellation_date=parse_weird_dates(subscription["cancellation_date"]),
next_payment_date=parse_weird_dates(subscription["next_payment_date"]),
created_time=parse_weird_dates(subscription["created_time"]),
last_modified=parse_weird_dates(subscription["last_modified"]),
)
for membership_order in membership_orders:
db.session.add(membership_order)
db.session.commit()
memberships += membership_orders
return memberships
subscription_obj = get_or_update(

Check warning on line 194 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L194

Added line #L194 was not covered by tests
session=db.session,
model=Subscription,
filters=["subscription_id"],
kwargs=subscription_kwargs,
)
subscription_objs.append(subscription_obj)

Check warning on line 200 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L200

Added line #L200 was not covered by tests

for subscription_obj in subscription_objs:
db.session.add(subscription_obj)
db.session.commit()
return subscription_objs

Check warning on line 205 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L202-L205

Added lines #L202 - L205 were not covered by tests


def find_missing_shipping(minibc_client: Minibc, skus):
Expand Down Expand Up @@ -323,21 +261,33 @@ def find_missing_shipping(minibc_client: Minibc, skus):
return missing_shipping_subs

Check warning on line 261 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L261

Added line #L261 was not covered by tests


def minibc_orders_etl(minibc_client: Minibc, skus, load_all):
from member_card import models
def parse_weird_dates(date_str):
date_str = date_str.strip("-")
if date_str == "0":
return None

Check warning on line 267 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L265-L267

Added lines #L265 - L267 were not covered by tests

# etl_start_time = datetime.now(tz=ZoneInfo("UTC"))
try:
return parse(date_str).replace(tzinfo=timezone.utc)
except ParserError as err:
logger.warning(f"Unable to parse {date_str}: {err=}")
return None

Check warning on line 273 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L269-L273

Added lines #L269 - L273 were not covered by tests

membership_table_name = models.AnnualMembership.__tablename__

def minibc_subscriptions_etl(minibc_client: Minibc, skus, load_all=False):
from member_card import models

Check warning on line 277 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L277

Added line #L277 was not covered by tests

subscriptions_table_name = models.Subscription.__tablename__

Check warning on line 279 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L279

Added line #L279 was not covered by tests

if load_all:
start_page_num = 1
max_pages = 1000
else:
start_page_num = table_metadata.get_last_run_start_page(membership_table_name)
start_page_num = table_metadata.get_last_run_start_page(

Check warning on line 285 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L285

Added line #L285 was not covered by tests
subscriptions_table_name
)
max_pages = 20

memberships = list()
subscription_objs = list()

Check warning on line 290 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L290

Added line #L290 was not covered by tests

last_page_num = start_page_num
end_page_num = start_page_num + max_pages + 1
Expand All @@ -360,19 +310,19 @@ def minibc_orders_etl(minibc_client: Minibc, skus, load_all):
break

last_page_num = page_num
memberships += parse_subscriptions(skus, subscriptions)
subscription_objs += parse_subscriptions(subscriptions)

Check warning on line 313 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L313

Added line #L313 was not covered by tests
logger.debug(f"after {page_num=} sleeping for 1 second...")
sleep(1)

if not load_all:
logger.debug(
f"Setting start_page_num metadata on {membership_table_name=} to {last_page_num=}"
f"Setting start_page_num metadata on {subscriptions_table_name=} to {last_page_num=}"
)
table_metadata.set_last_run_start_page(
membership_table_name, max(1, last_page_num - 1)
subscriptions_table_name, max(1, last_page_num - 1)
)

return memberships
return subscription_objs

Check warning on line 325 in member_card/minibc.py

View check run for this annotation

Codecov / codecov/patch

member_card/minibc.py#L325

Added line #L325 was not covered by tests


def load_single_subscription(minibc_client: Minibc, skus, order_id):
Expand Down
22 changes: 22 additions & 0 deletions member_card/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from flask import Blueprint, current_app, request

from member_card import minibc
from member_card import bigcommerce, slack
from member_card.db import db
from member_card.image import ensure_uploaded_card_image
Expand Down Expand Up @@ -224,6 +225,26 @@ def sync_customers_etl(message):
)


def sync_minibc_subscriptions_etl(message):
log_extra = dict(pubsub_message=message)
logger.debug(

Check warning on line 230 in member_card/worker.py

View check run for this annotation

Codecov / codecov/patch

member_card/worker.py#L229-L230

Added lines #L229 - L230 were not covered by tests
f"sync_minibc_subscriptions_etl(): Processing message: {message}",
extra=log_extra,
)

minibc_client = minibc.Minibc(api_key=current_app.config["MINIBC_API_KEY"])
membership_skus = current_app.config["BIGCOMMERCE_MEMBERSHIP_SKUS"]

Check warning on line 236 in member_card/worker.py

View check run for this annotation

Codecov / codecov/patch

member_card/worker.py#L235-L236

Added lines #L235 - L236 were not covered by tests

etl_result = minibc.minibc_subscriptions_etl(

Check warning on line 238 in member_card/worker.py

View check run for this annotation

Codecov / codecov/patch

member_card/worker.py#L238

Added line #L238 was not covered by tests
minibc_client=minibc_client,
skus=membership_skus,
)
logger.debug(

Check warning on line 242 in member_card/worker.py

View check run for this annotation

Codecov / codecov/patch

member_card/worker.py#L242

Added line #L242 was not covered by tests
f"sync_minibc_subscriptions_etl(): {len(etl_result)=}",
extra=log_extra,
)


@worker_bp.route("/pubsub", methods=["POST"])
def pubsub_ingress():
try:
Expand All @@ -234,6 +255,7 @@ def pubsub_ingress():
MESSAGE_TYPE_HANDLERS = {
"email_distribution_request": process_email_distribution_request,
"sync_subscriptions_etl": sync_subscriptions_etl,
"sync_minibc_subscriptions_etl": sync_minibc_subscriptions_etl,
"sync_customers_etl": sync_customers_etl,
"sync_squarespace_order": sync_squarespace_order,
"sync_bigcommerce_order": sync_bigcommerce_order,
Expand Down

0 comments on commit 5a0003d

Please sign in to comment.