@aglyn/plugins-marketplace 1.0.0-beta.143

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 (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,211 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { checkEntitlement, createResourceUid, decodeStoredNodes } from "@aglyn/aglyn/server";
18
+ import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
19
+ import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
20
+ import { EMAIL_NODE_ROOT_ID, getTenantEmail, isTenantEmailEditable, TENANT_EMAIL_COLLECTION } from "@aglyn/shared-util-email";
21
+ import { MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST, marketplacePriceRefusal, sanitizeMarketplaceDefinition } from "../model/index.js";
22
+ import { resolvePublisherProfile } from "./publisher-profile.js";
23
+ import { publishPreconditionRefusal } from "./publish-preconditions.js";
24
+ /**
25
+ * Publishes a site's designed transactional email to the marketplace
26
+ * (AGL-657).
27
+ *
28
+ * An email design is a node tree like any other artifact, but it does NOT live
29
+ * in the host's screen routing map — which is exactly why `publish-template`
30
+ * could never capture one (it only sees `hostSnapshot.get('screens')`). The
31
+ * tree lives at `hosts/{h}/emailTemplates/{key}/versions/{versionId}`, so this
32
+ * reads it directly.
33
+ *
34
+ * `templateKey` is a fixed catalog key ('booking-confirmed', …), not a free
35
+ * id: a published email means "a design FOR this transactional email", so the
36
+ * key travels with the listing and the installer lands on the same key.
37
+ */ export const publishEmailTemplateHandler = async (req, res)=>{
38
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _req_headers_authorization;
39
+ var _req_body, _req_body1, _req_body2, _req_body3, _req_body4, _req_body5;
40
+ if (req.method !== 'POST') {
41
+ return res.status(405).json({
42
+ error: 'Method not allowed'
43
+ });
44
+ }
45
+ const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
46
+ const templateKey = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.templateKey) != null ? _ref1 : '');
47
+ const displayName = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.displayName) != null ? _ref2 : '').slice(0, 80);
48
+ const description = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.description) != null ? _ref3 : '').slice(0, 500);
49
+ const category = String((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.category) != null ? _ref4 : '').slice(0, 40);
50
+ const priceUsd = Math.round(Number((_ref5 = (_req_body5 = req.body) == null ? void 0 : _req_body5.priceUsd) != null ? _ref5 : 0)) || 0;
51
+ // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing
52
+ // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
53
+ // marketplace checkout is a destination charge whose Stripe fee is debited
54
+ // from the platform's balance. Also enforced in `publishPreconditionRefusal`
55
+ // below, so a door that drops this line is still covered.
56
+ const priceRefusal = marketplacePriceRefusal(priceUsd);
57
+ if (priceRefusal) {
58
+ return res.status(400).json({
59
+ error: priceRefusal
60
+ });
61
+ }
62
+ if (!hostId || !templateKey || !displayName.trim()) {
63
+ return res.status(400).json({
64
+ error: 'Missing hostId, templateKey, or displayName'
65
+ });
66
+ }
67
+ const entry = getTenantEmail(templateKey);
68
+ if (!entry || !isTenantEmailEditable(entry)) {
69
+ return res.status(400).json({
70
+ error: 'Not a designable email template'
71
+ });
72
+ }
73
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
74
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
75
+ if (!idToken) return res.status(401).json({
76
+ error: 'Unauthenticated'
77
+ });
78
+ try {
79
+ var _hostSnapshot_get, _ref6, _versionSnapshot_get, _existing_docs__get, _templateSnapshot_get, _templateSnapshot_get1;
80
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
81
+ const membership = await resolveOrgPermissions(decoded.uid, {
82
+ hostId
83
+ });
84
+ if (!membership.permissions.publishToMarketplace) {
85
+ return res.status(403).json({
86
+ error: 'Your organization role does not allow publishing to the marketplace'
87
+ });
88
+ }
89
+ const firestore = firebaseAdmin.app().firestore();
90
+ const hostRef = firestore.collection('hosts').doc(hostId);
91
+ const hostSnapshot = await hostRef.get();
92
+ if (!hostSnapshot.exists) {
93
+ return res.status(404).json({
94
+ error: 'Unknown site'
95
+ });
96
+ }
97
+ const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
98
+ if (memberRole !== 'admin' && memberRole !== 'editor') {
99
+ return res.status(403).json({
100
+ error: 'Not a site admin or editor'
101
+ });
102
+ }
103
+ const orgForHost = await getOrgForHost(hostId);
104
+ if (!checkEntitlement((_ref6 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref6 : {}, 'marketplaceSelling')) {
105
+ return res.status(403).json({
106
+ error: 'Publishing to the marketplace requires a Pro plan'
107
+ });
108
+ }
109
+ if (!(orgForHost == null ? void 0 : orgForHost.orgId)) {
110
+ return res.status(409).json({
111
+ error: 'Site has no owning organization'
112
+ });
113
+ }
114
+ const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId);
115
+ // Profile, payouts AND the publisher agreement, in one gate
116
+ // (AGL-2282) — see `publishPreconditionRefusal`.
117
+ const refusal = publishPreconditionRefusal(publisher, {
118
+ priceUsd,
119
+ sells: 'templates'
120
+ });
121
+ if (refusal) return res.status(refusal.status).json(refusal.body);
122
+ const templateRef = hostRef.collection(TENANT_EMAIL_COLLECTION).doc(templateKey);
123
+ const templateSnapshot = await templateRef.get();
124
+ const activeVersionId = templateSnapshot.get('versionId');
125
+ if (!templateSnapshot.exists || !activeVersionId) {
126
+ return res.status(404).json({
127
+ error: 'This email has no published design yet — design and save it first'
128
+ });
129
+ }
130
+ const versionSnapshot = await templateRef.collection('versions').doc(String(activeVersionId)).get();
131
+ // Decoded, like the screen and layout publish paths (AGL-1395): the email
132
+ // besigner now compresses through a converter as those do, and versions
133
+ // written before it did are still plain maps, which come back unchanged.
134
+ //
135
+ // `sanitizeMarketplaceDefinition` below would refuse an undecoded tree
136
+ // rather than publish garbage, so this is the difference between the
137
+ // feature working and a publish that always says the design is invalid.
138
+ const nodes = decodeStoredNodes(versionSnapshot.get('nodes'));
139
+ if (!nodes || !Object.keys(nodes).length) {
140
+ return res.status(404).json({
141
+ error: 'This email design is empty'
142
+ });
143
+ }
144
+ // Emails are built from `plugins-email` blocks only, so the page allowlist
145
+ // is REPLACED rather than extended — see MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST
146
+ // for why `emailHtml` is not on the list.
147
+ const rootId = (_versionSnapshot_get = versionSnapshot.get('rootId')) != null ? _versionSnapshot_get : EMAIL_NODE_ROOT_ID;
148
+ const sanitized = sanitizeMarketplaceDefinition({
149
+ rootId,
150
+ nodes
151
+ }, {
152
+ componentIds: MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST
153
+ });
154
+ if (sanitized.ok === false) {
155
+ return res.status(422).json({
156
+ error: sanitized.error
157
+ });
158
+ }
159
+ // One listing per (source site, template key).
160
+ const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('sourceHostId', '==', hostId).where('emailTemplateKey', '==', templateKey).limit(1).get();
161
+ const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
162
+ const version = existing.empty ? 1 : Number((_existing_docs__get = existing.docs[0].get('latestVersion')) != null ? _existing_docs__get : 0) + 1;
163
+ const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
164
+ await listingRef.set(_extends({
165
+ profileId: publisher.orgId,
166
+ artifactType: 'emailTemplate',
167
+ sourceHostId: hostId,
168
+ emailTemplateKey: templateKey,
169
+ displayName: displayName.trim()
170
+ }, description.trim() && {
171
+ description: description.trim()
172
+ }, category.trim() && {
173
+ category: category.trim()
174
+ }, {
175
+ priceUsd,
176
+ latestVersion: version,
177
+ deletedAt: null
178
+ }, existing.empty && {
179
+ createdAt: now
180
+ }, {
181
+ updatedAt: now,
182
+ versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({
183
+ version,
184
+ publishedAt: firebaseAdmin.firestore.Timestamp.now()
185
+ })
186
+ }), {
187
+ merge: true
188
+ });
189
+ await listingRef.collection('versions').doc(String(version)).set({
190
+ rootId: sanitized.rootId,
191
+ nodes: sanitized.nodes,
192
+ emailTemplateKey: templateKey,
193
+ // The copy fields are part of the design, not the tree.
194
+ subject: String((_templateSnapshot_get = templateSnapshot.get('subject')) != null ? _templateSnapshot_get : ''),
195
+ preheader: String((_templateSnapshot_get1 = templateSnapshot.get('preheader')) != null ? _templateSnapshot_get1 : ''),
196
+ publishedAt: now
197
+ });
198
+ return res.status(200).json({
199
+ listingId: listingRef.id,
200
+ version
201
+ });
202
+ } catch (error) {
203
+ console.error(error);
204
+ return res.status(500).json({
205
+ error: 'Publish failed'
206
+ });
207
+ }
208
+ };
209
+ export default publishEmailTemplateHandler;
210
+
211
+ //# sourceMappingURL=publish-email-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-email-template.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n checkEntitlement,\n createResourceUid,\n decodeStoredNodes,\n} from '@aglyn/aglyn/server'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n EMAIL_NODE_ROOT_ID,\n getTenantEmail,\n isTenantEmailEditable,\n TENANT_EMAIL_COLLECTION,\n} from '@aglyn/shared-util-email'\nimport {\n MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST,\n marketplacePriceRefusal,\n sanitizeMarketplaceDefinition,\n} from '../model'\nimport { resolvePublisherProfile } from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\n\n/**\n * Publishes a site's designed transactional email to the marketplace\n * (AGL-657).\n *\n * An email design is a node tree like any other artifact, but it does NOT live\n * in the host's screen routing map — which is exactly why `publish-template`\n * could never capture one (it only sees `hostSnapshot.get('screens')`). The\n * tree lives at `hosts/{h}/emailTemplates/{key}/versions/{versionId}`, so this\n * reads it directly.\n *\n * `templateKey` is a fixed catalog key ('booking-confirmed', …), not a free\n * id: a published email means \"a design FOR this transactional email\", so the\n * key travels with the listing and the installer lands on the same key.\n */\nexport const publishEmailTemplateHandler: PluginApiHandler = async (\n req,\n res,\n) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const templateKey = String(req.body?.templateKey ?? '')\n const displayName = String(req.body?.displayName ?? '').slice(0, 80)\n const description = String(req.body?.description ?? '').slice(0, 500)\n const category = String(req.body?.category ?? '').slice(0, 40)\n const priceUsd = Math.round(Number(req.body?.priceUsd ?? 0)) || 0\n // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing\n // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because\n // marketplace checkout is a destination charge whose Stripe fee is debited\n // from the platform's balance. Also enforced in `publishPreconditionRefusal`\n // below, so a door that drops this line is still covered.\n const priceRefusal = marketplacePriceRefusal(priceUsd)\n if (priceRefusal) {\n return res.status(400).json({ error: priceRefusal })\n }\n if (!hostId || !templateKey || !displayName.trim()) {\n return res\n .status(400)\n .json({ error: 'Missing hostId, templateKey, or displayName' })\n }\n const entry = getTenantEmail(templateKey)\n if (!entry || !isTenantEmailEditable(entry)) {\n return res.status(400).json({ error: 'Not a designable email template' })\n }\n\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n\n const orgForHost = await getOrgForHost(hostId)\n if (!checkEntitlement(orgForHost?.org ?? {}, 'marketplaceSelling')) {\n return res\n .status(403)\n .json({ error: 'Publishing to the marketplace requires a Pro plan' })\n }\n if (!orgForHost?.orgId) {\n return res.status(409).json({ error: 'Site has no owning organization' })\n }\n\n const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId)\n // Profile, payouts AND the publisher agreement, in one gate\n // (AGL-2282) — see `publishPreconditionRefusal`.\n const refusal = publishPreconditionRefusal(publisher, {\n priceUsd,\n sells: 'templates',\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const templateRef = hostRef\n .collection(TENANT_EMAIL_COLLECTION)\n .doc(templateKey)\n const templateSnapshot = await templateRef.get()\n const activeVersionId = templateSnapshot.get('versionId')\n if (!templateSnapshot.exists || !activeVersionId) {\n return res.status(404).json({\n error: 'This email has no published design yet — design and save it first',\n })\n }\n const versionSnapshot = await templateRef\n .collection('versions')\n .doc(String(activeVersionId))\n .get()\n // Decoded, like the screen and layout publish paths (AGL-1395): the email\n // besigner now compresses through a converter as those do, and versions\n // written before it did are still plain maps, which come back unchanged.\n //\n // `sanitizeMarketplaceDefinition` below would refuse an undecoded tree\n // rather than publish garbage, so this is the difference between the\n // feature working and a publish that always says the design is invalid.\n const nodes = decodeStoredNodes<Record<string, any>>(\n versionSnapshot.get('nodes'),\n )\n if (!nodes || !Object.keys(nodes).length) {\n return res.status(404).json({ error: 'This email design is empty' })\n }\n // Emails are built from `plugins-email` blocks only, so the page allowlist\n // is REPLACED rather than extended — see MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST\n // for why `emailHtml` is not on the list.\n const rootId = versionSnapshot.get('rootId') ?? EMAIL_NODE_ROOT_ID\n const sanitized = sanitizeMarketplaceDefinition(\n { rootId, nodes },\n { componentIds: MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST },\n )\n if (sanitized.ok === false) {\n return res.status(422).json({ error: sanitized.error })\n }\n\n // One listing per (source site, template key).\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('sourceHostId', '==', hostId)\n .where('emailTemplateKey', '==', templateKey)\n .limit(1)\n .get()\n const listingRef = existing.empty\n ? firestore.collection('marketplaceListings').doc(createResourceUid())\n : existing.docs[0].ref\n const version = existing.empty\n ? 1\n : Number(existing.docs[0].get('latestVersion') ?? 0) + 1\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n\n await listingRef.set(\n {\n profileId: publisher.orgId,\n artifactType: 'emailTemplate',\n sourceHostId: hostId,\n emailTemplateKey: templateKey,\n displayName: displayName.trim(),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n latestVersion: version,\n deletedAt: null,\n ...(existing.empty && { createdAt: now }),\n updatedAt: now,\n versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({\n version,\n publishedAt: firebaseAdmin.firestore.Timestamp.now(),\n }),\n },\n { merge: true },\n )\n await listingRef\n .collection('versions')\n .doc(String(version))\n .set({\n rootId: sanitized.rootId,\n nodes: sanitized.nodes,\n emailTemplateKey: templateKey,\n // The copy fields are part of the design, not the tree.\n subject: String(templateSnapshot.get('subject') ?? ''),\n preheader: String(templateSnapshot.get('preheader') ?? ''),\n publishedAt: now,\n })\n\n return res.status(200).json({ listingId: listingRef.id, version })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publish failed' })\n }\n}\n\nexport default publishEmailTemplateHandler\n"],"names":["checkEntitlement","createResourceUid","decodeStoredNodes","firebaseAdmin","getOrgForHost","resolveOrgPermissions","EMAIL_NODE_ROOT_ID","getTenantEmail","isTenantEmailEditable","TENANT_EMAIL_COLLECTION","MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST","marketplacePriceRefusal","sanitizeMarketplaceDefinition","resolvePublisherProfile","publishPreconditionRefusal","publishEmailTemplateHandler","req","res","method","status","json","error","hostId","String","body","templateKey","displayName","slice","description","category","priceUsd","Math","round","Number","priceRefusal","trim","entry","authorization","headers","idToken","startsWith","length","undefined","hostSnapshot","versionSnapshot","existing","templateSnapshot","decoded","app","auth","verifyIdToken","membership","uid","permissions","publishToMarketplace","firestore","hostRef","collection","doc","get","exists","memberRole","orgForHost","org","orgId","publisher","refusal","sells","templateRef","activeVersionId","nodes","Object","keys","rootId","sanitized","componentIds","ok","where","limit","listingRef","empty","docs","ref","version","now","FieldValue","serverTimestamp","set","profileId","artifactType","sourceHostId","emailTemplateKey","latestVersion","deletedAt","createdAt","updatedAt","versionHistory","arrayUnion","publishedAt","Timestamp","merge","subject","preheader","listingId","id","console"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
18
+ /**
19
+ * Publishes a shared layout to the marketplace (AGL-671).
20
+ *
21
+ * Layouts were the one design asset that could not be shared at all:
22
+ * `publish-template` captures screens and theme only, and `layoutSlot` is
23
+ * excluded from the marketplace allowlist as "layout chrome". Both are
24
+ * addressed here — the slot is permitted for this artifact type
25
+ * specifically, since a layout without one is chrome with nowhere to put
26
+ * the page.
27
+ *
28
+ * Gates mirror template publishing exactly: org permission, host role,
29
+ * `marketplaceSelling`, an org publisher profile, and payouts configured
30
+ * before anything can be sold.
31
+ */
32
+ export declare const publishLayoutHandler: PluginApiHandler;
33
+ export default publishLayoutHandler;
@@ -0,0 +1,243 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { CANVAS_ROOT_ELEMENT_ID, checkEntitlement, createResourceUid, decodeStoredNodes } from "@aglyn/aglyn/server";
18
+ import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
19
+ import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
20
+ import { marketplacePriceRefusal, sanitizeMarketplaceDefinition } from "../model/marketplace.js";
21
+ import { sanitizeMarketplaceProps } from "../model/marketplace-props.js";
22
+ import { resolvePublisherProfile } from "./publisher-profile.js";
23
+ import { publishPreconditionRefusal } from "./publish-preconditions.js";
24
+ /** The node marking where a bound screen's content grafts in. */ const LAYOUT_SLOT_COMPONENT_ID = 'layoutSlot';
25
+ /**
26
+ * Publishes a shared layout to the marketplace (AGL-671).
27
+ *
28
+ * Layouts were the one design asset that could not be shared at all:
29
+ * `publish-template` captures screens and theme only, and `layoutSlot` is
30
+ * excluded from the marketplace allowlist as "layout chrome". Both are
31
+ * addressed here — the slot is permitted for this artifact type
32
+ * specifically, since a layout without one is chrome with nowhere to put
33
+ * the page.
34
+ *
35
+ * Gates mirror template publishing exactly: org permission, host role,
36
+ * `marketplaceSelling`, an org publisher profile, and payouts configured
37
+ * before anything can be sold.
38
+ */ export const publishLayoutHandler = async (req, res)=>{
39
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _req_headers_authorization;
40
+ var _req_body, _req_body1, _req_body2, _req_body3, _req_body4, _req_body5;
41
+ if (req.method !== 'POST') {
42
+ return res.status(405).json({
43
+ error: 'Method not allowed'
44
+ });
45
+ }
46
+ const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
47
+ const layoutId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.layoutId) != null ? _ref1 : '');
48
+ const displayName = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.displayName) != null ? _ref2 : '');
49
+ const description = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.description) != null ? _ref3 : '');
50
+ const category = String((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.category) != null ? _ref4 : '');
51
+ const priceUsd = Math.max(0, Math.round(Number((_ref5 = (_req_body5 = req.body) == null ? void 0 : _req_body5.priceUsd) != null ? _ref5 : 0)));
52
+ if (!hostId || !layoutId || !displayName.trim()) {
53
+ return res.status(400).json({
54
+ error: 'Missing hostId, layoutId or displayName'
55
+ });
56
+ }
57
+ // The price floor and ceiling, from ONE validator (AGL-2343). This door had
58
+ // NO price validation at all: it clamped to zero and stored whatever came in,
59
+ // so both a $1 listing that loses money on every sale and a $250,000 one were
60
+ // publishable here. Also enforced in `publishPreconditionRefusal` below.
61
+ const priceRefusal = marketplacePriceRefusal(priceUsd);
62
+ if (priceRefusal) {
63
+ return res.status(400).json({
64
+ error: priceRefusal
65
+ });
66
+ }
67
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
68
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
69
+ if (!idToken) return res.status(401).json({
70
+ error: 'Unauthenticated'
71
+ });
72
+ try {
73
+ var _hostSnapshot_get, _ref6, _existing_docs__get;
74
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
75
+ const membership = await resolveOrgPermissions(decoded.uid, {
76
+ hostId
77
+ });
78
+ if (!membership.permissions.publishToMarketplace) {
79
+ return res.status(403).json({
80
+ error: 'Your organization role does not allow publishing to the marketplace'
81
+ });
82
+ }
83
+ const firestore = firebaseAdmin.app().firestore();
84
+ const hostRef = firestore.collection('hosts').doc(hostId);
85
+ const hostSnapshot = await hostRef.get();
86
+ if (!hostSnapshot.exists) {
87
+ return res.status(404).json({
88
+ error: 'Unknown site'
89
+ });
90
+ }
91
+ const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
92
+ if (memberRole !== 'admin' && memberRole !== 'editor') {
93
+ return res.status(403).json({
94
+ error: 'Not a site admin or editor'
95
+ });
96
+ }
97
+ const orgForHost = await getOrgForHost(hostId);
98
+ const org = (_ref6 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref6 : {};
99
+ if (!checkEntitlement(org, 'marketplaceSelling')) {
100
+ return res.status(403).json({
101
+ error: 'Publishing to the marketplace requires a Pro plan'
102
+ });
103
+ }
104
+ if (!(orgForHost == null ? void 0 : orgForHost.orgId)) {
105
+ return res.status(409).json({
106
+ error: 'Site has no owning organization'
107
+ });
108
+ }
109
+ const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId);
110
+ // Profile, payouts AND the publisher agreement, in one gate
111
+ // (AGL-2282) — see `publishPreconditionRefusal`.
112
+ const refusal = publishPreconditionRefusal(publisher, {
113
+ priceUsd,
114
+ sells: 'layouts'
115
+ });
116
+ if (refusal) return res.status(refusal.status).json(refusal.body);
117
+ const layoutRef = hostRef.collection('layouts').doc(layoutId);
118
+ const layoutSnapshot = await layoutRef.get();
119
+ if (!layoutSnapshot.exists) {
120
+ return res.status(404).json({
121
+ error: 'Unknown layout'
122
+ });
123
+ }
124
+ const versionId = layoutSnapshot.get('versionId');
125
+ if (!versionId) {
126
+ return res.status(422).json({
127
+ error: 'Publish this layout on your site first — there is no ' + 'published version to share'
128
+ });
129
+ }
130
+ const versionSnapshot = await layoutRef.collection('versions').doc(String(versionId)).get();
131
+ // `nodes` is stored in more than one form and the besigner writes the
132
+ // COMPRESSED one, which the Admin SDK materialises as a Node `Buffer`
133
+ // (AGL-1395). Read raw, this was worse than the template path: the length
134
+ // check passed on the BYTE COUNT, and the root search below walked byte
135
+ // INDICES — byte 0 is a number, a number has no `parentId`, so `rootId`
136
+ // resolved to `'0'` and the sanitizer built a wrapper around nothing.
137
+ // Every besigner-edited layout was refused as empty or slotless.
138
+ const nodes = decodeStoredNodes(versionSnapshot.get('nodes'));
139
+ if (!nodes || !Object.keys(nodes).length) {
140
+ return res.status(422).json({
141
+ error: 'Layout version has no content'
142
+ });
143
+ }
144
+ // The layout properties its tree binds to (AGL-2933), from the same
145
+ // version. A default the sanitizer refuses is cleared and its property
146
+ // kept — see `sanitizeMarketplaceProps`.
147
+ const props = sanitizeMarketplaceProps(versionSnapshot.get('props'));
148
+ if (props.ok === false) {
149
+ return res.status(422).json({
150
+ error: props.error
151
+ });
152
+ }
153
+ // Layout versions have no explicit rootId. The besigner canvas roots them
154
+ // at the same wrapper screens use, so prefer it outright; the parentless
155
+ // search stays as the fallback for anything authored before that, and
156
+ // matches how the renderer walks them. Preferring the wrapper also means a
157
+ // top-level node that relies on child arrays alone — legal, see
158
+ // `compose-layout-nodes` — can no longer win the search over the real root.
159
+ const rootId = nodes[CANVAS_ROOT_ELEMENT_ID] && CANVAS_ROOT_ELEMENT_ID || Object.keys(nodes).find((id)=>{
160
+ var _nodes_id;
161
+ return !((_nodes_id = nodes[id]) == null ? void 0 : _nodes_id.parentId);
162
+ }) || Object.keys(nodes)[0] || '';
163
+ const sanitized = sanitizeMarketplaceDefinition({
164
+ rootId,
165
+ nodes
166
+ }, {
167
+ extraComponentIds: [
168
+ LAYOUT_SLOT_COMPONENT_ID
169
+ ],
170
+ // A link or image bound whole to a Link or Image property travels
171
+ // bound (AGL-2933).
172
+ declaredProps: props.props
173
+ });
174
+ // `=== false` rather than `!sanitized.ok`: the compiler does not narrow
175
+ // the union on the negated form here, and publish-template hit the same
176
+ // thing first.
177
+ if (sanitized.ok === false) {
178
+ return res.status(400).json({
179
+ error: sanitized.error
180
+ });
181
+ }
182
+ // A layout whose slot did not survive would render as chrome with the
183
+ // page content missing — better to refuse than to publish that.
184
+ const hasSlot = Object.values(sanitized.nodes).some((node)=>node.componentId === LAYOUT_SLOT_COMPONENT_ID);
185
+ if (!hasSlot) {
186
+ return res.status(422).json({
187
+ error: 'This layout has no content slot, so pages using it would have ' + 'nowhere to render'
188
+ });
189
+ }
190
+ // One listing per source layout; re-publish bumps the version.
191
+ const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('sourceLayoutId', '==', layoutId).limit(1).get();
192
+ const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
193
+ const version = existing.empty ? 1 : Number((_existing_docs__get = existing.docs[0].get('latestVersion')) != null ? _existing_docs__get : 0) + 1;
194
+ const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
195
+ await listingRef.set(_extends({
196
+ artifactType: 'layout',
197
+ profileId: publisher.orgId,
198
+ sourceHostId: hostId,
199
+ sourceLayoutId: layoutId,
200
+ displayName: displayName.trim()
201
+ }, description.trim() && {
202
+ description: description.trim()
203
+ }, category.trim() && {
204
+ category: category.trim()
205
+ }, {
206
+ priceUsd,
207
+ latestVersion: version,
208
+ deletedAt: null
209
+ }, existing.empty && {
210
+ createdAt: now
211
+ }, {
212
+ updatedAt: now,
213
+ versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({
214
+ version,
215
+ publishedAt: firebaseAdmin.firestore.Timestamp.now()
216
+ })
217
+ }), {
218
+ merge: true
219
+ });
220
+ await listingRef.collection('versions').doc(String(version)).set({
221
+ // `props` is always a list, even an empty one: install and update read a
222
+ // version with none as one published before properties were carried.
223
+ layout: {
224
+ rootId: sanitized.rootId,
225
+ nodes: sanitized.nodes,
226
+ props: props.props
227
+ },
228
+ publishedAt: now
229
+ });
230
+ return res.status(200).json({
231
+ listingId: listingRef.id,
232
+ version
233
+ });
234
+ } catch (error) {
235
+ console.error(error);
236
+ return res.status(500).json({
237
+ error: 'Layout publish failed'
238
+ });
239
+ }
240
+ };
241
+ export default publishLayoutHandler;
242
+
243
+ //# sourceMappingURL=publish-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-layout.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CANVAS_ROOT_ELEMENT_ID,\n checkEntitlement,\n createResourceUid,\n decodeStoredNodes,\n} from '@aglyn/aglyn/server'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n marketplacePriceRefusal,\n sanitizeMarketplaceDefinition,\n} from '../model/marketplace'\nimport { sanitizeMarketplaceProps } from '../model/marketplace-props'\nimport { resolvePublisherProfile } from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\n\n/** The node marking where a bound screen's content grafts in. */\nconst LAYOUT_SLOT_COMPONENT_ID = 'layoutSlot'\n\n/**\n * Publishes a shared layout to the marketplace (AGL-671).\n *\n * Layouts were the one design asset that could not be shared at all:\n * `publish-template` captures screens and theme only, and `layoutSlot` is\n * excluded from the marketplace allowlist as \"layout chrome\". Both are\n * addressed here — the slot is permitted for this artifact type\n * specifically, since a layout without one is chrome with nowhere to put\n * the page.\n *\n * Gates mirror template publishing exactly: org permission, host role,\n * `marketplaceSelling`, an org publisher profile, and payouts configured\n * before anything can be sold.\n */\nexport const publishLayoutHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const layoutId = String(req.body?.layoutId ?? '')\n const displayName = String(req.body?.displayName ?? '')\n const description = String(req.body?.description ?? '')\n const category = String(req.body?.category ?? '')\n const priceUsd = Math.max(0, Math.round(Number(req.body?.priceUsd ?? 0)))\n if (!hostId || !layoutId || !displayName.trim()) {\n return res\n .status(400)\n .json({ error: 'Missing hostId, layoutId or displayName' })\n }\n // The price floor and ceiling, from ONE validator (AGL-2343). This door had\n // NO price validation at all: it clamped to zero and stored whatever came in,\n // so both a $1 listing that loses money on every sale and a $250,000 one were\n // publishable here. Also enforced in `publishPreconditionRefusal` below.\n const priceRefusal = marketplacePriceRefusal(priceUsd)\n if (priceRefusal) {\n return res.status(400).json({ error: priceRefusal })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n\n const orgForHost = await getOrgForHost(hostId)\n const org = orgForHost?.org ?? {}\n if (!checkEntitlement(org as any, 'marketplaceSelling')) {\n return res.status(403).json({\n error: 'Publishing to the marketplace requires a Pro plan',\n })\n }\n if (!orgForHost?.orgId) {\n return res.status(409).json({ error: 'Site has no owning organization' })\n }\n\n const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId)\n // Profile, payouts AND the publisher agreement, in one gate\n // (AGL-2282) — see `publishPreconditionRefusal`.\n const refusal = publishPreconditionRefusal(publisher, {\n priceUsd,\n sells: 'layouts',\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const layoutRef = hostRef.collection('layouts').doc(layoutId)\n const layoutSnapshot = await layoutRef.get()\n if (!layoutSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown layout' })\n }\n const versionId = layoutSnapshot.get('versionId')\n if (!versionId) {\n return res.status(422).json({\n error: 'Publish this layout on your site first — there is no ' +\n 'published version to share',\n })\n }\n const versionSnapshot = await layoutRef\n .collection('versions')\n .doc(String(versionId))\n .get()\n // `nodes` is stored in more than one form and the besigner writes the\n // COMPRESSED one, which the Admin SDK materialises as a Node `Buffer`\n // (AGL-1395). Read raw, this was worse than the template path: the length\n // check passed on the BYTE COUNT, and the root search below walked byte\n // INDICES — byte 0 is a number, a number has no `parentId`, so `rootId`\n // resolved to `'0'` and the sanitizer built a wrapper around nothing.\n // Every besigner-edited layout was refused as empty or slotless.\n const nodes = decodeStoredNodes<Record<string, any>>(\n versionSnapshot.get('nodes'),\n )\n if (!nodes || !Object.keys(nodes).length) {\n return res.status(422).json({ error: 'Layout version has no content' })\n }\n\n // The layout properties its tree binds to (AGL-2933), from the same\n // version. A default the sanitizer refuses is cleared and its property\n // kept — see `sanitizeMarketplaceProps`.\n const props = sanitizeMarketplaceProps(versionSnapshot.get('props'))\n if (props.ok === false) {\n return res.status(422).json({ error: props.error })\n }\n\n // Layout versions have no explicit rootId. The besigner canvas roots them\n // at the same wrapper screens use, so prefer it outright; the parentless\n // search stays as the fallback for anything authored before that, and\n // matches how the renderer walks them. Preferring the wrapper also means a\n // top-level node that relies on child arrays alone — legal, see\n // `compose-layout-nodes` — can no longer win the search over the real root.\n const rootId: string =\n (nodes[CANVAS_ROOT_ELEMENT_ID] && CANVAS_ROOT_ELEMENT_ID) ||\n Object.keys(nodes).find((id) => !nodes[id]?.parentId) ||\n Object.keys(nodes)[0] ||\n ''\n const sanitized = sanitizeMarketplaceDefinition(\n { rootId, nodes },\n {\n extraComponentIds: [LAYOUT_SLOT_COMPONENT_ID],\n // A link or image bound whole to a Link or Image property travels\n // bound (AGL-2933).\n declaredProps: props.props,\n },\n )\n // `=== false` rather than `!sanitized.ok`: the compiler does not narrow\n // the union on the negated form here, and publish-template hit the same\n // thing first.\n if (sanitized.ok === false) {\n return res.status(400).json({ error: sanitized.error })\n }\n // A layout whose slot did not survive would render as chrome with the\n // page content missing — better to refuse than to publish that.\n const hasSlot = Object.values(sanitized.nodes).some(\n (node: any) => node.componentId === LAYOUT_SLOT_COMPONENT_ID,\n )\n if (!hasSlot) {\n return res.status(422).json({\n error:\n 'This layout has no content slot, so pages using it would have ' +\n 'nowhere to render',\n })\n }\n\n // One listing per source layout; re-publish bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('sourceLayoutId', '==', layoutId)\n .limit(1)\n .get()\n const listingRef = existing.empty\n ? firestore.collection('marketplaceListings').doc(createResourceUid())\n : existing.docs[0].ref\n const version = existing.empty\n ? 1\n : Number(existing.docs[0].get('latestVersion') ?? 0) + 1\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n\n await listingRef.set(\n {\n artifactType: 'layout',\n profileId: publisher.orgId,\n sourceHostId: hostId,\n sourceLayoutId: layoutId,\n displayName: displayName.trim(),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n latestVersion: version,\n deletedAt: null,\n ...(existing.empty && { createdAt: now }),\n updatedAt: now,\n versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({\n version,\n publishedAt: firebaseAdmin.firestore.Timestamp.now(),\n }),\n },\n { merge: true },\n )\n await listingRef.collection('versions').doc(String(version)).set({\n // `props` is always a list, even an empty one: install and update read a\n // version with none as one published before properties were carried.\n layout: {\n rootId: sanitized.rootId,\n nodes: sanitized.nodes,\n props: props.props,\n },\n publishedAt: now,\n })\n\n return res.status(200).json({ listingId: listingRef.id, version })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Layout publish failed' })\n }\n}\n\nexport default publishLayoutHandler\n"],"names":["CANVAS_ROOT_ELEMENT_ID","checkEntitlement","createResourceUid","decodeStoredNodes","firebaseAdmin","getOrgForHost","resolveOrgPermissions","marketplacePriceRefusal","sanitizeMarketplaceDefinition","sanitizeMarketplaceProps","resolvePublisherProfile","publishPreconditionRefusal","LAYOUT_SLOT_COMPONENT_ID","publishLayoutHandler","req","res","method","status","json","error","hostId","String","body","layoutId","displayName","description","category","priceUsd","Math","max","round","Number","trim","priceRefusal","authorization","headers","idToken","startsWith","slice","length","undefined","hostSnapshot","existing","decoded","app","auth","verifyIdToken","membership","uid","permissions","publishToMarketplace","firestore","hostRef","collection","doc","get","exists","memberRole","orgForHost","org","orgId","publisher","refusal","sells","layoutRef","layoutSnapshot","versionId","versionSnapshot","nodes","Object","keys","props","ok","rootId","find","id","parentId","sanitized","extraComponentIds","declaredProps","hasSlot","values","some","node","componentId","where","limit","listingRef","empty","docs","ref","version","now","FieldValue","serverTimestamp","set","artifactType","profileId","sourceHostId","sourceLayoutId","latestVersion","deletedAt","createdAt","updatedAt","versionHistory","arrayUnion","publishedAt","Timestamp","merge","layout","listingId","console"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
18
+ export declare const publishPluginHandler: PluginApiHandler;