{item.product.name}
37 | Código do produto: {item.product.id.substring(0, 7)} 38 | Quant.: {item.quantity} 39 |R$ {item.product.toFixed(2)}
41 |├── 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 |
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 | 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 | 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 |Resumo do pedido
28 | { 29 | cart.items.map(item => ( 30 |R$ {item.product.toFixed(2)}
41 |