@aglyn/plugins-marketplace 1.0.0-beta.199 → 1.0.0-beta.201
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/package.json +14 -14
- package/src/lib/components/marketplace-browse.component.js +104 -169
- package/src/lib/components/marketplace-browse.component.js.map +1 -1
- package/src/lib/components/marketplace-reports-page.component.js +9 -0
- package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
- package/src/lib/components/org-licences-panel.component.js +212 -170
- package/src/lib/components/org-licences-panel.component.js.map +1 -1
- package/src/lib/components/org-seller-panel.component.js +23 -5
- package/src/lib/components/org-seller-panel.component.js.map +1 -1
- package/src/lib/components/plugin-reviews-queue.component.d.ts +9 -0
- package/src/lib/components/plugin-reviews-queue.component.js +190 -114
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
- package/src/lib/hooks/use-listing-name-lookup.d.ts +34 -0
- package/src/lib/hooks/use-listing-name-lookup.js +131 -0
- package/src/lib/hooks/use-listing-name-lookup.js.map +1 -0
- package/src/lib/model/listing-query.d.ts +140 -0
- package/src/lib/model/listing-query.js +393 -0
- package/src/lib/model/listing-query.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +11 -0
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/server/admin-reports.js +30 -12
- package/src/lib/server/admin-reports.js.map +1 -1
- package/src/lib/server/admin-reviews.js +180 -128
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/admin-trust.js +2 -1
- package/src/lib/server/admin-trust.js.map +1 -1
- package/src/lib/server/billing-webhook.js +13 -1
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/checkout.js +3 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/install-email-starter.js +9 -2
- package/src/lib/server/install-email-starter.js.map +1 -1
- package/src/lib/server/install-layout.js +17 -5
- package/src/lib/server/install-layout.js.map +1 -1
- package/src/lib/server/install-template.js +15 -5
- package/src/lib/server/install-template.js.map +1 -1
- package/src/lib/server/install.js +10 -2
- package/src/lib/server/install.js.map +1 -1
- package/src/lib/server/listing-query-fields.d.ts +38 -0
- package/src/lib/server/listing-query-fields.js +50 -0
- package/src/lib/server/listing-query-fields.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.js +6 -2
- package/src/lib/server/publish-dataset-schema.js.map +1 -1
- package/src/lib/server/publish-email-starter.js +6 -2
- package/src/lib/server/publish-email-starter.js.map +1 -1
- package/src/lib/server/publish-email-template.js +6 -2
- package/src/lib/server/publish-email-template.js.map +1 -1
- package/src/lib/server/publish-layout.js +6 -2
- package/src/lib/server/publish-layout.js.map +1 -1
- package/src/lib/server/publish-plugin.js +75 -7
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/publish-template.js +6 -2
- package/src/lib/server/publish-template.js.map +1 -1
- package/src/lib/server/publish-theme.js +6 -2
- package/src/lib/server/publish-theme.js.map +1 -1
- package/src/lib/server/publish.js +6 -2
- package/src/lib/server/publish.js.map +1 -1
- package/src/lib/server/update-artifact.js +18 -4
- package/src/lib/server/update-artifact.js.map +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { nameSearchNormalizers } from "@aglyn/aglyn/app-utils/name-search";
|
|
19
|
+
import { planListQuery } from "@aglyn/shared-ui-jsx/const/list-query-plan";
|
|
20
|
+
import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
21
|
+
import { listQueryConstraints } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
|
|
22
|
+
import { collection, documentId, getDocs, limit, query, where } from "firebase/firestore";
|
|
23
|
+
import { useEffect, useMemo, useState } from "react";
|
|
24
|
+
import { LICENCE_LISTING_MATCH_CAP, LISTING_NAME_LOOKUP } from "../model/listing-query.js";
|
|
25
|
+
/**
|
|
26
|
+
* The listings whose NAME holds a typed word (AGL-3321).
|
|
27
|
+
*
|
|
28
|
+
* A license row names its listing by id, and the name lives on the listing,
|
|
29
|
+
* where the publisher can change it. So a license search asks the listings
|
|
30
|
+
* first — `nameTokens array-contains` the word, the key every listing writer
|
|
31
|
+
* stamps — and the license query then asks for those listing ids. Nothing is
|
|
32
|
+
* matched over rows already loaded.
|
|
33
|
+
*
|
|
34
|
+
* Firestore takes at most thirty values in one `in`, so a word naming more
|
|
35
|
+
* than thirty listings is refused by name rather than answered with thirty of
|
|
36
|
+
* them.
|
|
37
|
+
*/ export function useListingNameLookup(words) {
|
|
38
|
+
var _read_data;
|
|
39
|
+
const firestore = useFirestore();
|
|
40
|
+
const plan = useMemo(()=>planListQuery(LISTING_NAME_LOOKUP, {
|
|
41
|
+
clauses: [],
|
|
42
|
+
search: words
|
|
43
|
+
}, nameSearchNormalizers), // The words are data; their text is their identity.
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
|
+
[
|
|
46
|
+
words.join(' ')
|
|
47
|
+
]);
|
|
48
|
+
const read = useFirestoreCollection(()=>plan.searched ? query(collection(firestore, 'marketplaceListings'), ...listQueryConstraints(plan), limit(LICENCE_LISTING_MATCH_CAP + 1)) : null, [
|
|
49
|
+
firestore,
|
|
50
|
+
plan.searched
|
|
51
|
+
], {
|
|
52
|
+
idField: '$id'
|
|
53
|
+
});
|
|
54
|
+
if (!plan.searched) {
|
|
55
|
+
return {
|
|
56
|
+
ids: null,
|
|
57
|
+
pending: false,
|
|
58
|
+
refused: plan.refused,
|
|
59
|
+
notices: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (read.status !== 'success') {
|
|
63
|
+
return {
|
|
64
|
+
ids: null,
|
|
65
|
+
pending: true,
|
|
66
|
+
refused: [],
|
|
67
|
+
notices: plan.notices
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const ids = ((_read_data = read.data) != null ? _read_data : []).map((row)=>row.$id);
|
|
71
|
+
if (ids.length > LICENCE_LISTING_MATCH_CAP) {
|
|
72
|
+
return {
|
|
73
|
+
ids: null,
|
|
74
|
+
pending: false,
|
|
75
|
+
refused: [
|
|
76
|
+
{
|
|
77
|
+
clause: 'search',
|
|
78
|
+
reason: `more than ${LICENCE_LISTING_MATCH_CAP} listings have a name ` + 'with that word — type more of it'
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
notices: []
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ids,
|
|
86
|
+
pending: false,
|
|
87
|
+
refused: [],
|
|
88
|
+
notices: plan.notices
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Listing id → display name, for the rows on screen: read BY ID, thirty at a
|
|
93
|
+
* time, so a license always names its listing however large the catalog is.
|
|
94
|
+
* A listing that cannot be read keeps its id as its label.
|
|
95
|
+
*/ export function useListingNames(ids) {
|
|
96
|
+
const firestore = useFirestore();
|
|
97
|
+
const key = [
|
|
98
|
+
...new Set(ids.filter(Boolean))
|
|
99
|
+
].sort().join(',');
|
|
100
|
+
const [names, setNames] = useState({});
|
|
101
|
+
useEffect(()=>{
|
|
102
|
+
const wanted = key ? key.split(',') : [];
|
|
103
|
+
if (!wanted.length) return;
|
|
104
|
+
let active = true;
|
|
105
|
+
const chunks = [];
|
|
106
|
+
for(let at = 0; at < wanted.length; at += LICENCE_LISTING_MATCH_CAP){
|
|
107
|
+
chunks.push(wanted.slice(at, at + LICENCE_LISTING_MATCH_CAP));
|
|
108
|
+
}
|
|
109
|
+
void Promise.all(chunks.map((chunk)=>getDocs(query(collection(firestore, 'marketplaceListings'), where(documentId(), 'in', chunk))).catch(()=>null))).then((snapshots)=>{
|
|
110
|
+
if (!active) return;
|
|
111
|
+
const found = {};
|
|
112
|
+
for (const snapshot of snapshots){
|
|
113
|
+
var _ref;
|
|
114
|
+
for (const listing of (_ref = snapshot == null ? void 0 : snapshot.docs) != null ? _ref : []){
|
|
115
|
+
const name = listing.get('displayName');
|
|
116
|
+
if (typeof name === 'string' && name) found[listing.id] = name;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
setNames((previous)=>Object.entries(found).every(([id, name])=>previous[id] === name) ? previous : _extends({}, previous, found));
|
|
120
|
+
});
|
|
121
|
+
return ()=>{
|
|
122
|
+
active = false;
|
|
123
|
+
};
|
|
124
|
+
}, [
|
|
125
|
+
firestore,
|
|
126
|
+
key
|
|
127
|
+
]);
|
|
128
|
+
return names;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=use-listing-name-lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/hooks/use-listing-name-lookup.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'use client'\n\nimport { nameSearchNormalizers } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n type ListQueryRefusal,\n planListQuery,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useFirestore, useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { listQueryConstraints } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n collection,\n documentId,\n getDocs,\n limit,\n query,\n where,\n} from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n LICENCE_LISTING_MATCH_CAP,\n LISTING_NAME_LOOKUP,\n} from '../model/listing-query'\n\nexport interface ListingNameLookup {\n /**\n * The listings a search names, for a `listingId in [...]` clause; `null`\n * when nothing is searched, so the list is not narrowed at all.\n */\n ids: string[] | null\n /** Still asking which listings the word names. */\n pending: boolean\n /** The search, refused — too many listings share the word. */\n refused: ListQueryRefusal[]\n /** Said about what was searched, e.g. one word of two. */\n notices: string[]\n}\n\n/**\n * The listings whose NAME holds a typed word (AGL-3321).\n *\n * A license row names its listing by id, and the name lives on the listing,\n * where the publisher can change it. So a license search asks the listings\n * first — `nameTokens array-contains` the word, the key every listing writer\n * stamps — and the license query then asks for those listing ids. Nothing is\n * matched over rows already loaded.\n *\n * Firestore takes at most thirty values in one `in`, so a word naming more\n * than thirty listings is refused by name rather than answered with thirty of\n * them.\n */\nexport function useListingNameLookup(words: readonly string[]): ListingNameLookup {\n const firestore = useFirestore()\n const plan = useMemo(\n () =>\n planListQuery(LISTING_NAME_LOOKUP, { clauses: [], search: words }, nameSearchNormalizers),\n // The words are data; their text is their identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [words.join(' ')],\n )\n const read = useFirestoreCollection<{ $id: string }>(\n () =>\n plan.searched\n ? query(\n collection(firestore, 'marketplaceListings'),\n ...listQueryConstraints(plan),\n limit(LICENCE_LISTING_MATCH_CAP + 1),\n )\n : null,\n [firestore, plan.searched],\n { idField: '$id' },\n )\n if (!plan.searched) {\n return { ids: null, pending: false, refused: plan.refused, notices: [] }\n }\n if (read.status !== 'success') {\n return { ids: null, pending: true, refused: [], notices: plan.notices }\n }\n const ids = (read.data ?? []).map((row) => row.$id)\n if (ids.length > LICENCE_LISTING_MATCH_CAP) {\n return {\n ids: null,\n pending: false,\n refused: [\n {\n clause: 'search',\n reason:\n `more than ${LICENCE_LISTING_MATCH_CAP} listings have a name ` +\n 'with that word — type more of it',\n },\n ],\n notices: [],\n }\n }\n return { ids, pending: false, refused: [], notices: plan.notices }\n}\n\n/**\n * Listing id → display name, for the rows on screen: read BY ID, thirty at a\n * time, so a license always names its listing however large the catalog is.\n * A listing that cannot be read keeps its id as its label.\n */\nexport function useListingNames(ids: readonly string[]): Record<string, string> {\n const firestore = useFirestore()\n const key = [...new Set(ids.filter(Boolean))].sort().join(',')\n const [names, setNames] = useState<Record<string, string>>({})\n useEffect(() => {\n const wanted = key ? key.split(',') : []\n if (!wanted.length) return\n let active = true\n const chunks: string[][] = []\n for (let at = 0; at < wanted.length; at += LICENCE_LISTING_MATCH_CAP) {\n chunks.push(wanted.slice(at, at + LICENCE_LISTING_MATCH_CAP))\n }\n void Promise.all(\n chunks.map((chunk) =>\n getDocs(\n query(\n collection(firestore, 'marketplaceListings'),\n where(documentId(), 'in', chunk),\n ),\n ).catch(() => null),\n ),\n ).then((snapshots) => {\n if (!active) return\n const found: Record<string, string> = {}\n for (const snapshot of snapshots) {\n for (const listing of snapshot?.docs ?? []) {\n const name = listing.get('displayName')\n if (typeof name === 'string' && name) found[listing.id] = name\n }\n }\n setNames((previous) =>\n Object.entries(found).every(([id, name]) => previous[id] === name)\n ? previous\n : { ...previous, ...found },\n )\n })\n return () => {\n active = false\n }\n }, [firestore, key])\n return names\n}\n"],"names":["nameSearchNormalizers","planListQuery","useFirestore","useFirestoreCollection","listQueryConstraints","collection","documentId","getDocs","limit","query","where","useEffect","useMemo","useState","LICENCE_LISTING_MATCH_CAP","LISTING_NAME_LOOKUP","useListingNameLookup","words","read","firestore","plan","clauses","search","join","searched","idField","ids","pending","refused","notices","status","data","map","row","$id","length","clause","reason","useListingNames","key","Set","filter","Boolean","sort","names","setNames","wanted","split","active","chunks","at","push","slice","Promise","all","chunk","catch","then","snapshots","found","snapshot","listing","docs","name","get","id","previous","Object","entries","every"],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
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 { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
|
|
18
|
+
import { type ListQueryDeclaration, type ListQueryFilter, type ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
19
|
+
/** Everyone, as a browse audience scope. */
|
|
20
|
+
export declare const BROWSE_EVERYONE = "*";
|
|
21
|
+
/** Joins an audience scope to the publisher it is narrowed to. */
|
|
22
|
+
export declare const BROWSE_PUBLISHER_JOIN = "|";
|
|
23
|
+
export declare const LISTING_BROWSE_AUDIENCE = "browseAudience";
|
|
24
|
+
export declare const LISTING_BROWSE_TOKENS = "browseTokens";
|
|
25
|
+
export declare const LISTING_NAME_TOKENS = "nameTokens";
|
|
26
|
+
export declare const LISTING_TAKEN_DOWN = "takenDown";
|
|
27
|
+
/** The fields a listing's lists query by, all derived. */
|
|
28
|
+
export interface ListingQueryFields {
|
|
29
|
+
nameLower: string;
|
|
30
|
+
nameTokens: string[];
|
|
31
|
+
nameReversed: string;
|
|
32
|
+
browseAudience: string[];
|
|
33
|
+
browseTokens: string[];
|
|
34
|
+
takenDown: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** What the derivation reads off a listing. */
|
|
37
|
+
export interface ListingQuerySource {
|
|
38
|
+
displayName?: unknown;
|
|
39
|
+
profileId?: unknown;
|
|
40
|
+
deletedAt?: unknown;
|
|
41
|
+
hiddenAt?: unknown;
|
|
42
|
+
visibility?: string;
|
|
43
|
+
reviewStatus?: string;
|
|
44
|
+
artifactType?: string;
|
|
45
|
+
type?: string;
|
|
46
|
+
kind?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Who browse shows a listing to — the same rule the grid applied in memory:
|
|
50
|
+
* never a deleted or private listing; a browsable one to everyone; one that
|
|
51
|
+
* is not browsable (in review, rejected, taken down) to its own publisher
|
|
52
|
+
* only, so a publisher can watch a submission move through review.
|
|
53
|
+
*/
|
|
54
|
+
export declare function listingBrowseAudience(listing: ListingQuerySource): string[];
|
|
55
|
+
/** Every derived field, from the listing as stored. */
|
|
56
|
+
export declare function listingQueryFields(listing: ListingQuerySource): ListingQueryFields;
|
|
57
|
+
/**
|
|
58
|
+
* What a listing that never had one holds in each field browse ORDERS by. An
|
|
59
|
+
* `orderBy` drops a document missing its field, so a listing nobody has
|
|
60
|
+
* installed or rated would vanish from Most installed and Highest rated
|
|
61
|
+
* rather than sort last. `null` sorts after every number in a descending
|
|
62
|
+
* order, which is where "not yet rated" belongs; the reviews route writes a
|
|
63
|
+
* number over it on the first rating. Only ever stamped where the field is
|
|
64
|
+
* ABSENT — the install and review routes own the values.
|
|
65
|
+
*/
|
|
66
|
+
export declare const LISTING_SORT_DEFAULTS: Readonly<Record<string, number | null>>;
|
|
67
|
+
/**
|
|
68
|
+
* The derived fields a stored listing is missing or holds stale, plus any
|
|
69
|
+
* sort field it lacks, or null when it is current — what a writer's refresh
|
|
70
|
+
* and the backfill write.
|
|
71
|
+
*/
|
|
72
|
+
export declare function listingQueryFieldsPatch(listing: ListingQuerySource & Record<string, unknown>): Record<string, unknown> | null;
|
|
73
|
+
export type BrowseSort = 'newest' | 'installed' | 'rated';
|
|
74
|
+
export declare const BROWSE_SORTS: Readonly<Record<BrowseSort, ListQuerySort>>;
|
|
75
|
+
/**
|
|
76
|
+
* The browse shelf: a category chip, the search box and three orders, every
|
|
77
|
+
* one on the query. The audience clause is its base (`browseBase`), and the
|
|
78
|
+
* search folds into it.
|
|
79
|
+
*/
|
|
80
|
+
export declare const MARKETPLACE_BROWSE_QUERY: ListQueryDeclaration;
|
|
81
|
+
/** The base clause's path, for the index enumeration. */
|
|
82
|
+
export declare const MARKETPLACE_BROWSE_BASE_PATHS: readonly [{
|
|
83
|
+
readonly path: "browseAudience";
|
|
84
|
+
readonly array: true;
|
|
85
|
+
}];
|
|
86
|
+
/**
|
|
87
|
+
* What a reader may see on browse: everything public, plus their own org's
|
|
88
|
+
* listings still in review — narrowed to one publisher on a publisher page.
|
|
89
|
+
*/
|
|
90
|
+
export declare function browseBase(viewerOrgId: string | null | undefined, publisherId?: string | null): ListQueryFilter[];
|
|
91
|
+
/** "This workspace": who on the team bought it. */
|
|
92
|
+
export declare const HELD_LICENCE_QUERY: ListQueryDeclaration;
|
|
93
|
+
/**
|
|
94
|
+
* "Bought by you": which workspace a license landed in. A purchase that
|
|
95
|
+
* names none (before AGL-2331) is stored with `buyerOrgId: null` and is
|
|
96
|
+
* asked for as empty.
|
|
97
|
+
*/
|
|
98
|
+
export declare const MINE_LICENCE_QUERY: ListQueryDeclaration;
|
|
99
|
+
/** The picked value that means "every workspace" — a purchase naming none. */
|
|
100
|
+
export declare const EVERY_WORKSPACE = "every";
|
|
101
|
+
/**
|
|
102
|
+
* The Licensed-to select's "every workspace" as the query reads it: a
|
|
103
|
+
* purchase whose `buyerOrgId` is null.
|
|
104
|
+
*/
|
|
105
|
+
export declare function mineLicenceClauses(clauses: readonly ListFilterClause[]): ListFilterClause[];
|
|
106
|
+
/**
|
|
107
|
+
* At most this many listings are named by one license search: they go on the
|
|
108
|
+
* purchases query as `listingId in [...]`, which Firestore caps at thirty.
|
|
109
|
+
*/
|
|
110
|
+
export declare const LICENCE_LISTING_MATCH_CAP = 30;
|
|
111
|
+
/**
|
|
112
|
+
* The listing-name lookup a license search runs first: the listings whose
|
|
113
|
+
* name has the typed word, by the same token array the staff queue searches.
|
|
114
|
+
*/
|
|
115
|
+
export declare const LISTING_NAME_LOOKUP: ListQueryDeclaration;
|
|
116
|
+
/**
|
|
117
|
+
* A license table's scope: its owner clause, live purchases only (a refunded
|
|
118
|
+
* purchase is not a license, AGL-1546), and the listings a search named.
|
|
119
|
+
*/
|
|
120
|
+
export declare function licenceBase(owner: {
|
|
121
|
+
path: 'buyerOrgId' | 'buyerUid';
|
|
122
|
+
value: string;
|
|
123
|
+
}, listingIds: readonly string[] | null): ListQueryFilter[];
|
|
124
|
+
/** The queue's Status select, as clauses the query serves. */
|
|
125
|
+
export declare const REVIEW_QUEUE_QUERY: ListQueryDeclaration;
|
|
126
|
+
/** The three sections the queue reads, each its own query. */
|
|
127
|
+
export type ReviewQueueSection = 'queue' | 'listed' | 'verification';
|
|
128
|
+
/** A listing's newest bytes have no approval: the `latestVersionReviewState` values that mean it. */
|
|
129
|
+
export declare const AWAITING_REVIEW_STATES: readonly ["pending", "rejected"];
|
|
130
|
+
export declare const LISTED_REVIEW_STATUSES: readonly ["listed", "verified"];
|
|
131
|
+
/**
|
|
132
|
+
* One section's scope: live plugin listings, narrowed to the section. The
|
|
133
|
+
* Listed section's own `reviewStatus in [...]` steps aside for a Status
|
|
134
|
+
* clause that names one of its two values, and a clause naming any other
|
|
135
|
+
* value leaves the section empty by definition — `null`, never queried.
|
|
136
|
+
*/
|
|
137
|
+
export declare function reviewQueueBase(section: ReviewQueueSection, clauses: readonly ListFilterClause[]): ListQueryFilter[] | null;
|
|
138
|
+
/** The queue's Status select as the clause it asks for, or none for "All". */
|
|
139
|
+
export declare function reviewQueueStatusClauses(status: string): ListFilterClause[];
|
|
140
|
+
export declare const MARKETPLACE_REPORTS_QUERY: ListQueryDeclaration;
|
|
@@ -0,0 +1,393 @@
|
|
|
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 { displayNameSearchFields, scopedSearch, scopedSearchTokens } from "@aglyn/aglyn/app-utils/name-search";
|
|
18
|
+
import { isListingBrowsable, isListingDeleted, isPrivateListing } from "@aglyn/aglyn/app-utils/marketplace-listing-visibility";
|
|
19
|
+
import { LIST_QUERY_ID_PATH } from "@aglyn/shared-ui-jsx/const/list-query-plan";
|
|
20
|
+
/*
|
|
21
|
+
* EVERY MARKETPLACE LIST ON ITS QUERY (AGL-3321).
|
|
22
|
+
*
|
|
23
|
+
* Browse, the licenses tab, the staff review queue and the abuse-report queue
|
|
24
|
+
* each matched their filters and search over the rows they had loaded — a
|
|
25
|
+
* window of ninety listings, two hundred reports, a hundred plugins. A match
|
|
26
|
+
* on the next page was answered "no match". Each list now declares what its
|
|
27
|
+
* query can hold, and the fields below are what the listing writers stamp so
|
|
28
|
+
* the query has something to ask.
|
|
29
|
+
*
|
|
30
|
+
* ## What a listing carries for its lists
|
|
31
|
+
*
|
|
32
|
+
* - `nameLower`/`nameTokens`/`nameReversed` — the display name's search
|
|
33
|
+
* keys (`displayNameSearchFields`), for the staff queue's search and for
|
|
34
|
+
* the licenses tab's listing-name lookup.
|
|
35
|
+
* - `browseAudience` — who browse shows the listing to, as scope tokens:
|
|
36
|
+
* `*` for everyone, the publisher's org id for the owner's own view of a
|
|
37
|
+
* listing still in review or taken down, and each again joined to the
|
|
38
|
+
* publisher (`*|<org>`) so a publisher page asks the same question
|
|
39
|
+
* narrowed to one publisher without a second equality. Empty for a
|
|
40
|
+
* deleted or private listing, which browse never shows anyone.
|
|
41
|
+
* - `browseTokens` — the name tokens under each audience scope
|
|
42
|
+
* (`scopedSearchTokens`), so browse's search folds into its audience
|
|
43
|
+
* clause: one `array-contains-any` answers both.
|
|
44
|
+
* - `takenDown` — `hiddenAt` as a boolean, for the staff queue's Taken down
|
|
45
|
+
* status by equality rather than by a range that would lead the order.
|
|
46
|
+
*
|
|
47
|
+
* All five follow from fields other writers own (`displayName`, `deletedAt`,
|
|
48
|
+
* `visibility`, `hiddenAt`, `reviewStatus`, the artifact discriminators,
|
|
49
|
+
* `profileId`), so no writer computes them from its own patch: each calls
|
|
50
|
+
* `refreshListingQueryFields` after it writes, which re-derives them from the
|
|
51
|
+
* document as stored. `tools/scripts/lib/listing-query-fields.mjs`
|
|
52
|
+
* restates the derivation for the seeds and the backfill, held to
|
|
53
|
+
* `listing-query-fields.fixtures.json` beside it by both sides.
|
|
54
|
+
*/ /** Everyone, as a browse audience scope. */ export const BROWSE_EVERYONE = '*';
|
|
55
|
+
/** Joins an audience scope to the publisher it is narrowed to. */ export const BROWSE_PUBLISHER_JOIN = '|';
|
|
56
|
+
export const LISTING_BROWSE_AUDIENCE = 'browseAudience';
|
|
57
|
+
export const LISTING_BROWSE_TOKENS = 'browseTokens';
|
|
58
|
+
export const LISTING_NAME_TOKENS = 'nameTokens';
|
|
59
|
+
export const LISTING_TAKEN_DOWN = 'takenDown';
|
|
60
|
+
/**
|
|
61
|
+
* Who browse shows a listing to — the same rule the grid applied in memory:
|
|
62
|
+
* never a deleted or private listing; a browsable one to everyone; one that
|
|
63
|
+
* is not browsable (in review, rejected, taken down) to its own publisher
|
|
64
|
+
* only, so a publisher can watch a submission move through review.
|
|
65
|
+
*/ export function listingBrowseAudience(listing) {
|
|
66
|
+
if (isListingDeleted(listing) || isPrivateListing(listing)) return [];
|
|
67
|
+
const publisher = typeof listing.profileId === 'string' ? listing.profileId : '';
|
|
68
|
+
const scope = isListingBrowsable(listing) ? BROWSE_EVERYONE : publisher;
|
|
69
|
+
if (!scope) return [];
|
|
70
|
+
return publisher ? [
|
|
71
|
+
scope,
|
|
72
|
+
`${scope}${BROWSE_PUBLISHER_JOIN}${publisher}`
|
|
73
|
+
] : [
|
|
74
|
+
scope
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
/** Every derived field, from the listing as stored. */ export function listingQueryFields(listing) {
|
|
78
|
+
const name = displayNameSearchFields(listing.displayName);
|
|
79
|
+
const browseAudience = listingBrowseAudience(listing);
|
|
80
|
+
return _extends({}, name, {
|
|
81
|
+
browseAudience,
|
|
82
|
+
browseTokens: scopedSearchTokens(browseAudience, name.nameTokens),
|
|
83
|
+
takenDown: Boolean(listing.hiddenAt)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const same = (left, right)=>JSON.stringify(left != null ? left : null) === JSON.stringify(right != null ? right : null);
|
|
87
|
+
/**
|
|
88
|
+
* What a listing that never had one holds in each field browse ORDERS by. An
|
|
89
|
+
* `orderBy` drops a document missing its field, so a listing nobody has
|
|
90
|
+
* installed or rated would vanish from Most installed and Highest rated
|
|
91
|
+
* rather than sort last. `null` sorts after every number in a descending
|
|
92
|
+
* order, which is where "not yet rated" belongs; the reviews route writes a
|
|
93
|
+
* number over it on the first rating. Only ever stamped where the field is
|
|
94
|
+
* ABSENT — the install and review routes own the values.
|
|
95
|
+
*/ export const LISTING_SORT_DEFAULTS = {
|
|
96
|
+
installCount: 0,
|
|
97
|
+
ratingAverage: null
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The derived fields a stored listing is missing or holds stale, plus any
|
|
101
|
+
* sort field it lacks, or null when it is current — what a writer's refresh
|
|
102
|
+
* and the backfill write.
|
|
103
|
+
*/ export function listingQueryFieldsPatch(listing) {
|
|
104
|
+
const computed = listingQueryFields(listing);
|
|
105
|
+
const patch = {};
|
|
106
|
+
for (const [key, value] of Object.entries(computed)){
|
|
107
|
+
if (!same(listing[key], value)) patch[key] = value;
|
|
108
|
+
}
|
|
109
|
+
for (const [key, value] of Object.entries(LISTING_SORT_DEFAULTS)){
|
|
110
|
+
if (listing[key] === undefined) patch[key] = value;
|
|
111
|
+
}
|
|
112
|
+
return Object.keys(patch).length ? patch : null;
|
|
113
|
+
}
|
|
114
|
+
export const BROWSE_SORTS = {
|
|
115
|
+
newest: {
|
|
116
|
+
path: 'createdAt',
|
|
117
|
+
direction: 'desc'
|
|
118
|
+
},
|
|
119
|
+
installed: {
|
|
120
|
+
path: 'installCount',
|
|
121
|
+
direction: 'desc'
|
|
122
|
+
},
|
|
123
|
+
rated: {
|
|
124
|
+
path: 'ratingAverage',
|
|
125
|
+
direction: 'desc'
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* The browse shelf: a category chip, the search box and three orders, every
|
|
130
|
+
* one on the query. The audience clause is its base (`browseBase`), and the
|
|
131
|
+
* search folds into it.
|
|
132
|
+
*/ export const MARKETPLACE_BROWSE_QUERY = {
|
|
133
|
+
fields: [
|
|
134
|
+
{
|
|
135
|
+
column: 'category',
|
|
136
|
+
kind: 'exact',
|
|
137
|
+
path: 'category',
|
|
138
|
+
operators: [
|
|
139
|
+
'equals'
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
sorts: [
|
|
144
|
+
BROWSE_SORTS.newest,
|
|
145
|
+
BROWSE_SORTS.installed,
|
|
146
|
+
BROWSE_SORTS.rated
|
|
147
|
+
],
|
|
148
|
+
search: {
|
|
149
|
+
tokensPath: LISTING_BROWSE_TOKENS,
|
|
150
|
+
scoped: scopedSearch(LISTING_BROWSE_TOKENS)
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/** The base clause's path, for the index enumeration. */ export const MARKETPLACE_BROWSE_BASE_PATHS = [
|
|
154
|
+
{
|
|
155
|
+
path: LISTING_BROWSE_AUDIENCE,
|
|
156
|
+
array: true
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
/**
|
|
160
|
+
* What a reader may see on browse: everything public, plus their own org's
|
|
161
|
+
* listings still in review — narrowed to one publisher on a publisher page.
|
|
162
|
+
*/ export function browseBase(viewerOrgId, publisherId) {
|
|
163
|
+
const scopes = [
|
|
164
|
+
BROWSE_EVERYONE,
|
|
165
|
+
...viewerOrgId ? [
|
|
166
|
+
viewerOrgId
|
|
167
|
+
] : []
|
|
168
|
+
];
|
|
169
|
+
return [
|
|
170
|
+
{
|
|
171
|
+
path: LISTING_BROWSE_AUDIENCE,
|
|
172
|
+
op: 'array-contains-any',
|
|
173
|
+
value: publisherId ? scopes.map((scope)=>`${scope}${BROWSE_PUBLISHER_JOIN}${publisherId}`) : scopes
|
|
174
|
+
}
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
/*==========================================
|
|
178
|
+
* Licenses (`marketplacePurchases`, web SDK, buyer/org-gated).
|
|
179
|
+
*=========================================*/ /**
|
|
180
|
+
* Both license tables keep the order they always had — Firestore's, by
|
|
181
|
+
* document id — which index merging serves with no composite at all.
|
|
182
|
+
*/ const BY_ID = {
|
|
183
|
+
path: LIST_QUERY_ID_PATH,
|
|
184
|
+
direction: 'asc'
|
|
185
|
+
};
|
|
186
|
+
/** "This workspace": who on the team bought it. */ export const HELD_LICENCE_QUERY = {
|
|
187
|
+
fields: [
|
|
188
|
+
{
|
|
189
|
+
column: 'buyerUid',
|
|
190
|
+
kind: 'exact',
|
|
191
|
+
path: 'buyerUid',
|
|
192
|
+
operators: [
|
|
193
|
+
'equals'
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
sorts: [
|
|
198
|
+
BY_ID
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* "Bought by you": which workspace a license landed in. A purchase that
|
|
203
|
+
* names none (before AGL-2331) is stored with `buyerOrgId: null` and is
|
|
204
|
+
* asked for as empty.
|
|
205
|
+
*/ export const MINE_LICENCE_QUERY = {
|
|
206
|
+
fields: [
|
|
207
|
+
{
|
|
208
|
+
column: 'buyerOrgId',
|
|
209
|
+
kind: 'exact',
|
|
210
|
+
path: 'buyerOrgId',
|
|
211
|
+
presence: 'nullable',
|
|
212
|
+
operators: [
|
|
213
|
+
'equals',
|
|
214
|
+
'isEmpty'
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
sorts: [
|
|
219
|
+
BY_ID
|
|
220
|
+
]
|
|
221
|
+
};
|
|
222
|
+
/** The picked value that means "every workspace" — a purchase naming none. */ export const EVERY_WORKSPACE = 'every';
|
|
223
|
+
/**
|
|
224
|
+
* The Licensed-to select's "every workspace" as the query reads it: a
|
|
225
|
+
* purchase whose `buyerOrgId` is null.
|
|
226
|
+
*/ export function mineLicenceClauses(clauses) {
|
|
227
|
+
return clauses.map((clause)=>clause.field === 'buyerOrgId' && clause.op === 'equals' && clause.value === EVERY_WORKSPACE ? _extends({}, clause, {
|
|
228
|
+
op: 'isEmpty',
|
|
229
|
+
value: ''
|
|
230
|
+
}) : clause);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* At most this many listings are named by one license search: they go on the
|
|
234
|
+
* purchases query as `listingId in [...]`, which Firestore caps at thirty.
|
|
235
|
+
*/ export const LICENCE_LISTING_MATCH_CAP = 30;
|
|
236
|
+
/**
|
|
237
|
+
* The listing-name lookup a license search runs first: the listings whose
|
|
238
|
+
* name has the typed word, by the same token array the staff queue searches.
|
|
239
|
+
*/ export const LISTING_NAME_LOOKUP = {
|
|
240
|
+
fields: [],
|
|
241
|
+
sorts: [
|
|
242
|
+
BY_ID
|
|
243
|
+
],
|
|
244
|
+
search: {
|
|
245
|
+
tokensPath: LISTING_NAME_TOKENS
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* A license table's scope: its owner clause, live purchases only (a refunded
|
|
250
|
+
* purchase is not a license, AGL-1546), and the listings a search named.
|
|
251
|
+
*/ export function licenceBase(owner, listingIds) {
|
|
252
|
+
return [
|
|
253
|
+
{
|
|
254
|
+
path: owner.path,
|
|
255
|
+
op: '==',
|
|
256
|
+
value: owner.value
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
path: 'refundedAt',
|
|
260
|
+
op: '==',
|
|
261
|
+
value: null
|
|
262
|
+
},
|
|
263
|
+
...listingIds ? [
|
|
264
|
+
{
|
|
265
|
+
path: 'listingId',
|
|
266
|
+
op: 'in',
|
|
267
|
+
value: [
|
|
268
|
+
...listingIds
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
] : []
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
/*==========================================
|
|
275
|
+
* The staff review queue (Admin SDK, staff route).
|
|
276
|
+
*=========================================*/ /** The queue's Status select, as clauses the query serves. */ export const REVIEW_QUEUE_QUERY = {
|
|
277
|
+
fields: [
|
|
278
|
+
{
|
|
279
|
+
column: 'reviewStatus',
|
|
280
|
+
kind: 'exact',
|
|
281
|
+
path: 'reviewStatus',
|
|
282
|
+
operators: [
|
|
283
|
+
'equals'
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
column: LISTING_TAKEN_DOWN,
|
|
288
|
+
kind: 'boolean',
|
|
289
|
+
path: LISTING_TAKEN_DOWN
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
sorts: [
|
|
293
|
+
BY_ID
|
|
294
|
+
],
|
|
295
|
+
search: {
|
|
296
|
+
tokensPath: LISTING_NAME_TOKENS
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
/** A listing's newest bytes have no approval: the `latestVersionReviewState` values that mean it. */ export const AWAITING_REVIEW_STATES = [
|
|
300
|
+
'pending',
|
|
301
|
+
'rejected'
|
|
302
|
+
];
|
|
303
|
+
export const LISTED_REVIEW_STATUSES = [
|
|
304
|
+
'listed',
|
|
305
|
+
'verified'
|
|
306
|
+
];
|
|
307
|
+
/**
|
|
308
|
+
* One section's scope: live plugin listings, narrowed to the section. The
|
|
309
|
+
* Listed section's own `reviewStatus in [...]` steps aside for a Status
|
|
310
|
+
* clause that names one of its two values, and a clause naming any other
|
|
311
|
+
* value leaves the section empty by definition — `null`, never queried.
|
|
312
|
+
*/ export function reviewQueueBase(section, clauses) {
|
|
313
|
+
const base = [
|
|
314
|
+
{
|
|
315
|
+
path: 'type',
|
|
316
|
+
op: '==',
|
|
317
|
+
value: 'plugin'
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
path: 'deletedAt',
|
|
321
|
+
op: '==',
|
|
322
|
+
value: null
|
|
323
|
+
}
|
|
324
|
+
];
|
|
325
|
+
if (section === 'queue') {
|
|
326
|
+
base.push({
|
|
327
|
+
path: 'latestVersionReviewState',
|
|
328
|
+
op: 'in',
|
|
329
|
+
value: [
|
|
330
|
+
...AWAITING_REVIEW_STATES
|
|
331
|
+
]
|
|
332
|
+
});
|
|
333
|
+
} else if (section === 'verification') {
|
|
334
|
+
base.push({
|
|
335
|
+
path: 'verificationRequest.state',
|
|
336
|
+
op: '==',
|
|
337
|
+
value: 'pending'
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
const status = clauses.find((clause)=>clause.field === 'reviewStatus' && clause.op === 'equals' && clause.value);
|
|
341
|
+
if (status) {
|
|
342
|
+
if (!LISTED_REVIEW_STATUSES.includes(status.value)) return null;
|
|
343
|
+
} else {
|
|
344
|
+
base.push({
|
|
345
|
+
path: 'reviewStatus',
|
|
346
|
+
op: 'in',
|
|
347
|
+
value: [
|
|
348
|
+
...LISTED_REVIEW_STATUSES
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return base;
|
|
354
|
+
}
|
|
355
|
+
/** The queue's Status select as the clause it asks for, or none for "All". */ export function reviewQueueStatusClauses(status) {
|
|
356
|
+
if (!status || status === 'all') return [];
|
|
357
|
+
if (status === 'hidden') return [
|
|
358
|
+
{
|
|
359
|
+
field: LISTING_TAKEN_DOWN,
|
|
360
|
+
op: 'is',
|
|
361
|
+
value: 'true'
|
|
362
|
+
}
|
|
363
|
+
];
|
|
364
|
+
return [
|
|
365
|
+
{
|
|
366
|
+
field: 'reviewStatus',
|
|
367
|
+
op: 'equals',
|
|
368
|
+
value: status
|
|
369
|
+
}
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
/*==========================================
|
|
373
|
+
* The abuse-report queue (Admin SDK, staff route).
|
|
374
|
+
*=========================================*/ export const MARKETPLACE_REPORTS_QUERY = {
|
|
375
|
+
fields: [
|
|
376
|
+
{
|
|
377
|
+
column: 'status',
|
|
378
|
+
kind: 'exact',
|
|
379
|
+
path: 'status',
|
|
380
|
+
operators: [
|
|
381
|
+
'equals'
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
],
|
|
385
|
+
sorts: [
|
|
386
|
+
{
|
|
387
|
+
path: 'updatedAt',
|
|
388
|
+
direction: 'desc'
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
//# sourceMappingURL=listing-query.js.map
|