@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,313 @@
|
|
|
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 { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { describeTheme } from "@aglyn/aglyn/app-utils/marketplace-theme";
|
|
18
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
20
|
+
import { isPrivateListing, listingArtifactType } from "../model/marketplace.js";
|
|
21
|
+
import { canActAsPublisher } from "./publisher-profile.js";
|
|
22
|
+
import { requirePurchase } from "./purchase-entitlement.js";
|
|
23
|
+
import { hasDivergedFromBase, recordInstallProvenance } from "./provenance.js";
|
|
24
|
+
import { recordVersionMove } from "./version-stats.js";
|
|
25
|
+
/**
|
|
26
|
+
* Installs a marketplace theme onto a site (AGL-1020).
|
|
27
|
+
*
|
|
28
|
+
* Themes are the exception to "installing never changes a running site". A
|
|
29
|
+
* template or a layout can land inert in a library because it is one thing
|
|
30
|
+
* among many; a theme IS the site's appearance, and installing one into a
|
|
31
|
+
* library where it does nothing would be a control that appears to work and
|
|
32
|
+
* does not. So this writes `hosts/{hostId}.theme` and the site repaints.
|
|
33
|
+
*
|
|
34
|
+
* Which makes reversibility the requirement, not a nicety. Two things provide
|
|
35
|
+
* it, and neither is a confirmation dialog:
|
|
36
|
+
*
|
|
37
|
+
* * The theme being replaced is kept verbatim in `themeReplaced`, so `revert`
|
|
38
|
+
* restores exactly what was there — including a hand-built theme that was
|
|
39
|
+
* never a marketplace artifact and exists nowhere else.
|
|
40
|
+
* * `reset` clears the theme entirely, which is the "use the default theme"
|
|
41
|
+
* the issue asks to keep first-class. It is a distinct action from revert
|
|
42
|
+
* because "back to how it was" and "back to stock" are different intents and
|
|
43
|
+
* guessing between them is how people lose work.
|
|
44
|
+
*
|
|
45
|
+
* `preview` changes nothing and returns what the swap would do, so the
|
|
46
|
+
* confirmation can name it rather than showing a JSON blob.
|
|
47
|
+
*/ export const installThemeHandler = async (req, res)=>{
|
|
48
|
+
var _ref, _ref1, _req_headers_authorization;
|
|
49
|
+
var _req_body, _req_body1, _req_body2;
|
|
50
|
+
if (req.method !== 'POST') {
|
|
51
|
+
return res.status(405).json({
|
|
52
|
+
error: 'Method not allowed'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
56
|
+
const action = [
|
|
57
|
+
'revert',
|
|
58
|
+
'reset',
|
|
59
|
+
'preview',
|
|
60
|
+
'clear-overrides'
|
|
61
|
+
].includes((_req_body1 = req.body) == null ? void 0 : _req_body1.action) ? req.body.action : 'install';
|
|
62
|
+
const listingId = String((_ref1 = (_req_body2 = req.body) == null ? void 0 : _req_body2.listingId) != null ? _ref1 : '');
|
|
63
|
+
if (!hostId || action === 'install' && !listingId) {
|
|
64
|
+
return res.status(400).json({
|
|
65
|
+
error: 'Missing listingId or hostId'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
69
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
70
|
+
if (!idToken) return res.status(401).json({
|
|
71
|
+
error: 'Unauthenticated'
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
var _hostSnapshot_get, _hostSnapshot_get1, _listing_priceUsd, _membership_orgId, _hostSnapshot_get2, _listing_latestVersion;
|
|
75
|
+
var _req_body3;
|
|
76
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
77
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
78
|
+
hostId
|
|
79
|
+
});
|
|
80
|
+
if (!membership.permissions.installPlugins) {
|
|
81
|
+
return res.status(403).json({
|
|
82
|
+
error: 'Your organization role does not allow installing from the marketplace'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
86
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
87
|
+
const hostSnapshot = await hostRef.get();
|
|
88
|
+
if (!hostSnapshot.exists) {
|
|
89
|
+
return res.status(404).json({
|
|
90
|
+
error: 'Unknown site'
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
|
|
94
|
+
if (memberRole !== 'admin' && memberRole !== 'editor') {
|
|
95
|
+
return res.status(403).json({
|
|
96
|
+
error: 'Not a site admin or editor'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
100
|
+
const currentTheme = (_hostSnapshot_get1 = hostSnapshot.get('theme')) != null ? _hostSnapshot_get1 : null;
|
|
101
|
+
// ---- reset: back to the platform default ----
|
|
102
|
+
if (action === 'reset') {
|
|
103
|
+
await hostRef.set({
|
|
104
|
+
// Kept, not dropped: resetting is reversible for the same reason
|
|
105
|
+
// installing is, and someone who resets by mistake has otherwise
|
|
106
|
+
// lost a theme that existed in no other document.
|
|
107
|
+
themeReplaced: {
|
|
108
|
+
theme: currentTheme,
|
|
109
|
+
replacedAt: now
|
|
110
|
+
},
|
|
111
|
+
theme: firebaseAdmin.firestore.FieldValue.delete(),
|
|
112
|
+
themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete(),
|
|
113
|
+
themeOverride: firebaseAdmin.firestore.FieldValue.delete(),
|
|
114
|
+
updatedAt: now
|
|
115
|
+
}, {
|
|
116
|
+
merge: true
|
|
117
|
+
});
|
|
118
|
+
return res.status(200).json({
|
|
119
|
+
reset: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// ---- clear-overrides: drop this site's changes, keep the theme ----
|
|
123
|
+
// The third way back, and the narrowest: "reset to the publisher's
|
|
124
|
+
// version" is deleting the patch — the whole point of owning the patch
|
|
125
|
+
// rather than the copy (AGL-1019). The theme itself is untouched.
|
|
126
|
+
if (action === 'clear-overrides') {
|
|
127
|
+
await hostRef.set({
|
|
128
|
+
themeOverride: firebaseAdmin.firestore.FieldValue.delete(),
|
|
129
|
+
updatedAt: now
|
|
130
|
+
}, {
|
|
131
|
+
merge: true
|
|
132
|
+
});
|
|
133
|
+
return res.status(200).json({
|
|
134
|
+
cleared: true
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// ---- revert: back to whatever this site had before the last swap ----
|
|
138
|
+
if (action === 'revert') {
|
|
139
|
+
var _replaced_theme;
|
|
140
|
+
const replaced = hostSnapshot.get('themeReplaced');
|
|
141
|
+
if (!replaced) {
|
|
142
|
+
return res.status(409).json({
|
|
143
|
+
error: 'There is no previous theme to go back to on this site.'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
await hostRef.set(_extends({
|
|
147
|
+
theme: (_replaced_theme = replaced.theme) != null ? _replaced_theme : firebaseAdmin.firestore.FieldValue.delete()
|
|
148
|
+
}, replaced.installedFrom ? {
|
|
149
|
+
themeInstalledFrom: replaced.installedFrom
|
|
150
|
+
} : {
|
|
151
|
+
themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete()
|
|
152
|
+
}, replaced.override ? {
|
|
153
|
+
themeOverride: replaced.override
|
|
154
|
+
} : {
|
|
155
|
+
themeOverride: firebaseAdmin.firestore.FieldValue.delete()
|
|
156
|
+
}, {
|
|
157
|
+
themeReplaced: firebaseAdmin.firestore.FieldValue.delete(),
|
|
158
|
+
updatedAt: now
|
|
159
|
+
}), {
|
|
160
|
+
merge: true
|
|
161
|
+
});
|
|
162
|
+
return res.status(200).json({
|
|
163
|
+
reverted: true
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const listingRef = firestore.collection('marketplaceListings').doc(listingId);
|
|
167
|
+
const listingSnapshot = await listingRef.get();
|
|
168
|
+
const listing = listingSnapshot.data();
|
|
169
|
+
if (!listing || listing.deletedAt || // Staff takedown blocks new installs on EVERY artifact type
|
|
170
|
+
// (AGL-2290). AGL-948 extended takedown past plugins in the browse
|
|
171
|
+
// predicate and in `resolveMarketplacePluginVersion`, but the gate that
|
|
172
|
+
// decides whether content is HANDED OVER was only ever added to
|
|
173
|
+
// `install-plugin.ts`. So a component, theme, template, layout, email
|
|
174
|
+
// template or dataset schema that staff had taken down stayed
|
|
175
|
+
// installable by anyone holding its listing id — which makes takedown a
|
|
176
|
+
// suggestion for six of the seven artifact types.
|
|
177
|
+
//
|
|
178
|
+
// No owner exemption, matching `install-plugin.ts`: a takedown is a
|
|
179
|
+
// moderation decision about the artifact, not about who is asking.
|
|
180
|
+
listing.hiddenAt || listingArtifactType(listing) !== 'theme') {
|
|
181
|
+
return res.status(404).json({
|
|
182
|
+
error: 'Unknown theme'
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const priceUsd = Number((_listing_priceUsd = listing.priceUsd) != null ? _listing_priceUsd : 0);
|
|
186
|
+
const ownsListing = await canActAsPublisher(firestore, decoded.uid, listing.profileId);
|
|
187
|
+
// Private listings install ONLY for the owning org (AGL-2290).
|
|
188
|
+
//
|
|
189
|
+
// `install-plugin.ts` has carried this since AGL-968; the other six never
|
|
190
|
+
// did, so a private component, theme, template, layout, email template or
|
|
191
|
+
// dataset schema was installable by anyone who knew its listing id. Browse
|
|
192
|
+
// hides them and the detail page 404s, but neither is a control — the
|
|
193
|
+
// route is.
|
|
194
|
+
if (isPrivateListing(listing) && !ownsListing) {
|
|
195
|
+
return res.status(404).json({
|
|
196
|
+
error: 'Unknown listing'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// A FULLY refunded purchase stops entitling (AGL-1546), and until
|
|
200
|
+
// AGL-1699 only the component route knew that: this one asked whether a
|
|
201
|
+
// purchase doc EXISTED, so buy/install/refund kept the artifact. The
|
|
202
|
+
// predicate lives in one place now so the next route cannot miss it.
|
|
203
|
+
const unpaid = await requirePurchase({
|
|
204
|
+
firestore,
|
|
205
|
+
buyerUid: decoded.uid,
|
|
206
|
+
// THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is
|
|
207
|
+
// resolved server-side from the caller's own membership by the
|
|
208
|
+
// permission gate above — never a request-body field — so this is the
|
|
209
|
+
// workspace the install actually lands in, and the only one a purchase
|
|
210
|
+
// can entitle here.
|
|
211
|
+
buyerOrgId: (_membership_orgId = membership.orgId) != null ? _membership_orgId : '',
|
|
212
|
+
listingId,
|
|
213
|
+
priceUsd,
|
|
214
|
+
ownsListing
|
|
215
|
+
});
|
|
216
|
+
if (unpaid) return res.status(402).json(unpaid);
|
|
217
|
+
const versionSnapshot = await listingRef.collection('versions').doc(String(listing.latestVersion)).get();
|
|
218
|
+
const theme = versionSnapshot.get('theme');
|
|
219
|
+
if (!theme || !Object.keys(theme).length) {
|
|
220
|
+
return res.status(500).json({
|
|
221
|
+
error: 'Theme version missing'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// ---- preview: say what the swap changes, change nothing ----
|
|
225
|
+
if (action === 'preview') {
|
|
226
|
+
var _listing_latestVersion1;
|
|
227
|
+
return res.status(200).json({
|
|
228
|
+
incoming: describeTheme(theme),
|
|
229
|
+
current: describeTheme(currentTheme),
|
|
230
|
+
// A site with no theme is on the platform default, so there is nothing
|
|
231
|
+
// to lose and the confirmation should not imply there is.
|
|
232
|
+
replaces: Boolean(currentTheme && Object.keys(currentTheme).length),
|
|
233
|
+
version: (_listing_latestVersion1 = listing.latestVersion) != null ? _listing_latestVersion1 : null
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// ---- install ----
|
|
237
|
+
// Replacing a theme the workspace has edited destroys those edits (AGL-1018).
|
|
238
|
+
// Unlike a component this is not recoverable by looking in a library — the
|
|
239
|
+
// theme lives on one field on one document — so the refusal matters more.
|
|
240
|
+
const diverged = await hasDivergedFromBase({
|
|
241
|
+
firestore,
|
|
242
|
+
sha256: hostSnapshot.get('themeInstalledFrom.sha256'),
|
|
243
|
+
current: currentTheme
|
|
244
|
+
});
|
|
245
|
+
if (diverged && ((_req_body3 = req.body) == null ? void 0 : _req_body3.mode) !== 'replace') {
|
|
246
|
+
return res.status(409).json({
|
|
247
|
+
error: 'This site’s theme has been edited since it was installed. Review ' + 'the update to see what would change.',
|
|
248
|
+
diverged: true
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
const provenance = await recordInstallProvenance({
|
|
252
|
+
firestore,
|
|
253
|
+
listingId,
|
|
254
|
+
listing,
|
|
255
|
+
version: listing.latestVersion,
|
|
256
|
+
artifactType: 'theme',
|
|
257
|
+
content: theme
|
|
258
|
+
});
|
|
259
|
+
const previousVersion = (_hostSnapshot_get2 = hostSnapshot.get('themeInstalledFrom.version')) != null ? _hostSnapshot_get2 : null;
|
|
260
|
+
await hostRef.set({
|
|
261
|
+
theme,
|
|
262
|
+
themeInstalledFrom: provenance.installedFrom,
|
|
263
|
+
// Everything needed to put this site back exactly as it was, captured
|
|
264
|
+
// BEFORE the write that changes it.
|
|
265
|
+
themeReplaced: _extends({
|
|
266
|
+
theme: currentTheme
|
|
267
|
+
}, hostSnapshot.get('themeInstalledFrom') ? {
|
|
268
|
+
installedFrom: hostSnapshot.get('themeInstalledFrom')
|
|
269
|
+
} : {}, hostSnapshot.get('themeOverride') ? {
|
|
270
|
+
override: hostSnapshot.get('themeOverride')
|
|
271
|
+
} : {}, {
|
|
272
|
+
replacedAt: now
|
|
273
|
+
}),
|
|
274
|
+
// Overrides SURVIVE a theme swap (AGL-1021). A site that set its brand
|
|
275
|
+
// colour still means it under a new theme, and silently discarding
|
|
276
|
+
// that on install would be the more surprising of the two options.
|
|
277
|
+
//
|
|
278
|
+
// What must not happen is surviving *silently*: the override keeps the
|
|
279
|
+
// `baseSha256` it was authored against, which no longer matches the
|
|
280
|
+
// theme now installed, so `isOverrideForCurrentTheme` is false and the
|
|
281
|
+
// editor says so with an offer to clear them. Rewriting that hash here
|
|
282
|
+
// would launder a patch written for another theme into one that claims
|
|
283
|
+
// to belong to this one.
|
|
284
|
+
updatedAt: now
|
|
285
|
+
}, {
|
|
286
|
+
merge: true
|
|
287
|
+
});
|
|
288
|
+
await recordVersionMove({
|
|
289
|
+
firestore,
|
|
290
|
+
listingRef,
|
|
291
|
+
artifactType: 'theme',
|
|
292
|
+
from: previousVersion,
|
|
293
|
+
to: listing.latestVersion
|
|
294
|
+
});
|
|
295
|
+
await listingRef.update({
|
|
296
|
+
installCount: firebaseAdmin.firestore.FieldValue.increment(1)
|
|
297
|
+
}).catch(()=>undefined);
|
|
298
|
+
return res.status(200).json({
|
|
299
|
+
installed: true,
|
|
300
|
+
version: (_listing_latestVersion = listing.latestVersion) != null ? _listing_latestVersion : null,
|
|
301
|
+
baseStored: provenance.baseStored,
|
|
302
|
+
applied: describeTheme(theme)
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(error);
|
|
306
|
+
return res.status(500).json({
|
|
307
|
+
error: 'Theme install failed'
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
export default installThemeHandler;
|
|
312
|
+
|
|
313
|
+
//# sourceMappingURL=install-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/install-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 { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { describeTheme } from '@aglyn/aglyn/app-utils/marketplace-theme'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n isPrivateListing,\n listingArtifactType,\n} from '../model/marketplace'\nimport { canActAsPublisher } from './publisher-profile'\nimport { requirePurchase } from './purchase-entitlement'\nimport { hasDivergedFromBase, recordInstallProvenance } from './provenance'\nimport { recordVersionMove } from './version-stats'\n\n/**\n * Installs a marketplace theme onto a site (AGL-1020).\n *\n * Themes are the exception to \"installing never changes a running site\". A\n * template or a layout can land inert in a library because it is one thing\n * among many; a theme IS the site's appearance, and installing one into a\n * library where it does nothing would be a control that appears to work and\n * does not. So this writes `hosts/{hostId}.theme` and the site repaints.\n *\n * Which makes reversibility the requirement, not a nicety. Two things provide\n * it, and neither is a confirmation dialog:\n *\n * * The theme being replaced is kept verbatim in `themeReplaced`, so `revert`\n * restores exactly what was there — including a hand-built theme that was\n * never a marketplace artifact and exists nowhere else.\n * * `reset` clears the theme entirely, which is the \"use the default theme\"\n * the issue asks to keep first-class. It is a distinct action from revert\n * because \"back to how it was\" and \"back to stock\" are different intents and\n * guessing between them is how people lose work.\n *\n * `preview` changes nothing and returns what the swap would do, so the\n * confirmation can name it rather than showing a JSON blob.\n */\nexport const installThemeHandler: 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 action = ['revert', 'reset', 'preview', 'clear-overrides'].includes(\n req.body?.action,\n )\n ? (req.body.action as\n | 'revert'\n | 'reset'\n | 'preview'\n | 'clear-overrides')\n : 'install'\n const listingId = String(req.body?.listingId ?? '')\n if (!hostId || (action === 'install' && !listingId)) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\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.installPlugins) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n const currentTheme = hostSnapshot.get('theme') ?? null\n\n // ---- reset: back to the platform default ----\n if (action === 'reset') {\n await hostRef.set(\n {\n // Kept, not dropped: resetting is reversible for the same reason\n // installing is, and someone who resets by mistake has otherwise\n // lost a theme that existed in no other document.\n themeReplaced: { theme: currentTheme, replacedAt: now },\n theme: firebaseAdmin.firestore.FieldValue.delete(),\n themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete(),\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ reset: true })\n }\n\n // ---- clear-overrides: drop this site's changes, keep the theme ----\n // The third way back, and the narrowest: \"reset to the publisher's\n // version\" is deleting the patch — the whole point of owning the patch\n // rather than the copy (AGL-1019). The theme itself is untouched.\n if (action === 'clear-overrides') {\n await hostRef.set(\n {\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ cleared: true })\n }\n\n // ---- revert: back to whatever this site had before the last swap ----\n if (action === 'revert') {\n const replaced = hostSnapshot.get('themeReplaced')\n if (!replaced) {\n return res.status(409).json({\n error: 'There is no previous theme to go back to on this site.',\n })\n }\n await hostRef.set(\n {\n theme: replaced.theme ?? firebaseAdmin.firestore.FieldValue.delete(),\n ...(replaced.installedFrom\n ? { themeInstalledFrom: replaced.installedFrom }\n : { themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete() }),\n // The overrides that were live alongside that theme come back with\n // it. Reverting the base and keeping a patch authored against the\n // theme being reverted FROM is the one combination nobody asked for.\n ...(replaced.override\n ? { themeOverride: replaced.override }\n : { themeOverride: firebaseAdmin.firestore.FieldValue.delete() }),\n themeReplaced: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ reverted: true })\n }\n\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt ||\n listingArtifactType(listing) !== 'theme'\n ) {\n return res.status(404).json({ error: 'Unknown theme' })\n }\n\n const priceUsd = Number(listing.priceUsd ?? 0)\n const ownsListing = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n // Private listings install ONLY for the owning org (AGL-2290).\n //\n // `install-plugin.ts` has carried this since AGL-968; the other six never\n // did, so a private component, theme, template, layout, email template or\n // dataset schema was installable by anyone who knew its listing id. Browse\n // hides them and the detail page 404s, but neither is a control — the\n // route is.\n if (isPrivateListing(listing) && !ownsListing) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // A FULLY refunded purchase stops entitling (AGL-1546), and until\n // AGL-1699 only the component route knew that: this one asked whether a\n // purchase doc EXISTED, so buy/install/refund kept the artifact. The\n // predicate lives in one place now so the next route cannot miss it.\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing,\n })\n if (unpaid) return res.status(402).json(unpaid)\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n const theme = versionSnapshot.get('theme')\n if (!theme || !Object.keys(theme).length) {\n return res.status(500).json({ error: 'Theme version missing' })\n }\n\n // ---- preview: say what the swap changes, change nothing ----\n if (action === 'preview') {\n return res.status(200).json({\n incoming: describeTheme(theme),\n current: describeTheme(currentTheme),\n // A site with no theme is on the platform default, so there is nothing\n // to lose and the confirmation should not imply there is.\n replaces: Boolean(currentTheme && Object.keys(currentTheme).length),\n version: listing.latestVersion ?? null,\n })\n }\n\n // ---- install ----\n // Replacing a theme the workspace has edited destroys those edits (AGL-1018).\n // Unlike a component this is not recoverable by looking in a library — the\n // theme lives on one field on one document — so the refusal matters more.\n const diverged = await hasDivergedFromBase({\n firestore,\n sha256: hostSnapshot.get('themeInstalledFrom.sha256'),\n current: currentTheme,\n })\n if (diverged && req.body?.mode !== 'replace') {\n return res.status(409).json({\n error:\n 'This site’s theme has been edited since it was installed. Review ' +\n 'the update to see what would change.',\n diverged: true,\n })\n }\n\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: 'theme',\n content: theme,\n })\n const previousVersion =\n hostSnapshot.get('themeInstalledFrom.version') ?? null\n\n await hostRef.set(\n {\n theme,\n themeInstalledFrom: provenance.installedFrom,\n // Everything needed to put this site back exactly as it was, captured\n // BEFORE the write that changes it.\n themeReplaced: {\n theme: currentTheme,\n ...(hostSnapshot.get('themeInstalledFrom')\n ? { installedFrom: hostSnapshot.get('themeInstalledFrom') }\n : {}),\n ...(hostSnapshot.get('themeOverride')\n ? { override: hostSnapshot.get('themeOverride') }\n : {}),\n replacedAt: now,\n },\n // Overrides SURVIVE a theme swap (AGL-1021). A site that set its brand\n // colour still means it under a new theme, and silently discarding\n // that on install would be the more surprising of the two options.\n //\n // What must not happen is surviving *silently*: the override keeps the\n // `baseSha256` it was authored against, which no longer matches the\n // theme now installed, so `isOverrideForCurrentTheme` is false and the\n // editor says so with an offer to clear them. Rewriting that hash here\n // would launder a patch written for another theme into one that claims\n // to belong to this one.\n updatedAt: now,\n },\n { merge: true },\n )\n\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType: 'theme',\n from: previousVersion,\n to: listing.latestVersion,\n })\n await listingRef\n .update({\n installCount: firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n\n return res.status(200).json({\n installed: true,\n version: listing.latestVersion ?? null,\n baseStored: provenance.baseStored,\n applied: describeTheme(theme),\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Theme install failed' })\n }\n}\n\nexport default installThemeHandler\n"],"names":["describeTheme","firebaseAdmin","resolveOrgPermissions","isPrivateListing","listingArtifactType","canActAsPublisher","requirePurchase","hasDivergedFromBase","recordInstallProvenance","recordVersionMove","installThemeHandler","req","res","method","status","json","error","hostId","String","body","action","includes","listingId","authorization","headers","idToken","startsWith","slice","length","undefined","hostSnapshot","listing","membership","decoded","app","auth","verifyIdToken","uid","permissions","installPlugins","firestore","hostRef","collection","doc","get","exists","memberRole","now","FieldValue","serverTimestamp","currentTheme","set","themeReplaced","theme","replacedAt","delete","themeInstalledFrom","themeOverride","updatedAt","merge","reset","cleared","replaced","installedFrom","override","reverted","listingRef","listingSnapshot","data","deletedAt","hiddenAt","priceUsd","Number","ownsListing","profileId","unpaid","buyerUid","buyerOrgId","orgId","versionSnapshot","latestVersion","Object","keys","incoming","current","replaces","Boolean","version","diverged","sha256","mode","provenance","artifactType","content","previousVersion","from","to","update","installCount","increment","catch","installed","baseStored","applied","console"],"mappings":"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"}
|
|
@@ -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
|
+
* Installs (or updates) a marketplace listing into a host (AGL-44/46).
|
|
20
|
+
* Server-side because version snapshots are not client-readable — paid
|
|
21
|
+
* content would otherwise be free to anyone who read the subcollection.
|
|
22
|
+
* Access: the listing is free, the caller bought it (webhook-written
|
|
23
|
+
* purchase record), or the caller published it. Installing copies the
|
|
24
|
+
* latest version into `hosts/{hostId}/components` with `marketplace` source
|
|
25
|
+
* metadata; re-installing updates the same component doc in place.
|
|
26
|
+
*/
|
|
27
|
+
export declare const installHandler: PluginApiHandler;
|