#AWS #S3 upload with C# and HttpPostedFile

April 24, 2017 Leave a comment

Amazon-S3-outage-and-AWS-status

Uploading a file to AWS S3 via C# has been documented a million times, but I thought I’d put my own version here, a simple upload from a HTML page, a bit of ajax, and an ASPX file to upload the file to S3, and return a unique file name.

Firstly, you need to include the Nuget package with Install-Package AWSSDK.S3

Here’s the javascript:

$(document).ready(function(e) {
$(“#uploadimage”).on(‘submit’,
(function(e) {
e.preventDefault();
$.ajax({
url: “/api/imageUpload.aspx”, // Url to which the request is sent
type: “POST”, // Type of request to be send, called as method
data: new
FormData(
this), // Data sent to server, a set of key/value pairs (i.e. form fields and values)
contentType: false, // The content type used when sending data to the server.
cache: false, // To unable request pages to be cached
processData: false, // To send DOMDocument or non processed data file it is set to false
success: function(data) // A function to be called if request succeeds
{
$(“#previewing”).attr(“src”,data);
}
});}));});

Then, here is the api/imageUpload.aspx file codebehind (with keys removed)

var MyFiles = Request.Files;
for (int l = 0; l < MyFiles.Count; l++)
{
if (MyFiles.GetKey(l) != “file”) continue;
var file = MyFiles.Get(“file”);
var strExt = Path.GetExtension(file.FileName);
var filename = Guid.NewGuid().ToString(“D”) + strExt;
var cfg = new AmazonS3Config {RegionEndpoint = Amazon.RegionEndpoint.EUWest1};
const string bucketName = “xxxxxx”;
var s3Client = new AmazonS3Client(“xxxxx”, “xxxxx”, cfg);
var fileTransferUtility = new TransferUtility(s3Client);
var fileTransferUtilityRequest = new TransferUtilityUploadRequest
{
BucketName = bucketName,
InputStream = file.InputStream,
StorageClass = S3StorageClass.ReducedRedundancy,
Key = filename,
CannedACL = S3CannedACL.PublicRead
};
fileTransferUtility.Upload(fileTransferUtilityRequest);
Response.Write(“https://s3-eu-west-1.amazonaws.com/xxxxxx/&#8221; + filename);
}

Categories: Uncategorized

Bing Image search using #Cognitive Search

April 24, 2017 2 comments

bing speech

The Bing Image search based on the DataMarket service by Microsoft is closing down, and it is moving to cognitive services. This means that if you have code that uses an endpoint like this;

https://api.datamarket.azure.com/Bing/Search/v1/Image

It needs to be changed to code like this:

https://api.cognitive.microsoft.com/bing/v5.0/images/search

And your quote is reduced to 1,000 per month… 😦

Here’s some code I’ve written in C# to do a basic image search

public static List<String> GetImages(string searchText)
{
var strUrl = “https://api.cognitive.microsoft.com/bing/v5.0/images/search?q=&#8221; + searchText + “&count=1000”;
var wc = new WebClient();
wc.Headers[“Ocp-Apim-Subscription-Key”] = “xxxxxxxxxxx”;
var strJson = wc.DownloadString(strUrl);
var bing = JavascriptDeserialize<BingSearchObject>(strJson);
var lResults = new List<string>();
foreach (var result in bing.value)
{
var qs = HttpUtility.ParseQueryString(result.contentUrl);
lResults.Add(qs[“r”]);

}
return lResults;
}

Where “BingSearchObject” is defined as:

#region BingSearchResults
public class Instrumentation
{
public string pageLoadPingUrl { get; set; }
}

public class Thumbnail
{
public int width { get; set; }
public int height { get; set; }
}

public class InsightsSourcesSummary
{
public int shoppingSourcesCount { get; set; }
public int recipeSourcesCount { get; set; }
}

public class Value
{
public string name { get; set; }
public string webSearchUrl { get; set; }
public string thumbnailUrl { get; set; }
public string datePublished { get; set; }
public string contentUrl { get; set; }
public string hostPageUrl { get; set; }
public string contentSize { get; set; }
public string encodingFormat { get; set; }
public string hostPageDisplayUrl { get; set; }
public int width { get; set; }
public int height { get; set; }
public Thumbnail thumbnail { get; set; }
public string imageInsightsToken { get; set; }
public InsightsSourcesSummary insightsSourcesSummary { get; set; }
public string imageId { get; set; }
public string accentColor { get; set; }
}

public class Thumbnail2
{
public string thumbnailUrl { get; set; }
}

public class QueryExpansion
{
public string text { get; set; }
public string displayText { get; set; }
public string webSearchUrl { get; set; }
public string searchLink { get; set; }
public Thumbnail2 thumbnail { get; set; }
}

public class Thumbnail3
{
public string thumbnailUrl { get; set; }
}

public class Suggestion
{
public string text { get; set; }
public string displayText { get; set; }
public string webSearchUrl { get; set; }
public string searchLink { get; set; }
public Thumbnail3 thumbnail { get; set; }
}

public class PivotSuggestion
{
public string pivot { get; set; }
public List<Suggestion> suggestions { get; set; }
}

public class Thumbnail4
{
public string url { get; set; }
}

public class SimilarTerm
{
public string text { get; set; }
public string displayText { get; set; }
public string webSearchUrl { get; set; }
public Thumbnail4 thumbnail { get; set; }
}

public class BingSearchObject
{
public string _type { get; set; }
public Instrumentation instrumentation { get; set; }
public string readLink { get; set; }
public string webSearchUrl { get; set; }
public int totalEstimatedMatches { get; set; }
public List<Value> value { get; set; }
public List<QueryExpansion> queryExpansions { get; set; }
public int nextOffsetAddCount { get; set; }
public List<PivotSuggestion> pivotSuggestions { get; set; }
public bool displayShoppingSourcesBadges { get; set; }
public bool displayRecipeSourcesBadges { get; set; }
public List<SimilarTerm> similarTerms { get; set; }
}
#endregion

Categories: Uncategorized

Remove #Grey from an image using C# #ImageProcessing

April 12, 2017 Leave a comment

Image processing in C# is surprisingly fast, and not too difficult. Here is a simple example of how to remove greys from an image, an extract only colour.

Firstly, a pure Grey is one defined as a colour where the Red, Green, and blue component are exactly equal. This spans the spectrum from White (all 255), to Black (all 0). In natural photographs, then the Grey may not be exactly pure, with small variations in the colour components.

So, lets see the core processing code:

public static byte[] PreprocessStep(byte[] input)
{
Stream sInput = new MemoryStream(input);
var imgEasy = Bitmap.FromStream(sInput) as Bitmap;
var img2dEasy = new Color[imgEasy.Width, imgEasy.Height];
for (int i = 0; i < imgEasy.Width; i++)
{
for (int j = 0; j < imgEasy.Height; j++)
{
Color pixel = imgEasy.GetPixel(i, j);

// Preprocessing
if (pixel.R == pixel.G && pixel.B == pixel.G)
{
// Convert all greys to white.
pixel = Color.White;
}

img2dEasy[i, j] = pixel;
}
}

var imgOut = new Bitmap(imgEasy.Width, imgEasy.Height);
for (int i = 0; i < imgEasy.Width; i++)
{
for (int j = 0; j < imgEasy.Height; j++)
{
imgOut.SetPixel(i, j, img2dEasy[i, j]);
}
}
var stream = new MemoryStream();
imgOut.Save(stream, ImageFormat.Png);
var bytes = stream.ToArray();
return bytes;
}

This accepts an image as a byte array, and outputs a byte array in PNG format. by accepting and returning a byte array, it makes it more “pluggable” into different inputs and outputs, like Files, HTTP streams etc.

Here’s how to use it with an input and output file.

static void Main(string[] args)
{
var strExePath = AppDomain.CurrentDomain.BaseDirectory;
var strEasyImage = strExePath + “il_570xN.1036233188_neqb.jpg”;
var sIn = new FileStream(strEasyImage,FileMode.Open);
var br = new BinaryReader(sIn);
var bIn = br.ReadBytes((int)sIn.Length);
sIn.Close();
var bOut = PreprocessStep(bIn);
var sOut = new FileStream(strExePath + “flower.png”,FileMode.Create);
sOut.Write(bOut,0,bOut.Length);
sOut.Close();
}

 

Categories: Uncategorized

Mail Address Verification.com – #Physical #address #verification online

April 10, 2017 Leave a comment

mav

If you need to be 100% sure that your customer works or lives at the address they have provided you? Our service ensures this by mailing a secure pin code to your customer’s address, which they use to verify that they are physically at the address they have provided you.

It works internationally, and we’ve got an API, so you can integrate into the system programmatically, as part of your customer-onboarding process.

The website is at www.mailaddressverification.com – and you get $1 free to test the service once you open a test account.

 

 

Categories: Uncategorized

#Finnish Car registration #API now supports Commercial vehicles and Motorbikes

April 4, 2017 Leave a comment

15327-trafi-logo

The Finnish API for Car registrations; which you can access at http://www.rekisterinumeroapi.com/ now supports both all types of vehicles, not only cars – this includes;

  • Private Cars [M1]
  • Commercial vehicles (Vans / Trucks) [N1]
  • Motorcycles [L3e]
  • Mopeds [L1e]
  • Snowmobiles
  • Tractors
  • Trailers

 

Categories: Uncategorized

Send email using #NodeJS with #AWS #Lambda and #SES

March 30, 2017 Leave a comment

send-email-from-node-js

Amazon Lamdba is a great service for quickly deploying NodeJS apps, without worrying about dealing with servers. Great if your servers are strictly production only, and you need to have a “throw away” but internet-connected API.

So, I created a NodeJS Lambda function on AWS, triggered using the API gateway with no authentication. You need to set up AWS SES with a verified email address, and get your SMTP credentials ready.

Here’s the code I used;

var aws = require(‘aws-sdk’);
var ses = new aws.SES({
accessKeyId: ‘xxxxxx’,
secretAccesskey: ‘xxxxx’,
region: ‘eu-west-1’
});

exports.handler = function(event, context, callback) {
console.log(“Incoming: “, event);
//var output = querystring.parse(event);

var eParams = {
Destination: {
ToAddresses: [“xxxx.xxxx@gmail.com”]
},
Message: {
Body: {
Text: {
Data: JSON.stringify(event)
}
},
Subject: {
Data: “Ses Test Email”
}
},
Source: “info@xxxxxxx.com”
};

console.log(‘===SENDING EMAIL===’);
var email = ses.sendEmail(eParams, function(err, data){
if(err) console.log(err);
else {
console.log(“===EMAIL SENT===”);
console.log(data);

}
});
console.log(“EMAIL CODE END”);
console.log(‘EMAIL: ‘, email);
var response = {
statusCode: 200,
body: “Sending email”
};
callback(null, response);
};

When running this for the first time I got the error

AccessDenied: User `arn:aws:sts::005445879168:assumed-role/helloWorldRole/LambdaMail’ is not authorized to perform `ses:SendEmail’

Which meant that I had to log in to IAM, and attach the policy AmazonSESFullAccess to the Role “helloWorldRole”

The format of the email sent was:


{
“resource”: “\/LambdaMail”,
“path”: “\/LambdaMail”,
“httpMethod”: “GET”,
“headers”: {
“CloudFront-Forwarded-Proto”: “https”,
“CloudFront-Is-Desktop-Viewer”: “true”,
“CloudFront-Is-Mobile-Viewer”: “false”,
“CloudFront-Is-SmartTV-Viewer”: “false”,
“CloudFront-Is-Tablet-Viewer”: “false”,
“CloudFront-Viewer-Country”: “DE”,
“Host”: “0vuu0520rb.execute-api.eu-west-1.amazonaws.com”,
“Via”: “1.1 d2e34d11a094aa8f0c8077cfdf5b4b38.cloudfront.net (CloudFront)”,
“X-Amz-Cf-Id”: “uWqPSOmAwMbgRDiwh8Wtwigf_YRHyXYM2CnC1tj-NzXOvk287KXs6Q==”,
“X-Amzn-Trace-Id”: “Root=1-58dd11fa-6ead550f5465a3814fc53748”,
“X-Forwarded-For”: “82.165.24.222, 54.240.145.60”,
“X-Forwarded-Port”: “443”,
“X-Forwarded-Proto”: “https”
},
“queryStringParameters”: {
“FirstName”: “Fiach”,
“Reference”: “GPhxXsQ”,
“Address2”: “10 NUALAMONT DRIVE”,
“Country”: “United Kingdom”,
“Address1”: “10 Nualamont Drive”,
“City”: “Derry”,
“LastName”: “Reid”,
“Postcode”: “BT48 9PH”
},
“pathParameters”: null,
“stageVariables”: null,
“requestContext”: {
“accountId”: “005445879168”,
“resourceId”: “i6b7on”,
“stage”: “prod”,
“requestId”: “b745b5a7-1552-11e7-8da2-c13b9a89bb97”,
“identity”: {
“cognitoIdentityPoolId”: null,
“accountId”: null,
“cognitoIdentityId”: null,
“caller”: null,
“apiKey”: null,
“sourceIp”: “82.165.24.222”,
“accessKey”: null,
“cognitoAuthenticationType”: null,
“cognitoAuthenticationProvider”: null,
“userArn”: null,
“userAgent”: null,
“user”: null
},
“resourcePath”: “\/LambdaMail”,
“httpMethod”: “GET”,
“apiId”: “0vuu0520rb”
},
“body”: null,
“isBase64Encoded”: false
}

Categories: Uncategorized

Dissecting the #Shopify #NodeJs Bridge

March 29, 2017 Leave a comment

maxresdefault

The Shopify NodeJs bridge by MobileFront is a NodeJS API that runs on Heroku, and it allows easy access to your Shopify Data via a JSON based API.

Here’s the core of the code in index.js

(function () {

‘use strict’;

var express = require(‘express’),
sprintf = require(‘sprintf’),
crypto = require(‘crypto’),
fs = require(‘fs’),
toSlugCase = require(‘to-slug-case’),
httpRequest = require(‘./http.request’),
app = express(),
shopifyAPI = require(‘shopify-node-api’),
port = process.env.PORT || 3000,
config = JSON.parse(fs.readFileSync(‘config.json’, ‘utf8’)),
shopify = new shopifyAPI(config);
// Shopify App install

app.get(‘/install’, function (req, res) {
//res.send(‘This is ShopToApp installation page’);
var shop = req.query.shop,
apiKey = req.query.apiKey,
apiSecret = req.query.apiSecret,
Shopify,
authURL;

if (config.access_token) {
// reject, we should not allow installing the app more than once
res.status(400).send({
status: ‘failed’,
message: ‘The app is already installed’
});

} else if (!shop || !apiKey || !apiSecret) {
res.status(400).send({
status: ‘failed’,
message: ‘Missing required parameters’
});
} else {
config.shop = shop;
config.shopify_api_key = apiKey;
config.shopify_shared_secret = apiSecret;
config.redirect_uri = req.headers[“x-forwarded-proto”] + ‘://’ + req.get(‘host’) + ‘/auth’;
config.nonce = crypto.randomBytes(64).toString(‘hex’);

Shopify = new shopifyAPI(config);
authURL = Shopify.buildAuthURL();

res.redirect(authURL);
}

});

app.get(‘/auth’, function (req, res) {

//params: code, hmac, timestamp, state, shop

var requestParams = req.query,
shop,
Shopify;

if (req.query.shop) {
shop = req.query.shop.replace(‘.myshopify.com’, ”);
config.shop = shop;
Shopify = new shopifyAPI(config);

Shopify.exchange_temporary_token(requestParams, function (err, data) {
if (!err) {

config.access_token = data.access_token;
shopify = new shopifyAPI(config);

res.status(200).send(‘You have successfully installed the MobileFront Bridge! Your Access Token is: ‘ + config.access_token + ‘. Keep it safe.’);
} else {
res.status(400).send({
status: ‘failed’,
message: ‘Failed to install the app. Reason: ‘ + err + ‘, shop: ‘ + config.shop
});
}
});
} else {
res.status(400).send({
status: ‘failed’,
message: ‘Invalid parameters’
});
}

});

// API

// =======================
// CORS ==================
// =======================
app.use(‘/’, function (req, res, next) {
res.header(‘Access-Control-Allow-Origin’, ‘*’);
res.header(‘Access-Control-Allow-Methods’, ‘GET,PUT,POST,DELETE,OPTIONS’);
res.header(‘Access-Control-Allow-Headers’, ‘Content-Type, Authorization, Content-Length, X-Requested-With’);
// intercept OPTIONS method
if (‘OPTIONS’ === req.method) {
res.sendStatus(200);
} else {
next();
}
});

app.use(‘/api’, function (req, res, next) {
if (!config.shop || !config.access_token) {
res.status(400).send({
status: ‘failed’,
message: ‘Bridge was not installed’
});
} else {
req.shopify = shopify;
next();
}

});

app.get(‘/api/getShopData’, function (req, res) {
var shopify = req.shopify;

shopify.get(‘/admin/shop.json’, function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});

app.get(‘/api/getSmartCollections’, function (req, res) {
var shopify = req.shopify;

shopify.get(‘/admin/smart_collections.json’, function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});

app.get(‘/api/getCollection’, function (req, res) {
var shopify = req.shopify,
collectionId = req.query[‘collectionId’];

if (collectionId) {
// first look in smart collections
shopify.get(‘/admin/smart_collections/’ + collectionId + ‘.json’, function (err, data, headers) {
if (!err) {
res.json(data.smart_collection);
} else {
// if not found look in custom collections
shopify.get(‘/admin/custom_collections/’ + collectionId + ‘.json’, function (err, data, headers) {
if (!err) {
res.json(data.custom_collection);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
}
});
} else {
res.status(400).json({
status: ‘failed’,
message: ‘Missing or empty collectionId parameter’
});
}
});
app.get(‘/api/getProductsForCollection/’, function (req, res) {
var shopify = req.shopify,
query = req.query,
params = Object.keys(query).map(function (key) {
var keyValue = key + ‘=’;
if (key === ‘title’) {
keyValue += encodeURIComponent(query[key]);
} else {
keyValue += query[key];
}
return keyValue;
});

shopify.get(‘/admin/products.json?’ + params.join(‘&’), function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});

app.get(‘/api/getNavigationLinks’, function (req, res) {
var SHOPIFY_STORE_URL = ‘https://&#8217; + config.shop + ‘.myshopify.com’,
url = SHOPIFY_STORE_URL + ‘/apps/proxy/categories.json’;

httpRequest.get(url, null, null, function (result) {
res.json(result);
}, function (error) {
console.log(‘/api/getNavigationLinks: ‘ + JSON.stringify(error));
res.status(400).json({
status: ‘failed’,
message: ‘Failed to retrieve navigation links. Is your shop password protected?’
});
});
});

// the route below is used by Shopify when processing proxy requests
app.get(‘/proxy/categories.json’, function (req, res) {
var parentCategory = req.query.parent || ‘main-menu’,
output;

//output = ‘{% layout none %}{% capture items %}{% for link in linklists.’ + parentCategory + ‘.links %}{% if link.type == “collection_link” %}{ “title”: “{{link.title}}”, “handle”: “{{link.title | handleize}}”, “collection”: “{{link.object.id}}”},{% endif %}{% endfor %}{% endcapture items %}{% assign length = items | size %}{% if length != 0 %}{% assign sliceLength = length | minus: 1 %}{% assign items = items | slice : 0, sliceLength %}{% endif %}[{{items}}]’;
output = ‘{% layout none %}{% capture items %}{% for linklist in linklists %}{% for link in linklist.links %}{% if link.type == “collection_link” %}{ “title”: “{{link.title}}”, “handle”: “{{link.title | handleize}}”, “collection”: “{{link.object.id}}”, “parent”: “{{linklist.handle}}”},{% endif %}{% endfor %}{% endfor %}{% endcapture items %}{% assign length = items | size %}{% if length != 0 %}{% assign sliceLength = length | minus: 1 %}{% assign items = items | slice : 0, sliceLength %}{% endif %}[{{items}}]’;

res.set(‘Content-Type’, ‘application/liquid’);
res.send(output);
});

app.listen(port);
})();

I have this running on a development shop, with one product, so here is what it returns for it’s various endpoints;

http://hidden-bastion-72179.herokuapp.com/api/getNavigationLinks

[
{
“title”: “Gym”,
“handle”: “gym”,
“collection”: “422347917”,
“parent”: “main-menu”
}
]

http://hidden-bastion-72179.herokuapp.com/api/getShopData

{
“shop”: {
“id”: 18854285,
“name”: “webtropy”,
“email”: “fiach.reid@gmail.com”,
“domain”: “webtropy.myshopify.com”,
“created_at”: “2017-03-29T05:04:07-04:00”,
“province”: “”,
“country”: “GB”,
“address1”: “10 Nualamont Drive, 10 Nualamont Drive”,
“zip”: “BT48 9PH”,
“city”: “Derry”,
“source”: null,
“phone”: “”,
“updated_at”: “2017-03-29T05:25:02-04:00”,
“customer_email”: null,
“latitude”: null,
“longitude”: null,
“primary_location_id”: 40035597,
“primary_locale”: “en”,
“address2”: null,
“country_code”: “GB”,
“country_name”: “United Kingdom”,
“currency”: “GBP”,
“timezone”: “(GMT-05:00) Eastern Time (US & Canada)”,
“iana_timezone”: “America\/New_York”,
“shop_owner”: “Fiach Reid”,
“money_format”: “\u00a3{{amount}}”,
“money_with_currency_format”: “\u00a3{{amount}} GBP”,
“weight_unit”: “lb”,
“province_code”: null,
“taxes_included”: true,
“tax_shipping”: null,
“county_taxes”: true,
“plan_display_name”: “affiliate”,
“plan_name”: “affiliate”,
“has_discounts”: false,
“has_gift_cards”: false,
“myshopify_domain”: “webtropy.myshopify.com”,
“google_apps_domain”: null,
“google_apps_login_enabled”: null,
“money_in_emails_format”: “\u00a3{{amount}}”,
“money_with_currency_in_emails_format”: “\u00a3{{amount}} GBP”,
“eligible_for_payments”: true,
“requires_extra_payments_agreement”: false,
“password_enabled”: false,
“has_storefront”: true,
“eligible_for_card_reader_giveaway”: false,
“finances”: true,
“setup_required”: false,
“force_ssl”: true
}
}

http://hidden-bastion-72179.herokuapp.com/api/getSmartCollections

{
“smart_collections”: [
{
“id”: 422347917,
“handle”: “gym”,
“title”: “Gym”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“body_html”: “”,
“published_at”: “2017-03-29T05:38:00-04:00”,
“sort_order”: “best-selling”,
“template_suffix”: null,
“published_scope”: “global”,
“disjunctive”: false,
“rules”: [
{
“column”: “tag”,
“relation”: “equals”,
“condition”: “Gym”
}
]
}
]
}

http://hidden-bastion-72179.herokuapp.com/api/getCollection?collectionId=422347917

{
“id”: 422347917,
“handle”: “gym”,
“title”: “Gym”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“body_html”: “”,
“published_at”: “2017-03-29T05:38:00-04:00”,
“sort_order”: “best-selling”,
“template_suffix”: null,
“products_count”: 1,
“published_scope”: “global”,
“disjunctive”: false,
“rules”: [
{
“column”: “tag”,
“relation”: “equals”,
“condition”: “Gym”
}
]
}

http://hidden-bastion-72179.herokuapp.com/api/getProductsForCollection

{
“products”: [
{
“id”: 9487359757,
“title”: “Dumbells”,
“body_html”: “”,
“vendor”: “webtropy”,
“product_type”: “”,
“created_at”: “2017-03-29T05:40:56-04:00”,
“handle”: “dumbells”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“published_at”: “2017-03-29T05:39:00-04:00”,
“template_suffix”: null,
“published_scope”: “global”,
“tags”: “gym”,
“variants”: [
{
“id”: 35073137037,
“product_id”: 9487359757,
“title”: “Default Title”,
“price”: “100.00”,
“sku”: “”,
“position”: 1,
“grams”: 0,
“inventory_policy”: “deny”,
“compare_at_price”: null,
“fulfillment_service”: “manual”,
“inventory_management”: null,
“option1”: “Default Title”,
“option2”: null,
“option3”: null,
“created_at”: “2017-03-29T05:40:56-04:00”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“taxable”: true,
“barcode”: “”,
“image_id”: null,
“inventory_quantity”: 1,
“weight”: 0,
“weight_unit”: “lb”,
“old_inventory_quantity”: 1,
“requires_shipping”: true
}
],
“options”: [
{
“id”: 11375442061,
“product_id”: 9487359757,
“name”: “Title”,
“position”: 1,
“values”: [
“Default Title”
]
}
],
“images”: [
{
“id”: 22923954125,
“product_id”: 9487359757,
“position”: 1,
“created_at”: “2017-03-29T05:40:58-04:00”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“src”: “https:\/\/cdn.shopify.com\/s\/files\/1\/1885\/4285\/products\/exercise-equipment-product-image.jpg?v=1490780458”,
“variant_ids”: [

]
}
],
“image”: {
“id”: 22923954125,
“product_id”: 9487359757,
“position”: 1,
“created_at”: “2017-03-29T05:40:58-04:00”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“src”: “https:\/\/cdn.shopify.com\/s\/files\/1\/1885\/4285\/products\/exercise-equipment-product-image.jpg?v=1490780458”,
“variant_ids”: [

]
}
}
]
}

Categories: Uncategorized