├── README.md ├── commerce-admin ├── .docker │ ├── app-entrypoint.sh │ ├── prod-app-entrypoint.sh │ └── worker-entrypoint.sh ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── Dockerfile.app.prod ├── Dockerfile.celery.prod ├── README.md ├── app │ ├── __init__.py │ ├── admin.py │ ├── admin_tenant.py │ ├── api.py │ ├── apps.py │ ├── fixtures │ │ └── fake_data.json │ ├── forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── admin-site.py │ ├── middlewares.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20200702_1404.py │ │ ├── 0003_checkout_remote_id.py │ │ └── __init__.py │ ├── models.py │ ├── receivers.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── auth_app │ ├── __init__.py │ ├── admin.py │ ├── admin_tenant.py │ ├── apps.py │ ├── fixtures │ │ ├── fake_data.json │ │ └── initial_data.json │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── sync_tenant_permissions.py │ ├── managers.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── permissions.py │ ├── tests.py │ └── views.py ├── commerce_admin │ ├── __init__.py │ ├── asgi.py │ ├── celery.py │ ├── settings.py │ ├── urls.py │ ├── utils.py │ └── wsgi.py ├── common │ ├── __init__.py │ ├── models.py │ ├── sync_models.py │ └── validators.py ├── docker-compose.yml ├── fixtures │ ├── __init__.py │ ├── apps.py │ └── management │ │ ├── __init__.py │ │ └── commands │ │ ├── __init__.py │ │ └── db-reset.py ├── manage.py ├── my_admin │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── backends.py │ ├── fixtures │ │ └── initial_data.json │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20200629_1359.py │ │ └── __init__.py │ ├── models.py │ ├── receivers.py │ ├── serializers.py │ ├── tests.py │ └── views.py ├── requirements.txt └── tenant │ ├── __init__.py │ ├── admin.py │ ├── admin_tenant.py │ ├── apps.py │ ├── backends.py │ ├── fixtures │ └── fake_data.json │ ├── managers.py │ ├── middlewares.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── receivers.py │ ├── serializers.py │ ├── tests.py │ ├── utils.py │ └── views.py ├── commerce-shop ├── .docker │ └── entrypoint.sh ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── docker-compose.yaml ├── frontend │ ├── img │ │ ├── 404.png │ │ ├── Adidas-Y-3-KAIWA.png │ │ ├── BANNER.png │ │ ├── HTB1biCEb25TBuNjSspmq6yDRVXau.png │ │ ├── Imagem_3.png │ │ ├── Imagem_4.png │ │ ├── Nike-Air270-black.png │ │ ├── NikeAirZoomZeroPremium.png │ │ ├── Puma_Clyde_Court_Men.png │ │ ├── Puma_HOT_WHEELS_RS-X.png │ │ ├── ZEUSLAP.png │ │ ├── adidas-y-3-2.png │ │ ├── adidasFALCON.png │ │ ├── apple-watch.png │ │ ├── beats-headphones.png │ │ ├── bike2.png │ │ ├── card.png │ │ ├── favicon.ico │ │ ├── homepod.png │ │ ├── ipad-pro.png │ │ ├── iphone-x.png │ │ ├── jbl-speaker.png │ │ ├── jlab-audio-wireless.png │ │ ├── logo-nav.png │ │ ├── macbook-pro.png │ │ ├── menu.png │ │ ├── search.png │ │ ├── shopping-cart.png │ │ ├── slip.png │ │ ├── sort-down.png │ │ └── symbol_22_____3.png │ ├── js │ │ ├── checkout.js │ │ ├── component │ │ │ ├── Address.js │ │ │ ├── CustomerProfile.js │ │ │ ├── Payment.js │ │ │ ├── PaymentSuccess.js │ │ │ └── Step │ │ │ │ ├── StepContext.js │ │ │ │ ├── StepContextProvider.js │ │ │ │ └── index.js │ │ ├── http │ │ │ └── index.js │ │ ├── main.js │ │ └── util │ │ │ ├── url.js │ │ │ └── yup.js │ └── scss │ │ ├── _assets.scss │ │ ├── _cart.scss │ │ ├── _category.scss │ │ ├── _commons.scss │ │ ├── _footer.scss │ │ ├── _home.scss │ │ ├── _login.scss │ │ ├── _mixin.scss │ │ ├── _navbar-progress.scss │ │ ├── _navbar.scss │ │ ├── _payment-success.scss │ │ ├── _payment.scss │ │ ├── _placeholders.scss │ │ ├── _product.scss │ │ ├── _register.scss │ │ └── main.scss ├── mix-manifest.json ├── nest-cli.json ├── package-lock.json ├── package.json ├── public │ ├── css │ │ └── main.css │ ├── img │ │ ├── 404.png │ │ ├── Adidas-Y-3-KAIWA.png │ │ ├── BANNER.png │ │ ├── HTB1biCEb25TBuNjSspmq6yDRVXau.png │ │ ├── Imagem_3.png │ │ ├── Imagem_4.png │ │ ├── Nike-Air270-black.png │ │ ├── NikeAirZoomZeroPremium.png │ │ ├── Puma_Clyde_Court_Men.png │ │ ├── Puma_HOT_WHEELS_RS-X.png │ │ ├── ZEUSLAP.png │ │ ├── adidas-y-3-2.png │ │ ├── adidasFALCON.png │ │ ├── apple-watch.png │ │ ├── beats-headphones.png │ │ ├── bike2.png │ │ ├── card.png │ │ ├── favicon.ico │ │ ├── homepod.png │ │ ├── ipad-pro.png │ │ ├── iphone-x.png │ │ ├── jbl-speaker.png │ │ ├── jlab-audio-wireless.png │ │ ├── logo-nav.png │ │ ├── macbook-pro.png │ │ ├── menu.png │ │ ├── search.png │ │ ├── shopping-cart.png │ │ ├── slip.png │ │ ├── sort-down.png │ │ ├── symbol_22_____3.png │ │ └── symbol_total.png │ └── js │ │ ├── checkout.js │ │ └── main.js ├── src │ ├── app.module.ts │ ├── app.service.ts │ ├── commands │ │ ├── commands.module.ts │ │ └── fixtures │ │ │ ├── fixtures.service.spec.ts │ │ │ ├── fixtures.service.ts │ │ │ └── json │ │ │ ├── category.fixture.ts │ │ │ ├── index.ts │ │ │ ├── payment-method-config.ts │ │ │ ├── payment-method.ts │ │ │ ├── product.fixture.ts │ │ │ └── tenant.fixture.ts │ ├── console.ts │ ├── controllers │ │ ├── cart │ │ │ ├── cart.controller.spec.ts │ │ │ └── cart.controller.ts │ │ ├── category │ │ │ ├── category.controller.spec.ts │ │ │ └── category.controller.ts │ │ ├── checkout │ │ │ ├── checkout.controller.spec.ts │ │ │ └── checkout.controller.ts │ │ ├── home │ │ │ ├── home.controller.spec.ts │ │ │ └── home.controller.ts │ │ ├── index.ts │ │ ├── product │ │ │ ├── product.controller.spec.ts │ │ │ └── product.controller.ts │ │ └── search │ │ │ ├── search.controller.spec.ts │ │ │ └── search.controller.ts │ ├── decorators │ │ ├── cart.decorator.ts │ │ ├── categories-default.decorator.ts │ │ ├── main-dependencies.decorator.ts │ │ ├── order-product.decorator.ts │ │ ├── pagination.decorator.ts │ │ ├── payment-method-config-greater-installments.decorator.ts │ │ └── tenant.decorator.ts │ ├── elasticsearch │ │ ├── elasticsearch.module.ts │ │ └── es-data-source │ │ │ ├── es-data-source.service.spec.ts │ │ │ └── es-data-source.service.ts │ ├── interceptors │ │ ├── cart.interceptor.spec.ts │ │ ├── cart.interceptor.ts │ │ ├── categories-default.interceptor.spec.ts │ │ ├── categories-default.interceptor.ts │ │ ├── payment-method-config-greater-installments.interceptor.spec.ts │ │ ├── payment-method-config-greater-installments.interceptor.ts │ │ ├── tenant.interceptor.spec.ts │ │ └── tenant.interceptor.ts │ ├── main.ts │ ├── middlewares │ │ ├── tenant.middleware.spec.ts │ │ └── tenant.middleware.ts │ ├── models │ │ ├── base-tenant.model.ts │ │ ├── category.model.ts │ │ ├── payment-method-config.model.ts │ │ ├── payment-method.model.ts │ │ ├── product.model.ts │ │ └── tenant.model.ts │ ├── repositories │ │ ├── base.repository.ts │ │ ├── category │ │ │ ├── category.repository.spec.ts │ │ │ └── category.repository.ts │ │ ├── index.ts │ │ ├── payment-method-config │ │ │ ├── payment-method-config.repository.spec.ts │ │ │ └── payment-method-config.repository.ts │ │ ├── payment-method │ │ │ ├── payment-method.repository.spec.ts │ │ │ └── payment-method.repository.ts │ │ ├── product │ │ │ ├── product.repository.spec.ts │ │ │ └── product.repository.ts │ │ └── tenant │ │ │ ├── tenant.repository.spec.ts │ │ │ └── tenant.repository.ts │ ├── services │ │ ├── cart │ │ │ ├── cart.service.spec.ts │ │ │ └── cart.service.ts │ │ ├── index.ts │ │ ├── navbar │ │ │ ├── navbar.service.spec.ts │ │ │ └── navbar.service.ts │ │ └── tenant │ │ │ ├── tenant.service.spec.ts │ │ │ └── tenant.service.ts │ └── sync │ │ ├── base-model-sync.service.ts │ │ ├── category │ │ ├── category-sync.service.spec.ts │ │ └── category-sync.service.ts │ │ ├── index.ts │ │ ├── payment-method-config │ │ ├── payment-method-config-sync.service.spec.ts │ │ └── payment-method-config-sync.service.ts │ │ ├── payment-method │ │ ├── payment-method-sync.service.spec.ts │ │ └── payment-method-sync.service.ts │ │ ├── product │ │ ├── product-sync.service.spec.ts │ │ └── product-sync.service.ts │ │ └── tenant │ │ ├── tenant-sync.service.spec.ts │ │ └── tenant-sync.service.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── views │ ├── cart │ │ └── show.hbs │ ├── category │ │ └── show.hbs │ ├── checkout.hbs │ ├── checkout │ │ └── payment-success.hbs │ ├── home.hbs │ ├── layouts │ │ └── layout.hbs │ ├── partials │ │ ├── footer.hbs │ │ └── navbar.hbs │ └── product │ │ └── show.hbs └── webpack.mix.js ├── k8s ├── admin │ ├── admin.yaml │ └── redis.yaml ├── assinaturas │ ├── assinaturas.yaml │ └── sql.sql ├── loja │ ├── loja.yaml │ └── values.yaml └── rabbitmq │ └── rabbitmq.yaml ├── rabbitmq ├── .gitignore └── docker-compose.yml └── redis ├── .gitignore └── docker-compose.yml /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Descrição 6 | 7 | Maratona FullCycle 3.0 8 | 9 | Sistema SASS de lojas virtuais 10 | 11 | ## Rodar a aplicação 12 | 13 | #### Antes de começar 14 | 15 | A aplicação foi construída utilizando os conceitos de microsserviços e arquitetada com Docker. 16 | 17 | Para roda-la será necessário basicamente rodar o comando **docker-compose up**. 18 | 19 | Acesse cada microsserviço respectivamente e leia o README.md para ver mais detalhes de como rodar o microsserviço. 20 | 21 | * [Adminstração das lojas](https://github.com/codeedu/maratonafc3-repo-main/tree/master/commerce-admin) 22 | 23 | ### Para Windows 24 | 25 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=g4HKttouVxA](https://www.youtube.com/watch?v=g4HKttouVxA) 26 | 27 | Siga o guia rápido de instalação: [https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd](https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd) 28 | -------------------------------------------------------------------------------- /commerce-admin/.docker/app-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f ".env" ]; then 4 | cp .env.example .env 5 | fi 6 | 7 | pip install --cache-dir=/home/django/app/.docker/.pip -r requirements.txt 8 | python manage.py migrate 9 | python manage.py loaddata initial_data 10 | python manage.py loaddata fake_data 11 | python manage.py sync_tenant_permissions 12 | python manage.py runserver 0.0.0.0:8000 13 | -------------------------------------------------------------------------------- /commerce-admin/.docker/prod-app-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python manage.py migrate 4 | python manage.py sync_tenant_permissions 5 | gunicorn commerce_admin.wsgi:application --bind 0.0.0.0:8000 -------------------------------------------------------------------------------- /commerce-admin/.docker/worker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip install --cache-dir=/home/django/app/.docker/.pip -r requirements.txt 4 | cd /home/django/app 5 | dockerize -wait tcp://app:8000 -timeout 10s celery worker -B -l info -A commerce_admin.celery -s /tmp/celerybeat-schedule 6 | -------------------------------------------------------------------------------- /commerce-admin/.dockerignore: -------------------------------------------------------------------------------- 1 | .docker/.pip -------------------------------------------------------------------------------- /commerce-admin/.env.example: -------------------------------------------------------------------------------- 1 | DEBUG=on 2 | SECRET_KEY="2=3n$f!wd%a+^^9ulho24(lsy!5i%js%1)9e*sepa0!g9lur+n" 3 | DATABASE_URL="postgres://postgres:root@db:5432/commerce_admin" 4 | ALLOWED_HOSTS=code-commerce.test 5 | RABBITMQ_URI=amqp://admin:admin@rabbitmq:5672 6 | REDIS_URI=redis://redis:6379/1 7 | MICRO_SERVICE_PAYMENT_URL= 8 | MICRO_SERVICE_PAYMENT_API_KEY=abcde -------------------------------------------------------------------------------- /commerce-admin/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode/ 3 | .docker/.pip 4 | .env 5 | venv/ 6 | media/ 7 | 8 | __pycache__/ 9 | *.py[cod] 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | *.log 32 | *.pot 33 | *.pyc 34 | 35 | **/node_modules 36 | **/static -------------------------------------------------------------------------------- /commerce-admin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV DOCKERIZE_VERSION v0.6.1 4 | RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 5 | && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 6 | && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz 7 | 8 | RUN useradd -ms /bin/bash django 9 | 10 | USER django 11 | 12 | ENV PYTHONUNBUFFERED 1 13 | 14 | ENV PATH $PATH:/home/django/.local/bin 15 | 16 | WORKDIR /home/django/app 17 | 18 | -------------------------------------------------------------------------------- /commerce-admin/Dockerfile.app.prod: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN mkdir /code 6 | 7 | WORKDIR /code 8 | 9 | COPY requirements.txt /code/ 10 | 11 | RUN pip install -r requirements.txt 12 | 13 | COPY . /code/ 14 | 15 | ENTRYPOINT ["./.docker/prod-app-entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /commerce-admin/Dockerfile.celery.prod: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN mkdir /code 6 | 7 | WORKDIR /code 8 | 9 | COPY requirements.txt /code/ 10 | 11 | RUN pip install -r requirements.txt 12 | 13 | COPY . /code/ 14 | 15 | ENTRYPOINT ["celery", "worker", "-B", "-l", "info", "-A", "commerce_admin.celery", "-s", "/tmp/celerybeat-schedule"] 16 | -------------------------------------------------------------------------------- /commerce-admin/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Descrição 6 | 7 | Maratona FullCycle 3.0 8 | 9 | Microsserviço de administração da loja com Django + PostgreSQL + Redis 10 | 11 | ## Rodar a aplicação 12 | 13 | ### Para Windows 14 | 15 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=g4HKttouVxA](https://www.youtube.com/watch?v=g4HKttouVxA) 16 | 17 | Siga o guia rápido de instalação: [https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd](https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd) 18 | 19 | #### Crie os volumes 20 | 21 | ```bash 22 | docker volume create commerce-admin-pgdata 23 | ``` 24 | 25 | #### Altere seu /etc/hosts ou C:\Windows\System32\drivers\etc\hosts 26 | 27 | ``` 28 | 127.0.0.1 code-commerce.test 29 | 127.0.0.1 store1-admin.code-commerce.test store2-admin.code-commerce.test store3-admin.code-commerce.test store4-admin.code-commerce.test 30 | 127.0.0.1 store1-store.code-commerce.test store2-store.code-commerce.test store3-store.code-commerce.test 31 | 127.0.0.1 admin.code-commerce1.test admin.code-commerce2.test admin3.code-commerce3.test admin.code-commerce4.test 32 | 127.0.0.1 store.code-commerce1.test store.code-commerce2.test store.code-commerce3.test store.code-commerce4.test 33 | ``` 34 | 35 | 36 | #### Crie os containers com Docker 37 | 38 | ```bash 39 | cd .. && cd redis && docker-compose up -d 40 | cd .. && cd rabbitmq && docker-compose up -d 41 | $ docker-compose up 42 | ``` 43 | 44 | ##### Para Windows: Somente se acontecer erro No such file directory ao fazer docker-compose up 45 | ```bash 46 | $chmod +x ./.docker/app-entrypoint.sh 47 | $chmod +x ./.docker/worker-entrypoint.sh 48 | ``` 49 | 50 | #### Accesse no browser 51 | 52 | ``` 53 | http://code-commmerce.test:8000/admin ou http://admin.code-commerce1.test:8000/admin 54 | ``` 55 | 56 | ### Dicas 57 | 58 | * Verifique o arquivo **tenant/fixtures/fake_data.json** para saber quais endereços estão disponíveis 59 | * Verifique o arquivo **auth_app/fixtures/fake_data.json** para saber quais usuários estão disponiveis 60 | * O usuário **admin@user.com** é o dono do sistema 61 | 62 | 63 | -------------------------------------------------------------------------------- /commerce-admin/app/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'app.apps.AppConfig' -------------------------------------------------------------------------------- /commerce-admin/app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.options import ModelAdmin 2 | 3 | from my_admin.admin import admin_site 4 | 5 | 6 | # Register your models here. -------------------------------------------------------------------------------- /commerce-admin/app/admin_tenant.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import ModelAdmin 2 | from django.contrib import admin 3 | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelFilter, PolymorphicChildModelAdmin 4 | 5 | from app.forms import ProductPaymentMethodForm 6 | from app.models import Category, Product, ProductPaymentMethod, PaymentMethodConfig, PaymentGateway, PagarmeGateway, \ 7 | CustomerAddress, Customer, Checkout, CheckoutItem 8 | from tenant.admin_tenant import AdminTenantModelAdmin, admin_tenant_site 9 | 10 | 11 | @admin.register(Category, site=admin_tenant_site) 12 | class CategoryAdmin(ModelAdmin, AdminTenantModelAdmin): 13 | search_fields = ('name',) 14 | 15 | class ProductPaymentMethodInline(admin.TabularInline): 16 | model = ProductPaymentMethod 17 | extra = 1 18 | form = ProductPaymentMethodForm 19 | 20 | 21 | @admin.register(Product, site=admin_tenant_site) 22 | class ProductAdmin(ModelAdmin, AdminTenantModelAdmin): 23 | search_fields = ('name',) 24 | inlines = (ProductPaymentMethodInline,) 25 | 26 | @admin.register(PaymentMethodConfig, site=admin_tenant_site) 27 | class PaymentMethodConfig(ModelAdmin, AdminTenantModelAdmin): 28 | pass 29 | 30 | @admin.register(PaymentGateway, site=admin_tenant_site) 31 | class PaymentGatewayAdmin(PolymorphicParentModelAdmin): 32 | exclude = ['tenant'] 33 | base_model = PaymentGateway 34 | child_models = (PagarmeGateway,) 35 | list_filter = (PolymorphicChildModelFilter,) # This is optional. 36 | 37 | @admin.register(PagarmeGateway, site=admin_tenant_site) 38 | class PagarmeGatewayAdmin(PolymorphicChildModelAdmin): 39 | exclude = ['tenant'] 40 | base_model = PaymentGateway 41 | show_in_index = True 42 | 43 | class CustomerAddressInline(admin.StackedInline): 44 | model = CustomerAddress 45 | extra = 1 46 | 47 | @admin.register(Customer, site=admin_tenant_site) 48 | class CustomerAdmin(ModelAdmin): 49 | list_display = ('name', 'email') 50 | inlines = (CustomerAddressInline,) 51 | 52 | class CheckoutInline(admin.TabularInline): 53 | model = CheckoutItem 54 | 55 | @admin.register(Checkout, site=admin_tenant_site) 56 | class CheckoutAdmin(ModelAdmin): 57 | list_display = ('get_customer','status', 'get_payment_method', 'get_total') 58 | inlines = (CheckoutInline, ) 59 | 60 | def get_customer(self, obj): 61 | return obj.customer_address.customer.name 62 | 63 | get_customer.short_description = 'cliente' 64 | 65 | def get_payment_method(self, obj): 66 | return obj.payment_method.get_name_display() 67 | 68 | get_payment_method.short_description = 'método de pagamento' 69 | 70 | def get_total(self, obj): 71 | return obj.total 72 | 73 | get_total.short_description = 'total' -------------------------------------------------------------------------------- /commerce-admin/app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig as DjangoAppConfig 2 | 3 | 4 | class AppConfig(DjangoAppConfig): 5 | name = 'app' 6 | 7 | def ready(self): 8 | import app.receivers 9 | 10 | -------------------------------------------------------------------------------- /commerce-admin/app/fixtures/fake_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "app.paymentmethodconfig", 4 | "pk": "0744a1f3-508c-401e-abf0-f89ec7517fe4", 5 | "fields": { 6 | "payment_method_id": "2046b39c-e05a-4412-8264-73dd80217a11", 7 | "max_installments": 12, 8 | "discount_percentage": 0, 9 | "tenant_id": "1cc0182d-bdf9-435b-86c3-2a201100a134", 10 | "created_at": "2020-01-01T00:00:00.000Z", 11 | "updated_at": "2020-01-01T00:00:00.000Z" 12 | } 13 | }, 14 | { 15 | "model": "app.paymentmethodconfig", 16 | "pk": "3e8f9413-bca1-4a99-8083-d83ad2d14e25", 17 | "fields": { 18 | "payment_method_id": "22f7ee75-9586-418b-b989-174740a9831b", 19 | "max_installments": 0, 20 | "discount_percentage": 0, 21 | "tenant_id": "1cc0182d-bdf9-435b-86c3-2a201100a134", 22 | "created_at": "2020-01-01T00:01:00.000Z", 23 | "updated_at": "2020-01-01T00:01:00.000Z" 24 | } 25 | } 26 | ] -------------------------------------------------------------------------------- /commerce-admin/app/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from app.models import ProductPaymentMethod 4 | 5 | 6 | class ProductPaymentMethodForm(forms.ModelForm): 7 | 8 | def clean(self): 9 | self.validate_max_installments() 10 | self.validate_max_installments_discount() 11 | return self.cleaned_data 12 | 13 | def validate_max_installments(self): 14 | payment_method = self.cleaned_data.get('payment_method') 15 | max_installments = self.cleaned_data.get('max_installments') 16 | if payment_method and max_installments and not payment_method.allow_installments: 17 | self.add_error('max_installments', 'Este método de pgamento não suporta parcelamento') 18 | 19 | def validate_max_installments_discount(self): 20 | max_installments = self.cleaned_data.get('max_installments') 21 | max_installments_discount = self.cleaned_data.get('max_installments_discount') 22 | if max_installments \ 23 | and max_installments_discount \ 24 | and max_installments_discount > max_installments: 25 | self.add_error('max_installments_discount', 'O desconto em parcela não pode ser maior que o parcelamento') 26 | 27 | class Meta: 28 | model = ProductPaymentMethod 29 | fields = '__all__' -------------------------------------------------------------------------------- /commerce-admin/app/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/app/management/__init__.py -------------------------------------------------------------------------------- /commerce-admin/app/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/app/management/commands/__init__.py -------------------------------------------------------------------------------- /commerce-admin/app/management/commands/admin-site.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sites.models import _simple_domain_name_validator, Site 2 | from django.core.management.base import BaseCommand 3 | 4 | 5 | class Command(BaseCommand): 6 | 7 | def handle(self, **options): 8 | domain = input('Type the default site: ') 9 | _simple_domain_name_validator(domain) 10 | try: 11 | Site.objects.get_by_natural_key(domain) 12 | print('The domain must be unique') 13 | except Site.DoesNotExist: 14 | site = Site.objects.first() 15 | site.domain = domain 16 | site.save() 17 | print('The default saved') 18 | 19 | handle.short_description = u"Set the default site" 20 | -------------------------------------------------------------------------------- /commerce-admin/app/middlewares.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest 2 | from django.utils.deprecation import MiddlewareMixin 3 | 4 | from app.models import PaymentMethodConfig, PaymentGateway 5 | from tenant.utils import get_tenant 6 | from django.contrib import messages 7 | 8 | class CheckPaymentMethodConfigMiddleware(MiddlewareMixin): 9 | 10 | def process_request(self, request: HttpRequest): 11 | tenant = get_tenant() 12 | if tenant and not PaymentMethodConfig.objects.count(): 13 | add_once_message(request, messages.WARNING, 'Defina a configuração de métodos de pagamento.') 14 | 15 | class CheckPaymentGatewayDefaultMiddleware(MiddlewareMixin): 16 | 17 | def process_request(self, request: HttpRequest): 18 | tenant = get_tenant() 19 | if tenant and not PaymentGateway.objects.count(): 20 | add_once_message(request, messages.WARNING, 'Defina um gateway de pagamento padrão') 21 | 22 | def add_once_message(request, level, msg): 23 | if msg not in [m.message for m in messages.get_messages(request)]: 24 | messages.add_message(request,level, msg) 25 | -------------------------------------------------------------------------------- /commerce-admin/app/migrations/0002_auto_20200702_1404.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-02 17:04 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('tenant', '0001_initial'), 11 | ('my_admin', '0002_auto_20200629_1359'), 12 | ('app', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='checkout', 18 | options={'verbose_name': 'venda'}, 19 | ), 20 | migrations.AlterModelOptions( 21 | name='checkoutitem', 22 | options={'verbose_name': 'item', 'verbose_name_plural': 'itens'}, 23 | ), 24 | migrations.AlterField( 25 | model_name='checkout', 26 | name='bank_slip_url', 27 | field=models.URLField(null=True, verbose_name='url do boleto'), 28 | ), 29 | migrations.AlterField( 30 | model_name='checkout', 31 | name='customer_address', 32 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='app.CustomerAddress', verbose_name='cliente'), 33 | ), 34 | migrations.AlterField( 35 | model_name='checkout', 36 | name='payment_method', 37 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='my_admin.PaymentMethod', verbose_name='método de pagamento'), 38 | ), 39 | migrations.AlterField( 40 | model_name='checkoutitem', 41 | name='product', 42 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='app.Product', verbose_name='produto'), 43 | ), 44 | migrations.AlterField( 45 | model_name='checkoutitem', 46 | name='quantity', 47 | field=models.PositiveSmallIntegerField(verbose_name='quantidade'), 48 | ), 49 | migrations.AlterField( 50 | model_name='paymentgateway', 51 | name='tenant', 52 | field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='paymentgateway_tenants', to='tenant.Tenant', verbose_name='tenant'), 53 | ), 54 | ] 55 | -------------------------------------------------------------------------------- /commerce-admin/app/migrations/0003_checkout_remote_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-02 18:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('app', '0002_auto_20200702_1404'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='checkout', 15 | name='remote_id', 16 | field=models.CharField(default=None, help_text='id remoto da fatura no gateway de pagamento', max_length=255, null=True, verbose_name='id remoto da fatura'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /commerce-admin/app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/app/migrations/__init__.py -------------------------------------------------------------------------------- /commerce-admin/app/receivers.py: -------------------------------------------------------------------------------- 1 | from app.models import Category, Product, ProductPaymentMethod, PaymentMethodConfig 2 | from app.serializers import CategorySerializer, ProductSerializer, \ 3 | ProductPaymentMethodSerializer, PaymentMethodConfigSerializer 4 | from common.sync_models import PublisherModelObserver 5 | 6 | category_observer = PublisherModelObserver(Category, CategorySerializer) 7 | category_observer.register() 8 | 9 | product_observer = PublisherModelObserver(Product, ProductSerializer) 10 | product_observer.register() 11 | 12 | #product_payment_method_observer = PublisherModelObserver(ProductPaymentMethod, ProductPaymentMethodSerializer) 13 | #product_payment_method_observer.register() 14 | 15 | 16 | payment_method_config_observer = PublisherModelObserver(PaymentMethodConfig, PaymentMethodConfigSerializer) 17 | payment_method_config_observer.register() 18 | 19 | -------------------------------------------------------------------------------- /commerce-admin/app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /commerce-admin/app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | 4 | from app.api import CustomerViewSet, PaymentGatewayViewSet, CheckoutViewSet 5 | 6 | router = routers.DefaultRouter(trailing_slash='/?') 7 | router.register('customer', CustomerViewSet, basename='customer') 8 | router.register('payment_gateway', PaymentGatewayViewSet, basename='payment_gateway') 9 | router.register('checkout', CheckoutViewSet, basename='checkout') 10 | 11 | app_name = 'app' 12 | 13 | urlpatterns = [ 14 | path('api/', include(router.urls)), 15 | ] 16 | -------------------------------------------------------------------------------- /commerce-admin/app/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'auth_app.apps.AuthAppConfig' -------------------------------------------------------------------------------- /commerce-admin/auth_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin 2 | 3 | from my_admin.admin import admin_site 4 | from auth_app.models import User, Group 5 | 6 | 7 | class UserAdmin(DjangoUserAdmin): 8 | pass 9 | 10 | 11 | admin_site.register(Group) 12 | admin_site.register(User, UserAdmin) 13 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/admin_tenant.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import StackedInline, ModelAdmin 2 | from django.contrib import admin 3 | from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin 4 | from django.contrib.auth.models import Permission 5 | 6 | from auth_app.models import Member, UserTenant, GroupTenant 7 | from auth_app.permissions import DefaultPermission 8 | from tenant.admin_tenant import admin_tenant_site, AdminTenantModelAdmin 9 | from django.utils.translation import gettext_lazy as _ 10 | 11 | class UserCommonAdmin(DjangoUserAdmin): 12 | exclude = ('last_login', 'groups', 'is_superuser', 'user_permissions', 'role', 'is_staff') 13 | fieldsets = ( 14 | (None, {'fields': ('username', 'password')}), 15 | (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), 16 | (_('Permissions'), { 17 | 'fields': ('is_active',), 18 | }), 19 | ) 20 | add_fieldsets = ( 21 | (None, { 22 | 'classes': ('wide',), 23 | 'fields': ['username', 'password1', 'password2'], 24 | }), 25 | (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), 26 | ) 27 | list_filter = ('is_active', 'groups') 28 | list_display = ('username', 'email', 'first_name', 'last_name') 29 | search_fields = ( 30 | 'name', 31 | 'email', 32 | 'first_name', 33 | 'last_name', 34 | ) 35 | 36 | class MemberStackedAdmin(StackedInline, AdminTenantModelAdmin): 37 | model = Member 38 | max_num = 1 39 | min_num = 1 40 | verbose_name = _('Member') 41 | verbose_name_plural = _('Members') 42 | can_delete = False 43 | 44 | def get_formset(self, request, obj=None, **kwargs): 45 | formset = super().get_formset(request, obj, **kwargs) 46 | formset.validate_min = True 47 | 48 | form = formset.form 49 | allowed_permissions = self.__get_allowed_permissions() 50 | permissions = Permission.objects.filter(codename__in=allowed_permissions) 51 | form.base_fields['permissions'].queryset = permissions 52 | return formset 53 | 54 | def __get_allowed_permissions(self): 55 | permissions = DefaultPermission.unique_permissions() 56 | return permissions 57 | 58 | @admin.register(UserTenant, site=admin_tenant_site) 59 | class UserTenantAdmin(UserCommonAdmin): 60 | inlines = [MemberStackedAdmin, ] 61 | 62 | @admin.register(GroupTenant, site=admin_tenant_site) 63 | class GroupTenantAdmin(ModelAdmin, AdminTenantModelAdmin): 64 | pass -------------------------------------------------------------------------------- /commerce-admin/auth_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class AuthAppConfig(AppConfig): 6 | name = 'auth_app' 7 | verbose_name = _("Authentication and Authorization") 8 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/fixtures/fake_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "auth_app.user", 4 | "pk": "e5182812-d1b0-4585-99bf-6510497601ab", 5 | "fields": { 6 | "password": "pbkdf2_sha256$180000$YDGrT1pcKb4Y$ZXgiacltNEw55sWrPRlyQ5INNQx4zCzo3xiUw3qZ6Hs=", 7 | "last_login": null, 8 | "is_superuser": true, 9 | "username": "admin@user.com", 10 | "first_name": "", 11 | "last_name": "", 12 | "email": "admin@user.com", 13 | "is_staff": true, 14 | "is_active": true, 15 | "date_joined": "2020-01-01T00:00:00.000Z", 16 | "groups": [], 17 | "user_permissions": [] 18 | } 19 | }, 20 | { 21 | "model": "auth_app.user", 22 | "pk": "df8d851d-12e4-4020-a955-46294b8b4906", 23 | "fields": { 24 | "password": "pbkdf2_sha256$180000$YDGrT1pcKb4Y$ZXgiacltNEw55sWrPRlyQ5INNQx4zCzo3xiUw3qZ6Hs=", 25 | "last_login": null, 26 | "is_superuser": false, 27 | "username": "admin@code-commerce1.test", 28 | "first_name": "", 29 | "last_name": "", 30 | "email": "admin@code-commerce1.test", 31 | "is_staff": false, 32 | "is_active": true, 33 | "date_joined": "2020-01-01T00:00:01.000Z", 34 | "groups": [], 35 | "user_permissions": [] 36 | } 37 | }, 38 | { 39 | "model": "auth_app.user", 40 | "pk": "62be0292-9c83-4a37-a753-1cf7b46156a1", 41 | "fields": { 42 | "password": "pbkdf2_sha256$180000$YDGrT1pcKb4Y$ZXgiacltNEw55sWrPRlyQ5INNQx4zCzo3xiUw3qZ6Hs=", 43 | "last_login": null, 44 | "is_superuser": false, 45 | "username": "admin@code-commerce2.test", 46 | "first_name": "", 47 | "last_name": "", 48 | "email": "admin@code-commerce2.test", 49 | "is_staff": false, 50 | "is_active": true, 51 | "date_joined": "2020-01-01T00:00:02.000Z", 52 | "groups": [], 53 | "user_permissions": [] 54 | } 55 | }, 56 | { 57 | "model": "auth_app.member", 58 | "pk": "822014dc-5040-4871-9675-1e1ac9253b04", 59 | "fields": { 60 | "user_id": "df8d851d-12e4-4020-a955-46294b8b4906", 61 | "tenant_id": "1cc0182d-bdf9-435b-86c3-2a201100a134", 62 | "groups": [ 63 | 1 64 | ], 65 | "group_tenants": [], 66 | "permissions": [], 67 | "created_at": "2020-01-01T00:00:00.000Z", 68 | "updated_at": "2020-01-01T00:00:00.000Z" 69 | } 70 | }, 71 | { 72 | "model": "auth_app.member", 73 | "pk": "9074d349-ef17-45b2-93c7-9e97ecf6d529", 74 | "fields": { 75 | "user_id": "62be0292-9c83-4a37-a753-1cf7b46156a1", 76 | "tenant_id": "5404ef25-b6a1-43ec-977b-cce1509f2c8b", 77 | "groups": [ 78 | 1 79 | ], 80 | "group_tenants": [], 81 | "permissions": [], 82 | "created_at": "2020-01-01T00:00:01.000Z", 83 | "updated_at": "2020-01-01T00:00:01.000Z" 84 | } 85 | } 86 | ] -------------------------------------------------------------------------------- /commerce-admin/auth_app/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "auth.Group", 4 | "pk": 1, 5 | "fields": { 6 | "name": "Administrador" 7 | } 8 | }, 9 | { 10 | "model": "auth.Group", 11 | "pk": 2, 12 | "fields": { 13 | "name": "Membro" 14 | } 15 | } 16 | ] -------------------------------------------------------------------------------- /commerce-admin/auth_app/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/auth_app/management/__init__.py -------------------------------------------------------------------------------- /commerce-admin/auth_app/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/auth_app/management/commands/__init__.py -------------------------------------------------------------------------------- /commerce-admin/auth_app/management/commands/sync_tenant_permissions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import Group, Permission 2 | from django.core.management.base import BaseCommand 3 | from django.db import transaction 4 | 5 | from auth_app.permissions import DefaultPermission 6 | 7 | 8 | class Command(BaseCommand): 9 | 10 | def handle(self, **options): 11 | group_permissions = DefaultPermission.convert_in_codename_permissions() 12 | with transaction.atomic(): 13 | for group_shared, permissions in group_permissions.items(): 14 | group = Group.objects.get(name=group_shared) 15 | permissions_add = group.permissions.filter(codename__in=permissions).values_list('codename', flat=True) 16 | permissions_not_add = list(set(permissions) - set(permissions_add)) 17 | if not len(permissions_not_add): 18 | continue 19 | permissions_not_add = Permission.objects.filter(codename__in=permissions_not_add).values_list('id', 20 | flat=True) 21 | group.permissions.add(*permissions_not_add) 22 | 23 | handle.short_description = u"Sync permissions with tenant groups" 24 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import UserManager, GroupManager as DjangoGroupManager 2 | 3 | 4 | class UserTenantManager(UserManager): 5 | def get_queryset(self): 6 | from tenant.utils import get_tenant 7 | current_tenant = get_tenant() 8 | return super().get_queryset().filter(user_member__tenant=current_tenant) 9 | 10 | def create_tenant_admin(self, username, email=None, password=None, **extra_fields): 11 | from tenant.utils import get_tenant 12 | user = super().create_user(username, email, password, **extra_fields) 13 | current_tenant = get_tenant() 14 | if not current_tenant: 15 | raise Exception('Tenant must be defined') 16 | from auth_app.models import Member 17 | member = Member.objects.create( 18 | user=user 19 | ) 20 | from auth_app.models import Group 21 | member.groups.add(Group.objects.get_tenant_admin()) 22 | return user 23 | 24 | 25 | class GroupManager(DjangoGroupManager): 26 | 27 | def get_tenant_admin(self): 28 | return self.get(pk=1) 29 | 30 | def get_tenant_member(self): 31 | return self.get(pk=2) 32 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/auth_app/migrations/__init__.py -------------------------------------------------------------------------------- /commerce-admin/auth_app/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from django.db import models 3 | from django.contrib.auth.models import AbstractUser, Permission as DjangoPermission, Group as DjangoGroup 4 | from common.models import AutoCreatedUpdatedMixin 5 | from auth_app.managers import UserTenantManager, GroupManager 6 | from tenant.models import TenantModel 7 | from django.utils.translation import gettext_lazy as _ 8 | 9 | 10 | class PermissionTenantQuerySet(models.QuerySet): 11 | 12 | def only_tenant(self): 13 | return self.filter(app_label='app') 14 | 15 | 16 | class Group(DjangoGroup): 17 | 18 | objects = GroupManager() 19 | 20 | class Meta: 21 | proxy = True 22 | app_label = 'auth_app' 23 | verbose_name = 'grupo' 24 | 25 | 26 | class GroupTenant(AutoCreatedUpdatedMixin, TenantModel): 27 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 28 | name = models.CharField(max_length=150, unique=True, verbose_name=_('name')) 29 | permissions = models.ManyToManyField(DjangoPermission, related_name='group_tenants', verbose_name=_('permissions')) 30 | 31 | def __str__(self): 32 | return self.name 33 | 34 | class Meta: 35 | verbose_name = 'grupo personalizado' 36 | 37 | 38 | class User(AbstractUser): 39 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 40 | 41 | class Meta: 42 | verbose_name = 'usuário admin' 43 | 44 | class UserTenant(User): 45 | objects = UserTenantManager() 46 | 47 | class Meta: 48 | proxy = True 49 | verbose_name = 'usuário' 50 | 51 | class Member(AutoCreatedUpdatedMixin, TenantModel): 52 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 53 | user = models.OneToOneField( 54 | UserTenant, 55 | on_delete=models.PROTECT, 56 | related_name='user_member', 57 | ) 58 | groups = models.ManyToManyField( 59 | DjangoGroup, 60 | related_name='group_members', 61 | blank=True, 62 | ) 63 | group_tenants = models.ManyToManyField( 64 | GroupTenant, 65 | related_name='group_tenant_members', 66 | blank=True 67 | ) 68 | permissions = models.ManyToManyField( 69 | DjangoPermission, 70 | related_name='permission_members', 71 | blank=True 72 | ) 73 | 74 | class Meta: 75 | verbose_name = 'membro' 76 | 77 | def __str__(self): 78 | return self.user.get_username() 79 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/tests.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /commerce-admin/auth_app/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /commerce-admin/commerce_admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ('celery_app',) -------------------------------------------------------------------------------- /commerce-admin/commerce_admin/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for commerce_admin project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'commerce_admin.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /commerce-admin/commerce_admin/urls.py: -------------------------------------------------------------------------------- 1 | """commerce_admin URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls.static import static 18 | from django.urls import path, include 19 | 20 | from my_admin.admin import admin_site 21 | from tenant.admin_tenant import admin_tenant_site 22 | 23 | urlpatterns = [ 24 | path('admin/', admin_site.urls), 25 | path('tenant/', admin_tenant_site.urls), 26 | path('', include('app.urls')), 27 | ] 28 | 29 | if settings.DEBUG: 30 | urlpatterns = urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /commerce-admin/commerce_admin/utils.py: -------------------------------------------------------------------------------- 1 | from django.core.cache import cache 2 | 3 | class AllowedHosts(object): 4 | 5 | def __init__(self, defaults=None, cache=True): 6 | self.defaults = defaults or () 7 | self.cache = cache 8 | 9 | def get_sites(self): 10 | sites = cache.get('sites', None) 11 | if self.cache is True and sites is not None: 12 | return sites + self.defaults 13 | 14 | from tenant.models import Tenant 15 | tenants = Tenant.objects.all() 16 | sites = tuple('.' + tenant.site for tenant in tenants) 17 | cache.set('sites,', sites) 18 | 19 | return sites + self.defaults 20 | 21 | def clear(self): 22 | cache.delete('sites') 23 | 24 | def __iter__(self): 25 | return iter(self.get_sites()) 26 | 27 | def __str__(self): 28 | return ', '.join(self.get_sites()) 29 | 30 | def __contains__(self, other): 31 | return other in self.get_sites() 32 | 33 | def __len__(self): 34 | return len(self.get_sites()) 35 | 36 | def __add__(self, other): 37 | return self.__class__(defaults=self.defaults + other.defaults) 38 | -------------------------------------------------------------------------------- /commerce-admin/commerce_admin/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for commerce_admin project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'commerce_admin.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /commerce-admin/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/common/__init__.py -------------------------------------------------------------------------------- /commerce-admin/common/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | 5 | 6 | class AutoCreatedUpdatedMixin(models.Model): 7 | created_at = models.DateTimeField(auto_now_add=True) 8 | updated_at = models.DateTimeField(auto_now=True) 9 | 10 | class Meta: 11 | abstract = True 12 | 13 | class BaseCustomer(AutoCreatedUpdatedMixin): 14 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 15 | name = models.CharField(max_length=255, verbose_name='nome') 16 | email = models.CharField(max_length=255, verbose_name='e-mail') 17 | personal_document = models.CharField(max_length=20, verbose_name='cpf') 18 | 19 | class Meta: 20 | abstract = True 21 | 22 | def __str__(self): 23 | return self.name 24 | 25 | 26 | class BaseAddress(AutoCreatedUpdatedMixin): 27 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 28 | zip_code = models.CharField(max_length=15, verbose_name='cep') 29 | street = models.CharField(max_length=255, verbose_name='endereço') 30 | street_number = models.CharField(max_length=10, verbose_name='número') 31 | street_2 = models.CharField(max_length=255, null=True, verbose_name='complemento') 32 | neighborhood = models.CharField(max_length=255, verbose_name='bairro') 33 | city = models.CharField(max_length=255, verbose_name='cidade') 34 | state = models.CharField(max_length=255, verbose_name='estado') 35 | ddd1 = models.CharField(max_length=2, verbose_name='ddd') 36 | phone1 = models.CharField(max_length=255, verbose_name='telefone') 37 | 38 | class Meta: 39 | abstract = True 40 | 41 | def __str__(self): 42 | return "%s%s%s" % (self.street, self.city, self.neighborhood) -------------------------------------------------------------------------------- /commerce-admin/common/sync_models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.contenttypes.models import ContentType 2 | from django.db.models.signals import post_save, post_delete, m2m_changed 3 | from django.dispatch import receiver 4 | 5 | from commerce_admin.celery import rabbitmq_producer 6 | 7 | 8 | class PublisherModelObserver: 9 | 10 | def __init__(self, sender, serializer=None) -> None: 11 | self.sender = sender 12 | self.serializer = serializer 13 | 14 | def register(self): 15 | receiver(post_save, sender=self.sender)(self.model_saved) 16 | receiver(post_delete, sender=self.sender)(self.model_deleted) 17 | 18 | def model_saved(self, instance, created, **kwargs): 19 | action = 'created' if created else 'updated' 20 | routing_key = self._get_routing_key(action) 21 | message = self.serializer(instance).data 22 | print(message, routing_key) 23 | self._publish(message, routing_key) 24 | 25 | def model_deleted(self, instance, **kwargs): 26 | routing_key = self._get_routing_key('deleted') 27 | message = {'id': instance.id} 28 | self._publish(message, routing_key) 29 | 30 | def register_many(self): 31 | m2m_changed.connect(self.relation_add,sender=self.sender) 32 | 33 | def relation_add(self, instance, action, pk_set, **kwargs): 34 | if action != "post_add": 35 | return 36 | routing_key = self._get_routing_key('add') 37 | message = { 38 | 'id': instance.id, 39 | 'relation_ids': list(pk_set) 40 | } 41 | self._publish(message, routing_key) 42 | 43 | def _get_routing_key(self, action): 44 | model_name = self._get_model_name() 45 | return 'model.%s.%s' % (model_name, action) 46 | 47 | def _get_model_name(self): 48 | return ContentType.objects.get_for_model(self.sender).model 49 | 50 | def _publish(self, message, routing_key): 51 | with rabbitmq_producer() as producer: 52 | producer.publish( 53 | body=message, 54 | routing_key=routing_key, 55 | exchange='amq.topic' 56 | ) -------------------------------------------------------------------------------- /commerce-admin/common/validators.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | from django.core.exceptions import ValidationError 4 | 5 | 6 | def simple_domain_name_validator(value): 7 | checks = ((s in value) for s in string.whitespace) 8 | if any(checks): 9 | raise ValidationError( 10 | "O domínio não pode ter espaços ou tabs.", 11 | code='invalid', 12 | ) -------------------------------------------------------------------------------- /commerce-admin/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | build: . 6 | container_name: commerce-admin-app 7 | entrypoint: ./.docker/app-entrypoint.sh 8 | volumes: 9 | - .:/home/django/app 10 | ports: 11 | - "8000:8000" 12 | depends_on: 13 | - db 14 | networks: 15 | - default 16 | - rabbitmq_code-commerce 17 | - redis_code-commerce 18 | 19 | worker: 20 | build: . 21 | container_name: commerce-admin-worker 22 | restart: on-failure 23 | entrypoint: ./.docker/worker-entrypoint.sh 24 | volumes: 25 | - .:/home/django/app 26 | depends_on: 27 | - app 28 | - db 29 | networks: 30 | - default 31 | - rabbitmq_code-commerce 32 | - redis_code-commerce 33 | 34 | db: 35 | image: postgres 36 | container_name: commerce-admin-db 37 | tty: true 38 | environment: 39 | - POSTGRES_DB=commerce_admin 40 | - POSTGRES_USER=postgres 41 | - POSTGRES_PASSWORD=root 42 | volumes: 43 | - commerce-admin-pgdata:/var/lib/postgresql/data 44 | ports: 45 | - "5433:5432" 46 | networks: 47 | - default 48 | 49 | pgadmin: 50 | image: dpage/pgadmin4 51 | container_name: commerce-admin-pgadmin 52 | tty: true 53 | environment: 54 | - PGADMIN_DEFAULT_EMAIL=admin@fullcycle.com.br 55 | - PGADMIN_DEFAULT_PASSWORD=123456 56 | ports: 57 | - "9000:80" 58 | networks: 59 | - default 60 | 61 | volumes: 62 | commerce-admin-pgdata: 63 | external: true 64 | 65 | networks: 66 | default: 67 | driver: bridge 68 | rabbitmq_code-commerce: 69 | external: true 70 | redis_code-commerce: 71 | external: true 72 | -------------------------------------------------------------------------------- /commerce-admin/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/fixtures/__init__.py -------------------------------------------------------------------------------- /commerce-admin/fixtures/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FixturesConfig(AppConfig): 5 | name = 'fixtures' 6 | -------------------------------------------------------------------------------- /commerce-admin/fixtures/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/fixtures/management/__init__.py -------------------------------------------------------------------------------- /commerce-admin/fixtures/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/fixtures/management/commands/__init__.py -------------------------------------------------------------------------------- /commerce-admin/fixtures/management/commands/db-reset.py: -------------------------------------------------------------------------------- 1 | from django.db import connection 2 | from django.core.management.base import BaseCommand 3 | from django.conf import settings 4 | 5 | 6 | class Command(BaseCommand): 7 | 8 | def handle(self, **options): 9 | self.drop_tables() 10 | 11 | handle.short_description = u"Database reset" 12 | 13 | def drop_tables(self): 14 | cursor = connection.cursor() 15 | tables = connection.introspection.table_names() 16 | connection_driver = settings.DATABASES['default']['ENGINE'] 17 | end_command = None 18 | if connection_driver == 'django.db.backends.sqlite3': 19 | cursor.execute('PRAGMA foreign_keys = OFF;') 20 | drop_table_command = 'DROP TABLE IF EXISTS %s;' 21 | end_command = 'PRAGMA foreign_keys = ON;' 22 | elif connection_driver == 'django.db.backends.postgresql': 23 | drop_table_command = 'DROP TABLE IF EXISTS %s CASCADE;' 24 | for table in tables: 25 | cursor.execute(drop_table_command % table) 26 | 27 | if end_command: 28 | cursor.execute(end_command) 29 | -------------------------------------------------------------------------------- /commerce-admin/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'commerce_admin.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/__init__.py: -------------------------------------------------------------------------------- 1 | from django.utils.module_loading import autodiscover_modules 2 | 3 | 4 | def autodiscover(): 5 | autodiscover_modules('admin') 6 | 7 | 8 | default_app_config = 'my_admin.apps.MyAdminConfig' 9 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.admin import AdminSite as DjangoAdminSite 3 | from django.contrib.admin.options import ModelAdmin 4 | from django.contrib.auth.forms import AuthenticationForm 5 | from django.shortcuts import redirect 6 | from django.urls import reverse 7 | 8 | from my_admin.models import PaymentMethod, Plan, TenantProxy, Subscription, Checkout, Customer, CustomerAddress 9 | from tenant.models import Tenant 10 | from tenant.utils import get_tenant 11 | 12 | 13 | class AdminSite(DjangoAdminSite): 14 | site_header = "CodeCommerce" 15 | site_title = "CodeCommerce Admin" 16 | index_title = "Bem-vindo ao CodeCommerce Admin" 17 | login_form = AuthenticationForm 18 | 19 | def login(self, request, extra_context=None): 20 | tenant = get_tenant() 21 | if tenant and tenant.is_admin is False: 22 | return redirect(reverse('admin_tenant:login')) 23 | return super().login(request, extra_context) 24 | 25 | 26 | admin_site = AdminSite(name='admin_site') 27 | 28 | @admin.register(TenantProxy, site=admin_site) 29 | class TenantAdmin(ModelAdmin): 30 | list_display = ('company', 'site') 31 | 32 | @admin.register(PaymentMethod, site=admin_site) 33 | class PaymentMethodAdmin(ModelAdmin): 34 | list_display = ('name', 'allow_installments') 35 | 36 | @admin.register(Plan, site=admin_site) 37 | class PlanAdmin(ModelAdmin): 38 | list_display = ('name', ) 39 | 40 | class CustomerAddressInline(admin.StackedInline): 41 | model = CustomerAddress 42 | extra = 1 43 | 44 | @admin.register(Customer, site=admin_site) 45 | class CustomerAdmin(ModelAdmin): 46 | list_display = ('name', 'email') 47 | inlines = (CustomerAddressInline,) 48 | 49 | @admin.register(Subscription, site=admin_site) 50 | class SubscriptionAdmin(ModelAdmin): 51 | list_display = ('get_customer', ) 52 | 53 | def get_customer(self, obj): 54 | return obj.checkout.customer_address.customer.name 55 | 56 | get_customer.short_description = 'Cliente' 57 | 58 | class SubscriptionInline(admin.StackedInline): 59 | model = Subscription 60 | 61 | @admin.register(Checkout, site=admin_site) 62 | class CheckoutAdmin(ModelAdmin): 63 | model = Checkout 64 | inlines = (SubscriptionInline,) 65 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.contrib.admin.apps import AdminConfig 3 | 4 | 5 | class MyAdminConfig(AdminConfig): 6 | name = 'my_admin' 7 | default_site = 'admin.admin.AdminSite' 8 | 9 | def ready(self): 10 | import my_admin.receivers 11 | super().ready() 12 | 13 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.backends import ModelBackend 2 | 3 | class AdminBackend(ModelBackend): 4 | 5 | def user_can_authenticate(self, user): 6 | can_authenticate = super().user_can_authenticate(user) 7 | return True if can_authenticate and user.is_staff else False 8 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "my_admin.PaymentMethod", 4 | "pk": "2046b39c-e05a-4412-8264-73dd80217a11", 5 | "fields": { 6 | "name": "credit_card", 7 | "allow_installments": true, 8 | "created_at": "2020-01-01T00:00:00.000Z", 9 | "updated_at": "2020-01-01T00:00:00.000Z" 10 | } 11 | }, 12 | { 13 | "model": "my_admin.PaymentMethod", 14 | "pk": "22f7ee75-9586-418b-b989-174740a9831b", 15 | "fields": { 16 | "name": "bank_slip", 17 | "allow_installments": false, 18 | "created_at": "2020-01-01T00:01:00.000Z", 19 | "updated_at": "2020-01-01T00:01:00.000Z" 20 | } 21 | }, 22 | { 23 | "model": "my_admin.plan", 24 | "pk": "6ac00aa0-a9be-4b64-8bbd-f5bb094bc081", 25 | "fields": { 26 | "created_at": "2020-06-28T20:04:54.512Z", 27 | "updated_at": "2020-06-28T20:04:54.512Z", 28 | "name": "Semestral", 29 | "billing_cycle": "6", 30 | "price": "120.00", 31 | "remote_plan_id": "teste" 32 | } 33 | } 34 | ] 35 | 36 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/migrations/0002_auto_20200629_1359.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-29 16:59 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('my_admin', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='checkout', 16 | options={'verbose_name': 'fatura'}, 17 | ), 18 | migrations.RemoveField( 19 | model_name='checkout', 20 | name='subscription', 21 | ), 22 | migrations.AddField( 23 | model_name='subscription', 24 | name='checkout', 25 | field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.PROTECT, to='my_admin.Checkout'), 26 | preserve_default=False, 27 | ), 28 | migrations.AlterField( 29 | model_name='checkout', 30 | name='customer_address', 31 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='my_admin.CustomerAddress', verbose_name='Cliente'), 32 | ), 33 | migrations.AlterField( 34 | model_name='checkout', 35 | name='remote_plan_id', 36 | field=models.CharField(help_text='id remoto do plano no gateway de pagamento', max_length=255, verbose_name='id remoto do plano'), 37 | ), 38 | migrations.AlterField( 39 | model_name='subscription', 40 | name='expires_date', 41 | field=models.DateField(verbose_name='fim'), 42 | ), 43 | migrations.AlterField( 44 | model_name='subscription', 45 | name='remote_subscription_id', 46 | field=models.CharField(help_text='id da assinatura no gateway de pagamento', max_length=255, verbose_name='id remoto da assinatura'), 47 | ), 48 | migrations.AlterField( 49 | model_name='subscription', 50 | name='start_date', 51 | field=models.DateField(verbose_name='início'), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/my_admin/migrations/__init__.py -------------------------------------------------------------------------------- /commerce-admin/my_admin/receivers.py: -------------------------------------------------------------------------------- 1 | from my_admin.models import PaymentMethod 2 | from common.sync_models import PublisherModelObserver 3 | from my_admin.serializers import PaymentMethodSerializer 4 | from my_admin.models import TenantProxy 5 | from tenant.serializers import TenantSerializer 6 | 7 | 8 | payment_method_observer = PublisherModelObserver(PaymentMethod, PaymentMethodSerializer) 9 | payment_method_observer.register() 10 | 11 | tenant_observer = PublisherModelObserver(TenantProxy, TenantSerializer) 12 | tenant_observer.register() -------------------------------------------------------------------------------- /commerce-admin/my_admin/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from my_admin.models import PaymentMethod 4 | 5 | 6 | class PaymentMethodSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = PaymentMethod 9 | fields = '__all__' -------------------------------------------------------------------------------- /commerce-admin/my_admin/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /commerce-admin/my_admin/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /commerce-admin/requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==2.6.0 2 | asgiref==3.2.7 3 | billiard==3.6.3.0 4 | celery==4.4.3 5 | certifi==2020.6.20 6 | chardet==3.0.4 7 | Django==3.0.7 8 | django-cors-headers==3.4.0 9 | django-environ==0.4.5 10 | django-extensions==2.2.9 11 | django-polymorphic==2.1.2 12 | django-redis==4.12.1 13 | django-rest-polymorphic==0.1.9 14 | djangorestframework==3.11.0 15 | gunicorn==20.0.4 16 | idna==2.9 17 | importlib-metadata==1.6.0 18 | kombu==4.6.9 19 | Pillow==7.1.2 20 | psycopg2==2.8.5 21 | pytz==2020.1 22 | redis==3.5.3 23 | requests==2.24.0 24 | requests-file==1.5.1 25 | six==1.15.0 26 | sqlparse==0.3.1 27 | tldextract==2.2.2 28 | urllib3==1.25.9 29 | vine==1.3.0 30 | whitenoise===5.1.0 31 | zipp==3.1.0 32 | -------------------------------------------------------------------------------- /commerce-admin/tenant/__init__.py: -------------------------------------------------------------------------------- 1 | from django.utils.module_loading import autodiscover_modules 2 | 3 | 4 | def autodiscover(): 5 | autodiscover_modules('admin_tenant') 6 | 7 | 8 | default_app_config = 'tenant.apps.TenantConfig' 9 | -------------------------------------------------------------------------------- /commerce-admin/tenant/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from my_admin.admin import admin_site 5 | from tenant.models import Tenant 6 | 7 | #admin_site.register(Tenant) -------------------------------------------------------------------------------- /commerce-admin/tenant/admin_tenant.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import AdminSite 2 | from django.contrib.admin.options import BaseModelAdmin 3 | from django.contrib.auth.forms import AuthenticationForm 4 | 5 | 6 | class AdminTenantModelAdmin(BaseModelAdmin): 7 | def get_fields(self, request, obj=None): 8 | fields = super().get_fields(request, obj) 9 | if 'tenant' in fields: 10 | fields.remove('tenant') 11 | return fields 12 | 13 | 14 | class AdminTenantSite(AdminSite): 15 | site_header = "CodeCommerce" 16 | site_title = "CodeCommerce Tenant" 17 | index_title = "Bem-vindo ao CodeCommerce Tenant" 18 | login_form = AuthenticationForm 19 | 20 | def has_permission(self, request): 21 | return request.user.is_active 22 | 23 | 24 | admin_tenant_site = AdminTenantSite(name='admin_tenant') 25 | -------------------------------------------------------------------------------- /commerce-admin/tenant/apps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.apps import AdminConfig 2 | 3 | 4 | class TenantConfig(AdminConfig): 5 | name = 'tenant' 6 | default_site = 'tenant.admin_tenant.admin_tenant_site' 7 | 8 | def ready(self): 9 | import tenant.receivers 10 | super().ready() 11 | -------------------------------------------------------------------------------- /commerce-admin/tenant/fixtures/fake_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "tenant.tenant", 4 | "pk": "1cc0182d-bdf9-435b-86c3-2a201100a134", 5 | "fields": { 6 | "company": "Store 1", 7 | "site": "code-commerce1.test", 8 | "is_admin": false, 9 | "fallback_subdomain": "store1", 10 | "created_at": "2020-01-01T00:00:00.000Z", 11 | "updated_at": "2020-01-01T00:00:00.000Z" 12 | } 13 | }, 14 | { 15 | "model": "tenant.tenant", 16 | "pk": "5404ef25-b6a1-43ec-977b-cce1509f2c8b", 17 | "fields": { 18 | "company": "Store 2", 19 | "site": "code-commerce2.test", 20 | "is_admin": false, 21 | "fallback_subdomain": "store2", 22 | "created_at": "2020-01-01T00:01:00.000Z", 23 | "updated_at": "2020-01-01T00:01:00.000Z" 24 | } 25 | }, 26 | { 27 | "model": "tenant.tenant", 28 | "pk": "e17e6950-2be4-42d6-b6a2-11b9cf5e4897", 29 | "fields": { 30 | "company": "Store 3", 31 | "site": "code-commerce3.test", 32 | "is_admin": false, 33 | "fallback_subdomain": "store3", 34 | "created_at": "2020-01-01T00:02:00.000Z", 35 | "updated_at": "2020-01-01T00:02:00.000Z" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /commerce-admin/tenant/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from tenant.utils import get_tenant, get_tenant_filters 3 | from polymorphic.managers import PolymorphicManager 4 | 5 | class TenantManager(models.Manager): 6 | def get_queryset(self): 7 | queryset = self._queryset_class(self.model) 8 | current_tenant = get_tenant() 9 | if current_tenant: 10 | kwargs = get_tenant_filters() 11 | return queryset.filter(**kwargs) 12 | return queryset 13 | 14 | class TenantPolymorphicManager(PolymorphicManager): 15 | def get_queryset(self): 16 | queryset = super().get_queryset() 17 | current_tenant = get_tenant() 18 | if current_tenant: 19 | kwargs = get_tenant_filters() 20 | return queryset.filter(**kwargs) 21 | return queryset -------------------------------------------------------------------------------- /commerce-admin/tenant/middlewares.py: -------------------------------------------------------------------------------- 1 | from django.db.models.query_utils import Q 2 | from django.http import HttpRequest 3 | from django.utils.deprecation import MiddlewareMixin 4 | 5 | from tenant.models import Tenant 6 | from tenant.utils import set_tenant 7 | import tldextract 8 | 9 | 10 | class TenantMiddleware(MiddlewareMixin): 11 | 12 | def process_request(self, request: HttpRequest): 13 | try: 14 | self._load_tenant(self._get_domain(request.get_host())) 15 | except: 16 | try: 17 | subdomain = request.get_host().split('.')[0].replace('-admin', '') 18 | self._load_tenant(subdomain) 19 | except: 20 | pass 21 | 22 | @staticmethod 23 | def _get_domain(hostname): 24 | extract = tldextract.extract(hostname) 25 | subdomain_list = extract.subdomain.split('.') 26 | subdomain = subdomain_list[len(subdomain_list) - 1] if len(subdomain_list) > 1 else "" 27 | subdomain = subdomain + "." if subdomain != "" else "" 28 | suffix = "." + extract.suffix if extract.suffix != "" else "" 29 | return "%s%s%s" % (subdomain, extract.domain, suffix) 30 | 31 | def _load_tenant(self, domain): 32 | tenant = Tenant.objects.get( 33 | Q(site=domain) | 34 | Q(fallback_subdomain__iexact=domain) 35 | ) 36 | set_tenant(tenant) 37 | -------------------------------------------------------------------------------- /commerce-admin/tenant/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-29 16:43 2 | 3 | from django.db import migrations, models 4 | 5 | import common.validators 6 | import tenant.models 7 | import uuid 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Tenant', 20 | fields=[ 21 | ('created_at', models.DateTimeField(auto_now_add=True)), 22 | ('updated_at', models.DateTimeField(auto_now=True)), 23 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 24 | ('company', models.CharField(max_length=255, verbose_name='empresa')), 25 | ('is_admin', models.BooleanField(default=False, verbose_name='administrador')), 26 | ('site', models.CharField(max_length=100, unique=True, validators=[ 27 | common.validators.simple_domain_name_validator], verbose_name='site')), 28 | ('fallback_subdomain', models.CharField(help_text='Este subdomínio serve para o cliente acessar quando não registrou o domínio principal', max_length=100, unique=True, validators=[ 29 | common.validators.simple_domain_name_validator], verbose_name='subdomínio')), 30 | ], 31 | options={ 32 | 'abstract': False, 33 | }, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /commerce-admin/tenant/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-admin/tenant/migrations/__init__.py -------------------------------------------------------------------------------- /commerce-admin/tenant/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from django.conf import settings 3 | from django.db import models 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | from common.models import AutoCreatedUpdatedMixin 7 | from common.validators import simple_domain_name_validator 8 | from tenant.managers import TenantManager, TenantPolymorphicManager 9 | from tenant.utils import get_tenant 10 | 11 | class Tenant(AutoCreatedUpdatedMixin): 12 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 13 | company = models.CharField(max_length=255, verbose_name='empresa') 14 | is_admin = models.BooleanField(default=False, verbose_name='administrador') 15 | site = models.CharField( 16 | 'site', 17 | max_length=100, 18 | validators=[simple_domain_name_validator], 19 | unique=True, 20 | ) 21 | fallback_subdomain = models.CharField( 22 | 'subdomínio', 23 | max_length=100, 24 | validators=[simple_domain_name_validator], 25 | unique=True, 26 | help_text='Este subdomínio serve para o cliente acessar quando não registrou o domínio principal' 27 | ) 28 | 29 | def __str__(self): 30 | 31 | return "%s | %s" % (self.company, self.site) 32 | 33 | def assign_tenant(sender, instance, **kwargs): 34 | if not getattr(instance, settings.TENANT_FIELD): 35 | tenant = get_tenant() 36 | if tenant: 37 | setattr(instance, settings.TENANT_FIELD, tenant.id) 38 | 39 | class BaseTenantModel(models.Model): 40 | tenant = models.ForeignKey( 41 | Tenant, 42 | null=True, 43 | editable=False, 44 | on_delete=models.PROTECT, 45 | related_name='%(class)s_tenants', 46 | verbose_name=_('tenant') 47 | ) 48 | 49 | def __init_subclass__(cls, **kwargs): 50 | super().__init_subclass__(**kwargs) 51 | models.signals.pre_save.connect(assign_tenant, sender=cls) 52 | 53 | class Meta: 54 | abstract = True 55 | 56 | class TenantModel(BaseTenantModel): 57 | objects = TenantManager() 58 | 59 | class Meta: 60 | abstract = True 61 | 62 | #meios de pagamentos 63 | class TenantPolymorphicModel(BaseTenantModel): 64 | objects = TenantPolymorphicManager() 65 | 66 | class Meta: 67 | abstract = True -------------------------------------------------------------------------------- /commerce-admin/tenant/receivers.py: -------------------------------------------------------------------------------- 1 | from common.sync_models import PublisherModelObserver 2 | from tenant.models import Tenant 3 | from tenant.serializers import TenantSerializer 4 | 5 | tenant_observer = PublisherModelObserver(Tenant, TenantSerializer) 6 | tenant_observer.register() -------------------------------------------------------------------------------- /commerce-admin/tenant/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from tenant.models import Tenant 3 | 4 | 5 | class TenantSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Tenant 8 | fields = '__all__' -------------------------------------------------------------------------------- /commerce-admin/tenant/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /commerce-admin/tenant/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from threading import local 3 | 4 | _thread_local = local() 5 | 6 | 7 | def get_tenant_filters(): 8 | filters = {} 9 | 10 | tenant_id = get_tenant().id if get_tenant() else None 11 | 12 | if not tenant_id: 13 | return filters 14 | 15 | filters[settings.TENANT_FIELD] = tenant_id 16 | 17 | return filters 18 | 19 | 20 | def set_tenant(current_tenant): 21 | from tenant.models import Tenant 22 | 23 | tenant = current_tenant \ 24 | if isinstance(current_tenant, Tenant) \ 25 | else Tenant.objects.get(site__domain=current_tenant) 26 | 27 | setattr(_thread_local, 'tenant', tenant) 28 | 29 | 30 | def get_tenant(): 31 | return getattr(_thread_local, 'tenant', None) 32 | -------------------------------------------------------------------------------- /commerce-admin/tenant/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /commerce-shop/.docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/node/app 4 | 5 | if [ ! -f ".env" ]; then 6 | cp .env.example .env 7 | fi 8 | 9 | npm install 10 | npm run start:dev 11 | 12 | 13 | -------------------------------------------------------------------------------- /commerce-shop/.env.example: -------------------------------------------------------------------------------- 1 | #DEBUG=elasticsearch,loopback:connector:elasticsearch,loopback:core:application 2 | APP_ENV=dev 3 | SESSION_SECRET_KEY=fullcycle.com.br 4 | ELASTIC_SEARCH_HOST=http://elasticsearch:9200 5 | ELASTIC_SEARCH_REQUEST_TIMEOUT=30000 6 | ELASTIC_SEARCH_PING_TIMEOUT=3000 7 | 8 | RABBITMQ_URI=amqp://admin:admin@rabbitmq:5672 9 | 10 | #Local colocar o endereço do Django 11 | MIX_ASSETS_URL=http://code-commerce.test:8001 12 | MIX_MAIN_DOMAIN=code-commerce.test 13 | MIX_DEV_ADMIN_PORT=8000 14 | 15 | 16 | REDIS_URI=redis://redis:6379/2 17 | -------------------------------------------------------------------------------- /commerce-shop/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | "@typescript-eslint/camelcase": 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /commerce-shop/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env 37 | .npm-cache 38 | .docker/elasticdata 39 | -------------------------------------------------------------------------------- /commerce-shop/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /commerce-shop/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14.0-alpine3.11 2 | 3 | RUN apk add --no-cache bash 4 | 5 | RUN touch /home/node/.bashrc | echo "PS1='\w\$ '" >> /home/node/.bashrc 6 | 7 | RUN npm config set cache /home/node/app/.npm-cache --global 8 | 9 | RUN npm i -g @nestjs/cli@7.0.0 10 | 11 | USER node 12 | 13 | WORKDIR /home/node/app 14 | -------------------------------------------------------------------------------- /commerce-shop/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:10-slim 2 | 3 | USER node 4 | 5 | RUN mkdir -p /home/node/app 6 | 7 | WORKDIR /home/node/app 8 | 9 | COPY --chown=node package*.json ./ 10 | 11 | RUN npm install 12 | 13 | COPY --chown=node ./ . 14 | 15 | RUN npm run build 16 | 17 | RUN npm run frontend-dev 18 | 19 | CMD ["npm", "run", "start:prod"] 20 | -------------------------------------------------------------------------------- /commerce-shop/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Descrição 6 | 7 | Maratona FullCycle 3.0 8 | 9 | Microsserviço da loja virtual com Nest.js + Loopback + ElasticSearch + React.js 10 | 11 | ## Rodar a aplicação 12 | 13 | ### Para Windows 14 | 15 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=g4HKttouVxA](https://www.youtube.com/watch?v=g4HKttouVxA) 16 | 17 | Siga o guia rápido de instalação: [https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd](https://gist.github.com/argentinaluiz/6bff167be40a2bf7a6bb879062cd25cd) 18 | 19 | #### Crie os volumes 20 | 21 | ```bash 22 | docker volume create code-commerce-elasticsearch-data 23 | ``` 24 | 25 | #### Altere seu /etc/hosts ou C:\Windows\System32\drivers\etc\hosts 26 | 27 | ``` 28 | 127.0.0.1 code-commerce.test 29 | 127.0.0.1 store1-admin.code-commerce.test store2-admin.code-commerce.test store3-admin.code-commerce.test store4-admin.code-commerce.test 30 | 127.0.0.1 store1-store.code-commerce.test store2-store.code-commerce.test store3-store.code-commerce.test 31 | 127.0.0.1 admin.code-commerce1.test admin.code-commerce2.test admin3.code-commerce3.test admin.code-commerce4.test 32 | 127.0.0.1 store.code-commerce1.test store.code-commerce2.test store.code-commerce3.test store.code-commerce4.test 33 | ``` 34 | 35 | 36 | #### Crie os containers com Docker 37 | 38 | ```bash 39 | cd .. && cd redis && docker-compose up -d 40 | cd .. && cd rabbitmq && docker-compose up -d 41 | $ docker-compose up 42 | ``` 43 | 44 | ##### Para Windows: Somente se acontecer erro No such file directory ao fazer docker-compose up 45 | ```bash 46 | $chmod +x ./.docker/entrypoint.sh 47 | ``` 48 | 49 | #### Accesse no browser 50 | 51 | ``` 52 | http://code-commmerce.test:8000/admin ou http://admin.code-commerce1.test:8000/admin 53 | ``` 54 | 55 | ### Dicas 56 | 57 | * Inicie este microsserviço antes de iniciar a administração com o Django, porque toda sincronização da administração vai para a loja 58 | * Rode o comando **npm run console fixtures** para criar dados falsos para testar a loja com os domínios padrões. 59 | * A loja pode ser acessada com store1-admin.code-commerce.test admin.code-commerce1.test 60 | 61 | 62 | -------------------------------------------------------------------------------- /commerce-shop/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: ./.docker/entrypoint.sh 8 | container_name: commerce-shop-app 9 | environment: 10 | - CHOKIDAR_USEPOLLING=true 11 | ports: 12 | - 3000:3000 13 | volumes: 14 | - .:/home/node/app 15 | networks: 16 | - default 17 | - rabbitmq_code-commerce 18 | - redis_code-commerce 19 | 20 | elasticsearch: 21 | image: docker.elastic.co/elasticsearch/elasticsearch:7.5.1 22 | container_name: commerce-shop-elasticsearch 23 | environment: 24 | - discovery.type=single-node 25 | volumes: 26 | - code-commerce-elasticsearch-data:/usr/share/elasticsearch/data 27 | ports: 28 | - 9200:9200 29 | networks: 30 | - default 31 | 32 | kibana: 33 | image: docker.elastic.co/kibana/kibana:7.5.1 34 | container_name: commerce-shop-kibana 35 | ports: 36 | - 5601:5601 37 | networks: 38 | - default 39 | 40 | volumes: 41 | code-commerce-elasticsearch-data: 42 | external: true 43 | 44 | networks: 45 | default: 46 | driver: bridge 47 | rabbitmq_code-commerce: 48 | external: true 49 | redis_code-commerce: 50 | external: true 51 | -------------------------------------------------------------------------------- /commerce-shop/frontend/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/404.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Adidas-Y-3-KAIWA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Adidas-Y-3-KAIWA.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/BANNER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/BANNER.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/HTB1biCEb25TBuNjSspmq6yDRVXau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/HTB1biCEb25TBuNjSspmq6yDRVXau.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Imagem_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Imagem_3.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Imagem_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Imagem_4.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Nike-Air270-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Nike-Air270-black.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/NikeAirZoomZeroPremium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/NikeAirZoomZeroPremium.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Puma_Clyde_Court_Men.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Puma_Clyde_Court_Men.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/Puma_HOT_WHEELS_RS-X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/Puma_HOT_WHEELS_RS-X.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/ZEUSLAP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/ZEUSLAP.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/adidas-y-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/adidas-y-3-2.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/adidasFALCON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/adidasFALCON.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/apple-watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/apple-watch.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/beats-headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/beats-headphones.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/bike2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/bike2.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/card.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/favicon.ico -------------------------------------------------------------------------------- /commerce-shop/frontend/img/homepod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/homepod.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/ipad-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/ipad-pro.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/iphone-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/iphone-x.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/jbl-speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/jbl-speaker.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/jlab-audio-wireless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/jlab-audio-wireless.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/logo-nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/logo-nav.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/macbook-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/macbook-pro.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/menu.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/search.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/shopping-cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/shopping-cart.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/slip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/slip.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/sort-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/sort-down.png -------------------------------------------------------------------------------- /commerce-shop/frontend/img/symbol_22_____3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/frontend/img/symbol_22_____3.png -------------------------------------------------------------------------------- /commerce-shop/frontend/js/checkout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {BrowserRouter, Route, Switch} from 'react-router-dom'; 4 | import CustomerProfile from "./component/CustomerProfile"; 5 | import Address from "./component/Address"; 6 | import Payment from "./component/Payment"; 7 | import Step from "./component/Step"; 8 | import {StepContextProvider} from "./component/Step/StepContextProvider"; 9 | // import PaymentSuccess from "./component/PaymentSuccess"; 10 | 11 | 12 | const App = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {/**/} 22 | 23 | 24 | 25 | ) 26 | } 27 | 28 | ReactDOM.render(, document.getElementById('root')) 29 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/component/PaymentSuccess.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {httpShop} from "../http"; 3 | 4 | 5 | const PaymentSuccess = () => { 6 | const [cart, setCart] = React.useState(null); 7 | React.useEffect(() => { 8 | let isSubscribed = true; 9 | httpShop.get('cart/_json') 10 | .then((response) => { 11 | if (isSubscribed) { 12 | setCart(response.data) 13 | } 14 | }) 15 | return () => { 16 | isSubscribed = false; 17 | } 18 | }, []); 19 | 20 | return ( 21 | cart &&
22 |
23 |
24 |
25 |
26 |

Compra finalizada com sucesso

27 |

Resumo do pedido

28 | { 29 | cart.items.map(item => ( 30 |
31 |
32 | 34 |
35 |
36 |

{item.product.name}

37 | Código do produto: {item.product.id.substring(0, 7)} 38 | Quant.: {item.quantity} 39 |
40 |

R$ {item.product.toFixed(2)}

41 |
42 | )) 43 | } 44 |
45 |
46 |
47 |
48 | 49 |
50 | ); 51 | }; 52 | 53 | 54 | export default PaymentSuccess; 55 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/component/Step/StepContext.js: -------------------------------------------------------------------------------- 1 | import {createContext} from "react"; 2 | 3 | const StepContext = createContext({ 4 | value: 1, 5 | setValue: () => { 6 | 7 | } 8 | }); 9 | 10 | export default StepContext; 11 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/component/Step/StepContextProvider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import StepContext from "./StepContext"; 4 | 5 | 6 | export const StepContextProvider = (props) => { 7 | const [step, setStep] = React.useState(1); 8 | return ( 9 | 10 | {props.children} 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/component/Step/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import {Link} from "react-router-dom"; 4 | import StepContext from "./StepContext"; 5 | 6 | const Step = () => { 7 | const stepContext = React.useContext(StepContext); 8 | 9 | return ( 10 | 37 | ); 38 | }; 39 | 40 | export default Step; 41 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/http/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {getAdminUrl} from "../util/url"; 3 | 4 | export const httpShop = axios.create({ 5 | baseURL: '/' 6 | }); 7 | 8 | 9 | const ADMIN_URL = getAdminUrl(); 10 | 11 | export const httpAdmin = axios.create({ 12 | baseURL: `${window.location.protocol}//${ADMIN_URL}/api` 13 | }); 14 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/main.js: -------------------------------------------------------------------------------- 1 | window.$ = window.jQuery = require('jquery'); 2 | require('bootstrap'); 3 | 4 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/util/url.js: -------------------------------------------------------------------------------- 1 | import {parse} from "psl"; 2 | 3 | const getCurrentDomain = () => { 4 | console.log(window.location.origin); 5 | const {domain} = parse( 6 | window.location.hostname 7 | ); 8 | return `${domain}` 9 | } 10 | 11 | export const getAdminUrl = () => { 12 | const currentDomain = process.env.MIX_MAIN_DOMAIN; 13 | return "nrheqb-store.loja.maratona.fullcycle.com.br"; 14 | // const currentDomain = getCurrentDomain(); 15 | // const port = process.env.NODE_ENV === 'development' ? `:${process.env.MIX_DEV_ADMIN_PORT}` : ""; 16 | // if (process.env.MIX_MAIN_DOMAIN !== currentDomain) { 17 | // return `${window.domain.fallback_subdomain}-admin.${currentDomain}` + ":30902"; 18 | // } else { 19 | // return `${window.domain.site}` + ":30902"; 20 | // } 21 | } 22 | -------------------------------------------------------------------------------- /commerce-shop/frontend/js/util/yup.js: -------------------------------------------------------------------------------- 1 | import {setLocale} from 'yup'; 2 | 3 | const ptBR = { 4 | mixed: { 5 | required: '${path} é requerido', 6 | notType: '${path} é inválido' 7 | }, 8 | string: { 9 | max: '${path} precisa ter no máximo ${max} caracteres' 10 | }, 11 | number: { 12 | min: '${path} precisa ser no mínimo ${min}' 13 | } 14 | }; 15 | 16 | setLocale(ptBR); 17 | 18 | export * from 'yup'; 19 | -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_assets.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Source Sans Pro', sans-serif; 3 | } 4 | 5 | html, body { 6 | padding: 0; 7 | margin: 0 !important; 8 | height: 100%; 9 | } 10 | 11 | 12 | h1, h2, h3, h4, h5, h6 { 13 | margin: 0; 14 | } 15 | 16 | p { 17 | margin: 0; 18 | } 19 | 20 | a { 21 | &:hover, &:focus, &:active { 22 | text-decoration: none; 23 | } 24 | } 25 | 26 | iframe { 27 | margin: 0; 28 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_cart.scss: -------------------------------------------------------------------------------- 1 | .content-cart { 2 | min-height: calc(100% - 170px); 3 | height: auto; 4 | background-color: #ffffff; 5 | 6 | .breadcrumb { 7 | background-color: #ffffff; 8 | } 9 | 10 | .section-default { 11 | background-color: #ffffff; 12 | 13 | 14 | .table { 15 | border: none; 16 | margin-top: 30px; 17 | 18 | thead { 19 | tr { 20 | th { 21 | border: none; 22 | } 23 | } 24 | } 25 | 26 | tbody { 27 | tr { 28 | .product { 29 | border-top: 1px solid #ededed; 30 | @extend %flex-default; 31 | justify-content: flex-start; 32 | 33 | .img-fluid { 34 | max-width: 100px; 35 | margin-right: 30px; 36 | 37 | @include respond-to($_768) { 38 | max-width: 140px; 39 | } 40 | } 41 | 42 | .info { 43 | display: none; 44 | 45 | @include respond-to($_992) { 46 | display: inline; 47 | } 48 | 49 | p { 50 | font-size: 1.2em; 51 | font-weight: 600; 52 | color: #808080; 53 | } 54 | 55 | span { 56 | font-size: 0.8em; 57 | font-weight: 400; 58 | color: #808080; 59 | } 60 | } 61 | } 62 | 63 | .price { 64 | font-size: 1em; 65 | font-weight: 400; 66 | color: #303030; 67 | min-width: 130px; 68 | } 69 | } 70 | } 71 | } 72 | 73 | 74 | .details { 75 | background-color: #fafafa; 76 | padding: 15px; 77 | margin-top: 30px; 78 | 79 | h3 { 80 | font-size: 1.4em; 81 | font-weight: 400; 82 | color: #000000; 83 | } 84 | 85 | .list-info, .list-info-total { 86 | @extend %flex-default; 87 | 88 | margin: 15px 0; 89 | p { 90 | font-size: 1em; 91 | font-weight: 400; 92 | color: #707070; 93 | } 94 | } 95 | 96 | .list-info-total { 97 | border-top: 1px solid #ededed; 98 | border-bottom: 1px solid #ededed; 99 | padding: 20px 0; 100 | align-items: flex-start; 101 | 102 | p { 103 | font-weight: 600; 104 | color: #000000; 105 | text-align: right; 106 | 107 | span { 108 | display: block; 109 | font-size: 0.9em; 110 | font-weight: 300; 111 | } 112 | } 113 | } 114 | 115 | .btn-info { 116 | @extend %btn-default; 117 | width: 100%; 118 | margin: 15px 0; 119 | 120 | @include respond-to($_992) { 121 | font-size: 1.3em; 122 | } 123 | } 124 | 125 | .btn-link { 126 | font-weight: 400; 127 | color: #000000; 128 | width: 100%; 129 | 130 | @include respond-to($_992) { 131 | font-size: 1.3em; 132 | } 133 | 134 | &:hover { 135 | 136 | } 137 | } 138 | } 139 | 140 | } 141 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_category.scss: -------------------------------------------------------------------------------- 1 | .content-category { 2 | background-color: #fafafa; 3 | 4 | .info-filter { 5 | @extend %flex-default; 6 | flex-direction: column; 7 | align-items: flex-start; 8 | 9 | @include respond-to($_768) { 10 | flex-direction: row; 11 | align-items: center; 12 | } 13 | 14 | .info { 15 | @extend %flex-default; 16 | margin: 15px 0; 17 | 18 | @include respond-to($_768) { 19 | margin: 0; 20 | } 21 | 22 | img { 23 | margin-right: 5px; 24 | } 25 | 26 | p { 27 | font-size: 0.9em; 28 | font-weight: 400; 29 | color: #909090; 30 | } 31 | } 32 | 33 | .line { 34 | @include respond-to($_768) { 35 | border-top: 1px solid #ededed; 36 | flex-grow: 1; 37 | margin: 0 10px; 38 | } 39 | } 40 | 41 | .form-control { 42 | width: 190px; 43 | border-radius: 25px; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_commons.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 15px; 3 | 4 | @include respond-to($_1200) { 5 | padding-left: 0; 6 | padding-right: 0; 7 | } 8 | } 9 | 10 | .breadcrumb { 11 | padding: 0.75rem 0; 12 | background-color: #fafafa; 13 | margin-bottom: -60px; 14 | 15 | .breadcrumb-item { 16 | color: #909090; 17 | 18 | a { 19 | color: #909090; 20 | 21 | &:hover { 22 | text-decoration: underline; 23 | } 24 | } 25 | } 26 | } 27 | 28 | .section-default { 29 | @extend %section-default; 30 | background-color: #fafafa; 31 | 32 | .item { 33 | @extend %article-default; 34 | 35 | .item-img { 36 | @extend %img-box-default; 37 | } 38 | 39 | .title { 40 | @extend %title-box-default; 41 | } 42 | 43 | p { 44 | @extend %text-box-default-price; 45 | } 46 | } 47 | } 48 | 49 | .container-pagination { 50 | @extend %flex-default; 51 | justify-content: center; 52 | width: 100%; 53 | margin: 60px 0 0 0; 54 | } 55 | 56 | .page-item { 57 | margin: 0 10px; 58 | 59 | &.active { 60 | .page-link { 61 | border: 1px solid #000; 62 | background-color: #000; 63 | color: #fff; 64 | } 65 | } 66 | 67 | .page-link { 68 | color: #909090; 69 | background-color: #fff; 70 | border: 1px solid #dee2e6; 71 | 72 | &:hover { 73 | color: #ffffff; 74 | background-color: #d44d26; 75 | border-color: #d44d26; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_footer.scss: -------------------------------------------------------------------------------- 1 | .ftr { 2 | @extend %flex-default; 3 | justify-content: center; 4 | 5 | height: 50px; 6 | background-color: #1c1c1c; 7 | 8 | p { 9 | font-size: 0.9em; 10 | font-weight: 300; 11 | color: #ffffff; 12 | } 13 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_home.scss: -------------------------------------------------------------------------------- 1 | .content-home { 2 | 3 | .section-offers { 4 | @extend %section-default; 5 | background-color: #fafafa; 6 | 7 | .offer { 8 | @extend %article-default; 9 | border-top: 8px solid #d44d26; 10 | 11 | .offer-img { 12 | @extend %img-box-default; 13 | } 14 | } 15 | 16 | .title { 17 | @extend %title-box-default; 18 | } 19 | 20 | p { 21 | @extend %text-box-default-price; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_login.scss: -------------------------------------------------------------------------------- 1 | .content-login { 2 | min-height: calc(100% - 170px); 3 | height: auto; 4 | background-color: #fafafa; 5 | 6 | .section-login { 7 | @extend %flex-default; 8 | justify-content: center; 9 | padding: 0 15px; 10 | 11 | .container-login { 12 | max-width: 350px; 13 | width: auto; 14 | padding-top: 30px; 15 | 16 | @include respond-to($_768) { 17 | padding-top: 60px; 18 | } 19 | 20 | h2 { 21 | font-size: 1.8em; 22 | font-weight: 400; 23 | color: #909090; 24 | margin: 0 0 30px 0; 25 | 26 | @include respond-to($_768) { 27 | font-size: 2.4em; 28 | } 29 | } 30 | 31 | .form-login { 32 | width: 100%; 33 | 34 | @include respond-to($_768) { 35 | width: 350px; 36 | } 37 | 38 | .form-group { 39 | margin-bottom: 30px; 40 | 41 | .form-control { 42 | height: 46px; 43 | border-color: #ededed; 44 | } 45 | 46 | .text-muted { 47 | text-align: right; 48 | 49 | a { 50 | color: #909090; 51 | 52 | &:hover { 53 | text-decoration: underline; 54 | } 55 | } 56 | } 57 | } 58 | 59 | .btn-info { 60 | @extend %btn-default; 61 | height: 46px; 62 | width: 100%; 63 | } 64 | } 65 | 66 | .btn-link { 67 | font-weight: 400; 68 | color: #303030; 69 | width: 100%; 70 | margin-top: 15px; 71 | 72 | @include respond-to($_992) { 73 | font-size: 1.4em; 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_mixin.scss: -------------------------------------------------------------------------------- 1 | $color-son: #6ad270; 2 | 3 | $_240 : '(min-width: 240px)'; 4 | $_576 : '(min-width: 576px)'; 5 | $_768 : '(min-width: 768px)'; 6 | $_992 : '(min-width: 992px)'; 7 | $_1200: '(min-width: 1200px)'; 8 | 9 | @mixin respond-to($media) { 10 | @media only screen and #{$media} { 11 | @content; 12 | } 13 | } 14 | 15 | @mixin attr-text ($color: #878787) { 16 | font-weight: 300; 17 | color: $color; 18 | } 19 | 20 | @mixin attr-title ($color: #666666) { 21 | font-weight: 600; 22 | color: $color; 23 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_navbar-progress.scss: -------------------------------------------------------------------------------- 1 | .navbar-progress { 2 | box-shadow: 0 7px 15px -2px #dddddd; 3 | z-index: 2; 4 | 5 | background-color: #ffffff; 6 | height: 150px; 7 | @extend %flex-default; 8 | position: relative; 9 | 10 | @include respond-to($_768) { 11 | height: 105px; 12 | } 13 | 14 | .container { 15 | @extend %flex-default; 16 | 17 | flex-direction: column; 18 | 19 | @include respond-to($_768) { 20 | flex-direction: row; 21 | } 22 | 23 | .progressbar { 24 | counter-reset: step; 25 | width: 100%; 26 | margin: 0; 27 | padding: 0; 28 | 29 | li { 30 | list-style-type: none; 31 | width: 25%; 32 | float: left; 33 | font-size: 0.6em; 34 | font-weight: 400; 35 | position: relative; 36 | text-align: center; 37 | color: #808080; 38 | 39 | @include respond-to($_576) { 40 | font-size: 0.95em;; 41 | } 42 | 43 | &:before { 44 | width: 30px; 45 | height: 30px; 46 | //content: counter(step); 47 | //counter-increment: step; 48 | line-height: 30px; 49 | border: 2px solid #e2e2e2; 50 | display: block; 51 | text-align: center; 52 | margin: 0 auto 10px auto; 53 | border-radius: 50%; 54 | background-color: white; 55 | 56 | content: "\f00c"; 57 | font-family: FontAwesome; 58 | font-style: normal; 59 | font-weight: normal; 60 | text-decoration: inherit; 61 | font-size: 1em; 62 | color: #e2e2e2; 63 | 64 | @include respond-to($_576) { 65 | width: 35px; 66 | height: 35px; 67 | font-size: 1.4em; 68 | } 69 | } 70 | 71 | &:after { 72 | width: 100%; 73 | height: 2px; 74 | content: ''; 75 | position: absolute; 76 | background-color: #e2e2e2; 77 | top: 15px; 78 | left: -50%; 79 | z-index: -1; 80 | } 81 | 82 | &:first-child { 83 | &:after { 84 | content: none; 85 | } 86 | } 87 | 88 | &.active { 89 | color: #808080; 90 | font-weight: 700; 91 | 92 | a { 93 | color: #808080; 94 | } 95 | 96 | &:before { 97 | background-color: #d44d26; 98 | border-color: #e2e2e2; 99 | color: #ffffff; 100 | } 101 | 102 | + li { 103 | &:after { 104 | background-color: #d44d26; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_payment-success.scss: -------------------------------------------------------------------------------- 1 | .content-payment-success { 2 | min-height: calc(100% - 150px); 3 | height: auto; 4 | background-color: #fafafa; 5 | 6 | h2 { 7 | margin-bottom: 30px; 8 | } 9 | 10 | p { 11 | font-size: 0.9em; 12 | font-weight: 700; 13 | color: #303030; 14 | margin-bottom: 10px; 15 | } 16 | 17 | .box-item { 18 | @extend %flex-default; 19 | justify-content: flex-start; 20 | background-color: #ffffff; 21 | border: 1px solid #ededed; 22 | margin-bottom: 20px; 23 | padding: 20px; 24 | height: 110px; 25 | 26 | 27 | @include respond-to($_1200) { 28 | padding: 20px 50px; 29 | } 30 | 31 | .container-img { 32 | @extend %flex-default; 33 | justify-content: center; 34 | width: 140px; 35 | 36 | img { 37 | max-width: 140px; 38 | width: auto; 39 | max-height: 68px; 40 | height: auto; 41 | margin-right: 30px; 42 | } 43 | } 44 | 45 | 46 | .info { 47 | display: none; 48 | 49 | @include respond-to($_992) { 50 | display: inline; 51 | flex-grow: 1; 52 | } 53 | 54 | h2 { 55 | font-size: 1.2em; 56 | font-weight: 600; 57 | color: #808080; 58 | margin: 0; 59 | } 60 | 61 | span { 62 | font-size: 0.8em; 63 | font-weight: 400; 64 | color: #808080; 65 | } 66 | } 67 | 68 | p { 69 | font-size: 1em; 70 | font-weight: 400; 71 | color: #808080; 72 | } 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_payment.scss: -------------------------------------------------------------------------------- 1 | .content-payment { 2 | min-height: calc(100% - 150px); 3 | height: auto; 4 | background-color: #fafafa; 5 | 6 | .form-payment { 7 | margin-top: 30px; 8 | 9 | #fieldsetCard { 10 | display: inline; 11 | } 12 | 13 | h2 { 14 | margin-bottom: 30px; 15 | } 16 | 17 | .col-check { 18 | @include respond-to($_1200) { 19 | width: 100%; 20 | 21 | .two { 22 | margin-left: 50px; 23 | } 24 | } 25 | 26 | .form-check { 27 | margin-bottom: 20px; 28 | 29 | img { 30 | margin-left: 10px; 31 | } 32 | } 33 | } 34 | 35 | .form-group { 36 | margin-bottom: 20px; 37 | .form-control { 38 | height: 46px; 39 | border-color: #ededed; 40 | } 41 | } 42 | 43 | .btn-info { 44 | @extend %btn-default; 45 | height: 46px; 46 | width: 100%; 47 | 48 | @include respond-to($_992) { 49 | width: 250px; 50 | } 51 | 52 | &.slip { 53 | @include respond-to($_1200) { 54 | width: 470px; 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | .details { 62 | background-color: #ffffff; 63 | padding: 15px; 64 | margin-top: 30px; 65 | box-shadow: 0px 3px 9px 4px rgba(221, 221, 221, 0.69); 66 | 67 | @include respond-to($_1200) { 68 | margin-top: 100px; 69 | } 70 | 71 | h3 { 72 | font-size: 1.4em; 73 | font-weight: 400; 74 | color: #000000; 75 | } 76 | 77 | .list-info, .list-info-total { 78 | @extend %flex-default; 79 | 80 | margin: 15px 0; 81 | 82 | p { 83 | font-size: 1em; 84 | font-weight: 400; 85 | color: #707070; 86 | } 87 | } 88 | 89 | .list-info-total { 90 | border-top: 1px solid #ededed; 91 | padding: 20px 0; 92 | align-items: flex-start; 93 | 94 | p { 95 | font-weight: 600; 96 | color: #000000; 97 | text-align: right; 98 | 99 | span { 100 | display: block; 101 | color: #707070; 102 | font-size: 0.9em; 103 | font-weight: 300; 104 | } 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_placeholders.scss: -------------------------------------------------------------------------------- 1 | %flex-default { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | justify-content: space-between; 6 | } 7 | 8 | %section-default { 9 | padding: 30px 0; 10 | 11 | h2 { 12 | font-size: 2.4em; 13 | font-weight: 400; 14 | color: #909090; 15 | margin: 0; 16 | } 17 | } 18 | 19 | %article-default { 20 | background-color: #ffffff; 21 | border: 1px solid #dddddd; 22 | padding: 20px; 23 | margin-top: 30px; 24 | 25 | &:hover { 26 | -webkit-box-shadow: 0px 3px 9px 4px rgba(221,221,221,0.69); 27 | -moz-box-shadow: 0px 3px 9px 4px rgba(221,221,221,0.69); 28 | box-shadow: 0px 3px 9px 4px rgba(221,221,221,0.69); 29 | } 30 | } 31 | 32 | %img-box-default { 33 | height: 160px; 34 | @extend %flex-default; 35 | justify-content: center; 36 | 37 | img { 38 | max-height: 160px;; 39 | } 40 | } 41 | 42 | %title-box-default { 43 | min-height: 100px; 44 | padding-top: 15px; 45 | @extend %flex-default; 46 | align-items: flex-start; 47 | 48 | h2 { 49 | font-size: 1.1em; 50 | font-weight: 400; 51 | color: #909090; 52 | } 53 | } 54 | 55 | %text-box-default { 56 | font-size: 1em; 57 | font-weight: 400; 58 | color: #000000; 59 | } 60 | 61 | %text-box-default-price { 62 | font-size: 1em; 63 | font-weight: 400; 64 | color: #000000; 65 | 66 | &.price { 67 | font-size: 1.8em; 68 | font-weight: 700; 69 | } 70 | } 71 | 72 | %btn-default { 73 | background-color: #d44d26; 74 | border-color: #d44d26; 75 | font-weight: 600; 76 | color: #ffffff; 77 | text-transform: uppercase; 78 | 79 | &:hover { 80 | background-color: #ffffff; 81 | color: #d44d26; 82 | } 83 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_product.scss: -------------------------------------------------------------------------------- 1 | .content-product { 2 | background-color: #fafafa; 3 | min-height: calc(100% - 240px); 4 | height: auto; 5 | 6 | .product { 7 | margin-top: 30px; 8 | padding-bottom: 30px; 9 | } 10 | 11 | .item { 12 | background-color: #ffffff; 13 | padding: 15px; 14 | 15 | .product-img { 16 | height: 380px; 17 | border: 1px solid #ddd; 18 | 19 | @extend %flex-default; 20 | justify-content: center; 21 | 22 | .img-fluid { 23 | max-height: 350px; 24 | height: auto; 25 | max-width: 525px; 26 | width: auto; 27 | } 28 | } 29 | 30 | .product-info { 31 | @extend %flex-default; 32 | flex-direction: column; 33 | align-items: flex-start; 34 | 35 | @include respond-to($_992) { 36 | margin-left: 50px; 37 | height: 380px; 38 | } 39 | 40 | .title { 41 | padding-top: 30px; 42 | 43 | @include respond-to($_992) { 44 | padding-top: 15px; 45 | } 46 | 47 | h2 { 48 | font-size: 1.6em; 49 | font-weight: 700; 50 | color: #909090; 51 | } 52 | 53 | span { 54 | font-size: 0.9em; 55 | font-weight: 400; 56 | color: #909090; 57 | } 58 | } 59 | 60 | .info { 61 | border-top: 1px solid #ededed; 62 | @extend %flex-default; 63 | flex-direction: column; 64 | flex-wrap: wrap; 65 | width: 100%; 66 | 67 | @include respond-to($_1200) { 68 | padding-bottom: 15px; 69 | } 70 | 71 | .info-price { 72 | text-align: center; 73 | p { 74 | font-size: 1em; 75 | font-weight: 400; 76 | color: #303030; 77 | margin: 0; 78 | 79 | &.price { 80 | font-size: 2em; 81 | font-weight: 700; 82 | 83 | @include respond-to($_1200) { 84 | font-size: 2.7em; 85 | } 86 | } 87 | 88 | &.price-div { 89 | margin-top: -15px; 90 | } 91 | } 92 | } 93 | 94 | .btn-info { 95 | @extend %btn-default; 96 | 97 | @include respond-to($_992) { 98 | font-size: 1.3em; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | .item-details { 106 | flex-direction: column; 107 | h2 { 108 | font-size: 2.4em; 109 | font-weight: 400; 110 | color: #909090; 111 | margin: 30px 0; 112 | } 113 | 114 | .item-details-info { 115 | background-color: #ffffff; 116 | padding: 15px; 117 | 118 | p { 119 | font-size: 1.18em; 120 | font-weight: 300; 121 | color: #909090; 122 | } 123 | } 124 | } 125 | 126 | .section-default { 127 | background-color: #ffffff; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/_register.scss: -------------------------------------------------------------------------------- 1 | .content-register { 2 | min-height: calc(100% - 170px); 3 | height: auto; 4 | background-color: #fafafa; 5 | 6 | 7 | .form-register { 8 | margin-top: 30px; 9 | 10 | h3 { 11 | font-size: 1.2em; 12 | font-weight: 600; 13 | color: #000000; 14 | margin-bottom: 15px; 15 | } 16 | 17 | .form-group { 18 | margin-bottom: 30px; 19 | 20 | .form-control { 21 | height: 46px; 22 | border-color: #ededed; 23 | } 24 | } 25 | 26 | .btn-info { 27 | @extend %btn-default; 28 | height: 46px; 29 | width: 100%; 30 | 31 | @include respond-to($_992) { 32 | width: 250px; 33 | } 34 | } 35 | } 36 | 37 | .btn-link { 38 | font-weight: 400; 39 | color: #303030; 40 | width: 100%; 41 | padding-left: 0; 42 | text-align: left; 43 | 44 | @include respond-to($_992) { 45 | font-size: 1.4em; 46 | width: auto; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /commerce-shop/frontend/scss/main.scss: -------------------------------------------------------------------------------- 1 | // bootstrap 2 | @import "~bootstrap/scss/bootstrap"; 3 | 4 | // project 5 | @import "assets"; 6 | @import "placeholders"; 7 | @import "mixin"; 8 | @import "navbar"; 9 | @import "navbar-progress"; 10 | @import "footer"; 11 | @import "commons"; 12 | @import "home"; 13 | @import "category"; 14 | @import "product"; 15 | @import "cart"; 16 | @import "login"; 17 | @import "register"; 18 | @import "payment"; 19 | @import "payment-success"; 20 | -------------------------------------------------------------------------------- /commerce-shop/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/public/js/checkout.js": "/public/js/checkout.js", 3 | "/public/js/main.js": "/public/js/main.js", 4 | "/public/css/main.css": "/public/css/main.css" 5 | } 6 | -------------------------------------------------------------------------------- /commerce-shop/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /commerce-shop/public/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/404.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Adidas-Y-3-KAIWA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Adidas-Y-3-KAIWA.png -------------------------------------------------------------------------------- /commerce-shop/public/img/BANNER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/BANNER.png -------------------------------------------------------------------------------- /commerce-shop/public/img/HTB1biCEb25TBuNjSspmq6yDRVXau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/HTB1biCEb25TBuNjSspmq6yDRVXau.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Imagem_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Imagem_3.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Imagem_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Imagem_4.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Nike-Air270-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Nike-Air270-black.png -------------------------------------------------------------------------------- /commerce-shop/public/img/NikeAirZoomZeroPremium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/NikeAirZoomZeroPremium.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Puma_Clyde_Court_Men.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Puma_Clyde_Court_Men.png -------------------------------------------------------------------------------- /commerce-shop/public/img/Puma_HOT_WHEELS_RS-X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/Puma_HOT_WHEELS_RS-X.png -------------------------------------------------------------------------------- /commerce-shop/public/img/ZEUSLAP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/ZEUSLAP.png -------------------------------------------------------------------------------- /commerce-shop/public/img/adidas-y-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/adidas-y-3-2.png -------------------------------------------------------------------------------- /commerce-shop/public/img/adidasFALCON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/adidasFALCON.png -------------------------------------------------------------------------------- /commerce-shop/public/img/apple-watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/apple-watch.png -------------------------------------------------------------------------------- /commerce-shop/public/img/beats-headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/beats-headphones.png -------------------------------------------------------------------------------- /commerce-shop/public/img/bike2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/bike2.png -------------------------------------------------------------------------------- /commerce-shop/public/img/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/card.png -------------------------------------------------------------------------------- /commerce-shop/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/favicon.ico -------------------------------------------------------------------------------- /commerce-shop/public/img/homepod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/homepod.png -------------------------------------------------------------------------------- /commerce-shop/public/img/ipad-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/ipad-pro.png -------------------------------------------------------------------------------- /commerce-shop/public/img/iphone-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/iphone-x.png -------------------------------------------------------------------------------- /commerce-shop/public/img/jbl-speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/jbl-speaker.png -------------------------------------------------------------------------------- /commerce-shop/public/img/jlab-audio-wireless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/jlab-audio-wireless.png -------------------------------------------------------------------------------- /commerce-shop/public/img/logo-nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/logo-nav.png -------------------------------------------------------------------------------- /commerce-shop/public/img/macbook-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/macbook-pro.png -------------------------------------------------------------------------------- /commerce-shop/public/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/menu.png -------------------------------------------------------------------------------- /commerce-shop/public/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/search.png -------------------------------------------------------------------------------- /commerce-shop/public/img/shopping-cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/shopping-cart.png -------------------------------------------------------------------------------- /commerce-shop/public/img/slip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/slip.png -------------------------------------------------------------------------------- /commerce-shop/public/img/sort-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/sort-down.png -------------------------------------------------------------------------------- /commerce-shop/public/img/symbol_22_____3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/symbol_22_____3.png -------------------------------------------------------------------------------- /commerce-shop/public/img/symbol_total.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratonafc3-repo-main/c7d8c0e2167fb2ecdbbaf60728e36ec412019fb5/commerce-shop/public/img/symbol_total.png -------------------------------------------------------------------------------- /commerce-shop/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/commands.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {ConsoleModule} from "nestjs-console"; 3 | import {FixturesService} from "./fixtures/fixtures.service"; 4 | import {EsDataSourceService} from "../elasticsearch/es-data-source/es-data-source.service"; 5 | import {TenantRepository} from "../repositories/tenant/tenant.repository"; 6 | import {CategoryRepository} from "../repositories/category/category.repository"; 7 | import {ProductRepository} from "../repositories/product/product.repository"; 8 | import {TenantService} from "../services/tenant/tenant.service"; 9 | import {PaymentMethodRepository} from "../repositories/payment-method/payment-method.repository"; 10 | import {PaymentMethodConfigRepository} from "../repositories/payment-method-config/payment-method-config.repository"; 11 | 12 | @Module({ 13 | imports: [ 14 | ConsoleModule 15 | ], 16 | providers: [ 17 | PaymentMethodRepository, 18 | PaymentMethodConfigRepository, 19 | TenantService, 20 | FixturesService, 21 | EsDataSourceService, 22 | TenantRepository, 23 | CategoryRepository, 24 | ProductRepository, 25 | ] 26 | }) 27 | export class CommandsModule {} 28 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/fixtures.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { FixturesService } from './fixtures.service'; 3 | 4 | describe('FixturesService', () => { 5 | let service: FixturesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [FixturesService], 10 | }).compile(); 11 | 12 | service = module.get(FixturesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/fixtures.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {ConsoleService} from "nestjs-console"; 3 | import {ModuleRef} from "@nestjs/core"; 4 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 5 | //@ts-ignore 6 | import {Client} from 'es7'; 7 | import fixtures from './json'; 8 | import {DefaultCrudRepository} from "@loopback/repository"; 9 | import chalk from 'chalk'; 10 | 11 | @Injectable() 12 | export class FixturesService { 13 | constructor( 14 | private readonly consoleService: ConsoleService, 15 | private moduleRef: ModuleRef, 16 | private dataSource: EsDataSourceService 17 | ) { 18 | const cli = this.consoleService.getCli(); 19 | 20 | this.consoleService.createCommand( 21 | { 22 | command: 'fixtures', 23 | description: 'Seed data in database' 24 | }, 25 | this.seed, 26 | cli 27 | ); 28 | } 29 | 30 | seed = async () => { 31 | await this.deleteAllDocuments(); 32 | 33 | for(const fixture of fixtures){ 34 | const repository = this.getRepository(fixture.model) as DefaultCrudRepository; 35 | await repository.create(fixture.fields); 36 | } 37 | 38 | console.log(chalk.green('Documents generated')) 39 | 40 | }; 41 | 42 | async deleteAllDocuments() { 43 | //@ts-ignore 44 | const connector = this.dataSource.adapter; 45 | //@ts-ignore 46 | const client: Client = this.dataSource.adapter.db; 47 | await client.deleteByQuery({ 48 | index: connector.settings.index, 49 | body: { 50 | query: { 51 | match_all: {} 52 | } 53 | } 54 | }); 55 | } 56 | 57 | getRepository(name): T { 58 | return this.moduleRef.get(`${name}Repository`); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/json/category.fixture.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | model: "Category", 4 | fields: { 5 | id: '75f0784c-793c-4798-bbdf-f11e231ec47a', 6 | name: "Calçados", 7 | slug: "calcados", 8 | tenant_id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 9 | created_at: "2020-01-01T00:00:00.000Z", 10 | updated_at: "2020-01-01T00:00:00.000Z" 11 | }, 12 | }, 13 | { 14 | model: "Category", 15 | fields: { 16 | id: '70c84108-b918-4cb0-a986-e821652e80a7', 17 | name: "Relógios", 18 | slug: "relogios", 19 | tenant_id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 20 | created_at: "2020-01-01T00:00:01.000Z", 21 | updated_at: "2020-01-01T00:00:01.000Z" 22 | }, 23 | }, 24 | { 25 | model: "Category", 26 | fields: { 27 | id: '41eabe34-9682-48f4-bbb4-d3c61f557358', 28 | name: "Celulares", 29 | slug: "celulares", 30 | tenant_id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 31 | created_at: "2020-01-01T00:00:02.000Z", 32 | updated_at: "2020-01-01T00:00:02.000Z" 33 | }, 34 | }, 35 | { 36 | model: "Category", 37 | fields: { 38 | id: '06647c18-f437-461c-a111-53b8d86b40a4', 39 | name: "Perfumes", 40 | slug: "perfumes", 41 | tenant_id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 42 | created_at: "2020-01-01T00:00:03.000Z", 43 | updated_at: "2020-01-01T00:00:03.000Z" 44 | }, 45 | }, 46 | { 47 | model: "Category", 48 | fields: { 49 | id: '1b6f552f-e962-4e97-8f8e-a16df050c622', 50 | name: "Eletrônicos", 51 | slug: "eletronicos", 52 | tenant_id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 53 | created_at: "2020-01-01T00:00:04.000Z", 54 | updated_at: "2020-01-01T00:00:04.000Z" 55 | }, 56 | } 57 | ] 58 | 59 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/json/index.ts: -------------------------------------------------------------------------------- 1 | import tenants from './tenant.fixture'; 2 | import paymentMethods from './payment-method'; 3 | import paymentMethodsConfig from './payment-method-config'; 4 | import categories from './category.fixture'; 5 | import products from './product.fixture'; 6 | 7 | export default [ 8 | ...tenants, 9 | ...paymentMethods, 10 | ...paymentMethodsConfig, 11 | ...categories, 12 | ...products 13 | ] 14 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/json/payment-method-config.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | "model": "PaymentMethodConfig", 4 | "fields": { 5 | "id": "0744a1f3-508c-401e-abf0-f89ec7517fe4", 6 | "payment_method_id": "2046b39c-e05a-4412-8264-73dd80217a11", 7 | "max_installments": 12, 8 | "discount_percentage": 0, 9 | "tenant_id": "1cc0182d-bdf9-435b-86c3-2a201100a134", 10 | "created_at": "2020-01-01T00:00:00.000Z", 11 | "updated_at": "2020-01-01T00:00:00.000Z" 12 | } 13 | }, 14 | { 15 | "model": "PaymentMethodConfig", 16 | "fields": { 17 | "id": "3e8f9413-bca1-4a99-8083-d83ad2d14e25", 18 | "payment_method_id": "22f7ee75-9586-418b-b989-174740a9831b", 19 | "max_installments": 0, 20 | "discount_percentage": 0, 21 | "tenant_id": "1cc0182d-bdf9-435b-86c3-2a201100a134", 22 | "created_at": "2020-01-01T00:01:00.000Z", 23 | "updated_at": "2020-01-01T00:01:00.000Z" 24 | } 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/json/payment-method.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | "model": "PaymentMethod", 4 | "fields": { 5 | "id": "2046b39c-e05a-4412-8264-73dd80217a11", 6 | "name": "credit_card", 7 | "allow_installments": true, 8 | "created_at": "2020-01-01T00:00:00.000Z", 9 | "updated_at": "2020-01-01T00:00:00.000Z" 10 | } 11 | }, 12 | { 13 | "model": "PaymentMethod", 14 | "fields": { 15 | "id": "22f7ee75-9586-418b-b989-174740a9831b", 16 | "name": "bank_slip", 17 | "allow_installments": false, 18 | "created_at": "2020-01-01T00:01:00.000Z", 19 | "updated_at": "2020-01-01T00:01:00.000Z" 20 | } 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /commerce-shop/src/commands/fixtures/json/tenant.fixture.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | model: "Tenant", 4 | fields: { 5 | id: '1cc0182d-bdf9-435b-86c3-2a201100a134', 6 | company: "Store 1", 7 | site: "code-commerce1.test", 8 | fallback_subdomain: "store1", 9 | created_at: "2020-01-01T00:00:00.000Z", 10 | updated_at: "2020-01-01T00:00:00.000Z" 11 | }, 12 | }, 13 | { 14 | model: "Tenant", 15 | fields: { 16 | id: '5404ef25-b6a1-43ec-977b-cce1509f2c8b', 17 | company: "Store 2", 18 | site: "code-commerce2.test", 19 | fallback_subdomain: "store2", 20 | created_at: "2020-01-01T00:01:00.000Z", 21 | updated_at: "2020-01-01T00:01:00.000Z" 22 | }, 23 | }, 24 | { 25 | model: "Tenant", 26 | fields: { 27 | id: 'e17e6950-2be4-42d6-b6a2-11b9cf5e4897', 28 | company: "Store 3", 29 | site: "code-commerce3.test", 30 | fallback_subdomain: "store3", 31 | created_at: "2020-01-01T00:02:00.000Z", 32 | updated_at: "2020-01-01T00:02:00.000Z" 33 | }, 34 | } 35 | ] 36 | 37 | 38 | -------------------------------------------------------------------------------- /commerce-shop/src/console.ts: -------------------------------------------------------------------------------- 1 | import { BootstrapConsole } from 'nestjs-console'; 2 | import {AppModule} from "./app.module"; 3 | 4 | const bootstrap = new BootstrapConsole({ 5 | module: AppModule, 6 | useDecorators: true 7 | }); 8 | bootstrap.init().then(async app => { 9 | try { 10 | await app.init(); 11 | await bootstrap.boot(); 12 | process.exit(0); 13 | } catch (e) { 14 | process.exit(1); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/cart/cart.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CartController } from './cart.controller'; 3 | 4 | describe('CartView Controller', () => { 5 | let controller: CartController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CartController], 10 | }).compile(); 11 | 12 | controller = module.get(CartController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/cart/cart.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClassSerializerInterceptor, 3 | Controller, 4 | Get, 5 | NotFoundException, 6 | Param, 7 | Render, 8 | Req, 9 | Res, 10 | UseInterceptors 11 | } from '@nestjs/common'; 12 | import {Request, Response} from "express"; 13 | import "express-session"; 14 | import {CartService} from "../../services"; 15 | import {ProductRepository} from "../../repositories"; 16 | import {classToPlain} from "class-transformer"; 17 | import {CartJson, CartView} from "../../decorators/cart.decorator"; 18 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 19 | 20 | @Controller('cart') 21 | export class CartController { 22 | 23 | constructor( 24 | private cartService: CartService, 25 | private productRepo: ProductRepository, 26 | ) { 27 | } 28 | 29 | @MainDependencies() 30 | @Get() 31 | @Render('cart/show') 32 | async index(@Req() request) { 33 | return { 34 | 35 | } 36 | } 37 | 38 | @CartView() 39 | @Get('add/:product') 40 | async add( 41 | @Param('product') productId, 42 | @Req() request: Request, 43 | @Res() response: Response 44 | ) { 45 | const product = await this.productRepo.findOne({where: {id: productId}}) 46 | if (!product) { 47 | throw new NotFoundException('Produto não existe'); 48 | } 49 | this.cartService.add(product); 50 | request.session.cart = this.cartService.serialize(); 51 | return response.redirect(request.header('Referer') || '/'); 52 | } 53 | 54 | @CartJson() 55 | @UseInterceptors(ClassSerializerInterceptor) 56 | @Get('_json') 57 | show() { 58 | return { 59 | ...classToPlain(this.cartService), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/category/category.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CategoryController } from './category.controller'; 3 | 4 | describe('Category Controller', () => { 5 | let controller: CategoryController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CategoryController], 10 | }).compile(); 11 | 12 | controller = module.get(CategoryController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/category/category.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, NotFoundException, Param, Query, Render} from '@nestjs/common'; 2 | import {ProductRepository} from "../../repositories"; 3 | import {CategoryRepository} from "../../repositories"; 4 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 5 | import {Filter} from "@loopback/repository"; 6 | import {Product} from "../../models/product.model"; 7 | import {Pagination, PaginationData} from "../../decorators/pagination.decorator"; 8 | import {OrderProducts, OrderProductsData} from "../../decorators/order-product.decorator"; 9 | 10 | @Controller('category') 11 | export class CategoryController { 12 | 13 | constructor( 14 | private productRepo: ProductRepository, 15 | private categoryRepo: CategoryRepository, 16 | ) { 17 | } 18 | 19 | @MainDependencies() 20 | @Get(':slug') 21 | @Render('category/show') 22 | async show( 23 | @Param('slug') slug: string, 24 | @Pagination({limit: 15}) pagination: PaginationData, 25 | @OrderProducts() order: OrderProductsData 26 | ) { 27 | const category = await this.categoryRepo.findOne({where: {slug}}) 28 | if (!category) { 29 | throw new NotFoundException('Categoria não existe'); 30 | } 31 | 32 | const filterRepo: Filter = { 33 | order: order.order, 34 | limit: pagination.limit, 35 | offset: pagination.offset, 36 | where: { 37 | // @ts-ignore 38 | 'category.id': category.id 39 | } 40 | }; 41 | 42 | const products = await this.productRepo.find(filterRepo) 43 | const {count} = await this.productRepo.count(filterRepo.where); 44 | 45 | return { 46 | pagination: { 47 | ...pagination, 48 | countPages: pagination.countPages(count) 49 | }, 50 | order: order.orderParam, 51 | totalProducts: count, 52 | category, 53 | products 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/checkout/checkout.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CheckoutController } from './checkout.controller'; 3 | 4 | describe('Checkout Controller', () => { 5 | let controller: CheckoutController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CheckoutController], 10 | }).compile(); 11 | 12 | controller = module.get(CheckoutController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/checkout/checkout.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Render, Req} from '@nestjs/common'; 2 | import {CartService} from "../../services"; 3 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 4 | import {Request} from "express"; 5 | 6 | @Controller('checkout*') 7 | export class CheckoutController { 8 | 9 | constructor( 10 | private cartService: CartService 11 | ) { 12 | } 13 | 14 | 15 | @MainDependencies() 16 | @Get('/payment-success') 17 | @Render('checkout/payment-success') 18 | success(@Req() req: Request) { 19 | 20 | req.session.cart = null; 21 | const oldCartItems = this.cartService.enumerateItems 22 | this.cartService.clear(); 23 | 24 | return { 25 | oldCartItems 26 | } 27 | } 28 | 29 | @MainDependencies() 30 | @Get('') 31 | @Render('checkout') 32 | async index(@Req() request) { 33 | return { 34 | layout: false, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/home/home.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { HomeController } from './home.controller'; 3 | 4 | describe('Home Controller', () => { 5 | let controller: HomeController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [HomeController], 10 | }).compile(); 11 | 12 | controller = module.get(HomeController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/home/home.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Render} from '@nestjs/common'; 2 | import { 3 | CategoryRepository, 4 | ProductRepository, 5 | } from "../../repositories"; 6 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 7 | 8 | @Controller('') 9 | export class HomeController { 10 | 11 | constructor( 12 | private categoryRepo: CategoryRepository, 13 | private productRepo: ProductRepository, 14 | ) { 15 | } 16 | 17 | @MainDependencies() 18 | @Get() 19 | @Render('home') 20 | async index() { 21 | const featuredProducts = await this.productRepo.find({ 22 | limit: 10, 23 | where: { 24 | featured: true 25 | } 26 | }); 27 | const topSellingProducts = await this.productRepo.find({ 28 | order: ['count_sales DESC'], 29 | limit: 10, 30 | }); 31 | 32 | return { 33 | featuredProducts, 34 | topSellingProducts, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cart/cart.controller'; 2 | export * from './category/category.controller'; 3 | export * from './checkout/checkout.controller'; 4 | export * from './home/home.controller'; 5 | export * from './product/product.controller'; 6 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/product/product.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductController } from './product.controller'; 3 | 4 | describe('Product Controller', () => { 5 | let controller: ProductController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ProductController], 10 | }).compile(); 11 | 12 | controller = module.get(ProductController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/product/product.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, NotFoundException, Param, Render, Req} from '@nestjs/common'; 2 | import {ProductRepository} from "../../repositories"; 3 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 4 | 5 | @Controller('product') 6 | export class ProductController { 7 | 8 | constructor( 9 | private productRepo: ProductRepository, 10 | ) { 11 | } 12 | 13 | @MainDependencies() 14 | @Get(':slug') 15 | @Render('product/show') 16 | async show( 17 | @Req() request, 18 | @Param('slug') slug: string 19 | ) { 20 | const product = await this.productRepo.findOne({where: {slug}}) 21 | if (!product) { 22 | throw new NotFoundException('Produto não existe'); 23 | } 24 | 25 | const categorizedProducts = await this.productRepo.findOne({ 26 | where: { 27 | // @ts-ignore 28 | 'category.id': product.category.id 29 | } 30 | }) 31 | 32 | return { 33 | product, 34 | categorizedProducts, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/search/search.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { SearchController } from './search.controller'; 3 | 4 | describe('Search Controller', () => { 5 | let controller: SearchController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [SearchController], 10 | }).compile(); 11 | 12 | controller = module.get(SearchController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/controllers/search/search.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Query, Render} from '@nestjs/common'; 2 | import {CategoryRepository, ProductRepository} from "../../repositories"; 3 | import MainDependencies from "../../decorators/main-dependencies.decorator"; 4 | import {Filter} from "@loopback/repository"; 5 | import {Product} from "../../models/product.model"; 6 | import {Pagination, PaginationData} from "../../decorators/pagination.decorator"; 7 | import {OrderProducts, OrderProductsData} from "../../decorators/order-product.decorator"; 8 | 9 | @Controller('search') 10 | export class SearchController { 11 | 12 | constructor( 13 | private productRepo: ProductRepository, 14 | private categoryRepo: CategoryRepository, 15 | ) { 16 | } 17 | 18 | @MainDependencies() 19 | @Get() 20 | @Render('category/show') 21 | async show( 22 | @Query('search') search: string, 23 | @Pagination({limit: 15}) pagination: PaginationData, 24 | @OrderProducts() order: OrderProductsData 25 | ) { 26 | 27 | const filterRepo: Filter = { 28 | order: ['_score DESC', ...order.order], 29 | where: { 30 | or: [ 31 | { 32 | // @ts-ignore 33 | fuzzy: { 34 | name: search, 35 | } 36 | }, 37 | { 38 | // @ts-ignore 39 | fuzzy: { 40 | description: search 41 | } 42 | } 43 | ] 44 | } 45 | }; 46 | 47 | const products = await this.productRepo.find(filterRepo) 48 | const {count} = await this.productRepo.count(filterRepo.where); 49 | 50 | return { 51 | search, 52 | pagination: { 53 | ...pagination, 54 | countPages: pagination.countPages(count) 55 | }, 56 | order: order.orderParam, 57 | totalProducts: count, 58 | products 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/cart.decorator.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | 3 | export const CartView = () => 4 | SetMetadata('cart-view', true); 5 | export const CartJson = () => 6 | SetMetadata('cart-json', true); 7 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/categories-default.decorator.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | 3 | const CategoriesDefault = () => SetMetadata('categories-default', true); 4 | 5 | export default CategoriesDefault; 6 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/main-dependencies.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import {CartView} from "./cart.decorator"; 3 | import CategoriesDefault from "./categories-default.decorator"; 4 | import PaymentMethodConfigGreaterInstallmentsDecorator from "./payment-method-config-greater-installments.decorator"; 5 | import {Tenant} from "./tenant.decorator"; 6 | 7 | function MainDependencies() { 8 | return applyDecorators( 9 | Tenant(), 10 | CartView(), 11 | CategoriesDefault(), 12 | PaymentMethodConfigGreaterInstallmentsDecorator() 13 | ); 14 | } 15 | 16 | export default MainDependencies; 17 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/order-product.decorator.ts: -------------------------------------------------------------------------------- 1 | import {createParamDecorator, ExecutionContext, SetMetadata} from "@nestjs/common"; 2 | import {Request} from "express"; 3 | 4 | export enum OrderProductQueryParam { 5 | ALL = 'todos', 6 | BEST_SELLERS = 'mais_vendidos', 7 | LOWEST_PRICE = 'menor_preco', 8 | BIGGEST_PRICE = 'maior_preco', 9 | } 10 | 11 | export interface OrderProductsData { 12 | order?: any; 13 | orderParam: OrderProductQueryParam 14 | } 15 | 16 | export const OrderProducts = createParamDecorator( 17 | (data: unknown, ctx: ExecutionContext) => { 18 | const request = ctx.switchToHttp().getRequest(); 19 | const orderParam = request.query.order; 20 | switch (orderParam) { 21 | case OrderProductQueryParam.BEST_SELLERS: 22 | return { 23 | order: ['count_sales DESC'], 24 | orderParam: OrderProductQueryParam.BEST_SELLERS 25 | } 26 | case OrderProductQueryParam.LOWEST_PRICE: 27 | return { 28 | order: ['price ASC'], 29 | orderParam: OrderProductQueryParam.LOWEST_PRICE 30 | } 31 | case OrderProductQueryParam.BIGGEST_PRICE: 32 | return { 33 | order: ['price DESC'], 34 | orderParam: OrderProductQueryParam.BIGGEST_PRICE 35 | } 36 | default: 37 | return { 38 | order: [], 39 | orderParam: OrderProductQueryParam.ALL 40 | } 41 | } 42 | }, 43 | ); 44 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/pagination.decorator.ts: -------------------------------------------------------------------------------- 1 | import {createParamDecorator, ExecutionContext, SetMetadata} from "@nestjs/common"; 2 | import {Request} from "express"; 3 | 4 | export interface PaginationData { 5 | page: number, 6 | limit: number; 7 | offset: number; 8 | countPages: (count: number) => number 9 | } 10 | 11 | export const Pagination = createParamDecorator( 12 | ({limit}, ctx: ExecutionContext) => { 13 | const request = ctx.switchToHttp().getRequest(); 14 | const pageParam = request.query.page; 15 | const page = +pageParam > 0 ? +pageParam : 1; 16 | return { 17 | page, 18 | limit: limit, 19 | offset: (page - 1) * limit, 20 | countPages: (count) => ((count + limit - 1) / limit | 0) 21 | } 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/payment-method-config-greater-installments.decorator.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | 3 | const PaymentMethodConfigGreaterInstallmentsDecorator = () => SetMetadata('payment-method-config-greater-installments', true); 4 | 5 | export default PaymentMethodConfigGreaterInstallmentsDecorator; 6 | -------------------------------------------------------------------------------- /commerce-shop/src/decorators/tenant.decorator.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | 3 | export const Tenant = () => SetMetadata('tenant', true); 4 | -------------------------------------------------------------------------------- /commerce-shop/src/elasticsearch/elasticsearch.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EsDataSourceService } from './es-data-source/es-data-source.service'; 3 | import {ConfigService} from "@nestjs/config"; 4 | 5 | @Module({ 6 | providers: [EsDataSourceService, ConfigService], 7 | exports: [EsDataSourceService] 8 | }) 9 | export class ElasticsearchModule {} 10 | -------------------------------------------------------------------------------- /commerce-shop/src/elasticsearch/es-data-source/es-data-source.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EsDataSourceService } from './es-data-source.service'; 3 | 4 | describe('DataSourceService', () => { 5 | let service: EsDataSourceService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [EsDataSourceService], 10 | }).compile(); 11 | 12 | service = module.get(EsDataSourceService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/cart.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { CartInterceptor } from './cart.interceptor'; 2 | 3 | describe('CartInterceptor', () => { 4 | it('should be defined', () => { 5 | expect(new CartInterceptor()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/cart.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {CallHandler, ExecutionContext, Injectable, NestInterceptor, Scope} from '@nestjs/common'; 2 | import {Observable} from 'rxjs'; 3 | import {Reflector} from "@nestjs/core"; 4 | import {map} from "rxjs/operators"; 5 | import {CartService} from "../services"; 6 | 7 | @Injectable({ 8 | scope: Scope.REQUEST 9 | }) 10 | export class CartInterceptor implements NestInterceptor { 11 | 12 | constructor( 13 | private reflector: Reflector, 14 | private cartService: CartService, 15 | ) { 16 | 17 | } 18 | 19 | intercept(context: ExecutionContext, next: CallHandler): Observable { 20 | const hasCartView = this.hasCartViewDecorator(context); 21 | const hasCartJson = this.hasCartJsonDecorator(context); 22 | const request = context.switchToHttp().getRequest(); 23 | if (hasCartView || hasCartJson) { 24 | this.cartService.deserialize(request.session.cart || {}); 25 | } 26 | return next 27 | .handle() 28 | .pipe( 29 | map(responseData => { 30 | if (hasCartView) { 31 | return { 32 | ...responseData, 33 | cart: this.cartService 34 | } 35 | } 36 | return responseData; 37 | }) 38 | ) 39 | ; 40 | } 41 | 42 | hasCartViewDecorator(context: ExecutionContext) { 43 | return this.reflector.get('cart-view', context.getHandler()); 44 | } 45 | 46 | hasCartJsonDecorator(context: ExecutionContext) { 47 | return this.reflector.get('cart-json', context.getHandler()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/categories-default.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { CategoriesDefaultInterceptor } from './categories-default.interceptor'; 2 | 3 | describe('CategoriesDefaultInterceptor', () => { 4 | it('should be defined', () => { 5 | expect(new CategoriesDefaultInterceptor()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/categories-default.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {CallHandler, ExecutionContext, Injectable, NestInterceptor} from '@nestjs/common'; 2 | import {CategoryRepository} from "../repositories"; 3 | import {Reflector} from "@nestjs/core"; 4 | import {map} from "rxjs/operators"; 5 | 6 | @Injectable() 7 | export class CategoriesDefaultInterceptor implements NestInterceptor { 8 | 9 | constructor( 10 | private reflector: Reflector, 11 | private categoryRepo: CategoryRepository 12 | ) { 13 | } 14 | 15 | async intercept(context: ExecutionContext, next: CallHandler){ 16 | let categories; 17 | const hasDecorator = this.hasDecorator(context); 18 | if (hasDecorator) { 19 | categories = await this.categoryRepo.find({ 20 | order: ['name.keyword DESC'] 21 | }) 22 | 23 | } 24 | return next.handle() 25 | .pipe( 26 | map(responseData => { 27 | if(hasDecorator) { 28 | return { 29 | ...responseData, 30 | ...(categories && {categories}) 31 | }; 32 | } 33 | 34 | return responseData 35 | }) 36 | ); 37 | } 38 | 39 | hasDecorator(context: ExecutionContext) { 40 | return this.reflector.get('categories-default', context.getHandler()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/payment-method-config-greater-installments.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { PaymentMethodConfigGreaterInstallmentsInterceptor } from './payment-method-config-greater-installments.interceptor'; 2 | 3 | describe('PaymentMethodConfigGreaterInstallmentsInterceptor', () => { 4 | it('should be defined', () => { 5 | expect(new PaymentMethodConfigGreaterInstallmentsInterceptor()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/payment-method-config-greater-installments.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {CallHandler, ExecutionContext, Injectable, NestInterceptor} from '@nestjs/common'; 2 | import {Reflector} from "@nestjs/core"; 3 | import {PaymentMethodConfigRepository} from "../repositories"; 4 | import {map} from "rxjs/operators"; 5 | 6 | @Injectable() 7 | export class PaymentMethodConfigGreaterInstallmentsInterceptor implements NestInterceptor { 8 | constructor( 9 | private reflector: Reflector, 10 | private paymentMethodConfig: PaymentMethodConfigRepository, 11 | ) { 12 | } 13 | 14 | async intercept(context: ExecutionContext, next: CallHandler) { 15 | let payMethodConfigs; 16 | const hasDecorator = this.hasDecorator(context); 17 | if (hasDecorator) { 18 | payMethodConfigs = await this.paymentMethodConfig.find({ 19 | order: ['max_installments DESC'] 20 | }); 21 | } 22 | return next.handle() 23 | .pipe( 24 | map(responseData => { 25 | if(hasDecorator) { 26 | return { 27 | ...responseData, 28 | ...(payMethodConfigs && payMethodConfigs.length && {payMethodConfig: payMethodConfigs[0]}) 29 | } 30 | } 31 | 32 | return responseData; 33 | }) 34 | ); 35 | } 36 | 37 | hasDecorator(context: ExecutionContext) { 38 | return this.reflector.get('payment-method-config-greater-installments', context.getHandler()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/tenant.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { TenantInterceptor } from './tenant.interceptor'; 2 | 3 | describe('TenantInterceptor', () => { 4 | it('should be defined', () => { 5 | expect(new TenantInterceptor()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /commerce-shop/src/interceptors/tenant.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import {map} from "rxjs/operators"; 4 | import {TenantService} from "../services"; 5 | import {Reflector} from "@nestjs/core"; 6 | 7 | @Injectable() 8 | export class TenantInterceptor implements NestInterceptor { 9 | 10 | constructor( 11 | private reflector: Reflector, 12 | private tenantService: TenantService 13 | ) { 14 | } 15 | 16 | intercept(context: ExecutionContext, next: CallHandler): Observable { 17 | const hasDecorator = this.hasDecorator(context); 18 | 19 | return next 20 | .handle() 21 | .pipe( 22 | map(responseData => { 23 | if (hasDecorator) { 24 | return { 25 | ...responseData, 26 | tenant: this.tenantService.tenant 27 | } 28 | } 29 | return responseData; 30 | }) 31 | ) 32 | ; 33 | } 34 | 35 | hasDecorator(context: ExecutionContext) { 36 | return this.reflector.get('tenant', context.getHandler()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /commerce-shop/src/main.ts: -------------------------------------------------------------------------------- 1 | import {NestFactory} from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import {join} from "path"; 4 | import {NestExpressApplication} from "@nestjs/platform-express"; 5 | import * as expressHbs from 'express-handlebars'; 6 | import * as handlebarsHelpers from 'handlebars-helpers'; 7 | import * as session from 'express-session'; 8 | import * as connectRedis from 'connect-redis'; 9 | import * as redis from 'redis'; 10 | 11 | async function bootstrap() { 12 | const app = await NestFactory.create(AppModule); 13 | 14 | const hbs = expressHbs.create({ 15 | helpers: { 16 | ...handlebarsHelpers(), 17 | ASSETS_URL: () => process.env.MIX_ASSETS_URL, 18 | }, 19 | extname: '.hbs', 20 | defaultLayout: 'layout', 21 | partialsDir: [ 22 | join(__dirname, '..', 'views') 23 | ] 24 | }); 25 | 26 | const RedisStore = connectRedis(session); 27 | const redisClient = redis.createClient({ 28 | url: process.env.REDIS_URI 29 | }) 30 | app.use(session({ 31 | store: new RedisStore({ client: redisClient }), 32 | secret: process.env.SESSION_SECRET_KEY, 33 | resave: false, 34 | saveUninitialized: true, 35 | cookie: { secure: false } //true only in HTTPS 36 | })) 37 | 38 | app.engine('hbs', hbs.engine); 39 | app.setViewEngine('hbs'); 40 | app.useStaticAssets(join(__dirname, '..', 'public')) 41 | 42 | await app.listen(3000); 43 | } 44 | bootstrap(); 45 | -------------------------------------------------------------------------------- /commerce-shop/src/middlewares/tenant.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { TenantMiddleware } from './tenant.middleware'; 2 | 3 | describe('TenantMiddleware', () => { 4 | it('should be defined', () => { 5 | expect(new TenantMiddleware()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /commerce-shop/src/middlewares/tenant.middleware.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, NestMiddleware, NotFoundException} from '@nestjs/common'; 2 | import {Request, Response} from 'express'; 3 | import {TenantService} from "../services/tenant/tenant.service"; 4 | 5 | @Injectable() 6 | export class TenantMiddleware implements NestMiddleware { 7 | 8 | constructor(private tenantService: TenantService) { 9 | } 10 | 11 | async use(req: Request, res: Response, next: () => void) { 12 | try { 13 | await this.tenantService.setTenantBy(req.hostname); 14 | }catch (e) { 15 | console.error(e); 16 | throw new NotFoundException('Site not found'); 17 | } 18 | next(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /commerce-shop/src/models/base-tenant.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, property} from '@loopback/repository'; 2 | import {Exclude} from "class-transformer"; 3 | 4 | export class BaseTenantModel extends Entity { 5 | 6 | @Exclude() 7 | @property({ 8 | type: 'string', 9 | required: true, 10 | }) 11 | tenant_id: string; 12 | } 13 | -------------------------------------------------------------------------------- /commerce-shop/src/models/category.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, model, property} from '@loopback/repository'; 2 | import {BaseTenantModel} from "./base-tenant.model"; 3 | 4 | export interface SmallCategory { 5 | id: string; 6 | name: string; 7 | } 8 | 9 | @model() 10 | export class Category extends BaseTenantModel { 11 | 12 | @property({ 13 | type: 'string', 14 | id: true, 15 | generated: false, 16 | required: true, 17 | }) 18 | id: string; 19 | 20 | @property({ 21 | type: 'string', 22 | required: true, 23 | }) 24 | name: string; 25 | 26 | @property({ 27 | type: 'string', 28 | required: true, 29 | }) 30 | slug: string; 31 | 32 | @property({ 33 | type: 'date', 34 | required: true, 35 | }) 36 | created_at: string; 37 | 38 | @property({ 39 | type: 'date', 40 | required: true, 41 | }) 42 | updated_at: string; 43 | 44 | constructor(data?: Partial) { 45 | super(data); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /commerce-shop/src/models/payment-method-config.model.ts: -------------------------------------------------------------------------------- 1 | import {model, property} from '@loopback/repository'; 2 | import {BaseTenantModel} from "./base-tenant.model"; 3 | 4 | @model() 5 | export class PaymentMethodConfig extends BaseTenantModel { 6 | 7 | @property({ 8 | type: 'string', 9 | id: true, 10 | generated: false, 11 | required: true, 12 | }) 13 | id: string; 14 | 15 | @property({ 16 | type: 'string', 17 | required: true, 18 | }) 19 | payment_method_id: string; 20 | 21 | @property({ 22 | type: 'number', 23 | required: false, 24 | default: 0 25 | }) 26 | max_installments: number; 27 | 28 | @property({ 29 | type: 'number', 30 | required: false, 31 | default: 0 32 | }) 33 | discount_percentage: number; 34 | 35 | @property({ 36 | type: 'date', 37 | required: true, 38 | }) 39 | created_at: string; 40 | 41 | @property({ 42 | type: 'date', 43 | required: true, 44 | }) 45 | updated_at: string; 46 | 47 | constructor(data?: Partial) { 48 | super(data); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /commerce-shop/src/models/payment-method.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, model, property} from '@loopback/repository'; 2 | 3 | @model() 4 | export class PaymentMethod extends Entity{ 5 | 6 | @property({ 7 | type: 'string', 8 | id: true, 9 | generated: false, 10 | required: true, 11 | }) 12 | id: string; 13 | 14 | @property({ 15 | type: 'string', 16 | required: true, 17 | }) 18 | name: string; 19 | 20 | @property({ 21 | type: 'boolean', 22 | required: true, 23 | }) 24 | allow_installments: boolean; 25 | 26 | @property({ 27 | type: 'date', 28 | required: true, 29 | }) 30 | created_at: string; 31 | 32 | @property({ 33 | type: 'date', 34 | required: true, 35 | }) 36 | updated_at: string; 37 | 38 | constructor(data?: Partial) { 39 | super(data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /commerce-shop/src/models/tenant.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, model, property} from '@loopback/repository'; 2 | 3 | @model() 4 | export class Tenant extends Entity { 5 | 6 | @property({ 7 | type: 'string', 8 | id: true, 9 | generated: false, 10 | required: true, 11 | }) 12 | id: string; 13 | 14 | @property({ 15 | type: 'string', 16 | required: true, 17 | }) 18 | company: string; 19 | 20 | @property({ 21 | type: 'string', 22 | required: true, 23 | }) 24 | site: string; 25 | 26 | @property({ 27 | type: 'string', 28 | required: true, 29 | }) 30 | fallback_subdomain: string; 31 | 32 | @property({ 33 | type: 'date', 34 | required: true, 35 | }) 36 | created_at: string; 37 | 38 | @property({ 39 | type: 'date', 40 | required: true, 41 | }) 42 | updated_at: string; 43 | 44 | constructor(data?: Partial) { 45 | super(data); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/base.repository.ts: -------------------------------------------------------------------------------- 1 | import {DefaultCrudRepository} from '@loopback/repository'; 2 | import {Entity} from "@loopback/repository"; 3 | //@ts-ignore 4 | import {Client} from 'es6'; 5 | 6 | export abstract class BaseRepository< 7 | T extends Entity, 8 | ID, 9 | Relations extends object = {} 10 | > extends DefaultCrudRepository { 13 | 14 | async addMany(id, {relation, data}: { relation: string, data: Array }) { 15 | const document = { 16 | index: this.dataSource.settings.index, 17 | refresh: true, 18 | body: { 19 | query: { 20 | terms: {_id: [id]} 21 | }, 22 | "script": { 23 | "source": ` 24 | if ( !ctx._source.containsKey('${relation}') ) { 25 | ctx._source['${relation}'] = [] 26 | } 27 | for(item in params['${relation}']){ 28 | if(ctx._source['${relation}'].find( i -> i.id == item.id ) == null ) { 29 | ctx._source['${relation}'].add( item ) 30 | } 31 | } 32 | `, 33 | "params": { 34 | [relation]: data 35 | } 36 | }, 37 | }, 38 | }; 39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 40 | const db: Client = (this.dataSource.connector as any).db; 41 | await db.update_by_query(document) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/category/category.repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CategoryRepository } from './category.repository'; 3 | 4 | describe('CategoryRepository', () => { 5 | let service: CategoryRepository; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CategoryRepository], 10 | }).compile(); 11 | 12 | service = module.get(CategoryRepository); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); -------------------------------------------------------------------------------- /commerce-shop/src/repositories/category/category.repository.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, OnModuleInit} from '@nestjs/common'; 2 | import {Category} from "../../models/category.model"; 3 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 4 | import {TenantService} from "../../services/tenant/tenant.service"; 5 | import {BaseRepository} from "../base.repository"; 6 | 7 | @Injectable() 8 | export class CategoryRepository extends BaseRepository< 9 | Category, 10 | typeof Category.prototype.id 11 | > implements OnModuleInit{ 12 | constructor( 13 | dataSource: EsDataSourceService, 14 | private tenantService: TenantService 15 | ) { 16 | super(Category, dataSource); 17 | } 18 | 19 | onModuleInit(): any { 20 | this.tenantService.applyTenantScope(this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './category/category.repository'; 2 | export * from './payment-method/payment-method.repository'; 3 | export * from './payment-method-config/payment-method-config.repository'; 4 | export * from './product/product.repository'; 5 | export * from './tenant/tenant.repository'; 6 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/payment-method-config/payment-method-config.repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PaymentMethodConfigRepository } from './payment-method-config.repository'; 3 | 4 | describe('PaymentMethodConfigRepository', () => { 5 | let service: PaymentMethodConfigRepository; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PaymentMethodConfigRepository], 10 | }).compile(); 11 | 12 | service = module.get(PaymentMethodConfigRepository); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/payment-method-config/payment-method-config.repository.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, OnModuleInit} from '@nestjs/common'; 2 | import {PaymentMethodConfig} from "../../models/payment-method-config.model"; 3 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 4 | import {TenantService} from "../../services/tenant/tenant.service"; 5 | import {BaseRepository} from "../base.repository"; 6 | 7 | @Injectable() 8 | export class PaymentMethodConfigRepository extends BaseRepository< 9 | PaymentMethodConfig, 10 | typeof PaymentMethodConfig.prototype.id 11 | > implements OnModuleInit{ 12 | constructor( 13 | dataSource: EsDataSourceService, 14 | private tenantService: TenantService 15 | ) { 16 | super(PaymentMethodConfig, dataSource); 17 | } 18 | 19 | onModuleInit(): any { 20 | this.tenantService.applyTenantScope(this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/payment-method/payment-method.repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PaymentMethodRepository } from './payment-method.repository'; 3 | 4 | describe('PaymentMethodRepository', () => { 5 | let service: PaymentMethodRepository; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PaymentMethodRepository], 10 | }).compile(); 11 | 12 | service = module.get(PaymentMethodRepository); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/payment-method/payment-method.repository.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import {BaseRepository} from "../base.repository"; 3 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 4 | import {PaymentMethod} from "../../models/payment-method.model"; 5 | 6 | @Injectable() 7 | export class PaymentMethodRepository extends BaseRepository { 9 | constructor( 10 | dataSource: EsDataSourceService, 11 | ) { 12 | super(PaymentMethod, dataSource); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/product/product.repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductRepository } from './product.repository'; 3 | 4 | describe('ProductRepository', () => { 5 | let service: ProductRepository; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProductRepository], 10 | }).compile(); 11 | 12 | service = module.get(ProductRepository); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/product/product.repository.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, OnModuleInit} from '@nestjs/common'; 2 | import {Product} from "../../models/product.model"; 3 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 4 | import {BaseRepository} from "../base.repository"; 5 | import {TenantService} from "../../services"; 6 | 7 | @Injectable() 8 | export class ProductRepository extends BaseRepository< 9 | Product, 10 | typeof Product.prototype.id 11 | > implements OnModuleInit{ 12 | constructor( 13 | dataSource: EsDataSourceService, 14 | private tenantService: TenantService 15 | ) { 16 | super(Product, dataSource); 17 | } 18 | 19 | onModuleInit(): any { 20 | this.tenantService.applyTenantScope(this); 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/tenant/tenant.repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TenantRepository } from './tenant.repository'; 3 | 4 | describe('TenantRepository', () => { 5 | let service: TenantRepository; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TenantRepository], 10 | }).compile(); 11 | 12 | service = module.get(TenantRepository); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/repositories/tenant/tenant.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {Tenant} from "../../models/tenant.model"; 3 | import {EsDataSourceService} from "../../elasticsearch/es-data-source/es-data-source.service"; 4 | import {BaseRepository} from "../base.repository"; 5 | 6 | @Injectable() 7 | export class TenantRepository extends BaseRepository< 8 | Tenant, 9 | typeof Tenant.prototype.id 10 | > { 11 | constructor( 12 | dataSource: EsDataSourceService, 13 | ) { 14 | super(Tenant, dataSource); 15 | } 16 | } 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /commerce-shop/src/services/cart/cart.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CartService } from './cart.service'; 3 | 4 | describe('CartService', () => { 5 | let service: CartService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CartService], 10 | }).compile(); 11 | 12 | service = module.get(CartService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/services/cart/cart.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, Scope} from '@nestjs/common'; 2 | import {Product} from "../../models/product.model"; 3 | import {Exclude, Expose} from 'class-transformer'; 4 | 5 | interface CartItem { 6 | [id: string]: { product: Product, quantity: number } 7 | } 8 | 9 | @Injectable({ 10 | scope: Scope.REQUEST 11 | }) 12 | export class CartService { 13 | 14 | private updated = false; 15 | 16 | @Exclude() 17 | items: CartItem = {} 18 | 19 | add(product: Product) { 20 | this.updated = true; 21 | let quantity = product.id in this.items ? this.items[product.id].quantity : 0; 22 | this.items[product.id] = { 23 | product, 24 | quantity: ++quantity 25 | } 26 | } 27 | 28 | clear() { 29 | this.updated = true; 30 | this.items = {}; 31 | } 32 | 33 | isUpdated(){ 34 | return this.updated; 35 | } 36 | 37 | @Expose() 38 | get countItems(): number { 39 | return Object.keys(this.items).reduce((sum, value) => { 40 | return sum + this.items[value].quantity; 41 | }, 0) 42 | } 43 | 44 | @Expose() 45 | get total(): number { 46 | return Object.keys(this.items).reduce(((sum, value) => { 47 | const item = this.items[value]; 48 | return sum + item.product.price * item.quantity; 49 | }), 0) 50 | } 51 | 52 | @Expose({name: 'items'}) 53 | get enumerateItems(){ 54 | return this.toArray() 55 | } 56 | 57 | toArray() { 58 | return Object.keys(this.items).map(key => this.items[key]) 59 | } 60 | 61 | serialize() { 62 | return Object.keys(this.items).reduce(((obj, value) => { 63 | const item = this.items[value]; 64 | obj[value] = { 65 | product: item.product.toJSON(), 66 | quantity: item.quantity 67 | } 68 | return obj; 69 | }), {}) 70 | } 71 | 72 | deserialize(items) { 73 | this.items = Object.keys(items).reduce(((obj, value) => { 74 | const item = items[value]; 75 | obj[value] = { 76 | product: new Product(item.product), 77 | quantity: item.quantity 78 | } 79 | return obj; 80 | }), {}) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /commerce-shop/src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cart/cart.service'; 2 | export * from './navbar/navbar.service'; 3 | export * from './tenant/tenant.service'; 4 | -------------------------------------------------------------------------------- /commerce-shop/src/services/navbar/navbar.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { NavbarService } from './navbar.service'; 3 | 4 | describe('NavbarService', () => { 5 | let service: NavbarService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [NavbarService], 10 | }).compile(); 11 | 12 | service = module.get(NavbarService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/services/navbar/navbar.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import {CategoryRepository} from "../../repositories/category/category.repository"; 3 | import {PaymentMethodConfigRepository} from "../../repositories/payment-method-config/payment-method-config.repository"; 4 | import {CartService} from "../cart/cart.service"; 5 | import {Request} from "express"; 6 | import {TenantService} from "../tenant/tenant.service"; 7 | 8 | @Injectable() 9 | export class NavbarService { 10 | 11 | 12 | constructor( 13 | private categoryRepo: CategoryRepository, 14 | private paymentMethodConfig: PaymentMethodConfigRepository, 15 | private cartService: CartService, 16 | private tenantService: TenantService 17 | ) { 18 | } 19 | 20 | 21 | async getDependencies(request){ 22 | this.loadCart(request); 23 | return { 24 | domain: { 25 | site: this.tenantService.tenant.site, 26 | fallback_subdomain: this.tenantService.tenant.fallback_subdomain, 27 | }, 28 | // categories: await this.categoryRepo.find({ 29 | // order: ['name.keyword DESC'] 30 | // }), 31 | payMethodConfig: (await this.paymentMethodConfig.find({ 32 | order: ['max_installments DESC'] 33 | }))[0], 34 | // cart: this.cartService 35 | } 36 | 37 | } 38 | 39 | loadCart(request: Request,){ 40 | this.cartService.deserialize(request.session.cart || {}); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /commerce-shop/src/services/tenant/tenant.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TenantService } from './tenant.service'; 3 | 4 | describe('TenantService', () => { 5 | let service: TenantService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TenantService], 10 | }).compile(); 11 | 12 | service = module.get(TenantService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/services/tenant/tenant.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import {TenantRepository} from "../../repositories/tenant/tenant.repository"; 3 | import {Tenant} from "../../models/tenant.model"; 4 | import {DefaultCrudRepository, EntityNotFoundError} from "@loopback/repository"; 5 | import {merge} from 'lodash'; 6 | import {parse} from "psl"; 7 | 8 | @Injectable() 9 | export class TenantService { 10 | 11 | private _tenant: Tenant | null = null; 12 | 13 | constructor( 14 | private tenantRepo: TenantRepository, 15 | ) { 16 | } 17 | 18 | get tenant() { 19 | return this._tenant; 20 | } 21 | 22 | set tenant(tenant: Tenant) { 23 | this._tenant = tenant; 24 | } 25 | 26 | async setTenantBy(site: string) { 27 | const subdomain = site.split('.')[0].replace('-store', '') 28 | const {domain} = parse( 29 | site 30 | ); 31 | const tenant = await this.tenantRepo.findOne({ 32 | where: { 33 | or: [ 34 | {fallback_subdomain: subdomain}, 35 | {site: domain} 36 | ] 37 | } 38 | }); 39 | 40 | if (!tenant) { 41 | throw new EntityNotFoundError(Tenant, site); 42 | } 43 | this.tenant = tenant; 44 | } 45 | 46 | applyTenantScope(repo: DefaultCrudRepository) { 47 | repo.modelClass.observe('access', (ctx, next) => { 48 | if(this.tenant) { 49 | ctx.query.where = merge( 50 | ctx.query.where, 51 | { 52 | and: [ 53 | {tenant_id: this.tenant.id} 54 | ] 55 | }, 56 | ) 57 | } 58 | 59 | next(); 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/category/category-sync.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CategorySyncService } from './category-sync.service'; 3 | 4 | describe('CategorySyncService', () => { 5 | let service: CategorySyncService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CategorySyncService], 10 | }).compile(); 11 | 12 | service = module.get(CategorySyncService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/category/category-sync.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {RabbitSubscribe} from "@golevelup/nestjs-rabbitmq"; 3 | import {CategoryRepository} from "../../repositories/category/category.repository"; 4 | import {BaseModelSyncService} from "../base-model-sync.service"; 5 | import {ModuleRef} from "@nestjs/core"; 6 | 7 | @Injectable() 8 | export class CategorySyncService extends BaseModelSyncService{ 9 | 10 | constructor( 11 | moduleRef: ModuleRef, 12 | private repo: CategoryRepository 13 | ) { 14 | super(moduleRef) 15 | } 16 | 17 | @RabbitSubscribe({ 18 | exchange: 'amq.topic', 19 | routingKey: 'model.category.*', //expressao regular created updated deleted 20 | queue: 'commerce-shop/sync-admin/category' 21 | }) 22 | public async rpcHandler(data, message) { 23 | await this.sync({ 24 | repo: this.repo, 25 | data, 26 | message 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/index.ts: -------------------------------------------------------------------------------- 1 | export * from './category/category-sync.service'; 2 | export * from './payment-method/payment-method-sync.service'; 3 | export * from './payment-method-config/payment-method-config-sync.service'; 4 | export * from './product/product-sync.service'; 5 | export * from './tenant/tenant-sync.service'; 6 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/payment-method-config/payment-method-config-sync.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PaymentMethodConfigSyncService } from './payment-method-config-sync.service'; 3 | 4 | describe('PaymentMethodConfigSyncService', () => { 5 | let service: PaymentMethodConfigSyncService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PaymentMethodConfigSyncService], 10 | }).compile(); 11 | 12 | service = module.get(PaymentMethodConfigSyncService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/payment-method-config/payment-method-config-sync.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {RabbitSubscribe} from "@golevelup/nestjs-rabbitmq"; 3 | import {BaseModelSyncService} from "../base-model-sync.service"; 4 | import {ModuleRef} from "@nestjs/core"; 5 | import {PaymentMethodConfigRepository} from "../../repositories/payment-method-config/payment-method-config.repository"; 6 | 7 | @Injectable() 8 | export class PaymentMethodConfigSyncService extends BaseModelSyncService{ 9 | 10 | constructor( 11 | moduleRef: ModuleRef, 12 | private repo: PaymentMethodConfigRepository 13 | ) { 14 | super(moduleRef) 15 | } 16 | 17 | @RabbitSubscribe({ 18 | exchange: 'amq.topic', 19 | routingKey: 'model.paymentmethodconfig.*', 20 | queue: 'commerce-shop/sync-admin/payment_method_config' 21 | }) 22 | public async rpcHandler(data, message) { 23 | await this.sync({ 24 | repo: this.repo, 25 | data: { 26 | ...data, 27 | payment_method_id: data['payment_method'] 28 | }, 29 | message 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/payment-method/payment-method-sync.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PaymentMethodSyncService } from './payment-method-sync.service'; 3 | 4 | describe('PaymentMethodSyncService', () => { 5 | let service: PaymentMethodSyncService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PaymentMethodSyncService], 10 | }).compile(); 11 | 12 | service = module.get(PaymentMethodSyncService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/payment-method/payment-method-sync.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {RabbitSubscribe} from "@golevelup/nestjs-rabbitmq"; 3 | import {BaseModelSyncService} from "../base-model-sync.service"; 4 | import {ModuleRef} from "@nestjs/core"; 5 | import {PaymentMethodRepository} from "../../repositories"; 6 | @Injectable() 7 | export class PaymentMethodSyncService extends BaseModelSyncService{ 8 | 9 | constructor( 10 | moduleRef: ModuleRef, 11 | private repo: PaymentMethodRepository 12 | ) { 13 | super(moduleRef) 14 | } 15 | 16 | 17 | @RabbitSubscribe({ 18 | exchange: 'amq.topic', 19 | routingKey: 'model.paymentmethod.*', 20 | queue: 'commerce-shop/sync-admin/payment_method' 21 | }) 22 | public async rpcHandler(data, message) { 23 | await this.sync({ 24 | repo: this.repo, 25 | data, 26 | message 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/product/product-sync.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductSyncService } from './product-sync.service'; 3 | 4 | describe('ProductSyncService', () => { 5 | let service: ProductSyncService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProductSyncService], 10 | }).compile(); 11 | 12 | service = module.get(ProductSyncService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/product/product-sync.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {RabbitSubscribe} from "@golevelup/nestjs-rabbitmq"; 3 | import {BaseModelSyncService} from "../base-model-sync.service"; 4 | import {ProductRepository} from "../../repositories/product/product.repository"; 5 | import {pick} from 'lodash'; 6 | import {ModuleRef} from "@nestjs/core"; 7 | @Injectable() 8 | export class ProductSyncService extends BaseModelSyncService{ 9 | 10 | constructor( 11 | moduleRef: ModuleRef, 12 | private repo: ProductRepository 13 | ) { 14 | super(moduleRef) 15 | } 16 | 17 | 18 | @RabbitSubscribe({ 19 | exchange: 'amq.topic', 20 | routingKey: 'model.product.*', 21 | queue: 'commerce-shop/sync-admin/product' 22 | }) 23 | public async rpcHandler(data, message) { 24 | await this.sync({ 25 | repo: this.repo, 26 | data: { 27 | ...pick(data, this.getModelFields(this.repo)), 28 | price: data['sale_price'], 29 | tenant_id: data['tenant'], 30 | count_sales: 1 31 | }, 32 | message 33 | }); 34 | } 35 | 36 | 37 | @RabbitSubscribe({ 38 | exchange: 'amq.topic', 39 | routingKey: 'model.productpaymentmethod.*', 40 | queue: 'commerce-shop/sync-admin/product_payment_method' 41 | }) 42 | public async handlerCategories(data, message) { 43 | // if (this.getAction(message) === 'created') { 44 | // const fields = Object.keys(this.repo.modelClass.definition.properties['payment_methods'].jsonSchema.items.properties) 45 | // const entity = { 46 | // ...pick(data, fields), 47 | // payment_method_id: data['payment_method'] 48 | // } 49 | // console.log(data, entity, fields); 50 | // await this.repo.addMany( 51 | // data.product, 52 | // {relation: 'payment_methods', data: [entity]} 53 | // ) 54 | // } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/tenant/tenant-sync.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TenantSyncService } from './category-sync.service'; 3 | 4 | describe('TenantSyncService', () => { 5 | let service: TenantSyncService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TenantSyncService], 10 | }).compile(); 11 | 12 | service = module.get(TenantSyncService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /commerce-shop/src/sync/tenant/tenant-sync.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {RabbitSubscribe} from "@golevelup/nestjs-rabbitmq"; 3 | import {BaseModelSyncService} from "../base-model-sync.service"; 4 | import {ModuleRef} from "@nestjs/core"; 5 | import {TenantRepository} from "../../repositories/tenant/tenant.repository"; 6 | 7 | @Injectable() 8 | export class TenantSyncService extends BaseModelSyncService{ 9 | 10 | constructor( 11 | moduleRef: ModuleRef, 12 | private repo: TenantRepository 13 | ) { 14 | super(moduleRef) 15 | } 16 | 17 | @RabbitSubscribe({ 18 | exchange: 'amq.topic', 19 | routingKey: 'model.tenant.*', 20 | queue: 'commerce-shop/sync-admin/tenant' 21 | }) 22 | public async rpcHandler(data, message) { 23 | await this.sync({ 24 | repo: this.repo, 25 | data, 26 | message 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /commerce-shop/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /commerce-shop/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /commerce-shop/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /commerce-shop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true, 13 | "skipLibCheck": true 14 | }, 15 | "exclude": ["node_modules", "dist"] 16 | } 17 | -------------------------------------------------------------------------------- /commerce-shop/views/checkout.hbs: -------------------------------------------------------------------------------- 1 | {{#> layouts/layout}} 2 | {{#*inline "content"}} 3 |
4 | {{/inline}} 5 | {{#*inline "scripts"}} 6 | 7 | {{/inline}} 8 | {{/layouts/layout}} 9 | 10 | -------------------------------------------------------------------------------- /commerce-shop/views/checkout/payment-success.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | {{#gt (length oldCartItems) 0}} 7 |

Compra finalizada com sucesso

8 |

Resumo do pedido

9 | {{#forEach oldCartItems}} 10 | {{#with product}} 11 |
12 |
13 | {{name}} 14 |
15 |
16 |

{{name}}

17 | Código do produto: {{uppercase (truncate id 7)}} 18 | Quant.: {{../quantity}} 19 |
20 |

R${{price}}

21 |
22 | {{/with}} 23 | {{/forEach}} 24 | {{else}} 25 | Clique aqui para voltar para a área principal 26 | {{/gt}} 27 |
28 |
29 |
30 |
31 | 32 |
33 | -------------------------------------------------------------------------------- /commerce-shop/views/layouts/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Code Shop | Maratona Full Cycle 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | {{> partials/navbar }} 28 | 29 | {{{body}}} 30 | {{#> content}}{{/content}} 31 | 32 | {{> partials/footer }} 33 | 34 | {{#> scripts}}{{/scripts}} 35 | 36 | 37 | -------------------------------------------------------------------------------- /commerce-shop/views/partials/footer.hbs: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /commerce-shop/webpack.mix.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const mix = require('laravel-mix'); 3 | 4 | mix 5 | .js('./frontend/js/main.js', './public/js') 6 | .react('./frontend/js/checkout.js', './public/js') 7 | .sass('./frontend/scss/main.scss', './public/css') 8 | .options({ 9 | processCssUrls: false, 10 | }) 11 | .copy('./frontend/img', './public/img'); 12 | 13 | 14 | if (mix.inProduction()) { 15 | mix.version(); 16 | } else { 17 | mix.sourceMaps(); 18 | } 19 | -------------------------------------------------------------------------------- /k8s/admin/redis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: admin-redis 5 | labels: 6 | app: admin-redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: admin-redis 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | app: admin-redis 16 | spec: 17 | containers: 18 | - name: master 19 | image: redis 20 | # resources: 21 | # requests: 22 | # cpu: 100m 23 | # memory: 100Mi 24 | ports: 25 | - containerPort: 6379 26 | 27 | --- 28 | 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: admin-redis-service 33 | labels: 34 | app: admin-redis 35 | spec: 36 | ports: 37 | - port: 6379 38 | targetPort: 6379 39 | selector: 40 | app: admin-redis -------------------------------------------------------------------------------- /k8s/loja/loja.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: shop-conf 5 | labels: 6 | name: shop-conf 7 | data: 8 | env: | 9 | DEBUG=elasticsearch,loopback:connector:elasticsearch,loopback:core:application 10 | APP_ENV=dev 11 | SESSION_SECRET_KEY=fullcycle.com.br 12 | ELASTIC_SEARCH_HOST=http://elasticsearch-master:9200 13 | ELASTIC_SEARCH_REQUEST_TIMEOUT=30000 14 | ELASTIC_SEARCH_PING_TIMEOUT=3000 15 | 16 | RABBITMQ_URI=amqp://user:2WQwGFMkNb@rabbitmq:5672 17 | 18 | #Local colocar o endereço do Django 19 | MIX_ASSETS_URL=http://admin.loja.maratona.fullcycle.com.br 20 | MIX_MAIN_DOMAIN=loja.maratona.fullcycle.com.br 21 | MIX_DEV_ADMIN_PORT= 22 | REDIS_URI=redis://admin-redis-service:6379/1 23 | 24 | --- 25 | 26 | apiVersion: apps/v1 27 | kind: Deployment 28 | metadata: 29 | name: shop 30 | spec: 31 | replicas: 3 32 | selector: 33 | matchLabels: 34 | app: shop 35 | template: 36 | metadata: 37 | labels: 38 | app: shop 39 | version: "1.0" 40 | spec: 41 | containers: 42 | - name: shop 43 | image: wesleywillians/maratonafc3-shop 44 | ports: 45 | - containerPort: 3000 46 | envFrom: 47 | - configMapRef: 48 | name: shop-conf 49 | volumeMounts: 50 | - name: shop-conf 51 | subPath: .env 52 | mountPath: /home/node/app/.env 53 | 54 | volumes: 55 | - name: shop-conf 56 | configMap: 57 | name: shop-conf 58 | items: 59 | - key: env 60 | path: .env 61 | 62 | --- 63 | 64 | apiVersion: v1 65 | kind: Service 66 | metadata: 67 | name: shop-service 68 | labels: 69 | app: shop 70 | spec: 71 | type: ClusterIP 72 | ports: 73 | - protocol: TCP 74 | name: http-svc 75 | port: 3000 76 | selector: 77 | app: shop 78 | 79 | --- 80 | 81 | apiVersion: networking.istio.io/v1alpha3 82 | kind: VirtualService 83 | metadata: 84 | name: shop-vs 85 | spec: 86 | hosts: 87 | - "*" 88 | gateways: 89 | - admin-gateway 90 | http: 91 | - name: "shop" 92 | match: 93 | - uri: 94 | prefix: / 95 | port: 80 96 | route: 97 | - destination: 98 | port: 99 | number: 3000 100 | host: shop-service -------------------------------------------------------------------------------- /k8s/rabbitmq/rabbitmq.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rabbitmq 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: rabbitmq 9 | 10 | template: 11 | metadata: 12 | labels: 13 | app: rabbitmq 14 | spec: 15 | containers: 16 | - name: rabbitmq 17 | image: "rabbitmq:3-management" 18 | ports: 19 | - containerPort: 15672 20 | - containerPort: 5672 21 | env: 22 | - name: RABBITMQ_ERLANG_COOKIE 23 | value: "SWQOKODSQALRPCLNMEQG" 24 | - name: RABBITMQ_DEFAULT_USER 25 | value: "rabbitmq" 26 | - name: RABBITMQ_DEFAULT_PASS 27 | value: "rabbitmq" 28 | - name: RABBITMQ_DEFAULT_VHOST 29 | value: "/" 30 | 31 | --- 32 | 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: rabbitmq-service 37 | labels: 38 | app: rabbitmq-service 39 | spec: 40 | type: LoadBalancer 41 | ports: 42 | - name: http 43 | protocol: TCP 44 | port: 15672 45 | nodePort: 30081 46 | - name: amqp 47 | protocol: TCP 48 | port: 5672 49 | nodePort: 30082 50 | selector: 51 | app: rabbitmq 52 | -------------------------------------------------------------------------------- /rabbitmq/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | rabbitmq_data -------------------------------------------------------------------------------- /rabbitmq/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | rabbitmq: 5 | image: 'rabbitmq:3.8-management-alpine' 6 | container_name: commerce-rabbitmq 7 | hostname: rabbitmq 8 | ports: 9 | - "15672:15672" 10 | - "5672:5672" 11 | volumes: 12 | - './rabbitmq_data:/var/lib/rabbitmq/mnesia' 13 | environment: 14 | - RABBITMQ_DEFAULT_USER=admin 15 | - RABBITMQ_DEFAULT_PASS=admin 16 | networks: 17 | - code-commerce 18 | 19 | networks: 20 | code-commerce: 21 | driver: bridge -------------------------------------------------------------------------------- /redis/.gitignore: -------------------------------------------------------------------------------- 1 | redisdata/ -------------------------------------------------------------------------------- /redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | redis: 6 | image: redis:alpine 7 | container_name: commerce-redis 8 | volumes: 9 | - ./redisdata:/data 10 | ports: 11 | - 6379:6379 12 | networks: 13 | - code-commerce 14 | 15 | networks: 16 | code-commerce: 17 | driver: bridge --------------------------------------------------------------------------------