@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,86 @@
|
|
|
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 { ResolvedPublisher } from './publisher-profile';
|
|
18
|
+
/** A ready-to-send refusal: the status and the body, decided together. */
|
|
19
|
+
export interface PublishRefusal {
|
|
20
|
+
status: number;
|
|
21
|
+
body: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* EVERY precondition a marketplace publish shares, in one place (AGL-2282).
|
|
25
|
+
*
|
|
26
|
+
* THE DEFECT THIS CLOSES. The publisher agreement (AGL-1077) is the document
|
|
27
|
+
* that says what Aglyn may do with a listing, what the publisher warrants,
|
|
28
|
+
* who indemnifies whom, and — §8 — how paid sales, the platform fee, sales
|
|
29
|
+
* tax and refund clawbacks work. Its gate was written into
|
|
30
|
+
* `publish-plugin.ts` and **nowhere else**. The other six publish doors —
|
|
31
|
+
* components (`publish.ts`), themes, layouts, site templates, email templates
|
|
32
|
+
* and dataset schemas — enforced the profile and the payout binding and then
|
|
33
|
+
* created a listing, `priceUsd` included, with no acceptance on file.
|
|
34
|
+
*
|
|
35
|
+
* So six of the seven ways to put a PAID artifact on the marketplace let an
|
|
36
|
+
* org sell through Aglyn as merchant of record while under no publisher terms
|
|
37
|
+
* at all: no warranty that they own what they are selling, no indemnity, and
|
|
38
|
+
* no consent to the §8.4 clawback the refund path relies on to take a
|
|
39
|
+
* publisher's share back out of their Connect account. Every one of those
|
|
40
|
+
* routes reads `priceUsd` from the request body and stamps it on the listing.
|
|
41
|
+
*
|
|
42
|
+
* WHY THE THREE CHECKS MOVED TOGETHER rather than a bare `requireAgreement`
|
|
43
|
+
* being sprinkled beside the existing two. A gate that must be REMEMBERED at
|
|
44
|
+
* six call sites is exactly what just failed; a gate that arrives bundled
|
|
45
|
+
* with the profile and payout refusals cannot be dropped without also
|
|
46
|
+
* dropping those, which no reviewer would miss. The next publish door gets
|
|
47
|
+
* all three by construction, which is the only version of this fix that
|
|
48
|
+
* stays fixed.
|
|
49
|
+
*
|
|
50
|
+
* WHY IT IS PURE, and lives here rather than beside `resolvePublisherProfile`.
|
|
51
|
+
* Every publish spec in this directory mocks `./publisher-profile` wholesale,
|
|
52
|
+
* and a wholesale mock is a CLOSED WORLD: a gate exported from that module
|
|
53
|
+
* would be `undefined` under those mocks, and — worse — a gate that CALLED
|
|
54
|
+
* the mocked resolver would never run the real agreement check in any of
|
|
55
|
+
* them. Taking the already-resolved profile as an argument keeps the decision
|
|
56
|
+
* live in every existing spec, and makes it directly unit-testable without a
|
|
57
|
+
* Firestore double.
|
|
58
|
+
*
|
|
59
|
+
* THE PRICE FLOOR (AGL-2343) joined the gate for the same reason: a paid
|
|
60
|
+
* listing under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
|
|
61
|
+
* marketplace checkout is a destination charge whose Stripe fee is debited
|
|
62
|
+
* from the PLATFORM's balance, and two of the seven doors validated the price
|
|
63
|
+
* not at all. It reads FIRST — see the comment on the check itself.
|
|
64
|
+
*
|
|
65
|
+
* ORDER IS DELIBERATE and matches what `publish-plugin.ts` already did:
|
|
66
|
+
* profile, then payouts, then the agreement. The first two are things a
|
|
67
|
+
* publisher sets up once; the third can go stale under a publisher who did
|
|
68
|
+
* everything right, so it reads last and says so specifically.
|
|
69
|
+
*
|
|
70
|
+
* THE AGREEMENT IS ASKED FOR FREE LISTINGS TOO, exactly as
|
|
71
|
+
* `publish-plugin.ts` asked. Sections 2 (the distribution licence), 3 (the
|
|
72
|
+
* warranties) and 9 (indemnity) are what make a free listing distributable at
|
|
73
|
+
* all; only §8 is about money. Gating on `priceUsd > 0` would put every free
|
|
74
|
+
* listing outside the terms that let us serve it.
|
|
75
|
+
*
|
|
76
|
+
* @param publisher the org's resolved marketplace profile, or null/undefined
|
|
77
|
+
* when it has none.
|
|
78
|
+
* @param options.sells the plural artifact noun for the payout refusal —
|
|
79
|
+
* "components", "themes", "layouts" — so the message keeps naming what the
|
|
80
|
+
* publisher was actually trying to sell.
|
|
81
|
+
* @returns the refusal to send, or `null` when the publish may proceed.
|
|
82
|
+
*/
|
|
83
|
+
export declare function publishPreconditionRefusal(publisher: ResolvedPublisher | null | undefined, options: {
|
|
84
|
+
priceUsd: number;
|
|
85
|
+
sells: string;
|
|
86
|
+
}): PublishRefusal | null;
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
*/ import { PUBLISHER_AGREEMENT_VERSION, publisherAgreementRefusal, publisherAgreementState } from "@aglyn/aglyn/app-utils/publisher-agreement";
|
|
17
|
+
import { connectLinkageIsReady } from "@aglyn/tenant-data-admin/server/stripe-account-mode";
|
|
18
|
+
import { marketplacePriceRefusal } from "../model/marketplace.js";
|
|
19
|
+
/**
|
|
20
|
+
* EVERY precondition a marketplace publish shares, in one place (AGL-2282).
|
|
21
|
+
*
|
|
22
|
+
* THE DEFECT THIS CLOSES. The publisher agreement (AGL-1077) is the document
|
|
23
|
+
* that says what Aglyn may do with a listing, what the publisher warrants,
|
|
24
|
+
* who indemnifies whom, and — §8 — how paid sales, the platform fee, sales
|
|
25
|
+
* tax and refund clawbacks work. Its gate was written into
|
|
26
|
+
* `publish-plugin.ts` and **nowhere else**. The other six publish doors —
|
|
27
|
+
* components (`publish.ts`), themes, layouts, site templates, email templates
|
|
28
|
+
* and dataset schemas — enforced the profile and the payout binding and then
|
|
29
|
+
* created a listing, `priceUsd` included, with no acceptance on file.
|
|
30
|
+
*
|
|
31
|
+
* So six of the seven ways to put a PAID artifact on the marketplace let an
|
|
32
|
+
* org sell through Aglyn as merchant of record while under no publisher terms
|
|
33
|
+
* at all: no warranty that they own what they are selling, no indemnity, and
|
|
34
|
+
* no consent to the §8.4 clawback the refund path relies on to take a
|
|
35
|
+
* publisher's share back out of their Connect account. Every one of those
|
|
36
|
+
* routes reads `priceUsd` from the request body and stamps it on the listing.
|
|
37
|
+
*
|
|
38
|
+
* WHY THE THREE CHECKS MOVED TOGETHER rather than a bare `requireAgreement`
|
|
39
|
+
* being sprinkled beside the existing two. A gate that must be REMEMBERED at
|
|
40
|
+
* six call sites is exactly what just failed; a gate that arrives bundled
|
|
41
|
+
* with the profile and payout refusals cannot be dropped without also
|
|
42
|
+
* dropping those, which no reviewer would miss. The next publish door gets
|
|
43
|
+
* all three by construction, which is the only version of this fix that
|
|
44
|
+
* stays fixed.
|
|
45
|
+
*
|
|
46
|
+
* WHY IT IS PURE, and lives here rather than beside `resolvePublisherProfile`.
|
|
47
|
+
* Every publish spec in this directory mocks `./publisher-profile` wholesale,
|
|
48
|
+
* and a wholesale mock is a CLOSED WORLD: a gate exported from that module
|
|
49
|
+
* would be `undefined` under those mocks, and — worse — a gate that CALLED
|
|
50
|
+
* the mocked resolver would never run the real agreement check in any of
|
|
51
|
+
* them. Taking the already-resolved profile as an argument keeps the decision
|
|
52
|
+
* live in every existing spec, and makes it directly unit-testable without a
|
|
53
|
+
* Firestore double.
|
|
54
|
+
*
|
|
55
|
+
* THE PRICE FLOOR (AGL-2343) joined the gate for the same reason: a paid
|
|
56
|
+
* listing under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
|
|
57
|
+
* marketplace checkout is a destination charge whose Stripe fee is debited
|
|
58
|
+
* from the PLATFORM's balance, and two of the seven doors validated the price
|
|
59
|
+
* not at all. It reads FIRST — see the comment on the check itself.
|
|
60
|
+
*
|
|
61
|
+
* ORDER IS DELIBERATE and matches what `publish-plugin.ts` already did:
|
|
62
|
+
* profile, then payouts, then the agreement. The first two are things a
|
|
63
|
+
* publisher sets up once; the third can go stale under a publisher who did
|
|
64
|
+
* everything right, so it reads last and says so specifically.
|
|
65
|
+
*
|
|
66
|
+
* THE AGREEMENT IS ASKED FOR FREE LISTINGS TOO, exactly as
|
|
67
|
+
* `publish-plugin.ts` asked. Sections 2 (the distribution licence), 3 (the
|
|
68
|
+
* warranties) and 9 (indemnity) are what make a free listing distributable at
|
|
69
|
+
* all; only §8 is about money. Gating on `priceUsd > 0` would put every free
|
|
70
|
+
* listing outside the terms that let us serve it.
|
|
71
|
+
*
|
|
72
|
+
* @param publisher the org's resolved marketplace profile, or null/undefined
|
|
73
|
+
* when it has none.
|
|
74
|
+
* @param options.sells the plural artifact noun for the payout refusal —
|
|
75
|
+
* "components", "themes", "layouts" — so the message keeps naming what the
|
|
76
|
+
* publisher was actually trying to sell.
|
|
77
|
+
* @returns the refusal to send, or `null` when the publish may proceed.
|
|
78
|
+
*/ export function publishPreconditionRefusal(publisher, options) {
|
|
79
|
+
// THE PRICE FLOOR (AGL-2343), first, and 400 rather than 412: it is the only
|
|
80
|
+
// refusal here that is a property of the REQUEST rather than of the org, so
|
|
81
|
+
// it is the only one the publisher can fix in the form in front of them
|
|
82
|
+
// without leaving it. Reported before the org-setup refusals so that a
|
|
83
|
+
// publisher does not go and set up payouts only to be bounced again on the
|
|
84
|
+
// price they had already typed.
|
|
85
|
+
//
|
|
86
|
+
// It rides in the shared gate for the same reason the agreement does: two of
|
|
87
|
+
// the seven doors (`publish-theme.ts`, `publish-layout.ts`) validated the
|
|
88
|
+
// price not at all, and a gate that must be REMEMBERED at seven call sites
|
|
89
|
+
// is exactly what fails. The doors also call `marketplacePriceRefusal`
|
|
90
|
+
// inline — this is the backstop that makes the eighth door safe on the day
|
|
91
|
+
// it is written.
|
|
92
|
+
const priceRefusal = marketplacePriceRefusal(options.priceUsd);
|
|
93
|
+
if (priceRefusal) {
|
|
94
|
+
return {
|
|
95
|
+
status: 400,
|
|
96
|
+
body: {
|
|
97
|
+
error: priceRefusal
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!publisher) {
|
|
102
|
+
return {
|
|
103
|
+
status: 412,
|
|
104
|
+
body: {
|
|
105
|
+
error: 'Set up your publisher profile first — Marketplace → Profile.'
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Paid listings require completed Stripe Connect onboarding (AGL-46) —
|
|
110
|
+
// there is nowhere to send the seller's share otherwise.
|
|
111
|
+
//
|
|
112
|
+
// AGL-2471 folded the MODE question in here too, and not only at checkout.
|
|
113
|
+
// Publishing is where a seller is told they are set up to sell; letting a
|
|
114
|
+
// paid listing go live behind a linkage no sale will ever be allowed to use
|
|
115
|
+
// just moves the same lie one step earlier, to the point where the seller
|
|
116
|
+
// stops checking.
|
|
117
|
+
if (options.priceUsd > 0 && !connectLinkageIsReady({
|
|
118
|
+
accountId: publisher.stripeAccountId,
|
|
119
|
+
chargesEnabled: publisher.stripeChargesEnabled,
|
|
120
|
+
accountLivemode: publisher.stripeAccountLivemode
|
|
121
|
+
}, {
|
|
122
|
+
subject: `publish for org ${publisher.orgId}`
|
|
123
|
+
})) {
|
|
124
|
+
return {
|
|
125
|
+
status: 412,
|
|
126
|
+
body: {
|
|
127
|
+
error: `Set up payouts first — Marketplace → Payouts — to sell ${options.sells}.`
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// The publisher agreement (AGL-1077). A precondition on the ORG, checked
|
|
132
|
+
// here beside the profile and payout preconditions it belongs with —
|
|
133
|
+
// deliberately NOT the 428 the attestation uses. "You did not confirm the
|
|
134
|
+
// checklist for this bundle" and "your organization has never agreed to our
|
|
135
|
+
// terms" are different problems, fixed in different places, and a shared
|
|
136
|
+
// status code would send a publisher back to the checklist to tick
|
|
137
|
+
// something that is already ticked.
|
|
138
|
+
//
|
|
139
|
+
// An acceptance of an older version does not count: whoever publishes next
|
|
140
|
+
// reads the changed agreement and accepts it, or does not publish.
|
|
141
|
+
const agreementState = publisherAgreementState(publisher.agreement);
|
|
142
|
+
if (agreementState !== 'current') {
|
|
143
|
+
var _ref;
|
|
144
|
+
var _publisher_agreement;
|
|
145
|
+
return {
|
|
146
|
+
status: 412,
|
|
147
|
+
body: {
|
|
148
|
+
error: publisherAgreementRefusal(agreementState),
|
|
149
|
+
agreement: {
|
|
150
|
+
required: PUBLISHER_AGREEMENT_VERSION,
|
|
151
|
+
accepted: (_ref = (_publisher_agreement = publisher.agreement) == null ? void 0 : _publisher_agreement.version) != null ? _ref : null,
|
|
152
|
+
state: agreementState
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=publish-preconditions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-preconditions.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 PUBLISHER_AGREEMENT_VERSION,\n publisherAgreementRefusal,\n publisherAgreementState,\n} from '@aglyn/aglyn/app-utils/publisher-agreement'\nimport { connectLinkageIsReady } from '@aglyn/tenant-data-admin/server/stripe-account-mode'\nimport { marketplacePriceRefusal } from '../model/marketplace'\nimport type { ResolvedPublisher } from './publisher-profile'\n\n/** A ready-to-send refusal: the status and the body, decided together. */\nexport interface PublishRefusal {\n status: number\n body: Record<string, unknown>\n}\n\n/**\n * EVERY precondition a marketplace publish shares, in one place (AGL-2282).\n *\n * THE DEFECT THIS CLOSES. The publisher agreement (AGL-1077) is the document\n * that says what Aglyn may do with a listing, what the publisher warrants,\n * who indemnifies whom, and — §8 — how paid sales, the platform fee, sales\n * tax and refund clawbacks work. Its gate was written into\n * `publish-plugin.ts` and **nowhere else**. The other six publish doors —\n * components (`publish.ts`), themes, layouts, site templates, email templates\n * and dataset schemas — enforced the profile and the payout binding and then\n * created a listing, `priceUsd` included, with no acceptance on file.\n *\n * So six of the seven ways to put a PAID artifact on the marketplace let an\n * org sell through Aglyn as merchant of record while under no publisher terms\n * at all: no warranty that they own what they are selling, no indemnity, and\n * no consent to the §8.4 clawback the refund path relies on to take a\n * publisher's share back out of their Connect account. Every one of those\n * routes reads `priceUsd` from the request body and stamps it on the listing.\n *\n * WHY THE THREE CHECKS MOVED TOGETHER rather than a bare `requireAgreement`\n * being sprinkled beside the existing two. A gate that must be REMEMBERED at\n * six call sites is exactly what just failed; a gate that arrives bundled\n * with the profile and payout refusals cannot be dropped without also\n * dropping those, which no reviewer would miss. The next publish door gets\n * all three by construction, which is the only version of this fix that\n * stays fixed.\n *\n * WHY IT IS PURE, and lives here rather than beside `resolvePublisherProfile`.\n * Every publish spec in this directory mocks `./publisher-profile` wholesale,\n * and a wholesale mock is a CLOSED WORLD: a gate exported from that module\n * would be `undefined` under those mocks, and — worse — a gate that CALLED\n * the mocked resolver would never run the real agreement check in any of\n * them. Taking the already-resolved profile as an argument keeps the decision\n * live in every existing spec, and makes it directly unit-testable without a\n * Firestore double.\n *\n * THE PRICE FLOOR (AGL-2343) joined the gate for the same reason: a paid\n * listing 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, and two of the seven doors validated the price\n * not at all. It reads FIRST — see the comment on the check itself.\n *\n * ORDER IS DELIBERATE and matches what `publish-plugin.ts` already did:\n * profile, then payouts, then the agreement. The first two are things a\n * publisher sets up once; the third can go stale under a publisher who did\n * everything right, so it reads last and says so specifically.\n *\n * THE AGREEMENT IS ASKED FOR FREE LISTINGS TOO, exactly as\n * `publish-plugin.ts` asked. Sections 2 (the distribution licence), 3 (the\n * warranties) and 9 (indemnity) are what make a free listing distributable at\n * all; only §8 is about money. Gating on `priceUsd > 0` would put every free\n * listing outside the terms that let us serve it.\n *\n * @param publisher the org's resolved marketplace profile, or null/undefined\n * when it has none.\n * @param options.sells the plural artifact noun for the payout refusal —\n * \"components\", \"themes\", \"layouts\" — so the message keeps naming what the\n * publisher was actually trying to sell.\n * @returns the refusal to send, or `null` when the publish may proceed.\n */\nexport function publishPreconditionRefusal(\n publisher: ResolvedPublisher | null | undefined,\n options: { priceUsd: number; sells: string },\n): PublishRefusal | null {\n // THE PRICE FLOOR (AGL-2343), first, and 400 rather than 412: it is the only\n // refusal here that is a property of the REQUEST rather than of the org, so\n // it is the only one the publisher can fix in the form in front of them\n // without leaving it. Reported before the org-setup refusals so that a\n // publisher does not go and set up payouts only to be bounced again on the\n // price they had already typed.\n //\n // It rides in the shared gate for the same reason the agreement does: two of\n // the seven doors (`publish-theme.ts`, `publish-layout.ts`) validated the\n // price not at all, and a gate that must be REMEMBERED at seven call sites\n // is exactly what fails. The doors also call `marketplacePriceRefusal`\n // inline — this is the backstop that makes the eighth door safe on the day\n // it is written.\n const priceRefusal = marketplacePriceRefusal(options.priceUsd)\n if (priceRefusal) {\n return { status: 400, body: { error: priceRefusal } }\n }\n if (!publisher) {\n return {\n status: 412,\n body: {\n error: 'Set up your publisher profile first — Marketplace → Profile.',\n },\n }\n }\n // Paid listings require completed Stripe Connect onboarding (AGL-46) —\n // there is nowhere to send the seller's share otherwise.\n //\n // AGL-2471 folded the MODE question in here too, and not only at checkout.\n // Publishing is where a seller is told they are set up to sell; letting a\n // paid listing go live behind a linkage no sale will ever be allowed to use\n // just moves the same lie one step earlier, to the point where the seller\n // stops checking.\n if (\n options.priceUsd > 0 &&\n !connectLinkageIsReady(\n {\n accountId: publisher.stripeAccountId,\n chargesEnabled: publisher.stripeChargesEnabled,\n accountLivemode: publisher.stripeAccountLivemode,\n },\n { subject: `publish for org ${publisher.orgId}` },\n )\n ) {\n return {\n status: 412,\n body: {\n error: `Set up payouts first — Marketplace → Payouts — to sell ${options.sells}.`,\n },\n }\n }\n // The publisher agreement (AGL-1077). A precondition on the ORG, checked\n // here beside the profile and payout preconditions it belongs with —\n // deliberately NOT the 428 the attestation uses. \"You did not confirm the\n // checklist for this bundle\" and \"your organization has never agreed to our\n // terms\" are different problems, fixed in different places, and a shared\n // status code would send a publisher back to the checklist to tick\n // something that is already ticked.\n //\n // An acceptance of an older version does not count: whoever publishes next\n // reads the changed agreement and accepts it, or does not publish.\n const agreementState = publisherAgreementState(publisher.agreement)\n if (agreementState !== 'current') {\n return {\n status: 412,\n body: {\n error: publisherAgreementRefusal(agreementState),\n agreement: {\n required: PUBLISHER_AGREEMENT_VERSION,\n accepted: publisher.agreement?.version ?? null,\n state: agreementState,\n },\n },\n }\n }\n return null\n}\n"],"names":["PUBLISHER_AGREEMENT_VERSION","publisherAgreementRefusal","publisherAgreementState","connectLinkageIsReady","marketplacePriceRefusal","publishPreconditionRefusal","publisher","options","priceRefusal","priceUsd","status","body","error","accountId","stripeAccountId","chargesEnabled","stripeChargesEnabled","accountLivemode","stripeAccountLivemode","subject","orgId","sells","agreementState","agreement","required","accepted","version","state"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,yBAAyB,EACzBC,uBAAuB,QAClB,6CAA4C;AACnD,SAASC,qBAAqB,QAAQ,sDAAqD;AAC3F,SAASC,uBAAuB,QAAQ,0BAAsB;AAS9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DC,GACD,OAAO,SAASC,2BACdC,SAA+C,EAC/CC,OAA4C;IAE5C,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,uEAAuE;IACvE,2EAA2E;IAC3E,gCAAgC;IAChC,EAAE;IACF,6EAA6E;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,iBAAiB;IACjB,MAAMC,eAAeJ,wBAAwBG,QAAQE,QAAQ;IAC7D,IAAID,cAAc;QAChB,OAAO;YAAEE,QAAQ;YAAKC,MAAM;gBAAEC,OAAOJ;YAAa;QAAE;IACtD;IACA,IAAI,CAACF,WAAW;QACd,OAAO;YACLI,QAAQ;YACRC,MAAM;gBACJC,OAAO;YACT;QACF;IACF;IACA,uEAAuE;IACvE,yDAAyD;IACzD,EAAE;IACF,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,0EAA0E;IAC1E,kBAAkB;IAClB,IACEL,QAAQE,QAAQ,GAAG,KACnB,CAACN,sBACC;QACEU,WAAWP,UAAUQ,eAAe;QACpCC,gBAAgBT,UAAUU,oBAAoB;QAC9CC,iBAAiBX,UAAUY,qBAAqB;IAClD,GACA;QAAEC,SAAS,CAAC,gBAAgB,EAAEb,UAAUc,KAAK,EAAE;IAAC,IAElD;QACA,OAAO;YACLV,QAAQ;YACRC,MAAM;gBACJC,OAAO,CAAC,uDAAuD,EAAEL,QAAQc,KAAK,CAAC,CAAC,CAAC;YACnF;QACF;IACF;IACA,yEAAyE;IACzE,qEAAqE;IACrE,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,mEAAmE;IACnE,oCAAoC;IACpC,EAAE;IACF,2EAA2E;IAC3E,mEAAmE;IACnE,MAAMC,iBAAiBpB,wBAAwBI,UAAUiB,SAAS;IAClE,IAAID,mBAAmB,WAAW;;YAOhBhB;QANhB,OAAO;YACLI,QAAQ;YACRC,MAAM;gBACJC,OAAOX,0BAA0BqB;gBACjCC,WAAW;oBACTC,UAAUxB;oBACVyB,QAAQ,WAAEnB,uBAAAA,UAAUiB,SAAS,qBAAnBjB,qBAAqBoB,OAAO,mBAAI;oBAC1CC,OAAOL;gBACT;YACF;QACF;IACF;IACA,OAAO;AACT"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 as a site template (AGL-137): captures every published
|
|
20
|
+
* screen (nodes run through the same marketplace sanitizer as component
|
|
21
|
+
* publishing — the id allowlist and size caps apply per screen) plus the
|
|
22
|
+
* theme into a `marketplaceListings` doc with `kind: 'template'`. Same
|
|
23
|
+
* gates as component publishing: host admin, marketplace profile,
|
|
24
|
+
* `marketplaceSelling` for plan-gated workspaces, Stripe onboarding for paid
|
|
25
|
+
* listings. One template per source host; re-publishing bumps the version.
|
|
26
|
+
*/
|
|
27
|
+
export declare const publishTemplateHandler: PluginApiHandler;
|
|
@@ -0,0 +1,215 @@
|
|
|
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 { marketplacePriceRefusal, sanitizeMarketplaceDefinition } from "../model/index.js";
|
|
19
|
+
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
20
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
21
|
+
import { resolvePublisherProfile } from "./publisher-profile.js";
|
|
22
|
+
import { publishPreconditionRefusal } from "./publish-preconditions.js";
|
|
23
|
+
const MAX_TEMPLATE_SCREENS = 25;
|
|
24
|
+
/**
|
|
25
|
+
* Publishes a host as a site template (AGL-137): captures every published
|
|
26
|
+
* screen (nodes run through the same marketplace sanitizer as component
|
|
27
|
+
* publishing — the id allowlist and size caps apply per screen) plus the
|
|
28
|
+
* theme into a `marketplaceListings` doc with `kind: 'template'`. Same
|
|
29
|
+
* gates as component publishing: host admin, marketplace profile,
|
|
30
|
+
* `marketplaceSelling` for plan-gated workspaces, Stripe onboarding for paid
|
|
31
|
+
* listings. One template per source host; re-publishing bumps the version.
|
|
32
|
+
*/ export const publishTemplateHandler = async (req, res)=>{
|
|
33
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _req_headers_authorization;
|
|
34
|
+
var _req_body, _req_body1, _req_body2, _req_body3, _req_body4;
|
|
35
|
+
if (req.method !== 'POST') {
|
|
36
|
+
return res.status(405).json({
|
|
37
|
+
error: 'Method not allowed'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
41
|
+
const displayName = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.displayName) != null ? _ref1 : '').slice(0, 80);
|
|
42
|
+
const description = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.description) != null ? _ref2 : '').slice(0, 500);
|
|
43
|
+
const category = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.category) != null ? _ref3 : '').slice(0, 40);
|
|
44
|
+
const priceUsd = Math.round(Number((_ref4 = (_req_body4 = req.body) == null ? void 0 : _req_body4.priceUsd) != null ? _ref4 : 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 || !displayName.trim()) {
|
|
57
|
+
return res.status(400).json({
|
|
58
|
+
error: 'Missing hostId 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, _ref5, _hostSnapshot_get1, _existing_docs__get, _hostSnapshot_get2;
|
|
68
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
69
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
70
|
+
hostId
|
|
71
|
+
});
|
|
72
|
+
if (!membership.permissions.publishToMarketplace) {
|
|
73
|
+
return res.status(403).json({
|
|
74
|
+
error: 'Your organization role does not allow publishing to the marketplace'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
78
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
79
|
+
const hostSnapshot = await hostRef.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 = (_ref5 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref5 : {};
|
|
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).
|
|
105
|
+
const publisher = await resolvePublisherProfile(firestore, orgForHost.orgId);
|
|
106
|
+
// Profile, payouts AND the publisher agreement, in one gate
|
|
107
|
+
// (AGL-2282) — see `publishPreconditionRefusal`.
|
|
108
|
+
const refusal = publishPreconditionRefusal(publisher, {
|
|
109
|
+
priceUsd,
|
|
110
|
+
sells: 'templates'
|
|
111
|
+
});
|
|
112
|
+
if (refusal) return res.status(refusal.status).json(refusal.body);
|
|
113
|
+
// Capture: every screen in the routing map (published) with its
|
|
114
|
+
// published version, sanitized like any marketplace definition.
|
|
115
|
+
const routingMap = (_hostSnapshot_get1 = hostSnapshot.get('screens')) != null ? _hostSnapshot_get1 : {};
|
|
116
|
+
const screenIds = Object.keys(routingMap).slice(0, MAX_TEMPLATE_SCREENS);
|
|
117
|
+
if (!screenIds.length) {
|
|
118
|
+
return res.status(422).json({
|
|
119
|
+
error: 'Publish at least one screen before templating'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const screens = [];
|
|
123
|
+
for (const screenId of screenIds){
|
|
124
|
+
var _screen_displayName;
|
|
125
|
+
const screenSnapshot = await hostRef.collection('screens').doc(screenId).get();
|
|
126
|
+
const screen = screenSnapshot.data();
|
|
127
|
+
if (!screen || screen.deletedAt || !screen.versionId) continue;
|
|
128
|
+
const versionSnapshot = await hostRef.collection('screens').doc(screenId).collection('versions').doc(String(screen.versionId)).get();
|
|
129
|
+
// `nodes` is stored in more than one form and the besigner writes the
|
|
130
|
+
// COMPRESSED one, which the Admin SDK materialises as a Node `Buffer`
|
|
131
|
+
// (AGL-1395). Read raw, that Buffer is truthy, so the guard below let it
|
|
132
|
+
// straight through to the sanitizer — which found no `_@_` on it and
|
|
133
|
+
// told the author their screen had no root node. Every besigner-edited
|
|
134
|
+
// screen failed here, blaming content that was fine.
|
|
135
|
+
const nodes = decodeStoredNodes(versionSnapshot.get('nodes'));
|
|
136
|
+
if (!nodes) continue;
|
|
137
|
+
const sanitized = sanitizeMarketplaceDefinition({
|
|
138
|
+
rootId: CANVAS_ROOT_ELEMENT_ID,
|
|
139
|
+
nodes
|
|
140
|
+
});
|
|
141
|
+
if (sanitized.ok === false) {
|
|
142
|
+
var _screen_displayName1;
|
|
143
|
+
return res.status(422).json({
|
|
144
|
+
error: `Screen "${(_screen_displayName1 = screen.displayName) != null ? _screen_displayName1 : screenId}": ${sanitized.error}`
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
screens.push(_extends({
|
|
148
|
+
displayName: (_screen_displayName = screen.displayName) != null ? _screen_displayName : 'Screen'
|
|
149
|
+
}, screen.description && {
|
|
150
|
+
description: screen.description
|
|
151
|
+
}, screen.seo && {
|
|
152
|
+
seo: screen.seo
|
|
153
|
+
}, {
|
|
154
|
+
slug: routingMap[screenId],
|
|
155
|
+
nodes: sanitized.nodes
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
if (!screens.length) {
|
|
159
|
+
return res.status(422).json({
|
|
160
|
+
error: 'No publishable screens with versions found'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
// One template listing per source host; re-publish bumps the version.
|
|
164
|
+
const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('sourceHostId', '==', hostId).limit(1).get();
|
|
165
|
+
const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
|
|
166
|
+
const version = existing.empty ? 1 : Number((_existing_docs__get = existing.docs[0].get('latestVersion')) != null ? _existing_docs__get : 0) + 1;
|
|
167
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
168
|
+
await listingRef.set(_extends({
|
|
169
|
+
// `kind` retained alongside artifactType so listings published by
|
|
170
|
+
// an older build still resolve (AGL-654).
|
|
171
|
+
kind: 'template',
|
|
172
|
+
artifactType: 'template',
|
|
173
|
+
profileId: publisher.orgId,
|
|
174
|
+
sourceHostId: hostId,
|
|
175
|
+
displayName: displayName.trim()
|
|
176
|
+
}, description.trim() && {
|
|
177
|
+
description: description.trim()
|
|
178
|
+
}, category.trim() && {
|
|
179
|
+
category: category.trim()
|
|
180
|
+
}, {
|
|
181
|
+
priceUsd,
|
|
182
|
+
screenCount: screens.length,
|
|
183
|
+
latestVersion: version,
|
|
184
|
+
deletedAt: null
|
|
185
|
+
}, existing.empty && {
|
|
186
|
+
createdAt: now
|
|
187
|
+
}, {
|
|
188
|
+
updatedAt: now,
|
|
189
|
+
versionHistory: firebaseAdmin.firestore.FieldValue.arrayUnion({
|
|
190
|
+
version,
|
|
191
|
+
publishedAt: firebaseAdmin.firestore.Timestamp.now()
|
|
192
|
+
})
|
|
193
|
+
}), {
|
|
194
|
+
merge: true
|
|
195
|
+
});
|
|
196
|
+
await listingRef.collection('versions').doc(String(version)).set({
|
|
197
|
+
template: {
|
|
198
|
+
screens,
|
|
199
|
+
theme: (_hostSnapshot_get2 = hostSnapshot.get('theme')) != null ? _hostSnapshot_get2 : null
|
|
200
|
+
},
|
|
201
|
+
publishedAt: now
|
|
202
|
+
});
|
|
203
|
+
return res.status(200).json({
|
|
204
|
+
listingId: listingRef.id,
|
|
205
|
+
version
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(error);
|
|
209
|
+
return res.status(500).json({
|
|
210
|
+
error: 'Template publish failed'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
//# sourceMappingURL=publish-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-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 CANVAS_ROOT_ELEMENT_ID,\n checkEntitlement,\n createResourceUid,\n decodeStoredNodes,\n} from '@aglyn/aglyn/server'\nimport {\n marketplacePriceRefusal,\n sanitizeMarketplaceDefinition,\n} from '../model'\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\nconst MAX_TEMPLATE_SCREENS = 25\n\n/**\n * Publishes a host as a site template (AGL-137): captures every published\n * screen (nodes run through the same marketplace sanitizer as component\n * publishing — the id allowlist and size caps apply per screen) plus the\n * theme into a `marketplaceListings` doc with `kind: 'template'`. Same\n * gates as component publishing: host admin, marketplace profile,\n * `marketplaceSelling` for plan-gated workspaces, Stripe onboarding for paid\n * listings. One template per source host; re-publishing bumps the version.\n */\nexport const publishTemplateHandler: 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 ?? '').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 || !displayName.trim()) {\n return res.status(400).json({ error: 'Missing hostId 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 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 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 // 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 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 // Publishing is ORG-ONLY (AGL-652).\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 // Capture: every screen in the routing map (published) with its\n // published version, sanitized like any marketplace definition.\n const routingMap = (hostSnapshot.get('screens') ?? {}) as Record<\n string,\n string\n >\n const screenIds = Object.keys(routingMap).slice(0, MAX_TEMPLATE_SCREENS)\n if (!screenIds.length) {\n return res\n .status(422)\n .json({ error: 'Publish at least one screen before templating' })\n }\n const screens: any[] = []\n for (const screenId of screenIds) {\n const screenSnapshot = await hostRef\n .collection('screens')\n .doc(screenId)\n .get()\n const screen = screenSnapshot.data() as any\n if (!screen || screen.deletedAt || !screen.versionId) continue\n const versionSnapshot = await hostRef\n .collection('screens')\n .doc(screenId)\n .collection('versions')\n .doc(String(screen.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, that Buffer is truthy, so the guard below let it\n // straight through to the sanitizer — which found no `_@_` on it and\n // told the author their screen had no root node. Every besigner-edited\n // screen failed here, blaming content that was fine.\n const nodes = decodeStoredNodes(versionSnapshot.get('nodes'))\n if (!nodes) continue\n const sanitized = sanitizeMarketplaceDefinition({\n rootId: CANVAS_ROOT_ELEMENT_ID,\n nodes,\n })\n if (sanitized.ok === false) {\n return res.status(422).json({\n error: `Screen \"${screen.displayName ?? screenId}\": ${sanitized.error}`,\n })\n }\n screens.push({\n displayName: screen.displayName ?? 'Screen',\n ...(screen.description && { description: screen.description }),\n ...(screen.seo && { seo: screen.seo }),\n slug: routingMap[screenId],\n nodes: sanitized.nodes,\n })\n }\n if (!screens.length) {\n return res\n .status(422)\n .json({ error: 'No publishable screens with versions found' })\n }\n\n // One template listing per source host; re-publish bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('sourceHostId', '==', 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 // `kind` retained alongside artifactType so listings published by\n // an older build still resolve (AGL-654).\n kind: 'template',\n artifactType: 'template',\n profileId: publisher.orgId,\n sourceHostId: hostId,\n displayName: displayName.trim(),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n screenCount: screens.length,\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 template: {\n screens,\n theme: hostSnapshot.get('theme') ?? null,\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: 'Template publish failed' })\n }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","checkEntitlement","createResourceUid","decodeStoredNodes","marketplacePriceRefusal","sanitizeMarketplaceDefinition","firebaseAdmin","getOrgForHost","resolveOrgPermissions","resolvePublisherProfile","publishPreconditionRefusal","MAX_TEMPLATE_SCREENS","publishTemplateHandler","req","res","method","status","json","error","hostId","String","body","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","hostRef","collection","doc","get","exists","memberRole","orgForHost","org","orgId","publisher","refusal","sells","routingMap","screenIds","Object","keys","screens","screenId","screen","screenSnapshot","data","deletedAt","versionId","versionSnapshot","nodes","sanitized","rootId","ok","push","seo","slug","where","limit","listingRef","empty","docs","ref","version","now","FieldValue","serverTimestamp","set","kind","artifactType","profileId","sourceHostId","screenCount","latestVersion","createdAt","updatedAt","versionHistory","arrayUnion","publishedAt","Timestamp","merge","template","theme","listingId","id","console"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
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 site's theme to the marketplace (AGL-1020).
|
|
20
|
+
*
|
|
21
|
+
* A theme is the one artifact that is pure data — no nodes, no code, no
|
|
22
|
+
* bundle — so it needs no sanitizer allowlist the way a component tree does.
|
|
23
|
+
* What it needs instead is validation of the two things that make an installed
|
|
24
|
+
* theme unusable, and both are refused here rather than caught in review:
|
|
25
|
+
* a missing colour scheme, and text nobody can read. See
|
|
26
|
+
* `validateThemeForPublish` for why those two and not others.
|
|
27
|
+
*
|
|
28
|
+
* The theme is stored as a field on the host document, not as its own
|
|
29
|
+
* collection, so unlike every other publish route there is no artifact id to
|
|
30
|
+
* pick — the site IS the selection. That also makes the listing key
|
|
31
|
+
* `sourceThemeHostId`, so re-publishing the same site's theme bumps its version
|
|
32
|
+
* rather than opening a second listing.
|
|
33
|
+
*
|
|
34
|
+
* Gates mirror `publish-layout` exactly: org permission, host role,
|
|
35
|
+
* `marketplaceSelling`, a publisher profile, and payouts before anything sells.
|
|
36
|
+
*/
|
|
37
|
+
export declare const publishThemeHandler: PluginApiHandler;
|
|
38
|
+
export default publishThemeHandler;
|