wiki-plugin-shoppe 0.0.31 → 0.0.32

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 (2) hide show
  1. package/package.json +1 -1
  2. package/server/server.js +32 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-shoppe",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "Multi-tenant digital goods shoppe for federated wiki, powered by Sanora",
5
5
  "keywords": [
6
6
  "wiki",
package/server/server.js CHANGED
@@ -543,11 +543,32 @@ async function sanoraCreateProduct(tenant, title, category, description, price,
543
543
  }
544
544
  );
545
545
 
546
+ if (!resp.ok) {
547
+ const text = await resp.text().catch(() => '');
548
+ throw new Error(`Create product failed (${resp.status}): ${text.slice(0, 200)}`);
549
+ }
546
550
  const product = await resp.json();
547
551
  if (product.error) throw new Error(`Create product failed: ${product.error}`);
548
552
  return product;
549
553
  }
550
554
 
555
+ // Wrapper used by processArchive. On "not found" (Sanora Redis cleared mid-upload),
556
+ // re-registers the tenant and retries once. tenant.uuid may be updated in place.
557
+ async function sanoraCreateProductResilient(tenant, title, category, description, price, shipping, tags) {
558
+ try {
559
+ return await sanoraCreateProduct(tenant, title, category, description, price, shipping, tags);
560
+ } catch (err) {
561
+ if (err.message.includes('not found') || err.message.includes('404')) {
562
+ console.warn(`[shoppe] Sanora user lost mid-upload, re-registering and retrying: ${title}`);
563
+ const updated = await sanoraEnsureUser(tenant);
564
+ // Mutate tenant in place so all subsequent calls use the new UUID
565
+ tenant.uuid = updated.uuid;
566
+ return await sanoraCreateProduct(tenant, title, category, description, price, shipping, tags);
567
+ }
568
+ throw err;
569
+ }
570
+ }
571
+
551
572
  async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifactType) {
552
573
  const { uuid, keys } = tenant;
553
574
  const timestamp = Date.now().toString();
@@ -811,7 +832,7 @@ async function processArchive(zipPath) {
811
832
  const description = info.description || '';
812
833
  const price = info.price || 0;
813
834
 
814
- await sanoraCreateProduct(tenant, title, 'book', description, price, 0, buildTags('book', info.keywords));
835
+ await sanoraCreateProductResilient(tenant, title, 'book', description, price, 0, buildTags('book', info.keywords));
815
836
 
816
837
  // Cover image — use info.cover to pin a specific file, else first image found
817
838
  const covers = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
@@ -853,7 +874,7 @@ async function processArchive(zipPath) {
853
874
  try {
854
875
  const description = info.description || `Album: ${albumTitle}`;
855
876
  const price = info.price || 0;
856
- await sanoraCreateProduct(tenant, albumTitle, 'music', description, price, 0, buildTags('music,album', info.keywords));
877
+ await sanoraCreateProductResilient(tenant, albumTitle, 'music', description, price, 0, buildTags('music,album', info.keywords));
857
878
  const coverFile = info.cover ? (covers.find(f => f === info.cover) || covers[0]) : covers[0];
858
879
  if (coverFile) {
859
880
  const coverBuf = fs.readFileSync(path.join(entryPath, coverFile));
@@ -882,7 +903,7 @@ async function processArchive(zipPath) {
882
903
  const buf = fs.readFileSync(entryPath);
883
904
  const description = trackInfo.description || `Track: ${title}`;
884
905
  const price = trackInfo.price || 0;
885
- await sanoraCreateProduct(tenant, title, 'music', description, price, 0, buildTags('music,track', trackInfo.keywords));
906
+ await sanoraCreateProductResilient(tenant, title, 'music', description, price, 0, buildTags('music,track', trackInfo.keywords));
886
907
  await sanoraUploadArtifact(tenant, title, buf, entry, 'audio');
887
908
  results.music.push({ title, type: 'track' });
888
909
  console.log(`[shoppe] 🎵 track: ${title}`);
@@ -922,7 +943,7 @@ async function processArchive(zipPath) {
922
943
  // Register the series itself as a parent product
923
944
  try {
924
945
  const description = info.description || `A ${subDirs.length}-part series`;
925
- await sanoraCreateProduct(tenant, seriesTitle, 'post-series', description, 0, 0, buildTags(`post,series,order:${order}`, info.keywords));
946
+ await sanoraCreateProductResilient(tenant, seriesTitle, 'post-series', description, 0, 0, buildTags(`post,series,order:${order}`, info.keywords));
926
947
 
927
948
  const covers = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
928
949
  if (covers.length > 0) {
@@ -962,7 +983,7 @@ async function processArchive(zipPath) {
962
983
  const productTitle = `${seriesTitle}: ${resolvedTitle}`;
963
984
  const description = partInfo.description || partFm.body.split('\n\n')[0].replace(/^#+\s*/, '').trim() || resolvedTitle;
964
985
 
965
- await sanoraCreateProduct(tenant, productTitle, 'post', description, 0, 0,
986
+ await sanoraCreateProductResilient(tenant, productTitle, 'post', description, 0, 0,
966
987
  buildTags(`post,blog,series:${seriesTitle},part:${partIndex + 1},order:${order}`, partInfo.keywords));
967
988
 
968
989
  await sanoraUploadArtifact(tenant, productTitle, mdBuf, partMdFiles[0], 'text');
@@ -1004,7 +1025,7 @@ async function processArchive(zipPath) {
1004
1025
  const firstLine = fm.body.split('\n').find(l => l.trim()).replace(/^#+\s*/, '');
1005
1026
  const description = info.description || fm.body.split('\n\n')[0].replace(/^#+\s*/, '').trim() || firstLine || title;
1006
1027
 
1007
- await sanoraCreateProduct(tenant, title, 'post', description, 0, 0, buildTags(`post,blog,order:${order}`, info.keywords));
1028
+ await sanoraCreateProductResilient(tenant, title, 'post', description, 0, 0, buildTags(`post,blog,order:${order}`, info.keywords));
1008
1029
  await sanoraUploadArtifact(tenant, title, mdBuf, mdFiles[0], 'text');
1009
1030
 
1010
1031
  const covers = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
@@ -1041,7 +1062,7 @@ async function processArchive(zipPath) {
1041
1062
  if (!fs.statSync(entryPath).isDirectory()) continue;
1042
1063
  const images = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
1043
1064
  try {
1044
- await sanoraCreateProduct(tenant, entry, 'album', `Photo album: ${entry}`, 0, 0, 'album,photos');
1065
+ await sanoraCreateProductResilient(tenant, entry, 'album', `Photo album: ${entry}`, 0, 0, 'album,photos');
1045
1066
  if (images.length > 0) {
1046
1067
  const coverBuf = fs.readFileSync(path.join(entryPath, images[0]));
1047
1068
  await sanoraUploadImage(tenant, entry, coverBuf, images[0]);
@@ -1078,7 +1099,7 @@ async function processArchive(zipPath) {
1078
1099
  const price = info.price || 0;
1079
1100
  const shipping = info.shipping || 0;
1080
1101
 
1081
- await sanoraCreateProduct(tenant, title, 'product', description, price, shipping, buildTags(`product,physical,order:${order}`, info.keywords));
1102
+ await sanoraCreateProductResilient(tenant, title, 'product', description, price, shipping, buildTags(`product,physical,order:${order}`, info.keywords));
1082
1103
 
1083
1104
  // Hero image: prefer hero.jpg / hero.png, fall back to first image
1084
1105
  const images = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
@@ -1119,7 +1140,7 @@ async function processArchive(zipPath) {
1119
1140
  renewalDays: info.renewalDays || 30
1120
1141
  };
1121
1142
 
1122
- await sanoraCreateProduct(tenant, title, 'subscription', description, price, 0, buildTags('subscription', info.keywords));
1143
+ await sanoraCreateProductResilient(tenant, title, 'subscription', description, price, 0, buildTags('subscription', info.keywords));
1123
1144
 
1124
1145
  // Upload tier metadata (benefits list, renewal period) as a JSON artifact
1125
1146
  const tierBuf = Buffer.from(JSON.stringify(tierMeta));
@@ -1194,7 +1215,7 @@ async function processArchive(zipPath) {
1194
1215
  (lucilleVideoId ? `,lucille-id:${lucilleVideoId},lucille-url:${lucilleBase}` : '');
1195
1216
 
1196
1217
  // Sanora catalog entry (for discovery / storefront)
1197
- await sanoraCreateProduct(tenant, title, 'video', description, price, 0, videoTags);
1218
+ await sanoraCreateProductResilient(tenant, title, 'video', description, price, 0, videoTags);
1198
1219
 
1199
1220
  // Cover / poster image (optional)
1200
1221
  const images = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
@@ -1240,7 +1261,7 @@ async function processArchive(zipPath) {
1240
1261
  advanceDays: info.advanceDays || 30
1241
1262
  };
1242
1263
 
1243
- await sanoraCreateProduct(tenant, title, 'appointment', description, price, 0, buildTags('appointment', info.keywords));
1264
+ await sanoraCreateProductResilient(tenant, title, 'appointment', description, price, 0, buildTags('appointment', info.keywords));
1244
1265
 
1245
1266
  // Upload schedule as a JSON artifact so the booking page can retrieve it
1246
1267
  const scheduleBuf = Buffer.from(JSON.stringify(schedule));