diff --git a/teespring-api/Dockerfile b/teespring-api/Dockerfile index 3e9833f..f94b465 100644 --- a/teespring-api/Dockerfile +++ b/teespring-api/Dockerfile @@ -3,6 +3,9 @@ FROM python:alpine WORKDIR /app COPY . . +RUN apk add --no-cache gcc libc-dev libxml2-dev libxslt-dev && \ + rm -rf /var/cache/apk/* + RUN pip install --no-cache-dir --requirement requirements.txt RUN addgroup -g 9999 lilia diff --git a/teespring-api/app.py b/teespring-api/app.py index fd05d26..3b9def5 100644 --- a/teespring-api/app.py +++ b/teespring-api/app.py @@ -9,7 +9,7 @@ import flask_restful.reqparse import sqlalchemy import sqlalchemy.engine -from db import db, Product +from db import db, Product, Variant app = flask.Flask(__name__) @@ -53,6 +53,13 @@ product_fields = { 'image_url': flask_restful.fields.String(), } +variant_fields = { + 'id': flask_restful.fields.Integer(), + 'color': flask_restful.fields.String(), + 'front_url': flask_restful.fields.String(), + 'back_url': flask_restful.fields.String(), +} + filter_parser = flask_restful.reqparse.RequestParser() filter_parser.add_argument('filter', type=str) @@ -102,8 +109,47 @@ class ProductsResource(flask_restful.Resource): return products, 200, {'X-Total-Count': count} +class VariantResource(flask_restful.Resource): + @flask_restful.marshal_with(variant_fields) + def get(self, product_id, id): + q = db.session.query(Variant).filter(Variant.product_id == product_id, Variant.id == id) + variant = q.first() + if not variant: + flask_restful.abort(404, message='Variant {0} does not exist'.format(id)) + return variant, 200 + + +class VariantsResource(flask_restful.Resource): + @flask_restful.marshal_with(variant_fields) + def get(self, product_id): + args = filter_parser.parse_args() + q = db.session.query(Variant).filter(Variant.product_id == product_id) + if args['filter']: + q = q.filter(Variant.color.ilike('%{}%'.format(args['filter']))) + count = q.count() + if args['sort_order'] == 'random': + q = q.order_by(sqlalchemy.func.random()) + elif args['sort_by']: + col = getattr(Variant, args['sort_by'], None) + if col: + if args['sort_order']: + order_by = getattr(col, args['sort_order'], None) + if order_by: + q = q.order_by(order_by()) + else: + q = q.order_by(col) + if args['page_size']: + q = q.limit(args['page_size']) + if args['page_number'] and args['page_size']: + q = q.offset(args['page_number'] * args['page_size']) + variants = q.all() + return variants, 200, {'X-Total-Count': count} + + api.add_resource(ProductResource, '/products/') api.add_resource(ProductsResource, '/products') +api.add_resource(VariantResource, '/products//variants/') +api.add_resource(VariantsResource, '/products//variants') if __name__ == '__main__': diff --git a/teespring-api/db.py b/teespring-api/db.py index 8ac4be0..9448a4d 100644 --- a/teespring-api/db.py +++ b/teespring-api/db.py @@ -15,3 +15,14 @@ class Product(db.Model): days_left = db.Column(db.Integer) url = db.Column(db.String) image_url = db.Column(db.String) + variants = db.relationship('Variant', backref='product') + + +class Variant(db.Model): + __tablename__ = 'variants' + + id = db.Column(db.Integer, primary_key=True) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True) + color = db.Column(db.String) + front_url = db.Column(db.String) + back_url = db.Column(db.String) diff --git a/teespring-api/requirements.txt b/teespring-api/requirements.txt index eeacc1c..9eb3159 100644 --- a/teespring-api/requirements.txt +++ b/teespring-api/requirements.txt @@ -2,4 +2,5 @@ Flask Flask-APScheduler Flask-RESTful Flask-SQLAlchemy +pyquery requests-futures diff --git a/teespring-api/sync.py b/teespring-api/sync.py index 2f92cd4..877c209 100644 --- a/teespring-api/sync.py +++ b/teespring-api/sync.py @@ -1,7 +1,7 @@ import datetime import os -from db import Product +from db import Product, Variant from teespring import Teespring @@ -42,6 +42,18 @@ class Sync(object): product.days_left = cls._get(prod, 'days_left') product.url = cls._get(prod, 'url') product.image_url = cls._get(prod, 'image_url') + for var in cls._get(prod, 'variants'): + color_id = cls._get(var, 'color_id') + q = db.session.query(Variant).filter( + Variant.id == color_id, + Variant.product_id == id) + variant = q.first() + if not variant: + variant = Variant(id=color_id) + variant.front_url = cls._get(var, 'front_url') + variant.back_url = cls._get(var, 'back_url') + variant.color = cls._get(var, 'color_value') + product.variants.append(variant) db.session.add(product) db.session.commit() app.logger.info('Synchronization of products completed') diff --git a/teespring-api/teespring.py b/teespring-api/teespring.py index 8aa8ab1..ed9b9bc 100644 --- a/teespring-api/teespring.py +++ b/teespring-api/teespring.py @@ -2,6 +2,7 @@ import json from urllib.parse import urlparse, parse_qs +from pyquery import PyQuery as pq from requests_futures.sessions import FuturesSession @@ -12,6 +13,22 @@ class Teespring(object): def __init__(self, store_name): self.store_name = store_name + def _parse_variants(self, pid, html): + result = [] + d = pq(html) + for div in d('div.image_stack__container[data-visible-to-buyer="true"][data-product-id="{0}"]'.format(pid)).items(): + color_id = div.attr('data-color-id') + front_url = div.find('div[data-side="front"]').find('img').eq(0).attr('data-original') + back_url = div.find('div[data-side="back"]').find('img').eq(0).attr('data-original') + li = d('li.product__color_list_item[data-product-id="{0}"][data-color-id="{1}"]'.format(pid, color_id)).eq(0) + color_value = li.find('div').eq(0).attr('style').split(':')[1] + result.append(dict( + color_id=color_id, + front_url=front_url, + back_url=back_url, + color_value=color_value)) + return result + def fetch_products(self): session = FuturesSession() def get_products(page): @@ -19,6 +36,7 @@ class Teespring(object): params = dict(page=page) return session.get(url, params=params, headers={'Accept': 'application/json'}) result = [] + requests = [] page = 1 while True: request = get_products(page) @@ -28,10 +46,16 @@ class Teespring(object): data = r.json() for product in data.get('products', []): product['url'] = BASE_URL + product['url'] + requests.append(session.get(product['url'])) result.append(product) next_url = data.get('next') if not next_url: break q = parse_qs(urlparse(next_url).query) page = q.get('page', [])[0] + for product, request in zip(result, requests): + q = parse_qs(urlparse(product['url']).fragment) + pid = q.get('pid', [])[0] + r = request.result() + product['variants'] = self._parse_variants(pid, r.text) return result