@fayz-ai/shop 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { getSupabaseClientOptional } from '@fayz-ai/core'
2
2
  import type { ShopProvider } from './provider'
3
3
  import { getShopTenantId } from './tenant'
4
+ import { T } from './tables'
4
5
  import type {
5
6
  Product, ProductImage, Category,
6
7
  Order, ShopCustomer, Discount,
@@ -150,7 +151,7 @@ export class SupabaseShopProvider implements ShopProvider {
150
151
  async listProducts(options?: ListProductsOptions): Promise<Product[]> {
151
152
  const db = getDb()
152
153
  const tenantId = getTenantId()
153
- let q = db.from('shop_products').select('*, images:shop_product_images(*)')
154
+ let q = db.from(T.products).select(`*, images:${T.productImages}(*)`)
154
155
  if (tenantId) q = q.eq('tenant_id', tenantId)
155
156
  if (options?.status) q = q.eq('status', options.status)
156
157
  if (options?.categoryId) q = q.eq('category_id', options.categoryId)
@@ -167,8 +168,8 @@ export class SupabaseShopProvider implements ShopProvider {
167
168
  async getProduct(id: string): Promise<Product | null> {
168
169
  const db = getDb()
169
170
  const { data, error } = await db
170
- .from('shop_products')
171
- .select('*, images:shop_product_images(*)')
171
+ .from(T.products)
172
+ .select(`*, images:${T.productImages}(*)`)
172
173
  .eq('id', id)
173
174
  .maybeSingle()
174
175
  if (error) throw error
@@ -179,7 +180,7 @@ export class SupabaseShopProvider implements ShopProvider {
179
180
  const db = getDb()
180
181
  const tenantId = getTenantId()
181
182
  const { data, error } = await db
182
- .from('shop_products')
183
+ .from(T.products)
183
184
  .insert({
184
185
  tenant_id: tenantId,
185
186
  name: input.name,
@@ -198,7 +199,7 @@ export class SupabaseShopProvider implements ShopProvider {
198
199
  weight: input.weight ?? null,
199
200
  weight_unit: input.weightUnit ?? 'kg',
200
201
  })
201
- .select('*, images:shop_product_images(*)')
202
+ .select(`*, images:${T.productImages}(*)`)
202
203
  .single()
203
204
  if (error) throw error
204
205
  return rowToProduct(data)
@@ -220,15 +221,15 @@ export class SupabaseShopProvider implements ShopProvider {
220
221
  if (input.isPhysical !== undefined) updates.is_physical = input.isPhysical
221
222
  if (input.weight !== undefined) updates.weight = input.weight
222
223
  const { data, error } = await db
223
- .from('shop_products').update(updates).eq('id', id)
224
- .select('*, images:shop_product_images(*)')
224
+ .from(T.products).update(updates).eq('id', id)
225
+ .select(`*, images:${T.productImages}(*)`)
225
226
  .single()
226
227
  if (error) throw error
227
228
  return rowToProduct(data)
228
229
  }
229
230
 
230
231
  async deleteProduct(id: string): Promise<void> {
231
- const { error } = await getDb().from('shop_products').delete().eq('id', id)
232
+ const { error } = await getDb().from(T.products).delete().eq('id', id)
232
233
  if (error) throw error
233
234
  }
234
235
 
@@ -240,7 +241,7 @@ export class SupabaseShopProvider implements ShopProvider {
240
241
  if (uploadErr) throw uploadErr
241
242
  const { data: { publicUrl } } = db.storage.from('shop-images').getPublicUrl(path)
242
243
  const { data, error } = await db
243
- .from('shop_product_images')
244
+ .from(T.productImages)
244
245
  .insert({ product_id: productId, tenant_id: tenantId, url: publicUrl, sort_order: 0, is_primary: false })
245
246
  .select().single()
246
247
  if (error) throw error
@@ -248,7 +249,7 @@ export class SupabaseShopProvider implements ShopProvider {
248
249
  }
249
250
 
250
251
  async deleteProductImage(imageId: string): Promise<void> {
251
- const { error } = await getDb().from('shop_product_images').delete().eq('id', imageId)
252
+ const { error } = await getDb().from(T.productImages).delete().eq('id', imageId)
252
253
  if (error) throw error
253
254
  }
254
255
 
@@ -259,7 +260,7 @@ export class SupabaseShopProvider implements ShopProvider {
259
260
  async listCategories(): Promise<Category[]> {
260
261
  const db = getDb()
261
262
  const tenantId = getTenantId()
262
- let q = db.from('shop_categories').select('*').order('sort_order', { ascending: true })
263
+ let q = db.from(T.categories).select('*').order('sort_order', { ascending: true })
263
264
  if (tenantId) q = q.eq('tenant_id', tenantId)
264
265
  const { data, error } = await q
265
266
  if (error) throw error
@@ -269,7 +270,7 @@ export class SupabaseShopProvider implements ShopProvider {
269
270
  async createCategory(input: CreateCategoryInput): Promise<Category> {
270
271
  const db = getDb()
271
272
  const { data, error } = await db
272
- .from('shop_categories')
273
+ .from(T.categories)
273
274
  .insert({ tenant_id: getTenantId(), name: input.name, slug: input.slug ?? slugify(input.name), description: input.description ?? null, parent_id: input.parentId ?? null, sort_order: input.sortOrder ?? 0 })
274
275
  .select().single()
275
276
  if (error) throw error
@@ -284,13 +285,13 @@ export class SupabaseShopProvider implements ShopProvider {
284
285
  if (input.description !== undefined) updates.description = input.description
285
286
  if (input.parentId !== undefined) updates.parent_id = input.parentId
286
287
  if (input.sortOrder !== undefined) updates.sort_order = input.sortOrder
287
- const { data, error } = await db.from('shop_categories').update(updates).eq('id', id).select().single()
288
+ const { data, error } = await db.from(T.categories).update(updates).eq('id', id).select().single()
288
289
  if (error) throw error
289
290
  return rowToCategory(data)
290
291
  }
291
292
 
292
293
  async deleteCategory(id: string): Promise<void> {
293
- const { error } = await getDb().from('shop_categories').delete().eq('id', id)
294
+ const { error } = await getDb().from(T.categories).delete().eq('id', id)
294
295
  if (error) throw error
295
296
  }
296
297
 
@@ -301,7 +302,7 @@ export class SupabaseShopProvider implements ShopProvider {
301
302
  async listOrders(options?: ListOrdersOptions): Promise<Order[]> {
302
303
  const db = getDb()
303
304
  const tenantId = getTenantId()
304
- let q = db.from('shop_orders').select('*, items:shop_order_items(*)')
305
+ let q = db.from(T.orders).select(`*, items:${T.orderItems}(*)`)
305
306
  if (tenantId) q = q.eq('tenant_id', tenantId)
306
307
  if (options?.status) q = q.eq('status', options.status)
307
308
  if (options?.financialStatus) q = q.eq('financial_status', options.financialStatus)
@@ -318,7 +319,7 @@ export class SupabaseShopProvider implements ShopProvider {
318
319
 
319
320
  async getOrder(id: string): Promise<Order | null> {
320
321
  const { data, error } = await getDb()
321
- .from('shop_orders').select('*, items:shop_order_items(*)')
322
+ .from(T.orders).select(`*, items:${T.orderItems}(*)`)
322
323
  .eq('id', id).maybeSingle()
323
324
  if (error) throw error
324
325
  return data ? rowToOrder(data) : null
@@ -332,12 +333,12 @@ export class SupabaseShopProvider implements ShopProvider {
332
333
  const shippingTotal = input.shippingTotal ?? 0
333
334
  const total = Math.round((subtotal - discountTotal + shippingTotal) * 100) / 100
334
335
  const { data: order, error: orderErr } = await db
335
- .from('shop_orders')
336
+ .from(T.orders)
336
337
  .insert({ tenant_id: tenantId, customer_id: input.customerId ?? null, customer_name: input.customerName ?? null, customer_email: input.customerEmail ?? null, currency: input.currency ?? 'BRL', notes: input.notes ?? null, subtotal, discount_total: discountTotal, shipping_total: shippingTotal, discount_code: input.discountCode ?? null, total, status: 'open', financial_status: 'pending', fulfillment_status: 'unfulfilled' })
337
338
  .select().single()
338
339
  if (orderErr) throw orderErr
339
340
  const items = input.items.map(i => ({ order_id: order.id, product_id: asUuid(i.productId), name: i.name, sku: i.sku ?? null, quantity: i.quantity, unit_price: i.unitPrice, total: i.quantity * i.unitPrice, image_url: i.imageUrl ?? null }))
340
- await db.from('shop_order_items').insert(items)
341
+ await db.from(T.orderItems).insert(items)
341
342
  return this.getOrder(order.id) as Promise<Order>
342
343
  }
343
344
 
@@ -347,7 +348,7 @@ export class SupabaseShopProvider implements ShopProvider {
347
348
  if (input.financialStatus !== undefined) updates.financial_status = input.financialStatus
348
349
  if (input.fulfillmentStatus !== undefined) updates.fulfillment_status = input.fulfillmentStatus
349
350
  if (input.notes !== undefined) updates.notes = input.notes
350
- const { error } = await getDb().from('shop_orders').update(updates).eq('id', id)
351
+ const { error } = await getDb().from(T.orders).update(updates).eq('id', id)
351
352
  if (error) throw error
352
353
  return this.getOrder(id) as Promise<Order>
353
354
  }
@@ -383,7 +384,7 @@ export class SupabaseShopProvider implements ShopProvider {
383
384
  async listCustomers(options?: ListCustomersOptions): Promise<ShopCustomer[]> {
384
385
  const db = getDb()
385
386
  const tenantId = getTenantId()
386
- let q = db.from('shop_customers').select('*').order('first_name', { ascending: true })
387
+ let q = db.from(T.customers).select('*').order('first_name', { ascending: true })
387
388
  if (tenantId) q = q.eq('tenant_id', tenantId)
388
389
  if (options?.search) q = q.or(`first_name.ilike.%${options.search}%,last_name.ilike.%${options.search}%,email.ilike.%${options.search}%`)
389
390
  if (options?.limit) q = q.limit(options.limit)
@@ -393,14 +394,14 @@ export class SupabaseShopProvider implements ShopProvider {
393
394
  }
394
395
 
395
396
  async getCustomer(id: string): Promise<ShopCustomer | null> {
396
- const { data, error } = await getDb().from('shop_customers').select('*').eq('id', id).maybeSingle()
397
+ const { data, error } = await getDb().from(T.customers).select('*').eq('id', id).maybeSingle()
397
398
  if (error) throw error
398
399
  return data ? rowToCustomer(data) : null
399
400
  }
400
401
 
401
402
  async createCustomer(input: CreateCustomerInput): Promise<ShopCustomer> {
402
403
  const { data, error } = await getDb()
403
- .from('shop_customers')
404
+ .from(T.customers)
404
405
  .insert({ tenant_id: getTenantId(), first_name: input.firstName, last_name: input.lastName ?? '', email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null })
405
406
  .select().single()
406
407
  if (error) throw error
@@ -414,13 +415,13 @@ export class SupabaseShopProvider implements ShopProvider {
414
415
  if (input.email !== undefined) updates.email = input.email
415
416
  if (input.phone !== undefined) updates.phone = input.phone
416
417
  if (input.notes !== undefined) updates.notes = input.notes
417
- const { data, error } = await getDb().from('shop_customers').update(updates).eq('id', id).select().single()
418
+ const { data, error } = await getDb().from(T.customers).update(updates).eq('id', id).select().single()
418
419
  if (error) throw error
419
420
  return rowToCustomer(data)
420
421
  }
421
422
 
422
423
  async deleteCustomer(id: string): Promise<void> {
423
- const { error } = await getDb().from('shop_customers').delete().eq('id', id)
424
+ const { error } = await getDb().from(T.customers).delete().eq('id', id)
424
425
  if (error) throw error
425
426
  }
426
427
 
@@ -448,7 +449,7 @@ export class SupabaseShopProvider implements ShopProvider {
448
449
  async listDiscounts(options?: ListDiscountsOptions): Promise<Discount[]> {
449
450
  const db = getDb()
450
451
  const tenantId = getTenantId()
451
- let q = db.from('shop_discounts').select('*').order('created_at', { ascending: false })
452
+ let q = db.from(T.discounts).select('*').order('created_at', { ascending: false })
452
453
  if (tenantId) q = q.eq('tenant_id', tenantId)
453
454
  if (options?.status) q = q.eq('status', options.status)
454
455
  if (options?.type) q = q.eq('type', options.type)
@@ -460,14 +461,14 @@ export class SupabaseShopProvider implements ShopProvider {
460
461
  }
461
462
 
462
463
  async getDiscount(id: string): Promise<Discount | null> {
463
- const { data, error } = await getDb().from('shop_discounts').select('*').eq('id', id).maybeSingle()
464
+ const { data, error } = await getDb().from(T.discounts).select('*').eq('id', id).maybeSingle()
464
465
  if (error) throw error
465
466
  return data ? rowToDiscount(data) : null
466
467
  }
467
468
 
468
469
  async createDiscount(input: CreateDiscountInput): Promise<Discount> {
469
470
  const { data, error } = await getDb()
470
- .from('shop_discounts')
471
+ .from(T.discounts)
471
472
  .insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? 'code', value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, starts_at: input.startsAt ?? new Date().toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? 'active', times_used: 0 })
472
473
  .select().single()
473
474
  if (error) throw error
@@ -483,13 +484,13 @@ export class SupabaseShopProvider implements ShopProvider {
483
484
  if (input.usageLimit !== undefined) updates.usage_limit = input.usageLimit
484
485
  if (input.status !== undefined) updates.status = input.status
485
486
  if (input.endsAt !== undefined) updates.ends_at = input.endsAt
486
- const { data, error } = await getDb().from('shop_discounts').update(updates).eq('id', id).select().single()
487
+ const { data, error } = await getDb().from(T.discounts).update(updates).eq('id', id).select().single()
487
488
  if (error) throw error
488
489
  return rowToDiscount(data)
489
490
  }
490
491
 
491
492
  async deleteDiscount(id: string): Promise<void> {
492
- const { error } = await getDb().from('shop_discounts').delete().eq('id', id)
493
+ const { error } = await getDb().from(T.discounts).delete().eq('id', id)
493
494
  if (error) throw error
494
495
  }
495
496
  }
package/src/tables.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Physical table names for @fayz-ai/shop. Renamed to the plg_shop_* industry-pool
2
+ // prefix (was shop_*) when the core DB moved into the public schema. RPC function
3
+ // names (shop_place_order, shop_get_order, shop_resolve_customer) are unchanged.
4
+ export const T = {
5
+ products: 'plg_shop_products',
6
+ productImages: 'plg_shop_product_images',
7
+ categories: 'plg_shop_categories',
8
+ orders: 'plg_shop_orders',
9
+ orderItems: 'plg_shop_order_items',
10
+ customers: 'plg_shop_customers',
11
+ discounts: 'plg_shop_discounts',
12
+ } as const
package/src/types.ts CHANGED
@@ -86,6 +86,51 @@ export interface ListProductsOptions {
86
86
  order?: 'asc' | 'desc'
87
87
  }
88
88
 
89
+ // ---------------------------------------------------------------------------
90
+ // Product enquiry
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export type ProductEnquiryStatus = 'new' | 'contacted' | 'closed'
94
+
95
+ export interface ProductEnquiry {
96
+ id: string
97
+ tenantId: string
98
+ productId: string | null
99
+ productName: string
100
+ productSlug: string | null
101
+ customerName: string
102
+ customerEmail: string
103
+ customerPhone: string | null
104
+ subject: string | null
105
+ message: string
106
+ sourceUrl: string | null
107
+ status: ProductEnquiryStatus
108
+ metadata: Record<string, unknown>
109
+ createdAt: string
110
+ updatedAt: string
111
+ }
112
+
113
+ export interface CreateProductEnquiryInput {
114
+ productId?: string
115
+ productName: string
116
+ productSlug?: string
117
+ customerName: string
118
+ customerEmail: string
119
+ customerPhone?: string
120
+ subject?: string
121
+ message: string
122
+ sourceUrl?: string
123
+ metadata?: Record<string, unknown>
124
+ }
125
+
126
+ export interface ListProductEnquiriesOptions {
127
+ productId?: string
128
+ customerEmail?: string
129
+ status?: ProductEnquiryStatus
130
+ limit?: number
131
+ offset?: number
132
+ }
133
+
89
134
  // ---------------------------------------------------------------------------
90
135
  // Category
91
136
  // ---------------------------------------------------------------------------