Initial commit
This commit is contained in:
138
api/hooks/action/post_create.js
Normal file
138
api/hooks/action/post_create.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const { frontendBase, jwtSecret, logoPath, noReplyEmail, operatorEmail, serverBaseURL } = require("../config")
|
||||
const { getCustomerById } = require("../lib/bigcommerceRestAPI")
|
||||
const { createNewsletterSubscriber, checkIfNewsletterSubscriber } = require("../lib/omnisendRestApi")
|
||||
const { cryptchaCheck } = require("../lib/utils")
|
||||
;(function () {
|
||||
const operation = context.data.operation
|
||||
if (operation === "requestPasswordReset") {
|
||||
cryptchaCheck()
|
||||
requestPasswordReset()
|
||||
} else if (operation === "checkUsernameTaken") {
|
||||
const username = context.data.username
|
||||
const user = context.db.find("bigCommerceCustomer", {
|
||||
filter: { username: username.toLowerCase() },
|
||||
})[0]
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
data: !!user,
|
||||
}
|
||||
} else if (operation === "reportRecord") {
|
||||
const customerId = context.data.customerId
|
||||
if (!customerId) {
|
||||
throw {
|
||||
status: 400,
|
||||
message: "customerId is required",
|
||||
}
|
||||
}
|
||||
const recordTitle = context.data.record
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: operatorEmail,
|
||||
from: noReplyEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Record Reported",
|
||||
plain: "Record reported, customer ID: " + customerId + ", record: " + recordTitle,
|
||||
})
|
||||
throw {
|
||||
status: 200,
|
||||
message: "Record reported",
|
||||
}
|
||||
} else if (operation === "subscribeToNewsletter") {
|
||||
if (!context.data.email) {
|
||||
throw {
|
||||
status: 400,
|
||||
message: "Email is required",
|
||||
}
|
||||
}
|
||||
createNewsletterSubscriber(context.data.email?.toLowerCase())
|
||||
throw {
|
||||
status: 200,
|
||||
message: "Newsletter subscriber created",
|
||||
}
|
||||
} else if (operation === "checkIfNewsletterSubscriber") {
|
||||
const email = context.data.email
|
||||
const isSubscriber = checkIfNewsletterSubscriber(email.toLowerCase())
|
||||
throw {
|
||||
status: 200,
|
||||
data: isSubscriber,
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} tibiId
|
||||
* @param {string} bigCommerceId
|
||||
* @param {string} key
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildPwResetToken(tibiId, bigCommerceId, key) {
|
||||
return context.jwt.create(
|
||||
{
|
||||
tibiId: tibiId,
|
||||
bigCommerceId: bigCommerceId,
|
||||
check: key,
|
||||
},
|
||||
{
|
||||
secret: jwtSecret,
|
||||
validityDuration: 60 * 60 * 24 * 90, // 90 days
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function requestPasswordReset() {
|
||||
const random256BitString = Math.random().toString(36).substring(2, 34)
|
||||
const email = context.data.email
|
||||
if (!email) {
|
||||
throw {
|
||||
status: 400,
|
||||
message: "Email is required",
|
||||
}
|
||||
}
|
||||
let customer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: email?.toLowerCase() },
|
||||
})[0]
|
||||
if (!customer) {
|
||||
const customerBigCommerce = getCustomerById(null, email)
|
||||
if (!customerBigCommerce) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "Customer not found",
|
||||
}
|
||||
}
|
||||
const username = customerBigCommerce.form_fields.find((field) => field.name === "username")?.value
|
||||
customer = context.db.create("bigCommerceCustomer", {
|
||||
bigCommerceId: customerBigCommerce.id,
|
||||
email: customerBigCommerce.email.toLowerCase(),
|
||||
username: username ? username?.toLowerCase() : Math.random().toString(36).substring(2, 15),
|
||||
})
|
||||
}
|
||||
|
||||
const pwResetToken = buildPwResetToken(customer.id, customer.bigCommerceId, random256BitString)
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
pwReset: `${frontendBase}profile/reset-password?token=${pwResetToken}`,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/requestPWReset.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Passwort Zurücksetzen",
|
||||
html,
|
||||
})
|
||||
context.db.update("bigCommerceCustomer", customer.id, {
|
||||
currentToken: random256BitString,
|
||||
})
|
||||
throw {
|
||||
status: 200,
|
||||
message: "Password reset email sent",
|
||||
}
|
||||
}
|
||||
112
api/hooks/backups/post_create.js
Normal file
112
api/hooks/backups/post_create.js
Normal file
@@ -0,0 +1,112 @@
|
||||
;(function () {
|
||||
let backup = context.data
|
||||
const updateLogs = compareAndUpdateEntry(backup?.entry, backup?.collectionName, backup?.versionNr - 1)
|
||||
backup.updateLogs = updateLogs
|
||||
return { data: backup }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} entry
|
||||
* @param {string} collectionName
|
||||
* @param {number} versionNr
|
||||
* @returns
|
||||
*/
|
||||
function compareAndUpdateEntry(entry, collectionName, versionNr) {
|
||||
let updateLogs
|
||||
if (versionNr == 0) {
|
||||
updateLogs = getUpdateLogs({}, entry)
|
||||
}
|
||||
delete entry.updateTime
|
||||
let previousEntry = context.db.find("backups", {
|
||||
filter: { entryId: context.data.entryId, versionNr, collectionName },
|
||||
})[0]
|
||||
if (!previousEntry) {
|
||||
console.error("No previous entry found")
|
||||
updateLogs = getUpdateLogs({}, entry)
|
||||
} else {
|
||||
delete previousEntry.updateTime
|
||||
updateLogs = getUpdateLogs(previousEntry.entry, entry)
|
||||
}
|
||||
console.log(context.json.stringify(previousEntry?.entry), context.json.stringify(entry))
|
||||
|
||||
return updateLogs
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any[]} array
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function filterValidObjects(array) {
|
||||
return array.filter((object) => {
|
||||
for (let key in object) {
|
||||
if (typeof object[key] !== "string" && object[key] !== null) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} oldObj
|
||||
* @param {Object} newObj
|
||||
* @param {string} path
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function getUpdateLogs(oldObj = {}, newObj = {}, path = "") {
|
||||
let updateLogs = []
|
||||
const ignoredKeys = ["id", "insertTime", "updateTime"]
|
||||
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)])
|
||||
|
||||
allKeys.forEach((key) => {
|
||||
if (ignoredKeys.includes(key)) return
|
||||
|
||||
const newPath = path ? `${path}.${key}` : key
|
||||
const oldVal = oldObj.hasOwnProperty(key) ? oldObj[key] : ""
|
||||
const newVal = newObj.hasOwnProperty(key) ? newObj[key] : ""
|
||||
|
||||
// Handle Arrays
|
||||
if (Array.isArray(oldVal) || Array.isArray(newVal)) {
|
||||
const oldArr = oldVal || []
|
||||
const newArr = newVal || []
|
||||
|
||||
for (let i = 0; i < Math.max(oldArr.length, newArr.length); i++) {
|
||||
const itemPath = `${newPath}[${i}]`
|
||||
|
||||
if (oldArr[i] !== newArr[i]) {
|
||||
if (typeof oldArr[i] === "object" || typeof newArr[i] === "object") {
|
||||
const arrUpdates = getUpdateLogs(oldArr[i], newArr[i], itemPath)
|
||||
updateLogs = updateLogs.concat(arrUpdates)
|
||||
} else {
|
||||
updateLogs.push({
|
||||
field: itemPath,
|
||||
previous: oldArr[i] === undefined ? "" : oldArr[i].toString(),
|
||||
current: newArr[i] === undefined ? "" : newArr[i].toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle nested objects but not arrays
|
||||
else if (
|
||||
(typeof oldVal === "object" && oldVal !== null) ||
|
||||
(typeof newVal === "object" && newVal !== null)
|
||||
) {
|
||||
const nestedUpdates = getUpdateLogs(oldVal || {}, newVal || {}, newPath)
|
||||
updateLogs = updateLogs.concat(nestedUpdates)
|
||||
}
|
||||
// Handle primitive types
|
||||
else if (oldVal !== newVal) {
|
||||
updateLogs.push({
|
||||
field: newPath,
|
||||
previous: oldVal?.toString(),
|
||||
current: newVal?.toString(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return filterValidObjects(updateLogs)
|
||||
}
|
||||
})()
|
||||
46
api/hooks/cart/get_read.js
Normal file
46
api/hooks/cart/get_read.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { getCart, getRedirectUrl } = require("../lib/bigcommerceRestAPI")
|
||||
const { bigcommerceStoreHash, bigcommerceBaseURL, bigCommerceCliendId, bigCommerceClientSecret } = require("../config")
|
||||
const { getCustomerById, getLoginUrl } = require("../lib/bigcommerceRestAPI")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { mapRestApiCartToGraphQL } = require("./helper")
|
||||
;(function () {
|
||||
if (context.user.auth()) return {}
|
||||
const checkout = context.request().query("checkout")
|
||||
const loggedInCheckout = context.request().query("loggedInCheckout")
|
||||
if (checkout) {
|
||||
if (loggedInCheckout) {
|
||||
withAccount((loginClaims) => {
|
||||
const cartId = context.request().param("id")
|
||||
const cart = getCart(cartId)
|
||||
const { checkoutURL } = getRedirectUrl(cartId)
|
||||
const loginUrl = getLoginUrl(
|
||||
loginClaims.bigCommerceId,
|
||||
bigcommerceStoreHash,
|
||||
bigcommerceBaseURL,
|
||||
bigCommerceCliendId,
|
||||
bigCommerceClientSecret,
|
||||
checkoutURL.split(bigcommerceBaseURL).pop()
|
||||
)
|
||||
throw {
|
||||
status: 200,
|
||||
data: cart && {
|
||||
cart: mapRestApiCartToGraphQL(cart),
|
||||
checkoutURL: loginUrl,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const cartId = context.request().param("id")
|
||||
const cart = getCart(cartId)
|
||||
const { checkoutURL, embeddedCheckoutURL } = getRedirectUrl(cartId)
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
data: cart && {
|
||||
cart: mapRestApiCartToGraphQL(cart),
|
||||
checkoutURL: checkoutURL,
|
||||
embeddedCheckoutURL: embeddedCheckoutURL,
|
||||
},
|
||||
}
|
||||
})()
|
||||
53
api/hooks/cart/helper.js
Normal file
53
api/hooks/cart/helper.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
*
|
||||
* @param {RestApiCart} cart
|
||||
* @returns {BigCommerceCart}
|
||||
*/
|
||||
|
||||
function mapRestApiCartToGraphQL(cart) {
|
||||
return {
|
||||
entityId: cart.id,
|
||||
currencyCode: cart.currency.code,
|
||||
isTaxIncluded: cart.tax_included,
|
||||
baseAmount: { value: cart.base_amount, currencyCode: cart.currency.code },
|
||||
discountedAmount: { value: cart.discount_amount, currencyCode: cart.currency.code },
|
||||
amount: { value: cart.cart_amount, currencyCode: cart.currency.code },
|
||||
discounts: cart.discounts.map((d) => ({
|
||||
entityId: d.id,
|
||||
discountedAmount: { value: d.discounted_amount, currencyCode: cart.currency.code },
|
||||
})),
|
||||
lineItems: {
|
||||
physicalItems: cart.line_items.physical_items.map((item) => ({
|
||||
entityId: item.id,
|
||||
parentEntityId: cart.parent_id,
|
||||
productEntityId: item.product_id,
|
||||
variantEntityId: item.variant_id,
|
||||
sku: item.sku,
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
imageUrl: item.image_url,
|
||||
brand: "BKDF", // REST API does not provide brand directly
|
||||
quantity: item.quantity,
|
||||
isTaxable: item.is_taxable,
|
||||
listPrice: { value: item.list_price, currencyCode: cart.currency.code },
|
||||
extendedListPrice: { value: item.extended_list_price, currencyCode: cart.currency.code },
|
||||
selectedOptions: item?.options?.map((option) => ({
|
||||
// Map each option to the GraphQL format
|
||||
// Assuming a simplified structure here for the example
|
||||
})),
|
||||
isShippingRequired: true, // Example assumption
|
||||
})),
|
||||
digitalItems: [],
|
||||
customItems: [],
|
||||
giftCertificates: [],
|
||||
totalQuantity: cart.line_items.physical_items.reduce((total, item) => total + item.quantity, 0),
|
||||
},
|
||||
createdAt: { utc: new Date(cart.created_time) },
|
||||
updatedAt: { utc: new Date(cart.updated_time) },
|
||||
locale: cart.locale,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapRestApiCartToGraphQL,
|
||||
}
|
||||
25
api/hooks/cart/post_validate.js
Normal file
25
api/hooks/cart/post_validate.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { addCartItem, updateCartItem, deleteCartItem, getCart } = require("../lib/bigcommerceRestAPI")
|
||||
const { postAddToCart } = require("../lib/facebookRestAPI")
|
||||
|
||||
;(function () {
|
||||
const operation = context?.data?.operation
|
||||
|
||||
if (operation == "add") {
|
||||
addCartItem(context?.data?.cartId, context?.data?.lineItems)
|
||||
try {
|
||||
postAddToCart()
|
||||
} catch (e) {}
|
||||
} else if (operation == "update") {
|
||||
updateCartItem(context?.data?.cartId, context?.data?.lineItem, context?.data?.entityId)
|
||||
try {
|
||||
postAddToCart()
|
||||
} catch (e) {}
|
||||
} else if (operation == "delete") {
|
||||
deleteCartItem(context?.data?.cartId, context?.data?.entityId)
|
||||
}
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
message: "success",
|
||||
}
|
||||
})()
|
||||
5
api/hooks/clear_cache.js
Normal file
5
api/hooks/clear_cache.js
Normal file
@@ -0,0 +1,5 @@
|
||||
var utils = require("./lib/utils")
|
||||
|
||||
;(function () {
|
||||
utils.clearSSRCache()
|
||||
})()
|
||||
59
api/hooks/config-client.js
Normal file
59
api/hooks/config-client.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const release = "binkrassdufass.dirty"
|
||||
const apiClientBaseURL = "/api/"
|
||||
|
||||
const originURL = "https://bkdf-tibi-2024.code.testversion.online"
|
||||
const cryptchaSiteId = "66cb701e093846000151707e"
|
||||
|
||||
// @ts-ignore
|
||||
if (release && typeof context !== "undefined") {
|
||||
context.response.header("X-Release", release)
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {number} price
|
||||
* @param {number} quantity
|
||||
|
||||
* @returns {number|string}
|
||||
*/
|
||||
function formatPrice(price, quantity = 1) {
|
||||
const newPrice = Math.floor((Math.round(price * 100) / 100) * quantity)
|
||||
return newPrice.toFixed(2) + " €"
|
||||
}
|
||||
const daysForReturn = 30
|
||||
const daysToArrive = 3
|
||||
/**
|
||||
*
|
||||
* @param {Date} countdownStart
|
||||
* @returns {Date}
|
||||
*/
|
||||
function getLastDayForReturn(countdownStart) {
|
||||
const date = new Date(countdownStart)
|
||||
date.setDate(date.getDate() + daysForReturn + daysToArrive)
|
||||
return date
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Date} countdownStart
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkIfOrderMayArrived(countdownStart) {
|
||||
const date = new Date(countdownStart)
|
||||
date.setDate(date.getDate() + daysToArrive)
|
||||
return date < new Date()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
release,
|
||||
apiClientBaseURL,
|
||||
frontendBase: "https://binkrassdufass.de/",
|
||||
tibiUrl: "https://dev.tibicms.de",
|
||||
bkdfApiUrl: "https://binkrassdufass.de/api/",
|
||||
originURL,
|
||||
cryptchaSiteId,
|
||||
formatPrice,
|
||||
daysForReturn: daysForReturn,
|
||||
getLastDayForReturn,
|
||||
daysToArrive,
|
||||
checkIfOrderMayArrived,
|
||||
}
|
||||
55
api/hooks/config.js
Normal file
55
api/hooks/config.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const apiSsrBaseURL = "http://localhost:8080/api/v1/_/bkdf_tibi_2024/"
|
||||
const { frontendBase, tibiUrl } = require("./config-client")
|
||||
module.exports = {
|
||||
operatorEmail: "info@binkrassdufass.de",
|
||||
operatorName: "BinKrassDuFass",
|
||||
contactEmail: "support@binkrassdufass.de",
|
||||
noReplyEmail: "noreply@binkrassdufass.de",
|
||||
frontendBase,
|
||||
apiBase: frontendBase + "/api/",
|
||||
tibiUrl,
|
||||
apiSsrBaseURL,
|
||||
ssrValidatePath: function (/** @type {string} */ path) {
|
||||
// validate if path ssr rendering is ok, -1 = NOTFOUND, 0 = NO SSR, 1 = SSR
|
||||
// pe. use context.readCollection("product", {filter: {path: path}}) ... to validate dynamic urls
|
||||
|
||||
// // / is de home
|
||||
// if (path == "/") return 1
|
||||
|
||||
// // all other sites are in db
|
||||
//path = path?.replace(/^\//, "")
|
||||
if (path.includes("profile") || path.includes("@") || path.includes("redirectToProfile")) return 0
|
||||
const resp = context.db.find("content", {
|
||||
filter: {
|
||||
$and: [{ $or: [{ path }, { "alternativePaths.path": path }] }],
|
||||
},
|
||||
|
||||
selector: { _id: 1 },
|
||||
})
|
||||
if (resp && resp.length) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// not found
|
||||
return -1
|
||||
},
|
||||
ssrPublishCheckCollections: ["content"],
|
||||
LIGHTHOUSE_TOKEN: "AIzaSyC0UxHp3-MpJiDL3ws7pEV6lj57bfIc7GQ",
|
||||
bigcommerceApiOAuth: "92tgx3by44vmizj6gu1ev4jjdjqu2rb",
|
||||
serverBaseURL: "https://www.binkrassdufass.de/api/", // "https://bkdf-tibi-2024-tibiserver.code.testversion.online/api/v1/_/bkdf_tibi_2024/", // LIVE:
|
||||
bigcommerceStoreHash: "punbvyqteo",
|
||||
channelId: 1578456,
|
||||
channelName: "BinKrassDuFass",
|
||||
logoPath: "_/assets/logo/logo-white.svg",
|
||||
jwtSecret: "7456ztjihugf@@ofg#iehuifdnfgobvcxsdafa+rpeertuhbfgnvcxfasdijnfvdssecretodhxfgoidfsjgpias",
|
||||
masterPassword: "ycxfvöklpcxylkäpöcdsyüäp#öldsa#öpü+äAds#+äüööü+ä#pcxdsäö.#cvx.vl,bcxlvöä,cbkmköldfgoplü+fsda",
|
||||
DeepLkey: "1a248829-fcd8-40e2-8437-c3fa1d2a416e",
|
||||
jwtValidityDuration: 600,
|
||||
bigCommerceCliendId: "3zx5dwd9n9h7w72d2yg8845g2b28m5x",
|
||||
bigCommerceClientSecret: "444dc8e9ce6fea29110cff1238a952f4b807655c1b1175cbbea8bd039e236e7d",
|
||||
omnisendApiKey: "6682ded7516e60cf4800023f-HHGGCOm2tCuPdb81NLo3K018AjqekWL3gFGWlNWae08NbJbdk2",
|
||||
bigcommerceBaseURL: "https://store-punbvyqteo.mybigcommerce.com",
|
||||
fb_accessToken:
|
||||
"EAA2c78abUOoBOxX9IPbHZBSJ6rZA0tqpv6jkZCCRzZA6OvIHkTbl9h02MwoBKu8ZAqFGfYFSOXV0nBwNJ9f6319PyGdtfUrfpECZCdQj1a6cAJJaKxcguSDNVMYr5aSQ1UlovOGaaQeMzwVUW1LKzx3KFq1m7Vwl30xTMjQTaQSYVk9zIoTlySqv2pFZBZAuCasPsAZDZD",
|
||||
printfulAPIToken: "dtHTQ8JMe4eeBklIIfcZf2YHtq3NgKhwmhIC7FND",
|
||||
}
|
||||
8
api/hooks/contact/post_create.js
Normal file
8
api/hooks/contact/post_create.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const { cryptchaCheck } = require("../lib/utils")
|
||||
;(function () {
|
||||
throw {
|
||||
status: 500,
|
||||
data: "Hello, World!",
|
||||
}
|
||||
cryptchaCheck()
|
||||
})()
|
||||
11
api/hooks/contact/post_return.js
Normal file
11
api/hooks/contact/post_return.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { noReplyEmail, contactEmail } = require("../config")
|
||||
;(function () {
|
||||
context.smtp.sendMail({
|
||||
to: contactEmail,
|
||||
from: noReplyEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "New Contact Request",
|
||||
plain: "Neue Kontaktanfrage",
|
||||
})
|
||||
})()
|
||||
55
api/hooks/customer/get_read.js
Normal file
55
api/hooks/customer/get_read.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { getCustomerAddressById, getCustomerAddresses, getCustomerById } = require("../lib/bigcommerceRestAPI")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
console.log("wtf")
|
||||
if (context.user.auth()) return
|
||||
const request = context.request()
|
||||
const queryUsername = request.query("username")
|
||||
if (queryUsername) {
|
||||
const user = context.db.find("bigCommerceCustomer", {
|
||||
filter: { username: queryUsername },
|
||||
})[0]
|
||||
if (!user) {
|
||||
throw { status: 404, error: "customer not found" }
|
||||
}
|
||||
throw {
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
socialMediaAccounts: user.socialMediaAccounts,
|
||||
personalRecords: user.personalRecords,
|
||||
},
|
||||
status: 200,
|
||||
}
|
||||
}
|
||||
const foreign = request.query("foreign")
|
||||
if (foreign) {
|
||||
const id = request.param("id")
|
||||
const user = context.db.find("bigCommerceCustomer", {
|
||||
filter: { _id: id },
|
||||
})[0]
|
||||
if (!user) {
|
||||
throw { status: 404, error: "customer not found" }
|
||||
}
|
||||
throw {
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
socialMediaAccounts: user.socialMediaAccounts,
|
||||
personalRecords: user.personalRecords,
|
||||
},
|
||||
status: 200,
|
||||
}
|
||||
}
|
||||
withAccount((loginClaims) => {
|
||||
const queryAddressId = request.query("address")
|
||||
const queryAddresses = request.query("addresses")
|
||||
|
||||
if (queryAddressId) {
|
||||
throw { data: getCustomerAddressById(loginClaims.bigCommerceId, Number(queryAddressId)), status: 200 }
|
||||
} else if (queryAddresses) {
|
||||
throw { data: getCustomerAddresses(loginClaims.bigCommerceId), status: 200 }
|
||||
} else throw { data: getCustomerById(loginClaims.bigCommerceId), status: 200 }
|
||||
})
|
||||
throw { status: 401, error: "unauthorized", log: false }
|
||||
})()
|
||||
0
api/hooks/customer/post_create.js
Normal file
0
api/hooks/customer/post_create.js
Normal file
250
api/hooks/customer/put_update.js
Normal file
250
api/hooks/customer/put_update.js
Normal file
@@ -0,0 +1,250 @@
|
||||
const {
|
||||
updateCustomer,
|
||||
updateCustomerAddressById,
|
||||
deleteCustomerAddressById,
|
||||
addCustomerAddress,
|
||||
validateCredentials,
|
||||
getCustomerById,
|
||||
} = require("../lib/bigcommerceRestAPI")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { getJwt } = require("../lib/utils")
|
||||
const emailRegex =
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
let data = context.data
|
||||
const r = context.request()
|
||||
const customerId = r.param("id")
|
||||
|
||||
const backendAuth = context.user.auth()
|
||||
|
||||
let returnCustomer = undefined
|
||||
if (!backendAuth) {
|
||||
// require authorization header with jwt
|
||||
const token = getJwt(context)
|
||||
/** @type {JWTPwResetClaims} */ // @ts-ignore
|
||||
const pwResetClaims = token.claims
|
||||
/** @type {Customer} */ // @ts-ignore
|
||||
const customer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { _id: pwResetClaims.tibiId },
|
||||
})[0]
|
||||
|
||||
if (!customer) {
|
||||
throw {
|
||||
status: 404,
|
||||
error: "customer not found",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (data.operation === "resetPassword") {
|
||||
if (pwResetClaims && pwResetClaims.tibiId && pwResetClaims.check) {
|
||||
// is password reset token since it has .check
|
||||
if (pwResetClaims.tibiId != customerId)
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token not valid for this id",
|
||||
log: false,
|
||||
}
|
||||
|
||||
if (pwResetClaims.check != customer.currentToken)
|
||||
throw {
|
||||
status: 403,
|
||||
error: "password reset token expired",
|
||||
log: false,
|
||||
}
|
||||
updateCustomer({
|
||||
authentication: {
|
||||
force_password_reset: false,
|
||||
new_password: data.password,
|
||||
},
|
||||
id: Number(customer.bigCommerceId),
|
||||
})
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
log: false,
|
||||
}
|
||||
} else {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "invalid token",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
withAccount((loginClaims) => {
|
||||
/** @type {BigCommerceCustomer} */
|
||||
const customer = data.customer
|
||||
if (customer) customer.id = Number(loginClaims.bigCommerceId)
|
||||
if (data.operation === "updateCustomer") {
|
||||
customer.email = loginClaims.email
|
||||
const index = customer.form_fields.findIndex((f) => f.name === "username")
|
||||
const internalCustomer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: customer.email },
|
||||
})[0]
|
||||
if (index >= 0) customer.form_fields[index].value = internalCustomer.username
|
||||
const response = updateCustomer(customer)
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
data: response,
|
||||
message: "customer updated",
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation == "updateInternalCustomer") {
|
||||
const customer = data.customer
|
||||
returnCustomer = customer
|
||||
} else if (data.operation === "updateCustomerAddress") {
|
||||
/** @type {BigCommerceAddress} */
|
||||
const address = data.address
|
||||
address.customer_id = Number(loginClaims.bigCommerceId)
|
||||
const response = updateCustomerAddressById(address)
|
||||
throw {
|
||||
status: 200,
|
||||
message: "customer addresses updated",
|
||||
data: response,
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation === "deleteCustomerAddress") {
|
||||
deleteCustomerAddressById(Number(loginClaims.bigCommerceId), data.addressId)
|
||||
throw {
|
||||
status: 200,
|
||||
message: "customer address deleted",
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation === "addCustomerAddress") {
|
||||
/** @type {BigCommerceAddress} */
|
||||
const address = data.address
|
||||
address.customer_id = Number(loginClaims.bigCommerceId)
|
||||
delete address.id
|
||||
const response = addCustomerAddress(address)
|
||||
throw {
|
||||
status: 200,
|
||||
message: "customer addresses updated",
|
||||
data: response,
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation === "changePassword") {
|
||||
const res = validateCredentials(loginClaims.email, data.currentPassword)
|
||||
if (!res.is_valid) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "current password incorrect",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
const resPw = updateCustomer({
|
||||
authentication: {
|
||||
force_password_reset: false,
|
||||
new_password: data.newPassword,
|
||||
},
|
||||
id: Number(loginClaims.bigCommerceId),
|
||||
})
|
||||
throw {
|
||||
status: 200,
|
||||
message: "password reset successful",
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation === "updateEmail") {
|
||||
const resValidation = validateCredentials(loginClaims.email, data.password)
|
||||
if (!resValidation.is_valid) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "password incorrect",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
if (!data.email)
|
||||
throw {
|
||||
status: 400,
|
||||
error: "email is required",
|
||||
log: false,
|
||||
}
|
||||
data.email = data.email.toLowerCase()
|
||||
const customers = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: data.email },
|
||||
})
|
||||
if (customers.length > 0)
|
||||
throw {
|
||||
status: 409,
|
||||
error: "email already in use",
|
||||
log: false,
|
||||
}
|
||||
if (emailRegex.test(data.email) === false) {
|
||||
throw {
|
||||
status: 400,
|
||||
error: "invalid email",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
const res = updateCustomer({ id: Number(loginClaims.bigCommerceId), email: data.email })
|
||||
context.db.update("bigCommerceCustomer", loginClaims.tibiId, { email: data.email })
|
||||
throw {
|
||||
status: 200,
|
||||
message: "customer updated",
|
||||
data: res,
|
||||
log: false,
|
||||
}
|
||||
} else if (data.operation === "updateUsername") {
|
||||
const resValidation = validateCredentials(loginClaims.email, data.password)
|
||||
if (!resValidation.is_valid) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "password incorrect",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
/**@type {string} */
|
||||
let username = data.username
|
||||
if (!username)
|
||||
throw {
|
||||
status: 400,
|
||||
error: "username is required",
|
||||
log: false,
|
||||
}
|
||||
username = username.toLowerCase()
|
||||
|
||||
const userWithUsername = context.db.find("bigCommerceCustomer", {
|
||||
filter: { username: username },
|
||||
})[0]
|
||||
if (userWithUsername)
|
||||
throw {
|
||||
status: 409,
|
||||
message: "username already in use",
|
||||
}
|
||||
const internalCustomer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: loginClaims.email },
|
||||
})[0]
|
||||
internalCustomer.username = username
|
||||
if (internalCustomer) {
|
||||
context.db.update("bigCommerceCustomer", internalCustomer.id, internalCustomer)
|
||||
}
|
||||
|
||||
//update bigcommerce customer too : get it and then update it with username swapped
|
||||
const bCCustomer = getCustomerById(loginClaims.bigCommerceId)
|
||||
if (!bCCustomer.form_fields) bCCustomer.form_fields = []
|
||||
const usernameIndex = bCCustomer.form_fields.findIndex((f) => f.name === "username")
|
||||
if (usernameIndex >= 0) bCCustomer.form_fields[usernameIndex].value = username
|
||||
else bCCustomer.form_fields = [...bCCustomer.form_fields, { name: "username", value: username }]
|
||||
updateCustomer(bCCustomer)
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
message: "customer updated",
|
||||
data: internalCustomer,
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (returnCustomer) {
|
||||
return {
|
||||
data: returnCustomer,
|
||||
}
|
||||
}
|
||||
throw { status: 401, error: "unauthorized", log: false }
|
||||
})()
|
||||
15
api/hooks/forms/post_create.js
Normal file
15
api/hooks/forms/post_create.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { validateFields } = require("./validateFields")
|
||||
;(function () {
|
||||
if (context?.data?.formular?.honey) {
|
||||
throw { status: 400, error: "Bot detection" }
|
||||
}
|
||||
delete context.data.formular.honey
|
||||
/**
|
||||
* @type {Array<[string, FormObj]>}
|
||||
*/
|
||||
let values = Object.entries(context?.data?.formular)
|
||||
let validation = validateFields(values)
|
||||
if (validation.length) {
|
||||
throw { status: 400, error: validation }
|
||||
}
|
||||
})()
|
||||
79
api/hooks/forms/post_return.js
Normal file
79
api/hooks/forms/post_return.js
Normal file
@@ -0,0 +1,79 @@
|
||||
;(function () {
|
||||
/** @type {FormObj} */
|
||||
let formular = context?.data?.formular
|
||||
|
||||
/** @type {TempFormBefore | TempFormAfter} */
|
||||
let tempForm = {}
|
||||
|
||||
let formTitle = `${formular.formTitle}`
|
||||
delete formular.formTitle
|
||||
delete formular["agreement"]
|
||||
delete formular["honey"]
|
||||
|
||||
formular?.formRows?.forEach(
|
||||
/** @param {string} rowName */
|
||||
(rowName) => {
|
||||
tempForm[rowName] = {}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {[string, any]} e
|
||||
* @returns {any}
|
||||
*/
|
||||
let getValue = (e) => {
|
||||
if (e[0].includes("numberLabel")) return e[1][0]
|
||||
return e[1]
|
||||
}
|
||||
|
||||
/** @type {TempFormIndices} */
|
||||
let indices = {}
|
||||
|
||||
Object.entries(formular).forEach((e) => {
|
||||
if (e[0] === "formRows") return
|
||||
|
||||
let key = e[0].split("_")
|
||||
let newKey = key[key.length - 1]
|
||||
|
||||
// @ts-ignore
|
||||
let rowName = formular?.formRows[Number(key[key.length - 2])]
|
||||
let index = isNaN(Number(key[key.length - 3])) ? 100 : Number(key[key.length - 3])
|
||||
|
||||
if (!rowName) return
|
||||
if (!tempForm[rowName]) tempForm[rowName] = {}
|
||||
|
||||
// @ts-ignore
|
||||
if (tempForm[rowName][newKey]) {
|
||||
// @ts-ignore
|
||||
tempForm[rowName][newKey][0] += "\\" + getValue(e)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
tempForm[rowName][newKey] = [getValue(e), newKey[0] === "n", newKey.includes("invalid")]
|
||||
indices[rowName] = { ...indices[rowName], [newKey]: index }
|
||||
}
|
||||
})
|
||||
|
||||
// Now iterate over tempForm to sort fields in each row
|
||||
Object.entries(tempForm).forEach(([rowName, fields]) => {
|
||||
tempForm[rowName] = Object.entries(fields)
|
||||
.sort((a, b) => indices[rowName][a[0]] - indices[rowName][b[0]])
|
||||
.reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
|
||||
})
|
||||
|
||||
Object.keys(tempForm).forEach((row) => {
|
||||
tempForm[row] = Object.entries(tempForm[row])
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
delete tempForm[undefined]
|
||||
context.smtp.sendMail({
|
||||
to: "allkids.erfurt@gmail.com",
|
||||
from: "mail@webmakers.de",
|
||||
subject: "AllKids " + formTitle,
|
||||
html: context.tpl.execute(context.fs.readFile("templates/form_mail.html"), {
|
||||
context: context,
|
||||
formularRows: Object.entries(tempForm),
|
||||
formTitle: formTitle,
|
||||
}),
|
||||
})
|
||||
})()
|
||||
80
api/hooks/forms/validateFields.js
Normal file
80
api/hooks/forms/validateFields.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @param {Array<[string, FormObj]>} fieldsArray
|
||||
* @returns {(string | (() => void))[][]}
|
||||
*/
|
||||
function validateFields(fieldsArray) {
|
||||
/**@type {(string | (() => void))[][]} */
|
||||
const errors = []
|
||||
/**@type {number} */
|
||||
let selectedGroup
|
||||
|
||||
const numberRegex = /^[+]?([.]\d+|\d+([.]\d+)?)$/
|
||||
const emailRegex =
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
|
||||
const dateRegex = /^\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])$/
|
||||
const timeRegex = /^\d{1,2}:\d{2}-\d{1,2}:\d{2}$/
|
||||
const phoneRegex = /^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/
|
||||
|
||||
const wholeBlockInvalid = () => {
|
||||
const blockContainer = document.getElementsByClassName("blockContainer")[0]
|
||||
blockContainer.classList.add("invalidBlocks")
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} value
|
||||
* @param {any} field
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
const validateNumber = (value, field, element) => {
|
||||
if (!numberRegex.test(`${value}`)) {
|
||||
errors.push(["block", () => element.classList.add("border-red")])
|
||||
}
|
||||
}
|
||||
|
||||
fieldsArray.forEach(([field, value]) => {
|
||||
if (field === "blockGroups" || field.includes("numberLabel")) {
|
||||
if (!field.includes("numberLabel")) return
|
||||
// @ts-ignore
|
||||
const [elementValue, element, group] = value
|
||||
|
||||
if (!elementValue) return
|
||||
|
||||
if (selectedGroup !== undefined) {
|
||||
if (group !== selectedGroup) {
|
||||
errors.push(["block", wholeBlockInvalid])
|
||||
} else {
|
||||
validateNumber(elementValue, field, element)
|
||||
}
|
||||
} else {
|
||||
selectedGroup = group
|
||||
validateNumber(elementValue, field, element)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
errors.push(["Eingabe ist erforderlich.", field])
|
||||
} else if (field.includes("number_")) {
|
||||
if (!numberRegex.test(`${value}`)) {
|
||||
errors.push(["Ungültiger numerischer Wert.", field])
|
||||
}
|
||||
} else if (field.includes("agreement_")) {
|
||||
if (value !== true) errors.push(["Bitte das Kontrollkästchen anklicken.", field])
|
||||
} else if (field.includes("Email_")) {
|
||||
if (!emailRegex.test(value)) errors.push(["Ungültiges E-Mail-Format.", field])
|
||||
} else if (field.includes("date_")) {
|
||||
if (!dateRegex.test(value)) errors.push(["Ungültiges Datumsformat.", field])
|
||||
} else if (field.includes("times_")) {
|
||||
if (!timeRegex.test(value)) errors.push(["Ungültiges Zeitformat.", field])
|
||||
} else if (field.includes("Telefon_")) {
|
||||
if (!phoneRegex.test(value)) errors.push(["Ungültiges Telefonnummernformat.", field])
|
||||
}
|
||||
})
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateFields,
|
||||
}
|
||||
919
api/hooks/lib/bigcommerceRestAPI.js
Normal file
919
api/hooks/lib/bigcommerceRestAPI.js
Normal file
@@ -0,0 +1,919 @@
|
||||
const { bigcommerceApiOAuth, bigcommerceStoreHash, channelId } = require("../config.js")
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} status
|
||||
*/
|
||||
|
||||
function statusIsValid(status) {
|
||||
return !!status && status < 400
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} orderId
|
||||
* @returns {V2OrderProductsResponseBase[]}
|
||||
*/
|
||||
function getOrdersProducts(orderId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/products`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
|
||||
return response.body.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} orderId∂
|
||||
* @returns {V2OrderResponseBase}
|
||||
*/
|
||||
function getOrderById(orderId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {V2OrderResponseBase}
|
||||
*/
|
||||
const order = response.body.json()
|
||||
order.productObjs = getOrdersProducts(orderId)
|
||||
order.shipping_addressObjs = getOrderShippingAddressesById(String(orderId))
|
||||
return order
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} orderId
|
||||
* @param {V2OrderResponseBase} order
|
||||
* @returns
|
||||
*/
|
||||
|
||||
function updateOrderById(orderId, order) {
|
||||
delete order.id
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify(order),
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} customerId
|
||||
*/
|
||||
function getOrdersForCustomer(customerId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders?customer_id=${customerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {V2OrderResponseBase[]}
|
||||
*/
|
||||
const orders = response.body.json()
|
||||
orders.forEach((order) => {
|
||||
order.productObjs = getOrdersProducts(order.id)
|
||||
order.shipping_addressObjs = getOrderShippingAddressesById(String(order.id))
|
||||
})
|
||||
|
||||
// every order has date_created field, so we can sort by it (latest first)
|
||||
orders.sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime())
|
||||
|
||||
return orders
|
||||
}
|
||||
/**
|
||||
* @returns {V2OrderProductsResponseBase[]}
|
||||
*/
|
||||
function getAllProducts() {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products?limit=250`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
|
||||
return response.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [orderId]
|
||||
* @returns {V2OrderShippingAddressesResponseBase[]}
|
||||
*/
|
||||
function getOrderShippingAddressesById(orderId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/shipping_addresses`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [productId]
|
||||
* @returns {V2OrderProductsResponseBase}
|
||||
*/
|
||||
function getProductById(productId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
|
||||
return response.body.json().data
|
||||
}
|
||||
/**
|
||||
* @param {string} orderId
|
||||
* @returns {V2OrderProductsResponseBase[]}
|
||||
*/
|
||||
function getOrderProductsById(orderId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/products`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [productId]
|
||||
* @returns {ProductImage[]}
|
||||
*/
|
||||
function getProductImages(productId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}/images`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
|
||||
return response.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [productId]
|
||||
* @param {string} [ratingId]
|
||||
*/
|
||||
function deleteRating(productId, ratingId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}/reviews/${ratingId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [cartId]
|
||||
* @returns {RestApiCart}
|
||||
*/
|
||||
function getCart(cartId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText + " cart",
|
||||
}
|
||||
}
|
||||
|
||||
return response.body.json().data
|
||||
}
|
||||
/**
|
||||
* @param {string} [cartId]
|
||||
* @returns {any}
|
||||
*/
|
||||
function getRedirectUrl(cartId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/redirect_urls`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"x-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText + "redirect_url",
|
||||
}
|
||||
}
|
||||
const data = response.body.json().data
|
||||
return { embeddedCheckoutURL: data?.embedded_checkout_url, checkoutURL: data?.checkout_url }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} cartId
|
||||
* @param { { merchandiseId: string; quantity: number; productId?: string }[]} items
|
||||
*/
|
||||
function addCartItem(cartId, items) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"x-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({ line_items: items }),
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} cartId
|
||||
* @param {{ variant_id: string;product_id: string, quantity: number;} } item
|
||||
* @param {string} entityId
|
||||
* @returns {any}
|
||||
*/
|
||||
function updateCartItem(cartId, item, entityId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items/${entityId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"x-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({ line_item: item }),
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} cartId
|
||||
* @param {string} entityId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function deleteCartItem(cartId, entityId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items/${entityId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"x-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} customerId
|
||||
* @param {string} email
|
||||
* @returns {BigCommerceCustomer}
|
||||
*/
|
||||
function getCustomerById(customerId, email = "", noException = false) {
|
||||
let res
|
||||
if (customerId) {
|
||||
res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers?id:in=${customerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
} else {
|
||||
let encodedEmail = encodeURIComponent(email)
|
||||
res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers?email:in=${encodedEmail}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
if (!statusIsValid(res.status)) {
|
||||
if (noException) return null
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
if (res.body.json().data.length === 0) return null
|
||||
/** @type {BigCommerceCustomer} */
|
||||
const customer = res.body.json().data[0]
|
||||
|
||||
const formFields = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/form-field-values?customer_id=${customer.id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(formFields.status)) {
|
||||
if (noException) return null
|
||||
throw {
|
||||
status: formFields.status,
|
||||
error: formFields.statusText,
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (formFields.body && formFields.body.json()) customer.form_fields = formFields.body.json()?.data || []
|
||||
} catch (e) {}
|
||||
return customer
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} customerId
|
||||
* @param {number} addressId
|
||||
* @returns {BigCommerceAddress}
|
||||
*/
|
||||
|
||||
function getCustomerAddressById(customerId, addressId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses/?customer_id:in=${customerId}&id:in=${addressId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} customerId
|
||||
* @param {number} addressId
|
||||
*
|
||||
*/
|
||||
function deleteCustomerAddressById(customerId, addressId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/customers/${customerId}/addresses/${addressId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
console.log(res.status, "status")
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BigCommerceAddress} address
|
||||
*/
|
||||
|
||||
function updateCustomerAddressById(address) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify([address]),
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} customerId
|
||||
* @returns {BigCommerceAddress[]}
|
||||
*/
|
||||
|
||||
function getCustomerAddresses(customerId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses?customer_id:in=${customerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* @param {BigCommerceAddress} address
|
||||
* @returns
|
||||
*/
|
||||
function addCustomerAddress(address) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify([address]),
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(res.status))
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
return res.body.json().data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} email
|
||||
* @param {string} password
|
||||
* @returns {{is_valid: boolean}}
|
||||
*/
|
||||
function validateCredentials(email, password) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/validate-credentials`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
channelId,
|
||||
}),
|
||||
}
|
||||
)
|
||||
if (res.status == 429) {
|
||||
throw {
|
||||
status: 429,
|
||||
error: "Too many requests",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
if (res.status == 422) {
|
||||
throw {
|
||||
status: 422,
|
||||
error: "Invalid email or password",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
if (!statusIsValid(res.status)) {
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
return res.body.json()
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Partial<BigCommerceCustomer>} customer
|
||||
* @returns {BigCommerceCustomer}
|
||||
*/
|
||||
function updateCustomer(customer) {
|
||||
const customerRes = context.http.fetch(`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify([customer]),
|
||||
})
|
||||
if (!statusIsValid(customerRes.status))
|
||||
throw {
|
||||
status: customerRes.status,
|
||||
error: customerRes.statusText,
|
||||
}
|
||||
return customerRes.body.json().data[0]
|
||||
}
|
||||
|
||||
function generateJTI() {
|
||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} customerId
|
||||
* @param {string} storeHash
|
||||
* @param {string} storeUrl
|
||||
* @param {string} clientId
|
||||
* @param {string} clientSecret
|
||||
* @param {string} [redirect_to]
|
||||
* @returns
|
||||
*/
|
||||
function getLoginUrl(customerId, storeHash, storeUrl, clientId, clientSecret, redirect_to = "") {
|
||||
const dateCreated = Math.round(new Date().getTime() / 1000)
|
||||
const payload = {
|
||||
iss: clientId,
|
||||
iat: dateCreated,
|
||||
jti: generateJTI(),
|
||||
operation: "customer_login",
|
||||
store_hash: storeHash,
|
||||
customer_id: customerId,
|
||||
redirect_to,
|
||||
}
|
||||
const token = context.jwt.create(payload, { secret: clientSecret, validityDuration: 600 })
|
||||
return `${storeUrl}/login/token/${token}`
|
||||
}
|
||||
/**
|
||||
* @param {number} customerId
|
||||
* @returns {RestWishlist | false}
|
||||
*/
|
||||
function getWishlistEntries(customerId) {
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists?customer_id=${customerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (res.status == 404) {
|
||||
return false
|
||||
}
|
||||
if (!statusIsValid(res.status)) {
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
if (res.body.json().data?.length === 0) return false
|
||||
return res.body.json().data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} customerId
|
||||
* @param {number} productId
|
||||
* @param {number} variantId
|
||||
* @returns {RestWishlist}
|
||||
*/
|
||||
function createWishlistEntry(productId, variantId, customerId) {
|
||||
const currentWishlist = getWishlistEntries(customerId)
|
||||
let res
|
||||
|
||||
if (currentWishlist) {
|
||||
res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists/${currentWishlist?.id}/items`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
product_id: Number(productId),
|
||||
variant_id: Number(variantId),
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
} else {
|
||||
res = context.http.fetch(`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_id: Number(customerId),
|
||||
is_public: false,
|
||||
name: "Customer Wishlist",
|
||||
items: [
|
||||
{
|
||||
product_id: Number(productId),
|
||||
variant_id: Number(variantId),
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (!statusIsValid(res.status)) {
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
return res.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} customerId
|
||||
* @param {number} productId
|
||||
* @param {number} variantId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function removeWishlistEntry(customerId, productId, variantId) {
|
||||
const currentWishlist = getWishlistEntries(customerId)
|
||||
if (!currentWishlist) {
|
||||
console.log("no wishlist at all")
|
||||
return true
|
||||
}
|
||||
|
||||
const wishListEntry = currentWishlist.items.find(
|
||||
(item) => item.product_id == productId && item.variant_id == variantId
|
||||
)
|
||||
if (!wishListEntry) {
|
||||
console.log("no wishlist entry")
|
||||
return true
|
||||
}
|
||||
|
||||
const res = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists/${currentWishlist.id}/items/${wishListEntry.id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(res.status)) {
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {number} orderId
|
||||
* @returns {Partial<Order>}
|
||||
*/
|
||||
function createInternalOrderObject(orderId) {
|
||||
const orderProducts = getOrderProductsById(String(orderId))
|
||||
const bigCommerceId = orderId
|
||||
const order = getOrderById(bigCommerceId)
|
||||
|
||||
let [internalCustomer] = context.db.find("bigCommerceCustomer", {
|
||||
filter: {
|
||||
bigCommerceId: order?.customer_id,
|
||||
},
|
||||
})
|
||||
/** @type {Partial<Order>} */
|
||||
const internalOrderReference = {
|
||||
bigCommerceId,
|
||||
customerBigCommerceId: order?.customer_id,
|
||||
cost: order?.total_inc_tax,
|
||||
customerTibiId: internalCustomer?.id,
|
||||
status: "draft",
|
||||
products: orderProducts.map((product) => {
|
||||
const internalProduct = context.db.find("bigCommerceProduct", {
|
||||
filter: {
|
||||
bigCommerceId: product.product_id,
|
||||
},
|
||||
})[0]
|
||||
return {
|
||||
bigCommerceId: product.product_id,
|
||||
tibiId: internalProduct?.id,
|
||||
quantity: product.quantity,
|
||||
}
|
||||
}),
|
||||
}
|
||||
return internalOrderReference
|
||||
}
|
||||
module.exports = {
|
||||
updateCustomer,
|
||||
getOrderById,
|
||||
getOrderProductsById,
|
||||
getProductImages,
|
||||
getProductById,
|
||||
getOrderShippingAddressesById,
|
||||
deleteRating,
|
||||
getCart,
|
||||
getRedirectUrl,
|
||||
addCartItem,
|
||||
deleteCartItem,
|
||||
updateCartItem,
|
||||
getCustomerById,
|
||||
validateCredentials,
|
||||
updateCustomerAddressById,
|
||||
deleteCustomerAddressById,
|
||||
getCustomerAddressById,
|
||||
getCustomerAddresses,
|
||||
addCustomerAddress,
|
||||
getLoginUrl,
|
||||
getOrdersForCustomer,
|
||||
removeWishlistEntry,
|
||||
createWishlistEntry,
|
||||
getWishlistEntries,
|
||||
updateOrderById,
|
||||
getAllProducts,
|
||||
createInternalOrderObject,
|
||||
}
|
||||
40
api/hooks/lib/deepLRestApi.js
Normal file
40
api/hooks/lib/deepLRestApi.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { DeepLkey } = require("../config.js")
|
||||
const { statusIsValid } = require("./utils.js")
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function translateText(text) {
|
||||
const response = context.http.fetch(`https://api.deepl.com/v2/translate`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
Authorization: `DeepL-Auth-Key ${DeepLkey}`,
|
||||
"Content-Length": String(text.length),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: [text],
|
||||
target_lang: "DE",
|
||||
formality: "less",
|
||||
context:
|
||||
"Es handelt sich um eine Produktbeschreibung - insbesondere um die Größenbeschreibungen. Dinge wie A Länge, B Breite, C Ärmellänge etc. sollten präzise übersetzt werden, da die buchstaben hier auf ein Bild verweisen, welches du nicht kennst!",
|
||||
}),
|
||||
})
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
console.log(JSON.stringify(response), JSON.stringify(response.body.json()))
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
const res = response.body.json()
|
||||
const textMerged = res.translations.map((t) => t.text).join(" ")
|
||||
return textMerged
|
||||
}
|
||||
module.exports = {
|
||||
translateText,
|
||||
}
|
||||
108
api/hooks/lib/facebookRestAPI.js
Normal file
108
api/hooks/lib/facebookRestAPI.js
Normal file
@@ -0,0 +1,108 @@
|
||||
const { fb_accessToken } = require("../config")
|
||||
|
||||
function postAddToCart() {
|
||||
const request = context.request()
|
||||
const headers = request.header("User-Agent")
|
||||
const ipAddress = request.clientIp()
|
||||
|
||||
const eventPayload = {
|
||||
data: [
|
||||
{
|
||||
event_name: "AddToCart",
|
||||
event_time: Math.floor(Date.now() / 1000),
|
||||
event_source_url: "https://www.binkrassdufass.de",
|
||||
action_source: "website",
|
||||
user_data: {
|
||||
client_user_agent: headers,
|
||||
client_ip_address: ipAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
const res = context.http.fetch(
|
||||
"https://graph.facebook.com/v16.0/1117933239951751/events?access_token=" + fb_accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(eventPayload),
|
||||
}
|
||||
)
|
||||
console.log(JSON.stringify(res), "CARTADD")
|
||||
const response = res.body.json()
|
||||
console.log(JSON.stringify(response))
|
||||
}
|
||||
|
||||
function postUserRegister() {
|
||||
const request = context.request()
|
||||
const headers = request.header("User-Agent")
|
||||
const ipAddress = request.clientIp()
|
||||
const eventPayload = {
|
||||
data: [
|
||||
{
|
||||
event_name: "CompleteRegistration",
|
||||
event_time: Math.floor(Date.now() / 1000),
|
||||
event_source_url: "https://www.binkrassdufass.de",
|
||||
action_source: "website",
|
||||
user_data: {
|
||||
client_user_agent: headers,
|
||||
client_ip_address: ipAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
const res = context.http.fetch(
|
||||
"https://graph.facebook.com/v16.0/1117933239951751/events?access_token=" + fb_accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(eventPayload),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} total
|
||||
*/
|
||||
function postPurchase(total) {
|
||||
const eventPayload = {
|
||||
data: [
|
||||
{
|
||||
event_name: "Purchase",
|
||||
event_time: Math.floor(Date.now() / 1000),
|
||||
event_source_url: "https://www.binkrassdufass.de",
|
||||
action_source: "website",
|
||||
user_data: {
|
||||
// dummy data bc inside webhook we don't have access to user data
|
||||
client_user_agent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
client_ip_address: "192.168.1.1", // random IP address
|
||||
},
|
||||
custom_data: {
|
||||
currency: "EUR",
|
||||
value: total,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
const res = context.http.fetch(
|
||||
"https://graph.facebook.com/v12.0/1117933239951751/events?access_token=" + fb_accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(eventPayload),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
postAddToCart,
|
||||
postUserRegister,
|
||||
postPurchase,
|
||||
}
|
||||
61
api/hooks/lib/omnisendRestApi.js
Normal file
61
api/hooks/lib/omnisendRestApi.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const { omnisendApiKey } = require("../config.js")
|
||||
const { statusIsValid } = require("./utils.js")
|
||||
/**
|
||||
*
|
||||
* @param {string} email
|
||||
*/
|
||||
function createNewsletterSubscriber(email) {
|
||||
const res = context.http.fetch("https://api.omnisend.com/v5/contacts", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-KEY": omnisendApiKey,
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
createdAt: new Date().toISOString(),
|
||||
identifiers: [
|
||||
{
|
||||
channels: {
|
||||
email: {
|
||||
status: "subscribed",
|
||||
statusDate: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
consent: {
|
||||
createdAt: new Date().toISOString(),
|
||||
source: "headless-frontend",
|
||||
},
|
||||
id: email,
|
||||
type: "email",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
if (!statusIsValid(res.status)) {
|
||||
throw {
|
||||
status: res.status,
|
||||
error: res.statusText,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} email
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkIfNewsletterSubscriber(email) {
|
||||
const res = context.http.fetch("https://api.omnisend.com/v5/contacts", {
|
||||
headers: {
|
||||
"X-API-KEY": omnisendApiKey,
|
||||
accept: "application/json",
|
||||
},
|
||||
})
|
||||
const resJson = res.body.json()
|
||||
return resJson.contacts.some((contact) => contact.email === email)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createNewsletterSubscriber,
|
||||
checkIfNewsletterSubscriber,
|
||||
}
|
||||
238
api/hooks/lib/printfulRestAPI.js
Normal file
238
api/hooks/lib/printfulRestAPI.js
Normal file
@@ -0,0 +1,238 @@
|
||||
const { printfulAPIToken, serverBaseURL } = require("../config")
|
||||
const { translateText } = require("./deepLRestApi")
|
||||
const { statusIsValid } = require("./utils")
|
||||
/**
|
||||
*
|
||||
* @param {number} bigCommerceOrderId
|
||||
*/
|
||||
function getPrintfulOrder(bigCommerceOrderId) {
|
||||
const response = context.http.fetch("https://api.printful.com/v2/orders/@" + bigCommerceOrderId, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} bigCommerceOrderId
|
||||
*/
|
||||
function getPrintfulOrderShipments(bigCommerceOrderId) {
|
||||
const response = context.http.fetch(`https://api.printful.com/v2/orders/@${bigCommerceOrderId}/shipments`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} bigCommerceOrderId
|
||||
* @param {any} data
|
||||
* @returns
|
||||
*/
|
||||
function patchPrintfulOrder(bigCommerceOrderId, data) {
|
||||
const response = context.http.fetch("https://api.printful.com/v2/orders/@" + bigCommerceOrderId, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} type
|
||||
*/
|
||||
function createPrintfulWebhook(type) {
|
||||
const response = context.http.fetch("https://api.printful.com/v2/webhooks/" + type, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type,
|
||||
url: `${serverBaseURL}webhook`,
|
||||
}),
|
||||
})
|
||||
console.log("response:", response.status)
|
||||
if (!statusIsValid(response.status)) {
|
||||
console.log("wtf?!")
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
|
||||
*/
|
||||
function deletePrintfulWebhook(type) {
|
||||
const response = context.http.fetch("https://api.printful.com/v2/webhooks/" + type, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
})
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} bigCommerceOrderId
|
||||
|
||||
*/
|
||||
|
||||
function cancelPrintfulOrder(bigCommerceOrderId) {
|
||||
const response = context.http.fetch("https://api.printful.com/orders/@" + bigCommerceOrderId, {
|
||||
method: "delete",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
})
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json()
|
||||
}
|
||||
/**
|
||||
* @param {string} printfulProductId
|
||||
*/
|
||||
function getPrintfulProductSizingChart(printfulProductId) {
|
||||
const response = context.http.fetch(
|
||||
`https://api.printful.com/v2/catalog-products/${printfulProductId}/sizes?unit=cm`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${printfulAPIToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
return response.body.json().data
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} printfulProductId
|
||||
* @param {string} productId
|
||||
* @returns
|
||||
*/
|
||||
function extractSizingChart(printfulProductId, productId) {
|
||||
if (!printfulProductId) return null
|
||||
|
||||
const productSizingChart = getPrintfulProductSizingChart(printfulProductId)
|
||||
const sizeTable = productSizingChart.size_tables.find((t) => t.type === "measure_yourself")
|
||||
|
||||
if (!sizeTable) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "No sizing chart found for this product",
|
||||
}
|
||||
}
|
||||
|
||||
const productSizingImageDescriptionTranslation = translateText(sizeTable.image_description)
|
||||
const generalProductSizingDescription = translateText(sizeTable.description)
|
||||
|
||||
return {
|
||||
imageURL: sizeTable.image_url,
|
||||
imageDescription: productSizingImageDescriptionTranslation,
|
||||
availableSizes: productSizingChart.available_sizes,
|
||||
generalDescription: generalProductSizingDescription,
|
||||
columns: sizeTable.measurements.map((m) => {
|
||||
let label = context.db.find("module", {
|
||||
filter: {
|
||||
label: m.type_label,
|
||||
type: "sizeLabel",
|
||||
},
|
||||
})
|
||||
|
||||
if (label.length === 0) {
|
||||
const germanLabelTranslation = translateText(m.type_label)
|
||||
label[0] = context.db.create("module", {
|
||||
label: m.type_label,
|
||||
type: "sizeLabel",
|
||||
germanLabelTranslation: germanLabelTranslation,
|
||||
})
|
||||
} else {
|
||||
label = [...label]
|
||||
}
|
||||
|
||||
return {
|
||||
label: label[0].label,
|
||||
sizes: m.values.map((v) => {
|
||||
if (v.min_value && v.max_value) {
|
||||
if (sizeTable.unit === "inches") {
|
||||
return `${Math.round(Number(v.min_value) * 2.54)} - ${Math.round(
|
||||
Number(v.max_value) * 2.54
|
||||
)}`
|
||||
}
|
||||
return `${v.min_value} - ${v.max_value}`
|
||||
}
|
||||
if (sizeTable.unit === "inches") {
|
||||
return String(Math.round(Number(v.value) * 2.54))
|
||||
}
|
||||
return v.value
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getPrintfulOrder,
|
||||
patchPrintfulOrder,
|
||||
createPrintfulWebhook,
|
||||
deletePrintfulWebhook,
|
||||
cancelPrintfulOrder,
|
||||
getPrintfulProductSizingChart,
|
||||
getPrintfulOrderShipments,
|
||||
extractSizingChart,
|
||||
}
|
||||
67
api/hooks/lib/ssr-server.js
Normal file
67
api/hooks/lib/ssr-server.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const { apiSsrBaseURL } = require("../config")
|
||||
|
||||
/**
|
||||
* api request via server, cache result in context.ssrCache
|
||||
* should be elimated in client code via tree shaking
|
||||
*
|
||||
* @param {string} cacheKey
|
||||
* @param {string} endpoint
|
||||
* @param {string} query
|
||||
* @param {ApiOptions} options
|
||||
* @returns {ApiResult<any>}
|
||||
*/
|
||||
function ssrRequest(cacheKey, endpoint, query, options) {
|
||||
let url = endpoint + (query ? "?" + query : "")
|
||||
|
||||
// console.log("############ FETCHING ", apiSsrBaseURL + url)
|
||||
|
||||
const response = context.http.fetch(apiSsrBaseURL + url, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
})
|
||||
|
||||
// console.log(JSON.stringify(response.headers, null, 2))
|
||||
|
||||
const json = response.body.json()
|
||||
const count = parseInt(response.headers["X-Results-Count"] || "0")
|
||||
|
||||
// json is go data structure and incompatible with js, so we need to convert it
|
||||
const r = { data: JSON.parse(JSON.stringify(json)), count: count }
|
||||
|
||||
// @ts-ignore
|
||||
context.ssrCache[cacheKey] = r
|
||||
return r
|
||||
}
|
||||
/**
|
||||
* api request via server, cache result in context.ssrCache
|
||||
* for BigCommerce requests, using context.http.fetch
|
||||
*
|
||||
* @param {string} cacheKey
|
||||
* @param {string} endpoint
|
||||
* @param {string} query
|
||||
* @param {ApiOptions} options
|
||||
* @returns {{status: number, body: any}}
|
||||
*/
|
||||
function ssrRequestBigCommerce(cacheKey, endpoint, query, options) {
|
||||
const response = context.http.fetch(endpoint, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
})
|
||||
if (response.status >= 300) {
|
||||
console.log("SSR ERROR?!?!?!", response.status, JSON.stringify(response))
|
||||
return { status: response.status, body: { data: {}, count: 0 } }
|
||||
}
|
||||
|
||||
const count = parseInt(response.headers["X-Results-Count"] || "0")
|
||||
const json = response.body.json()
|
||||
const result = { status: response.status, body: JSON.parse(JSON.stringify(json)), count: count }
|
||||
|
||||
// @ts-ignore
|
||||
context.ssrCache[cacheKey] = result
|
||||
return result
|
||||
}
|
||||
module.exports = {
|
||||
ssrRequest,
|
||||
ssrRequestBigCommerce,
|
||||
}
|
||||
163
api/hooks/lib/ssr.js
Normal file
163
api/hooks/lib/ssr.js
Normal file
@@ -0,0 +1,163 @@
|
||||
const { login, newNotification } = require("../../../frontend/src/lib/store")
|
||||
const { apiClientBaseURL } = require("../config-client")
|
||||
|
||||
/**
|
||||
* convert object to string
|
||||
* @param {any} obj object
|
||||
* @returns {string} string
|
||||
*/
|
||||
function obj2str(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
return JSON.stringify(
|
||||
obj.map(function (idx) {
|
||||
return obj2str(idx)
|
||||
})
|
||||
)
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
var elements = Object.keys(obj)
|
||||
.sort()
|
||||
.map(function (key) {
|
||||
var val = obj2str(obj[key])
|
||||
if (val) {
|
||||
return key + ":" + val
|
||||
}
|
||||
})
|
||||
|
||||
var elementsCleaned = []
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i]) elementsCleaned.push(elements[i])
|
||||
}
|
||||
|
||||
return "{" + elementsCleaned.join("|") + "}"
|
||||
}
|
||||
|
||||
if (obj) return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* api request via client or server
|
||||
* server function ssrRequest is called via context.ssrRequest, binded in ssr hook
|
||||
*
|
||||
* @param {string} endpoint
|
||||
* @param {ApiOptions} options
|
||||
* @param {any} body
|
||||
* @param {import("../../../frontend/src/sentry")} sentry
|
||||
* @param {typeof fetch} _fetch
|
||||
* @returns {Promise<ApiResult<any>>}
|
||||
*/
|
||||
function apiRequest(endpoint, options, body, sentry, _fetch) {
|
||||
// first check cache if on client
|
||||
const cacheKey = obj2str({ endpoint: endpoint, options: options })
|
||||
let method = options?.method || "GET"
|
||||
|
||||
// @ts-ignore
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.__SSR_CACHE__ &&
|
||||
method === "GET" &&
|
||||
!location.pathname.includes("profile")
|
||||
) {
|
||||
// @ts-ignore
|
||||
const cache = window.__SSR_CACHE__[cacheKey]
|
||||
if (cache) {
|
||||
return Promise.resolve(cache)
|
||||
}
|
||||
}
|
||||
|
||||
let query = "&count=1"
|
||||
if (options?.filter) query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
|
||||
if (options?.sort) query += "&sort=" + options.sort + "&sort=_id"
|
||||
if (options?.limit) query += "&limit=" + options.limit
|
||||
if (options?.offset) query += "&offset=" + options.offset
|
||||
if (options?.projection) query += "&projection=" + options.projection
|
||||
if (options?.lookup) query += "&lookup=" + options.lookup
|
||||
|
||||
if (options?.params) {
|
||||
Object.keys(options.params).forEach((p) => {
|
||||
query += "&" + p + "=" + encodeURIComponent(options.params[p])
|
||||
})
|
||||
}
|
||||
|
||||
/** @type {{[key: string]: string}} */
|
||||
let headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if (options?.headers) headers = { ...headers, ...options.headers }
|
||||
|
||||
if (typeof window === "undefined" && method === "GET") {
|
||||
// server
|
||||
|
||||
// reference via context from get hook to tree shake in client
|
||||
// @ts-ignore
|
||||
const d = context.ssrRequest(cacheKey, endpoint, query, Object.assign({}, options, { method, headers }))
|
||||
return Promise.resolve(d)
|
||||
} else {
|
||||
// client
|
||||
let url = (endpoint.startsWith("/") ? "" : apiClientBaseURL) + endpoint + (query ? "?" + query : "")
|
||||
|
||||
const span = sentry?.currentTransaction()?.startChild({
|
||||
op: "fetch",
|
||||
description: method + " " + url,
|
||||
data: Object.assign({}, options, { url }),
|
||||
})
|
||||
const trace_id = span?.toTraceparent()
|
||||
if (trace_id) {
|
||||
headers["sentry-trace"] = trace_id
|
||||
}
|
||||
|
||||
/** @type {{[key: string]: any}} */
|
||||
const requestOptions = {
|
||||
method,
|
||||
mode: "cors",
|
||||
headers,
|
||||
}
|
||||
|
||||
if (method === "POST" || method === "PUT") {
|
||||
requestOptions.body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
const response = _fetch(url, requestOptions)
|
||||
.then((response) => {
|
||||
return response?.json().then((json) => {
|
||||
if (response?.status < 200 || response?.status >= 400) {
|
||||
if (response?.status === 401) {
|
||||
login.set(null)
|
||||
newNotification({
|
||||
html: "Nicht autorisiert. Bitte melde dich sich erneut an.",
|
||||
class: "error",
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject({ response, data: json })
|
||||
}
|
||||
return Promise.resolve({ data: json || null, count: response.headers?.get("x-results-count") || 0 })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (options.noError) {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
...error.data,
|
||||
},
|
||||
count: 0,
|
||||
})
|
||||
}
|
||||
if (error.status === 401) {
|
||||
login.set(null)
|
||||
newNotification({
|
||||
html: "Nicht autorisiert. Bitte melde dich sich erneut an.",
|
||||
class: "error",
|
||||
})
|
||||
} else throw error
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
obj2str,
|
||||
apiRequest,
|
||||
}
|
||||
494
api/hooks/lib/utils.js
Normal file
494
api/hooks/lib/utils.js
Normal file
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
*
|
||||
* @param {any} str
|
||||
*/
|
||||
function log(str) {
|
||||
console.log(JSON.stringify(str, undefined, 4))
|
||||
}
|
||||
|
||||
/**
|
||||
* convert object to string
|
||||
* @param {any} obj object
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function obj2str(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
return JSON.stringify(
|
||||
obj.map(function (idx) {
|
||||
return obj2str(idx)
|
||||
})
|
||||
)
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
var elements = Object.keys(obj)
|
||||
.sort()
|
||||
.map(function (key) {
|
||||
var val = obj2str(obj[key])
|
||||
if (val) {
|
||||
return key + ":" + val
|
||||
}
|
||||
})
|
||||
|
||||
var elementsCleaned = []
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i]) elementsCleaned.push(elements[i])
|
||||
}
|
||||
|
||||
return "{" + elementsCleaned.join("|") + "}"
|
||||
}
|
||||
|
||||
if (obj) return obj
|
||||
}
|
||||
var { LIGHTHOUSE_TOKEN } = require("../config")
|
||||
|
||||
/**
|
||||
* clear SSR cache
|
||||
*/
|
||||
function clearSSRCache() {
|
||||
var info = context.db.deleteMany("ssr", {})
|
||||
context.response.header("X-SSR-Cleared", info.removed)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ [x: string]: any; }[]} dbObjs
|
||||
*/
|
||||
function calculateAverageDynamically(dbObjs) {
|
||||
const sumObj = {}
|
||||
let count = 0
|
||||
|
||||
dbObjs.forEach((/** @type {{ [x: string]: any; }} */ obj) => {
|
||||
accumulate(obj, sumObj)
|
||||
count++
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {{ [x: string]: any; }} sourceObj
|
||||
* @param {{ [x: string]: any; }} targetObj
|
||||
*/
|
||||
function accumulate(sourceObj, targetObj) {
|
||||
for (const key in sourceObj) {
|
||||
if (typeof sourceObj[key] === "number") {
|
||||
targetObj[key] = (targetObj[key] || 0) + sourceObj[key]
|
||||
} else if (typeof sourceObj[key] === "object" && sourceObj[key] !== null) {
|
||||
targetObj[key] = targetObj[key] || {}
|
||||
accumulate(sourceObj[key], targetObj[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ [x: string]: any; }} targetObj
|
||||
*/
|
||||
function average(targetObj) {
|
||||
for (const key in targetObj) {
|
||||
if (typeof targetObj[key] === "number") {
|
||||
targetObj[key] = targetObj[key] / count
|
||||
} else if (typeof targetObj[key] === "object") {
|
||||
average(targetObj[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
average(sumObj)
|
||||
return sumObj
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*/
|
||||
function run(url) {
|
||||
const response = context.http
|
||||
.fetch(url, {
|
||||
timeout: 300,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.body.json()
|
||||
// needs enough traffic to be collected
|
||||
const cruxMetrics = {
|
||||
"First Contentful Paint": response?.loadingExperience?.metrics?.FIRST_CONTENTFUL_PAINT_MS?.category,
|
||||
"First Input Delay": response?.loadingExperience?.metrics?.FIRST_INPUT_DELAY_MS?.category,
|
||||
}
|
||||
const lighthouse = response.lighthouseResult
|
||||
const lighthouseMetrics = {
|
||||
FCPS: lighthouse.audits["first-contentful-paint"].score * 100,
|
||||
FCPV: lighthouse.audits["first-contentful-paint"].numericValue / 1000,
|
||||
FMPS: lighthouse.audits["first-meaningful-paint"].score * 100,
|
||||
FMPV: lighthouse.audits["first-meaningful-paint"].numericValue / 1000,
|
||||
|
||||
SIS: lighthouse.audits["speed-index"].score * 100,
|
||||
SIV: lighthouse.audits["speed-index"].numericValue / 1000,
|
||||
TTIS: lighthouse.audits["interactive"].score * 100,
|
||||
TTIV: lighthouse.audits["interactive"].numericValue / 1000,
|
||||
|
||||
FPIDS: lighthouse.audits["max-potential-fid"].score * 100,
|
||||
FPIDV: lighthouse.audits["max-potential-fid"].numericValue / 1000,
|
||||
}
|
||||
|
||||
let dbObject = {
|
||||
cruxMetrics,
|
||||
lighthouseMetrics,
|
||||
performance: Math.round(lighthouse.categories.performance.score * 100),
|
||||
accessibility: Math.round(lighthouse.categories.accessibility.score * 100),
|
||||
bestPractices: Math.round(lighthouse.categories["best-practices"].score * 100),
|
||||
seo: Math.round(lighthouse.categories.seo.score * 100),
|
||||
}
|
||||
return dbObject
|
||||
}
|
||||
function setUpQuery(subPath = "/") {
|
||||
const api = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
|
||||
let params = `category=performance&category=accessibility&category=best-practices&category=seo`
|
||||
|
||||
const parameters = {
|
||||
url: encodeURIComponent(`https://allkids-erfurt.de/${subPath}`),
|
||||
key: LIGHTHOUSE_TOKEN,
|
||||
}
|
||||
|
||||
let query = `${api}?`
|
||||
for (let key in parameters) {
|
||||
// @ts-ignore
|
||||
query += `${key}=${parameters[key]}&`
|
||||
}
|
||||
query += params // Append other parameters without URL encoding
|
||||
return query
|
||||
}
|
||||
|
||||
var config = require("../config")
|
||||
/**
|
||||
*
|
||||
* @param {HookContext} c
|
||||
* @param {string} filename
|
||||
* @param {string} locale
|
||||
* @returns {string}
|
||||
*/
|
||||
function tpl(c, filename, locale) {
|
||||
return c.tpl.execute(c.fs.readFile(filename), {
|
||||
context: c,
|
||||
config: config,
|
||||
})
|
||||
}
|
||||
const { operatorEmail, operatorName } = require("../config")
|
||||
const { cryptchaSiteId } = require("../config-client")
|
||||
const { postUserRegister } = require("./facebookRestAPI")
|
||||
|
||||
function sendOperatorRatingMail() {
|
||||
if (!context.data) context.data = {}
|
||||
let locale = context.request().query("locale")
|
||||
locale = locale ? locale : "de-DE"
|
||||
context.data.tibiLink = `project/${context.project().id}/collection/rating/edit/${context.data.id}` //projekt ID
|
||||
context.smtp.sendMail({
|
||||
to: operatorEmail,
|
||||
from: operatorEmail,
|
||||
fromName: operatorName,
|
||||
subject: tpl(context, "templates/operator_rating_subject.de-DE.txt", locale),
|
||||
html: tpl(context, "templates/operator_rating_body.de-DE.html", locale),
|
||||
})
|
||||
}
|
||||
|
||||
/**@param {ProductRating} currentRating */
|
||||
function validateAndModifyRating(currentRating) {
|
||||
// Fetch the rating object from the database
|
||||
|
||||
/** @type {ProductRating} */ // @ts-ignore
|
||||
let oldRating = context.db.find("rating", {
|
||||
filter: { orderId: currentRating.bigcommerceOrderId, productId: currentRating.bigCommerceProductId },
|
||||
})[0]
|
||||
|
||||
if (oldRating && (oldRating.status != "pending" || currentRating.status != "pending")) {
|
||||
//@ts-ignore
|
||||
oldRating.review_date = convertDateToUTCISO(oldRating.review_date)
|
||||
if (oldRating.status == "pending") {
|
||||
oldRating.status = currentRating.status
|
||||
if (currentRating.status == "approved") return currentRating
|
||||
}
|
||||
// if currentrating is approved but oldrating is not pending, disallow the change by returning the old rating
|
||||
return context?.user?.auth()?.id &&
|
||||
(currentRating?.status == "pending" || currentRating?.status == "rejected") &&
|
||||
(oldRating?.status == "approved" || oldRating?.status == "rejected")
|
||||
? currentRating
|
||||
: oldRating
|
||||
}
|
||||
|
||||
// If the status of the current rating is 'pending', or not provided
|
||||
if (currentRating.status == "pending" || !currentRating.status) {
|
||||
currentRating.status = "pending"
|
||||
if (!currentRating.review_date) {
|
||||
//@ts-ignore
|
||||
currentRating.review_date = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
return currentRating
|
||||
}
|
||||
|
||||
/** @param {ProductRating} [orderRating] */ // @ts-ignore
|
||||
function productInsideOrder(orderRating) {
|
||||
/**@type {Order} */
|
||||
//@ts-ignore
|
||||
withAccount((login) => {
|
||||
let order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: orderRating?.bigcommerceOrderId,
|
||||
customerBigCommerceId: login.bigCommerceId,
|
||||
},
|
||||
})[0]
|
||||
if (!order) {
|
||||
throw {
|
||||
status: 404,
|
||||
data: {
|
||||
message: "Order not found",
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
let order = context.db.find("bigCommerceOrder", {
|
||||
filter: { bigCommerceId: orderRating?.bigcommerceOrderId },
|
||||
})[0]
|
||||
|
||||
if (!order) throw { error: "No Order object with given ID.", status: 400 }
|
||||
const productIds = order.products?.map((product) => product.bigCommerceId)
|
||||
if (productIds.length == 0) throw { error: "No products inside the Order.", status: 400 }
|
||||
|
||||
let productInRating = orderRating?.bigCommerceProductId
|
||||
let productInsideOrder = productIds.includes(productInRating)
|
||||
|
||||
if (!productInsideOrder) throw { error: "Rated products are not inside the Order.", status: 400 }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HookContext | string} c
|
||||
*/
|
||||
function getJwt(c, required = true) {
|
||||
let tokenString
|
||||
|
||||
if (typeof c == "string") {
|
||||
tokenString = c
|
||||
} else {
|
||||
const authHeader = c.request().header("Authorization")
|
||||
|
||||
if (!authHeader) {
|
||||
if (required) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "missing authorization header",
|
||||
log: false,
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
tokenString = authHeader.replace("Bearer ", "")
|
||||
}
|
||||
|
||||
if (!tokenString) {
|
||||
if (required) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: is empty",
|
||||
log: false,
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const token = context.jwt.parse(tokenString, {
|
||||
secret: config.jwtSecret,
|
||||
})
|
||||
|
||||
if (required && !token.valid) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: " + token.error,
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
/**
|
||||
* call callback with login clains
|
||||
*
|
||||
* @param {(claims: JWTLoginClaims) => void} callback
|
||||
* @param {boolean} required
|
||||
* @param {string} apiTokenName
|
||||
* @returns {boolean} void if backendAuth
|
||||
*/
|
||||
function withAccount(callback, required = true, apiTokenName = null, allowViaRefreshToken = false) {
|
||||
if (apiTokenName) {
|
||||
const r = context.request()
|
||||
if (r.header(apiTokenName)) {
|
||||
// checked via api collection config, so ensure it is given
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const backendAuth = context.user.auth()
|
||||
|
||||
if (!backendAuth) {
|
||||
// require authorization header with jwt
|
||||
let token
|
||||
/** @type {JWTLoginClaims} */ // @ts-ignore
|
||||
let loginClaims
|
||||
try {
|
||||
token = getJwt(context, required)
|
||||
// @ts-ignore
|
||||
loginClaims = token && token.claims
|
||||
} catch (e) {
|
||||
if (allowViaRefreshToken) {
|
||||
const rT = getRefreshToken()
|
||||
if (rT?.valid) {
|
||||
/** @type {JWTRefreshClaims} */ // @ts-ignore
|
||||
const refreshClaims = rT.claims
|
||||
const accountId = refreshClaims && refreshClaims.tibiId
|
||||
/** @type {Customer} */ // @ts-ignore
|
||||
const customer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { _id: accountId },
|
||||
})[0]
|
||||
loginClaims = {
|
||||
bigCommerceId: customer.bigCommerceId,
|
||||
email: customer.email,
|
||||
tibiId: customer.id,
|
||||
}
|
||||
} else {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: invalid refresh token",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// log(loginClaims)
|
||||
|
||||
if (!loginClaims || !loginClaims.tibiId || !loginClaims.bigCommerceId || !loginClaims.email) {
|
||||
if (required) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: invalid claims",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
callback(loginClaims)
|
||||
return true
|
||||
}
|
||||
}
|
||||
/**
|
||||
*/
|
||||
function getRefreshToken() {
|
||||
const refreshToken = context.cookie.get("bkdfRefreshToken")
|
||||
if (refreshToken) {
|
||||
console.log("REFRESHFOUND")
|
||||
return getJwt(refreshToken, false)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BigCommerceCustomer} customer
|
||||
*/
|
||||
function createTibiCustomer(customer) {
|
||||
const username = customer.form_fields.find((field) => field.name === "username")?.value
|
||||
const internalCustomer = context.db.create("bigCommerceCustomer", {
|
||||
bigCommerceId: customer.id,
|
||||
email: customer.email.toLowerCase(),
|
||||
username: username.toLowerCase(),
|
||||
})
|
||||
const orders = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
customerBigCommerceId: customer.id,
|
||||
},
|
||||
})
|
||||
if (orders && orders.length) {
|
||||
orders.forEach((order) => {
|
||||
context.db.update("bigCommerceOrder", order.id, {
|
||||
customerTibiId: internalCustomer.id,
|
||||
})
|
||||
})
|
||||
}
|
||||
/*
|
||||
not necessary for now, as bigcommerce itself takes care of it
|
||||
try {
|
||||
postUserRegister()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}*/
|
||||
}
|
||||
/**
|
||||
* @param {BigCommerceCustomer} customer
|
||||
* @param {Customer} internalCustomer
|
||||
*/
|
||||
function updateTibiCustomer(customer, internalCustomer) {
|
||||
const username = customer.form_fields.find((field) => field.name === "username")?.value
|
||||
context.db.update("bigCommerceCustomer", internalCustomer.id, {
|
||||
email: customer.email.toLowerCase(),
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} status
|
||||
*/
|
||||
|
||||
function statusIsValid(status) {
|
||||
return !!status && status < 400
|
||||
}
|
||||
|
||||
function cryptchaCheck() {
|
||||
const solutionId = context.data._sId
|
||||
const solution = context.data._s
|
||||
|
||||
const body = JSON.stringify({ solution: solution })
|
||||
const resp = context.http.fetch(
|
||||
"https://cryptcha.webmakers.de/api/command?siteId=" +
|
||||
cryptchaSiteId +
|
||||
"&cmd=check&clear=1&solutionId=" +
|
||||
solutionId,
|
||||
{
|
||||
method: "POST",
|
||||
body,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
)
|
||||
const j = resp.body.json()
|
||||
|
||||
const corrent = j.status == "ok" && resp.status == 200
|
||||
if (!corrent) {
|
||||
throw {
|
||||
status: 400,
|
||||
log: false,
|
||||
error: "incorrect data",
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
clearSSRCache,
|
||||
obj2str,
|
||||
run,
|
||||
setUpQuery,
|
||||
calculateAverageDynamically,
|
||||
sendOperatorRatingMail,
|
||||
validateAndModifyRating,
|
||||
productInsideOrder,
|
||||
getJwt,
|
||||
withAccount,
|
||||
getRefreshToken,
|
||||
statusIsValid,
|
||||
createTibiCustomer,
|
||||
updateTibiCustomer,
|
||||
cryptchaCheck,
|
||||
}
|
||||
16
api/hooks/lighthouse/post_create.js
Normal file
16
api/hooks/lighthouse/post_create.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var { setUpQuery, calculateAverageDynamically, run } = require("../lib/utils")
|
||||
;(function () {
|
||||
let subPaths = context.db.find("lighthouseSubpath")
|
||||
let urls = []
|
||||
for (let i = 0; i < subPaths.length; i++) {
|
||||
urls.push(setUpQuery(subPaths[i].lighthouseSubpath))
|
||||
}
|
||||
let dbObjs = []
|
||||
urls.forEach((url) => {
|
||||
console.log("URL:", url)
|
||||
dbObjs.push(run(url))
|
||||
})
|
||||
let dbObject = calculateAverageDynamically(dbObjs)
|
||||
dbObject.analyzedPaths = [...subPaths].map((subPath) => subPath.lighthouseSubpath)
|
||||
return { data: dbObject }
|
||||
})()
|
||||
160
api/hooks/login/post_create.js
Normal file
160
api/hooks/login/post_create.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const { jwtSecret, jwtValidityDuration } = require("../config")
|
||||
const { validateCredentials, getCustomerById } = require("../lib/bigcommerceRestAPI")
|
||||
const { getRefreshToken, createTibiCustomer } = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
function deleteRefreshTokenCookie() {
|
||||
context.cookie.set("bkdfRefreshToken", "", {
|
||||
maxAge: -1,
|
||||
httpOnly: true,
|
||||
path: "/api",
|
||||
})
|
||||
}
|
||||
|
||||
if (context.request().query("logout")) {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 200,
|
||||
message: "ok",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
context.data.email = context?.data?.email?.toLowerCase()
|
||||
const { email, password } = context.data
|
||||
let bigCommerceId
|
||||
|
||||
if (!email || !password) {
|
||||
const rT = getRefreshToken()
|
||||
console.log(JSON.stringify(rT), "refreshtoken")
|
||||
|
||||
if (rT) {
|
||||
/** @type {JWTRefreshClaims} */ // @ts-ignore
|
||||
const refreshClaims = rT.claims
|
||||
|
||||
if (!rT.valid) {
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: " + rT.error,
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
|
||||
bigCommerceId = refreshClaims && refreshClaims.bigCommerceId
|
||||
if (!bigCommerceId) {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "token: inavlid claims",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "missing email and/or password",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Customer[]} */ // @ts-ignore
|
||||
let [customer] = context.db.find("bigCommerceCustomer", {
|
||||
filter: bigCommerceId
|
||||
? { bigCommerceId }
|
||||
: {
|
||||
email,
|
||||
},
|
||||
})
|
||||
|
||||
if (!customer) {
|
||||
if (!bigCommerceId && email) {
|
||||
const customerByEmail = getCustomerById(undefined, email, true)
|
||||
if (customerByEmail) {
|
||||
createTibiCustomer(customerByEmail)
|
||||
;[customer] = context.db.find("bigCommerceCustomer", {
|
||||
filter: bigCommerceId
|
||||
? { bigCommerceId }
|
||||
: {
|
||||
email,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "login failed",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "login failed",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bigCommerceId) {
|
||||
// login via username/password
|
||||
const validate = validateCredentials(email, password)
|
||||
const passwordOK = validate.is_valid
|
||||
if (!passwordOK) {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "login failed",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (customer.locked) {
|
||||
deleteRefreshTokenCookie()
|
||||
throw {
|
||||
status: 403,
|
||||
error: "customer locked",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {JWTLoginClaims} */
|
||||
const loginClaims = {
|
||||
tibiId: customer.id,
|
||||
bigCommerceId: customer.bigCommerceId,
|
||||
email: customer.email,
|
||||
}
|
||||
|
||||
const token = context.jwt.create(loginClaims, {
|
||||
secret: jwtSecret,
|
||||
validityDuration: jwtValidityDuration,
|
||||
})
|
||||
|
||||
const refreshTokenMaxAge = 60 * 60 * 24 // 24h
|
||||
/** @type {JWTRefreshClaims} */
|
||||
const refreshClaims = {
|
||||
tibiId: customer.id,
|
||||
bigCommerceId: customer.bigCommerceId,
|
||||
r: 1,
|
||||
}
|
||||
const nextRefreshToken = context.jwt.create(refreshClaims, {
|
||||
secret: jwtSecret,
|
||||
validityDuration: refreshTokenMaxAge,
|
||||
})
|
||||
|
||||
context.cookie.set("bkdfRefreshToken", nextRefreshToken, {
|
||||
maxAge: refreshTokenMaxAge - 60 * 60, // 1h earlier expire
|
||||
httpOnly: true,
|
||||
path: "/api",
|
||||
})
|
||||
|
||||
throw {
|
||||
status: 200,
|
||||
customer,
|
||||
created: new Date(),
|
||||
token,
|
||||
log: false,
|
||||
}
|
||||
})()
|
||||
25
api/hooks/medialib/delete_delete.js
Normal file
25
api/hooks/medialib/delete_delete.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
if (context.user.auth()) return {}
|
||||
withAccount((login) => {
|
||||
const id = context.request().param("id")
|
||||
const user = context.db.find("bigCommerceCustomer", {
|
||||
filter: {
|
||||
$or: [
|
||||
{
|
||||
"personalRecords.recording": id,
|
||||
},
|
||||
{
|
||||
"personalRecords.thumbnail": id,
|
||||
},
|
||||
],
|
||||
},
|
||||
})[0]
|
||||
if (login.tibiId !== user.id) {
|
||||
return {
|
||||
status: 403,
|
||||
message: "You are not authorized to delete this media",
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
56
api/hooks/order/get_read.js
Normal file
56
api/hooks/order/get_read.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const { getOrderById, getOrdersForCustomer, createInternalOrderObject } = require("../lib/bigcommerceRestAPI")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
if (context.user.auth()) return {}
|
||||
withAccount((login) => {
|
||||
const orderId = context.request().param("id")
|
||||
if (orderId) {
|
||||
/** @type {Order} */
|
||||
// @ts-ignore
|
||||
const internalOrder = context.db.find("bigCommerceOrder", {
|
||||
filter: { _id: orderId },
|
||||
})[0]
|
||||
if (internalOrder.customerTibiId !== login.tibiId) {
|
||||
if (String(internalOrder?.customerBigCommerceId) !== String(login?.bigCommerceId)) {
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
} else {
|
||||
context.db.update("bigCommerceOrder", internalOrder.id, {
|
||||
customerTibiId: login.tibiId,
|
||||
})
|
||||
}
|
||||
}
|
||||
const order = getOrderById(internalOrder.bigCommerceId)
|
||||
order.tibiId = internalOrder.id
|
||||
order.status = internalOrder.status
|
||||
order.shipments = internalOrder.shipments
|
||||
order.statusSetAt = internalOrder.statusSetAt
|
||||
throw {
|
||||
data: order,
|
||||
status: 200,
|
||||
}
|
||||
} else {
|
||||
const orders = getOrdersForCustomer(login.bigCommerceId)
|
||||
orders.forEach((order) => {
|
||||
let internalOrder = context.db.find("bigCommerceOrder", {
|
||||
filter: { bigCommerceId: order.id },
|
||||
})[0]
|
||||
if (!internalOrder && order.id) {
|
||||
const internalOrderReference = createInternalOrderObject(order.id)
|
||||
internalOrder = context.db.create("bigCommerceOrder", internalOrderReference)
|
||||
} else if (!internalOrder) return
|
||||
order.tibiId = internalOrder?.id
|
||||
order.status = internalOrder?.status
|
||||
order.shipments = internalOrder?.shipments
|
||||
order.statusSetAt = internalOrder?.statusSetAt
|
||||
})
|
||||
throw {
|
||||
data: orders,
|
||||
status: 200,
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
5
api/hooks/order/put_update.js
Normal file
5
api/hooks/order/put_update.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { withAccount } from "../lib/utils"
|
||||
;(function () {
|
||||
if (context.user.auth()) return {}
|
||||
withAccount((login) => {})
|
||||
})
|
||||
47
api/hooks/orderReturnRequest/delete_delete.js
Normal file
47
api/hooks/orderReturnRequest/delete_delete.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
withAccount((login) => {
|
||||
const id = context.request().param("id")
|
||||
if (!id) {
|
||||
throw {
|
||||
message: "id is required",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
const existingReturnRequest = context.db.find("orderReturnRequest", {
|
||||
filter: {
|
||||
_id: id,
|
||||
},
|
||||
})[0]
|
||||
if (!existingReturnRequest)
|
||||
throw {
|
||||
message: "Return request not found",
|
||||
code: 404,
|
||||
}
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(existingReturnRequest.bigCommerceId),
|
||||
},
|
||||
})[0]
|
||||
if (!order)
|
||||
throw {
|
||||
message: "Order not found",
|
||||
code: 404,
|
||||
}
|
||||
|
||||
if (order.customerBigCommerceId !== login.bigCommerceId)
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
if (
|
||||
existingReturnRequest.status !== "pending" &&
|
||||
existingReturnRequest.status !== "approved" &&
|
||||
!!existingReturnRequest.status
|
||||
)
|
||||
throw {
|
||||
message: "Return request is not pending or approved",
|
||||
code: 400,
|
||||
}
|
||||
})
|
||||
})()
|
||||
33
api/hooks/orderReturnRequest/delete_return.js
Normal file
33
api/hooks/orderReturnRequest/delete_return.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
|
||||
const { frontendBase } = require("../config-client")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
withAccount((login) => {
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/orderReturnRequestRevoked.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: contactEmail,
|
||||
from: noReplyEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "New Order Return Request Cancellation",
|
||||
plain: `Die Bestellung von ${login.email} soll doch nicht zurückgegeben werden.`,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: login.email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Widerruf deiner Bestellung abgebrochen",
|
||||
html,
|
||||
})
|
||||
})
|
||||
})()
|
||||
41
api/hooks/orderReturnRequest/get_read.js
Normal file
41
api/hooks/orderReturnRequest/get_read.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
const queryParam = context.request().query("orderId")
|
||||
if (!queryParam) {
|
||||
if (context.user.auth()) return
|
||||
throw {
|
||||
message: "orderId is required",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
|
||||
withAccount((login) => {
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(queryParam),
|
||||
},
|
||||
})[0]
|
||||
if (!order) {
|
||||
throw {
|
||||
message: "Order not found",
|
||||
code: 404,
|
||||
}
|
||||
}
|
||||
if (order.customerBigCommerceId !== login.bigCommerceId) {
|
||||
console.log(
|
||||
"order.customerBigCommerceId",
|
||||
order.customerBigCommerceId,
|
||||
"login.bigCommerceId",
|
||||
login.bigCommerceId
|
||||
)
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
}
|
||||
context.filter = {
|
||||
bigCommerceId: order.bigCommerceId,
|
||||
}
|
||||
})
|
||||
return context
|
||||
})()
|
||||
81
api/hooks/orderReturnRequest/post_create.js
Normal file
81
api/hooks/orderReturnRequest/post_create.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { getOrderProductsById } = require("../lib/bigcommerceRestAPI")
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
withAccount((login) => {
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(context.data.bigCommerceId),
|
||||
},
|
||||
})[0]
|
||||
if (!order)
|
||||
throw {
|
||||
message: "Order not found",
|
||||
code: 404,
|
||||
}
|
||||
|
||||
if (order.customerBigCommerceId !== login.bigCommerceId)
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
|
||||
const bigcommerceProducts = getOrderProductsById(context.data.bigCommerceId)
|
||||
const returnRequestProducts = context.data.products
|
||||
// make sure all ids are also in the orderproducts
|
||||
returnRequestProducts.forEach((/** @type {OrderReturnRequestProduct} */ returnRequestProduct) => {
|
||||
const bigcommerceProduct = bigcommerceProducts.find(
|
||||
(bigcommerceProduct) => Number(bigcommerceProduct.id) === returnRequestProduct.productId
|
||||
)
|
||||
if (!bigcommerceProduct)
|
||||
throw {
|
||||
message: "Product not found",
|
||||
code: 404,
|
||||
}
|
||||
if (bigcommerceProduct.quantity < returnRequestProduct.quantity)
|
||||
throw {
|
||||
message: "Quantity exceeds the available quantity",
|
||||
code: 400,
|
||||
}
|
||||
const existingReturnRequests = context.db.find("orderReturnRequest", {
|
||||
filter: {
|
||||
bigCommerceId: context.data.bigCommerceId,
|
||||
$or: [
|
||||
{
|
||||
status: "pending",
|
||||
},
|
||||
{
|
||||
status: "approved",
|
||||
},
|
||||
{
|
||||
status: "refunded",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
returnRequestProduct.baseProductId = bigcommerceProduct.product_id
|
||||
let totalExistingQuantity = 0
|
||||
existingReturnRequests.forEach((existingReturnRequest) => {
|
||||
existingReturnRequest.products.forEach(
|
||||
(/** @type {OrderReturnRequestProduct} */ existingReturnRequestProduct) => {
|
||||
if (existingReturnRequestProduct.productId === returnRequestProduct.productId) {
|
||||
totalExistingQuantity += existingReturnRequestProduct.quantity
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
if (totalExistingQuantity + returnRequestProduct.quantity > bigcommerceProduct.quantity) {
|
||||
throw {
|
||||
message: "Quantity exceeds the available quantity",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
})
|
||||
context.data.products = context.data.products.filter(
|
||||
(/** @type {OrderReturnRequestProduct} */ p) => p.quantity > 0
|
||||
)
|
||||
context.data.status = "pending"
|
||||
context.data.email = login.email
|
||||
})
|
||||
return context
|
||||
})()
|
||||
30
api/hooks/orderReturnRequest/post_return.js
Normal file
30
api/hooks/orderReturnRequest/post_return.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
|
||||
const { frontendBase } = require("../config-client")
|
||||
|
||||
;(function () {
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/orderReturnRequestApproval.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: contactEmail,
|
||||
from: noReplyEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "New Order Return Request",
|
||||
plain: `Die Bestellung von ${context.data.email} soll zurückgegeben werden.`,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: context.data.email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Widerruf deiner Bestellung",
|
||||
html,
|
||||
})
|
||||
})()
|
||||
51
api/hooks/orderReturnRequest/put_update.js
Normal file
51
api/hooks/orderReturnRequest/put_update.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { logoPath, noReplyEmail, operatorEmail, serverBaseURL } = require("../config")
|
||||
const { frontendBase } = require("../config-client")
|
||||
const { getOrderById, updateOrderById } = require("../lib/bigcommerceRestAPI")
|
||||
;(function () {
|
||||
const oldOrderReturnRequest = context.db.find("orderReturnRequest", {
|
||||
filter: {
|
||||
_id: context.data.id,
|
||||
},
|
||||
})[0]
|
||||
if (!oldOrderReturnRequest)
|
||||
throw {
|
||||
message: "Order return request not found",
|
||||
code: 404,
|
||||
}
|
||||
if (context.data.status == "approved") {
|
||||
const bigCommerceOrder = getOrderById(context.data.bigCommerceId)
|
||||
delete bigCommerceOrder.productObjs
|
||||
delete bigCommerceOrder.shipping_addressObjs
|
||||
bigCommerceOrder.status_id = 5
|
||||
updateOrderById(bigCommerceOrder.id, { status_id: 5 })
|
||||
}
|
||||
if (oldOrderReturnRequest.status !== context.data.status) {
|
||||
const oRR = context.data
|
||||
const customer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: oRR.email },
|
||||
})[0]
|
||||
|
||||
if (!customer) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "Customer not found",
|
||||
}
|
||||
}
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/statusUpdateReturnRequest.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: oRR.email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Stornierung deiner Bestellung",
|
||||
html,
|
||||
})
|
||||
}
|
||||
})()
|
||||
35
api/hooks/orderRevokeRequest/get_read.js
Normal file
35
api/hooks/orderRevokeRequest/get_read.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
const queryParam = context.request().query("orderId")
|
||||
if (!queryParam) {
|
||||
if (context.user.auth()) return
|
||||
throw {
|
||||
message: "orderId is required",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
|
||||
withAccount((login) => {
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(queryParam),
|
||||
},
|
||||
})[0]
|
||||
if (!order) {
|
||||
throw {
|
||||
message: "Order not found",
|
||||
code: 404,
|
||||
}
|
||||
}
|
||||
if (order.customerBigCommerceId !== login.bigCommerceId) {
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
}
|
||||
context.filter = {
|
||||
bigCommerceId: order.bigCommerceId,
|
||||
}
|
||||
})
|
||||
return context
|
||||
})()
|
||||
45
api/hooks/orderRevokeRequest/post_create.js
Normal file
45
api/hooks/orderRevokeRequest/post_create.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { getPrintfulOrder, cancelPrintfulOrder } = require("../lib/printfulRestAPI")
|
||||
;(function () {
|
||||
withAccount((login) => {
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(context.data.bigCommerceId),
|
||||
},
|
||||
})[0]
|
||||
if (!order)
|
||||
throw {
|
||||
message: "Order not found",
|
||||
code: 404,
|
||||
}
|
||||
|
||||
if (order.customerBigCommerceId !== login.bigCommerceId)
|
||||
throw {
|
||||
message: "You don't have permission to access this order",
|
||||
code: 403,
|
||||
}
|
||||
const existingRevokeRequests = context.db.find("orderRevokeRequest", {
|
||||
filter: {
|
||||
bigCommerceId: context.data.bigCommerceId,
|
||||
},
|
||||
})
|
||||
if (existingRevokeRequests.length > 0)
|
||||
throw {
|
||||
message: "Revoke request already exists",
|
||||
code: 400,
|
||||
}
|
||||
if (!!order.status && order.status !== "draft") {
|
||||
throw {
|
||||
message: "Order is already in process",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
const printfulOrder = getPrintfulOrder(order.bigCommerceId)
|
||||
|
||||
cancelPrintfulOrder(context.data.bigCommerceId)
|
||||
context.data.status = "pending"
|
||||
context.data.email = login.email
|
||||
context.data.printfulId = printfulOrder.id
|
||||
})
|
||||
return context
|
||||
})()
|
||||
36
api/hooks/orderRevokeRequest/post_return.js
Normal file
36
api/hooks/orderRevokeRequest/post_return.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
|
||||
const { frontendBase } = require("../config-client")
|
||||
const { getOrderById, updateOrderById } = require("../lib/bigcommerceRestAPI")
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
const bigCommerceOrder = getOrderById(context.data.bigCommerceId)
|
||||
delete bigCommerceOrder.productObjs
|
||||
delete bigCommerceOrder.shipping_addressObjs
|
||||
bigCommerceOrder.status_id = 5
|
||||
updateOrderById(bigCommerceOrder.id, { status_id: 5 })
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/orderRevokedApproval.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: contactEmail,
|
||||
from: noReplyEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "New Order Revoke Request",
|
||||
plain: `Die Bestellung von ${context.data.email} wurde abgebrochen.`,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: context.data.email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Abbruch deiner Bestellung",
|
||||
html,
|
||||
})
|
||||
})()
|
||||
43
api/hooks/orderRevokeRequest/put_update.js
Normal file
43
api/hooks/orderRevokeRequest/put_update.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { logoPath, noReplyEmail, operatorEmail, serverBaseURL } from "../config"
|
||||
import { frontendBase } from "../config-client"
|
||||
;(function () {
|
||||
const oldOrderReturnRequest = context.db.find("orderReturnRequest", {
|
||||
filter: {
|
||||
_id: context.data.id,
|
||||
},
|
||||
})[0]
|
||||
if (!oldOrderReturnRequest)
|
||||
throw {
|
||||
message: "Order return request not found",
|
||||
code: 404,
|
||||
}
|
||||
if (oldOrderReturnRequest.status !== context.data.status) {
|
||||
const oRR = context.data
|
||||
const customer = context.db.find("bigCommerceCustomer", {
|
||||
filter: { email: oRR.email },
|
||||
})[0]
|
||||
|
||||
if (!customer) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "Customer not found",
|
||||
}
|
||||
}
|
||||
const store = {
|
||||
logo: `${serverBaseURL}${logoPath}`,
|
||||
frontendBase,
|
||||
}
|
||||
const html = context.tpl.execute(context.fs.readFile("templates/statusUpdateReturnRequest.html"), {
|
||||
store,
|
||||
})
|
||||
|
||||
context.smtp.sendMail({
|
||||
to: oRR.email,
|
||||
from: operatorEmail,
|
||||
fromName: "BinKrassDuFass",
|
||||
replyTo: noReplyEmail,
|
||||
subject: "Stornierung Ihrer Bestellung",
|
||||
html,
|
||||
})
|
||||
}
|
||||
})()
|
||||
23
api/hooks/product/get_read.js
Normal file
23
api/hooks/product/get_read.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { attachRatingObjsToProduct } = require("./helper")
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
const bigCommerceProductId = context.request().query("bigCommerceProductId")
|
||||
if (bigCommerceProductId) {
|
||||
/**@type {LocalProduct[]} */
|
||||
//@ts-ignore
|
||||
let products = context.db.find("bigCommerceProduct", {
|
||||
filter: {
|
||||
bigCommerceId: Number(bigCommerceProductId),
|
||||
},
|
||||
})
|
||||
products = attachRatingObjsToProduct(products)
|
||||
|
||||
if (products.length > 0) {
|
||||
throw {
|
||||
status: 200,
|
||||
data: products[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})()
|
||||
12
api/hooks/product/get_return.js
Normal file
12
api/hooks/product/get_return.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { attachRatingObjsToProduct } = require("./helper")
|
||||
;(function () {
|
||||
if (context.user.auth()) return
|
||||
/**@type {LocalProduct[]} */
|
||||
//@ts-ignore
|
||||
let products = context.results()
|
||||
products = attachRatingObjsToProduct(products)
|
||||
let hookResponse = {
|
||||
results: products,
|
||||
}
|
||||
return hookResponse
|
||||
})()
|
||||
26
api/hooks/product/helper.js
Normal file
26
api/hooks/product/helper.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @param {LocalProduct[]} products
|
||||
* @returns {LocalProduct[]}
|
||||
*/
|
||||
function attachRatingObjsToProduct(products) {
|
||||
let productIds = products.map((product) => product.bigCommerceId)
|
||||
/**@type {ProductRating[]} */
|
||||
//@ts-ignore
|
||||
let allRatings = context.db.find("rating", {
|
||||
filter: {
|
||||
status: "approved",
|
||||
bigCommerceProductId: { $in: productIds },
|
||||
},
|
||||
})
|
||||
|
||||
products.forEach((product, i) => {
|
||||
let ratings = allRatings.filter((rating) => rating.bigCommerceProductId === product.bigCommerceId).reverse()
|
||||
products[i].ratings = ratings
|
||||
})
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
attachRatingObjsToProduct,
|
||||
}
|
||||
36
api/hooks/product/post_create.js
Normal file
36
api/hooks/product/post_create.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { getAllProducts, getProductImages } = require("../lib/bigcommerceRestAPI")
|
||||
const { extractSizingChart } = require("../lib/printfulRestAPI")
|
||||
;(function () {
|
||||
const products = getAllProducts()
|
||||
const productIds = products.map((p) => p.id)
|
||||
const currentProducts = context.db.find("bigCommerceProduct", { filter: { bigCommerceId: { $in: productIds } } })
|
||||
const notFoundProductIds = productIds.filter((id) => !currentProducts.some((p) => p.bigCommerceId == id))
|
||||
|
||||
const newProducts = products.filter((p) => notFoundProductIds.includes(p.id))
|
||||
newProducts.forEach((p) => {
|
||||
const productImage = getProductImages(String(p.id))
|
||||
context.db.create("bigCommerceProduct", {
|
||||
forcedWarning: "",
|
||||
productName: p.name_customer || p.name,
|
||||
previewImage: productImage?.[0]?.url_thumbnail,
|
||||
bigCommerceId: p?.id,
|
||||
sizingChart: null,
|
||||
})
|
||||
})
|
||||
|
||||
currentProducts.forEach((p) => {
|
||||
context.db.update("bigCommerceProduct", p.id, {
|
||||
...p,
|
||||
productName:
|
||||
products.find((bp) => bp.id == p.bigCommerceId).name_customer ||
|
||||
products.find((bp) => bp.id == p.bigCommerceId).name,
|
||||
bigCommerceSKU: products.find((bp) => bp.id == p.bigCommerceId).sku,
|
||||
bigCommerceId: products.find((bp) => bp.id == p.bigCommerceId).id,
|
||||
sizingChart: extractSizingChart(p.printfulProductId, p.id),
|
||||
})
|
||||
})
|
||||
throw {
|
||||
status: 200,
|
||||
message: "Products created or updated successfully",
|
||||
}
|
||||
})()
|
||||
13
api/hooks/product/put_update.js
Normal file
13
api/hooks/product/put_update.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { extractSizingChart } = require("../lib/printfulRestAPI")
|
||||
let { clearSSRCache } = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
clearSSRCache()
|
||||
if (context.data.printfulProductId) {
|
||||
const sizingChart = extractSizingChart(context.data.printfulProductId, context.data.id)
|
||||
if (sizingChart) {
|
||||
context.data.sizingChart = sizingChart
|
||||
return { data: context.data }
|
||||
}
|
||||
}
|
||||
})()
|
||||
15
api/hooks/rating/delete_delete.js
Normal file
15
api/hooks/rating/delete_delete.js
Normal file
@@ -0,0 +1,15 @@
|
||||
;(function () {
|
||||
const ratingId = context.request().param("id")
|
||||
let rating = context.db.find("rating", {
|
||||
filter: {
|
||||
_id: ratingId,
|
||||
},
|
||||
})[0]
|
||||
if (!rating.id)
|
||||
throw {
|
||||
status: 400,
|
||||
error: "No id specified.",
|
||||
}
|
||||
// @ts-ignore
|
||||
context["rating"] = rating
|
||||
})()
|
||||
7
api/hooks/rating/delete_return.js
Normal file
7
api/hooks/rating/delete_return.js
Normal file
@@ -0,0 +1,7 @@
|
||||
let { clearSSRCache } = require("../lib/utils")
|
||||
let { deleteRating } = require("../lib/bigcommerceRestAPI")
|
||||
;(function () {
|
||||
clearSSRCache()
|
||||
// @ts-ignore
|
||||
deleteRating(context["rating"].bigCommerceProductId, context["rating"].bigcommerceReviewId)
|
||||
})()
|
||||
46
api/hooks/rating/get_read.js
Normal file
46
api/hooks/rating/get_read.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// @ts-check
|
||||
const { withAccount } = require("../lib/utils")
|
||||
;(function () {
|
||||
/** @type {HookResponse} */
|
||||
let hookResponse
|
||||
let request = context.request()
|
||||
if (context.user.auth()) return
|
||||
const orderId = Number(request.query("orderId"))
|
||||
if (orderId) {
|
||||
withAccount((login) => {
|
||||
let order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: orderId,
|
||||
customerBigCommerceId: login.bigCommerceId,
|
||||
},
|
||||
})[0]
|
||||
if (!order) {
|
||||
throw {
|
||||
status: 404,
|
||||
data: {
|
||||
message: "Order not found",
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
hookResponse = {
|
||||
filter: {
|
||||
bigcommerceOrderId: orderId,
|
||||
},
|
||||
}
|
||||
|
||||
return hookResponse
|
||||
} else {
|
||||
hookResponse = {
|
||||
filter: context.filter,
|
||||
selector: {
|
||||
bigCommerceProductId: 1,
|
||||
rating: 1,
|
||||
comment: 1,
|
||||
title: 1,
|
||||
review_date: 1,
|
||||
},
|
||||
}
|
||||
return hookResponse
|
||||
}
|
||||
})()
|
||||
17
api/hooks/rating/post_create.js
Normal file
17
api/hooks/rating/post_create.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { productInsideOrder } = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
if (!context?.user?.auth()?.id) {
|
||||
if (!context.data) throw { status: 400, error: "No data provided" }
|
||||
// @ts-ignore
|
||||
productInsideOrder(context.data)
|
||||
/** @type {ProductRating[]} */ // @ts-ignore
|
||||
let ratings = context.db.find("rating", {
|
||||
filter: {
|
||||
bigcommerceOrderId: context?.data?.bigcommerceOrderId,
|
||||
bigCommerceProductId: context?.data?.bigCommerceProductId,
|
||||
},
|
||||
})
|
||||
if (ratings.length) throw { status: 400, error: "Rating already existing" }
|
||||
}
|
||||
})()
|
||||
41
api/hooks/rating/post_return.js
Normal file
41
api/hooks/rating/post_return.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { bigcommerceApiOAuth, serverBaseURL, bigcommerceStoreHash } = require("../config.js")
|
||||
let { sendOperatorRatingMail, clearSSRCache, statusIsValid } = require("../lib/utils")
|
||||
;(function () {
|
||||
const response = context.http.fetch(
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${context?.data?.bigCommerceProductId}/reviews`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: context?.data?.title,
|
||||
comment: context?.data?.comment,
|
||||
status:
|
||||
context?.data?.status === "approved"
|
||||
? "approved"
|
||||
: context?.data?.status == "pending"
|
||||
? "pending"
|
||||
: "disapproved",
|
||||
rating: context?.data?.rating?.overall,
|
||||
date_reviewed: context?.data?.review_date,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
const bigcommerceRating = response.body.json()
|
||||
context.db.update("rating", context?.data?.id || "", {
|
||||
bigcommerceReviewId: String(bigcommerceRating?.data?.id),
|
||||
})
|
||||
|
||||
clearSSRCache()
|
||||
sendOperatorRatingMail()
|
||||
})()
|
||||
5
api/hooks/rating/post_validate.js
Normal file
5
api/hooks/rating/post_validate.js
Normal file
@@ -0,0 +1,5 @@
|
||||
let { validateAndModifyRating } = require("../lib/utils")
|
||||
;(function () {
|
||||
// @ts-ignore
|
||||
return { data: validateAndModifyRating(context.data) }
|
||||
})()
|
||||
42
api/hooks/rating/put_return.js
Normal file
42
api/hooks/rating/put_return.js
Normal file
@@ -0,0 +1,42 @@
|
||||
let { sendOperatorRatingMail, clearSSRCache, statusIsValid } = require("../lib/utils")
|
||||
const { bigcommerceApiOAuth, bigcommerceStoreHash } = require("../config.js")
|
||||
;(function () {
|
||||
const response = context.http.fetch(
|
||||
// @ts-ignore
|
||||
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${context?.data?.bigCommerceProductId}/reviews/${context["oldRating"]?.bigcommerceReviewId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Auth-Token": bigcommerceApiOAuth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: context?.data?.title,
|
||||
comment: context?.data?.comment,
|
||||
status:
|
||||
context?.data?.status === "approved"
|
||||
? "approved"
|
||||
: context?.data?.status == "pending"
|
||||
? "pending"
|
||||
: "disapproved",
|
||||
rating: context?.data?.rating?.overall,
|
||||
date_reviewed: context?.data?.review_date,
|
||||
}),
|
||||
}
|
||||
)
|
||||
if (!statusIsValid(response.status)) {
|
||||
throw {
|
||||
status: response.status,
|
||||
error: response.statusText,
|
||||
}
|
||||
}
|
||||
let rating = context.data
|
||||
// @ts-ignore
|
||||
let oldRating = context["oldRating"]
|
||||
// @ts-ignore
|
||||
if (!oldRating || JSON.stringify(rating) != JSON.stringify(oldRating)) {
|
||||
sendOperatorRatingMail()
|
||||
clearSSRCache()
|
||||
}
|
||||
})()
|
||||
16
api/hooks/rating/put_update.js
Normal file
16
api/hooks/rating/put_update.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { productInsideOrder } = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
if (!context?.user?.auth()?.id) {
|
||||
// @ts-ignore
|
||||
productInsideOrder(context?.data)
|
||||
}
|
||||
/** @type {ProductRating} */ // @ts-ignore
|
||||
let ratingObj = context.db.find("rating", {
|
||||
filter: {
|
||||
_id: context?.data?.id,
|
||||
},
|
||||
})[0]
|
||||
// @ts-ignore
|
||||
context["oldRating"] = ratingObj
|
||||
})()
|
||||
5
api/hooks/rating/put_validate.js
Normal file
5
api/hooks/rating/put_validate.js
Normal file
@@ -0,0 +1,5 @@
|
||||
let { validateAndModifyRating } = require("../lib/utils")
|
||||
;(function () {
|
||||
// @ts-ignore
|
||||
return { data: validateAndModifyRating(context.data) }
|
||||
})()
|
||||
205
api/hooks/ssr/get_read.js
Normal file
205
api/hooks/ssr/get_read.js
Normal file
@@ -0,0 +1,205 @@
|
||||
const utils = require("../lib/utils")
|
||||
|
||||
const { release } = require("../config-client")
|
||||
|
||||
const { ssrValidatePath } = require("../config")
|
||||
const { ssrRequest, ssrRequestBigCommerce } = require("../lib/ssr-server")
|
||||
;(function () {
|
||||
var request = context.request()
|
||||
|
||||
var trackingCall = request.header("x-ssr-skip")
|
||||
if (trackingCall) {
|
||||
// skip tracking
|
||||
// no cache header
|
||||
context.response.header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
throw {
|
||||
status: parseInt(trackingCall),
|
||||
html: "",
|
||||
log: false,
|
||||
}
|
||||
}
|
||||
|
||||
let url = request.query("url") || request.header("x-ssr-url") || "/"
|
||||
const noCache = request.query("noCache")
|
||||
|
||||
const trace_id = context.debug.sentryTraceId()
|
||||
/**
|
||||
* @param {string} content
|
||||
*/
|
||||
function addSentryTrace(content) {
|
||||
return content.replace("</head>", '<meta name="sentry-trace" content="' + trace_id + '" /></head>')
|
||||
}
|
||||
|
||||
context.response.header("sentry-trace", trace_id)
|
||||
const auth = context.user.auth()
|
||||
if (auth && auth.role == 0) {
|
||||
} else if (url) {
|
||||
/** @type {Date} */ // @ts-ignore
|
||||
context.ssrCacheValidUntil = null
|
||||
|
||||
var comment = ""
|
||||
|
||||
url = url.split("?")[0]
|
||||
comment += "url: " + url
|
||||
|
||||
if (url && url.length > 1) {
|
||||
url = url.replace(/\/$/, "")
|
||||
}
|
||||
if (url == "/index" || !url) {
|
||||
url = "/" // see .htaccess
|
||||
}
|
||||
|
||||
function useCache(/** @type {string} */ _url) {
|
||||
var cache =
|
||||
!noCache &&
|
||||
context.db.find("ssr", {
|
||||
filter: {
|
||||
path: _url,
|
||||
},
|
||||
})
|
||||
|
||||
if (cache && cache.length) {
|
||||
// check that entry is still allowed to be published
|
||||
const validUntil = cache[0].validUntil ? new Date(cache[0].validUntil.unixMilli()) : null
|
||||
|
||||
if (!validUntil || validUntil > new Date()) {
|
||||
// use cache
|
||||
context.response.header("X-SSR-Cache", "true")
|
||||
throw {
|
||||
status: 200,
|
||||
log: false,
|
||||
html: addSentryTrace(cache[0].content),
|
||||
}
|
||||
} else {
|
||||
// cache is invalid, delete it
|
||||
context.response.header("X-SSR-Cache", "invalid")
|
||||
context.db.delete("ssr", cache[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validate url
|
||||
var status = 200
|
||||
|
||||
var pNorender = false
|
||||
var pNotfound = false
|
||||
|
||||
const pR = ssrValidatePath(url)
|
||||
if (pR === -1) {
|
||||
// pNotfound = true
|
||||
// comment += ", notFound"
|
||||
} else if (!pR) {
|
||||
pNorender = true
|
||||
comment += ", noRender"
|
||||
} else if (typeof pR === "string") {
|
||||
url = pR
|
||||
comment += ", cache url: " + url
|
||||
}
|
||||
|
||||
if (noCache) {
|
||||
comment += ", noCache"
|
||||
}
|
||||
|
||||
if (!pNorender && !pNotfound) {
|
||||
// check if we have a cache
|
||||
useCache(url)
|
||||
}
|
||||
|
||||
let head = ""
|
||||
let html = ""
|
||||
let error = ""
|
||||
|
||||
comment += ", path: " + url
|
||||
|
||||
var cacheIt = false
|
||||
if (pNorender) {
|
||||
html = "<!-- NO SSR RENDERING -->"
|
||||
} else if (pNotfound) {
|
||||
status = 404
|
||||
html = "404 NOT FOUND"
|
||||
} else {
|
||||
// @ts-ignore
|
||||
context.ssrCache = {}
|
||||
// @ts-ignore
|
||||
context.ssrRequest = ssrRequest
|
||||
// @ts-ignore
|
||||
context.ssrRequestBigCommerce = ssrRequestBigCommerce
|
||||
|
||||
try {
|
||||
// if error, output plain html without prerendering
|
||||
// @ts-ignore
|
||||
const app = require("../lib/app.server")
|
||||
|
||||
const rendered = app.default.render({
|
||||
url: url,
|
||||
})
|
||||
head = rendered.head
|
||||
html = rendered.html
|
||||
|
||||
head +=
|
||||
"\n\n" +
|
||||
"<script>window.__SSR_CACHE__ = " +
|
||||
// @ts-ignore
|
||||
JSON.stringify(context.ssrCache) +
|
||||
"</script>"
|
||||
|
||||
// status from webapp
|
||||
// @ts-ignore
|
||||
if (context.is404) {
|
||||
status = 404
|
||||
} else {
|
||||
cacheIt = true
|
||||
}
|
||||
} catch (/** @type {any} */ e) {
|
||||
utils.log(e.message)
|
||||
utils.log(e.stack)
|
||||
error = "error: " + e.message + "\n\n" + e.stack
|
||||
|
||||
// utils.log(e)
|
||||
// for (var property in e) {
|
||||
// utils.log(property + ": " + e[property])
|
||||
// }
|
||||
// error = JSON.stringify(e)
|
||||
}
|
||||
}
|
||||
|
||||
var tpl = context.fs.readFile("templates/spa.html")
|
||||
tpl = tpl.replace("<!--HEAD-->", head)
|
||||
tpl = tpl.replace("<!--HTML-->", html)
|
||||
tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "")
|
||||
tpl = tpl.replace("<!--SSR.COMMENT-->", comment ? "<!--" + comment + "-->" : "")
|
||||
|
||||
if (cacheIt && !noCache) {
|
||||
// save cache
|
||||
const existing = context.db.find("ssr", {
|
||||
filter: {
|
||||
path: url,
|
||||
},
|
||||
})
|
||||
if (existing && existing.length) {
|
||||
context.db.delete("ssr", existing[0].id)
|
||||
}
|
||||
|
||||
context.db.create("ssr", {
|
||||
path: url,
|
||||
content: tpl,
|
||||
// @ts-ignore
|
||||
validUntil: context.ssrCacheValidUntil,
|
||||
})
|
||||
}
|
||||
|
||||
throw {
|
||||
status: status,
|
||||
log: false,
|
||||
html: addSentryTrace(tpl),
|
||||
}
|
||||
} else {
|
||||
// only admins are allowed
|
||||
throw {
|
||||
status: 403,
|
||||
message: "invalid auth",
|
||||
auth: auth,
|
||||
release: release,
|
||||
}
|
||||
}
|
||||
})()
|
||||
25
api/hooks/ssr/post_bind.js
Normal file
25
api/hooks/ssr/post_bind.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { release } = require("../config-client")
|
||||
var utils = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
if (context.request().query("clear")) {
|
||||
console.log("CLEARING SSR CACHE")
|
||||
var removed = utils.clearSSRCache()
|
||||
var stats = {
|
||||
status: 200,
|
||||
message: "ok",
|
||||
removed: removed,
|
||||
release: release,
|
||||
}
|
||||
|
||||
utils.log(stats)
|
||||
|
||||
throw stats
|
||||
}
|
||||
|
||||
throw {
|
||||
status: 500,
|
||||
message: "ssr is only a dummy collection",
|
||||
release: release,
|
||||
}
|
||||
})()
|
||||
25
api/hooks/webhook/delete_delete.js
Normal file
25
api/hooks/webhook/delete_delete.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { bigcommerceApiOAuth, serverBaseURL, bigcommerceStoreHash } = require("../config.js")
|
||||
const { deletePrintfulWebhook } = require("../lib/printfulRestAPI.js")
|
||||
;(function () {
|
||||
const data = context.data
|
||||
if (context?.user?.auth()?.id) {
|
||||
const webhook = context.db.find("webhook", {
|
||||
filter: {
|
||||
_id: context.request().param("id"),
|
||||
},
|
||||
})[0]
|
||||
|
||||
if (webhook.type === "printful") {
|
||||
deletePrintfulWebhook(webhook.scope)
|
||||
} else {
|
||||
let url = `https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/hooks/${webhook.webhookId}`
|
||||
|
||||
let options = {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json", "X-Auth-Token": bigcommerceApiOAuth },
|
||||
}
|
||||
|
||||
const response = context.http.fetch(url, options).body.json()
|
||||
}
|
||||
}
|
||||
})()
|
||||
266
api/hooks/webhook/post_create.js
Normal file
266
api/hooks/webhook/post_create.js
Normal file
@@ -0,0 +1,266 @@
|
||||
const { bigcommerceApiOAuth, serverBaseURL, bigcommerceStoreHash } = require("../config.js")
|
||||
let { clearSSRCache, createTibiCustomer, updateTibiCustomer } = require("../lib/utils")
|
||||
const {
|
||||
getProductImages,
|
||||
getOrderProductsById,
|
||||
getProductById,
|
||||
getCustomerById,
|
||||
getOrderById,
|
||||
createInternalOrderObject,
|
||||
} = require("../lib/bigcommerceRestAPI.js")
|
||||
const { createPrintfulWebhook, getPrintfulOrderShipments } = require("../lib/printfulRestAPI.js")
|
||||
const { postPurchase } = require("../lib/facebookRestAPI.js")
|
||||
;(function () {
|
||||
const data = context.data
|
||||
if (context?.user?.auth()?.id) {
|
||||
if (context.data.type === "printful") {
|
||||
createPrintfulWebhook(context.data.scope)
|
||||
return
|
||||
} else {
|
||||
let url = `https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/hooks`
|
||||
|
||||
let options = {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Auth-Token": bigcommerceApiOAuth },
|
||||
body: JSON.stringify({
|
||||
scope: context?.data?.scope,
|
||||
destination: `${serverBaseURL}webhook`,
|
||||
is_active: context?.data?.active,
|
||||
events_history_enabled: true,
|
||||
headers: { token: "super_secure_and_extremely_secret_big_commerce_webhook_token_123" },
|
||||
}),
|
||||
}
|
||||
|
||||
const response = context.http.fetch(url, options).body.json()
|
||||
const hookResponse = {
|
||||
data: context.data,
|
||||
}
|
||||
// @ts-ignore
|
||||
hookResponse.data.webhookId = response.data.id
|
||||
return hookResponse
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
if (data?.type === "order_updated") {
|
||||
const bigCommerceOrderId = data?.data?.order?.external_id
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(bigCommerceOrderId),
|
||||
},
|
||||
})[0]
|
||||
if (order) {
|
||||
context.db.update("bigCommerceOrder", order.id, {
|
||||
status: data?.data?.order?.status,
|
||||
statusSetAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
throw {
|
||||
status: 200,
|
||||
data: {
|
||||
message: "",
|
||||
},
|
||||
}
|
||||
} else if (data?.type == "shipment_sent") {
|
||||
const bigCommerceOrderId = data?.data?.order?.external_id
|
||||
|
||||
const printfulOrderShipments = getPrintfulOrderShipments(Number(bigCommerceOrderId))
|
||||
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(bigCommerceOrderId),
|
||||
},
|
||||
})[0]
|
||||
if (order) {
|
||||
context.db.update("bigCommerceOrder", order.id, {
|
||||
shipments: (printfulOrderShipments || []).map((shipped) => {
|
||||
return {
|
||||
trackingUrl: shipped.tracking_url,
|
||||
trackingNumber: data?.data?.shipment?.tracking_number,
|
||||
carrier: shipped.carrier,
|
||||
sentAt: new Date(),
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
throw {
|
||||
status: 200,
|
||||
data: {
|
||||
message: "",
|
||||
},
|
||||
}
|
||||
} else if (data?.scope?.includes("product")) {
|
||||
if (data.scope.includes("product/deleted")) {
|
||||
handleProductDeleted(data.data.id)
|
||||
clearSSRCache()
|
||||
} else {
|
||||
const product = getProductById(data?.data?.id)
|
||||
const productImage = getProductImages(data?.data?.id)
|
||||
|
||||
if (data.scope.includes("product/created") && product?.id) {
|
||||
handleProductCreateOrUpdate(product, productImage)
|
||||
}
|
||||
if (data.scope.includes("product/updated") && product?.id) {
|
||||
handleProductCreateOrUpdate(product, productImage)
|
||||
}
|
||||
clearSSRCache()
|
||||
}
|
||||
throw {
|
||||
status: 200,
|
||||
data: {
|
||||
message: "",
|
||||
},
|
||||
}
|
||||
} else if (data?.scope?.includes("order")) {
|
||||
if (data.scope.includes("order/statusUpdated")) {
|
||||
if (data.data.status.new_status_id == 11) {
|
||||
const internalOrderReference = createInternalOrderObject(data.data.id)
|
||||
// make sure the order doesn't already exist
|
||||
const orderExists = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: Number(data.data.id),
|
||||
},
|
||||
})
|
||||
if (!orderExists.length) context.db.create("bigCommerceOrder", internalOrderReference)
|
||||
else context.db.update("bigCommerceOrder", orderExists[0].id, internalOrderReference)
|
||||
/*
|
||||
not necessary for now, as bigcommerce itself takes care of it
|
||||
try {
|
||||
if (!orderExists) {
|
||||
postPurchase(internalOrderReference.cost.replace(",", "."))
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}*/
|
||||
} else {
|
||||
const order = context.db.find("bigCommerceOrder", {
|
||||
filter: {
|
||||
bigCommerceId: data.data.id,
|
||||
},
|
||||
})[0]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} orderStatus
|
||||
* @returns {PrintfulStates | false}
|
||||
*/
|
||||
function mapOrderStatusToPrintful(orderStatus) {
|
||||
switch (orderStatus.new_status_id) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 7:
|
||||
case 11:
|
||||
return "draft" // Unvollständig
|
||||
case 6:
|
||||
return "failed" // Abgelehnt
|
||||
case 10:
|
||||
return "fulfilled" // Versandt, Versand ausstehend, Abgeschlossen
|
||||
case 3:
|
||||
return "partial" // Teilweise Versandt, Teilweise erstattet
|
||||
case 12:
|
||||
case 13:
|
||||
return "onhold" // Manuelle Überprüfung erforderlich, Umstritten (closest match)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (order && mapOrderStatusToPrintful(data.data.status) !== false) {
|
||||
context.db.update("bigCommerceOrder", order.id, {
|
||||
status: mapOrderStatusToPrintful(data.data.status),
|
||||
statusSetAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
throw {
|
||||
status: 200,
|
||||
data: {
|
||||
message: "",
|
||||
},
|
||||
}
|
||||
} else if (data?.scope?.includes("customer")) {
|
||||
let internalCustomer
|
||||
if (data.data?.id)
|
||||
/** @type {Customer} */
|
||||
internalCustomer =
|
||||
context.db.find("bigCommerceCustomer", {
|
||||
filter: {
|
||||
bigCommerceId: data.data.id,
|
||||
},
|
||||
})?.[0] || null
|
||||
|
||||
if (data.scope.includes("customer/deleted") && !!internalCustomer) {
|
||||
context.db.delete("bigCommerceCustomer", internalCustomer.id)
|
||||
} else {
|
||||
const customer = getCustomerById(data?.data?.id)
|
||||
|
||||
if (!internalCustomer && customer?.id) {
|
||||
createTibiCustomer(customer)
|
||||
} else if (data.scope.includes("customer/updated") && !!internalCustomer) {
|
||||
updateTibiCustomer(customer, internalCustomer)
|
||||
}
|
||||
}
|
||||
throw {
|
||||
status: 200,
|
||||
data: {
|
||||
message: "",
|
||||
},
|
||||
}
|
||||
} else
|
||||
throw {
|
||||
status: 403,
|
||||
error: "Forbidden",
|
||||
}
|
||||
} else
|
||||
throw {
|
||||
status: 403,
|
||||
error: "Forbidden",
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* Handles both product creation and update by checking if the product exists and then
|
||||
* creating or updating the record accordingly.
|
||||
*
|
||||
* @param {V2OrderProductsResponseBase} [product] The product data.
|
||||
* @param { ProductImage[]} [productImage] The product image data.
|
||||
*/
|
||||
function handleProductCreateOrUpdate(product, productImage) {
|
||||
const internalProduct =
|
||||
context.db.find("bigCommerceProduct", {
|
||||
filter: {
|
||||
bigCommerceId: Number(product?.id),
|
||||
},
|
||||
})?.[0] || null
|
||||
if (internalProduct) {
|
||||
// If the product exists, update it.
|
||||
context.db.update("bigCommerceProduct", internalProduct.id || "", {
|
||||
productName: product?.name_customer || product?.name,
|
||||
bigCommerceSKU: product?.sku,
|
||||
previewImage: productImage?.[0]?.url_thumbnail,
|
||||
bigCommerceId: Number(product?.id),
|
||||
})
|
||||
} else {
|
||||
// If the product does not exist, create it.
|
||||
context.db.create("bigCommerceProduct", {
|
||||
productName: product?.name_customer || product?.name,
|
||||
bigCommerceSKU: product?.sku,
|
||||
previewImage: productImage?.[0]?.url_thumbnail,
|
||||
bigCommerceId: Number(product?.id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles product deletion by removing the product record if it exists.
|
||||
* @param { V2OrderProductsResponseBase } [id] The product data to delete.
|
||||
*/
|
||||
function handleProductDeleted(id) {
|
||||
const internalProduct = context.db.find("bigCommerceProduct", {
|
||||
filter: {
|
||||
bigCommerceId: Number(id),
|
||||
},
|
||||
})[0]
|
||||
if (internalProduct) {
|
||||
context.db.delete("bigCommerceProduct", internalProduct.id || "")
|
||||
}
|
||||
}
|
||||
28
api/hooks/webhook/put_update.js
Normal file
28
api/hooks/webhook/put_update.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { bigcommerceApiOAuth, serverBaseURL, bigcommerceStoreHash } = require("../config.js")
|
||||
;(function () {
|
||||
const data = context.data
|
||||
if (context?.user?.auth()?.id) {
|
||||
if (context.data.type === "printful") {
|
||||
throw {
|
||||
status: 500,
|
||||
message: "Printful webhooks update are not supported",
|
||||
}
|
||||
} else {
|
||||
let url = `https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/hooks/${context.data.webhookId}`
|
||||
|
||||
let options = {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", "X-Auth-Token": bigcommerceApiOAuth },
|
||||
body: JSON.stringify({
|
||||
scope: context?.data?.scope,
|
||||
destination: `${serverBaseURL}webhook`,
|
||||
is_active: context?.data?.active,
|
||||
events_history_enabled: true,
|
||||
headers: { token: "super_secure_and_extremely_secret_big_commerce_webhook_token_123" },
|
||||
}),
|
||||
}
|
||||
|
||||
const response = context.http.fetch(url, options).body.json()
|
||||
}
|
||||
}
|
||||
})()
|
||||
12
api/hooks/wishlist/get_read.js
Normal file
12
api/hooks/wishlist/get_read.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { getWishlistEntries } = require("../lib/bigcommerceRestAPI.js")
|
||||
;(function () {
|
||||
withAccount((login) => {
|
||||
const customerId = login.bigCommerceId
|
||||
const wishlist = getWishlistEntries(customerId)
|
||||
throw {
|
||||
status: 200,
|
||||
data: wishlist,
|
||||
}
|
||||
})
|
||||
})()
|
||||
35
api/hooks/wishlist/post_create.js
Normal file
35
api/hooks/wishlist/post_create.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { removeWishlistEntry } = require("../lib/bigcommerceRestAPI.js")
|
||||
const { withAccount } = require("../lib/utils")
|
||||
const { createWishlistEntry } = require("../lib/bigcommerceRestAPI.js")
|
||||
;(function () {
|
||||
withAccount((login) => {
|
||||
const productId = context.data.productId
|
||||
const variantId = context.data.variantId
|
||||
const customerId = login.bigCommerceId
|
||||
if (context.data.delete) {
|
||||
if (!productId || !variantId) {
|
||||
throw {
|
||||
message: "Invalid product or variant id",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
const wishlist = removeWishlistEntry(customerId, productId, variantId)
|
||||
throw {
|
||||
status: 200,
|
||||
data: wishlist,
|
||||
}
|
||||
}
|
||||
|
||||
if (!productId || !variantId) {
|
||||
throw {
|
||||
message: "Invalid product or variant id",
|
||||
code: 400,
|
||||
}
|
||||
}
|
||||
const wishlist = createWishlistEntry(productId, variantId, customerId)
|
||||
throw {
|
||||
status: 200,
|
||||
data: wishlist,
|
||||
}
|
||||
})
|
||||
})()
|
||||
Reference in New Issue
Block a user