@aglyn/aglyn 1.0.0-beta.277 → 1.0.0-beta.283
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 +11 -11
- package/src/lib/app-utils/account-preferences.d.ts +146 -11
- package/src/lib/app-utils/account-preferences.js +266 -18
- package/src/lib/app-utils/account-preferences.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.js +2 -26
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/activity-type-labels.d.ts +18 -0
- package/src/lib/app-utils/activity-type-labels.js +46 -0
- package/src/lib/app-utils/activity-type-labels.js.map +1 -0
- package/src/lib/app-utils/artifact-provenance.d.ts +3 -1
- package/src/lib/app-utils/artifact-provenance.js.map +1 -1
- package/src/lib/app-utils/bundle-intent.d.ts +37 -0
- package/src/lib/app-utils/bundle-intent.js +47 -3
- package/src/lib/app-utils/bundle-intent.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +17 -1
- package/src/lib/app-utils/compose-reusable-components.js +80 -5
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +13 -1
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +23 -11
- package/src/lib/app-utils/docs-help.generated.js +64 -7
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +359 -51
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +19 -0
- package/src/lib/app-utils/error-beacon.js +28 -0
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +3 -2
- package/src/lib/app-utils/health-report.js +22 -2
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/native-nav.d.ts +39 -4
- package/src/lib/app-utils/native-nav.js +69 -3
- package/src/lib/app-utils/native-nav.js.map +1 -1
- package/src/lib/app-utils/native-themes.d.ts +28 -17
- package/src/lib/app-utils/native-themes.js +123 -45
- package/src/lib/app-utils/native-themes.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.js +4 -2
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/org-permission-editor.d.ts +75 -0
- package/src/lib/app-utils/org-permission-editor.js +124 -0
- package/src/lib/app-utils/org-permission-editor.js.map +1 -0
- package/src/lib/app-utils/org-permissions.d.ts +36 -0
- package/src/lib/app-utils/org-permissions.js +78 -26
- package/src/lib/app-utils/org-permissions.js.map +1 -1
- package/src/lib/app-utils/org-role-grants.d.ts +59 -9
- package/src/lib/app-utils/org-role-grants.js +97 -10
- package/src/lib/app-utils/org-role-grants.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +13 -0
- package/src/lib/app-utils/plan-entitlements.js +22 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +52 -1
- package/src/lib/app-utils/release-flags.js +68 -2
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/reusable-component-keys.d.ts +13 -0
- package/src/lib/app-utils/reusable-component-keys.js +12 -0
- package/src/lib/app-utils/reusable-component-keys.js.map +1 -1
- package/src/lib/app-utils/screen-analytics-aggregate.d.ts +34 -0
- package/src/lib/app-utils/screen-analytics-aggregate.js +37 -0
- package/src/lib/app-utils/screen-analytics-aggregate.js.map +1 -1
- package/src/lib/app-utils/site-care.d.ts +35 -0
- package/src/lib/app-utils/site-care.js +48 -0
- package/src/lib/app-utils/site-care.js.map +1 -1
- package/src/lib/app-utils/site-font-pairings.d.ts +59 -0
- package/src/lib/app-utils/site-font-pairings.js +514 -0
- package/src/lib/app-utils/site-font-pairings.js.map +1 -0
- package/src/lib/app-utils/site-starter.d.ts +136 -0
- package/src/lib/app-utils/site-starter.js +116 -0
- package/src/lib/app-utils/site-starter.js.map +1 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker-server.d.ts +95 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js +333 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js.map +1 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker.d.ts +188 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker.js +193 -0
- package/src/lib/app-utils/stock-photos/stock-photo-picker.js.map +1 -0
- package/src/lib/plugin-manager/feature-plugins.d.ts +32 -0
- package/src/lib/plugin-manager/feature-plugins.js +12 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +27 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +37 -0
- package/src/lib/plugin-manager/plugin-entitlements.js +84 -2
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-media-ingest.d.ts +15 -0
- package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
- package/src/lib/plugin-manager/plugin-permissions.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-product-catalog.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-starters.d.ts +73 -0
- package/src/lib/plugin-manager/plugin-site-starters.js +30 -0
- package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +8 -1
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -1
- package/src/lib/plugin-manager/stock-photo-provider.d.ts +12 -0
- package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
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 { SCREEN_ROOT_PATH } from "./screen-slug.js";
|
|
17
|
+
/**
|
|
18
|
+
* Site starters (AGL-4049): a whole site a person picks before it exists,
|
|
19
|
+
* every one of them a listing anyone may publish and price, the platform's own
|
|
20
|
+
* included.
|
|
21
|
+
*
|
|
22
|
+
* This module is the vocabulary every reader of a starter shares: the artifact
|
|
23
|
+
* type it is listed under, the facets a search filters on (stamped on the
|
|
24
|
+
* listing as plain fields, so a query can match them), the payload a resolved
|
|
25
|
+
* starter hands the site it creates, and the one check that payload must pass.
|
|
26
|
+
* Who stores starters and how they are bought is the starter source's business
|
|
27
|
+
* (`plugin-manager/plugin-site-starters.ts`); nothing here reads or writes.
|
|
28
|
+
*
|
|
29
|
+
* Pure data and readers, safe for any surface to import.
|
|
30
|
+
*/ /** The artifact type a site starter is listed and installed under (AGL-4049). */ export const SITE_STARTER_ARTIFACT_TYPE = 'siteStarter';
|
|
31
|
+
/**
|
|
32
|
+
* The platform's built-in themes a site's look is layered on (AGL-3497,
|
|
33
|
+
* AGL-4049), by the short name of the themes plugin's preset id
|
|
34
|
+
* (`theme-presets.<name>`), and `starter`: the theme a new site is born with.
|
|
35
|
+
* A starter on a built-in theme names one of these; the AI site start picks
|
|
36
|
+
* among the same list.
|
|
37
|
+
*/ export const SITE_THEME_BASES = [
|
|
38
|
+
'starter',
|
|
39
|
+
'material-ui',
|
|
40
|
+
'bootstrap',
|
|
41
|
+
'minimal',
|
|
42
|
+
'material3',
|
|
43
|
+
'ant-design',
|
|
44
|
+
'fluent',
|
|
45
|
+
'carbon',
|
|
46
|
+
'cupertino'
|
|
47
|
+
];
|
|
48
|
+
/** Whether a value names one of the {@link SITE_THEME_BASES}. */ export function isSiteThemeBase(value) {
|
|
49
|
+
return typeof value === 'string' && SITE_THEME_BASES.includes(value);
|
|
50
|
+
}
|
|
51
|
+
/** The most pages one starter may create. */ export const SITE_STARTER_MAX_SCREENS = 25;
|
|
52
|
+
const isRecord = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
|
|
53
|
+
const facetString = (value)=>typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
54
|
+
/**
|
|
55
|
+
* A listing's starter facets, read defensively from the fields
|
|
56
|
+
* {@link siteStarterFacetFields} stamps: a missing or malformed field reads as
|
|
57
|
+
* none, a page count as a whole number from zero, and a mode as `base` unless
|
|
58
|
+
* it says `ai`.
|
|
59
|
+
*/ export function siteStarterFacetsOf(doc) {
|
|
60
|
+
const pages = doc['starterPages'];
|
|
61
|
+
return {
|
|
62
|
+
kind: facetString(doc['starterKind']),
|
|
63
|
+
base: facetString(doc['starterBase']),
|
|
64
|
+
fonts: facetString(doc['starterFonts']),
|
|
65
|
+
pages: typeof pages === 'number' && Number.isFinite(pages) && pages > 0 ? Math.floor(pages) : 0,
|
|
66
|
+
mode: doc['starterMode'] === 'ai' ? 'ai' : 'base'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** The listing fields a starter's publish stamps, the inverse of {@link siteStarterFacetsOf}. */ export function siteStarterFacetFields(facets) {
|
|
70
|
+
return {
|
|
71
|
+
starterKind: facets.kind,
|
|
72
|
+
starterBase: facets.base,
|
|
73
|
+
starterFonts: facets.fonts,
|
|
74
|
+
starterPages: facets.pages,
|
|
75
|
+
starterMode: facets.mode
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const FACET_KEYS = [
|
|
79
|
+
'kind',
|
|
80
|
+
'base',
|
|
81
|
+
'fonts',
|
|
82
|
+
'pages',
|
|
83
|
+
'mode'
|
|
84
|
+
];
|
|
85
|
+
/** Whether a starter's facets meet a filter: every facet the filter names, equal; an absent one, any. */ export function matchSiteStarterFacets(facets, filter) {
|
|
86
|
+
return FACET_KEYS.every((key)=>filter[key] === undefined || facets[key] === filter[key]);
|
|
87
|
+
}
|
|
88
|
+
/** Whether a page is the published home page: routed at the site root and not marked unpublished. */ const isPublishedHome = (screen)=>isRecord(screen) && screen['slug'] === SCREEN_ROOT_PATH && screen['published'] !== false;
|
|
89
|
+
/**
|
|
90
|
+
* Why a starter's payload cannot start a site, in the words its author is
|
|
91
|
+
* told, or `null` when it can (AGL-4049). Checked when a starter is published
|
|
92
|
+
* and again when one is resolved, since a site is created from it with no
|
|
93
|
+
* step between to catch what is missing.
|
|
94
|
+
*/ export function siteStarterRefusal(payload, known = {}) {
|
|
95
|
+
const screens = Array.isArray(payload.screens) ? payload.screens : [];
|
|
96
|
+
if (!screens.some(isPublishedHome)) return 'A starter needs a published Home page at /';
|
|
97
|
+
if (!isRecord(payload.layout)) return 'A starter needs a shared layout';
|
|
98
|
+
if (screens.length > SITE_STARTER_MAX_SCREENS) {
|
|
99
|
+
return `A starter can have at most ${SITE_STARTER_MAX_SCREENS} pages`;
|
|
100
|
+
}
|
|
101
|
+
const unknown = (id, ids)=>id !== null && ids !== undefined && !ids.has(id);
|
|
102
|
+
const { facets, theme } = payload;
|
|
103
|
+
if (unknown(facets.kind, known.kinds)) return `Unknown site kind "${facets.kind}"`;
|
|
104
|
+
if (unknown(facets.base, known.bases)) return `Unknown base theme "${facets.base}"`;
|
|
105
|
+
if (unknown(facets.fonts, known.fonts)) return `Unknown font pairing "${facets.fonts}"`;
|
|
106
|
+
if ((theme == null ? void 0 : theme.kind) === 'builtin') {
|
|
107
|
+
if (unknown(theme.base, known.bases)) return `Unknown base theme "${theme.base}"`;
|
|
108
|
+
if (unknown(theme.fonts, known.fonts)) return `Unknown font pairing "${theme.fonts}"`;
|
|
109
|
+
}
|
|
110
|
+
if (payload.mode === 'ai' && !isRecord(payload.answers)) {
|
|
111
|
+
return 'An AI starter needs the questions it asks';
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=site-starter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-starter.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 { HostTheme } from '@aglyn/shared-data-types'\nimport { SCREEN_ROOT_PATH } from './screen-slug'\n\n/**\n * Site starters (AGL-4049): a whole site a person picks before it exists,\n * every one of them a listing anyone may publish and price, the platform's own\n * included.\n *\n * This module is the vocabulary every reader of a starter shares: the artifact\n * type it is listed under, the facets a search filters on (stamped on the\n * listing as plain fields, so a query can match them), the payload a resolved\n * starter hands the site it creates, and the one check that payload must pass.\n * Who stores starters and how they are bought is the starter source's business\n * (`plugin-manager/plugin-site-starters.ts`); nothing here reads or writes.\n *\n * Pure data and readers, safe for any surface to import.\n */\n\n/** The artifact type a site starter is listed and installed under (AGL-4049). */\nexport const SITE_STARTER_ARTIFACT_TYPE = 'siteStarter' as const\n\n/**\n * How a starter fills the site it creates: `base` copies its pages as they\n * are; `ai` hands them to the AI site start with the answers it asks for, so\n * the pages are rewritten for the business.\n */\nexport type SiteStarterMode = 'base' | 'ai'\n\n/**\n * The platform's built-in themes a site's look is layered on (AGL-3497,\n * AGL-4049), by the short name of the themes plugin's preset id\n * (`theme-presets.<name>`), and `starter`: the theme a new site is born with.\n * A starter on a built-in theme names one of these; the AI site start picks\n * among the same list.\n */\nexport const SITE_THEME_BASES = [\n 'starter',\n 'material-ui',\n 'bootstrap',\n 'minimal',\n 'material3',\n 'ant-design',\n 'fluent',\n 'carbon',\n 'cupertino',\n] as const\nexport type SiteThemeBase = (typeof SITE_THEME_BASES)[number]\n\n/** Whether a value names one of the {@link SITE_THEME_BASES}. */\nexport function isSiteThemeBase(value: unknown): value is SiteThemeBase {\n return typeof value === 'string' && (SITE_THEME_BASES as readonly string[]).includes(value)\n}\n\n/**\n * A starter's theme (AGL-4049), one of two shapes: a built-in theme with the\n * starter's brand colors and font pairing over it, which is how a person's own\n * pick and edits are stored, or a full theme of its author's own.\n */\nexport type SiteStarterTheme =\n | {\n kind: 'builtin'\n /** One of the {@link SITE_THEME_BASES}. */\n base: string\n /** Palette overrides by scheme, as the theme editor stores them. */\n brand: { light: Record<string, string>; dark?: Record<string, string> }\n /** A font pairing id (`site-font-pairings.ts`), or `null` for the base theme's own fonts. */\n fonts: string | null\n }\n | { kind: 'full'; theme: HostTheme }\n\n/**\n * What a starter search filters on (AGL-4049). `kind` is the kind of site it\n * is, an id the core does not interpret (the AI plugin keeps the kinds),\n * `base` a {@link SITE_THEME_BASES} name and `fonts` a font pairing id\n * (`site-font-pairings.ts`), each `null` where the starter names none;\n * `pages` is how many pages it creates.\n */\nexport interface SiteStarterFacets {\n kind: string | null\n base: string | null\n fonts: string | null\n pages: number\n mode: SiteStarterMode\n}\n\n/** A person's pick: the listing, and the version when one is pinned. */\nexport interface SiteStarterChoice {\n listingId: string\n version?: string\n}\n\n/** What a resolved starter hands the site it creates (AGL-4049). */\nexport interface SiteStarterPayload {\n mode: SiteStarterMode\n facets: SiteStarterFacets\n theme: SiteStarterTheme\n /**\n * The starter's pages, each carrying its routing `slug` as a starter\n * template's screens do: `'/'` is the home page, and a page without one is\n * unpublished.\n */\n screens: unknown[]\n /** The shared layout every page sits in: the header and footer. */\n layout: unknown | null\n /** The questions an `ai` starter asks before the site job runs; required for `ai`. */\n answers?: Record<string, unknown>\n}\n\n/** The most pages one starter may create. */\nexport const SITE_STARTER_MAX_SCREENS = 25\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object' && !Array.isArray(value)\n\nconst facetString = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/**\n * A listing's starter facets, read defensively from the fields\n * {@link siteStarterFacetFields} stamps: a missing or malformed field reads as\n * none, a page count as a whole number from zero, and a mode as `base` unless\n * it says `ai`.\n */\nexport function siteStarterFacetsOf(doc: Record<string, unknown>): SiteStarterFacets {\n const pages = doc['starterPages']\n return {\n kind: facetString(doc['starterKind']),\n base: facetString(doc['starterBase']),\n fonts: facetString(doc['starterFonts']),\n pages: typeof pages === 'number' && Number.isFinite(pages) && pages > 0 ? Math.floor(pages) : 0,\n mode: doc['starterMode'] === 'ai' ? 'ai' : 'base',\n }\n}\n\n/** The listing fields a starter's publish stamps, the inverse of {@link siteStarterFacetsOf}. */\nexport function siteStarterFacetFields(facets: SiteStarterFacets): Record<string, unknown> {\n return {\n starterKind: facets.kind,\n starterBase: facets.base,\n starterFonts: facets.fonts,\n starterPages: facets.pages,\n starterMode: facets.mode,\n }\n}\n\nconst FACET_KEYS: readonly (keyof SiteStarterFacets)[] = ['kind', 'base', 'fonts', 'pages', 'mode']\n\n/** Whether a starter's facets meet a filter: every facet the filter names, equal; an absent one, any. */\nexport function matchSiteStarterFacets(\n facets: SiteStarterFacets,\n filter: Partial<SiteStarterFacets>,\n): boolean {\n return FACET_KEYS.every((key) => filter[key] === undefined || facets[key] === filter[key])\n}\n\n/** Whether a page is the published home page: routed at the site root and not marked unpublished. */\nconst isPublishedHome = (screen: unknown): boolean =>\n isRecord(screen) && screen['slug'] === SCREEN_ROOT_PATH && screen['published'] !== false\n\n/**\n * The ids a caller knows, by facet. `siteStarterRefusal` checks a kind, base\n * or font pairing id only against the set its caller passes: the core keeps\n * no list of kinds, and which bases and pairings are offered is the caller's\n * to say (`new Set(SITE_THEME_BASES)`, the ids of `SITE_FONT_PAIRINGS`).\n */\nexport interface SiteStarterKnownIds {\n kinds?: ReadonlySet<string>\n bases?: ReadonlySet<string>\n fonts?: ReadonlySet<string>\n}\n\n/**\n * Why a starter's payload cannot start a site, in the words its author is\n * told, or `null` when it can (AGL-4049). Checked when a starter is published\n * and again when one is resolved, since a site is created from it with no\n * step between to catch what is missing.\n */\nexport function siteStarterRefusal(payload: SiteStarterPayload, known: SiteStarterKnownIds = {}): string | null {\n const screens = Array.isArray(payload.screens) ? payload.screens : []\n if (!screens.some(isPublishedHome)) return 'A starter needs a published Home page at /'\n if (!isRecord(payload.layout)) return 'A starter needs a shared layout'\n if (screens.length > SITE_STARTER_MAX_SCREENS) {\n return `A starter can have at most ${SITE_STARTER_MAX_SCREENS} pages`\n }\n const unknown = (id: string | null, ids: ReadonlySet<string> | undefined) =>\n id !== null && ids !== undefined && !ids.has(id)\n const { facets, theme } = payload\n if (unknown(facets.kind, known.kinds)) return `Unknown site kind \"${facets.kind}\"`\n if (unknown(facets.base, known.bases)) return `Unknown base theme \"${facets.base}\"`\n if (unknown(facets.fonts, known.fonts)) return `Unknown font pairing \"${facets.fonts}\"`\n if (theme?.kind === 'builtin') {\n if (unknown(theme.base, known.bases)) return `Unknown base theme \"${theme.base}\"`\n if (unknown(theme.fonts, known.fonts)) return `Unknown font pairing \"${theme.fonts}\"`\n }\n if (payload.mode === 'ai' && !isRecord(payload.answers)) {\n return 'An AI starter needs the questions it asks'\n }\n return null\n}\n"],"names":["SCREEN_ROOT_PATH","SITE_STARTER_ARTIFACT_TYPE","SITE_THEME_BASES","isSiteThemeBase","value","includes","SITE_STARTER_MAX_SCREENS","isRecord","Array","isArray","facetString","trim","siteStarterFacetsOf","doc","pages","kind","base","fonts","Number","isFinite","Math","floor","mode","siteStarterFacetFields","facets","starterKind","starterBase","starterFonts","starterPages","starterMode","FACET_KEYS","matchSiteStarterFacets","filter","every","key","undefined","isPublishedHome","screen","siteStarterRefusal","payload","known","screens","some","layout","length","unknown","id","ids","has","theme","kinds","bases","answers"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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 PluginMediaIngest, type PluginMediaIngestRequest } from '../../plugin-manager/plugin-media-ingest';
|
|
18
|
+
import { type StockPhoto, type StockPhotoProvider } from '../../plugin-manager/stock-photo-provider';
|
|
19
|
+
import { type StockPhotoHitRef, type StockPhotoPickerHit, type StockPhotoPickerTarget } from './stock-photo-picker';
|
|
20
|
+
/**
|
|
21
|
+
* The server half of the media picker's Stock photos tab (AGL-4066). The
|
|
22
|
+
* console's route verifies the caller and hands this the request body with
|
|
23
|
+
* two doors of its own: {@link StockPhotoPickerServerDeps.authorize}, the
|
|
24
|
+
* same scope check an upload makes (the member may add files to that
|
|
25
|
+
* library, the lockdown's uploads gate), and
|
|
26
|
+
* {@link StockPhotoPickerServerDeps.readMedia}, the stored document. The rest
|
|
27
|
+
* is here, against core's contracts alone:
|
|
28
|
+
*
|
|
29
|
+
* - `status` — whether a library is configured, and its name.
|
|
30
|
+
* - `search` — the provider's own search, its 24-hour cache and its rate
|
|
31
|
+
* limit in charge. A `null` answer (rate-limited, or the library down) is
|
|
32
|
+
* `busy`, a state the tab shows, never a 5xx: a library's bad minute is
|
|
33
|
+
* not a fault of the console's.
|
|
34
|
+
* - `preview` — a hit's small copy, as bytes, so no browser loads an
|
|
35
|
+
* address at the library.
|
|
36
|
+
* - `import` — the photo copied into the library through `core.media-ingest`
|
|
37
|
+
* as the member, which makes the upload's own checks (access, lockdown,
|
|
38
|
+
* structure, takedowns, plan and storage band). A copy the library already
|
|
39
|
+
* holds under the photo's source key is placed instead, never stored a
|
|
40
|
+
* second time. The credit rides the asset as the AI build writes it:
|
|
41
|
+
* `description` "Photo by … on …" and `stockPhoto` with the photographer,
|
|
42
|
+
* the photo's page and the license.
|
|
43
|
+
*
|
|
44
|
+
* A hit is found again by the search that found it, which the provider
|
|
45
|
+
* answers from its cache, so the addresses and the credit are always the
|
|
46
|
+
* provider's own and never what a browser sent.
|
|
47
|
+
*/
|
|
48
|
+
/** The largest stored copy a photo may be. */
|
|
49
|
+
export declare const STOCK_PHOTO_IMPORT_MAX_BYTES: number;
|
|
50
|
+
/** The largest preview handed to a browser. */
|
|
51
|
+
export declare const STOCK_PHOTO_PREVIEW_MAX_BYTES: number;
|
|
52
|
+
/** How long one process keeps a search's photos to find hits again in, before asking the provider. */
|
|
53
|
+
export declare const STOCK_PHOTO_HIT_MEMO_MS: number;
|
|
54
|
+
export interface StockPhotoPickerServerDeps {
|
|
55
|
+
/** The signed-in member. */
|
|
56
|
+
uid: string;
|
|
57
|
+
/**
|
|
58
|
+
* `null` when the member may add files to `target`'s library; otherwise
|
|
59
|
+
* the refusal to answer with (401, 403, 404, 423 …).
|
|
60
|
+
*/
|
|
61
|
+
authorize(target: StockPhotoPickerTarget): Promise<Response | null>;
|
|
62
|
+
/** The stored media document `mediaId` in `target`'s library, or `null`. */
|
|
63
|
+
readMedia(target: StockPhotoPickerTarget, mediaId: string): Promise<Record<string, unknown> | null>;
|
|
64
|
+
provider?: () => StockPhotoProvider | null;
|
|
65
|
+
ingest?: () => PluginMediaIngest | null;
|
|
66
|
+
memo?: StockPhotoHitMemo;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
/** A process's recent searches, to find a hit again without asking the provider. */
|
|
70
|
+
export interface StockPhotoHitMemo {
|
|
71
|
+
get(key: string): StockPhoto[] | null;
|
|
72
|
+
set(key: string, photos: StockPhoto[]): void;
|
|
73
|
+
}
|
|
74
|
+
/** A memo of the last {@link HIT_MEMO_SIZE} searches, each kept {@link STOCK_PHOTO_HIT_MEMO_MS}. */
|
|
75
|
+
export declare function createStockPhotoHitMemo(now?: () => number): StockPhotoHitMemo;
|
|
76
|
+
/** A provider's hit as the tab shows it. */
|
|
77
|
+
export declare function stockPhotoPickerHitOf(provider: Pick<StockPhotoProvider, 'credit'>, photo: StockPhoto, search: Pick<StockPhotoHitRef, 'query' | 'orientation'>): StockPhotoPickerHit | null;
|
|
78
|
+
/**
|
|
79
|
+
* What the stored copy of `photo` is sent to the media library as: the
|
|
80
|
+
* shape the AI build's copies take, so every stock asset carries one credit.
|
|
81
|
+
*/
|
|
82
|
+
export declare function stockPhotoPickerIngestRequest(input: {
|
|
83
|
+
provider: Pick<StockPhotoProvider, 'credit'>;
|
|
84
|
+
target: StockPhotoPickerTarget;
|
|
85
|
+
uid: string;
|
|
86
|
+
photo: StockPhoto;
|
|
87
|
+
bytes: {
|
|
88
|
+
bytes: Uint8Array;
|
|
89
|
+
contentType: string;
|
|
90
|
+
};
|
|
91
|
+
query: string;
|
|
92
|
+
folderId?: string | null;
|
|
93
|
+
}): PluginMediaIngestRequest;
|
|
94
|
+
/** Answers one request of the Stock photos tab. Never throws for the library's sake. */
|
|
95
|
+
export declare function answerStockPhotoPickerRequest(body: unknown, deps: StockPhotoPickerServerDeps): Promise<Response>;
|
|
@@ -0,0 +1,333 @@
|
|
|
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 { pluginMediaIngest } from "../../plugin-manager/plugin-media-ingest.js";
|
|
18
|
+
import { stockPhotoProvider, stockPhotoSourceKey } from "../../plugin-manager/stock-photo-provider.js";
|
|
19
|
+
import { normalizeStockPhotoQuery, STOCK_PHOTO_GONE_MESSAGE, stockPhotoHitRefOf, stockPhotoOrientationOf, stockPhotoPickerMediaOf, stockPhotoSafeLink, STOCK_PHOTOS_UNCONFIGURED_MESSAGE, stockPhotosBusyMessage, stockPhotoTargetOf } from "./stock-photo-picker.js";
|
|
20
|
+
/**
|
|
21
|
+
* The server half of the media picker's Stock photos tab (AGL-4066). The
|
|
22
|
+
* console's route verifies the caller and hands this the request body with
|
|
23
|
+
* two doors of its own: {@link StockPhotoPickerServerDeps.authorize}, the
|
|
24
|
+
* same scope check an upload makes (the member may add files to that
|
|
25
|
+
* library, the lockdown's uploads gate), and
|
|
26
|
+
* {@link StockPhotoPickerServerDeps.readMedia}, the stored document. The rest
|
|
27
|
+
* is here, against core's contracts alone:
|
|
28
|
+
*
|
|
29
|
+
* - `status` — whether a library is configured, and its name.
|
|
30
|
+
* - `search` — the provider's own search, its 24-hour cache and its rate
|
|
31
|
+
* limit in charge. A `null` answer (rate-limited, or the library down) is
|
|
32
|
+
* `busy`, a state the tab shows, never a 5xx: a library's bad minute is
|
|
33
|
+
* not a fault of the console's.
|
|
34
|
+
* - `preview` — a hit's small copy, as bytes, so no browser loads an
|
|
35
|
+
* address at the library.
|
|
36
|
+
* - `import` — the photo copied into the library through `core.media-ingest`
|
|
37
|
+
* as the member, which makes the upload's own checks (access, lockdown,
|
|
38
|
+
* structure, takedowns, plan and storage band). A copy the library already
|
|
39
|
+
* holds under the photo's source key is placed instead, never stored a
|
|
40
|
+
* second time. The credit rides the asset as the AI build writes it:
|
|
41
|
+
* `description` "Photo by … on …" and `stockPhoto` with the photographer,
|
|
42
|
+
* the photo's page and the license.
|
|
43
|
+
*
|
|
44
|
+
* A hit is found again by the search that found it, which the provider
|
|
45
|
+
* answers from its cache, so the addresses and the credit are always the
|
|
46
|
+
* provider's own and never what a browser sent.
|
|
47
|
+
*/ /** The largest stored copy a photo may be. */ export const STOCK_PHOTO_IMPORT_MAX_BYTES = 10 * 1024 * 1024;
|
|
48
|
+
/** The largest preview handed to a browser. */ export const STOCK_PHOTO_PREVIEW_MAX_BYTES = 2 * 1024 * 1024;
|
|
49
|
+
/** How long a browser may keep a preview. */ const PREVIEW_CACHE_CONTROL = 'private, max-age=3600';
|
|
50
|
+
/** How long one process keeps a search's photos to find hits again in, before asking the provider. */ export const STOCK_PHOTO_HIT_MEMO_MS = 5 * 60 * 1000;
|
|
51
|
+
/** Searches one process keeps in that memo. */ const HIT_MEMO_SIZE = 50;
|
|
52
|
+
/** The tags a hit shows. */ const HIT_TAGS = 8;
|
|
53
|
+
/** The file extension a stored copy is named with, by type. */ const EXTENSIONS = {
|
|
54
|
+
'image/jpeg': 'jpg',
|
|
55
|
+
'image/png': 'png',
|
|
56
|
+
'image/webp': 'webp',
|
|
57
|
+
'image/gif': 'gif'
|
|
58
|
+
};
|
|
59
|
+
/** A memo of the last {@link HIT_MEMO_SIZE} searches, each kept {@link STOCK_PHOTO_HIT_MEMO_MS}. */ export function createStockPhotoHitMemo(now = Date.now) {
|
|
60
|
+
const held = new Map();
|
|
61
|
+
return {
|
|
62
|
+
get (key) {
|
|
63
|
+
const entry = held.get(key);
|
|
64
|
+
if (!entry) return null;
|
|
65
|
+
if (now() - entry.at > STOCK_PHOTO_HIT_MEMO_MS) {
|
|
66
|
+
held.delete(key);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return entry.photos;
|
|
70
|
+
},
|
|
71
|
+
set (key, photos) {
|
|
72
|
+
held.delete(key);
|
|
73
|
+
held.set(key, {
|
|
74
|
+
at: now(),
|
|
75
|
+
photos
|
|
76
|
+
});
|
|
77
|
+
while(held.size > HIT_MEMO_SIZE)held.delete(held.keys().next().value);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const processMemo = createStockPhotoHitMemo();
|
|
82
|
+
const json = (body, status = 200)=>Response.json(body, {
|
|
83
|
+
status,
|
|
84
|
+
headers: {
|
|
85
|
+
'Cache-Control': 'no-store'
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const refused = (status, message)=>json({
|
|
89
|
+
status: 'refused',
|
|
90
|
+
message
|
|
91
|
+
}, status);
|
|
92
|
+
/** The memo's key for a search. */ const memoKey = (provider, ref)=>`${provider.id}\n${ref.orientation}\n${ref.query.toLowerCase()}`;
|
|
93
|
+
/** A provider's hit as the tab shows it. */ export function stockPhotoPickerHitOf(provider, photo, search) {
|
|
94
|
+
var _photo_tags;
|
|
95
|
+
const pageUrl = stockPhotoSafeLink(photo.pageUrl);
|
|
96
|
+
if (!photo.id || !pageUrl || !(photo.width > 0) || !(photo.height > 0)) return null;
|
|
97
|
+
const credit = provider.credit(photo);
|
|
98
|
+
const licenseUrl = stockPhotoSafeLink(credit.licenseUrl);
|
|
99
|
+
const photographerUrl = stockPhotoSafeLink(photo.photographerUrl);
|
|
100
|
+
return _extends({
|
|
101
|
+
ref: {
|
|
102
|
+
query: search.query,
|
|
103
|
+
orientation: search.orientation,
|
|
104
|
+
id: photo.id
|
|
105
|
+
},
|
|
106
|
+
sourceKey: stockPhotoSourceKey(photo),
|
|
107
|
+
width: photo.width,
|
|
108
|
+
height: photo.height,
|
|
109
|
+
photographer: photo.photographer
|
|
110
|
+
}, photographerUrl ? {
|
|
111
|
+
photographerUrl
|
|
112
|
+
} : {}, {
|
|
113
|
+
pageUrl,
|
|
114
|
+
tags: ((_photo_tags = photo.tags) != null ? _photo_tags : []).slice(0, HIT_TAGS)
|
|
115
|
+
}, photo.alt ? {
|
|
116
|
+
alt: photo.alt
|
|
117
|
+
} : {}, {
|
|
118
|
+
credit: credit.text,
|
|
119
|
+
license: credit.license,
|
|
120
|
+
licenseUrl: licenseUrl != null ? licenseUrl : '',
|
|
121
|
+
attributionRequired: credit.attributionRequired
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* What the stored copy of `photo` is sent to the media library as: the
|
|
126
|
+
* shape the AI build's copies take, so every stock asset carries one credit.
|
|
127
|
+
*/ export function stockPhotoPickerIngestRequest(input) {
|
|
128
|
+
var _target_hostId, _EXTENSIONS_bytes_contentType;
|
|
129
|
+
const { photo, bytes, target } = input;
|
|
130
|
+
const credit = input.provider.credit(photo);
|
|
131
|
+
const photographerUrl = stockPhotoSafeLink(photo.photographerUrl);
|
|
132
|
+
return _extends({
|
|
133
|
+
hostId: (_target_hostId = target.hostId) != null ? _target_hostId : ''
|
|
134
|
+
}, target.orgId ? {
|
|
135
|
+
orgId: target.orgId
|
|
136
|
+
} : {}, {
|
|
137
|
+
uid: input.uid,
|
|
138
|
+
fileName: `${photo.provider}-${photo.id}.${(_EXTENSIONS_bytes_contentType = EXTENSIONS[bytes.contentType]) != null ? _EXTENSIONS_bytes_contentType : 'jpg'}`,
|
|
139
|
+
contentType: bytes.contentType,
|
|
140
|
+
bytes: bytes.bytes
|
|
141
|
+
}, photo.alt ? {
|
|
142
|
+
alt: photo.alt
|
|
143
|
+
} : {}, {
|
|
144
|
+
description: credit.text
|
|
145
|
+
}, input.folderId ? {
|
|
146
|
+
folderId: input.folderId
|
|
147
|
+
} : {}, {
|
|
148
|
+
stockPhoto: _extends({
|
|
149
|
+
key: stockPhotoSourceKey(photo),
|
|
150
|
+
provider: photo.provider,
|
|
151
|
+
providerLabel: credit.providerLabel,
|
|
152
|
+
id: photo.id,
|
|
153
|
+
pageUrl: photo.pageUrl,
|
|
154
|
+
photographer: photo.photographer
|
|
155
|
+
}, photographerUrl ? {
|
|
156
|
+
photographerUrl
|
|
157
|
+
} : {}, {
|
|
158
|
+
license: credit.license,
|
|
159
|
+
licenseUrl: credit.licenseUrl,
|
|
160
|
+
attributionRequired: credit.attributionRequired,
|
|
161
|
+
query: input.query
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** The photo `ref` names, found again in its search; `null` when the provider cannot be asked, `false` when it is gone. */ async function findHit(provider, ref, memo, signal) {
|
|
166
|
+
var _photos_find;
|
|
167
|
+
const key = memoKey(provider, ref);
|
|
168
|
+
let photos = memo.get(key);
|
|
169
|
+
if (!photos) {
|
|
170
|
+
const answer = await provider.search({
|
|
171
|
+
query: ref.query,
|
|
172
|
+
orientation: ref.orientation
|
|
173
|
+
}, signal ? {
|
|
174
|
+
signal
|
|
175
|
+
} : undefined);
|
|
176
|
+
if (!answer) return null;
|
|
177
|
+
photos = answer.photos;
|
|
178
|
+
memo.set(key, photos);
|
|
179
|
+
}
|
|
180
|
+
return (_photos_find = photos.find((photo)=>photo.id === ref.id)) != null ? _photos_find : false;
|
|
181
|
+
}
|
|
182
|
+
/** Answers one request of the Stock photos tab. Never throws for the library's sake. */ export async function answerStockPhotoPickerRequest(body, deps) {
|
|
183
|
+
var _deps_provider, _deps_memo, _deps_ingest, _target_hostId;
|
|
184
|
+
const record = body != null ? body : {};
|
|
185
|
+
const action = record['action'];
|
|
186
|
+
if (action !== 'status' && action !== 'search' && action !== 'preview' && action !== 'import') {
|
|
187
|
+
return refused(400, 'Unknown request');
|
|
188
|
+
}
|
|
189
|
+
const target = stockPhotoTargetOf(record);
|
|
190
|
+
if (!target) return refused(400, 'Name the site or the workspace whose library to use');
|
|
191
|
+
const denied = await deps.authorize(target);
|
|
192
|
+
if (denied) return denied;
|
|
193
|
+
const provider = ((_deps_provider = deps.provider) != null ? _deps_provider : stockPhotoProvider)();
|
|
194
|
+
if (!provider) {
|
|
195
|
+
if (action === 'preview') return new Response(null, {
|
|
196
|
+
status: 204
|
|
197
|
+
});
|
|
198
|
+
return json({
|
|
199
|
+
status: 'unconfigured',
|
|
200
|
+
message: STOCK_PHOTOS_UNCONFIGURED_MESSAGE
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const library = {
|
|
204
|
+
label: provider.label
|
|
205
|
+
};
|
|
206
|
+
const memo = (_deps_memo = deps.memo) != null ? _deps_memo : processMemo;
|
|
207
|
+
const signal = deps.signal;
|
|
208
|
+
if (action === 'status') return json({
|
|
209
|
+
status: 'ready',
|
|
210
|
+
library
|
|
211
|
+
});
|
|
212
|
+
if (action === 'search') {
|
|
213
|
+
const query = normalizeStockPhotoQuery(record['query']);
|
|
214
|
+
if (!query) return refused(400, 'Type a few words to search for, up to 100 characters');
|
|
215
|
+
const orientation = stockPhotoOrientationOf(record['orientation']);
|
|
216
|
+
let answer = null;
|
|
217
|
+
try {
|
|
218
|
+
answer = await provider.search({
|
|
219
|
+
query,
|
|
220
|
+
orientation
|
|
221
|
+
}, signal ? {
|
|
222
|
+
signal
|
|
223
|
+
} : undefined);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.warn('stock photos: the search failed', {
|
|
226
|
+
provider: provider.id,
|
|
227
|
+
error: String(error).slice(0, 160)
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (!answer) return json({
|
|
231
|
+
status: 'busy',
|
|
232
|
+
message: stockPhotosBusyMessage(provider.label)
|
|
233
|
+
});
|
|
234
|
+
memo.set(memoKey(provider, {
|
|
235
|
+
query,
|
|
236
|
+
orientation
|
|
237
|
+
}), answer.photos);
|
|
238
|
+
const hits = answer.photos.map((photo)=>stockPhotoPickerHitOf(provider, photo, {
|
|
239
|
+
query,
|
|
240
|
+
orientation
|
|
241
|
+
})).filter((hit)=>hit !== null);
|
|
242
|
+
return json({
|
|
243
|
+
status: 'ok',
|
|
244
|
+
library,
|
|
245
|
+
query,
|
|
246
|
+
orientation,
|
|
247
|
+
hits
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const ref = stockPhotoHitRefOf(record['ref']);
|
|
251
|
+
if (!ref) return refused(400, 'That photo could not be read. Search again and choose it from the results.');
|
|
252
|
+
let photo = null;
|
|
253
|
+
try {
|
|
254
|
+
photo = await findHit(provider, ref, memo, signal);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn('stock photos: a search to find a photo again failed', {
|
|
257
|
+
provider: provider.id,
|
|
258
|
+
error: String(error).slice(0, 160)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (action === 'preview') {
|
|
262
|
+
if (!photo || typeof provider.preview !== 'function') return new Response(null, {
|
|
263
|
+
status: 204
|
|
264
|
+
});
|
|
265
|
+
const small = await provider.preview(photo, _extends({
|
|
266
|
+
maxBytes: STOCK_PHOTO_PREVIEW_MAX_BYTES
|
|
267
|
+
}, signal ? {
|
|
268
|
+
signal
|
|
269
|
+
} : {})).catch(()=>null);
|
|
270
|
+
if (!small) return new Response(null, {
|
|
271
|
+
status: 204
|
|
272
|
+
});
|
|
273
|
+
return new Response(new Uint8Array(small.bytes), {
|
|
274
|
+
status: 200,
|
|
275
|
+
headers: {
|
|
276
|
+
'Content-Type': small.contentType,
|
|
277
|
+
'Cache-Control': PREVIEW_CACHE_CONTROL,
|
|
278
|
+
'X-Content-Type-Options': 'nosniff'
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// import
|
|
283
|
+
if (photo === null) return json({
|
|
284
|
+
status: 'busy',
|
|
285
|
+
message: stockPhotosBusyMessage(provider.label)
|
|
286
|
+
});
|
|
287
|
+
if (photo === false) return json({
|
|
288
|
+
status: 'gone',
|
|
289
|
+
message: STOCK_PHOTO_GONE_MESSAGE
|
|
290
|
+
}, 404);
|
|
291
|
+
const ingest = ((_deps_ingest = deps.ingest) != null ? _deps_ingest : pluginMediaIngest)();
|
|
292
|
+
if (!ingest) return refused(503, 'The media library cannot take photos this way right now. Try again in a minute.');
|
|
293
|
+
const sourceKey = stockPhotoSourceKey(photo);
|
|
294
|
+
const placed = async (mediaId, reused)=>{
|
|
295
|
+
const data = await deps.readMedia(target, mediaId);
|
|
296
|
+
if (!data) return refused(500, 'The photo was stored but could not be read back. It is in the library; choose it there.');
|
|
297
|
+
return json({
|
|
298
|
+
status: 'ok',
|
|
299
|
+
reused,
|
|
300
|
+
media: stockPhotoPickerMediaOf(mediaId, data)
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
const held = await ingest.findStockPhoto(_extends({
|
|
304
|
+
hostId: (_target_hostId = target.hostId) != null ? _target_hostId : '',
|
|
305
|
+
sourceKey
|
|
306
|
+
}, target.orgId ? {
|
|
307
|
+
orgId: target.orgId
|
|
308
|
+
} : {}));
|
|
309
|
+
if (held) return placed(held.mediaId, true);
|
|
310
|
+
const bytes = await provider.download(photo, _extends({
|
|
311
|
+
maxBytes: STOCK_PHOTO_IMPORT_MAX_BYTES
|
|
312
|
+
}, signal ? {
|
|
313
|
+
signal
|
|
314
|
+
} : {})).catch(()=>null);
|
|
315
|
+
if (!bytes) return json({
|
|
316
|
+
status: 'busy',
|
|
317
|
+
message: stockPhotosBusyMessage(provider.label)
|
|
318
|
+
});
|
|
319
|
+
const folderId = typeof record['folderId'] === 'string' && record['folderId'] ? String(record['folderId']).slice(0, 64) : null;
|
|
320
|
+
const stored = await ingest.ingest(stockPhotoPickerIngestRequest({
|
|
321
|
+
provider,
|
|
322
|
+
target,
|
|
323
|
+
uid: deps.uid,
|
|
324
|
+
photo,
|
|
325
|
+
bytes,
|
|
326
|
+
query: ref.query,
|
|
327
|
+
folderId
|
|
328
|
+
}));
|
|
329
|
+
if (stored.ok === false) return refused(stored.status >= 400 ? stored.status : 500, stored.reason);
|
|
330
|
+
return placed(stored.mediaId, false);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
//# sourceMappingURL=stock-photo-picker-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/stock-photos/stock-photo-picker-server.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 pluginMediaIngest,\n type PluginMediaIngest,\n type PluginMediaIngestRequest,\n} from '../../plugin-manager/plugin-media-ingest'\nimport {\n stockPhotoProvider,\n stockPhotoSourceKey,\n type StockPhoto,\n type StockPhotoProvider,\n} from '../../plugin-manager/stock-photo-provider'\nimport {\n normalizeStockPhotoQuery,\n STOCK_PHOTO_GONE_MESSAGE,\n stockPhotoHitRefOf,\n stockPhotoOrientationOf,\n stockPhotoPickerMediaOf,\n stockPhotoSafeLink,\n STOCK_PHOTOS_UNCONFIGURED_MESSAGE,\n stockPhotosBusyMessage,\n stockPhotoTargetOf,\n type StockPhotoHitRef,\n type StockPhotoImportAnswer,\n type StockPhotoPickerHit,\n type StockPhotoPickerTarget,\n type StockPhotoSearchAnswer,\n type StockPhotoStatusAnswer,\n} from './stock-photo-picker'\n\n/**\n * The server half of the media picker's Stock photos tab (AGL-4066). The\n * console's route verifies the caller and hands this the request body with\n * two doors of its own: {@link StockPhotoPickerServerDeps.authorize}, the\n * same scope check an upload makes (the member may add files to that\n * library, the lockdown's uploads gate), and\n * {@link StockPhotoPickerServerDeps.readMedia}, the stored document. The rest\n * is here, against core's contracts alone:\n *\n * - `status` — whether a library is configured, and its name.\n * - `search` — the provider's own search, its 24-hour cache and its rate\n * limit in charge. A `null` answer (rate-limited, or the library down) is\n * `busy`, a state the tab shows, never a 5xx: a library's bad minute is\n * not a fault of the console's.\n * - `preview` — a hit's small copy, as bytes, so no browser loads an\n * address at the library.\n * - `import` — the photo copied into the library through `core.media-ingest`\n * as the member, which makes the upload's own checks (access, lockdown,\n * structure, takedowns, plan and storage band). A copy the library already\n * holds under the photo's source key is placed instead, never stored a\n * second time. The credit rides the asset as the AI build writes it:\n * `description` \"Photo by … on …\" and `stockPhoto` with the photographer,\n * the photo's page and the license.\n *\n * A hit is found again by the search that found it, which the provider\n * answers from its cache, so the addresses and the credit are always the\n * provider's own and never what a browser sent.\n */\n\n/** The largest stored copy a photo may be. */\nexport const STOCK_PHOTO_IMPORT_MAX_BYTES = 10 * 1024 * 1024\n/** The largest preview handed to a browser. */\nexport const STOCK_PHOTO_PREVIEW_MAX_BYTES = 2 * 1024 * 1024\n/** How long a browser may keep a preview. */\nconst PREVIEW_CACHE_CONTROL = 'private, max-age=3600'\n/** How long one process keeps a search's photos to find hits again in, before asking the provider. */\nexport const STOCK_PHOTO_HIT_MEMO_MS = 5 * 60 * 1000\n/** Searches one process keeps in that memo. */\nconst HIT_MEMO_SIZE = 50\n/** The tags a hit shows. */\nconst HIT_TAGS = 8\n/** The file extension a stored copy is named with, by type. */\nconst EXTENSIONS: Readonly<Record<string, string>> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n}\n\nexport interface StockPhotoPickerServerDeps {\n /** The signed-in member. */\n uid: string\n /**\n * `null` when the member may add files to `target`'s library; otherwise\n * the refusal to answer with (401, 403, 404, 423 …).\n */\n authorize(target: StockPhotoPickerTarget): Promise<Response | null>\n /** The stored media document `mediaId` in `target`'s library, or `null`. */\n readMedia(target: StockPhotoPickerTarget, mediaId: string): Promise<Record<string, unknown> | null>\n provider?: () => StockPhotoProvider | null\n ingest?: () => PluginMediaIngest | null\n memo?: StockPhotoHitMemo\n signal?: AbortSignal\n}\n\n/** A process's recent searches, to find a hit again without asking the provider. */\nexport interface StockPhotoHitMemo {\n get(key: string): StockPhoto[] | null\n set(key: string, photos: StockPhoto[]): void\n}\n\n/** A memo of the last {@link HIT_MEMO_SIZE} searches, each kept {@link STOCK_PHOTO_HIT_MEMO_MS}. */\nexport function createStockPhotoHitMemo(now: () => number = Date.now): StockPhotoHitMemo {\n const held = new Map<string, { at: number; photos: StockPhoto[] }>()\n return {\n get(key) {\n const entry = held.get(key)\n if (!entry) return null\n if (now() - entry.at > STOCK_PHOTO_HIT_MEMO_MS) {\n held.delete(key)\n return null\n }\n return entry.photos\n },\n set(key, photos) {\n held.delete(key)\n held.set(key, { at: now(), photos })\n while (held.size > HIT_MEMO_SIZE) held.delete(held.keys().next().value as string)\n },\n }\n}\n\nconst processMemo = createStockPhotoHitMemo()\n\nconst json = (body: unknown, status = 200): Response =>\n Response.json(body, { status, headers: { 'Cache-Control': 'no-store' } })\n\nconst refused = (status: number, message: string): Response => json({ status: 'refused', message }, status)\n\n/** The memo's key for a search. */\nconst memoKey = (provider: StockPhotoProvider, ref: Pick<StockPhotoHitRef, 'query' | 'orientation'>) =>\n `${provider.id}\\n${ref.orientation}\\n${ref.query.toLowerCase()}`\n\n/** A provider's hit as the tab shows it. */\nexport function stockPhotoPickerHitOf(\n provider: Pick<StockPhotoProvider, 'credit'>,\n photo: StockPhoto,\n search: Pick<StockPhotoHitRef, 'query' | 'orientation'>,\n): StockPhotoPickerHit | null {\n const pageUrl = stockPhotoSafeLink(photo.pageUrl)\n if (!photo.id || !pageUrl || !(photo.width > 0) || !(photo.height > 0)) return null\n const credit = provider.credit(photo)\n const licenseUrl = stockPhotoSafeLink(credit.licenseUrl)\n const photographerUrl = stockPhotoSafeLink(photo.photographerUrl)\n return {\n ref: { query: search.query, orientation: search.orientation, id: photo.id },\n sourceKey: stockPhotoSourceKey(photo),\n width: photo.width,\n height: photo.height,\n photographer: photo.photographer,\n ...(photographerUrl ? { photographerUrl } : {}),\n pageUrl,\n tags: (photo.tags ?? []).slice(0, HIT_TAGS),\n ...(photo.alt ? { alt: photo.alt } : {}),\n credit: credit.text,\n license: credit.license,\n licenseUrl: licenseUrl ?? '',\n attributionRequired: credit.attributionRequired,\n }\n}\n\n/**\n * What the stored copy of `photo` is sent to the media library as: the\n * shape the AI build's copies take, so every stock asset carries one credit.\n */\nexport function stockPhotoPickerIngestRequest(input: {\n provider: Pick<StockPhotoProvider, 'credit'>\n target: StockPhotoPickerTarget\n uid: string\n photo: StockPhoto\n bytes: { bytes: Uint8Array; contentType: string }\n query: string\n folderId?: string | null\n}): PluginMediaIngestRequest {\n const { photo, bytes, target } = input\n const credit = input.provider.credit(photo)\n const photographerUrl = stockPhotoSafeLink(photo.photographerUrl)\n return {\n hostId: target.hostId ?? '',\n ...(target.orgId ? { orgId: target.orgId } : {}),\n uid: input.uid,\n fileName: `${photo.provider}-${photo.id}.${EXTENSIONS[bytes.contentType] ?? 'jpg'}`,\n contentType: bytes.contentType,\n bytes: bytes.bytes,\n ...(photo.alt ? { alt: photo.alt } : {}),\n description: credit.text,\n ...(input.folderId ? { folderId: input.folderId } : {}),\n stockPhoto: {\n key: stockPhotoSourceKey(photo),\n provider: photo.provider,\n providerLabel: credit.providerLabel,\n id: photo.id,\n pageUrl: photo.pageUrl,\n photographer: photo.photographer,\n ...(photographerUrl ? { photographerUrl } : {}),\n license: credit.license,\n licenseUrl: credit.licenseUrl,\n attributionRequired: credit.attributionRequired,\n query: input.query,\n },\n }\n}\n\n/** The photo `ref` names, found again in its search; `null` when the provider cannot be asked, `false` when it is gone. */\nasync function findHit(\n provider: StockPhotoProvider,\n ref: StockPhotoHitRef,\n memo: StockPhotoHitMemo,\n signal?: AbortSignal,\n): Promise<StockPhoto | null | false> {\n const key = memoKey(provider, ref)\n let photos = memo.get(key)\n if (!photos) {\n const answer = await provider.search({ query: ref.query, orientation: ref.orientation }, signal ? { signal } : undefined)\n if (!answer) return null\n photos = answer.photos\n memo.set(key, photos)\n }\n return photos.find((photo) => photo.id === ref.id) ?? false\n}\n\n/** Answers one request of the Stock photos tab. Never throws for the library's sake. */\nexport async function answerStockPhotoPickerRequest(\n body: unknown,\n deps: StockPhotoPickerServerDeps,\n): Promise<Response> {\n const record = (body ?? {}) as Record<string, unknown>\n const action = record['action']\n if (action !== 'status' && action !== 'search' && action !== 'preview' && action !== 'import') {\n return refused(400, 'Unknown request')\n }\n const target = stockPhotoTargetOf(record)\n if (!target) return refused(400, 'Name the site or the workspace whose library to use')\n const denied = await deps.authorize(target)\n if (denied) return denied\n\n const provider = (deps.provider ?? stockPhotoProvider)()\n if (!provider) {\n if (action === 'preview') return new Response(null, { status: 204 })\n return json({ status: 'unconfigured', message: STOCK_PHOTOS_UNCONFIGURED_MESSAGE } satisfies StockPhotoStatusAnswer)\n }\n const library = { label: provider.label }\n const memo = deps.memo ?? processMemo\n const signal = deps.signal\n\n if (action === 'status') return json({ status: 'ready', library } satisfies StockPhotoStatusAnswer)\n\n if (action === 'search') {\n const query = normalizeStockPhotoQuery(record['query'])\n if (!query) return refused(400, 'Type a few words to search for, up to 100 characters')\n const orientation = stockPhotoOrientationOf(record['orientation'])\n let answer: Awaited<ReturnType<StockPhotoProvider['search']>> = null\n try {\n answer = await provider.search({ query, orientation }, signal ? { signal } : undefined)\n } catch (error) {\n console.warn('stock photos: the search failed', { provider: provider.id, error: String(error).slice(0, 160) })\n }\n if (!answer) return json({ status: 'busy', message: stockPhotosBusyMessage(provider.label) } satisfies StockPhotoSearchAnswer)\n memo.set(memoKey(provider, { query, orientation }), answer.photos)\n const hits = answer.photos\n .map((photo) => stockPhotoPickerHitOf(provider, photo, { query, orientation }))\n .filter((hit): hit is StockPhotoPickerHit => hit !== null)\n return json({ status: 'ok', library, query, orientation, hits } satisfies StockPhotoSearchAnswer)\n }\n\n const ref = stockPhotoHitRefOf(record['ref'])\n if (!ref) return refused(400, 'That photo could not be read. Search again and choose it from the results.')\n let photo: StockPhoto | null | false = null\n try {\n photo = await findHit(provider, ref, memo, signal)\n } catch (error) {\n console.warn('stock photos: a search to find a photo again failed', { provider: provider.id, error: String(error).slice(0, 160) })\n }\n\n if (action === 'preview') {\n if (!photo || typeof provider.preview !== 'function') return new Response(null, { status: 204 })\n const small = await provider.preview(photo, { maxBytes: STOCK_PHOTO_PREVIEW_MAX_BYTES, ...(signal ? { signal } : {}) }).catch((): null => null)\n if (!small) return new Response(null, { status: 204 })\n return new Response(new Uint8Array(small.bytes), {\n status: 200,\n headers: { 'Content-Type': small.contentType, 'Cache-Control': PREVIEW_CACHE_CONTROL, 'X-Content-Type-Options': 'nosniff' },\n })\n }\n\n // import\n if (photo === null) return json({ status: 'busy', message: stockPhotosBusyMessage(provider.label) } satisfies StockPhotoImportAnswer)\n if (photo === false) return json({ status: 'gone', message: STOCK_PHOTO_GONE_MESSAGE } satisfies StockPhotoImportAnswer, 404)\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n if (!ingest) return refused(503, 'The media library cannot take photos this way right now. Try again in a minute.')\n const sourceKey = stockPhotoSourceKey(photo)\n const placed = async (mediaId: string, reused: boolean): Promise<Response> => {\n const data = await deps.readMedia(target, mediaId)\n if (!data) return refused(500, 'The photo was stored but could not be read back. It is in the library; choose it there.')\n return json({ status: 'ok', reused, media: stockPhotoPickerMediaOf(mediaId, data) } satisfies StockPhotoImportAnswer)\n }\n const held = await ingest.findStockPhoto({\n hostId: target.hostId ?? '',\n sourceKey,\n ...(target.orgId ? { orgId: target.orgId } : {}),\n })\n if (held) return placed(held.mediaId, true)\n const bytes = await provider\n .download(photo, { maxBytes: STOCK_PHOTO_IMPORT_MAX_BYTES, ...(signal ? { signal } : {}) })\n .catch((): null => null)\n if (!bytes) return json({ status: 'busy', message: stockPhotosBusyMessage(provider.label) } satisfies StockPhotoImportAnswer)\n const folderId = typeof record['folderId'] === 'string' && record['folderId'] ? String(record['folderId']).slice(0, 64) : null\n const stored = await ingest.ingest(\n stockPhotoPickerIngestRequest({ provider, target, uid: deps.uid, photo, bytes, query: ref.query, folderId }),\n )\n if (stored.ok === false) return refused(stored.status >= 400 ? stored.status : 500, stored.reason)\n return placed(stored.mediaId, false)\n}\n"],"names":["pluginMediaIngest","stockPhotoProvider","stockPhotoSourceKey","normalizeStockPhotoQuery","STOCK_PHOTO_GONE_MESSAGE","stockPhotoHitRefOf","stockPhotoOrientationOf","stockPhotoPickerMediaOf","stockPhotoSafeLink","STOCK_PHOTOS_UNCONFIGURED_MESSAGE","stockPhotosBusyMessage","stockPhotoTargetOf","STOCK_PHOTO_IMPORT_MAX_BYTES","STOCK_PHOTO_PREVIEW_MAX_BYTES","PREVIEW_CACHE_CONTROL","STOCK_PHOTO_HIT_MEMO_MS","HIT_MEMO_SIZE","HIT_TAGS","EXTENSIONS","createStockPhotoHitMemo","now","Date","held","Map","get","key","entry","at","delete","photos","set","size","keys","next","value","processMemo","json","body","status","Response","headers","refused","message","memoKey","provider","ref","id","orientation","query","toLowerCase","stockPhotoPickerHitOf","photo","search","pageUrl","width","height","credit","licenseUrl","photographerUrl","sourceKey","photographer","tags","slice","alt","text","license","attributionRequired","stockPhotoPickerIngestRequest","input","target","bytes","hostId","orgId","uid","fileName","contentType","description","folderId","stockPhoto","providerLabel","findHit","memo","signal","answer","undefined","find","answerStockPhotoPickerRequest","deps","record","action","denied","authorize","library","label","error","console","warn","String","hits","map","filter","hit","preview","small","maxBytes","catch","Uint8Array","ingest","placed","mediaId","reused","data","readMedia","media","findStockPhoto","download","stored","ok","reason"],"mappings":"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"}
|