Skip to content
This repository was archived by the owner on Feb 14, 2024. It is now read-only.

Commit 697e50d

Browse files
committed
initial commit
0 parents  commit 697e50d

14 files changed

+700
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Duffle
2+
Bulk order management for Purse.io
3+
4+
## Requirements
5+
- nodejs
6+
- npm
7+
- git
8+
9+
## Getting Started
10+
- `git clone [email protected]:dylanbathurst/duffle.git`
11+
- `cd duffle`
12+
- `npm install`
13+
- `npm link`
14+
- `duffle -h`
15+
16+
## Authentication
17+
1. Log in to your account on [Purse.io](https://purse.io/)
18+
1. In your browser's web inspector, look for the `purse_token` cookie and copy the contents. ![purse token in web inspector](./examples/images/purse_token.png)
19+
1. This token will used on the command line when using duffle.
20+
21+
## Creating Orders
22+
To create a bulk set of orders on Purse, create a `.csv` file and format it exactly like <a href="./examples/files/create.csv">this file</a> in the examples directory. Then run:
23+
```
24+
duffle --auth-token YOUR_PURSE_TOKEN create-orders /PATH/TO/YOUR/CSV/FILE.csv
25+
```
26+
example:
27+
```
28+
duffle --auth-token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyasdllkflkasdflaslljfjlJ1c2VyX2lkIjo2NzYsInVzZXJuYW1lIjoiZHlsYW5iYXRodXJzdCIsImV4cCI6MTU1MzE5MzUwMiwiZW1haWwiOiJkeWxhbmJhdGh1cnN0QGdtYWlsLmNvbSIsInNkIjoiZ2Z4M081T0kiLCJyIjo.iNkd4cmFoRWYiLCJkcyI6ImZVM create-orders examples/files/create.csv
29+
```
30+
31+
## Modify Orders
32+
To modify a bulk set of orders on Purse, create a `.csv` file and format it exactly like <a href="./examples/files/modify.csv">this file</a> in the examples directory. Then run:
33+
```
34+
duffle --auth-token YOUR_PURSE_TOKEN modify-orders /PATH/TO/YOUR/CSV/FILE.csv
35+
```
36+
example:
37+
```
38+
duffle --auth-token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyasdllkflkasdflaslljfjlJ1c2VyX2lkIjo2NzYsInVzZXJuYW1lIjoiZHlsYW5iYXRodXJzdCIsImV4cCI6MTU1MzE5MzUwMiwiZW1haWwiOiJkeWxhbmJhdGh1cnN0QGdtYWlsLmNvbSIsInNkIjoiZ2Z4M081T0kiLCJyIjo.iNkd4cmFoRWYiLCJkcyI6ImZVM modify-orders examples/files/modify.csv
39+
```
40+
Your console output will show the number of successful and unsuccessfully created orders, as well as the response text for each order.

bin/duffle

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const program = require('commander');
6+
const parseCSV = require('../lib/parseCSV');
7+
const apiRequest = require('../lib/api');
8+
9+
const initApi = (jwt) => {
10+
11+
};
12+
13+
program
14+
.version('0.0.1')
15+
.option('-a, --auth-token <jwt>', 'Your Purse JWT Auth Token');
16+
17+
program
18+
.command('create-orders <path>')
19+
.description('Creates a bulk list of orders from a CSV file')
20+
.action(async (path, options) => {
21+
let Api;
22+
try {
23+
Api = new apiRequest(options.parent.authToken);
24+
} catch (e) {
25+
console.log('*********************');
26+
console.log(e.message);
27+
console.log('*********************');
28+
return;
29+
}
30+
let parsedFile;
31+
try {
32+
parsedFile = await parseCSV(path);
33+
} catch (e) {
34+
console.log(e);
35+
return;
36+
}
37+
const bulkOrders = await Api.createBulkOrders(parsedFile);
38+
console.log(`${bulkOrders.successfulOrders.length} successful orders:`);
39+
console.log(`${JSON.stringify(bulkOrders.successfulOrders)}`);
40+
console.log(`${bulkOrders.failedOrders.length} failed orders:`);
41+
console.log(`${JSON.stringify(bulkOrders.failedOrders)}`);
42+
});
43+
44+
program
45+
.command('modify-orders <path>')
46+
.description('Modifies a bulk list of orders from a CSV file')
47+
.action(async (path, options) => {
48+
let Api;
49+
try {
50+
Api = new apiRequest(options.parent.authToken);
51+
} catch (e) {
52+
console.log('*********************');
53+
console.log(e.message);
54+
console.log('*********************');
55+
return;
56+
}
57+
let parsedFile;
58+
try {
59+
parsedFile = await parseCSV(path);
60+
} catch (e) {
61+
console.log(e);
62+
return;
63+
}
64+
const bulkModify = await Api.modifyBulkOrders(parsedFile);
65+
console.log(`${bulkModify.successfulOrders.length} successful orders:`);
66+
console.log(`${JSON.stringify(bulkModify.successfulOrders)}`);
67+
console.log(`${bulkModify.failedOrders.length} failed orders:`);
68+
console.log(`${JSON.stringify(bulkModify.failedOrders)}`);
69+
});
70+
71+
program
72+
.command('cancel-orders <path>')
73+
.description('Cancels a bulk list of orders from a CSV file');
74+
75+
program.parse(process.argv);

examples/files/create.csv

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
asin,quantity,discount,full_name,street1,street2,city,state,zip,country,phone
2+
B016QO5YNG,1,0.15,Bob Smith,Some St,,San Francisco,CA,94000,US,1234567890

examples/files/modify.csv

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
id,discount,shipping_cost
2+
SP-PFTU2FQK,0.10,0

examples/images/purse_token.png

171 KB
Loading

lib/api.js

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
const request = require('request-promise-native');
2+
const NODE_ENV = process.env.NODE_ENV || 'production';
3+
4+
class apiRequest {
5+
constructor(jwt) {
6+
if (!jwt) {
7+
throw new Error('You must provide a valid Purse Auth Token.');
8+
}
9+
this.jwt = jwt;
10+
if (NODE_ENV === 'production') {
11+
this.apiUrl = 'https://api.purse.io/api/v1'
12+
} else {
13+
this.apiUrl = 'https://master.api.dev.purse.io/api/v1'
14+
}
15+
this.createOrder = this.createOrder.bind(this);
16+
this.modifyOrder = this.modifyOrder.bind(this);
17+
}
18+
async bulkChange(bulkMethod, orders) {
19+
const successfulOrders = [];
20+
const failedOrders = [];
21+
22+
for (let order of orders) {
23+
const orderReq = await bulkMethod(order);
24+
if (orderReq.success) {
25+
successfulOrders.push(orderReq);
26+
} else {
27+
failedOrders.push(orderReq);
28+
}
29+
}
30+
31+
return { successfulOrders, failedOrders };
32+
}
33+
34+
async createBulkOrders(orders) {
35+
return this.bulkChange(this.createOrder, orders);
36+
}
37+
async modifyBulkOrders(orders) {
38+
return this.bulkChange(this.modifyOrder, orders);
39+
}
40+
async createOrder(order) {
41+
const validOrder = this.buildNYDOrder(order);
42+
43+
if (!validOrder) {
44+
order.success = false;
45+
order.errorMessage = 'This was not a valid order';
46+
return order;
47+
} else {
48+
const opts = {
49+
method: 'POST',
50+
uri: `${this.apiUrl}/orders/instant`,
51+
resolveWithFullResponse: true,
52+
headers: { Authorization: `JWT ${this.jwt}` },
53+
json: validOrder
54+
};
55+
56+
let orderRequest;
57+
try {
58+
orderRequest = await request(opts);
59+
} catch (e) {
60+
order.success = false;
61+
order.errorMessage = e.message;
62+
return order;
63+
}
64+
65+
if (orderRequest.statusCode !== 200) {
66+
order.success = false;
67+
order.errorMessage = orderRequest.body;
68+
return order;
69+
} else {
70+
order.success = true;
71+
order.body = orderRequest.body;
72+
return order;
73+
}
74+
}
75+
}
76+
async modifyOrder(order) {
77+
const validModifiedOrder = this.buildModifiedOrder(order);
78+
79+
if (!validModifiedOrder) {
80+
order.success = false;
81+
order.errorMessage = 'This was not a valid modified order';
82+
} else {
83+
const opts = {
84+
method: 'PATCH',
85+
uri: `${this.apiUrl}/orders/${order.id}`,
86+
resolveWithFullResponse: true,
87+
headers: { Authorization: `JWT ${this.jwt}` },
88+
json: validModifiedOrder
89+
};
90+
91+
let modifyRequest;
92+
try {
93+
modifyRequest = await request(opts);
94+
} catch (e) {
95+
order.success = false;
96+
order.errorMessage = e.message;
97+
return order;
98+
}
99+
100+
if (modifyRequest.statusCode !== 200) {
101+
order.success = false;
102+
order.errorMessage = modifyRequest.body;
103+
return order;
104+
} else {
105+
order.success = true;
106+
order.body = modifyRequest.body;
107+
return order;
108+
}
109+
}
110+
}
111+
buildNYDOrder(order) {
112+
const { asin, quantity, discount, full_name, street1,
113+
street2, city, state, zip, country, phone } = order;
114+
if (!asin || !quantity || !discount || !full_name ||
115+
!street1 || !city || !state || !zip || !country || !phone ) {
116+
return false;
117+
}
118+
119+
const orderModel = {
120+
coin: 'BTC',
121+
discount: parseFloat(discount),
122+
country,
123+
shipping_cost: 0,
124+
service: {
125+
type: 'instant',
126+
items: [{
127+
asin, quantity
128+
}],
129+
shipping_address: {
130+
full_name, street1, street2, city, state, zip, country, phone
131+
}
132+
}
133+
};
134+
return orderModel;
135+
}
136+
buildModifiedOrder(order) {
137+
let { discount, shipping_cost } = order;
138+
139+
const modifyModel = {
140+
discount,
141+
shipping_cost
142+
};
143+
return modifyModel;
144+
}
145+
}
146+
147+
module.exports = apiRequest;

lib/parseCSV.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const csvParse = require('csv-parse');
2+
const fs = require('fs');
3+
4+
const parser = (filePath) => {
5+
const parsedFile = [];
6+
return new Promise((resolve, reject) => {
7+
const parser = csvParse({
8+
skip_empty_lines: true,
9+
columns: true
10+
});
11+
12+
fs.createReadStream(filePath)
13+
.pipe(parser)
14+
.on('data', (row) => {
15+
parsedFile.push(row);
16+
})
17+
.on('end', () => {
18+
resolve(parsedFile);
19+
})
20+
.on('error', (err) => {
21+
reject(err);
22+
})
23+
});
24+
};
25+
26+
module.exports = parser;

order/cancelBulk.js

Whitespace-only changes.

order/createBulk.js

Whitespace-only changes.

order/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const createBulk = require('./createBulk');
2+
const modifyBulk = require('./modifyBulk');
3+
const cancelBulk = require('./cancelBulk');
4+
5+
module.exports = { createBulk, modifyBulk, cancelBulk };

order/modifyBulk.js

Whitespace-only changes.

0 commit comments

Comments
 (0)