@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +51 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
- package/src/lib/components/artifact-update-dialog.component.js +244 -0
- package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
- package/src/lib/components/host-plugins-card.component.d.ts +23 -0
- package/src/lib/components/host-plugins-card.component.js +566 -0
- package/src/lib/components/host-plugins-card.component.js.map +1 -0
- package/src/lib/components/listing-content.component.d.ts +57 -0
- package/src/lib/components/listing-content.component.js +1880 -0
- package/src/lib/components/listing-content.component.js.map +1 -0
- package/src/lib/components/listing-image.component.d.ts +46 -0
- package/src/lib/components/listing-image.component.js +38 -0
- package/src/lib/components/listing-image.component.js.map +1 -0
- package/src/lib/components/listing-reviews.component.d.ts +22 -0
- package/src/lib/components/listing-reviews.component.js +419 -0
- package/src/lib/components/listing-reviews.component.js.map +1 -0
- package/src/lib/components/marketplace-browse.component.d.ts +38 -0
- package/src/lib/components/marketplace-browse.component.js +802 -0
- package/src/lib/components/marketplace-browse.component.js.map +1 -0
- package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
- package/src/lib/components/plugin-site-set-panel.component.js +137 -0
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
- package/src/lib/components/plugin-site-set.component.d.ts +43 -0
- package/src/lib/components/plugin-site-set.component.js +425 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -0
- package/src/lib/components/rating-input.component.d.ts +3 -0
- package/src/lib/components/rating-input.component.js +47 -0
- package/src/lib/components/rating-input.component.js.map +1 -0
- package/src/lib/components/report-target.component.d.ts +36 -0
- package/src/lib/components/report-target.component.js +181 -0
- package/src/lib/components/report-target.component.js.map +1 -0
- package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
- package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
- package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-artifact-update.d.ts +41 -0
- package/src/lib/hooks/use-artifact-update.js +169 -0
- package/src/lib/hooks/use-artifact-update.js.map +1 -0
- package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
- package/src/lib/hooks/use-marketplace-actions.js +523 -0
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
- package/src/lib/model/artifact-merge.d.ts +154 -0
- package/src/lib/model/artifact-merge.js +228 -0
- package/src/lib/model/artifact-merge.js.map +1 -0
- package/src/lib/model/index.d.ts +43 -0
- package/src/lib/model/index.js +41 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketplace-props.d.ts +57 -0
- package/src/lib/model/marketplace-props.js +441 -0
- package/src/lib/model/marketplace-props.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +833 -0
- package/src/lib/model/marketplace.js +929 -0
- package/src/lib/model/marketplace.js.map +1 -0
- package/src/lib/model/rating-field.d.ts +24 -0
- package/src/lib/model/rating-field.js +33 -0
- package/src/lib/model/rating-field.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +25 -0
- package/src/lib/server/billing-webhook.js +1062 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/checkout.d.ts +76 -0
- package/src/lib/server/checkout.js +477 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +24 -0
- package/src/lib/server/connect.js +205 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/install-dataset-schema.d.ts +32 -0
- package/src/lib/server/install-dataset-schema.js +255 -0
- package/src/lib/server/install-dataset-schema.js.map +1 -0
- package/src/lib/server/install-email-starter.d.ts +65 -0
- package/src/lib/server/install-email-starter.js +279 -0
- package/src/lib/server/install-email-starter.js.map +1 -0
- package/src/lib/server/install-email-template.d.ts +34 -0
- package/src/lib/server/install-email-template.js +252 -0
- package/src/lib/server/install-email-template.js.map +1 -0
- package/src/lib/server/install-layout.d.ts +29 -0
- package/src/lib/server/install-layout.js +323 -0
- package/src/lib/server/install-layout.js.map +1 -0
- package/src/lib/server/install-pin-counts.d.ts +173 -0
- package/src/lib/server/install-pin-counts.js +248 -0
- package/src/lib/server/install-pin-counts.js.map +1 -0
- package/src/lib/server/install-plugin.d.ts +18 -0
- package/src/lib/server/install-plugin.js +405 -0
- package/src/lib/server/install-plugin.js.map +1 -0
- package/src/lib/server/install-template.d.ts +37 -0
- package/src/lib/server/install-template.js +348 -0
- package/src/lib/server/install-template.js.map +1 -0
- package/src/lib/server/install-theme.d.ts +42 -0
- package/src/lib/server/install-theme.js +313 -0
- package/src/lib/server/install-theme.js.map +1 -0
- package/src/lib/server/install.d.ts +27 -0
- package/src/lib/server/install.js +276 -0
- package/src/lib/server/install.js.map +1 -0
- package/src/lib/server/listing-versions.d.ts +26 -0
- package/src/lib/server/listing-versions.js +378 -0
- package/src/lib/server/listing-versions.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +37 -0
- package/src/lib/server/preview-image.js +216 -0
- package/src/lib/server/preview-image.js.map +1 -0
- package/src/lib/server/provenance.d.ts +100 -0
- package/src/lib/server/provenance.js +118 -0
- package/src/lib/server/provenance.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.d.ts +31 -0
- package/src/lib/server/publish-dataset-schema.js +202 -0
- package/src/lib/server/publish-dataset-schema.js.map +1 -0
- package/src/lib/server/publish-email-starter.d.ts +44 -0
- package/src/lib/server/publish-email-starter.js +231 -0
- package/src/lib/server/publish-email-starter.js.map +1 -0
- package/src/lib/server/publish-email-template.d.ts +33 -0
- package/src/lib/server/publish-email-template.js +211 -0
- package/src/lib/server/publish-email-template.js.map +1 -0
- package/src/lib/server/publish-layout.d.ts +33 -0
- package/src/lib/server/publish-layout.js +243 -0
- package/src/lib/server/publish-layout.js.map +1 -0
- package/src/lib/server/publish-plugin.d.ts +18 -0
- package/src/lib/server/publish-plugin.js +485 -0
- package/src/lib/server/publish-plugin.js.map +1 -0
- package/src/lib/server/publish-preconditions.d.ts +86 -0
- package/src/lib/server/publish-preconditions.js +160 -0
- package/src/lib/server/publish-preconditions.js.map +1 -0
- package/src/lib/server/publish-template.d.ts +27 -0
- package/src/lib/server/publish-template.js +215 -0
- package/src/lib/server/publish-template.js.map +1 -0
- package/src/lib/server/publish-theme.d.ts +38 -0
- package/src/lib/server/publish-theme.js +186 -0
- package/src/lib/server/publish-theme.js.map +1 -0
- package/src/lib/server/publish.d.ts +26 -0
- package/src/lib/server/publish.js +204 -0
- package/src/lib/server/publish.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +30 -0
- package/src/lib/server/publisher-profile-save.js +226 -0
- package/src/lib/server/publisher-profile-save.js.map +1 -0
- package/src/lib/server/publisher-profile.d.ts +86 -0
- package/src/lib/server/publisher-profile.js +116 -0
- package/src/lib/server/publisher-profile.js.map +1 -0
- package/src/lib/server/purchase-entitlement.d.ts +83 -0
- package/src/lib/server/purchase-entitlement.js +118 -0
- package/src/lib/server/purchase-entitlement.js.map +1 -0
- package/src/lib/server/report.d.ts +32 -0
- package/src/lib/server/report.js +101 -0
- package/src/lib/server/report.js.map +1 -0
- package/src/lib/server/reviews.d.ts +41 -0
- package/src/lib/server/reviews.js +240 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/update-artifact.d.ts +19 -0
- package/src/lib/server/update-artifact.js +569 -0
- package/src/lib/server/update-artifact.js.map +1 -0
- package/src/lib/server/verification-request.d.ts +31 -0
- package/src/lib/server/verification-request.js +173 -0
- package/src/lib/server/verification-request.js.map +1 -0
- package/src/lib/server/version-stats.d.ts +202 -0
- package/src/lib/server/version-stats.js +305 -0
- package/src/lib/server/version-stats.js.map +1 -0
- package/src/lib/server.d.ts +30 -0
- package/src/lib/server.js +95 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1,186 @@
|
|
|
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 } from "@aglyn/aglyn/server";
|
|
18
|
+
import { themeArtifactContent, validateThemeForPublish } from "@aglyn/aglyn/app-utils/marketplace-theme";
|
|
19
|
+
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
20
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
21
|
+
import { marketplacePriceRefusal } from "../model/marketplace.js";
|
|
22
|
+
import { resolvePublisherProfile } from "./publisher-profile.js";
|
|
23
|
+
import { publishPreconditionRefusal } from "./publish-preconditions.js";
|
|
24
|
+
/**
|
|
25
|
+
* Publishes a site's theme to the marketplace (AGL-1020).
|
|
26
|
+
*
|
|
27
|
+
* A theme is the one artifact that is pure data — no nodes, no code, no
|
|
28
|
+
* bundle — so it needs no sanitizer allowlist the way a component tree does.
|
|
29
|
+
* What it needs instead is validation of the two things that make an installed
|
|
30
|
+
* theme unusable, and both are refused here rather than caught in review:
|
|
31
|
+
* a missing colour scheme, and text nobody can read. See
|
|
32
|
+
* `validateThemeForPublish` for why those two and not others.
|
|
33
|
+
*
|
|
34
|
+
* The theme is stored as a field on the host document, not as its own
|
|
35
|
+
* collection, so unlike every other publish route there is no artifact id to
|
|
36
|
+
* pick — the site IS the selection. That also makes the listing key
|
|
37
|
+
* `sourceThemeHostId`, so re-publishing the same site's theme bumps its version
|
|
38
|
+
* rather than opening a second listing.
|
|
39
|
+
*
|
|
40
|
+
* Gates mirror `publish-layout` exactly: org permission, host role,
|
|
41
|
+
* `marketplaceSelling`, a publisher profile, and payouts before anything sells.
|
|
42
|
+
*/ export const publishThemeHandler = async (req, res)=>{
|
|
43
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _req_headers_authorization;
|
|
44
|
+
var _req_body, _req_body1, _req_body2, _req_body3, _req_body4;
|
|
45
|
+
if (req.method !== 'POST') {
|
|
46
|
+
return res.status(405).json({
|
|
47
|
+
error: 'Method not allowed'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
51
|
+
const displayName = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.displayName) != null ? _ref1 : '');
|
|
52
|
+
const description = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.description) != null ? _ref2 : '');
|
|
53
|
+
const category = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.category) != null ? _ref3 : '');
|
|
54
|
+
const priceUsd = Math.max(0, Math.round(Number((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.priceUsd) != null ? _ref4 : 0)));
|
|
55
|
+
if (!hostId || !displayName.trim()) {
|
|
56
|
+
return res.status(400).json({
|
|
57
|
+
error: 'Missing hostId or displayName'
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// The price floor and ceiling, from ONE validator (AGL-2343). This door had
|
|
61
|
+
// NO price validation at all: it clamped to zero and stored whatever came in,
|
|
62
|
+
// so both a $1 listing that loses money on every sale and a $250,000 one were
|
|
63
|
+
// publishable here. Also enforced in `publishPreconditionRefusal` below.
|
|
64
|
+
const priceRefusal = marketplacePriceRefusal(priceUsd);
|
|
65
|
+
if (priceRefusal) {
|
|
66
|
+
return res.status(400).json({
|
|
67
|
+
error: priceRefusal
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
71
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
72
|
+
if (!idToken) return res.status(401).json({
|
|
73
|
+
error: 'Unauthenticated'
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
var _hostSnapshot_get, _ref5, _hostSnapshot_get1, _existing_docs__get;
|
|
77
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
78
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
79
|
+
hostId
|
|
80
|
+
});
|
|
81
|
+
if (!membership.permissions.publishToMarketplace) {
|
|
82
|
+
return res.status(403).json({
|
|
83
|
+
error: 'Your organization role does not allow publishing to the marketplace'
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
87
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
88
|
+
const hostSnapshot = await hostRef.get();
|
|
89
|
+
if (!hostSnapshot.exists) {
|
|
90
|
+
return res.status(404).json({
|
|
91
|
+
error: 'Unknown site'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
|
|
95
|
+
if (memberRole !== 'admin' && memberRole !== 'editor') {
|
|
96
|
+
return res.status(403).json({
|
|
97
|
+
error: 'Not a site admin or editor'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const orgForHost = await getOrgForHost(hostId);
|
|
101
|
+
const org = (_ref5 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref5 : {};
|
|
102
|
+
if (!checkEntitlement(org, 'marketplaceSelling')) {
|
|
103
|
+
return res.status(403).json({
|
|
104
|
+
error: 'Publishing to the marketplace requires a Pro plan'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (!(orgForHost == null ? void 0 : orgForHost.orgId)) {
|
|
108
|
+
return res.status(409).json({
|
|
109
|
+
error: 'Site has no owning organization'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId);
|
|
113
|
+
// Profile, payouts AND the publisher agreement, in one gate
|
|
114
|
+
// (AGL-2282) — see `publishPreconditionRefusal`.
|
|
115
|
+
const refusal = publishPreconditionRefusal(publisher, {
|
|
116
|
+
priceUsd,
|
|
117
|
+
sells: 'themes'
|
|
118
|
+
});
|
|
119
|
+
if (refusal) return res.status(refusal.status).json(refusal.body);
|
|
120
|
+
// The SITE'S OWN theme, before any resolution against a marketplace base or
|
|
121
|
+
// the site's overrides. Publishing the resolved view would bake this site's
|
|
122
|
+
// private customisations into an artifact other people install.
|
|
123
|
+
const theme = themeArtifactContent((_hostSnapshot_get1 = hostSnapshot.get('theme')) != null ? _hostSnapshot_get1 : {});
|
|
124
|
+
const validation = validateThemeForPublish(theme);
|
|
125
|
+
if (!validation.ok) {
|
|
126
|
+
var _ref6;
|
|
127
|
+
var _validation_errors_;
|
|
128
|
+
return res.status(422).json({
|
|
129
|
+
error: (_ref6 = (_validation_errors_ = validation.errors[0]) == null ? void 0 : _validation_errors_.message) != null ? _ref6 : 'This theme is not publishable',
|
|
130
|
+
// The whole list, so the dialog can show every problem at once rather
|
|
131
|
+
// than making the publisher fix them one refusal at a time.
|
|
132
|
+
errors: validation.errors,
|
|
133
|
+
warnings: validation.warnings
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// One listing per source site's theme; re-publish bumps the version.
|
|
137
|
+
const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('sourceThemeHostId', '==', hostId).limit(1).get();
|
|
138
|
+
const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
|
|
139
|
+
const version = existing.empty ? 1 : Number((_existing_docs__get = existing.docs[0].get('latestVersion')) != null ? _existing_docs__get : 0) + 1;
|
|
140
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
141
|
+
await listingRef.set(_extends({
|
|
142
|
+
artifactType: 'theme',
|
|
143
|
+
profileId: publisher.orgId,
|
|
144
|
+
sourceHostId: hostId,
|
|
145
|
+
sourceThemeHostId: hostId,
|
|
146
|
+
displayName: displayName.trim()
|
|
147
|
+
}, description.trim() && {
|
|
148
|
+
description: description.trim()
|
|
149
|
+
}, category.trim() && {
|
|
150
|
+
category: category.trim()
|
|
151
|
+
}, {
|
|
152
|
+
priceUsd,
|
|
153
|
+
latestVersion: version,
|
|
154
|
+
deletedAt: null
|
|
155
|
+
}, existing.empty && {
|
|
156
|
+
createdAt: now
|
|
157
|
+
}, {
|
|
158
|
+
updatedAt: now,
|
|
159
|
+
versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({
|
|
160
|
+
version,
|
|
161
|
+
publishedAt: firebaseAdmin.firestore.Timestamp.now()
|
|
162
|
+
})
|
|
163
|
+
}), {
|
|
164
|
+
merge: true
|
|
165
|
+
});
|
|
166
|
+
await listingRef.collection('versions').doc(String(version)).set({
|
|
167
|
+
theme,
|
|
168
|
+
publishedAt: now
|
|
169
|
+
});
|
|
170
|
+
return res.status(200).json({
|
|
171
|
+
listingId: listingRef.id,
|
|
172
|
+
version,
|
|
173
|
+
// Warnings do not block, but publishing past one silently would make the
|
|
174
|
+
// pre-flight pointless — the dialog reports what shipped anyway.
|
|
175
|
+
warnings: validation.warnings
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(error);
|
|
179
|
+
return res.status(500).json({
|
|
180
|
+
error: 'Theme publish failed'
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
export default publishThemeHandler;
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=publish-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-theme.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 { checkEntitlement, createResourceUid } from '@aglyn/aglyn/server'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n themeArtifactContent,\n validateThemeForPublish,\n} from '@aglyn/aglyn/app-utils/marketplace-theme'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { marketplacePriceRefusal } from '../model/marketplace'\nimport { resolvePublisherProfile } from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\n\n/**\n * Publishes a site's theme to the marketplace (AGL-1020).\n *\n * A theme is the one artifact that is pure data — no nodes, no code, no\n * bundle — so it needs no sanitizer allowlist the way a component tree does.\n * What it needs instead is validation of the two things that make an installed\n * theme unusable, and both are refused here rather than caught in review:\n * a missing colour scheme, and text nobody can read. See\n * `validateThemeForPublish` for why those two and not others.\n *\n * The theme is stored as a field on the host document, not as its own\n * collection, so unlike every other publish route there is no artifact id to\n * pick — the site IS the selection. That also makes the listing key\n * `sourceThemeHostId`, so re-publishing the same site's theme bumps its version\n * rather than opening a second listing.\n *\n * Gates mirror `publish-layout` exactly: org permission, host role,\n * `marketplaceSelling`, a publisher profile, and payouts before anything sells.\n */\nexport const publishThemeHandler: 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 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 || !displayName.trim()) {\n return res.status(400).json({ error: 'Missing hostId 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: 'themes',\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n // The SITE'S OWN theme, before any resolution against a marketplace base or\n // the site's overrides. Publishing the resolved view would bake this site's\n // private customisations into an artifact other people install.\n const theme = themeArtifactContent((hostSnapshot.get('theme') ?? {}) as any)\n const validation = validateThemeForPublish(theme)\n if (!validation.ok) {\n return res.status(422).json({\n error: validation.errors[0]?.message ?? 'This theme is not publishable',\n // The whole list, so the dialog can show every problem at once rather\n // than making the publisher fix them one refusal at a time.\n errors: validation.errors,\n warnings: validation.warnings,\n })\n }\n\n // One listing per source site's theme; re-publish bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('sourceThemeHostId', '==', hostId)\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: 'theme',\n profileId: publisher.orgId,\n sourceHostId: hostId,\n sourceThemeHostId: hostId,\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 theme,\n publishedAt: now,\n })\n\n return res.status(200).json({\n listingId: listingRef.id,\n version,\n // Warnings do not block, but publishing past one silently would make the\n // pre-flight pointless — the dialog reports what shipped anyway.\n warnings: validation.warnings,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Theme publish failed' })\n }\n}\n\nexport default publishThemeHandler\n"],"names":["checkEntitlement","createResourceUid","themeArtifactContent","validateThemeForPublish","firebaseAdmin","getOrgForHost","resolveOrgPermissions","marketplacePriceRefusal","resolvePublisherProfile","publishPreconditionRefusal","publishThemeHandler","req","res","method","status","json","error","hostId","String","body","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","theme","validation","ok","errors","message","warnings","where","limit","listingRef","empty","docs","ref","version","now","FieldValue","serverTimestamp","set","artifactType","profileId","sourceHostId","sourceThemeHostId","latestVersion","deletedAt","createdAt","updatedAt","versionHistory","arrayUnion","publishedAt","Timestamp","merge","listingId","id","console"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 host reusable component to the marketplace (AGL-44). Runs
|
|
20
|
+
* server-side so the sanitization pass (component-id allowlist, key strip,
|
|
21
|
+
* size cap — see `sanitizeMarketplaceDefinition`) cannot be bypassed: clients
|
|
22
|
+
* have no write access to `marketplaceListings` creates. Requirements: host
|
|
23
|
+
* admin, a marketplace profile, and the `marketplaceSelling` plan flag.
|
|
24
|
+
* Re-publishing the same component bumps the listing's version.
|
|
25
|
+
*/
|
|
26
|
+
export declare const publishHandler: PluginApiHandler;
|
|
@@ -0,0 +1,204 @@
|
|
|
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 { marketplacePriceRefusal, sanitizeMarketplaceDefinition } from "../model/index.js";
|
|
19
|
+
import { sanitizeMarketplaceProps } from "../model/marketplace-props.js";
|
|
20
|
+
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
21
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
22
|
+
import { resolvePublisherProfile } from "./publisher-profile.js";
|
|
23
|
+
import { publishPreconditionRefusal } from "./publish-preconditions.js";
|
|
24
|
+
/**
|
|
25
|
+
* Publishes a host reusable component to the marketplace (AGL-44). Runs
|
|
26
|
+
* server-side so the sanitization pass (component-id allowlist, key strip,
|
|
27
|
+
* size cap — see `sanitizeMarketplaceDefinition`) cannot be bypassed: clients
|
|
28
|
+
* have no write access to `marketplaceListings` creates. Requirements: host
|
|
29
|
+
* admin, a marketplace profile, and the `marketplaceSelling` plan flag.
|
|
30
|
+
* Re-publishing the same component bumps the listing's version.
|
|
31
|
+
*/ export const publishHandler = async (req, res)=>{
|
|
32
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _req_headers_authorization;
|
|
33
|
+
var _req_body, _req_body1, _req_body2, _req_body3, _req_body4, _req_body5;
|
|
34
|
+
if (req.method !== 'POST') {
|
|
35
|
+
return res.status(405).json({
|
|
36
|
+
error: 'Method not allowed'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
40
|
+
const componentId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.componentId) != null ? _ref1 : '');
|
|
41
|
+
const displayName = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.displayName) != null ? _ref2 : '').slice(0, 80);
|
|
42
|
+
const description = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.description) != null ? _ref3 : '').slice(0, 500);
|
|
43
|
+
const category = String((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.category) != null ? _ref4 : '').slice(0, 40);
|
|
44
|
+
const priceUsd = Math.round(Number((_ref5 = (_req_body5 = req.body) == null ? void 0 : _req_body5.priceUsd) != null ? _ref5 : 0)) || 0;
|
|
45
|
+
// The price floor and ceiling, from ONE validator (AGL-2343): a paid listing
|
|
46
|
+
// under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
|
|
47
|
+
// marketplace checkout is a destination charge whose Stripe fee is debited
|
|
48
|
+
// from the platform's balance. Also enforced in `publishPreconditionRefusal`
|
|
49
|
+
// below, so a door that drops this line is still covered.
|
|
50
|
+
const priceRefusal = marketplacePriceRefusal(priceUsd);
|
|
51
|
+
if (priceRefusal) {
|
|
52
|
+
return res.status(400).json({
|
|
53
|
+
error: priceRefusal
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!hostId || !componentId || !displayName.trim()) {
|
|
57
|
+
return res.status(400).json({
|
|
58
|
+
error: 'Missing hostId, componentId, or displayName'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
62
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
63
|
+
if (!idToken) return res.status(401).json({
|
|
64
|
+
error: 'Unauthenticated'
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
var _hostSnapshot_get, _ref6, _decodeStoredNodes, _existing_docs__get;
|
|
68
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
69
|
+
// Org-role permission gate (AGL-238).
|
|
70
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
71
|
+
hostId
|
|
72
|
+
});
|
|
73
|
+
if (!membership.permissions.publishToMarketplace) {
|
|
74
|
+
return res.status(403).json({
|
|
75
|
+
error: 'Your organization role does not allow publishing to the marketplace'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
79
|
+
const hostSnapshot = await firestore.collection('hosts').doc(hostId).get();
|
|
80
|
+
if (!hostSnapshot.exists) {
|
|
81
|
+
return res.status(404).json({
|
|
82
|
+
error: 'Unknown site'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
|
|
86
|
+
if (memberRole !== 'admin' && memberRole !== 'editor') {
|
|
87
|
+
return res.status(403).json({
|
|
88
|
+
error: 'Not a site admin or editor'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Plan gate rides the owning org's doc (AGL-238).
|
|
92
|
+
const orgForHost = await getOrgForHost(hostId);
|
|
93
|
+
const org = (_ref6 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref6 : {};
|
|
94
|
+
if (!checkEntitlement(org, 'marketplaceSelling')) {
|
|
95
|
+
return res.status(403).json({
|
|
96
|
+
error: 'Publishing to the marketplace requires a Pro plan'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (!(orgForHost == null ? void 0 : orgForHost.orgId)) {
|
|
100
|
+
return res.status(409).json({
|
|
101
|
+
error: 'Site has no owning organization'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Publishing is ORG-ONLY (AGL-652): the publisher is the org, not
|
|
105
|
+
// whoever clicked publish. `profileId` keeps its name — it is still the
|
|
106
|
+
// publisher profile's doc id, which is now the org id.
|
|
107
|
+
const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId);
|
|
108
|
+
// Profile, payouts AND the publisher agreement, in one gate
|
|
109
|
+
// (AGL-2282) — see `publishPreconditionRefusal`.
|
|
110
|
+
const refusal = publishPreconditionRefusal(publisher, {
|
|
111
|
+
priceUsd,
|
|
112
|
+
sells: 'components'
|
|
113
|
+
});
|
|
114
|
+
if (refusal) return res.status(refusal.status).json(refusal.body);
|
|
115
|
+
const definitionSnapshot = await firestore.collection('hosts').doc(hostId).collection('components').doc(componentId).get();
|
|
116
|
+
// Decoded, like the screen and layout publish paths (AGL-1395). This is
|
|
117
|
+
// the component DOCUMENT rather than its versions subcollection, and it
|
|
118
|
+
// is now compressed like everything else (AGL-1151) — a plain map from
|
|
119
|
+
// before that comes back unchanged.
|
|
120
|
+
//
|
|
121
|
+
// `sanitizeMarketplaceDefinition` refuses an undecoded tree by name, so
|
|
122
|
+
// reading raw here is the difference between publishing a component and
|
|
123
|
+
// being told the design is stored compressed on every attempt.
|
|
124
|
+
const definition = definitionSnapshot.data();
|
|
125
|
+
if (!definition || definition.deletedAt) {
|
|
126
|
+
return res.status(404).json({
|
|
127
|
+
error: 'Unknown component'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// The properties the tree binds to (AGL-2933), read off the same document
|
|
131
|
+
// the tree is: a publish copies a version's props onto it with its nodes.
|
|
132
|
+
// A default the sanitizer refuses is cleared and its property kept — see
|
|
133
|
+
// `sanitizeMarketplaceProps`.
|
|
134
|
+
const props = sanitizeMarketplaceProps(definition.props);
|
|
135
|
+
if (props.ok === false) {
|
|
136
|
+
return res.status(422).json({
|
|
137
|
+
error: props.error
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Held to the rule with those properties beside it, so a link or image
|
|
141
|
+
// bound whole to a Link or Image property travels bound.
|
|
142
|
+
const sanitized = sanitizeMarketplaceDefinition({
|
|
143
|
+
rootId: definition.rootId,
|
|
144
|
+
nodes: (_decodeStoredNodes = decodeStoredNodes(definition.nodes)) != null ? _decodeStoredNodes : {}
|
|
145
|
+
}, {
|
|
146
|
+
declaredProps: props.props
|
|
147
|
+
});
|
|
148
|
+
if (sanitized.ok === false) {
|
|
149
|
+
return res.status(422).json({
|
|
150
|
+
error: sanitized.error
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// One listing per source component: re-publish bumps latestVersion.
|
|
154
|
+
const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('sourceComponentId', '==', componentId).limit(1).get();
|
|
155
|
+
const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
|
|
156
|
+
const version = existing.empty ? 1 : Number((_existing_docs__get = existing.docs[0].get('latestVersion')) != null ? _existing_docs__get : 0) + 1;
|
|
157
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
158
|
+
await listingRef.set(_extends({
|
|
159
|
+
profileId: publisher.orgId,
|
|
160
|
+
artifactType: 'component',
|
|
161
|
+
sourceComponentId: componentId,
|
|
162
|
+
displayName: displayName.trim()
|
|
163
|
+
}, description.trim() && {
|
|
164
|
+
description: description.trim()
|
|
165
|
+
}, category.trim() && {
|
|
166
|
+
category: category.trim()
|
|
167
|
+
}, {
|
|
168
|
+
priceUsd,
|
|
169
|
+
latestVersion: version,
|
|
170
|
+
deletedAt: null
|
|
171
|
+
}, existing.empty && {
|
|
172
|
+
createdAt: now
|
|
173
|
+
}, {
|
|
174
|
+
updatedAt: now,
|
|
175
|
+
// Version metadata for the detail page (AGL-95) — snapshots
|
|
176
|
+
// themselves stay server-only, so history rides the listing doc.
|
|
177
|
+
versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({
|
|
178
|
+
version,
|
|
179
|
+
publishedAt: firebaseAdmin.firestore.Timestamp.now()
|
|
180
|
+
})
|
|
181
|
+
}), {
|
|
182
|
+
merge: true
|
|
183
|
+
});
|
|
184
|
+
await listingRef.collection('versions').doc(String(version)).set({
|
|
185
|
+
rootId: sanitized.rootId,
|
|
186
|
+
nodes: sanitized.nodes,
|
|
187
|
+
// Always a list, even an empty one: install and update read a version
|
|
188
|
+
// with none as one published before properties were carried.
|
|
189
|
+
props: props.props,
|
|
190
|
+
publishedAt: now
|
|
191
|
+
});
|
|
192
|
+
return res.status(200).json({
|
|
193
|
+
listingId: listingRef.id,
|
|
194
|
+
version
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(error);
|
|
198
|
+
return res.status(500).json({
|
|
199
|
+
error: 'Publish failed'
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish.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 {\n marketplacePriceRefusal,\n sanitizeMarketplaceDefinition,\n} from '../model'\nimport { sanitizeMarketplaceProps } from '../model/marketplace-props'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { resolvePublisherProfile } from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\n\n/**\n * Publishes a host reusable component to the marketplace (AGL-44). Runs\n * server-side so the sanitization pass (component-id allowlist, key strip,\n * size cap — see `sanitizeMarketplaceDefinition`) cannot be bypassed: clients\n * have no write access to `marketplaceListings` creates. Requirements: host\n * admin, a marketplace profile, and the `marketplaceSelling` plan flag.\n * Re-publishing the same component bumps the listing's version.\n */\nexport const publishHandler: 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 componentId = String(req.body?.componentId ?? '')\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 || !componentId || !displayName.trim()) {\n return res\n .status(400)\n .json({ error: 'Missing hostId, componentId, or displayName' })\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 // Org-role permission gate (AGL-238).\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error: 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n const hostSnapshot = await firestore.collection('hosts').doc(hostId).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 // Plan gate rides the owning org's doc (AGL-238).\n const orgForHost = await getOrgForHost(hostId)\n const org = orgForHost?.org ?? {}\n if (!checkEntitlement(org, '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 // Publishing is ORG-ONLY (AGL-652): the publisher is the org, not\n // whoever clicked publish. `profileId` keeps its name — it is still the\n // publisher profile's doc id, which is now the org id.\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: 'components',\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const definitionSnapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('components')\n .doc(componentId)\n .get()\n // Decoded, like the screen and layout publish paths (AGL-1395). This is\n // the component DOCUMENT rather than its versions subcollection, and it\n // is now compressed like everything else (AGL-1151) — a plain map from\n // before that comes back unchanged.\n //\n // `sanitizeMarketplaceDefinition` refuses an undecoded tree by name, so\n // reading raw here is the difference between publishing a component and\n // being told the design is stored compressed on every attempt.\n const definition = definitionSnapshot.data() as any\n if (!definition || definition.deletedAt) {\n return res.status(404).json({ error: 'Unknown component' })\n }\n // The properties the tree binds to (AGL-2933), read off the same document\n // the tree is: a publish copies a version's props onto it with its nodes.\n // A default the sanitizer refuses is cleared and its property kept — see\n // `sanitizeMarketplaceProps`.\n const props = sanitizeMarketplaceProps(definition.props)\n if (props.ok === false) {\n return res.status(422).json({ error: props.error })\n }\n // Held to the rule with those properties beside it, so a link or image\n // bound whole to a Link or Image property travels bound.\n const sanitized = sanitizeMarketplaceDefinition(\n {\n rootId: definition.rootId,\n nodes: decodeStoredNodes<Record<string, any>>(definition.nodes) ?? {},\n },\n { declaredProps: props.props },\n )\n if (sanitized.ok === false) {\n return res.status(422).json({ error: sanitized.error })\n }\n\n // One listing per source component: re-publish bumps latestVersion.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('sourceComponentId', '==', componentId)\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: 'component',\n sourceComponentId: componentId,\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 // Version metadata for the detail page (AGL-95) — snapshots\n // themselves stay server-only, so history rides the listing doc.\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 rootId: sanitized.rootId,\n nodes: sanitized.nodes,\n // Always a list, even an empty one: install and update read a version\n // with none as one published before properties were carried.\n props: props.props,\n publishedAt: now,\n })\n\n return res\n .status(200)\n .json({ listingId: listingRef.id, version })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publish failed' })\n }\n}\n"],"names":["checkEntitlement","createResourceUid","decodeStoredNodes","marketplacePriceRefusal","sanitizeMarketplaceDefinition","sanitizeMarketplaceProps","firebaseAdmin","getOrgForHost","resolveOrgPermissions","resolvePublisherProfile","publishPreconditionRefusal","publishHandler","req","res","method","status","json","error","hostId","String","body","componentId","displayName","slice","description","category","priceUsd","Math","round","Number","priceRefusal","trim","authorization","headers","idToken","startsWith","length","undefined","hostSnapshot","existing","decoded","app","auth","verifyIdToken","membership","uid","permissions","publishToMarketplace","firestore","collection","doc","get","exists","memberRole","orgForHost","org","orgId","publisher","refusal","sells","definitionSnapshot","definition","data","deletedAt","props","ok","sanitized","rootId","nodes","declaredProps","where","limit","listingRef","empty","docs","ref","version","now","FieldValue","serverTimestamp","set","profileId","artifactType","sourceComponentId","latestVersion","createdAt","updatedAt","versionHistory","arrayUnion","publishedAt","Timestamp","merge","listingId","id","console"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
* Save an org's publisher profile (AGL-652).
|
|
20
|
+
*
|
|
21
|
+
* The handle has to be claimed transactionally, and a client can't do that —
|
|
22
|
+
* a read-then-write uniqueness check lets two orgs racing for the same handle
|
|
23
|
+
* both succeed, and the loser silently loses its marketplace URL. So the
|
|
24
|
+
* handle is server-owned here, exactly like the host subdomain (AGL-642), and
|
|
25
|
+
* the rules freeze it against client writes.
|
|
26
|
+
*
|
|
27
|
+
* Manager-only: the profile is the org's public identity and its payout
|
|
28
|
+
* anchor, so it follows the same gate as the profile rules.
|
|
29
|
+
*/
|
|
30
|
+
export declare const publisherProfileSaveHandler: PluginApiHandler;
|