This repository has been archived by the owner on Mar 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
authentication via Environment Variables
- Loading branch information
Waldemar Lammert
committed
Nov 20, 2019
1 parent
10ab410
commit 5a47008
Showing
10 changed files
with
453 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.gradle | ||
.idea | ||
|
||
build | ||
*.log | ||
build/** | ||
out/** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
FROM gcr.io/distroless/java:11-debug | ||
|
||
COPY build/libs/bk_amazon_sync.jar /sync.jar | ||
|
||
ENTRYPOINT ["java","-jar","/sync.jar"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,31 @@ | ||
/* | ||
* This file was generated by the Gradle 'init' task. | ||
* | ||
* This generated file contains a sample Java Library project to get you started. | ||
* For more details take a look at the Java Libraries chapter in the Gradle | ||
* User Manual available at https://docs.gradle.org/5.5/userguide/java_library_plugin.html | ||
*/ | ||
|
||
plugins { | ||
// Apply the java-library plugin to add support for Java Library | ||
id 'java-library' | ||
id 'java' | ||
} | ||
|
||
repositories { | ||
// Use jcenter for resolving dependencies. | ||
// You can declare any Maven/Ivy/file repository here. | ||
jcenter() | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
// This dependency is exported to consumers, that is to say found on their compile classpath. | ||
api 'org.apache.commons:commons-math3:3.6.1' | ||
|
||
// This dependency is used internally, and not exposed to consumers on their own compile classpath. | ||
implementation 'com.google.guava:guava:27.1-jre' | ||
|
||
// Use JUnit Jupiter API for testing. | ||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2' | ||
implementation 'com.squareup.okhttp3:okhttp:4.2.1' | ||
implementation 'org.json:json:20190722' | ||
implementation 'com.google.code.gson:gson:2.8.6' | ||
implementation 'org.apache.logging.log4j:log4j-api:2.12.1' | ||
implementation 'org.apache.logging.log4j:log4j-core:2.12.0' | ||
|
||
// Use JUnit Jupiter Engine for testing. | ||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2' | ||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.1' | ||
testImplementation 'org.hamcrest:hamcrest-library:2.1' | ||
} | ||
|
||
test { | ||
// Use junit platform for unit tests | ||
useJUnitPlatform() | ||
} | ||
|
||
jar { | ||
manifest { | ||
attributes 'Main-Class': 'eu.dev089.BkUpdaterApplication' | ||
} | ||
from { | ||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1 @@ | ||
/* | ||
* This file was generated by the Gradle 'init' task. | ||
* | ||
* The settings file is used to specify which projects to include in your build. | ||
* | ||
* Detailed information about configuring a multi-project build in Gradle can be found | ||
* in the user manual at https://docs.gradle.org/5.5/userguide/multi_project_builds.html | ||
*/ | ||
|
||
rootProject.name = 'bk_amazon_sync' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package eu.dev089; | ||
|
||
import eu.dev089.components.Updater; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
public class BkUpdaterApplication { | ||
|
||
public static void main(String[] args) { | ||
var updater = new Updater(); | ||
var scheduler = Executors.newSingleThreadScheduledExecutor(); | ||
scheduler.scheduleWithFixedDelay(updater, 1, 5, TimeUnit.MINUTES); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package eu.dev089.components; | ||
|
||
import eu.dev089.model.Product; | ||
import okhttp3.MediaType; | ||
import okhttp3.Request; | ||
import okhttp3.RequestBody; | ||
|
||
public class RequestCreator { | ||
|
||
private static final String URL = "https://www.bienenkorb-shop.de/index.php/rest/V1/"; | ||
|
||
private Request.Builder requestBuilder(String contextPath) { | ||
var bk_token = System.getenv().get("BK_TOKEN"); | ||
if (bk_token == null || bk_token.isEmpty()) { | ||
throw new IllegalArgumentException("please provide a bearer token via ENV"); | ||
} | ||
return new Request.Builder() | ||
.url(URL + contextPath) | ||
.addHeader("Authorization", String.format("Bearer %s", bk_token)) | ||
.addHeader("Cache-Control", "no-cache") | ||
.addHeader("Connection", "keep-alive"); | ||
} | ||
|
||
public Request getActiveProductSkus() { | ||
var query = "searchCriteria[filter_groups][0][filters][0][field]=status&searchCriteria[filter_groups][0][filters][0][value]=1&searchCriteria[filter_groups][0][filters][0][condition_type]=finset&fields=items[sku]"; | ||
return requestBuilder("products" + "?" + query).get().build(); | ||
} | ||
|
||
public Request get100SkuForPage(int page) { | ||
var query = | ||
"searchCriteria[filter_groups][0][filters][0][field]=status&searchCriteria[filter_groups][0][filters][0][value]=1&searchCriteria[filter_groups][0][filters][0][condition_type]=finset&fields=items[sku]&searchCriteria[current_page]=" | ||
+ String.valueOf(page) + "&searchCriteria[page_size]=500"; | ||
return requestBuilder("products" + "?" + query).get().build(); | ||
} | ||
|
||
public Request getAmazonCategoryProducts() { | ||
var query = "searchCriteria[filterGroups][0][filters][0][field]=category_id& searchCriteria[filterGroups][0][filters][0][value]=2927&searchCriteria[filterGroups][0][filters][0][conditionType]=eq&fields=items[sku,name]"; | ||
return requestBuilder("products" + "?" + query).get().build(); | ||
} | ||
|
||
public Request getAmazonCategoryProducts(int page) { | ||
var query = | ||
"searchCriteria[filterGroups][0][filters][0][field]=category_id& searchCriteria[filterGroups][0][filters][0][value]=2927&searchCriteria[filterGroups][0][filters][0][conditionType]=eq&fields=items[sku,name]&searchCriteria[current_page]=" | ||
+ page + "&searchCriteria[page_size]=200"; | ||
return requestBuilder("products" + "?" + query).get().build(); | ||
} | ||
|
||
public Request getFirst100ForTesting() { | ||
var request = | ||
"products?searchCriteria[filterGroups][0][filters][0][field]=category_id& searchCriteria[filterGroups][0][filters][0][value]=2927&searchCriteria[filterGroups][0][filters][0][conditionType]=eq&fields=items[sku,name]&searchCriteria[current_page]=1&searchCriteria[page_size]=100"; | ||
return requestBuilder(request).get().build(); | ||
} | ||
|
||
public Request updateProductRequest(Product product) { | ||
var anotherJson = String.format("{\n" | ||
+ " \"product\": {\n" | ||
+ " \t\"sku\": \"%s\",\n" | ||
+ " \"custom_attributes\": [\n" | ||
+ " {\n" | ||
+ " \"attribute_code\": \"amazon_qty\",\n" | ||
+ " \"value\": \"%d\"\n" | ||
+ " },{\n" | ||
+ " \"attribute_code\": \"delivery\",\n" | ||
+ " \"value\": \"%d\"\n" | ||
+ " }\n" | ||
+ " ]\n" | ||
+ " },\n" | ||
+ " \"saveOptions\": true\n" | ||
+ "}", product.getSku(), product.getAmazonQuantity(), product.getAmazonDelivery()); | ||
|
||
var body = RequestBody.create(anotherJson, MediaType.parse("application/json")); | ||
return requestBuilder(String.format("products/%s/", product.getSku())).put(body).build(); | ||
} | ||
|
||
public Request getProduktDataForSku(String sku) { | ||
var query = "?fields=sku,name,extension_attributes[stock_item[qty]],custom_attributes"; | ||
return requestBuilder(String.format("products/%s/%s", sku, query)).get().build(); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package eu.dev089.components; | ||
|
||
import static eu.dev089.model.Product.STANDARD_AMAZON_QTY; | ||
|
||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParser; | ||
import eu.dev089.model.Product; | ||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.TimerTask; | ||
import java.util.concurrent.TimeUnit; | ||
import okhttp3.ConnectionPool; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.OkHttpClient.Builder; | ||
import okhttp3.Request; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
|
||
public class Updater extends TimerTask { | ||
|
||
private static final Logger LOGGER = LogManager.getLogger(Updater.class); | ||
private final RequestCreator creator; | ||
private ConnectionPool pool = new ConnectionPool(10, 30, TimeUnit.SECONDS); | ||
private OkHttpClient client; | ||
|
||
public Updater() { | ||
this.creator = new RequestCreator(); | ||
|
||
} | ||
|
||
@Override | ||
public void run() { | ||
this.client = new Builder() | ||
.connectionPool(pool) | ||
.readTimeout(300, TimeUnit.SECONDS) | ||
.build(); | ||
var previousLastsku = ""; | ||
var process = true; | ||
var page = 1; | ||
var productsToUpdate = new ArrayList<Product>(); | ||
while (process) { | ||
var jsonObject = readResponse(creator.getAmazonCategoryProducts(page)); | ||
if (jsonObject.isPresent()) { | ||
var items = jsonObject.get().get("items").getAsJsonArray(); | ||
var skus = new ArrayList<String>(); | ||
for (JsonElement item : items) { | ||
String sku = item.getAsJsonObject().get("sku").getAsString(); | ||
if (!sku.contains("/")) { | ||
skus.add(sku); | ||
} | ||
} | ||
var lastSku = skus.get(skus.size() - 1); | ||
if (!previousLastsku.equals(lastSku)) { | ||
previousLastsku = lastSku; | ||
for (String sku : skus) { | ||
Optional<Product> product = processSku(sku); | ||
product.ifPresent(productsToUpdate::add); | ||
} | ||
page = page + 1; | ||
} else { | ||
process = false; | ||
} | ||
} else { | ||
process = false; | ||
} | ||
} | ||
updateProducts(productsToUpdate); | ||
client.dispatcher().cancelAll(); | ||
client.connectionPool().evictAll(); | ||
} | ||
|
||
private void updateProducts(List<Product> updatableProducts) { | ||
if (!updatableProducts.isEmpty()) { | ||
for (var product : updatableProducts) { | ||
var request = creator.updateProductRequest(product); | ||
var response = readResponse(request); | ||
if (response.isPresent()) { | ||
if (product.getAmazonQuantity() == STANDARD_AMAZON_QTY) { | ||
LOGGER.info("Successfully updated sold out product {}, amazonQuantity:{}, amazonDelivery:{}", | ||
product.getSku(), product.getAmazonQuantity(), product.getAmazonDelivery()); | ||
} else { | ||
LOGGER.info("Successfully updated restocked product {}, amazonQuantity:{}, amazonDelivery:{}", | ||
product.getSku(), product.getAmazonQuantity(), product.getAmazonDelivery()); | ||
} | ||
} else { | ||
LOGGER.error("could not process request - pleaze check: {}", request); | ||
} | ||
} | ||
LOGGER.debug("Upadted {} products successfully", updatableProducts.size()); | ||
} | ||
} | ||
|
||
private Optional<Product> processSku(String sku) { | ||
var jsonObject = readResponse(creator.getProduktDataForSku(sku)); | ||
if (jsonObject.isPresent()) { | ||
var product = Product.builder() | ||
.setSku(sku) | ||
.setJson(jsonObject.get()) | ||
.build(); | ||
if (product.isUpdatable()) { | ||
return Optional.of(product); | ||
} | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
private Optional<JsonObject> readResponse(Request request) { | ||
Optional<JsonObject> jsonObject = Optional.empty(); | ||
String body = null; | ||
try { | ||
var response = client.newCall(request).execute(); | ||
if (response.isSuccessful()) { | ||
body = response.body().string(); | ||
jsonObject = Optional.of(JsonParser.parseString(body).getAsJsonObject()); | ||
} else { | ||
LOGGER.error("Bienenkorb responded with responseCode {} for request {}", response.code(), | ||
request.toString()); | ||
} | ||
} catch (IOException e) { | ||
LOGGER.error("Unparsable answer from Bienenkorb: {}", body, e); | ||
} catch (NullPointerException e) { | ||
LOGGER.error("Empty answer from Bienenkorb: {}", e.getMessage(), e); | ||
} catch (Exception e) { | ||
LOGGER.error("Error returned during call to Bienenkorb: {}", e.getMessage(), e); | ||
} | ||
|
||
return Optional.of(jsonObject).orElse(Optional.empty()); | ||
} | ||
} |
Oops, something went wrong.