Skip to content

Commit

Permalink
Merge branch 'feature/vtex-apptype' of github.com:weni-ai/weni-integr…
Browse files Browse the repository at this point in the history
…ations-engine into feature/create-vtex-catalog
  • Loading branch information
elitonzky committed Dec 4, 2023
2 parents f32a2d4 + 272ac74 commit 9d53cc0
Show file tree
Hide file tree
Showing 35 changed files with 976 additions and 245 deletions.
18 changes: 18 additions & 0 deletions marketplace/applications/migrations/0017_alter_app_platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2023-11-23 20:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('applications', '0016_app_configured'),
]

operations = [
migrations.AlterField(
model_name='app',
name='platform',
field=models.CharField(choices=[('IA', 'inteligence-artificial'), ('WF', 'weni-flows'), ('RC', 'rocketchat'), ('VT', 'vtex')], max_length=2),
),
]
2 changes: 2 additions & 0 deletions marketplace/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ class App(AppTypeBaseModel):
PLATFORM_IA = "IA"
PLATFORM_WENI_FLOWS = "WF"
PLATFORM_RC = "RC"
PLATFORM_VTEX = "VT"

PLATFORM_CHOICES = (
(PLATFORM_IA, "inteligence-artificial"),
(PLATFORM_WENI_FLOWS, "weni-flows"),
(PLATFORM_RC, "rocketchat"),
(PLATFORM_VTEX, "vtex"),
)

config = models.JSONField(default=dict)
Expand Down
7 changes: 5 additions & 2 deletions marketplace/clients/facebook/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create_product_feed(self, product_catalog_id, name):

return response.json()

def upload_product_feed(self, feed_id, file):
def upload_product_feed(self, feed_id, file, update_only=False):
url = self.get_url + f"{feed_id}/uploads"

headers = self._get_headers()
Expand All @@ -65,7 +65,10 @@ def upload_product_feed(self, feed_id, file):
file.content_type,
)
}
response = self.make_request(url, method="POST", headers=headers, files=files)
params = {"update_only": update_only}
response = self.make_request(
url, method="POST", headers=headers, params=params, files=files
)
return response.json()

def create_product_feed_by_url(
Expand Down
96 changes: 89 additions & 7 deletions marketplace/clients/vtex/client.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,100 @@
from marketplace.clients.base import RequestClient


class VtexPublicClient(RequestClient):
def list_products(self, domain):
url = f"https://{domain}/api/catalog_system/pub/products/search/"
response = self.make_request(
url, method="GET"
) # TODO: list all paginate products
return response
class VtexAuthorization(RequestClient):
def __init__(self, app_key, app_token):
self.app_key = app_key
self.app_token = app_token

def _get_headers(self):
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"X-VTEX-API-AppKey": self.app_key,
"X-VTEX-API-AppToken": self.app_token,
}
return headers


class VtexCommonClient(RequestClient):
def check_domain(self, domain):
try:
url = f"https://{domain}/api/catalog_system/pub/products/search/"
response = self.make_request(url, method="GET")
return response.status_code == 206
except Exception:
return False


class VtexPublicClient(VtexCommonClient):
def search_product_by_sku_id(self, skuid, domain, sellerid=1):
url = f"https://{domain}/api/catalog_system/pub/products/search?fq=skuId:{skuid}&sellerId={sellerid}"
response = self.make_request(url, method="GET")
return response


class VtexPrivateClient(VtexAuthorization, VtexCommonClient):
def is_valid_credentials(self, domain):
try:
url = (
f"https://{domain}/api/catalog_system/pvt/products/GetProductAndSkuIds"
)
headers = self._get_headers()
response = self.make_request(url, method="GET", headers=headers)
return response.status_code == 200
except Exception:
return False

def list_all_products_sku_ids(self, domain, page_size=1000):
all_skus = []
page = 1

while True:
url = f"https://{domain}/api/catalog_system/pvt/sku/stockkeepingunitids?page={page}&pagesize={page_size}"
headers = self._get_headers()
response = self.make_request(url, method="GET", headers=headers)

sku_ids = response.json()
if not sku_ids:
break

all_skus.extend(sku_ids)
page += 1

return all_skus

def list_active_sellers(self, domain):
url = f"https://{domain}/api/seller-register/pvt/sellers"
headers = self._get_headers()
response = self.make_request(url, method="GET", headers=headers)
sellers_data = response.json()
return [seller["id"] for seller in sellers_data["items"] if seller["isActive"]]

def get_product_details(self, sku_id, domain):
url = (
f"https://{domain}/api/catalog_system/pvt/sku/stockkeepingunitbyid/{sku_id}"
)
headers = self._get_headers()
response = self.make_request(url, method="GET", headers=headers)
return response.json()

def pub_simulate_cart_for_seller(self, sku_id, seller_id, domain):
cart_simulation_url = f"https://{domain}/api/checkout/pub/orderForms/simulation"
payload = {"items": [{"id": sku_id, "quantity": 1, "seller": seller_id}]}

response = self.make_request(cart_simulation_url, method="POST", json=payload)
simulation_data = response.json()

if simulation_data["items"]:
item_data = simulation_data["items"][0]
return {
"is_available": item_data["availability"] == "available",
"price": item_data["price"],
"list_price": item_data["listPrice"],
}
else:
return {
"is_available": False,
"price": 0,
"list_price": 0,
}
2 changes: 2 additions & 0 deletions marketplace/core/types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ class AbstractAppType(ABC):
CATEGORY_CLASSIFIER = "CF"
CATEGORY_TICKETER = "TK"
CATEGORY_EXTERNAL = "EXT"
CATEGORY_ECOMMERCE = "ECM"

CATEGORY_CHOICES = (
(CATEGORY_CHANNEL, "channel"),
(CATEGORY_CLASSIFIER, "classifier"),
(CATEGORY_TICKETER, "ticketer"),
(CATEGORY_EXTERNAL, "external"),
(CATEGORY_ECOMMERCE, "ecommerce"),
)

@abstractproperty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class MockFacebookService:
def __init__(self, *args, **kwargs):
pass

def catalog_creation(self, validated_data, app, vtex_app, user):
def create_vtex_catalog(self, validated_data, app, vtex_app, user):
if validated_data["name"] == "valid_catalog":
return (Catalog(app=app, facebook_catalog_id="123456789"), "123456789")
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def create(self, request, app_uuid, *args, **kwargs):

vtex_app = self.vtex_service.get_vtex_app_or_error(app.project_uuid)

catalog, _fba_catalog_id = self.fb_service.catalog_creation(
catalog, _fba_catalog_id = self.fb_service.create_vtex_catalog(
serializer.validated_data, app, vtex_app, self.request.user
)
if not catalog:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
client (ClientType): An instance of a client responsible for making requests to Facebook's API.
Methods:
catalog_creation(validated_data, app, vtex_app, user): Creates a new catalog associated
create_vtex_catalog(validated_data, app, vtex_app, user): Creates a new catalog associated
with an app and returns the created,
Catalog object along with the Facebook catalog ID.
Expand Down Expand Up @@ -48,7 +48,7 @@ def __init__(self, client):
# Public Methods
# ================================

def catalog_creation(self, validated_data, app, vtex_app, user):
def create_vtex_catalog(self, validated_data, app, vtex_app, user):
business_id = self._get_app_facebook_credentials(app=app).get("wa_business_id")
response = self.client.create_catalog(business_id, validated_data["name"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,17 @@ def setUp(self):
)

# TODO: resolve error "Key (facebook_catalog_id, app_id)=(0123456789010, 3) already exists." when running tests
# def test_catalog_creation_success(self):
# def test_create_vtex_catalog_success(self):
# validated_data = {"name": "Valid Catalog"}
# catalog, fb_catalog_id = self.service.catalog_creation(
# catalog, fb_catalog_id = self.service.create_vtex_catalog(
# validated_data, self.app, self.vtex_app, self.user
# )
# self.assertIsNotNone(catalog)
# self.assertEqual(fb_catalog_id, MockClient.VALID_CATALOGS_ID[0])

# def test_catalog_creation_failure(self):
# def test_create_vtex_catalog_failure(self):
# validated_data = {"name": "Invalid Catalog"}
# catalog, fb_catalog_id = self.service.catalog_creation(
# catalog, fb_catalog_id = self.service.create_vtex_catalog(
# validated_data, self.app, self.vtex_app, self.user
# )
# self.assertIsNone(catalog)
Expand Down
7 changes: 7 additions & 0 deletions marketplace/core/types/ecommerce/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from marketplace.core.types.base import AppType
from marketplace.applications.models import App


class EcommerceAppType(AppType):
platform = App.PLATFORM_VTEX
category = AppType.CATEGORY_ECOMMERCE
51 changes: 51 additions & 0 deletions marketplace/core/types/ecommerce/vtex/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from marketplace.core.serializers import AppTypeBaseSerializer
from marketplace.applications.models import App


class VtexSerializer(serializers.Serializer):
domain = serializers.CharField(required=True)
app_key = serializers.CharField(required=True)
app_token = serializers.CharField(required=True)
wpp_cloud_uuid = serializers.UUIDField(required=True)

def validate_wpp_cloud_uuid(self, value):
"""
Check that the wpp_cloud_uuid corresponds to an existing App with code 'wpp-cloud'.
"""
try:
App.objects.get(uuid=value, code="wpp-cloud")
except App.DoesNotExist:
raise ValidationError(
"The wpp_cloud_uuid does not correspond to a valid 'wpp-cloud' App."
)
return str(value)


class VtexAppSerializer(AppTypeBaseSerializer):
config = serializers.SerializerMethodField()

class Meta:
model = App
fields = (
"code",
"uuid",
"project_uuid",
"platform",
"config",
"created_by",
"created_on",
"modified_by",
)
read_only_fields = ("code", "uuid", "platform")

def get_config(self, obj):
config = obj.config.copy()
api_credentials = config.get("api_credentials", {})
if api_credentials:
api_credentials["app_key"] = "***"
api_credentials["app_token"] = "***"

return config
Empty file.
Loading

0 comments on commit 9d53cc0

Please sign in to comment.