Add public user-products storefront API and website docs.

Expose published customer listings under /tenants/:host/user-products (list, search, details, technical-info) and document them in the website API pack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 00:19:13 +03:30
co-authored by Cursor
parent 158523df7b
commit 953b87b616
32 changed files with 3326 additions and 138 deletions
@@ -14,13 +14,43 @@ import {
} from './dto/category-technical-form.dto';
function slugifyKey(value: string): string {
return (
value
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '') || 'field'
);
const ascii = value
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
if (ascii) {
return ascii;
}
// Keep letters/numbers from any script (e.g. Farsi labels) when ASCII strip is empty
const unicode = value
.trim()
.toLowerCase()
.normalize('NFKC')
.replace(/\s+/g, '-')
.replace(/[^\p{L}\p{N}-]+/gu, '')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');
return unicode || 'field';
}
function uniqueSlug(base: string, used: Set<string>): string {
let value = base || 'field';
if (!used.has(value)) {
used.add(value);
return value;
}
let suffix = 2;
while (used.has(`${value}-${suffix}`)) {
suffix += 1;
}
const next = `${value}-${suffix}`;
used.add(next);
return next;
}
@Injectable()
@@ -88,15 +118,7 @@ export class CategoryTechnicalFormService {
const usedKeys = new Set<string>();
for (const [index, field] of dto.fields.entries()) {
let fieldKey = slugifyKey(field.label);
if (usedKeys.has(fieldKey)) {
let suffix = 2;
while (usedKeys.has(`${fieldKey}-${suffix}`)) {
suffix += 1;
}
fieldKey = `${fieldKey}-${suffix}`;
}
usedKeys.add(fieldKey);
const fieldKey = uniqueSlug(slugifyKey(field.label), usedKeys);
const createdField = await tx.categoryTechnicalFormField.create({
data: {
@@ -113,12 +135,13 @@ export class CategoryTechnicalFormService {
const uniqueOptions = [
...new Set(field.options!.map((o) => o.trim()).filter(Boolean)),
];
const usedValues = new Set<string>();
await tx.categoryTechnicalFormFieldOption.createMany({
data: uniqueOptions.map((label, optionIndex) => ({
fieldId: createdField.id,
label,
value: slugifyKey(label) || `option-${optionIndex + 1}`,
value: uniqueSlug(slugifyKey(label), usedValues),
sortOrder: optionIndex,
})),
});