@eluvio/elv-client-js 3.1.74 → 3.1.78

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.
@@ -1094,6 +1094,8 @@ exports.DownloadFile = function _callee12(_ref13) {
1094
1094
  encryption,
1095
1095
  path,
1096
1096
  headers,
1097
+ ownerCapKey,
1098
+ ownerCap,
1097
1099
  bytesTotal,
1098
1100
  _args14 = arguments;
1099
1101
 
@@ -1139,33 +1141,53 @@ exports.DownloadFile = function _callee12(_ref13) {
1139
1141
  headers = _context14.sent;
1140
1142
  headers.Accept = "*/*"; // If not owner, indicate re-encryption
1141
1143
 
1142
- _context14.t0 = encrypted;
1144
+ ownerCapKey = "eluv.caps.iusr".concat(this.utils.AddressToHash(this.signer.address));
1145
+ _context14.next = 17;
1146
+ return _regeneratorRuntime.awrap(this.ContentObjectMetadata({
1147
+ libraryId: libraryId,
1148
+ objectId: objectId,
1149
+ metadataSubtree: ownerCapKey
1150
+ }));
1143
1151
 
1144
- if (!_context14.t0) {
1145
- _context14.next = 22;
1152
+ case 17:
1153
+ ownerCap = _context14.sent;
1154
+ _context14.t1 = encrypted;
1155
+
1156
+ if (!_context14.t1) {
1157
+ _context14.next = 26;
1146
1158
  break;
1147
1159
  }
1148
1160
 
1149
- _context14.t1 = this.utils;
1150
- _context14.t2 = this.signer.address;
1151
- _context14.next = 20;
1161
+ _context14.t2 = this.utils;
1162
+ _context14.t3 = this.signer.address;
1163
+ _context14.next = 24;
1152
1164
  return _regeneratorRuntime.awrap(this.ContentObjectOwner({
1153
1165
  objectId: objectId
1154
1166
  }));
1155
1167
 
1156
- case 20:
1157
- _context14.t3 = _context14.sent;
1158
- _context14.t0 = !_context14.t1.EqualAddress.call(_context14.t1, _context14.t2, _context14.t3);
1168
+ case 24:
1169
+ _context14.t4 = _context14.sent;
1170
+ _context14.t1 = !_context14.t2.EqualAddress.call(_context14.t2, _context14.t3, _context14.t4);
1171
+
1172
+ case 26:
1173
+ _context14.t0 = _context14.t1;
1159
1174
 
1160
- case 22:
1161
1175
  if (!_context14.t0) {
1162
- _context14.next = 24;
1176
+ _context14.next = 29;
1177
+ break;
1178
+ }
1179
+
1180
+ _context14.t0 = !ownerCap;
1181
+
1182
+ case 29:
1183
+ if (!_context14.t0) {
1184
+ _context14.next = 31;
1163
1185
  break;
1164
1186
  }
1165
1187
 
1166
1188
  headers["X-Content-Fabric-Decryption-Mode"] = "reencrypt";
1167
1189
 
1168
- case 24:
1190
+ case 31:
1169
1191
  // If using server side decryption, specify in header
1170
1192
  if (encrypted && !clientSideDecryption) {
1171
1193
  headers["X-Content-Fabric-Decryption-Mode"] = "decrypt"; // rep/files_download endpoint doesn't currently support Range header
@@ -1176,13 +1198,13 @@ exports.DownloadFile = function _callee12(_ref13) {
1176
1198
  bytesTotal = fileInfo["."].size;
1177
1199
 
1178
1200
  if (!(encrypted && clientSideDecryption)) {
1179
- _context14.next = 46;
1201
+ _context14.next = 53;
1180
1202
  break;
1181
1203
  }
1182
1204
 
1183
- _context14.t4 = _regeneratorRuntime;
1184
- _context14.t5 = this;
1185
- _context14.next = 31;
1205
+ _context14.t5 = _regeneratorRuntime;
1206
+ _context14.t6 = this;
1207
+ _context14.next = 38;
1186
1208
  return _regeneratorRuntime.awrap(this.EncryptionConk({
1187
1209
  libraryId: libraryId,
1188
1210
  objectId: objectId,
@@ -1190,39 +1212,39 @@ exports.DownloadFile = function _callee12(_ref13) {
1190
1212
  download: true
1191
1213
  }));
1192
1214
 
1193
- case 31:
1194
- _context14.t6 = _context14.sent;
1195
- _context14.t7 = path;
1196
- _context14.t8 = bytesTotal;
1197
- _context14.t9 = headers;
1198
- _context14.t10 = callback;
1199
- _context14.t11 = format;
1200
- _context14.t12 = clientSideDecryption;
1201
- _context14.t13 = chunked;
1202
- _context14.t14 = {
1203
- conk: _context14.t6,
1204
- downloadPath: _context14.t7,
1205
- bytesTotal: _context14.t8,
1206
- headers: _context14.t9,
1207
- callback: _context14.t10,
1208
- format: _context14.t11,
1209
- clientSideDecryption: _context14.t12,
1210
- chunked: _context14.t13
1215
+ case 38:
1216
+ _context14.t7 = _context14.sent;
1217
+ _context14.t8 = path;
1218
+ _context14.t9 = bytesTotal;
1219
+ _context14.t10 = headers;
1220
+ _context14.t11 = callback;
1221
+ _context14.t12 = format;
1222
+ _context14.t13 = clientSideDecryption;
1223
+ _context14.t14 = chunked;
1224
+ _context14.t15 = {
1225
+ conk: _context14.t7,
1226
+ downloadPath: _context14.t8,
1227
+ bytesTotal: _context14.t9,
1228
+ headers: _context14.t10,
1229
+ callback: _context14.t11,
1230
+ format: _context14.t12,
1231
+ clientSideDecryption: _context14.t13,
1232
+ chunked: _context14.t14
1211
1233
  };
1212
- _context14.t15 = _context14.t5.DownloadEncrypted.call(_context14.t5, _context14.t14);
1213
- _context14.next = 43;
1214
- return _context14.t4.awrap.call(_context14.t4, _context14.t15);
1234
+ _context14.t16 = _context14.t6.DownloadEncrypted.call(_context14.t6, _context14.t15);
1235
+ _context14.next = 50;
1236
+ return _context14.t5.awrap.call(_context14.t5, _context14.t16);
1215
1237
 
1216
- case 43:
1238
+ case 50:
1217
1239
  return _context14.abrupt("return", _context14.sent);
1218
1240
 
1219
- case 46:
1241
+ case 53:
1220
1242
  if (!chunkSize) {
1221
1243
  chunkSize = 10000000;
1222
1244
  }
1223
1245
 
1224
- _context14.prev = 47;
1225
- _context14.next = 50;
1246
+ _context14.prev = 54;
1247
+ _context14.next = 57;
1226
1248
  return _regeneratorRuntime.awrap(this.Download({
1227
1249
  downloadPath: path,
1228
1250
  bytesTotal: bytesTotal,
@@ -1233,15 +1255,15 @@ exports.DownloadFile = function _callee12(_ref13) {
1233
1255
  chunkSize: chunkSize
1234
1256
  }));
1235
1257
 
1236
- case 50:
1258
+ case 57:
1237
1259
  return _context14.abrupt("return", _context14.sent);
1238
1260
 
1239
- case 53:
1240
- _context14.prev = 53;
1241
- _context14.t16 = _context14["catch"](47);
1261
+ case 60:
1262
+ _context14.prev = 60;
1263
+ _context14.t17 = _context14["catch"](54);
1242
1264
 
1243
1265
  if (!(encrypted && !clientSideDecryption)) {
1244
- _context14.next = 57;
1266
+ _context14.next = 64;
1245
1267
  break;
1246
1268
  }
1247
1269
 
@@ -1249,15 +1271,15 @@ exports.DownloadFile = function _callee12(_ref13) {
1249
1271
  clientSideDecryption: true
1250
1272
  })));
1251
1273
 
1252
- case 57:
1253
- throw _context14.t16;
1274
+ case 64:
1275
+ throw _context14.t17;
1254
1276
 
1255
- case 58:
1277
+ case 65:
1256
1278
  case "end":
1257
1279
  return _context14.stop();
1258
1280
  }
1259
1281
  }
1260
- }, null, this, [[47, 53]]);
1282
+ }, null, this, [[54, 60]]);
1261
1283
  };
1262
1284
  /* Parts */
1263
1285
 
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "3.1.74",
3
+ "version": "3.1.78",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "3.1.74",
3
+ "version": "3.1.78",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/ElvClient.js",
6
6
  "author": "Kevin Talmadge",
@@ -176,7 +176,10 @@ class AuthorizationClient {
176
176
  let publicKey;
177
177
  if(encryption && encryption !== "none" && objectId && await this.AccessType(objectId) === ACCESS_TYPES.OBJECT) {
178
178
  const owner = await this.Owner({id: objectId});
179
- if(!Utils.EqualAddress(owner, this.client.signer.address)) {
179
+ const ownerCapKey = `eluv.caps.iusr${Utils.AddressToHash(this.client.signer.address)}`;
180
+ const ownerCap = await client.ContentObjectMetadata({libraryId, objectId, metadataSubtree: ownerCapKey});
181
+
182
+ if(!Utils.EqualAddress(owner, this.client.signer.address) && !ownerCap) {
180
183
  const cap = await this.ReEncryptionConk({libraryId, objectId});
181
184
  publicKey = cap.public_key;
182
185
  }
package/src/EthClient.js CHANGED
@@ -285,6 +285,8 @@ class EthClient {
285
285
  const latestBlock = await this.MakeProviderCall({methodName: "getBlock", args: ["latest"]});
286
286
  overrides.gasLimit = latestBlock.gasLimit;
287
287
  overrides.gasPrice = overrides.gasPrice ? overrides.gasPrice * 1.50 : 8000000000;
288
+ } else if(error.code === "NONCE_EXPIRED" && error.reason === "nonce has already been used") {
289
+ this.Log(`Retrying method call ${methodName}`);
288
290
  } else if(!(error.message || error).includes("invalid response")) {
289
291
  this.Log(typeof error === "object" ? JSON.stringify(error, null, 2) : error, true);
290
292
  throw error;
@@ -329,6 +329,7 @@ class FrameClient {
329
329
  "CreateFileUploadJob",
330
330
  "CreateLinks",
331
331
  "CreateNTPInstance",
332
+ "CreateNonOwnerCap",
332
333
  "CreatePart",
333
334
  "CreateProductionMaster",
334
335
  "CreateSignedToken",
@@ -6,6 +6,7 @@
6
6
  * @module ElvClient/ABRPublishing
7
7
  */
8
8
 
9
+ const R = require("ramda");
9
10
  const UrlJoin = require("url-join");
10
11
  const HttpClient = require("../HttpClient");
11
12
 
@@ -355,8 +356,6 @@ exports.CreateABRMezzanine = async function({
355
356
  ...metadata.public.asset_metadata
356
357
  };
357
358
 
358
- metadata.elv_created_at = new Date().getTime();
359
-
360
359
  if(name || !existingMez) {
361
360
  metadata.name = name || `${masterName} Mezzanine`;
362
361
  metadata.public.name = name || `${masterName} Mezzanine`;
@@ -367,7 +366,23 @@ exports.CreateABRMezzanine = async function({
367
366
  metadata.public.description = description || "";
368
367
  }
369
368
 
370
- await this.MergeMetadata({
369
+ // retrieve existing metadata to merge with updated metadata
370
+ const existingMetadata = await this.ContentObjectMetadata({
371
+ libraryId,
372
+ objectId: id,
373
+ writeToken: write_token,
374
+ });
375
+ // newer metadata values replace existing metadata, unless both new and old values are objects,
376
+ // in which case their keys are merged recursively
377
+ metadata = R.mergeDeepRight(existingMetadata, metadata);
378
+
379
+ if(!existingMez) {
380
+ // set creation date
381
+ metadata.elv_created_at = new Date().getTime();
382
+ }
383
+
384
+ // write metadata to mezzanine object
385
+ await this.ReplaceMetadata({
371
386
  libraryId,
372
387
  objectId: id,
373
388
  writeToken: write_token,
@@ -473,9 +473,8 @@ exports.LibraryContentTypes = async function({libraryId}) {
473
473
  * @param {object=} filterOptions - Pagination, sorting and filtering options
474
474
  * @param {number=} filterOptions.start - Start index for pagination
475
475
  * @param {number=} filterOptions.limit - Max number of objects to return
476
- * @param {string=} filterOptions.cacheId - Cache ID corresponding a previous query
477
476
  * @param {(Array<string> | string)=} filterOptions.sort - Sort by the specified key(s)
478
- * @param {boolean=} filterOptions.sortDesc=false - Sort in descending order
477
+ * @param {boolean=} filterOptions.sortDesc - Sort in descending order
479
478
  * @param {(Array<string> | string)=} filterOptions.select - Include only the specified metadata keys (all must start with /public)
480
479
  * @param {(Array<object> | object)=} filterOptions.filter - Filter objects by metadata
481
480
  * @param {string=} filterOptions.filter.key - Key to filter on (must start with /public)
@@ -2408,7 +2407,10 @@ exports.EncryptionConk = async function({libraryId, objectId, versionHash, write
2408
2407
 
2409
2408
  const owner = await this.authClient.Owner({id: objectId});
2410
2409
 
2411
- if(!this.utils.EqualAddress(owner, this.signer.address)) {
2410
+ const ownerCapKey = `eluv.caps.iusr${this.utils.AddressToHash(this.signer.address)}`;
2411
+ const ownerCap = await this.ContentObjectMetadata({libraryId, objectId, metadataSubtree: ownerCapKey});
2412
+
2413
+ if(!this.utils.EqualAddress(owner, this.signer.address) && !ownerCap) {
2412
2414
  if(download) {
2413
2415
  return await this.authClient.ReEncryptionConk({libraryId, objectId, versionHash});
2414
2416
  } else {
@@ -21,7 +21,8 @@ const {
21
21
  ValidateVersion,
22
22
  ValidateWriteToken,
23
23
  ValidateParameters,
24
- ValidatePresence
24
+ ValidatePresence,
25
+ ValidateAddress
25
26
  } = require("../Validation");
26
27
 
27
28
  exports.SetVisibility = async function({id, visibility}) {
@@ -58,8 +59,9 @@ exports.SetVisibility = async function({id, visibility}) {
58
59
  * @methodGroup Content Objects
59
60
  * @param {string} objectId - The ID of the object
60
61
  * @param {string} permission - The key for the permission to set - See client.permissionLevels for available permissions
62
+ * @param {string} writeToken - Write token for the content object - If specified, info will be retrieved from the write draft instead of creating a new draft and finalizing
61
63
  */
62
- exports.SetPermission = async function({objectId, permission}) {
64
+ exports.SetPermission = async function({objectId, permission, writeToken}) {
63
65
  ValidateObject(objectId);
64
66
  ValidatePresence("permission", permission);
65
67
 
@@ -118,14 +120,16 @@ exports.SetPermission = async function({objectId, permission}) {
118
120
  }
119
121
  });
120
122
  } else if(!kmsConk && settings.kmsConk) {
121
- await this.EditAndFinalizeContentObject({
122
- libraryId,
123
- objectId,
124
- commitMessage: "Add encryption conk",
125
- callback: async ({writeToken}) => {
126
- await this.CreateEncryptionConk({libraryId, objectId, writeToken, createKMSConk: true});
127
- }
128
- });
123
+ const finalize = !writeToken;
124
+ if(!writeToken) {
125
+ writeToken = (await this.EditContentObject({libraryId, objectId})).writeToken;
126
+ }
127
+
128
+ await this.CreateEncryptionConk({libraryId, objectId, writeToken, createKMSConk: true});
129
+
130
+ if(finalize) {
131
+ await this.FinalizeContentObject({libraryId, objectId, writeToken, commitMessage: `Set permissions to ${permission}`});
132
+ }
129
133
  }
130
134
  };
131
135
 
@@ -563,6 +567,17 @@ exports.CreateContentObject = async function({libraryId, objectId, options={}})
563
567
  }
564
568
 
565
569
  if(!objectId) {
570
+ const currentAccountAddress = await this.CurrentAccountAddress();
571
+ const canContribute = await this.CallContractMethod({
572
+ contractAddress: this.utils.HashToAddress(libraryId),
573
+ methodName: "canContribute",
574
+ methodArgs: [currentAccountAddress]
575
+ });
576
+
577
+ if(!canContribute) {
578
+ throw Error(`Current user does not have permission to create content in library ${libraryId}`);
579
+ }
580
+
566
581
  this.Log("Deploying contract...");
567
582
  const { contractAddress } = await this.authClient.CreateContentObject({libraryId, typeId});
568
583
 
@@ -622,7 +637,100 @@ exports.CopyContentObject = async function({libraryId, originalVersionHash, opti
622
637
 
623
638
  options.copy_from = originalVersionHash;
624
639
 
625
- return await this.CreateContentObject({libraryId, options});
640
+ const {objectId, writeToken} = await this.CreateContentObject({libraryId, options});
641
+ const originalObjectId = this.utils.DecodeVersionHash(originalVersionHash).objectId;
642
+ const metadata = await this.ContentObjectMetadata({versionHash: originalVersionHash});
643
+ const permission = await this.Permission({objectId: originalObjectId});
644
+
645
+ // User CAP
646
+ const userCapKey = `eluv.caps.iusr${this.utils.AddressToHash(this.signer.address)}`;
647
+
648
+ if(metadata[userCapKey]) {
649
+ const isOwner = this.utils.EqualAddress(this.signer.address, await this.ContentObjectOwner({objectId: originalObjectId}));
650
+
651
+ if(!isOwner) {
652
+ throw Error(`Current user is not owner of object ${metadata}`);
653
+ }
654
+
655
+ const userConkKey = await this.Crypto.DecryptCap(metadata[userCapKey], this.signer.signingKey.privateKey);
656
+ userConkKey.qid = objectId;
657
+
658
+ this.ReplaceMetadata({
659
+ libraryId,
660
+ objectId,
661
+ writeToken,
662
+ metadataSubtree: userCapKey,
663
+ metadata: await this.Crypto.EncryptConk(userConkKey, this.signer.signingKey.publicKey)
664
+ });
665
+ }
666
+
667
+ // KMS CAP
668
+ await Promise.all(
669
+ Object.keys(metadata)
670
+ .filter(key => key.startsWith("eluv.caps.ikms"))
671
+ .map(async kmsCapKey => await this.DeleteMetadata({
672
+ libraryId,
673
+ objectId,
674
+ writeToken,
675
+ metadataSubtree: kmsCapKey
676
+ }))
677
+ );
678
+
679
+ if(permission !== "owner") {
680
+ await this.SetPermission({objectId, permission, writeToken});
681
+ }
682
+
683
+ return await this.FinalizeContentObject({libraryId, objectId, writeToken});
684
+ };
685
+
686
+ /**
687
+ * Create a non-owner cap key using the specified public key and address
688
+ *
689
+ * @methodGroup Access Requests
690
+ * @namedParams
691
+ * @param {string} libraryId - ID of the library
692
+ * @param {string} objectId - ID of the object
693
+ * @param {string} publicKey - Public key for the target cap
694
+ * @param {string} publicAddress - Public address for the target cap key
695
+ * @param {string} writeToken - Write token for the content object - If specified, info will be retrieved from the write draft instead of creating a new draft and finalizing
696
+ *
697
+ * @returns {Promise<Object>}
698
+ */
699
+ exports.CreateNonOwnerCap = async function({objectId, libraryId, publicKey, publicAddress, writeToken}) {
700
+ publicAddress = ValidateAddress(publicAddress);
701
+ const userCapKey = `eluv.caps.iusr${this.utils.AddressToHash(this.signer.address)}`;
702
+ const userCapValue = await this.ContentObjectMetadata({objectId, libraryId, metadataSubtree: userCapKey});
703
+
704
+ if(!userCapValue) {
705
+ throw Error("No user cap found for current user");
706
+ }
707
+
708
+ const userConk = await this.Crypto.DecryptCap(userCapValue, this.signer.signingKey.privateKey);
709
+
710
+ const targetUserCapKey = `eluv.caps.iusr${this.utils.AddressToHash(publicAddress)}`;
711
+ const targetUserCapValue = await this.Crypto.EncryptConk(userConk, publicKey);
712
+
713
+ const finalize = !writeToken;
714
+ if(!writeToken) {
715
+ writeToken = await this.EditContentObject({libraryId, objectId}).writeToken;
716
+ }
717
+
718
+ this.ReplaceMetadata({
719
+ libraryId,
720
+ objectId,
721
+ writeToken,
722
+ metadataSubtree: targetUserCapKey,
723
+ metadata: targetUserCapValue
724
+ });
725
+
726
+ if(finalize) {
727
+ await this.FinalizeContentObject({
728
+ libraryId,
729
+ objectId,
730
+ writeToken,
731
+ commitMessage: "Create non-owner cap"
732
+ });
733
+ }
626
734
  };
627
735
 
628
736
  /**
@@ -641,7 +641,10 @@ exports.DownloadFile = async function({
641
641
  headers.Accept = "*/*";
642
642
 
643
643
  // If not owner, indicate re-encryption
644
- if(encrypted && !this.utils.EqualAddress(this.signer.address, await this.ContentObjectOwner({objectId}))) {
644
+ const ownerCapKey = `eluv.caps.iusr${this.utils.AddressToHash(this.signer.address)}`;
645
+ const ownerCap = await this.ContentObjectMetadata({libraryId, objectId, metadataSubtree: ownerCapKey});
646
+
647
+ if(encrypted && !this.utils.EqualAddress(this.signer.address, await this.ContentObjectOwner({objectId})) && !ownerCap) {
645
648
  headers["X-Content-Fabric-Decryption-Mode"] = "reencrypt";
646
649
  }
647
650
 
Binary file
@@ -5,8 +5,8 @@ const currencyOptions = [...new Set(Object.values(require("country-codes-list").
5
5
 
6
6
  const eventSiteSpec = {
7
7
  "profile": {
8
- name: "Eluvio LIVE Drop Event Site",
9
- version: "0.1",
8
+ name: "Eluvio LIVE Event Site",
9
+ version: "0.2",
10
10
  },
11
11
  manageApp: "default",
12
12
  associate_permissions: true,
@@ -22,7 +22,22 @@ const eventSiteSpec = {
22
22
  localization: {
23
23
  localizations: Object.keys(languageOptions)
24
24
  },
25
- associated_assets: [],
25
+ associated_assets: [
26
+ {
27
+ name: "promos",
28
+ label: "Promos (Apple TV)",
29
+ indexed: true,
30
+ slugged: true,
31
+ defaultable: true,
32
+ orderable: true
33
+ },
34
+ {
35
+ name: "channels",
36
+ label: "Channels (Apple TV)",
37
+ indexed: true,
38
+ defaultable: true
39
+ },
40
+ ],
26
41
  info_fields: [
27
42
  {
28
43
  "label": "Basic Info",
@@ -47,11 +62,6 @@ const eventSiteSpec = {
47
62
  "hash_only": true,
48
63
  "no_localize": true
49
64
  },
50
- {
51
- "name": "allow_login",
52
- "type": "checkbox",
53
- "default_value": false
54
- },
55
65
  {
56
66
  "name": "state",
57
67
  "type": "select",
@@ -64,6 +74,11 @@ const eventSiteSpec = {
64
74
  "default_value": "Inaccessible",
65
75
  "hint": "Specify the current state of the event. Inaccessible and ended events will not be visible to users."
66
76
  },
77
+ {
78
+ "label": "Accessible (Apple TV)",
79
+ "name": "accessible",
80
+ "type": "checkbox"
81
+ },
67
82
  {
68
83
  "name": "theme",
69
84
  "type": "select",
@@ -318,18 +333,28 @@ const eventSiteSpec = {
318
333
  "name": "type",
319
334
  "type": "select",
320
335
  "options": [
336
+ "drop",
321
337
  "marketplace",
322
338
  "link"
323
339
  ],
324
- "default_value": "marketplace"
340
+ "default_value": "marketplace",
341
+ "hint": "Specify what happens when clicking on the banner. The banner can link to a URL or a drop, or it can open the marketplace view."
325
342
  },
326
343
  {
327
344
  "name": "marketplace_filters",
328
- "type": "list"
345
+ "type": "list",
346
+ "hint": "If the banner links to the marketplace, you can specify filters to apply when the marketplace is opened via the banner."
329
347
  },
330
348
  {
331
349
  "name": "link",
332
- "type": "text"
350
+ "type": "text",
351
+ "hint": "If the banner is a link, specify the URL to link to."
352
+ },
353
+ {
354
+ "label": "Drop UUID",
355
+ "name": "drop_uuid",
356
+ "type": "text",
357
+ "hint": "If the banner links to a drop, you can specify a specific drop to link to. If not specified, the banner will link to the next upcoming drop."
333
358
  }
334
359
  ]
335
360
  }
@@ -997,6 +1022,11 @@ const eventSiteSpec = {
997
1022
  "name": "header_drops",
998
1023
  "type": "header"
999
1024
  },
1025
+ {
1026
+ "name": "hide_upcoming_events",
1027
+ "type": "checkbox",
1028
+ "default_value": false
1029
+ },
1000
1030
  {
1001
1031
  "name": "marketplace_drops",
1002
1032
  "type": "list",
@@ -1065,6 +1095,11 @@ const eventSiteSpec = {
1065
1095
  "type": "checkbox",
1066
1096
  "default_value": true
1067
1097
  },
1098
+ {
1099
+ "name": "requires_ticket",
1100
+ "type": "checkbox",
1101
+ "default_value": false
1102
+ },
1068
1103
  {
1069
1104
  "name": "event_header",
1070
1105
  "type": "text",
@@ -1500,6 +1535,15 @@ const eventSiteSpec = {
1500
1535
  "name": "description",
1501
1536
  "type": "textarea"
1502
1537
  },
1538
+ {
1539
+ "name": "location",
1540
+ "type": "text"
1541
+ },
1542
+ {
1543
+ "name": "type",
1544
+ "type": "select",
1545
+ "options": ["Online Only", "Online and In-Person"]
1546
+ },
1503
1547
  {
1504
1548
  "name": "images",
1505
1549
  "type": "list",
@@ -1509,6 +1553,26 @@ const eventSiteSpec = {
1509
1553
  "extensions": imageTypes
1510
1554
  }]
1511
1555
  },
1556
+ {
1557
+ "name": "performers",
1558
+ "type": "list",
1559
+ "fields": [
1560
+ {
1561
+ "name": "name",
1562
+ "type": "text"
1563
+ },
1564
+ {
1565
+ "name": "url",
1566
+ "label": "URL",
1567
+ "type": "text"
1568
+ },
1569
+ {
1570
+ "name": "image",
1571
+ "type": "file",
1572
+ "extensions": imageTypes
1573
+ }
1574
+ ]
1575
+ },
1512
1576
  {
1513
1577
  "name": "organizers",
1514
1578
  "type": "list",
@@ -1528,6 +1592,25 @@ const eventSiteSpec = {
1528
1592
  "extensions": imageTypes
1529
1593
  }
1530
1594
  ]
1595
+ },
1596
+ {
1597
+ "name": "showings",
1598
+ "type": "list",
1599
+ "fields": [
1600
+ {
1601
+ "name": "name",
1602
+ "type": "text"
1603
+ },
1604
+ {
1605
+ "name": "start_time",
1606
+ "type": "datetime",
1607
+ "hint": "Make sure this time exactly matches the corresponding ticket SKU start times"
1608
+ },
1609
+ {
1610
+ "name": "end_time",
1611
+ "type": "datetime"
1612
+ }
1613
+ ]
1531
1614
  }
1532
1615
  ],
1533
1616
  "label": "Search Listing Info",