Organizacion de las pool de eventos y implementacion

en el scheduler, de momento campos limitados
This commit is contained in:
2025-12-30 12:34:03 +01:00
parent da49685338
commit f4bd501267
10 changed files with 316 additions and 133 deletions

View File

@@ -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,

View 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]
}
)

View 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()]
})

View 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: [[], [], []]
})

View 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 = {
}

View 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"
}
]
})

View 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)
})
})

View 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
}
}

View File

@@ -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 = {
}

View File

@@ -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,