← Files WixARCHIVED FILE
skills/wix-headless-templates/portfolio/rest/portfolio.ts
3.97 KB · Oct 8, 2026 · 12:02 UTC
// Portfolio reads over REST — the twin of app/wix/portfolio/portfolio.ts. Same exports, same DTOs;
// the rules and mappers come from portfolio-core (the SAME file the SDK transport uses, deployed
// flat next to this one by deploy.mjs --stack static), so this file is only the transport: one
// fetch with a literal URL and body per function. Every call here is a public read, safe from a
// browser with a visitor token. Porting: keep the paths, keep the bodies, port the core once.
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/collections/list-collections.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/collections/query-collections.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/list-projects.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/query-projects.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/project-items/list-project-items.md
import { wixRequest } from "./client.js";
import { imgSrc } from "./media.js";
import {
galleryItems,
visibleCollection,
visibleCollections,
visibleProject,
visibleProjects,
type Raw,
type VideoSrc,
} from "./portfolio-core.js";
import type { CollectionSummary, GalleryItem, ProjectDetail, ProjectSummary } from "./types.js";
// REST never returns the bare wix:video:// string — videoInfo arrives as { resolutions, posters },
// which the core reads directly. A string here has no resolver on this transport → dropped.
const videoSrc: VideoSrc = () => null;
/** Visible collections in dashboard order. GET /portfolio/v1/collections?paging.limit=N → { collections } */
export async function fetchCollections({ limit = 100 } = {}): Promise<CollectionSummary[]> {
const res = await wixRequest<Raw>("/portfolio/v1/collections", { method: "GET", query: { "paging.limit": String(limit) } });
return visibleCollections((res?.collections ?? []) as Raw[], imgSrc);
}
/**
* One visible collection by URL slug; null when missing or hidden → a real 404.
* POST /portfolio/v1/collections/query { query: { filter: { slug }, cursorPaging: { limit: 1 } } } → { collections }
*/
export async function fetchCollectionBySlug(slug: string): Promise<CollectionSummary | null> {
const res = await wixRequest<Raw>("/portfolio/v1/collections/query", {
body: { query: { filter: { slug }, cursorPaging: { limit: 1 } } },
});
return visibleCollection(res?.collections?.[0] as Raw | undefined, imgSrc);
}
/** Visible projects in list order, optionally one collection's. GET /portfolio/v1/projects?paging.limit=N → { projects } */
export async function fetchProjects({
collectionId,
limit = 100,
}: { collectionId?: string; limit?: number } = {}): Promise<ProjectSummary[]> {
const res = await wixRequest<Raw>("/portfolio/v1/projects", { method: "GET", query: { "paging.limit": String(limit) } });
return visibleProjects((res?.projects ?? []) as Raw[], collectionId, imgSrc, videoSrc);
}
/**
* One visible project with its details rows by URL slug; null when missing or hidden.
* POST /portfolio/v1/projects/query { query: { filter: { slug }, cursorPaging: { limit: 1 } } } → { projects }
*/
export async function fetchProjectBySlug(slug: string): Promise<ProjectDetail | null> {
const res = await wixRequest<Raw>("/portfolio/v1/projects/query", {
body: { query: { filter: { slug }, cursorPaging: { limit: 1 } } },
});
return visibleProject(res?.projects?.[0] as Raw | undefined, imgSrc, videoSrc);
}
/**
* A project's gallery in dashboard order; IMAGE and VIDEO items only.
* GET /portfolio/v1/projectItems/{projectId}/items?paging.limit=100 → { items }
*/
export async function fetchProjectGallery(projectId: string): Promise<GalleryItem[]> {
const res = await wixRequest<Raw>(`/portfolio/v1/projectItems/${encodeURIComponent(projectId)}/items`, {
method: "GET",
query: { "paging.limit": "100" },
});
return galleryItems((res?.items ?? []) as Raw[], imgSrc, videoSrc);
}
SHA-256: 41211016cb8dca6039071e9be0adeb391164871ecc7e6f4421cccd97fcc153e4