1 Commits

Author SHA1 Message Date
0648dcb96c Added .env file support more directly 2024-03-23 00:14:31 -05:00
25 changed files with 178 additions and 1551 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.env
**/node_modules/
package-lock.json
*.swp
.prettyrc
.vscode

1
api/.gitignore vendored
View File

@@ -1 +0,0 @@
.env

View File

@@ -1,9 +1,9 @@
import fetch from "node-fetch";
import fetchEbayApplicationToken from "../utils/fetchEbayApplicationToken.js";
import fetchEbayReadToken from "../utils/fetchEbayReadToken.js"; // Adjust the import path according to your project structure
export const itemLookup = async (req, res) => {
const productCode = req.query.productCode;
const token = await fetchEbayApplicationToken();
const oauthToken = await fetchEbayReadToken();
console.log(productCode);
try {
const response = await fetch(
@@ -11,7 +11,7 @@ export const itemLookup = async (req, res) => {
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${oauthToken}`,
"Content-Type": "application/json",
},
}

View File

@@ -1,265 +1,45 @@
// inventoryController.js
import buildAddFixedPriceItemRequestXML from "../utils/buildAddFixedPriceItemRequestXML.js";
import fetch from "node-fetch";
import fetchEbayUserToken from "../utils/fetchEbayUserToken.js";
import fetchEbayUserToken from "../utils/fetchEbayUserToken.js"; // Adjust the import path according to your project structure
export const createAndListItem = async (req, res) => {
const {
sku,
marketplaceId,
format,
listingDuration,
availableQuantity,
categoryId,
condition,
listingDescription,
pricingSummary,
mpn,
merchantLocationKey,
quantityLimitPerBuyer,
product, // Extract the entire product object
packageWeightAndSize, // Adjusted to packageWeightAndSize from the refined payload
imageUrls,
} = req.body;
const token = await fetchEbayUserToken(req, res); // Get the eBay user token
// only need these if payloads is there?
let dynamicAspects = {};
for (const [key, value] of Object.entries(product.aspects)) {
dynamicAspects[key] = Array.isArray(value) ? value : [value];
}
try {
// Create the inventory item
const inventoryItemPayload = {
sku,
condition,
quantity: availableQuantity,
product: {
title: product.title.substring(0, 80), // Utilizing product title
brand: product.brand, // Using brand from the product object
mpn: mpn,
description: product.description, // Using product description
imageUrls: product.imageUrls, // Using all imageUrls from product
},
availability: {
shipToLocationAvailability: {
quantity: availableQuantity,
availabilityDistributions: [ // Adding availability distributions
{
availabilityType: "IN_STOCK", // Assuming items are in stock for pickup
fulfillmentTime: { // Fulfillment time for shipping
unit: "DAY",
value: 1 // Adjust based on your shipping fulfillment time
},
merchantLocationKey: merchantLocationKey, // Specify the location for shipping availability
quantity: availableQuantity // Quantity available for shipping from the specified location
}
]
},
},
packageWeightAndSize: {
weight: {
value: parseFloat(packageWeightAndSize.weight.value),
unit: packageWeightAndSize.weight.unit,
},
dimensions: {
length: parseFloat(packageWeightAndSize.dimensions.length),
width: parseFloat(packageWeightAndSize.dimensions.width),
height: parseFloat(packageWeightAndSize.dimensions.height),
unit: packageWeightAndSize.dimensions.unit,
},
},
};
const createItemResponse = await fetch(
`https://api.ebay.com/sell/inventory/v1/inventory_item/${sku}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
"Content-Language": "en-US",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
},
body: JSON.stringify(inventoryItemPayload),
}
);
// Optionally, you can parse the response body if eBay's API returns useful information in it
const itemResponseData = await createItemResponse;
// 2. Create the offer for the inventory item
const offerPayload = {
sku,
marketplaceId,
format, // This is equivalent to format: format,
categoryId, // Moved to top-level, outside of the 'listing' object
listingDescription, // Moved to top-level, outside of the 'listing' object
merchantLocationKey, // Moved to top-level, outside of the 'listing' object
pricingSummary: {
price: pricingSummary.price,
},
availableQuantity,
listingDuration,
listingPolicies: { // Include the listing policies with their respective IDs
fulfillmentPolicyId: "246576176016",
returnPolicyId: "246576188016",
paymentPolicyId: "246576154016",
},
// Additional fields from the sample payload can be included here as needed
};
const offerResponse = await fetch(
`https://api.ebay.com/sell/inventory/v1/offer`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Language": "en-US",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(offerPayload),
}
);
if (!offerResponse.ok) {
// Handle error in creating the offer
const errorBody = await offerResponse.text();
throw new Error(
`Failed to create offer: ${offerResponse.status} - ${errorBody}`
);
}
const offerData = await offerResponse.json();
const offerId = offerData.offerId;
console.log(offerId)
// 3. Publish the offer to convert it into a live listing
const publishOfferResponse = await fetch(
`https://api.ebay.com/sell/inventory/v1/offer/${offerId}/publish/`, // Ensure the offerId is correctly inserted
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`, // Ensure your token is valid
"Content-Type": "application/json",
},
}
);
const responseBody = await publishOfferResponse.json(); // Assuming the response will be in JSON format
console.log(responseBody);
res.json({ success: true, message: "Item listed successfully" });
} catch (error) {
console.error("Error in createAndListItem endpoint:", error);
res.status(500).json({
success: false,
message: "Failed to create and list item on eBay",
error: error.message,
})
}
};
export const deleteItemBySku = async (req, res) => {
const { sku } = req.params; // Assuming SKU is provided as a URL parameter
export const addItem = async (req, res) => {
const itemDetails = req.body; // Assuming item details are sent in the request body
itemDetails.userToken =
"v^1.1#i^1#r^0#I^3#f^0#p^3#t^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";
console.log(itemDetails);
const xmlRequest = buildAddFixedPriceItemRequestXML(itemDetails);
try {
const token = await fetchEbayUserToken(req, res); // Get the eBay user token
// Perform the deletion of the inventory item
const deleteResponse = await fetch(`https://api.ebay.com/sell/inventory/v1/inventory_item/${sku}`, {
method: "DELETE",
const response = await fetch("https://api.ebay.com/ws/api.dll", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"Accept": "application/json",
},
});
if (!deleteResponse.ok) {
// Handle unsuccessful deletion
const errorBody = await deleteResponse.text();
throw new Error(`Failed to delete inventory item: ${deleteResponse.status} - ${errorBody}`);
}
// Successfully deleted the item
res.json({ success: true, message: `Item with SKU: ${sku} deleted successfully.` });
} catch (error) {
console.error("Error in deleteItemBySku endpoint:", error);
res.status(500).json({
success: false,
message: `Failed to delete item with SKU: ${sku} from eBay`,
error: error.message,
});
}
};
export const getAllInventory = async (req, res) => {
const limit = req.query.limit || '100'; // Set default limit
const offset = req.query.offset || '0'; // Set default offset
try {
const token = await fetchEbayUserToken(req, res); // Assuming fetchEbayUserToken doesn't need req, res passed and handles token internally
const response = await fetch(`https://api.ebay.com/sell/inventory/v1/inventory_item?limit=${limit}&offset=${offset}`, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept-Language": "en-US",
"Content-Type": "text/xml",
"X-EBAY-API-SITEID": "0",
"X-EBAY-API-CALL-NAME": "AddFixedPriceItem",
"X-EBAY-API-COMPATIBILITY-LEVEL": "967", // Ensure this is the current compatibility level
"X-EBAY-API-APP-NAME": process.env.EBAY_CLIENT_ID,
"X-EBAY-API-DEV-NAME": process.env.EBAY_DEV_ID,
"X-EBAY-API-CERT-NAME": process.env.EBAY_CLIENT_SECRET,
},
body: xmlRequest,
});
if (!response.ok) {
// If the response is not okay, throw an error with status and statusText
throw new Error(`Failed to retrieve inventory items: ${response.status} ${response.statusText}`);
throw new Error(`eBay API responded with status ${response.status}`);
}
const inventoryItems = await response.json(); // Parsing the JSON body of the response
res.json(inventoryItems); // Sending the inventory items back to the client
const responseData = await response.text();
// Parse the XML response and handle it appropriately
// You might need an XML parser here to handle the response
res.json({ success: true, data: responseData }); // Send back a success response
} catch (error) {
console.error('Error fetching inventory:', error);
console.error("Error adding item to eBay:", error);
res.status(500).json({
error: 'Internal Server Error',
message: error.message,
success: false,
message: "Failed to add item to eBay",
error: error.message,
});
}
};
export const getOffers = async (req, res) => {
const sku = "LEMUG2022212234120245"; // Assuming this is your SKU
const offerId = 418806088016
try {
const token = await fetchEbayUserToken(req, res); // Assuming fetchEbayUserToken handles token retrieval internally
const response = await fetch('https://api.ebay.com/commerce/taxonomy/v1/get_default_category_tree_id?marketplace_id=EBAY_US', {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Content-Language": "en-US",
"Authorization": `Bearer ${token}`,
},
});
console.log(response)
const offers = await response.json(); // Parsing the JSON body of the response
res.json(offers); // Sending the offers back to the client
} catch (error) {
console.error('Error fetching offers:', error);
res.status(500).json({
error: 'Internal Server Error',
message: error.message,
});
}
};
// fulfill policy = 246576176016
// return policy = 246576188016
// payment policy = 246576154016

20
api/node_modules/.package-lock.json generated vendored
View File

@@ -177,26 +177,6 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",

View File

@@ -1,100 +0,0 @@
1.4.6 / 2021-11-16
==================
* deps: cookie@0.4.1
1.4.5 / 2020-03-14
==================
* deps: cookie@0.4.0
1.4.4 / 2019-02-12
==================
* perf: normalize `secret` argument only once
1.4.3 / 2016-05-26
==================
* deps: cookie@0.3.1
- perf: use for loop in parse
1.4.2 / 2016-05-20
==================
* deps: cookie@0.2.4
- perf: enable strict mode
- perf: use for loop in parse
- perf: use string concatenation for serialization
1.4.1 / 2016-01-11
==================
* deps: cookie@0.2.3
* perf: enable strict mode
1.4.0 / 2015-09-18
==================
* Accept array of secrets in addition to a single secret
* Fix `JSONCookie` to return `undefined` for non-string arguments
* Fix `signedCookie` to return `undefined` for non-string arguments
* deps: cookie@0.2.2
1.3.5 / 2015-05-19
==================
* deps: cookie@0.1.3
- Slight optimizations
1.3.4 / 2015-02-15
==================
* deps: cookie-signature@1.0.6
1.3.3 / 2014-09-05
==================
* deps: cookie-signature@1.0.5
1.3.2 / 2014-06-26
==================
* deps: cookie-signature@1.0.4
- fix for timing attacks
1.3.1 / 2014-06-17
==================
* actually export `signedCookie`
1.3.0 / 2014-06-17
==================
* add `signedCookie` export for single cookie unsigning
1.2.0 / 2014-06-17
==================
* export parsing functions
* `req.cookies` and `req.signedCookies` are now plain objects
* slightly faster parsing of many cookies
1.1.0 / 2014-05-12
==================
* Support for NodeJS version 0.8
* deps: cookie@0.1.2
- Fix for maxAge == 0
- made compat with expires field
- tweak maxAge NaN error message
1.0.1 / 2014-02-20
==================
* add missing dependencies
1.0.0 / 2014-02-15
==================
* Genesis from `connect`

View File

@@ -1,23 +0,0 @@
(The MIT License)
Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,119 +0,0 @@
# cookie-parser
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Parse `Cookie` header and populate `req.cookies` with an object keyed by the
cookie names. Optionally you may enable signed cookie support by passing a
`secret` string, which assigns `req.secret` so it may be used by other
middleware.
## Installation
```sh
$ npm install cookie-parser
```
## API
```js
var cookieParser = require('cookie-parser')
```
### cookieParser(secret, options)
Create a new cookie parser middleware function using the given `secret` and
`options`.
- `secret` a string or array used for signing cookies. This is optional and if
not specified, will not parse signed cookies. If a string is provided, this
is used as the secret. If an array is provided, an attempt will be made to
unsign the cookie with each secret in order.
- `options` an object that is passed to `cookie.parse` as the second option. See
[cookie](https://www.npmjs.org/package/cookie) for more information.
- `decode` a function to decode the value of the cookie
The middleware will parse the `Cookie` header on the request and expose the
cookie data as the property `req.cookies` and, if a `secret` was provided, as
the property `req.signedCookies`. These properties are name value pairs of the
cookie name to cookie value.
When `secret` is provided, this module will unsign and validate any signed cookie
values and move those name value pairs from `req.cookies` into `req.signedCookies`.
A signed cookie is a cookie that has a value prefixed with `s:`. Signed cookies
that fail signature validation will have the value `false` instead of the tampered
value.
In addition, this module supports special "JSON cookies". These are cookie where
the value is prefixed with `j:`. When these values are encountered, the value will
be exposed as the result of `JSON.parse`. If parsing fails, the original value will
remain.
### cookieParser.JSONCookie(str)
Parse a cookie value as a JSON cookie. This will return the parsed JSON value
if it was a JSON cookie, otherwise, it will return the passed value.
### cookieParser.JSONCookies(cookies)
Given an object, this will iterate over the keys and call `JSONCookie` on each
value, replacing the original value with the parsed value. This returns the
same object that was passed in.
### cookieParser.signedCookie(str, secret)
Parse a cookie value as a signed cookie. This will return the parsed unsigned
value if it was a signed cookie and the signature was valid. If the value was
not signed, the original value is returned. If the value was signed but the
signature could not be validated, `false` is returned.
The `secret` argument can be an array or string. If a string is provided, this
is used as the secret. If an array is provided, an attempt will be made to
unsign the cookie with each secret in order.
### cookieParser.signedCookies(cookies, secret)
Given an object, this will iterate over the keys and check if any value is a
signed cookie. If it is a signed cookie and the signature is valid, the key
will be deleted from the object and added to the new object that is returned.
The `secret` argument can be an array or string. If a string is provided, this
is used as the secret. If an array is provided, an attempt will be made to
unsign the cookie with each secret in order.
## Example
```js
var express = require('express')
var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())
app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
})
app.listen(8080)
// curl command that sends an HTTP request with two cookies
// curl http://127.0.0.1:8080 --cookie "Cho=Kim;Greet=Hello"
```
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/expressjs/cookie-parser/master?label=ci
[ci-url]: https://github.com/expressjs/cookie-parser/actions?query=workflow%3Aci
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/cookie-parser/master
[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master
[npm-downloads-image]: https://badgen.net/npm/dm/cookie-parser
[npm-url]: https://npmjs.org/package/cookie-parser
[npm-version-image]: https://badgen.net/npm/v/cookie-parser

View File

@@ -1,182 +0,0 @@
/*!
* cookie-parser
* Copyright(c) 2014 TJ Holowaychuk
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var cookie = require('cookie')
var signature = require('cookie-signature')
/**
* Module exports.
* @public
*/
module.exports = cookieParser
module.exports.JSONCookie = JSONCookie
module.exports.JSONCookies = JSONCookies
module.exports.signedCookie = signedCookie
module.exports.signedCookies = signedCookies
/**
* Parse Cookie header and populate `req.cookies`
* with an object keyed by the cookie names.
*
* @param {string|array} [secret] A string (or array of strings) representing cookie signing secret(s).
* @param {Object} [options]
* @return {Function}
* @public
*/
function cookieParser (secret, options) {
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret]
return function cookieParser (req, res, next) {
if (req.cookies) {
return next()
}
var cookies = req.headers.cookie
req.secret = secrets[0]
req.cookies = Object.create(null)
req.signedCookies = Object.create(null)
// no cookies
if (!cookies) {
return next()
}
req.cookies = cookie.parse(cookies, options)
// parse signed cookies
if (secrets.length !== 0) {
req.signedCookies = signedCookies(req.cookies, secrets)
req.signedCookies = JSONCookies(req.signedCookies)
}
// parse JSON cookies
req.cookies = JSONCookies(req.cookies)
next()
}
}
/**
* Parse JSON cookie string.
*
* @param {String} str
* @return {Object} Parsed object or undefined if not json cookie
* @public
*/
function JSONCookie (str) {
if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') {
return undefined
}
try {
return JSON.parse(str.slice(2))
} catch (err) {
return undefined
}
}
/**
* Parse JSON cookies.
*
* @param {Object} obj
* @return {Object}
* @public
*/
function JSONCookies (obj) {
var cookies = Object.keys(obj)
var key
var val
for (var i = 0; i < cookies.length; i++) {
key = cookies[i]
val = JSONCookie(obj[key])
if (val) {
obj[key] = val
}
}
return obj
}
/**
* Parse a signed cookie string, return the decoded value.
*
* @param {String} str signed cookie string
* @param {string|array} secret
* @return {String} decoded value
* @public
*/
function signedCookie (str, secret) {
if (typeof str !== 'string') {
return undefined
}
if (str.substr(0, 2) !== 's:') {
return str
}
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret]
for (var i = 0; i < secrets.length; i++) {
var val = signature.unsign(str.slice(2), secrets[i])
if (val !== false) {
return val
}
}
return false
}
/**
* Parse signed cookies, returning an object containing the decoded key/value
* pairs, while removing the signed key from obj.
*
* @param {Object} obj
* @param {string|array} secret
* @return {Object}
* @public
*/
function signedCookies (obj, secret) {
var cookies = Object.keys(obj)
var dec
var key
var ret = Object.create(null)
var val
for (var i = 0; i < cookies.length; i++) {
key = cookies[i]
val = obj[key]
dec = signedCookie(val, secret)
if (val !== dec) {
ret[key] = dec
delete obj[key]
}
}
return ret
}

View File

@@ -1,128 +0,0 @@
0.4.1 / 2020-04-21
==================
* Fix `maxAge` option to reject invalid values
0.4.0 / 2019-05-15
==================
* Add `SameSite=None` support
0.3.1 / 2016-05-26
==================
* Fix `sameSite: true` to work with draft-7 clients
- `true` now sends `SameSite=Strict` instead of `SameSite`
0.3.0 / 2016-05-26
==================
* Add `sameSite` option
- Replaces `firstPartyOnly` option, never implemented by browsers
* Improve error message when `encode` is not a function
* Improve error message when `expires` is not a `Date`
0.2.4 / 2016-05-20
==================
* perf: enable strict mode
* perf: use for loop in parse
* perf: use string concatination for serialization
0.2.3 / 2015-10-25
==================
* Fix cookie `Max-Age` to never be a floating point number
0.2.2 / 2015-09-17
==================
* Fix regression when setting empty cookie value
- Ease the new restriction, which is just basic header-level validation
* Fix typo in invalid value errors
0.2.1 / 2015-09-17
==================
* Throw on invalid values provided to `serialize`
- Ensures the resulting string is a valid HTTP header value
0.2.0 / 2015-08-13
==================
* Add `firstPartyOnly` option
* Throw better error for invalid argument to parse
* perf: hoist regular expression
0.1.5 / 2015-09-17
==================
* Fix regression when setting empty cookie value
- Ease the new restriction, which is just basic header-level validation
* Fix typo in invalid value errors
0.1.4 / 2015-09-17
==================
* Throw better error for invalid argument to parse
* Throw on invalid values provided to `serialize`
- Ensures the resulting string is a valid HTTP header value
0.1.3 / 2015-05-19
==================
* Reduce the scope of try-catch deopt
* Remove argument reassignments
0.1.2 / 2014-04-16
==================
* Remove unnecessary files from npm package
0.1.1 / 2014-02-23
==================
* Fix bad parse when cookie value contained a comma
* Fix support for `maxAge` of `0`
0.1.0 / 2013-05-01
==================
* Add `decode` option
* Add `encode` option
0.0.6 / 2013-04-08
==================
* Ignore cookie parts missing `=`
0.0.5 / 2012-10-29
==================
* Return raw cookie value if value unescape errors
0.0.4 / 2012-06-21
==================
* Use encode/decodeURIComponent for cookie encoding/decoding
- Improve server/client interoperability
0.0.3 / 2012-06-06
==================
* Only escape special characters per the cookie RFC
0.0.2 / 2012-06-01
==================
* Fix `maxAge` option to not throw error
0.0.1 / 2012-05-28
==================
* Add more tests
0.0.0 / 2012-05-28
==================
* Initial release

View File

@@ -1,24 +0,0 @@
(The MIT License)
Copyright (c) 2012-2014 Roman Shtylman <shtylman@gmail.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,257 +0,0 @@
# cookie
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Basic HTTP cookie parser and serializer for HTTP servers.
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```sh
$ npm install cookie
```
## API
```js
var cookie = require('cookie');
```
### cookie.parse(str, options)
Parse an HTTP `Cookie` header string and returning an object of all cookie name-value pairs.
The `str` argument is the string representing a `Cookie` header value and `options` is an
optional object containing additional parsing options.
```js
var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2');
// { foo: 'bar', equation: 'E=mc^2' }
```
#### Options
`cookie.parse` accepts these properties in the options object.
##### decode
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
has a limited character set (and must be a simple string), this function can be used to decode
a previously-encoded cookie value into a JavaScript string or other object.
The default function is the global `decodeURIComponent`, which will decode any URL-encoded
sequences into their byte representations.
**note** if an error is thrown from this function, the original, non-decoded cookie value will
be returned as the cookie's value.
### cookie.serialize(name, value, options)
Serialize a cookie name-value pair into a `Set-Cookie` header string. The `name` argument is the
name for the cookie, the `value` argument is the value to set the cookie to, and the `options`
argument is an optional object containing additional serialization options.
```js
var setCookie = cookie.serialize('foo', 'bar');
// foo=bar
```
#### Options
`cookie.serialize` accepts these properties in the options object.
##### domain
Specifies the value for the [`Domain` `Set-Cookie` attribute][rfc-6265-5.2.3]. By default, no
domain is set, and most clients will consider the cookie to apply to only the current domain.
##### encode
Specifies a function that will be used to encode a cookie's value. Since value of a cookie
has a limited character set (and must be a simple string), this function can be used to encode
a value into a string suited for a cookie's value.
The default function is the global `encodeURIComponent`, which will encode a JavaScript string
into UTF-8 byte sequences and then URL-encode any that fall outside of the cookie range.
##### expires
Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute][rfc-6265-5.2.1].
By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and
will delete it on a condition like exiting a web browser application.
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
so if both are set, they should point to the same date and time.
##### httpOnly
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute][rfc-6265-5.2.6]. When truthy,
the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set.
**note** be careful when setting this to `true`, as compliant clients will not allow client-side
JavaScript to see the cookie in `document.cookie`.
##### maxAge
Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute][rfc-6265-5.2.2].
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
so if both are set, they should point to the same date and time.
##### path
Specifies the value for the [`Path` `Set-Cookie` attribute][rfc-6265-5.2.4]. By default, the path
is considered the ["default path"][rfc-6265-5.1.4].
##### sameSite
Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-03-4.1.2.7].
- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
- `false` will not set the `SameSite` attribute.
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
More information about the different enforcement levels can be found in
[the specification][rfc-6265bis-03-4.1.2.7].
**note** This is an attribute that has not yet been fully standardized, and may change in the future.
This also means many clients may ignore this attribute until they understand it.
##### secure
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute][rfc-6265-5.2.5]. When truthy,
the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
**note** be careful when setting this to `true`, as compliant clients will not send the cookie back to
the server in the future if the browser does not have an HTTPS connection.
## Example
The following example uses this module in conjunction with the Node.js core HTTP server
to prompt a user for their name and display it back on future visits.
```js
var cookie = require('cookie');
var escapeHtml = require('escape-html');
var http = require('http');
var url = require('url');
function onRequest(req, res) {
// Parse the query string
var query = url.parse(req.url, true, true).query;
if (query && query.name) {
// Set a new cookie with the name
res.setHeader('Set-Cookie', cookie.serialize('name', String(query.name), {
httpOnly: true,
maxAge: 60 * 60 * 24 * 7 // 1 week
}));
// Redirect back after setting cookie
res.statusCode = 302;
res.setHeader('Location', req.headers.referer || '/');
res.end();
return;
}
// Parse the cookies on the request
var cookies = cookie.parse(req.headers.cookie || '');
// Get the visitor name set in the cookie
var name = cookies.name;
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
if (name) {
res.write('<p>Welcome back, <b>' + escapeHtml(name) + '</b>!</p>');
} else {
res.write('<p>Hello, new visitor!</p>');
}
res.write('<form method="GET">');
res.write('<input placeholder="enter your name" name="name"> <input type="submit" value="Set Name">');
res.end('</form>');
}
http.createServer(onRequest).listen(3000);
```
## Testing
```sh
$ npm test
```
## Benchmark
```
$ npm run bench
> cookie@0.3.1 bench cookie
> node benchmark/index.js
http_parser@2.8.0
node@6.14.2
v8@5.1.281.111
uv@1.16.1
zlib@1.2.11
ares@1.10.1-DEV
icu@58.2
modules@48
napi@3
openssl@1.0.2o
> node benchmark/parse.js
cookie.parse
6 tests completed.
simple x 1,200,691 ops/sec ±1.12% (189 runs sampled)
decode x 1,012,994 ops/sec ±0.97% (186 runs sampled)
unquote x 1,074,174 ops/sec ±2.43% (186 runs sampled)
duplicates x 438,424 ops/sec ±2.17% (184 runs sampled)
10 cookies x 147,154 ops/sec ±1.01% (186 runs sampled)
100 cookies x 14,274 ops/sec ±1.07% (187 runs sampled)
```
## References
- [RFC 6265: HTTP State Management Mechanism][rfc-6265]
- [Same-site Cookies][rfc-6265bis-03-4.1.2.7]
[rfc-6265bis-03-4.1.2.7]: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7
[rfc-6265]: https://tools.ietf.org/html/rfc6265
[rfc-6265-5.1.4]: https://tools.ietf.org/html/rfc6265#section-5.1.4
[rfc-6265-5.2.1]: https://tools.ietf.org/html/rfc6265#section-5.2.1
[rfc-6265-5.2.2]: https://tools.ietf.org/html/rfc6265#section-5.2.2
[rfc-6265-5.2.3]: https://tools.ietf.org/html/rfc6265#section-5.2.3
[rfc-6265-5.2.4]: https://tools.ietf.org/html/rfc6265#section-5.2.4
[rfc-6265-5.2.5]: https://tools.ietf.org/html/rfc6265#section-5.2.5
[rfc-6265-5.2.6]: https://tools.ietf.org/html/rfc6265#section-5.2.6
[rfc-6265-5.3]: https://tools.ietf.org/html/rfc6265#section-5.3
## License
[MIT](LICENSE)
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/cookie/master
[coveralls-url]: https://coveralls.io/r/jshttp/cookie?branch=master
[node-version-image]: https://badgen.net/npm/node/cookie
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/cookie
[npm-url]: https://npmjs.org/package/cookie
[npm-version-image]: https://badgen.net/npm/v/cookie
[travis-image]: https://badgen.net/travis/jshttp/cookie/master
[travis-url]: https://travis-ci.org/jshttp/cookie

View File

@@ -1,202 +0,0 @@
/*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict';
/**
* Module exports.
* @public
*/
exports.parse = parse;
exports.serialize = serialize;
/**
* Module variables.
* @private
*/
var decode = decodeURIComponent;
var encode = encodeURIComponent;
var pairSplitRegExp = /; */;
/**
* RegExp to match field-content in RFC 7230 sec 3.2
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
*/
var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
/**
* Parse a cookie header.
*
* Parse the given cookie header string into an object
* The object has the various cookies as keys(names) => values
*
* @param {string} str
* @param {object} [options]
* @return {object}
* @public
*/
function parse(str, options) {
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string');
}
var obj = {}
var opt = options || {};
var pairs = str.split(pairSplitRegExp);
var dec = opt.decode || decode;
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
var eq_idx = pair.indexOf('=');
// skip things that don't look like key=value
if (eq_idx < 0) {
continue;
}
var key = pair.substr(0, eq_idx).trim()
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
obj[key] = tryDecode(val, dec);
}
}
return obj;
}
/**
* Serialize data into a cookie header.
*
* Serialize the a name value pair into a cookie string suitable for
* http headers. An optional options object specified cookie parameters.
*
* serialize('foo', 'bar', { httpOnly: true })
* => "foo=bar; httpOnly"
*
* @param {string} name
* @param {string} val
* @param {object} [options]
* @return {string}
* @public
*/
function serialize(name, val, options) {
var opt = options || {};
var enc = opt.encode || encode;
if (typeof enc !== 'function') {
throw new TypeError('option encode is invalid');
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
var value = enc(val);
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
var str = name + '=' + value;
if (null != opt.maxAge) {
var maxAge = opt.maxAge - 0;
if (isNaN(maxAge) || !isFinite(maxAge)) {
throw new TypeError('option maxAge is invalid')
}
str += '; Max-Age=' + Math.floor(maxAge);
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
str += '; Domain=' + opt.domain;
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}
str += '; Path=' + opt.path;
}
if (opt.expires) {
if (typeof opt.expires.toUTCString !== 'function') {
throw new TypeError('option expires is invalid');
}
str += '; Expires=' + opt.expires.toUTCString();
}
if (opt.httpOnly) {
str += '; HttpOnly';
}
if (opt.secure) {
str += '; Secure';
}
if (opt.sameSite) {
var sameSite = typeof opt.sameSite === 'string'
? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true:
str += '; SameSite=Strict';
break;
case 'lax':
str += '; SameSite=Lax';
break;
case 'strict':
str += '; SameSite=Strict';
break;
case 'none':
str += '; SameSite=None';
break;
default:
throw new TypeError('option sameSite is invalid');
}
}
return str;
}
/**
* Try decoding a string using a decoding function.
*
* @param {string} str
* @param {function} decode
* @private
*/
function tryDecode(str, decode) {
try {
return decode(str);
} catch (e) {
return str;
}
}

View File

@@ -1,40 +0,0 @@
{
"name": "cookie",
"description": "HTTP server cookie parsing and serialization",
"version": "0.4.1",
"author": "Roman Shtylman <shtylman@gmail.com>",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>"
],
"license": "MIT",
"keywords": [
"cookie",
"cookies"
],
"repository": "jshttp/cookie",
"devDependencies": {
"beautify-benchmark": "0.2.4",
"benchmark": "2.1.4",
"eslint": "6.8.0",
"eslint-plugin-markdown": "1.0.2",
"mocha": "7.1.1",
"nyc": "15.0.1"
},
"files": [
"HISTORY.md",
"LICENSE",
"README.md",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"bench": "node benchmark/index.js",
"lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --reporter spec --bail --check-leaks --ui qunit test/",
"test-ci": "nyc --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test",
"version": "node scripts/version-history.js && git add HISTORY.md"
}
}

View File

@@ -1,45 +0,0 @@
{
"name": "cookie-parser",
"description": "Parse HTTP request cookies",
"version": "1.4.6",
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>"
],
"license": "MIT",
"repository": "expressjs/cookie-parser",
"keywords": [
"cookie",
"middleware"
],
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.25.2",
"eslint-plugin-markdown": "2.2.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.3.1",
"eslint-plugin-standard": "4.1.0",
"mocha": "9.1.3",
"nyc": "15.1.0",
"supertest": "6.1.6"
},
"files": [
"LICENSE",
"HISTORY.md",
"index.js"
],
"engines": {
"node": ">= 0.8.0"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec --bail --check-leaks test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

37
api/package-lock.json generated
View File

@@ -5,7 +5,6 @@
"packages": {
"": {
"dependencies": {
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.0",
@@ -187,26 +186,6 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -1265,22 +1244,6 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
},
"cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"requires": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
}
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",

View File

@@ -1,7 +1,6 @@
{
"type": "module",
"dependencies": {
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.0",

View File

@@ -1,13 +1,11 @@
// routes/dataRoutes.js
import express from "express";
import { createAndListItem, deleteItemBySku, getAllInventory, getOffers } from "../controllers/inventoryController.js";
import { addItem } from "../controllers/inventoryController.js";
const router = express.Router();
router.post("/create-list-item", createAndListItem);
router.get("/delete-item-by-sku", deleteItemBySku);
router.get("/get-all-inventory", getAllInventory);
router.get("/get-offers", getOffers)
router.post("/add-item", addItem);
// You can add more data-related routes here in the future
export default router;

10
api/routes/itemLookup.js Normal file
View File

@@ -0,0 +1,10 @@
// routes/itemLookup.js
import express from "express";
import itemLookup from "../controllers/itemLookup.js";
const router = express.Router();
// Use the itemLookup controller for the POST request to '/item-lookup'
router.get("/item-lookup", itemLookup);
export default router;

View File

@@ -4,7 +4,6 @@ import cors from "cors";
import dotenv from "dotenv";
import dataRoutes from "./routes/dataRoutes.js";
import inventoryRoutes from "./routes/inventoryRoutes.js";
import cookieParser from "cookie-parser";
dotenv.config();
@@ -12,68 +11,23 @@ const app = express();
app.use(cors());
app.use(express.json());
app.use(cookieParser());
// Use the itemLookupRoute with a base path, e.g., '/api'
app.use("/api/data", dataRoutes);
app.use("/api/inventory", inventoryRoutes);
/*
the below code needs to be encapsulated somehow. It requires a manual flow, including QUICKLY copy/pasting the code query string from the result of localhost:300/auth/ebay (below)
Here are the steps:
1. Go to localhost:3000/auth/ebay and copy/paste query string from code
2. Quickly go to the editor and paste that code into the const code= at /auth/ebay/callback
3. Go to localhost:3000/auth/ebay/callback and tokens will be in console - this is an 18 month deal, so we should tuck this code away
That is not really a callback... that was my intention, but it did not work out that way. That is simply an explicit API call to grab a user/refresh token from the manual process above
Next ToDos:
1. change the name of fetchEbayToken to fetchEbayReadToken
2. Add a fetchEbayUserToken endpoint and figure how to cycle user tokens every 2 hours with refresh token
3. Add refresh token to .env and figure out how to safely store user tokens serverside (cookie? knowledge gap here)
*/
app.get("/auth/ebay", async (req, res) => {
// the below URL is hardcoded because it's static in the eBay dev dashboard
const authUrl = `https://auth.ebay.com/oauth2/authorize?client_id=TylerPul-ebayimpo-PRD-a983027cf-9b6b8bba&response_type=code&redirect_uri=Tyler_Pulse-TylerPul-ebayim-ledkmyo&scope=https://api.ebay.com/oauth/api_scope https://api.ebay.com/oauth/api_scope/sell.marketing.readonly https://api.ebay.com/oauth/api_scope/sell.marketing https://api.ebay.com/oauth/api_scope/sell.inventory.readonly https://api.ebay.com/oauth/api_scope/sell.inventory https://api.ebay.com/oauth/api_scope/sell.account.readonly https://api.ebay.com/oauth/api_scope/sell.account https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly https://api.ebay.com/oauth/api_scope/sell.fulfillment https://api.ebay.com/oauth/api_scope/sell.analytics.readonly https://api.ebay.com/oauth/api_scope/sell.finances https://api.ebay.com/oauth/api_scope/sell.payment.dispute https://api.ebay.com/oauth/api_scope/commerce.identity.readonly https://api.ebay.com/oauth/api_scope/sell.reputation https://api.ebay.com/oauth/api_scope/sell.reputation.readonly https://api.ebay.com/oauth/api_scope/commerce.notification.subscription https://api.ebay.com/oauth/api_scope/commerce.notification.subscription.readonly https://api.ebay.com/oauth/api_scope/sell.stores https://api.ebay.com/oauth/api_scope/sell.stores.readonly`;
res.redirect(authUrl); // Redirect the user to eBay's sign-in page
});
// Step 2: Handle the redirect from eBay
app.get("/auth/ebay/callback", async (req, res) => {
// this code comes from a query string at localhost:3000/auth/ebay when we redirect
const code =
"v%5E1.1%23i%5E1%23p%5E3%23f%5E0%23r%5E1%23I%5E3%23t%5EUl41Xzc6NUE2ODc0NkU5Q0Q4N0QxQjhENTVCNzAxQTAwMEM2MzlfMF8xI0VeMjYw";
try {
// Step 3: Exchange the authorization code for access and refresh tokens
const tokenResponse = await fetch(
"https://api.ebay.com/identity/v1/oauth2/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(
`${process.env.EBAY_CLIENT_ID}:${process.env.EBAY_CLIENT_SECRET}`
).toString("base64")}`,
},
// we use Tyler_Pulse-TylerPul-ebayim-ledkmyo and not the initial redirect URL
body: `grant_type=authorization_code&code=${code}&redirect_uri=Tyler_Pulse-TylerPul-ebayim-ledkmyo`,
}
);
if (!tokenResponse.ok) {
throw new Error("Failed to exchange authorization code for tokens");
}
const tokenData = await tokenResponse.json();
console.log("Access Token:", tokenData.access_token);
console.log("Refresh Token:", tokenData.refresh_token);
res.send("Authentication successful! Tokens acquired."); // For demonstration purposes; you might want to redirect the user or show a different message
} catch (error) {
console.error("Error during token exchange:", error);
res.status(500).send("Internal Server Error");
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
app.listen(PORT, () => {
console.log(`Environment: \n\n
${JSON.stringify(process.env)}`);
console.log(`Server running on port ${PORT}`); //These will both get updated to smartLogging later
console.log(`System Settings: \n
port = ${process.env.PORT} \n
debug = ${process.env.DEBUG} \n
dotenv_key = ${process.env.DOTENV_KEY} \n
ebay_client_id = ${process.env.EBAY_CLIENT_ID} \n
ebay_client_secret = ${process.env.EBAY_CLIENT_SECRET} \n
ebay_dev_id = ${process.env.EBAY_DEV_ID} \n
ebay_redirect_uri = ${process.env.EBAY_REDIRECT_URI} \n
`);
});

View File

@@ -0,0 +1,63 @@
const buildAddFixedPriceItemRequestXML = (itemDetails) => {
// Constructing the XML payload using template literals
return `<?xml version="1.0" encoding="utf-8"?>
<AddFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<RequesterCredentials>
<eBayAuthToken>${itemDetails.userToken}</eBayAuthToken>
</RequesterCredentials>
<Item>
<Title>${itemDetails.title}</Title>
<Description><${itemDetails.description}></Description>
<PrimaryCategory>
<CategoryID>${itemDetails.categoryID}</CategoryID>
</PrimaryCategory>
<StartPrice>${itemDetails.startPrice}</StartPrice>
<ConditionID>${itemDetails.conditionID}</ConditionID>
<Country>${itemDetails.country}</Country>
<Currency>${itemDetails.currency}</Currency>
<DispatchTimeMax>${itemDetails.dispatchTimeMax}</DispatchTimeMax>
<ListingDuration>${itemDetails.listingDuration}</ListingDuration>
<ListingType>${itemDetails.listingType}</ListingType>
<PaymentMethods>
<PaymentMethod>${
itemDetails.paymentMethods
? itemDetails.paymentMethods[0]
: "DefaultPaymentMethod"
}</PaymentMethod>
</PaymentMethods>
<PayPalEmailAddress>${itemDetails.payPalEmailAddress}</PayPalEmailAddress>
<PictureDetails>
<PictureURL>${itemDetails.imageURLs}</PictureURL>
</PictureDetails>
<PostalCode>${itemDetails.postalCode}</PostalCode>
<Quantity>${itemDetails.quantity}</Quantity>
<ReturnPolicy>
<ReturnsAcceptedOption>${
itemDetails.returnsAcceptedOption
}</ReturnsAcceptedOption>
<RefundOption>${itemDetails.refundOption}</RefundOption>
<ReturnsWithinOption>${
itemDetails.returnsWithinOption
}</ReturnsWithinOption>
<Description>${itemDetails.returnPolicyDescription}</Description>
<ShippingCostPaidByOption>${
itemDetails.shippingCostPaidByOption
}</ShippingCostPaidByOption>
</ReturnPolicy>
<ShippingDetails>
<ShippingType>${itemDetails.shippingType}</ShippingType>
<ShippingServiceOptions>
<ShippingServicePriority>1</ShippingServicePriority>
<ShippingService>${itemDetails.shippingService}</ShippingService>
<ShippingServiceCost>${
itemDetails.shippingServiceCost
}</ShippingServiceCost>
</ShippingServiceOptions>
</ShippingDetails>
<Site>${itemDetails.site}</Site>
</Item>
</AddFixedPriceItemRequest>`;
};
export default buildAddFixedPriceItemRequestXML;

View File

@@ -1,7 +1,7 @@
// Need to figoure out expiration and make sure to cycle this appropriately to avoid unnecessary calls
import fetch from "node-fetch";
const fetchEbayApplicationToken = async () => {
const fetchEbayReadToken = async () => {
const ebayClientId = process.env.EBAY_CLIENT_ID;
const ebayClientSecret = process.env.EBAY_CLIENT_SECRET;
const credentials = Buffer.from(
@@ -15,7 +15,7 @@ const fetchEbayApplicationToken = async () => {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"authorization": `Basic ${credentials}`,
Authorization: `Basic ${credentials}`,
},
body: "grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope",
}
@@ -29,7 +29,6 @@ const fetchEbayApplicationToken = async () => {
}
const data = await response.json();
return data.access_token;
} catch (error) {
console.error("Error fetching eBay OAuth token:", error);
@@ -37,4 +36,4 @@ const fetchEbayApplicationToken = async () => {
}
};
export default fetchEbayApplicationToken;
export default fetchEbayReadToken;

View File

@@ -1,66 +1,41 @@
import fetch from "node-fetch";
const fetchEbayUserToken = async (req, res) => {
const ebayClientId = process.env.EBAY_CLIENT_ID;
const ebayClientSecret = process.env.EBAY_CLIENT_SECRET;
const refreshToken = process.env.EBAY_REFRESH_TOKEN; // This is retrieved from a manual process
const credentials = Buffer.from(`${ebayClientId}:${ebayClientSecret}`).toString("base64");
const scopes = encodeURIComponent([
"https://api.ebay.com/oauth/api_scope",
"https://api.ebay.com/oauth/api_scope/sell.marketing.readonly",
"https://api.ebay.com/oauth/api_scope/sell.marketing",
"https://api.ebay.com/oauth/api_scope/sell.inventory.readonly",
"https://api.ebay.com/oauth/api_scope/sell.inventory",
"https://api.ebay.com/oauth/api_scope/sell.account.readonly",
"https://api.ebay.com/oauth/api_scope/sell.account",
"https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly",
"https://api.ebay.com/oauth/api_scope/sell.fulfillment",
"https://api.ebay.com/oauth/api_scope/sell.analytics.readonly",
"https://api.ebay.com/oauth/api_scope/sell.finances",
"https://api.ebay.com/oauth/api_scope/sell.payment.dispute",
"https://api.ebay.com/oauth/api_scope/commerce.identity.readonly",
"https://api.ebay.com/oauth/api_scope/sell.reputation",
"https://api.ebay.com/oauth/api_scope/sell.reputation.readonly",
"https://api.ebay.com/oauth/api_scope/commerce.notification.subscription",
"https://api.ebay.com/oauth/api_scope/commerce.notification.subscription.readonly",
"https://api.ebay.com/oauth/api_scope/sell.stores",
"https://api.ebay.com/oauth/api_scope/sell.stores.readonly"
].join(' '));
const token = req.cookies.ebayUserToken;
const tokenExpiry = req.cookies.ebayUserTokenExpiry ? new Date(req.cookies.ebayUserTokenExpiry) : null;
// Check if the token exists and is not expired
if (token && tokenExpiry && new Date() < tokenExpiry) {
return token; // Token is valid, use it
}
const fetchEbayUserToken = async (authorizationCode) => {
const clientId = process.env.EBAY_CLIENT_ID;
const clientSecret = process.env.EBAY_CLIENT_SECRET;
const redirectUri = process.env.EBAY_REDIRECT_URI; // Make sure this matches the URI registered with eBay
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString(
"base64"
);
try {
const response = await fetch("https://api.ebay.com/identity/v1/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": `Basic ${credentials}`,
},
body: `grant_type=refresh_token&refresh_token=${refreshToken}&scope=${scopes}`,
});
const response = await fetch(
"https://api.ebay.com/identity/v1/oauth2/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${credentials}`,
},
body: `grant_type=authorization_code&code=${authorizationCode}&redirect_uri=${encodeURIComponent(
redirectUri
)}`,
}
);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Failed to fetch eBay user token: ${response.status} ${response.statusText} - ${errorBody}`);
throw new Error(
`Failed to fetch eBay user token: ${response.status} ${response.statusText} - ${errorBody}`
);
}
const data = await response.json();
const expiryDuration = 1.92 * 60 * 60 * 1000; // 1 hour 55 minutes in milliseconds
// Store the new token in an HTTP-only cookie
res.cookie('ebayUserToken', data.access_token, { httpOnly: true, maxAge: expiryDuration });
return data.access_token;
return data.access_token; // This is the User access token
} catch (error) {
console.error("Error fetching eBay user token:", error);
throw error;
}
};
export default fetchEbayUserToken
export default fetchEbayUserToken;

View File

@@ -4,5 +4,15 @@ services:
build: ./api
ports:
- "3000:3000"
env_file:
- .env
# environment:
# PORT: 8005
# DEBUG: "false"
# DOTENV_KEY: "DotKeyDC"
# EBAY_CLIENT_ID: "EbayClientIDDC"
# EBAY_CLIENT_SECRET: "EbayClientSecretDC"
# EBAY_DEV_ID: "EbayDevIDDC"
# EBAY_REDIRECT_URI: "EbayRedirectURIDC"
volumes:
- ./api:/usr/src/app # Mount the local ./api directory to /usr/src/app in the container

11
envSample Normal file
View File

@@ -0,0 +1,11 @@
PORT=8003
DEBUG=true
DOTENV_KEY=DotKey
EBAY_CLIENT_ID=EbayClientID
EBAY_CLIENT_SECRET=EbayClientSecret
EBAY_DEV_ID=EbayDevID
EBAY_REDIRECT_URI=EbayRedirectURI
#I'm not 100% on this, but I think the --env-file flag for docker containers should handle this. We just
#need to figure out how that gets consumed.