Organizacion de las pool de eventos y implementacion
en el scheduler, de momento campos limitados
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { ORDER } from "#data/data-pools/ORDER.js";
|
||||
import { generateHMACSignature } from "#middleware/hmac.js";
|
||||
import { requestBuilder, shopifyHeaderBuilder } from "#shared/requests.js";
|
||||
import { ScheduledEvent, ShopifyEvent, SubscriptionData, SubscriptorData, Topic } from "./subscriptions.types";
|
||||
@@ -11,6 +12,7 @@ export class EventScheduler {
|
||||
public eventList: ScheduledEvent[] = []
|
||||
public activeIntervals: NodeJS.Timeout[] = []
|
||||
public subscriptionManager: SubscriptionManager
|
||||
|
||||
constructor(args: {
|
||||
events?: ScheduledEvent[],
|
||||
subscriptionManager: SubscriptionManager
|
||||
@@ -36,7 +38,7 @@ export class EventScheduler {
|
||||
|
||||
this.subscriptionManager.poll(<ShopifyEvent>{
|
||||
topic: event.topic,
|
||||
data: { id: 123 }
|
||||
data: ORDER.randomValue()
|
||||
})
|
||||
|
||||
sentMesages++;
|
||||
@@ -84,13 +86,10 @@ export class SubscriptionManager {
|
||||
|
||||
const subscriptors = this.subscriptions.get(topic)
|
||||
|
||||
console.log("[Server] Enviando evento a ", subscriptors)
|
||||
const body = event.data
|
||||
const parsedBody = JSON.stringify(body)
|
||||
|
||||
for (const sub of subscriptors!.subscriptors) {
|
||||
const body = {
|
||||
id: 1234
|
||||
}
|
||||
const parsedBody = JSON.stringify(body)
|
||||
const signature = generateHMACSignature(parsedBody, sub.secretkey)
|
||||
const request = requestBuilder({
|
||||
method: sub.method,
|
||||
|
||||
24
src/data/data-pools/ADDRESS.ts
Normal file
24
src/data/data-pools/ADDRESS.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Shopify } from "#data/webhooks/order.js";
|
||||
import { FieldPool } from "./pool.type";
|
||||
|
||||
export const ADDRESS = new FieldPool<Shopify.Address>(
|
||||
{
|
||||
id: [9012345, 4455667, 1122334],
|
||||
customer_id: [7182931, 8293012, 1029384],
|
||||
first_name: ["Maria", "John", "Yuki"],
|
||||
last_name: ["García", "Doe", "Tanaka"],
|
||||
company: ["Tech Solutions S.L.", "Global Corp", null],
|
||||
address1: ["Calle Mayor 1, 4B", "123 Business Ave", "Shibuya-ku 1-2-3"],
|
||||
address2: ["Portal A", "Suite 500", null],
|
||||
city: ["Madrid", "New York", "Tokyo"],
|
||||
province: ["Madrid", "New York", "Tokyo"],
|
||||
country: ["Spain", "United States", "Japan"],
|
||||
zip: ["28001", "10001", "150-0002"],
|
||||
phone: ["+34600112233", "+15559876543", "+81312345678"],
|
||||
name: ["Maria García", "John Doe", "Yuki Tanaka"],
|
||||
province_code: ["M", "NY", "13"],
|
||||
country_code: ["ES", "US", "JP"],
|
||||
country_name: ["Spain", "United States", "Japan"],
|
||||
default: [true, false, false]
|
||||
}
|
||||
)
|
||||
46
src/data/data-pools/CUSTOMER.ts
Normal file
46
src/data/data-pools/CUSTOMER.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Shopify } from "#data/webhooks/order.js";
|
||||
import { ADDRESS } from "./ADDRESS";
|
||||
import { FieldPool } from "./pool.type";
|
||||
|
||||
export const CUSTOMER = new FieldPool<Shopify.CustomerData>(
|
||||
{
|
||||
id: [7182931, 8293012, 1029384],
|
||||
created_at: ["2023-01-15T10:00:00Z", "2023-11-20T15:30:45Z", "2024-02-05T08:12:00Z"],
|
||||
updated_at: ["2023-12-01T12:00:00Z", "2024-03-10T09:45:00Z", "2024-05-20T18:20:10Z"],
|
||||
first_name: ["Maria", "Carlos", "null"], // Ejemplo de nulidad
|
||||
last_name: ["García", "Smith", "Villanueva"],
|
||||
state: ["enabled", "disabled", "invited"],
|
||||
note: ["Cliente VIP", "Prefiere entrega por la tarde", null],
|
||||
verified_email: [true, false, true],
|
||||
multipass_identifier: ["ID-99283", "EXT-9102", null],
|
||||
tax_exempt: [false, true, false],
|
||||
email_marketing_consent: [
|
||||
{
|
||||
state: "subscribed",
|
||||
opt_in_level: "confirmed_opt_in",
|
||||
consent_updated_at: "2024-01-01T10:00:00Z"
|
||||
},
|
||||
{
|
||||
state: "unsubscribed",
|
||||
opt_in_level: "single_opt_in",
|
||||
consent_updated_at: "2023-05-12T14:30:00Z"
|
||||
},
|
||||
null
|
||||
],
|
||||
sms_marketing_consent: [true, false, null],
|
||||
tags: ["premium, newsletter", "wholesale", ""],
|
||||
email: ["maria.g@example.com", "carlos_dev@test.io", "info@empresa.es"],
|
||||
phone: ["+34600112233", "+15559876543", null],
|
||||
currency: ["EUR", "USD", "MXN"],
|
||||
tax_exemptions: [
|
||||
[],
|
||||
["CA_STATUS_CARD", "EXEMPT_CERTIFICATE"],
|
||||
["TAX_ID_99"]
|
||||
],
|
||||
admin_graphql_api_id: [
|
||||
"gid://shopify/Customer/7182931",
|
||||
"gid://shopify/Customer/8293012",
|
||||
"gid://shopify/Customer/1029384"
|
||||
],
|
||||
default_address: [ADDRESS.randomValue()]
|
||||
})
|
||||
68
src/data/data-pools/LINE_ITEM.ts
Normal file
68
src/data/data-pools/LINE_ITEM.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Shopify } from "#data/webhooks/order.js";
|
||||
import { FieldPool } from "./pool.type";
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Muchos de los campos se tendrian que generar a partir de las POOL de los demas
|
||||
*/
|
||||
|
||||
export const LINE_ITEM = new FieldPool<Shopify.LineItem>({
|
||||
id: [12131415, 22232425, 33343536],
|
||||
admin_graphql_api_id: [
|
||||
"gid://shopify/LineItem/12131415",
|
||||
"gid://shopify/LineItem/22232425",
|
||||
"gid://shopify/LineItem/33343536"
|
||||
],
|
||||
attributed_staffs: [[], [{ id: "staff_1", name: "Alex" }], []],
|
||||
current_quantity: [1, 2, 0],
|
||||
fulfillable_quantity: [1, 0, 5],
|
||||
fulfillment_service: ["manual", "amazon-fba", "shipstation"],
|
||||
fulfillment_status: ["fulfilled", "null", "partial"],
|
||||
gift_card: [false, false, true],
|
||||
grams: [500, 1200, 0],
|
||||
name: ["Camiseta Algodón - L / Azul", "Zapatillas Running - 42", "Tarjeta Regalo Digital"],
|
||||
price: ["29.99", "85.00", "50.00"],
|
||||
price_set: [
|
||||
{
|
||||
shop_money: { amount: "29.99", currency_code: "EUR" },
|
||||
presentment_money: { amount: "29.99", currency_code: "EUR" }
|
||||
},
|
||||
{
|
||||
shop_money: { amount: "85.00", currency_code: "USD" },
|
||||
presentment_money: { amount: "78.50", currency_code: "EUR" }
|
||||
},
|
||||
{
|
||||
shop_money: { amount: "50.00", currency_code: "EUR" },
|
||||
presentment_money: { amount: "50.00", currency_code: "EUR" }
|
||||
}
|
||||
],
|
||||
product_exists: [true, true, false],
|
||||
product_id: [7890123, 8901234, null],
|
||||
properties: [
|
||||
[{ name: "Grabado", value: "Para mi mejor amigo" }],
|
||||
[],
|
||||
[{ name: "Envoltorio", value: "Papel reciclado" }, { name: "Prioridad", value: "Alta" }]
|
||||
],
|
||||
quantity: [1, 2, 5],
|
||||
requires_shipping: [true, true, false],
|
||||
sku: ["TSHIRT-L-BL", "RUN-SHOE-42", "GIFT-CARD-DIG"],
|
||||
taxable: [true, true, false],
|
||||
title: ["Camiseta Algodón", "Zapatillas Running", "Tarjeta Regalo"],
|
||||
total_discount: ["0.00", "5.00", "10.00"],
|
||||
total_discount_set: [
|
||||
{ shop_money: { amount: "0.00", currency_code: "EUR" }, presentment_money: { amount: "0.00", currency_code: "EUR" } },
|
||||
{ shop_money: { amount: "5.00", currency_code: "EUR" }, presentment_money: { amount: "5.00", currency_code: "EUR" } },
|
||||
{ shop_money: { amount: "10.00", currency_code: "EUR" }, presentment_money: { amount: "10.00", currency_code: "EUR" } }
|
||||
],
|
||||
variant_id: [454567, 889910, null],
|
||||
variant_inventory_management: ["shopify", null, "external"],
|
||||
variant_title: ["L / Azul", "42", null],
|
||||
vendor: ["Mi Marca S.A.", "Nike", "Apple"],
|
||||
tax_lines: [
|
||||
[{ title: "IVA", price: "6.30", rate: 0.21, price_set: {}, channel_liable: false }],
|
||||
[{ title: "VAT", price: "17.85", rate: 0.21, price_set: {}, channel_liable: false }],
|
||||
[]
|
||||
],
|
||||
duties: [[], [], []],
|
||||
discount_allocations: [[], [], []]
|
||||
})
|
||||
22
src/data/data-pools/ORDER.ts
Normal file
22
src/data/data-pools/ORDER.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Opciones para los campos
|
||||
* TODO:
|
||||
* - Alguna forma de mapear todos los campos como listas de los originales
|
||||
*/
|
||||
|
||||
import { ADDRESS } from "#data/data-pools/ADDRESS.js";
|
||||
import { CUSTOMER } from "#data/data-pools/CUSTOMER.js";
|
||||
import { FieldPool, PoolOf } from "#data/data-pools/pool.type.js";
|
||||
import { Shopify } from "../webhooks/order";
|
||||
|
||||
|
||||
export const ORDER = new FieldPool<Shopify.Order>({
|
||||
id: [1, 2, 3],
|
||||
customer: CUSTOMER.randomValue,
|
||||
shipping_address: ADDRESS.randomValue,
|
||||
|
||||
})
|
||||
|
||||
export const INVALID_POOL = {
|
||||
|
||||
}
|
||||
40
src/data/data-pools/PRICE_SET.ts
Normal file
40
src/data/data-pools/PRICE_SET.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Shopify } from "#data/webhooks/order.js";
|
||||
import { FieldPool } from "./pool.type";
|
||||
|
||||
/**
|
||||
* shop_money y presentment_money tienen una correlacion que no entiendo. Necesitaria una
|
||||
* funcion aleatoria nueva
|
||||
*/
|
||||
|
||||
export const PRICE_SET = new FieldPool<Shopify.PriceSet>({
|
||||
shop_money: [
|
||||
{
|
||||
amount: "100.00",
|
||||
currency_code: "USD"
|
||||
},
|
||||
{
|
||||
amount: "45.50",
|
||||
currency_code: "EUR"
|
||||
},
|
||||
{
|
||||
amount: "12500",
|
||||
currency_code: "JPY"
|
||||
}
|
||||
],
|
||||
presentment_money: [
|
||||
{
|
||||
amount: "100.00",
|
||||
currency_code: "USD"
|
||||
},
|
||||
{
|
||||
amount: "39.95",
|
||||
currency_code: "GBP"
|
||||
},
|
||||
{
|
||||
amount: "12500",
|
||||
currency_code: "JPY"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
28
src/data/data-pools/pool.test.ts
Normal file
28
src/data/data-pools/pool.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, test } from "vitest";
|
||||
import { ORDER } from "./ORDER";
|
||||
import { CUSTOMER } from "./CUSTOMER";
|
||||
import { ADDRESS } from "./ADDRESS";
|
||||
|
||||
|
||||
describe("Test pool", () => {
|
||||
|
||||
test("generacion de costumers", () => {
|
||||
const costumers = [
|
||||
CUSTOMER.randomValue()
|
||||
]
|
||||
console.log("Costumers", costumers)
|
||||
})
|
||||
test("generacion de address", () => {
|
||||
const costumers = [
|
||||
ADDRESS.randomValue()
|
||||
]
|
||||
console.log("ADDRESS", costumers)
|
||||
})
|
||||
test("generacion de 3 order distintos", () => {
|
||||
const orders = [
|
||||
ORDER.randomValue(),
|
||||
]
|
||||
|
||||
console.log(orders)
|
||||
})
|
||||
})
|
||||
77
src/data/data-pools/pool.type.ts
Normal file
77
src/data/data-pools/pool.type.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { NullablePartial } from "#shared/NullablePartail.js";
|
||||
|
||||
/**
|
||||
* Una pool de valores derivada de un tipo existente
|
||||
* puede ser o bien:
|
||||
* - Una lista de valores predefinidos
|
||||
* - Una funcion que devuelve un valor geneerado del mismo tipo que el campo original
|
||||
*/
|
||||
export type PoolOf<T> = {
|
||||
[K in keyof T]: T[K][] | (() => T[K]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function getRandomOf<T>(pool: PoolOf<T>) {
|
||||
const mock: NullablePartial<T> = {}
|
||||
const options = pool
|
||||
if (options == undefined) throw new Error("El campo no existe en la pool")
|
||||
for (const optKey in options) {
|
||||
|
||||
const field: T[keyof T][] | (() => T[keyof T]) = options[optKey as keyof typeof options]
|
||||
if (field == undefined) continue;
|
||||
|
||||
if (typeof field === "function") {
|
||||
mock[optKey as keyof typeof mock] = field()
|
||||
} else {
|
||||
const examplesLen = field.length
|
||||
if (examplesLen == undefined) continue;
|
||||
const seleccionado = field[Math.floor(Math.random() * examplesLen)]
|
||||
mock[optKey as keyof typeof mock] = seleccionado as any // no se como resolver el tipo aqui, se confia que en el ejemplo esté bien
|
||||
}
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
/**
|
||||
* Un FieldPool es un wraper de PoolOf que asocia una funcion
|
||||
* aleatoria personalizada al tipo de la pool
|
||||
*/
|
||||
export class FieldPool<T> {
|
||||
public pool: PoolOf<T>
|
||||
public randomValue = () => {
|
||||
return this.randomFunc(this.pool)
|
||||
}
|
||||
|
||||
constructor(
|
||||
pool: PoolOf<T>,
|
||||
randFunc?: <T>(pool: PoolOf<T>) => T
|
||||
) {
|
||||
this.pool = pool
|
||||
if (randFunc != undefined) {
|
||||
this.randomFunc = randFunc
|
||||
}
|
||||
}
|
||||
private randomFunc(pool: PoolOf<T>) {
|
||||
const mock: NullablePartial<T> = {}
|
||||
const options = pool
|
||||
if (options == undefined) throw new Error("El campo no existe en la pool")
|
||||
for (const optKey in options) {
|
||||
|
||||
const field: T[keyof T][] | (() => T[keyof T]) = options[optKey as keyof typeof options]
|
||||
if (field == undefined) continue;
|
||||
|
||||
if (typeof field === "function") {
|
||||
mock[optKey as keyof typeof mock] = field()
|
||||
} else {
|
||||
const examplesLen = field.length
|
||||
if (examplesLen == undefined) continue;
|
||||
const seleccionado = field[Math.floor(Math.random() * examplesLen)]
|
||||
mock[optKey as keyof typeof mock] = seleccionado as any // no se como resolver el tipo aqui, se confia que en el ejemplo esté bien
|
||||
}
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Opciones para los campos
|
||||
* TODO:
|
||||
* - Alguna forma de mapear todos los campos como listas de los originales
|
||||
*/
|
||||
|
||||
import { NullablePartial } from "#shared/NullablePartail.js";
|
||||
import { Shopify } from "./order";
|
||||
|
||||
type PoolOf<T> = {
|
||||
[K in keyof T]: T[K][];
|
||||
};
|
||||
|
||||
type RepoOfPools<T> = {
|
||||
[K in keyof T]: FieldPool<T>;
|
||||
}
|
||||
|
||||
export class FieldPool<T> {
|
||||
public pool: PoolOf<T>
|
||||
public randomValue() {
|
||||
return getRandomOf(this.pool)
|
||||
}
|
||||
|
||||
constructor(
|
||||
pool: PoolOf<T>
|
||||
) {
|
||||
this.pool = pool
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
function getRandomOf<T>(pool: PoolOf<T>) {
|
||||
const mock: NullablePartial<T> = {}
|
||||
const options = pool
|
||||
if (options == undefined) throw new Error("El campo no existe en la pool")
|
||||
for (const optKey in options) {
|
||||
const field: any[] = options[optKey as keyof typeof options]
|
||||
if (field == undefined) continue;
|
||||
const examplesLen = field.length
|
||||
if (examplesLen == undefined) continue;
|
||||
const seleccionado = field[Math.floor(Math.random() * examplesLen)]
|
||||
mock[optKey as keyof typeof mock] = seleccionado as any // no se como resolver el tipo aqui, se confia que en el ejemplo esté bien
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
const ADDRESS = new FieldPool<Shopify.Address>(
|
||||
{
|
||||
id: [9012345, 4455667, 1122334],
|
||||
customer_id: [7182931, 8293012, 1029384],
|
||||
first_name: ["Maria", "John", "Yuki"],
|
||||
last_name: ["García", "Doe", "Tanaka"],
|
||||
company: ["Tech Solutions S.L.", "Global Corp", null],
|
||||
address1: ["Calle Mayor 1, 4B", "123 Business Ave", "Shibuya-ku 1-2-3"],
|
||||
address2: ["Portal A", "Suite 500", null],
|
||||
city: ["Madrid", "New York", "Tokyo"],
|
||||
province: ["Madrid", "New York", "Tokyo"],
|
||||
country: ["Spain", "United States", "Japan"],
|
||||
zip: ["28001", "10001", "150-0002"],
|
||||
phone: ["+34600112233", "+15559876543", "+81312345678"],
|
||||
name: ["Maria García", "John Doe", "Yuki Tanaka"],
|
||||
province_code: ["M", "NY", "13"],
|
||||
country_code: ["ES", "US", "JP"],
|
||||
country_name: ["Spain", "United States", "Japan"],
|
||||
default: [true, false, false]
|
||||
}
|
||||
)
|
||||
|
||||
const CUSTOMER = new FieldPool<Shopify.CustomerData>(
|
||||
{
|
||||
id: [7182931, 8293012, 1029384],
|
||||
created_at: ["2023-01-15T10:00:00Z", "2023-11-20T15:30:45Z", "2024-02-05T08:12:00Z"],
|
||||
updated_at: ["2023-12-01T12:00:00Z", "2024-03-10T09:45:00Z", "2024-05-20T18:20:10Z"],
|
||||
first_name: ["Maria", "Carlos", "null"], // Ejemplo de nulidad
|
||||
last_name: ["García", "Smith", "Villanueva"],
|
||||
state: ["enabled", "disabled", "invited"],
|
||||
note: ["Cliente VIP", "Prefiere entrega por la tarde", null],
|
||||
verified_email: [true, false, true],
|
||||
multipass_identifier: ["ID-99283", "EXT-9102", null],
|
||||
tax_exempt: [false, true, false],
|
||||
email_marketing_consent: [
|
||||
{
|
||||
state: "subscribed",
|
||||
opt_in_level: "confirmed_opt_in",
|
||||
consent_updated_at: "2024-01-01T10:00:00Z"
|
||||
},
|
||||
{
|
||||
state: "unsubscribed",
|
||||
opt_in_level: "single_opt_in",
|
||||
consent_updated_at: "2023-05-12T14:30:00Z"
|
||||
},
|
||||
null
|
||||
],
|
||||
sms_marketing_consent: [true, false, null],
|
||||
tags: ["premium, newsletter", "wholesale", ""],
|
||||
email: ["maria.g@example.com", "carlos_dev@test.io", "info@empresa.es"],
|
||||
phone: ["+34600112233", "+15559876543", null],
|
||||
currency: ["EUR", "USD", "MXN"],
|
||||
tax_exemptions: [
|
||||
[],
|
||||
["CA_STATUS_CARD", "EXEMPT_CERTIFICATE"],
|
||||
["TAX_ID_99"]
|
||||
],
|
||||
admin_graphql_api_id: [
|
||||
"gid://shopify/Customer/7182931",
|
||||
"gid://shopify/Customer/8293012",
|
||||
"gid://shopify/Customer/1029384"
|
||||
],
|
||||
default_address: [ADDRESS.randomValue()]
|
||||
})
|
||||
|
||||
|
||||
export const VALID_POOL: RepoOfPools<Shopify.Order> = {
|
||||
customer: CUSTOMER,
|
||||
shipping_address: ADDRESS,
|
||||
}
|
||||
|
||||
export const INVALID_POOL = {
|
||||
|
||||
}
|
||||
12
src/data/webhooks/order.d.ts
vendored
12
src/data/webhooks/order.d.ts
vendored
@@ -19,7 +19,7 @@ export declare namespace Shopify {
|
||||
discount_allocations: unknown[]
|
||||
}
|
||||
|
||||
type LineItem = {
|
||||
type LineItem = NullablePartial<{
|
||||
id: number,
|
||||
admin_graphql_api_id: string,
|
||||
attributed_staffs: unknown[],
|
||||
@@ -49,9 +49,9 @@ export declare namespace Shopify {
|
||||
tax_lines: TaxLine[],
|
||||
duties: unknown[],
|
||||
discount_allocations: unknown[]
|
||||
}
|
||||
}>
|
||||
|
||||
export type DiscountAplication = {
|
||||
type DiscountAplication = {
|
||||
target_type: string,
|
||||
type: string,
|
||||
value: string,
|
||||
@@ -147,7 +147,7 @@ export declare namespace Shopify {
|
||||
user_agent: string,
|
||||
}>
|
||||
|
||||
export type PriceSet = {
|
||||
export type PriceSet = NullablePartial<{
|
||||
shop_money: {
|
||||
amount: string,
|
||||
currency_code: string
|
||||
@@ -157,7 +157,7 @@ export declare namespace Shopify {
|
||||
currency_code: string
|
||||
}
|
||||
|
||||
}
|
||||
}>
|
||||
|
||||
// Webhook orders/create
|
||||
export type OrderCreate = Order
|
||||
@@ -252,7 +252,7 @@ export declare namespace Shopify {
|
||||
customer: CustomerData,
|
||||
discount_applications: NullablePartial<DiscountAplication>[],
|
||||
fulfillments: unknown[],
|
||||
line_items: NullablePartial<LineItem>[],
|
||||
line_items: LineItem[],
|
||||
payment_terms: unknown,
|
||||
refunds: unknown,
|
||||
shipping_address: Address,
|
||||
|
||||
Reference in New Issue
Block a user