diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59c0cc7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+# Created by .ignore support plugin (hsz.mobi)
+coverage/
+vendor/
+composer.lock
diff --git a/Api/Data/ProductInterface.php b/Api/Data/ProductInterface.php
new file mode 100644
index 0000000..2ae4262
--- /dev/null
+++ b/Api/Data/ProductInterface.php
@@ -0,0 +1,13 @@
+bestsellers = $bestsellers;
+ $this->products = $products;
+ $this->storeManager = $storeManager;
+ $this->rating = $rating;
+ $this->ratingAggregated = $ratingAggregated;
+ }
+
+ /**
+ * Get items from report
+ *
+ * @api
+ *
+ * @param string $type Type of source
+ * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria
+ *
+ * @return \OxCom\MagentoTopProducts\Api\Data\ProductSearchResultsInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function getList($type, ProductSearchCriteriaInterface $searchCriteria = null)
+ {
+ $allowed = [static::FILTER_TYPE_TOP_SELLING, static::FILTER_TYPE_TOP_FREE, static::FILTER_TYPE_TOP_RATED];
+ $type = mb_strtolower($type);
+
+ if (empty($searchCriteria)) {
+ $searchCriteria = new ProductSearchCriteria();
+ }
+
+ switch ($type) {
+ case static::FILTER_TYPE_TOP_SELLING:
+ $result = $this->getBestsellers('gt', $searchCriteria);
+ break;
+
+ case static::FILTER_TYPE_TOP_FREE:
+ $result = $this->getBestsellers('eq', $searchCriteria);
+ break;
+
+ case static::FILTER_TYPE_TOP_RATED:
+ $result = $this->getRatedProducts($searchCriteria);
+ break;
+
+ default:
+ $allowed = implode(', ', $allowed);
+ $phrase = __('Requested type "%s" doesn\'t exist. Allowed: %s', $type, $allowed);
+ throw new \Magento\Framework\Exception\InputException($phrase);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $condition - price filter
+ * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria
+ *
+ * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults
+ */
+ protected function getBestsellers($condition, ProductSearchCriteriaInterface $searchCriteria)
+ {
+ $pageSize = (int)$searchCriteria->getPageSize();
+ $page = (int)$searchCriteria->getCurrentPage();
+
+ $this->bestsellers
+ ->clear()
+ ->distinct(true)
+ ->setPeriod($searchCriteria->getPeriod())
+ ->setPageSize($pageSize)
+ ->setCurPage($page)
+ ->addStoreRestrictions($this->storeManager->getStore()->getId());
+
+ $this->bestsellers->addFieldToFilter('product_price', [$condition => 0]);
+
+ /** @var \Magento\Reports\Model\Item[] $items */
+ $items = $this->bestsellers->walk(function ($item) {
+ /** @var \Magento\Reports\Model\Item $item */
+ $productId = $item->getData('product_id');
+ $product = $this->products->getById($productId);
+
+ return $product;
+ });
+
+ $result = $this->process($items, $searchCriteria, $this->bestsellers->getSize());
+
+ return $result;
+ }
+
+ /**
+ * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria
+ *
+ * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults
+ */
+ public function getRatedProducts(ProductSearchCriteriaInterface $searchCriteria)
+ {
+ $pageSize = (int)$searchCriteria->getPageSize();
+ $page = (int)$searchCriteria->getCurrentPage();
+ $storeId = (int)$this->storeManager->getStore()->getId();
+
+ $this->ratingAggregated
+ ->clear()
+ ->setPageSize($pageSize)
+ ->setCurPage($page)
+ ->addFieldToFilter('store_id', ['eq' => $storeId]);
+
+ $code = $searchCriteria->getRatingCode();
+ if (!empty($code)) {
+ $rating = $this->rating->getItemByColumnValue('rating_code', $code);
+
+ if (!empty($rating)) {
+ // there is something like we are searching
+ $id = $rating->getData('rating_id');
+ $this->ratingAggregated->addFieldToFilter('rating_id', ['eq' => $id]);
+ }
+ }
+
+ $this->ratingAggregated
+ ->addOrder('percent', Collection::SORT_ORDER_DESC)
+ ->addOrder('vote_value_sum', Collection::SORT_ORDER_DESC);
+
+ /** @var \Magento\Reports\Model\Item[] $items */
+ $items = $this->ratingAggregated->walk(function ($item) {
+ /** @var \Magento\Reports\Model\Item $item */
+ $productId = $item->getData('entity_pk_value');
+ $product = $this->products->getById($productId);
+
+ return $product;
+ });
+
+ $result = $this->process($items, $searchCriteria, $this->ratingAggregated->getSize());
+
+ return $result;
+ }
+
+ /**
+ * Prepare response
+ *
+ * @param $items
+ * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria
+ * @param $size
+ *
+ * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults
+ */
+ protected function process($items, ProductSearchCriteriaInterface $searchCriteria, $size)
+ {
+ $result = new ProductSearchResults();
+ $result->setItems($items)
+ ->setSearchCriteria($searchCriteria)
+ ->setTotalCount($size);
+
+ return $result;
+ }
+}
diff --git a/Model/ProductSearchCriteria.php b/Model/ProductSearchCriteria.php
new file mode 100644
index 0000000..e038c65
--- /dev/null
+++ b/Model/ProductSearchCriteria.php
@@ -0,0 +1,83 @@
+period = $period;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPeriod()
+ {
+ return $this->period;
+ }
+
+ /**
+ * @param string $code Raging code filter
+ *
+ * @return $this
+ */
+ public function setRatingCode($code = null)
+ {
+ $this->ratingCode = $code;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRatingCode()
+ {
+ return $this->ratingCode;
+ }
+}
diff --git a/Model/ProductSearchResults.php b/Model/ProductSearchResults.php
new file mode 100644
index 0000000..1188520
--- /dev/null
+++ b/Model/ProductSearchResults.php
@@ -0,0 +1,84 @@
+_get(self::KEY_ITEMS) === null ? [] : $this->_get(self::KEY_ITEMS);
+ }
+
+ /**
+ * Set items
+ *
+ * @param \OxCom\MagentoTopProducts\Api\Data\ProductInterface[] $items
+ *
+ * @return $this
+ */
+ public function setItems(array $items)
+ {
+ return $this->setData(self::KEY_ITEMS, $items);
+ }
+
+ /**
+ * Get search criteria
+ *
+ * @return \OxCom\MagentoTopProducts\Model\ProductSearchCriteria
+ */
+ public function getSearchCriteria()
+ {
+ return $this->_get(self::KEY_SEARCH_CRITERIA);
+ }
+
+ /**
+ * Set search criteria
+ *
+ * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
+ *
+ * @return $this
+ */
+ public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
+ {
+ return $this->setData(self::KEY_SEARCH_CRITERIA, $searchCriteria);
+ }
+
+ /**
+ * Get total count
+ *
+ * @return int
+ */
+ public function getTotalCount()
+ {
+ return $this->_get(self::KEY_TOTAL_COUNT);
+ }
+
+ /**
+ * Set total count
+ *
+ * @param int $count
+ *
+ * @return $this
+ */
+ public function setTotalCount($count)
+ {
+ return $this->setData(self::KEY_TOTAL_COUNT, $count);
+ }
+}
diff --git a/Model/Rating/Option/Aggregated.php b/Model/Rating/Option/Aggregated.php
new file mode 100644
index 0000000..e770c69
--- /dev/null
+++ b/Model/Rating/Option/Aggregated.php
@@ -0,0 +1,23 @@
+_init(AggregatedResourceModel::class);
+ }
+}
diff --git a/Model/ResourceModel/Rating/Option/Aggregated.php b/Model/ResourceModel/Rating/Option/Aggregated.php
new file mode 100644
index 0000000..1e36225
--- /dev/null
+++ b/Model/ResourceModel/Rating/Option/Aggregated.php
@@ -0,0 +1,21 @@
+_init('rating_option_vote_aggregated', 'primary_id');
+ }
+}
diff --git a/Model/ResourceModel/Rating/Option/Aggregated/Collection.php b/Model/ResourceModel/Rating/Option/Aggregated/Collection.php
new file mode 100644
index 0000000..64c82ab
--- /dev/null
+++ b/Model/ResourceModel/Rating/Option/Aggregated/Collection.php
@@ -0,0 +1,24 @@
+_init(
+ \OxCom\MagentoTopProducts\Model\Rating\Option\Aggregated::class,
+ \OxCom\MagentoTopProducts\Model\ResourceModel\Rating\Option\Aggregated::class
+ );
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9b54c52
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# Magento2 Top Products API
+
+This is a module that extends Magento2 API to get list of TOP products by next filters:
+ - TOP selling products
+ - TOP Free products
+ - TOP Rated products
+
+Top selling and free products are fetching from Magento2 reports.
+
+Top rated products are fetching from Rates module and results are based on aggregated data.
+
+## Install
+```bash
+$ composer require oxcom/magento2-top-products
+$ bin/magento module:enable OxCom_MagentoTopProducts
+$ bin/magento setup:upgrade
+$ bin/magento setup:di:compile
+```
+
+## API requests
+```GET /V1/products/top/{type}``` - Get list of top products by type.
+Where ```type``` can be:
+- **selling** - TOP selling products
+- **free** - TOP Free products
+- **rated** - TOP Rated products
+
+###### Search criteria params
+**pageSize** - Page size
+
+**currentPage** - Current page
+
+**period** - filter by period. This options is related only for ```selling``` or ```free``` type. Possible values are:
+- yearly - annual report
+- monthly - monthly report
+- daily - daily report
+
+**ratingCode** - filter by rating type. This options is related ony for ```rated``` type. Possible values can be found in ```rating``` table.
+
+## Dependencies
+This module is using exists functionality of next modules:
+- **magento/module-catalog**
+- **magento/module-review**
+- **magento/module-sales**
+
+## Bugs and Issues
+Please, if You found a bug or something, that is not working properly, contact me and tell what's wrong. It's nice to have an example how to reproduce a bug, or any idea how to fix it in Your request. I'll take care about it ASAP.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ae54d37
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "oxcom/magento2-top-products",
+ "description": "This is a module that extends Magento2 API to get list of TOP products.",
+ "type": "magento2-module",
+ "license": "LGPL-3.0",
+ "version": "1.0.0",
+ "keywords": [
+ "magento2",
+ "top products",
+ "api"
+ ],
+ "homepage": "https://github.com/OxCom/magento2-top-products",
+ "require": {
+ "php": ">=5.6",
+ "magento/module-config": "100.0.*|100.1.*|101.0.*",
+ "magento/module-store": "100.0.*|100.1.*|100.2.*",
+ "magento/module-backend": "100.0.*|100.1.*|100.2.*",
+ "magento/module-directory": "100.0.*|100.1.*|100.2.*",
+ "magento/framework": "100.0.*|100.1.*|101.0.*"
+ },
+ "require-dev": {
+ "magento/zendframework1": "1.12.*|1.13.*",
+ "phpunit/phpunit": "5.3.5",
+ "squizlabs/php_codesniffer": "^3.1",
+ "sebastian/phpcpd": "^3.0"
+ },
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "OxCom\\MagentoTopProducts\\": ""
+ }
+ },
+ "autoload-dev": {
+ "classmap": [
+ ]
+ },
+ "authors": [
+ {
+ "name": "OxCom",
+ "email": "lancer.oxcom@gmail.com"
+ }
+ ],
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://repo.magento.com/"
+ }
+ ]
+}
diff --git a/etc/di.xml b/etc/di.xml
new file mode 100644
index 0000000..90e9928
--- /dev/null
+++ b/etc/di.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/etc/module.xml b/etc/module.xml
new file mode 100644
index 0000000..37ce748
--- /dev/null
+++ b/etc/module.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/etc/webapi.xml b/etc/webapi.xml
new file mode 100644
index 0000000..206e2a0
--- /dev/null
+++ b/etc/webapi.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/registration.php b/registration.php
new file mode 100644
index 0000000..877499d
--- /dev/null
+++ b/registration.php
@@ -0,0 +1,6 @@
+