create-fullstack-scaffold 0.4.2 → 0.4.4

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.
Files changed (156) hide show
  1. package/dist/cli/index.js +513 -122
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/template/dist/cli/index.js +237 -94
  5. package/template/dist/cli/index.js.map +1 -1
  6. package/template/dist/client/admin.html +15 -10
  7. package/template/dist/client/assets/App-BpFUIC-5.js +114 -0
  8. package/template/dist/client/assets/{CartPage-DESSHkaG.js → CartPage-DgDZP-R5.js} +2 -2
  9. package/template/dist/client/assets/{CategoriesPage-DqLZ_Mom.js → CategoriesPage-fuYNiSmE.js} +2 -2
  10. package/template/dist/client/assets/ContentDetailPage-BvvFU1Mr.js +1 -0
  11. package/template/dist/client/assets/ContentListPage-ShPxo6WG.js +1 -0
  12. package/template/dist/client/assets/DashboardPage-C1rZzZo4.js +11 -0
  13. package/template/dist/client/assets/{DeveloperDashboardPage-Cum7zFwO.js → DeveloperDashboardPage-_WtmbVoA.js} +2 -2
  14. package/template/dist/client/assets/{EmptyState-BK7ttK8D.js → EmptyState-DEyOQjbY.js} +1 -1
  15. package/template/dist/client/assets/LoadingSpinner-BcHjaM1m.js +6 -0
  16. package/template/dist/client/assets/LoginPage-CjeQqDdO.js +1 -0
  17. package/template/dist/client/assets/LogoutOutlined-ColFAHdd.js +1 -0
  18. package/template/dist/client/assets/NotificationPage-DRkEv02s.js +1 -0
  19. package/template/dist/client/assets/OrdersPage-7I3vPWLP.js +11 -0
  20. package/template/dist/client/assets/{PluginDetailPage-Br24YS4P.js → PluginDetailPage-CWyxdVCg.js} +3 -13
  21. package/template/dist/client/assets/PluginsPage-DRgWLeJT.js +6 -0
  22. package/template/dist/client/assets/ProfilePage-2FFkaii8.js +16 -0
  23. package/template/dist/client/assets/PublishPage-DW7yvROx.js +1 -0
  24. package/template/dist/client/assets/{RegisterPage-rPFG4Sry.js → RegisterPage-Dvaf9b0e.js} +2 -2
  25. package/template/dist/client/assets/SaveOutlined-CLQ2tsSY.js +2 -0
  26. package/template/dist/client/assets/{SearchPage-DQl1U5Uy.js → SearchPage-DvnEyswS.js} +2 -2
  27. package/template/dist/client/assets/{SettingsPage-DaYE80wj.js → SettingsPage-IrU-L9Zr.js} +2 -2
  28. package/template/dist/client/assets/{StatusBadge-BJ3W2Zc7.js → StatusBadge-CG7wVqpu.js} +1 -1
  29. package/template/dist/client/assets/{TodoPage-zC5wWNBa.js → TodoPage-B7D-bl65.js} +3 -3
  30. package/template/dist/client/assets/TopicsPage-CYCNr-mn.js +6 -0
  31. package/template/dist/client/assets/{WebSocketPage-B4NLdz1w.js → WebSocketPage-CGEg6QJE.js} +2 -2
  32. package/template/dist/client/assets/{activity-B94BUAsD.js → activity-CgTz9Mrd.js} +1 -1
  33. package/template/dist/client/assets/admin-D2iHWNOg.js +1 -0
  34. package/template/dist/client/assets/api-request-DIJocPxr.js +356 -0
  35. package/template/dist/client/assets/{arrow-left-H5JH0iU1.js → arrow-left-DtLQ3tTk.js} +1 -1
  36. package/template/dist/client/assets/{chevron-down-DwRh_wjD.js → chevron-down-CpIAV_yr.js} +1 -1
  37. package/template/dist/client/assets/{chevron-right-CpIWaqAL.js → chevron-right-CnU7oPQs.js} +1 -1
  38. package/template/dist/client/assets/{circle-check-big-CSElLscG.js → circle-check-big-CrZdwS75.js} +1 -1
  39. package/template/dist/client/assets/{circle-x-CYxr4RAw.js → circle-x-CvN9r3fW.js} +1 -1
  40. package/template/dist/client/assets/{clock-BseUJ7ex.js → clock-CTeSZBiH.js} +1 -1
  41. package/template/dist/client/assets/{download-BsGC_Xhq.js → download-OSVfVuNV.js} +1 -1
  42. package/template/dist/client/assets/{external-link-UTDzdjq8.js → external-link-fBsuHMby.js} +1 -1
  43. package/template/dist/client/assets/{eye-nR10ui7i.js → eye-CwlZZEsj.js} +1 -1
  44. package/template/dist/client/assets/index-09IqiOaE.css +1 -0
  45. package/template/dist/client/assets/index-B3CRby6r.js +51 -0
  46. package/template/dist/client/assets/index-CC_zy73y.js +17 -0
  47. package/template/dist/client/assets/main-Cn4IDo2I.js +92 -0
  48. package/template/dist/client/assets/merchant-DDDN9bMu.js +40 -0
  49. package/template/dist/client/assets/{message-square-CwWGL-Bh.js → message-square-BRaZfgbJ.js} +1 -1
  50. package/template/dist/client/assets/pluginStore-DDsWJPe6.js +1 -0
  51. package/template/dist/client/assets/{plus-pZld_nUL.js → plus-DDZfPiDJ.js} +1 -1
  52. package/template/dist/client/assets/sse-client-DGQ072hl.js +64 -0
  53. package/template/dist/client/assets/star-C7tO6w4H.js +11 -0
  54. package/template/dist/client/assets/{tag-DsiUPF90.js → tag-DBfpQDuJ.js} +1 -1
  55. package/template/dist/client/assets/tenant-CizbLaLJ.js +1 -0
  56. package/template/dist/client/assets/{trash-2-DRblNbnA.js → trash-2-BP7fkd5b.js} +1 -1
  57. package/template/dist/client/assets/{triangle-alert-D1CLh-Vd.js → triangle-alert-CrOQEdJ2.js} +1 -1
  58. package/template/dist/client/assets/{wifi-BwXjnCqI.js → wifi-CwYSE0J4.js} +1 -1
  59. package/template/dist/client/assets/{x-BH2rJdFq.js → x-t615pnNR.js} +1 -1
  60. package/template/dist/client/content/index.html +5 -4
  61. package/template/dist/client/index.html +4 -3
  62. package/template/dist/client/merchant.html +18 -0
  63. package/template/dist/client/notifications/index.html +6 -5
  64. package/template/dist/client/tenant.html +18 -0
  65. package/template/dist/client/todos/index.html +6 -5
  66. package/template/dist/client/websocket/index.html +6 -5
  67. package/template/dist/cloudflare/chunk-HX7DVRDX.js +1 -0
  68. package/template/dist/cloudflare/{chunk-QBHAPWIA.js → chunk-IXJL2LE4.js} +1 -1
  69. package/template/dist/cloudflare/chunk-Q34AN3RF.js +14 -0
  70. package/template/dist/cloudflare/{chunk-BHPTQ4K2.js → chunk-ZAMVL4EG.js} +2 -2
  71. package/template/dist/cloudflare/cloudflare.js +35 -29
  72. package/template/dist/cloudflare/db-4AQOSFAX.js +1 -0
  73. package/template/dist/cloudflare/{migrator-2ZYYKNI5.js → migrator-TEPBVUFO.js} +1 -1
  74. package/template/dist/cloudflare/permission-service-impl-RE7NPW3Q.js +1 -0
  75. package/template/dist/cloudflare/schema-DVEMT3X2.js +1 -0
  76. package/template/dist/cloudflare/test-setup-Z2RYE5B4.js +265 -0
  77. package/template/dist/server/chunk-27SIJSPF.js +1 -0
  78. package/template/dist/server/chunk-37QNGBQU.js +1 -0
  79. package/template/dist/server/chunk-FIY4CGJJ.js +1 -0
  80. package/template/dist/server/chunk-HZUDBIT4.js +1 -0
  81. package/template/dist/server/chunk-I6OBDFDU.js +1 -0
  82. package/template/dist/server/chunk-JHNYLNG7.js +1 -0
  83. package/template/dist/server/chunk-MDOVVKKD.js +1 -0
  84. package/template/dist/server/chunk-NSOCT7E5.js +1 -0
  85. package/template/dist/server/chunk-OU5IGVVO.js +1 -0
  86. package/template/dist/server/chunk-OYCBNRMK.js +1 -0
  87. package/template/dist/server/chunk-P2RJNNGD.js +1 -0
  88. package/template/dist/server/chunk-UMAQLNPE.js +1 -0
  89. package/template/dist/server/chunk-XZZTSRSN.js +1 -0
  90. package/template/dist/server/content-service-5QVHDR5X.js +1 -0
  91. package/template/dist/server/db-FLI5L5DD.js +1 -0
  92. package/template/dist/server/dispute-service-E2FCYUEM.js +1 -0
  93. package/template/dist/server/init-72JXJV3I.js +46 -0
  94. package/template/dist/server/init-FWYXSKPX.js +46 -0
  95. package/template/dist/server/node.js +34 -28
  96. package/template/dist/server/order-service-TKGG5S5K.js +1 -0
  97. package/template/dist/server/permission-service-impl-M3AKEYNY.js +1 -0
  98. package/template/dist/server/schema-V4UG7HMU.js +1 -0
  99. package/template/dist/server/tenant-service-JG6UUWXQ.js +1 -0
  100. package/template/dist/server/tenant-service-XJWZSEDX.js +1 -0
  101. package/template/dist/{cloudflare/test-setup-NMZJZLRV.js → server/test-setup-VFOPBGYM.js} +83 -2
  102. package/template/dist/server/ticket-service-3RSW7V2Z.js +1 -0
  103. package/template/dist/server/todo-service-S6BGIO7D.js +1 -0
  104. package/template/package.json +1 -1
  105. package/template/src/client/App.tsx +3 -1
  106. package/template/src/client/pages/ContentListPage.tsx +36 -4
  107. package/template/src/client/pages/PluginsPage.tsx +112 -20
  108. package/template/src/client/pages/TodoPage.tsx +9 -1
  109. package/template/src/client/preset-ui-config.ts +79 -2
  110. package/template/src/merchant/pages/DisputesPage.tsx +11 -11
  111. package/template/src/merchant/pages/OrdersPage.tsx +4 -3
  112. package/template/src/merchant/pages/ProductsPage.tsx +2 -2
  113. package/template/src/merchant/stores/merchantStore.ts +11 -6
  114. package/template/src/server/db/init.ts +5 -253
  115. package/template/src/server/module-content/routes/topics-routes.ts +250 -0
  116. package/template/src/server/module-content/services/content-service.ts +1 -1
  117. package/template/src/server/module-plugin/services/plugin-seed-service.ts +623 -0
  118. package/template/src/server/module-tenant/routes/tenant-routes.ts +2 -2
  119. package/template/src/server/module-tenant/services/tenant-service.ts +63 -17
  120. package/template/src/server/module-todos/services/todo-service.ts +1 -1
  121. package/template/src/shared/modules/merchant/index.ts +9 -0
  122. package/template/src/shared/modules/merchant/schemas.ts +37 -0
  123. package/template/src/shared/modules/tenant/index.ts +2 -0
  124. package/template/src/shared/schemas/index.ts +27 -0
  125. package/template/src/tenant/layouts/Header.tsx +1 -1
  126. package/template/src/tenant/layouts/Sidebar.tsx +1 -1
  127. package/template/src/tenant/pages/SettingsPage.tsx +4 -8
  128. package/template/src/tenant/stores/tenantStore.ts +41 -302
  129. package/template/tsconfig.json +1 -0
  130. package/template/tsup.config.ts +11 -3
  131. package/template/dist/client/assets/ContentDetailPage-DdltMANL.js +0 -1
  132. package/template/dist/client/assets/ContentListPage-B63sUB6N.js +0 -1
  133. package/template/dist/client/assets/DashboardPage-tUMilc7Z.js +0 -11
  134. package/template/dist/client/assets/LoadingSpinner-DVYOLSRM.js +0 -6
  135. package/template/dist/client/assets/LoginPage-g9mSLIMC.js +0 -6
  136. package/template/dist/client/assets/NotificationPage-COTzp-HD.js +0 -1
  137. package/template/dist/client/assets/OrdersPage-BsPh4oEI.js +0 -11
  138. package/template/dist/client/assets/PluginsPage-BNtAe_yo.js +0 -6
  139. package/template/dist/client/assets/ProfilePage-dL_iSuZN.js +0 -16
  140. package/template/dist/client/assets/PublishPage-D96FnbLA.js +0 -1
  141. package/template/dist/client/assets/TopicsPage-DyLjZa4D.js +0 -6
  142. package/template/dist/client/assets/admin-Bh1vfz_L.js +0 -486
  143. package/template/dist/client/assets/index-1bA-ixFv.js +0 -114
  144. package/template/dist/client/assets/index-CWh3vXC_.css +0 -1
  145. package/template/dist/client/assets/main-DHnArIp_.js +0 -82
  146. package/template/dist/client/assets/pluginStore-XQfsm3iR.js +0 -1
  147. package/template/dist/client/content/content-1/index.html +0 -18
  148. package/template/dist/client/content/content-13/index.html +0 -18
  149. package/template/dist/client/content/content-14/index.html +0 -18
  150. package/template/dist/client/content/content-15/index.html +0 -18
  151. package/template/dist/client/content/content-19/index.html +0 -18
  152. package/template/dist/client/content/content-2/index.html +0 -18
  153. package/template/dist/client/content/content-20/index.html +0 -18
  154. package/template/dist/cloudflare/chunk-YY6FRMAQ.js +0 -14
  155. package/template/dist/cloudflare/db-7J42RQ3T.js +0 -1
  156. package/template/dist/cloudflare/permission-service-impl-TGXVSSSA.js +0 -1
@@ -0,0 +1 @@
1
+ export{h as cancelOrder,i as completeOrder,d as createOrder,f as deleteOrder,c as getOrderById,b as getOrders,g as processOrder,a as seedOrdersIfEmpty,e as updateOrder}from'./chunk-HZUDBIT4.js';import'./chunk-I6OBDFDU.js';import'./chunk-FIY4CGJJ.js';import'./chunk-JCPOP4XL.js';import'./chunk-UMAQLNPE.js';import'./chunk-OYCBNRMK.js';import'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';
@@ -0,0 +1 @@
1
+ export{s as PermissionService,t as permissionService}from'./chunk-37QNGBQU.js';import'./chunk-JCPOP4XL.js';import'./chunk-UMAQLNPE.js';import'./chunk-OYCBNRMK.js';import'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';
@@ -0,0 +1 @@
1
+ import {J}from'./chunk-27SIJSPF.js';export{x as contentCategories,w as contentStatuses,y as contents,z as developers,u as disputeStatuses,t as disputeTypes,v as disputes,d as notifications,l as orderStatuses,m as orders,k as permissionAuditLogs,j as permissionRouteMappings,e as permissions,F as pluginCategories,G as pluginCategoryMappings,E as pluginReviews,B as pluginStatus,D as pluginVersions,C as plugins,g as rolePermissions,f as roles,i as routes,H as tenants,p as ticketCategories,o as ticketPriorities,s as ticketReplies,q as ticketReplyAuthorRoles,n as ticketStatuses,r as tickets,c as todoAttachments,a as todoStatus,b as todos,h as userRoles}from'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';J();
@@ -0,0 +1 @@
1
+ import {b}from'./chunk-MDOVVKKD.js';import {a}from'./chunk-UMAQLNPE.js';import {h,f,i}from'./chunk-OYCBNRMK.js';import {J as J$1,H}from'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';import {eq,like,desc}from'drizzle-orm';a();J$1();h();var c=f("tenant-service");async function O(){let s=await i();if((await s.select().from(H)).length===0){c.info({},"Seeding tenants...");let e=[{name:"Demo Corp",slug:"demo",status:"active",plan:"pro",maxUsers:50,settings:JSON.stringify({theme:"dark",language:"en"})},{name:"Test Inc",slug:"test",status:"trial",plan:"free",maxUsers:5,settings:null}];await s.insert(H).values(e),c.info({},"Tenants seeding complete!");}}async function J(s=1,r=20,e={}){let t=await i(),o=(s-1)*r,a=t.select().from(H);e.status&&(a=a.where(eq(H.status,e.status))),e.plan&&(a=a.where(eq(H.plan,e.plan))),e.search&&(a=a.where(like(H.name,`%${e.search}%`)));let u=await a.orderBy(desc(H.createdAt)).limit(r).offset(o),g=t.select().from(H);e.status&&(g=g.where(eq(H.status,e.status))),e.plan&&(g=g.where(eq(H.plan,e.plan))),e.search&&(g=g.where(like(H.name,`%${e.search}%`)));let T=(await g).length;return {items:u.map(i=>({id:i.id,name:i.name,slug:i.slug,status:i.status,plan:i.plan,maxUsers:i.maxUsers,settings:i.settings?JSON.parse(i.settings):null,createdAt:i.createdAt,updatedAt:i.updatedAt})),total:T,page:s,pageSize:r}}async function Q(s){let e=await(await i()).select().from(H).where(eq(H.slug,s));if(e.length===0)return null;let t=e[0];return {id:t.id,name:t.name,slug:t.slug,status:t.status,plan:t.plan,maxUsers:t.maxUsers,settings:t.settings?JSON.parse(t.settings):null,createdAt:t.createdAt,updatedAt:t.updatedAt}}async function k(s){let e=await(await i()).select().from(H).where(eq(H.id,s));if(e.length===0)return null;let t=e[0];return {id:t.id,name:t.name,slug:t.slug,status:t.status,plan:t.plan,maxUsers:t.maxUsers,settings:t.settings?JSON.parse(t.settings):null,createdAt:t.createdAt,updatedAt:t.updatedAt}}async function R(s){let r=await i();if((await r.select().from(H).where(eq(H.slug,s.slug))).length>0)throw new b(`Tenant with slug '${s.slug}' already exists`);let t=new Date().toISOString(),a=(await r.insert(H).values({name:s.name,slug:s.slug,status:"trial",plan:s.plan,maxUsers:s.maxUsers,settings:s.settings?JSON.stringify(s.settings):null,createdAt:t,updatedAt:t}).returning())[0];return c.info({tenantId:a.id,slug:a.slug},"Tenant created"),{id:a.id,name:a.name,slug:a.slug,status:a.status,plan:a.plan,maxUsers:a.maxUsers,settings:a.settings?JSON.parse(a.settings):null,createdAt:a.createdAt,updatedAt:a.updatedAt}}async function v(s,r){let e=await i();if((await e.select().from(H).where(eq(H.id,s))).length===0)return null;let o={updatedAt:new Date().toISOString()};r.name!==void 0&&(o.name=r.name),r.status!==void 0&&(o.status=r.status),r.plan!==void 0&&(o.plan=r.plan),r.maxUsers!==void 0&&(o.maxUsers=r.maxUsers),r.settings!==void 0&&(o.settings=r.settings?JSON.stringify(r.settings):null);let a=await e.update(H).set(o).where(eq(H.id,s)).returning();if(a.length===0)return null;let u=a[0];return c.info({tenantId:u.id,slug:u.slug},"Tenant updated"),{id:u.id,name:u.name,slug:u.slug,status:u.status,plan:u.plan,maxUsers:u.maxUsers,settings:u.settings?JSON.parse(u.settings):null,createdAt:u.createdAt,updatedAt:u.updatedAt}}async function D(s){return (await(await i()).delete(H).where(eq(H.id,s)).returning()).length>0?(c.info({tenantId:s},"Tenant deleted"),true):false}export{R as createTenant,D as deleteTenant,k as getTenantById,Q as getTenantBySlug,J as listTenants,O as seedTenantsIfEmpty,v as updateTenant};
@@ -0,0 +1 @@
1
+ import {b}from'./chunk-MDOVVKKD.js';import {a}from'./chunk-UMAQLNPE.js';import {h,f,i}from'./chunk-OYCBNRMK.js';import {J as J$1,H}from'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';import {eq,like,desc}from'drizzle-orm';a();J$1();h();var g=f("tenant-service");async function N(){let a=await i();if((await a.select().from(H)).length===0){g.info({},"Seeding tenants...");let s=new Date().toISOString(),t=[{name:"Demo Corp",slug:"demo",status:"active",plan:"pro",maxUsers:50,settings:JSON.stringify({theme:"dark",language:"en"}),createdAt:s,updatedAt:s},{name:"Test Inc",slug:"test",status:"trial",plan:"free",maxUsers:5,settings:null,createdAt:s,updatedAt:s}];await a.insert(H).values(t),g.info({},"Tenants seeding complete!");}}async function k(a=1,e=20,s={}){let t=await i(),i$1=(a-1)*e,r=t.select().from(H);s.status&&(r=r.where(eq(H.status,s.status))),s.plan&&(r=r.where(eq(H.plan,s.plan))),s.search&&(r=r.where(like(H.name,`%${s.search}%`)));let l=await r.orderBy(desc(H.createdAt)).limit(e).offset(i$1),c=t.select().from(H);s.status&&(c=c.where(eq(H.status,s.status))),s.plan&&(c=c.where(eq(H.plan,s.plan))),s.search&&(c=c.where(like(H.name,`%${s.search}%`)));let w=(await c).length;return {items:l.map(d=>({id:d.id,name:d.name,slug:d.slug,status:d.status,plan:d.plan,maxUsers:d.maxUsers,settings:d.settings?JSON.parse(d.settings):null,createdAt:d.createdAt,updatedAt:d.updatedAt})),total:w,page:a,pageSize:e}}async function R(a){let s=await(await i()).select().from(H).where(eq(H.slug,a));if(s.length===0)return null;let t=s[0];return {id:t.id,name:t.name,slug:t.slug,status:t.status,plan:t.plan,maxUsers:t.maxUsers,settings:t.settings?JSON.parse(t.settings):null,createdAt:t.createdAt,updatedAt:t.updatedAt}}async function J(a){let s=await(await i()).select().from(H).where(eq(H.id,a));if(s.length===0)return null;let t=s[0];return {id:t.id,name:t.name,slug:t.slug,status:t.status,plan:t.plan,maxUsers:t.maxUsers,settings:t.settings?JSON.parse(t.settings):null,createdAt:t.createdAt,updatedAt:t.updatedAt}}async function Q(a){let e=await i();if((await e.select().from(H).where(eq(H.slug,a.slug))).length>0)throw new b(`Tenant with slug '${a.slug}' already exists`);let t=new Date().toISOString(),r=(await e.insert(H).values({name:a.name,slug:a.slug,status:"trial",plan:a.plan,maxUsers:a.maxUsers,settings:a.settings?JSON.stringify(a.settings):null,createdAt:t,updatedAt:t}).returning())[0];return g.info({tenantId:r.id,slug:r.slug},"Tenant created"),{id:r.id,name:r.name,slug:r.slug,status:r.status,plan:r.plan,maxUsers:r.maxUsers,settings:r.settings?JSON.parse(r.settings):null,createdAt:r.createdAt,updatedAt:r.updatedAt}}async function P(a,e){let s=await i();if((await s.select().from(H).where(eq(H.id,a))).length===0)return null;let i$1={updatedAt:new Date().toISOString()};e.name!==void 0&&(i$1.name=e.name),e.status!==void 0&&e.status!==null&&(i$1.status=e.status),e.plan!==void 0&&e.plan!==null&&(i$1.plan=e.plan),e.maxUsers!==void 0&&e.maxUsers!==null&&(i$1.maxUsers=e.maxUsers),e.settings!==void 0&&(i$1.settings=e.settings?JSON.stringify(e.settings):null),e.name!==void 0&&(i$1.name=e.name),e.status!==void 0&&e.status!==null&&(i$1.status=e.status),e.plan!==void 0&&e.plan!==null&&(i$1.plan=e.plan),e.maxUsers!==void 0&&e.maxUsers!==null&&(i$1.maxUsers=e.maxUsers),e.name!==void 0&&(i$1.name=e.name),e.status!==void 0&&e.status!==null&&(i$1.status=e.status),e.plan!==void 0&&e.plan!==null&&(i$1.plan=e.plan),e.maxUsers!==void 0&&e.maxUsers!==null&&(i$1.maxUsers=e.maxUsers),e.settings!==void 0&&(i$1.settings=e.settings?JSON.stringify(e.settings):null);let r=await s.update(H).set(i$1).where(eq(H.id,a)).returning();if(r.length===0)return null;let l=r[0];return g.info({tenantId:l.id,slug:l.slug},"Tenant updated"),{id:l.id,name:l.name,slug:l.slug,status:l.status,plan:l.plan,maxUsers:l.maxUsers,settings:l.settings?JSON.parse(l.settings):null,createdAt:l.createdAt,updatedAt:l.updatedAt}}async function D(a){return (await(await i()).delete(H).where(eq(H.id,a)).returning()).length>0?(g.info({tenantId:a},"Tenant deleted"),true):false}export{Q as createTenant,D as deleteTenant,J as getTenantById,R as getTenantBySlug,k as listTenants,N as seedTenantsIfEmpty,P as updateTenant};
@@ -1,4 +1,4 @@
1
- import {V,S}from'./chunk-YY6FRMAQ.js';import'./chunk-QBHAPWIA.js';import'./chunk-5J6HXUF6.js';V();async function u(){let e=await S();if(!e||!("execute"in e))throw new Error("Test database client not available");let E=`
1
+ import {a}from'./chunk-UMAQLNPE.js';import {j}from'./chunk-OYCBNRMK.js';import'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';a();async function O(){let e=await j();if(!e||!("execute"in e))throw new Error("Test database client not available");let t=`
2
2
  CREATE TABLE IF NOT EXISTS todos (
3
3
  id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
4
4
  title TEXT NOT NULL,
@@ -181,4 +181,85 @@ import {V,S}from'./chunk-YY6FRMAQ.js';import'./chunk-QBHAPWIA.js';import'./chunk
181
181
  created_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL,
182
182
  updated_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL
183
183
  );
184
- `.split(";").filter(T=>T.trim());for(let T of E)if(T.trim())try{await e.execute(T);}catch{}await l(e);}async function l(e){let s=[{id:"role_super_admin",code:"super_admin",name:"\u8D85\u7EA7\u7BA1\u7406\u5458",label:"\u8D85\u7EA7\u7BA1\u7406\u5458",is_system:1,sort_order:1},{id:"role_admin",code:"admin",name:"\u7BA1\u7406\u5458",label:"\u7BA1\u7406\u5458",is_system:1,sort_order:2},{id:"role_user",code:"user",name:"\u666E\u901A\u7528\u6237",label:"\u666E\u901A\u7528\u6237",is_system:0,sort_order:3},{id:"role_customer_service",code:"customer_service",name:"\u5BA2\u670D\u4EBA\u5458",label:"\u5BA2\u670D",is_system:0,sort_order:4}];for(let t of s)try{await e.execute({sql:"INSERT OR IGNORE INTO roles (id, code, name, label, is_system, is_active, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",args:[t.id,t.code,t.name,t.label,t.is_system,1,t.sort_order,Date.now(),Date.now()]});}catch{}let E=[{id:"perm_user_view",code:"user:view",name:"\u67E5\u770B\u7528\u6237",label:"\u67E5\u770B\u7528\u6237",category:"user"},{id:"perm_user_create",code:"user:create",name:"\u521B\u5EFA\u7528\u6237",label:"\u521B\u5EFA\u7528\u6237",category:"user"},{id:"perm_user_update",code:"user:update",name:"\u66F4\u65B0\u7528\u6237",label:"\u66F4\u65B0\u7528\u6237",category:"user"},{id:"perm_user_delete",code:"user:delete",name:"\u5220\u9664\u7528\u6237",label:"\u5220\u9664\u7528\u6237",category:"user"}],T=[{id:"perm_content_view",code:"content:view",name:"\u67E5\u770B\u5185\u5BB9",label:"\u67E5\u770B\u5185\u5BB9",category:"content"},{id:"perm_content_create",code:"content:create",name:"\u521B\u5EFA\u5185\u5BB9",label:"\u521B\u5EFA\u5185\u5BB9",category:"content"},{id:"perm_content_update",code:"content:update",name:"\u66F4\u65B0\u5185\u5BB9",label:"\u66F4\u65B0\u5185\u5BB9",category:"content"},{id:"perm_content_delete",code:"content:delete",name:"\u5220\u9664\u5185\u5BB9",label:"\u5220\u9664\u5185\u5BB9",category:"content"}],o=[{id:"perm_system_settings",code:"system:settings",name:"\u7CFB\u7EDF\u8BBE\u7F6E",label:"\u7CFB\u7EDF\u8BBE\u7F6E",category:"system"},{id:"perm_system_logs",code:"system:logs",name:"\u67E5\u770B\u65E5\u5FD7",label:"\u67E5\u770B\u65E5\u5FD7",category:"system"},{id:"perm_system_backup",code:"system:backup",name:"\u7CFB\u7EDF\u5907\u4EFD",label:"\u7CFB\u7EDF\u5907\u4EFD",category:"system"}],a=[{id:"perm_order_view",code:"order:view",name:"\u67E5\u770B\u8BA2\u5355",label:"\u67E5\u770B\u8BA2\u5355",category:"order"},{id:"perm_order_create",code:"order:create",name:"\u521B\u5EFA\u8BA2\u5355",label:"\u521B\u5EFA\u8BA2\u5355",category:"order"},{id:"perm_order_edit",code:"order:edit",name:"\u7F16\u8F91\u8BA2\u5355",label:"\u7F16\u8F91\u8BA2\u5355",category:"order"},{id:"perm_order_delete",code:"order:delete",name:"\u5220\u9664\u8BA2\u5355",label:"\u5220\u9664\u8BA2\u5355",category:"order"},{id:"perm_order_process",code:"order:process",name:"\u5904\u7406\u8BA2\u5355",label:"\u5904\u7406\u8BA2\u5355",category:"order"}],d=[{id:"perm_ticket_view",code:"ticket:view",name:"\u67E5\u770B\u5DE5\u5355",label:"\u67E5\u770B\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_create",code:"ticket:create",name:"\u521B\u5EFA\u5DE5\u5355",label:"\u521B\u5EFA\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_edit",code:"ticket:edit",name:"\u7F16\u8F91\u5DE5\u5355",label:"\u7F16\u8F91\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_delete",code:"ticket:delete",name:"\u5220\u9664\u5DE5\u5355",label:"\u5220\u9664\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_reply",code:"ticket:reply",name:"\u56DE\u590D\u5DE5\u5355",label:"\u56DE\u590D\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_close",code:"ticket:close",name:"\u5173\u95ED\u5DE5\u5355",label:"\u5173\u95ED\u5DE5\u5355",category:"ticket"}],N=[{id:"perm_data_export",code:"data:export",name:"\u5BFC\u51FA\u6570\u636E",label:"\u5BFC\u51FA\u6570\u636E",category:"data"},{id:"perm_data_import",code:"data:import",name:"\u5BFC\u5165\u6570\u636E",label:"\u5BFC\u5165\u6570\u636E",category:"data"}],c=[{id:"perm_dispute_view",code:"dispute:view",name:"\u67E5\u770B\u4E89\u8BAE",label:"\u67E5\u770B\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_create",code:"dispute:create",name:"\u521B\u5EFA\u4E89\u8BAE",label:"\u521B\u5EFA\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_edit",code:"dispute:edit",name:"\u7F16\u8F91\u4E89\u8BAE",label:"\u7F16\u8F91\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_delete",code:"dispute:delete",name:"\u5220\u9664\u4E89\u8BAE",label:"\u5220\u9664\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_resolve",code:"dispute:resolve",name:"\u89E3\u51B3\u4E89\u8BAE",label:"\u89E3\u51B3\u4E89\u8BAE",category:"dispute"}],r=[...E,...T,...o,...a,...d,...N,...c];for(let t of r)try{await e.execute({sql:"INSERT OR IGNORE INTO permissions (id, code, name, label, category, is_active, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",args:[t.id,t.code,t.name,t.label,t.category,1,0,Date.now(),Date.now()]});}catch{}for(let t of r)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_super_admin",t.id,Date.now()]});}catch{}for(let t of r)if(t.code!=="system:settings")try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_admin",t.id,Date.now()]});}catch{}let L=["perm_user_view","perm_content_view","perm_order_view","perm_order_create","perm_order_edit","perm_order_delete","perm_order_process","perm_ticket_view","perm_ticket_create","perm_ticket_edit","perm_ticket_delete","perm_ticket_reply","perm_ticket_close","perm_dispute_view","perm_dispute_create","perm_dispute_edit","perm_dispute_delete","perm_dispute_resolve"];for(let t of L)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_customer_service",t,Date.now()]});}catch{}let _=[{userId:"super-admin-1",roleId:"role_super_admin"},{userId:"customer-service-1",roleId:"role_customer_service"},{userId:"user-1",roleId:"role_user"}];for(let t of _)try{await e.execute({sql:"INSERT OR IGNORE INTO user_roles (id, user_id, role_id, is_active, assigned_at) VALUES (?, ?, ?, ?, ?)",args:[`ur_${t.userId}`,t.userId,t.roleId,1,Date.now()]});}catch{}let n=["perm_content_view","perm_order_view"];for(let t of n)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_user",t,Date.now()]});}catch{}}async function O(){let e=await S();if(e&&"execute"in e){try{await e.execute("DELETE FROM todo_attachments");}catch{}await e.execute("DELETE FROM todos"),await e.execute("DELETE FROM notifications"),await e.execute("DELETE FROM permission_routes"),await e.execute("DELETE FROM permission_audit_logs"),await e.execute("DELETE FROM role_permissions"),await e.execute("DELETE FROM user_roles"),await e.execute("DELETE FROM permissions"),await e.execute("DELETE FROM roles"),await e.execute("DELETE FROM routes"),await e.execute("DELETE FROM ticket_replies"),await e.execute("DELETE FROM tickets"),await e.execute("DELETE FROM orders"),await e.execute("DELETE FROM disputes"),await e.execute("DELETE FROM contents");}}export{O as cleanupTestDatabase,u as setupTestDatabase};
184
+
185
+ CREATE TABLE IF NOT EXISTS developers (
186
+ id TEXT PRIMARY KEY NOT NULL,
187
+ username TEXT NOT NULL UNIQUE,
188
+ email TEXT NOT NULL UNIQUE,
189
+ password_hash TEXT NOT NULL,
190
+ role TEXT DEFAULT 'developer' NOT NULL,
191
+ api_key TEXT NOT NULL UNIQUE,
192
+ created_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL,
193
+ updated_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL
194
+ );
195
+
196
+ CREATE TABLE IF NOT EXISTS plugins (
197
+ id TEXT PRIMARY KEY NOT NULL,
198
+ name TEXT NOT NULL,
199
+ slug TEXT NOT NULL UNIQUE,
200
+ description TEXT NOT NULL DEFAULT '',
201
+ readme TEXT,
202
+ author_id TEXT NOT NULL,
203
+ author_name TEXT NOT NULL,
204
+ repository_url TEXT,
205
+ homepage_url TEXT,
206
+ npm_package TEXT,
207
+ license TEXT,
208
+ version TEXT NOT NULL DEFAULT '0.0.1',
209
+ status TEXT NOT NULL DEFAULT 'pending',
210
+ download_count INTEGER NOT NULL DEFAULT 0,
211
+ view_count INTEGER NOT NULL DEFAULT 0,
212
+ featured INTEGER NOT NULL DEFAULT 0,
213
+ screenshot_url TEXT,
214
+ site_urls TEXT,
215
+ tags TEXT,
216
+ commands TEXT,
217
+ reject_reason TEXT,
218
+ created_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL,
219
+ updated_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL
220
+ );
221
+
222
+ CREATE TABLE IF NOT EXISTS plugin_versions (
223
+ id TEXT PRIMARY KEY NOT NULL,
224
+ plugin_id TEXT NOT NULL,
225
+ version TEXT NOT NULL,
226
+ changelog TEXT,
227
+ package_url TEXT,
228
+ file_size INTEGER,
229
+ checksum TEXT,
230
+ status TEXT NOT NULL DEFAULT 'pending',
231
+ published_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL,
232
+ FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE,
233
+ UNIQUE(plugin_id, version)
234
+ );
235
+
236
+ CREATE TABLE IF NOT EXISTS plugin_reviews (
237
+ id TEXT PRIMARY KEY NOT NULL,
238
+ plugin_id TEXT NOT NULL,
239
+ user_id TEXT NOT NULL,
240
+ user_name TEXT NOT NULL,
241
+ rating INTEGER NOT NULL,
242
+ title TEXT,
243
+ content TEXT,
244
+ created_at INTEGER DEFAULT (unixepoch() * 1000) NOT NULL,
245
+ FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE,
246
+ UNIQUE(plugin_id, user_id)
247
+ );
248
+
249
+ CREATE TABLE IF NOT EXISTS plugin_categories (
250
+ id TEXT PRIMARY KEY NOT NULL,
251
+ name TEXT NOT NULL UNIQUE,
252
+ slug TEXT NOT NULL UNIQUE,
253
+ description TEXT,
254
+ icon TEXT,
255
+ sort_order INTEGER NOT NULL DEFAULT 0
256
+ );
257
+
258
+ CREATE TABLE IF NOT EXISTS plugin_category_mappings (
259
+ plugin_id TEXT NOT NULL,
260
+ category_id TEXT NOT NULL,
261
+ FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE,
262
+ FOREIGN KEY (category_id) REFERENCES plugin_categories(id) ON DELETE CASCADE,
263
+ UNIQUE(plugin_id, category_id)
264
+ );
265
+ `.split(";").filter(E=>E.trim());for(let E of t)if(E.trim())try{await e.execute(E);}catch{}await l(e);}async function l(e){let s=[{id:"role_super_admin",code:"super_admin",name:"\u8D85\u7EA7\u7BA1\u7406\u5458",label:"\u8D85\u7EA7\u7BA1\u7406\u5458",is_system:1,sort_order:1},{id:"role_admin",code:"admin",name:"\u7BA1\u7406\u5458",label:"\u7BA1\u7406\u5458",is_system:1,sort_order:2},{id:"role_user",code:"user",name:"\u666E\u901A\u7528\u6237",label:"\u666E\u901A\u7528\u6237",is_system:0,sort_order:3},{id:"role_customer_service",code:"customer_service",name:"\u5BA2\u670D\u4EBA\u5458",label:"\u5BA2\u670D",is_system:0,sort_order:4}];for(let T of s)try{await e.execute({sql:"INSERT OR IGNORE INTO roles (id, code, name, label, is_system, is_active, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",args:[T.id,T.code,T.name,T.label,T.is_system,1,T.sort_order,Date.now(),Date.now()]});}catch{}let t=[{id:"perm_user_view",code:"user:view",name:"\u67E5\u770B\u7528\u6237",label:"\u67E5\u770B\u7528\u6237",category:"user"},{id:"perm_user_create",code:"user:create",name:"\u521B\u5EFA\u7528\u6237",label:"\u521B\u5EFA\u7528\u6237",category:"user"},{id:"perm_user_update",code:"user:update",name:"\u66F4\u65B0\u7528\u6237",label:"\u66F4\u65B0\u7528\u6237",category:"user"},{id:"perm_user_delete",code:"user:delete",name:"\u5220\u9664\u7528\u6237",label:"\u5220\u9664\u7528\u6237",category:"user"}],E=[{id:"perm_content_view",code:"content:view",name:"\u67E5\u770B\u5185\u5BB9",label:"\u67E5\u770B\u5185\u5BB9",category:"content"},{id:"perm_content_create",code:"content:create",name:"\u521B\u5EFA\u5185\u5BB9",label:"\u521B\u5EFA\u5185\u5BB9",category:"content"},{id:"perm_content_update",code:"content:update",name:"\u66F4\u65B0\u5185\u5BB9",label:"\u66F4\u65B0\u5185\u5BB9",category:"content"},{id:"perm_content_delete",code:"content:delete",name:"\u5220\u9664\u5185\u5BB9",label:"\u5220\u9664\u5185\u5BB9",category:"content"}],a=[{id:"perm_system_settings",code:"system:settings",name:"\u7CFB\u7EDF\u8BBE\u7F6E",label:"\u7CFB\u7EDF\u8BBE\u7F6E",category:"system"},{id:"perm_system_logs",code:"system:logs",name:"\u67E5\u770B\u65E5\u5FD7",label:"\u67E5\u770B\u65E5\u5FD7",category:"system"},{id:"perm_system_backup",code:"system:backup",name:"\u7CFB\u7EDF\u5907\u4EFD",label:"\u7CFB\u7EDF\u5907\u4EFD",category:"system"}],o=[{id:"perm_order_view",code:"order:view",name:"\u67E5\u770B\u8BA2\u5355",label:"\u67E5\u770B\u8BA2\u5355",category:"order"},{id:"perm_order_create",code:"order:create",name:"\u521B\u5EFA\u8BA2\u5355",label:"\u521B\u5EFA\u8BA2\u5355",category:"order"},{id:"perm_order_edit",code:"order:edit",name:"\u7F16\u8F91\u8BA2\u5355",label:"\u7F16\u8F91\u8BA2\u5355",category:"order"},{id:"perm_order_delete",code:"order:delete",name:"\u5220\u9664\u8BA2\u5355",label:"\u5220\u9664\u8BA2\u5355",category:"order"},{id:"perm_order_process",code:"order:process",name:"\u5904\u7406\u8BA2\u5355",label:"\u5904\u7406\u8BA2\u5355",category:"order"}],N=[{id:"perm_ticket_view",code:"ticket:view",name:"\u67E5\u770B\u5DE5\u5355",label:"\u67E5\u770B\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_create",code:"ticket:create",name:"\u521B\u5EFA\u5DE5\u5355",label:"\u521B\u5EFA\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_edit",code:"ticket:edit",name:"\u7F16\u8F91\u5DE5\u5355",label:"\u7F16\u8F91\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_delete",code:"ticket:delete",name:"\u5220\u9664\u5DE5\u5355",label:"\u5220\u9664\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_reply",code:"ticket:reply",name:"\u56DE\u590D\u5DE5\u5355",label:"\u56DE\u590D\u5DE5\u5355",category:"ticket"},{id:"perm_ticket_close",code:"ticket:close",name:"\u5173\u95ED\u5DE5\u5355",label:"\u5173\u95ED\u5DE5\u5355",category:"ticket"}],d=[{id:"perm_data_export",code:"data:export",name:"\u5BFC\u51FA\u6570\u636E",label:"\u5BFC\u51FA\u6570\u636E",category:"data"},{id:"perm_data_import",code:"data:import",name:"\u5BFC\u5165\u6570\u636E",label:"\u5BFC\u5165\u6570\u636E",category:"data"}],L=[{id:"perm_dispute_view",code:"dispute:view",name:"\u67E5\u770B\u4E89\u8BAE",label:"\u67E5\u770B\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_create",code:"dispute:create",name:"\u521B\u5EFA\u4E89\u8BAE",label:"\u521B\u5EFA\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_edit",code:"dispute:edit",name:"\u7F16\u8F91\u4E89\u8BAE",label:"\u7F16\u8F91\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_delete",code:"dispute:delete",name:"\u5220\u9664\u4E89\u8BAE",label:"\u5220\u9664\u4E89\u8BAE",category:"dispute"},{id:"perm_dispute_resolve",code:"dispute:resolve",name:"\u89E3\u51B3\u4E89\u8BAE",label:"\u89E3\u51B3\u4E89\u8BAE",category:"dispute"}],i=[...t,...E,...a,...o,...N,...d,...L];for(let T of i)try{await e.execute({sql:"INSERT OR IGNORE INTO permissions (id, code, name, label, category, is_active, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",args:[T.id,T.code,T.name,T.label,T.category,1,0,Date.now(),Date.now()]});}catch{}for(let T of i)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_super_admin",T.id,Date.now()]});}catch{}for(let T of i)if(T.code!=="system:settings")try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_admin",T.id,Date.now()]});}catch{}let c=["perm_user_view","perm_content_view","perm_order_view","perm_order_create","perm_order_edit","perm_order_delete","perm_order_process","perm_ticket_view","perm_ticket_create","perm_ticket_edit","perm_ticket_delete","perm_ticket_reply","perm_ticket_close","perm_dispute_view","perm_dispute_create","perm_dispute_edit","perm_dispute_delete","perm_dispute_resolve"];for(let T of c)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_customer_service",T,Date.now()]});}catch{}let _=[{userId:"super-admin-1",roleId:"role_super_admin"},{userId:"customer-service-1",roleId:"role_customer_service"},{userId:"user-1",roleId:"role_user"}];for(let T of _)try{await e.execute({sql:"INSERT OR IGNORE INTO user_roles (id, user_id, role_id, is_active, assigned_at) VALUES (?, ?, ?, ?, ?)",args:[`ur_${T.userId}`,T.userId,T.roleId,1,Date.now()]});}catch{}let n=["perm_content_view","perm_order_view"];for(let T of n)try{await e.execute({sql:"INSERT OR IGNORE INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)",args:["role_user",T,Date.now()]});}catch{}}async function m(){let e=await j();if(e&&"execute"in e){try{await e.execute("DELETE FROM todo_attachments");}catch{}await e.execute("DELETE FROM todos"),await e.execute("DELETE FROM notifications"),await e.execute("DELETE FROM permission_routes"),await e.execute("DELETE FROM permission_audit_logs"),await e.execute("DELETE FROM role_permissions"),await e.execute("DELETE FROM user_roles"),await e.execute("DELETE FROM permissions"),await e.execute("DELETE FROM roles"),await e.execute("DELETE FROM routes"),await e.execute("DELETE FROM ticket_replies"),await e.execute("DELETE FROM tickets"),await e.execute("DELETE FROM orders"),await e.execute("DELETE FROM disputes"),await e.execute("DELETE FROM contents");try{await e.execute("DELETE FROM plugin_category_mappings"),await e.execute("DELETE FROM plugin_reviews"),await e.execute("DELETE FROM plugin_versions"),await e.execute("DELETE FROM plugins"),await e.execute("DELETE FROM plugin_categories"),await e.execute("DELETE FROM developers");}catch{}}}export{m as cleanupTestDatabase,O as setupTestDatabase};
@@ -0,0 +1 @@
1
+ export{h as closeTicket,d as createTicket,f as deleteTicket,c as getTicketById,b as getTickets,g as replyTicket,a as seedTicketsIfEmpty,e as updateTicket}from'./chunk-JHNYLNG7.js';import'./chunk-I6OBDFDU.js';import'./chunk-FIY4CGJJ.js';import'./chunk-JCPOP4XL.js';import'./chunk-UMAQLNPE.js';import'./chunk-OYCBNRMK.js';import'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';
@@ -0,0 +1 @@
1
+ export{l as createTodo,r as deleteAttachment,n as deleteTodo,k as getTodo,q as getTodoWithAttachments,p as listAttachments,j as listTodos,i as seedTodosIfEmpty,m as updateTodo,o as uploadAttachment}from'./chunk-OU5IGVVO.js';import'./chunk-FIY4CGJJ.js';import'./chunk-JCPOP4XL.js';import'./chunk-UMAQLNPE.js';import'./chunk-OYCBNRMK.js';import'./chunk-27SIJSPF.js';import'./chunk-5J6HXUF6.js';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "biomimic-todo-app",
3
3
  "private": true,
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "biomimic": "./dist/cli/index.js"
@@ -19,7 +19,9 @@ export const App: React.FC<{ presetId?: string }> = ({ presetId = 'todo' }) => {
19
19
  mobileTabs={mobileTabs}
20
20
  >
21
21
  <Routes>
22
- <Route path="/" element={<Navigate to={defaultRoute} replace />} />
22
+ {defaultRoute !== '/' && (
23
+ <Route path="/" element={<Navigate to={defaultRoute} replace />} />
24
+ )}
23
25
  {routes
24
26
  .filter(
25
27
  (r): r is RouteDef & { component: NonNullable<RouteDef['component']> } =>
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect, useCallback } from 'react'
2
2
  import { Link } from 'react-router-dom'
3
3
  import { Helmet } from 'react-helmet-async'
4
- import type { Content, ContentCategory } from '@shared/modules/content'
4
+ import type { Content, ContentCategory, ContentStatus } from '@shared/modules/content'
5
5
  import { apiClient } from '@client/services/apiClient'
6
6
 
7
7
  const CATEGORIES: { value: ContentCategory | ''; label: string }[] = [
@@ -66,6 +66,15 @@ export const ContentListPage: React.FC = () => {
66
66
  })
67
67
  }
68
68
 
69
+ const statusConfig = (status: ContentStatus) => {
70
+ const configs = {
71
+ draft: { color: 'bg-yellow-100 text-yellow-700', label: '草稿' },
72
+ published: { color: 'bg-green-100 text-green-700', label: '已发布' },
73
+ archived: { color: 'bg-gray-100 text-gray-700', label: '已归档' },
74
+ }
75
+ return configs[status]
76
+ }
77
+
69
78
  return (
70
79
  <>
71
80
  <Helmet>
@@ -137,16 +146,39 @@ export const ContentListPage: React.FC = () => {
137
146
  >
138
147
  <div className="flex items-start justify-between gap-4">
139
148
  <div className="flex-1">
140
- <div className="flex items-center gap-2 mb-2">
149
+ <div className="flex items-center gap-2 mb-2 flex-wrap">
141
150
  <span className="px-2 py-1 bg-blue-100 text-blue-700 text-xs font-medium rounded">
142
151
  {categoryLabel(content.category)}
143
152
  </span>
153
+ <span
154
+ className={`px-2 py-1 text-xs font-medium rounded ${
155
+ statusConfig(content.status).color
156
+ }`}
157
+ >
158
+ {statusConfig(content.status).label}
159
+ </span>
144
160
  <span className="text-sm text-gray-500">{content.author}</span>
145
161
  </div>
146
162
  <h2 className="text-xl font-semibold text-gray-900 mb-2">{content.title}</h2>
147
163
  <p className="text-gray-600 line-clamp-2">{content.content.slice(0, 150)}...</p>
148
- <div className="flex items-center gap-4 mt-3 text-sm text-gray-400">
149
- {content.publishedAt && <span>{formatDate(content.publishedAt)}</span>}
164
+ {content.tags && content.tags.length > 0 && (
165
+ <div className="flex gap-1.5 flex-wrap mt-3">
166
+ {content.tags.map(tag => (
167
+ <span
168
+ key={tag}
169
+ className="px-2 py-0.5 bg-gray-100 text-gray-600 text-xs rounded"
170
+ >
171
+ {tag}
172
+ </span>
173
+ ))}
174
+ </div>
175
+ )}
176
+ <div className="flex items-center gap-4 mt-3 text-sm text-gray-400 flex-wrap">
177
+ {content.createdAt && <span>创建于 {formatDate(content.createdAt)}</span>}
178
+ {content.updatedAt && content.updatedAt !== content.createdAt && (
179
+ <span>更新于 {formatDate(content.updatedAt)}</span>
180
+ )}
181
+ {content.publishedAt && <span>发布于 {formatDate(content.publishedAt)}</span>}
150
182
  <span>👁 {content.viewCount}</span>
151
183
  <span>❤ {content.likeCount}</span>
152
184
  </div>
@@ -10,10 +10,27 @@ import {
10
10
  Sparkles,
11
11
  TrendingUp,
12
12
  Zap,
13
+ Eye,
14
+ ExternalLink,
15
+ Star,
16
+ Shield,
13
17
  } from 'lucide-react'
14
18
  import { usePluginStore } from '@client/stores/pluginStore'
15
19
  import { LoadingSpinner, EmptyState } from '@client/components'
16
- import { StatusBadge } from '@client/components'
20
+ import { StatusBadge, type ColorScheme } from '@client/components'
21
+
22
+ const getStatusColor = (status: string): ColorScheme => {
23
+ switch (status?.toLowerCase()) {
24
+ case 'approved':
25
+ return 'green'
26
+ case 'pending':
27
+ return 'yellow'
28
+ case 'rejected':
29
+ return 'red'
30
+ default:
31
+ return 'gray'
32
+ }
33
+ }
17
34
 
18
35
  export const PluginsPage: React.FC = () => {
19
36
  const plugins = usePluginStore(state => state.plugins)
@@ -141,37 +158,112 @@ export const PluginsPage: React.FC = () => {
141
158
  <Link
142
159
  key={plugin.id}
143
160
  to={`/plugins/${plugin.slug}`}
144
- className="group relative p-5 bg-white rounded-2xl border border-gray-100 hover:border-violet-200 hover:shadow-xl hover:shadow-violet-500/5 transition-all duration-300 block overflow-hidden"
161
+ className="group relative bg-white rounded-2xl border border-gray-100 hover:border-violet-200 hover:shadow-xl hover:shadow-violet-500/5 transition-all duration-300 block overflow-hidden"
145
162
  >
146
163
  <div className="absolute inset-0 bg-gradient-to-br from-violet-500/0 to-fuchsia-500/0 group-hover:from-violet-500/[0.02] group-hover:to-fuchsia-500/[0.04] transition-all duration-300" />
147
164
  <div className="relative">
148
- <div className="flex items-start justify-between gap-3 mb-3">
149
- <div className="flex items-center gap-3">
150
- <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-100 to-fuchsia-100 flex items-center justify-center text-lg font-bold text-violet-600 shrink-0">
151
- {plugin.name.charAt(0).toUpperCase()}
165
+ {/* Screenshot thumbnail */}
166
+ <div className="relative">
167
+ {plugin.screenshotUrl ? (
168
+ <img
169
+ src={plugin.screenshotUrl}
170
+ alt={plugin.name}
171
+ className="w-full h-32 object-cover"
172
+ />
173
+ ) : (
174
+ <div className="w-full h-20 bg-gradient-to-br from-violet-100 to-fuchsia-100" />
175
+ )}
176
+ {plugin.featured && (
177
+ <div className="absolute top-2 right-2">
178
+ <div className="p-1.5 bg-amber-400 rounded-full shadow-lg">
179
+ <Star className="w-3.5 h-3.5 text-white fill-white" />
180
+ </div>
152
181
  </div>
153
- <div className="min-w-0">
154
- <h3 className="text-base font-semibold text-gray-900 truncate group-hover:text-violet-700 transition-colors">
155
- {plugin.name}
156
- </h3>
157
- <p className="text-xs text-gray-400">by {plugin.authorName}</p>
182
+ )}
183
+ </div>
184
+
185
+ <div className="p-5">
186
+ <div className="flex items-start justify-between gap-3 mb-3">
187
+ <div className="flex items-center gap-3">
188
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-100 to-fuchsia-100 flex items-center justify-center text-lg font-bold text-violet-600 shrink-0">
189
+ {plugin.name.charAt(0).toUpperCase()}
190
+ </div>
191
+ <div className="min-w-0">
192
+ <h3 className="text-base font-semibold text-gray-900 truncate group-hover:text-violet-700 transition-colors">
193
+ {plugin.name}
194
+ </h3>
195
+ <p className="text-xs text-gray-400">by {plugin.authorName}</p>
196
+ </div>
158
197
  </div>
198
+ <StatusBadge label={`v${plugin.version}`} colorScheme="purple" />
159
199
  </div>
160
- <StatusBadge label={`v${plugin.version}`} colorScheme="purple" />
161
- </div>
162
- <p className="text-sm text-gray-500 line-clamp-2 mb-4 leading-relaxed">
163
- {plugin.description}
164
- </p>
165
- <div className="flex items-center justify-between">
166
- <div className="flex items-center gap-3 text-xs text-gray-400">
200
+
201
+ {/* Status and License */}
202
+ <div className="flex items-center gap-2 mb-3">
203
+ <StatusBadge
204
+ label={plugin.status || 'Unknown'}
205
+ icon={Shield}
206
+ colorScheme={getStatusColor(plugin.status || '')}
207
+ />
208
+ {plugin.license && (
209
+ <span className="inline-flex items-center px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs font-medium">
210
+ {plugin.license}
211
+ </span>
212
+ )}
213
+ </div>
214
+
215
+ <p className="text-sm text-gray-500 line-clamp-2 mb-4 leading-relaxed">
216
+ {plugin.description}
217
+ </p>
218
+
219
+ {/* Download + View counts */}
220
+ <div className="flex items-center gap-3 text-xs text-gray-400 mb-3">
167
221
  <span className="flex items-center gap-1">
168
222
  <Download className="w-3.5 h-3.5" />
169
223
  {plugin.downloadCount}
170
224
  </span>
225
+ {plugin.viewCount !== undefined && (
226
+ <span className="flex items-center gap-1">
227
+ <Eye className="w-3.5 h-3.5" />
228
+ {plugin.viewCount}
229
+ </span>
230
+ )}
171
231
  </div>
232
+
233
+ {/* External links */}
234
+ {(plugin.repositoryUrl || plugin.homepageUrl) && (
235
+ <div className="flex items-center gap-1.5 mb-3">
236
+ {plugin.repositoryUrl && (
237
+ <a
238
+ href={plugin.repositoryUrl}
239
+ target="_blank"
240
+ rel="noopener noreferrer"
241
+ onClick={e => e.stopPropagation()}
242
+ className="p-1.5 rounded-lg bg-gray-50 hover:bg-gray-100 text-gray-400 hover:text-violet-600 transition-colors"
243
+ title="Repository"
244
+ >
245
+ <ExternalLink className="w-3.5 h-3.5" />
246
+ </a>
247
+ )}
248
+ {plugin.homepageUrl && (
249
+ <a
250
+ href={plugin.homepageUrl}
251
+ target="_blank"
252
+ rel="noopener noreferrer"
253
+ onClick={e => e.stopPropagation()}
254
+ className="p-1.5 rounded-lg bg-gray-50 hover:bg-gray-100 text-gray-400 hover:text-violet-600 transition-colors"
255
+ title="Homepage"
256
+ >
257
+ <ExternalLink className="w-3.5 h-3.5" />
258
+ </a>
259
+ )}
260
+ </div>
261
+ )}
262
+
263
+ {/* Tags - show all */}
172
264
  {plugin.tags && plugin.tags.length > 0 && (
173
- <div className="flex items-center gap-1">
174
- {plugin.tags.slice(0, 2).map(tag => (
265
+ <div className="flex flex-wrap items-center gap-1">
266
+ {plugin.tags.map(tag => (
175
267
  <span
176
268
  key={tag}
177
269
  className="inline-flex items-center px-2 py-0.5 bg-gray-50 text-gray-500 rounded-md text-xs"
@@ -112,7 +112,10 @@ export const TodoPage: React.FC = () => {
112
112
  <title>Todo List - Biomimic App</title>
113
113
  <meta name="description" content="Manage your todo items with full CRUD operations" />
114
114
  <meta property="og:title" content="Todo List - Biomimic App" />
115
- <meta property="og:description" content="Manage your todo items with full CRUD operations" />
115
+ <meta
116
+ property="og:description"
117
+ content="Manage your todo items with full CRUD operations"
118
+ />
116
119
  </Helmet>
117
120
  <div className="mb-8">
118
121
  <h1
@@ -275,6 +278,11 @@ export const TodoPage: React.FC = () => {
275
278
  </select>
276
279
  <span className="text-xs text-gray-400">
277
280
  {new Date(todo.createdAt).toLocaleString()}
281
+ {todo.updatedAt && todo.updatedAt !== todo.createdAt && (
282
+ <span className="ml-2">
283
+ · Updated {new Date(todo.updatedAt).toLocaleString()}
284
+ </span>
285
+ )}
278
286
  </span>
279
287
  </div>
280
288
  </div>
@@ -1,6 +1,6 @@
1
1
  import { lazy, type ComponentType } from 'react'
2
2
 
3
- export type PresetType = 'todo' | 'plugin' | 'ecommerce' | 'community' | 'saas'
3
+ export type PresetType = 'todo' | 'plugin' | 'ecommerce' | 'community' | 'forum' | 'saas'
4
4
 
5
5
  export type AppType = 'client' | 'admin'
6
6
  export type LayoutType = 'top-nav' | 'minimal'
@@ -481,10 +481,87 @@ export const PRESET_UI_CONFIGS: Record<PresetType, PresetUIConfig> = {
481
481
  },
482
482
  ],
483
483
  },
484
+
485
+ // forum is an alias for community — same config, different preset ID
486
+ forum: {
487
+ id: 'community',
488
+ name: 'Community',
489
+ appType: 'client',
490
+ layout: 'top-nav',
491
+ theme: COMMUNITY_THEME,
492
+ navigation: {
493
+ visible: true,
494
+ showLogo: true,
495
+ showSearch: false,
496
+ showCart: false,
497
+ authStyle: 'avatar',
498
+ navItems: 'desktop',
499
+ },
500
+ desktopNav: [
501
+ { label: 'Topics', icon: 'Hash', path: '/topics' },
502
+ { label: 'Popular', icon: 'TrendingUp', path: '/popular' },
503
+ { label: 'Notifications', icon: 'Bell', path: '/notifications' },
504
+ { label: 'Profile', icon: 'User', path: '/profile' },
505
+ ],
506
+ mobileTabs: [
507
+ { label: 'Topics', icon: 'Hash', path: '/topics' },
508
+ { label: 'Popular', icon: 'TrendingUp', path: '/popular' },
509
+ { label: 'Notifications', icon: 'Bell', path: '/notifications' },
510
+ { label: 'Me', icon: 'User', path: '/profile' },
511
+ ],
512
+ defaultRoute: '/topics',
513
+ routes: [
514
+ {
515
+ path: '/topics',
516
+ component: lazy(() => import('./pages/TopicsPage').then(m => ({ default: m.TopicsPage }))),
517
+ label: 'Topics',
518
+ },
519
+ {
520
+ path: '/popular',
521
+ component: lazy(() => import('./pages/TopicsPage').then(m => ({ default: m.TopicsPage }))),
522
+ label: 'Popular',
523
+ },
524
+ {
525
+ path: '/notifications',
526
+ component: lazy(() =>
527
+ import('./pages/NotificationPage').then(m => ({ default: m.NotificationPage }))
528
+ ),
529
+ label: 'Notifications',
530
+ },
531
+ {
532
+ path: '/profile',
533
+ component: lazy(() =>
534
+ import('./pages/ProfilePage').then(m => ({ default: m.ProfilePage }))
535
+ ),
536
+ label: 'Profile',
537
+ },
538
+ {
539
+ path: '/login',
540
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
541
+ label: 'Login',
542
+ },
543
+ {
544
+ path: '/register',
545
+ component: lazy(() =>
546
+ import('./pages/RegisterPage').then(m => ({ default: m.RegisterPage }))
547
+ ),
548
+ label: 'Register',
549
+ },
550
+ ],
551
+ },
552
+ }
553
+
554
+ // Preset ID aliases: deployed preset IDs may differ from internal config keys
555
+ const PRESET_ALIASES: Record<string, PresetType> = {
556
+ forum: 'community',
557
+ 'xbrowser-marketplace': 'plugin',
558
+ 'fullstack-admin': 'saas',
559
+ 'todo-app': 'todo',
484
560
  }
485
561
 
486
562
  export function getPresetUIConfig(id: string): PresetUIConfig {
487
- return PRESET_UI_CONFIGS[id as PresetType] ?? PRESET_UI_CONFIGS.todo
563
+ const resolvedId = PRESET_ALIASES[id] ?? (id as PresetType)
564
+ return PRESET_UI_CONFIGS[resolvedId] ?? PRESET_UI_CONFIGS.todo
488
565
  }
489
566
 
490
567
  export function getPresetUIConfigs(): Record<PresetType, PresetUIConfig> {
@@ -15,8 +15,8 @@ export const DisputesPage: FC = () => {
15
15
  setLoading(true)
16
16
  try {
17
17
  const response = await fetch('/api/merchant/disputes')
18
- const result = await response.json()
19
- if (result.success) {
18
+ const result = (await response.json()) as { success?: boolean; data?: Dispute[] }
19
+ if (result.success === true && result.data) {
20
20
  setDisputes(result.data)
21
21
  }
22
22
  } catch (error) {
@@ -31,7 +31,7 @@ export const DisputesPage: FC = () => {
31
31
  setShowModal(true)
32
32
  }
33
33
 
34
- const handleResolve = async (disputeId: number) => {
34
+ const handleResolve = async (disputeId: string) => {
35
35
  try {
36
36
  await fetch(`/api/merchant/disputes/${disputeId}/resolve`, {
37
37
  method: 'POST',
@@ -42,7 +42,7 @@ export const DisputesPage: FC = () => {
42
42
  }
43
43
  }
44
44
 
45
- const handleClose = async (disputeId: number) => {
45
+ const handleClose = async (disputeId: string) => {
46
46
  try {
47
47
  await fetch(`/api/merchant/disputes/${disputeId}/close`, {
48
48
  method: 'POST',
@@ -59,10 +59,10 @@ export const DisputesPage: FC = () => {
59
59
 
60
60
  const getStatusColor = (status: DisputeStatus): string => {
61
61
  const colors: Record<DisputeStatus, string> = {
62
- open: 'blue',
62
+ pending: 'blue',
63
63
  investigating: 'orange',
64
64
  resolved: 'green',
65
- closed: 'red',
65
+ rejected: 'red',
66
66
  }
67
67
  return colors[status] || 'default'
68
68
  }
@@ -74,9 +74,9 @@ export const DisputesPage: FC = () => {
74
74
  key: 'orderId',
75
75
  },
76
76
  {
77
- title: 'Reason',
78
- dataIndex: 'reason',
79
- key: 'reason',
77
+ title: 'Type',
78
+ dataIndex: 'type',
79
+ key: 'type',
80
80
  },
81
81
  {
82
82
  title: 'Status',
@@ -98,7 +98,7 @@ export const DisputesPage: FC = () => {
98
98
  <Button type="link" icon={<EyeOutlined />} onClick={() => handleView(record)}>
99
99
  View
100
100
  </Button>
101
- {record.status === 'open' && (
101
+ {record.status === 'pending' && (
102
102
  <>
103
103
  <Button type="link" icon={<CheckOutlined />} onClick={() => handleResolve(record.id)}>
104
104
  Resolve
@@ -128,7 +128,7 @@ export const DisputesPage: FC = () => {
128
128
  <h3>Dispute Details</h3>
129
129
  <Descriptions bordered>
130
130
  <Descriptions.Item label="Order ID">{selectedDispute.orderId}</Descriptions.Item>
131
- <Descriptions.Item label="Reason">{selectedDispute.reason}</Descriptions.Item>
131
+ <Descriptions.Item label="Type">{selectedDispute.type}</Descriptions.Item>
132
132
  <Descriptions.Item label="Status">
133
133
  <Tag color={getStatusColor(selectedDispute.status)}>{selectedDispute.status}</Tag>
134
134
  </Descriptions.Item>
@@ -16,8 +16,8 @@ export const OrdersPage: FC = () => {
16
16
  setLoading(true)
17
17
  try {
18
18
  const response = await fetch('/api/merchant/orders')
19
- const result = await response.json()
20
- if (result.success) {
19
+ const result = (await response.json()) as { success?: boolean; data?: Order[] }
20
+ if (result.success === true && result.data) {
21
21
  setOrders(result.data)
22
22
  }
23
23
  } catch (error) {
@@ -31,7 +31,8 @@ export const OrdersPage: FC = () => {
31
31
  setStatusFilter(value)
32
32
  }
33
33
 
34
- const handleDateRangeChange = (dates: [Date, Date] | null) => {
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Ant Design RangePicker types are complex
35
+ const handleDateRangeChange = (dates: any) => {
35
36
  setDateRange(dates)
36
37
  }
37
38