@withwiz/gallery 0.1.0
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/README.ko.md +484 -0
- package/README.md +484 -0
- package/dist/chunk-B3MQL6ZR.mjs +79 -0
- package/dist/chunk-B3MQL6ZR.mjs.map +1 -0
- package/dist/chunk-BC3TPJQY.mjs +122 -0
- package/dist/chunk-BC3TPJQY.mjs.map +1 -0
- package/dist/chunk-BTLELFKC.mjs +11 -0
- package/dist/chunk-BTLELFKC.mjs.map +1 -0
- package/dist/chunk-GDUGKVDQ.mjs +50 -0
- package/dist/chunk-GDUGKVDQ.mjs.map +1 -0
- package/dist/chunk-GFOWZGJX.js +50 -0
- package/dist/chunk-GFOWZGJX.js.map +1 -0
- package/dist/chunk-ILSZPLME.js +47 -0
- package/dist/chunk-ILSZPLME.js.map +1 -0
- package/dist/chunk-M7CCTDUG.js +122 -0
- package/dist/chunk-M7CCTDUG.js.map +1 -0
- package/dist/chunk-WVRTM6E7.js +19 -0
- package/dist/chunk-WVRTM6E7.js.map +1 -0
- package/dist/chunk-XLPMNJJV.js +11 -0
- package/dist/chunk-XLPMNJJV.js.map +1 -0
- package/dist/chunk-XSPWM4DD.js +79 -0
- package/dist/chunk-XSPWM4DD.js.map +1 -0
- package/dist/chunk-YUMB3GBN.mjs +47 -0
- package/dist/chunk-YUMB3GBN.mjs.map +1 -0
- package/dist/chunk-ZEZCUJJD.mjs +19 -0
- package/dist/chunk-ZEZCUJJD.mjs.map +1 -0
- package/dist/components/gallery.css +1074 -0
- package/dist/components/index.d.mts +156 -0
- package/dist/components/index.d.ts +156 -0
- package/dist/components/index.js +1215 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +1215 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/errors-B9gzUsX-.d.ts +39 -0
- package/dist/errors-DYrlDCzP.d.mts +39 -0
- package/dist/gallery.css +1074 -0
- package/dist/hooks/index.d.mts +65 -0
- package/dist/hooks/index.d.ts +65 -0
- package/dist/hooks/index.js +14 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +14 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +37 -0
- package/dist/index.mjs.map +1 -0
- package/dist/presets/ballet.d.mts +64 -0
- package/dist/presets/ballet.d.ts +64 -0
- package/dist/presets/ballet.js +162 -0
- package/dist/presets/ballet.js.map +1 -0
- package/dist/presets/ballet.mjs +162 -0
- package/dist/presets/ballet.mjs.map +1 -0
- package/dist/server/index.d.mts +157 -0
- package/dist/server/index.d.ts +157 -0
- package/dist/server/index.js +619 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +619 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/types/index.d.mts +137 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/useGalleryLightbox-BYzPyBYB.d.mts +22 -0
- package/dist/useGalleryLightbox-BYzPyBYB.d.ts +22 -0
- package/dist/validators/index.d.mts +68 -0
- package/dist/validators/index.d.ts +68 -0
- package/dist/validators/index.js +7 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/validators/index.mjs +7 -0
- package/dist/validators/index.mjs.map +1 -0
- package/package.json +85 -0
- package/prisma/gallery.schema.prisma +62 -0
- package/prisma/migrations/2026-05-24-enum-to-table.sql +65 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
cn
|
|
4
|
+
} from "../chunk-BTLELFKC.mjs";
|
|
5
|
+
import {
|
|
6
|
+
useGalleryLightbox,
|
|
7
|
+
useScrollReveal
|
|
8
|
+
} from "../chunk-B3MQL6ZR.mjs";
|
|
9
|
+
|
|
10
|
+
// src/presets/ballet.tsx
|
|
11
|
+
import { useEffect } from "react";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
var FALLBACK_I18N = {
|
|
14
|
+
sectionLabel: "\uAC24\uB7EC\uB9AC",
|
|
15
|
+
moments: "\uACF5\uC5F0\uC758 \uC21C\uAC04\uB4E4",
|
|
16
|
+
expandAria: "\uC774\uBBF8\uC9C0 \uD655\uB300",
|
|
17
|
+
lbClose: "\uB2EB\uAE30",
|
|
18
|
+
lbPrev: "\uC774\uC804 \uC774\uBBF8\uC9C0",
|
|
19
|
+
lbNext: "\uB2E4\uC74C \uC774\uBBF8\uC9C0"
|
|
20
|
+
};
|
|
21
|
+
function PublicGalleryMosaic(props) {
|
|
22
|
+
const {
|
|
23
|
+
images: rawImages,
|
|
24
|
+
count = 7,
|
|
25
|
+
i18n: i18nOverride,
|
|
26
|
+
className,
|
|
27
|
+
scrollReveal = true,
|
|
28
|
+
hideHeader = false
|
|
29
|
+
} = props;
|
|
30
|
+
const i18n = { ...FALLBACK_I18N, ...i18nOverride ?? {} };
|
|
31
|
+
const images = rawImages.slice(0, count);
|
|
32
|
+
const lightbox = useGalleryLightbox(images);
|
|
33
|
+
const { ref: revealRef, isVisible } = useScrollReveal({ once: true });
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!lightbox.isOpen) return;
|
|
36
|
+
if (typeof document === "undefined") return;
|
|
37
|
+
const prev = document.body.style.overflow;
|
|
38
|
+
document.body.style.overflow = "hidden";
|
|
39
|
+
return () => {
|
|
40
|
+
document.body.style.overflow = prev;
|
|
41
|
+
};
|
|
42
|
+
}, [lightbox.isOpen]);
|
|
43
|
+
if (images.length === 0) return null;
|
|
44
|
+
const showReveal = scrollReveal ? isVisible : true;
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"section",
|
|
47
|
+
{
|
|
48
|
+
ref: revealRef,
|
|
49
|
+
className: cn(
|
|
50
|
+
"gallery-public-mosaic-section",
|
|
51
|
+
showReveal && "gallery-public-mosaic-section--visible",
|
|
52
|
+
className
|
|
53
|
+
),
|
|
54
|
+
"aria-label": i18n.sectionLabel,
|
|
55
|
+
children: [
|
|
56
|
+
!hideHeader && /* @__PURE__ */ jsxs("header", { className: "gallery-public-mosaic__head", children: [
|
|
57
|
+
/* @__PURE__ */ jsx("div", { className: "gallery-public-mosaic__label", children: i18n.sectionLabel }),
|
|
58
|
+
/* @__PURE__ */ jsx("h2", { className: "gallery-public-mosaic__title", children: i18n.moments })
|
|
59
|
+
] }),
|
|
60
|
+
/* @__PURE__ */ jsx("div", { className: "gallery-public-mosaic", "data-count": images.length, children: images.map((img, i) => /* @__PURE__ */ jsxs(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
className: "gallery-public-mosaic__item",
|
|
64
|
+
role: "button",
|
|
65
|
+
tabIndex: 0,
|
|
66
|
+
"aria-label": img.alt ?? i18n.expandAria,
|
|
67
|
+
onClick: () => lightbox.open(i),
|
|
68
|
+
onKeyDown: (e) => {
|
|
69
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
lightbox.open(i);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
children: [
|
|
75
|
+
/* @__PURE__ */ jsx(
|
|
76
|
+
"img",
|
|
77
|
+
{
|
|
78
|
+
src: img.src,
|
|
79
|
+
alt: img.alt ?? "",
|
|
80
|
+
className: "gallery-public-mosaic__img",
|
|
81
|
+
loading: "lazy"
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsx("div", { className: "gallery-public-mosaic__hover", children: /* @__PURE__ */ jsx("div", { className: "gallery-public-mosaic__icon", "aria-hidden": "true", children: "+" }) })
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
`${img.src}-${i}`
|
|
88
|
+
)) }),
|
|
89
|
+
lightbox.isOpen && lightbox.current && /* @__PURE__ */ jsxs(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
className: "gallery-lightbox",
|
|
93
|
+
role: "dialog",
|
|
94
|
+
"aria-modal": "true",
|
|
95
|
+
"aria-label": i18n.moments,
|
|
96
|
+
onClick: lightbox.close,
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
className: "gallery-lightbox__close",
|
|
103
|
+
"aria-label": i18n.lbClose,
|
|
104
|
+
onClick: (e) => {
|
|
105
|
+
e.stopPropagation();
|
|
106
|
+
lightbox.close();
|
|
107
|
+
},
|
|
108
|
+
children: "\xD7"
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
"button",
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
className: "gallery-lightbox__nav gallery-lightbox__prev",
|
|
117
|
+
"aria-label": i18n.lbPrev,
|
|
118
|
+
onClick: (e) => {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
lightbox.prev();
|
|
121
|
+
},
|
|
122
|
+
children: "\u2039"
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
"button",
|
|
127
|
+
{
|
|
128
|
+
type: "button",
|
|
129
|
+
className: "gallery-lightbox__nav gallery-lightbox__next",
|
|
130
|
+
"aria-label": i18n.lbNext,
|
|
131
|
+
onClick: (e) => {
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
lightbox.next();
|
|
134
|
+
},
|
|
135
|
+
children: "\u203A"
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ jsx("div", { className: "gallery-lightbox__content", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
140
|
+
"img",
|
|
141
|
+
{
|
|
142
|
+
src: lightbox.current.src,
|
|
143
|
+
alt: lightbox.current.alt ?? "",
|
|
144
|
+
className: "gallery-lightbox__img"
|
|
145
|
+
}
|
|
146
|
+
) }),
|
|
147
|
+
images.length > 1 && /* @__PURE__ */ jsxs("div", { className: "gallery-lightbox__counter", children: [
|
|
148
|
+
lightbox.currentIndex + 1,
|
|
149
|
+
" / ",
|
|
150
|
+
images.length
|
|
151
|
+
] })
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
PublicGalleryMosaic
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=ballet.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/presets/ballet.tsx"],"sourcesContent":["/**\n * @withwiz/gallery/presets/ballet\n *\n * 발레용 reference public 컴포넌트. ballet 의\n * `src/components/sections/Gallery.tsx`\n * 의 모자이크 + 라이트박스 결합을 host 독립으로 추출.\n *\n * 사용 예 (host RSC + client mount):\n *\n * import { PublicGalleryMosaic } from \"@withwiz/gallery/presets/ballet\";\n * import { getFeaturedGalleries } from \"@withwiz/gallery/server\";\n * import \"@withwiz/gallery/components/gallery.css\";\n *\n * export default async function HomePage() {\n * const featured = await getFeaturedGalleries(config, 7);\n * const images = featured.map((g) => ({ src: g.imageUrl, alt: g.caption ?? \"갤러리\" }));\n * return <PublicGalleryMosaic images={images} count={7} />;\n * }\n *\n * yeroom 등 다른 host 는 이 preset 을 fork 하거나 `useGalleryLightbox` hook 만\n * 가져다 직접 그릴 수 있다.\n *\n * Host-independence:\n * - `next/image`, `next/navigation`, `useI18n`, `@withwiz/pms` 등 직접 의존 0\n * - 이미지는 plain `<img>` 사용. host 가 next/image 로 그리고 싶다면 fork.\n * - 모든 라벨은 props.i18n 으로만 (fallback 한글).\n */\n\nimport { useEffect, type JSX } from \"react\";\nimport { useGalleryLightbox, type LightboxImage } from \"../hooks/useGalleryLightbox\";\nimport { useScrollReveal } from \"../hooks/useScrollReveal\";\nimport { cn } from \"../utils/cn\";\n\nconst FALLBACK_I18N = {\n sectionLabel: \"갤러리\",\n moments: \"공연의 순간들\",\n expandAria: \"이미지 확대\",\n lbClose: \"닫기\",\n lbPrev: \"이전 이미지\",\n lbNext: \"다음 이미지\",\n} as const;\n\nexport interface PublicGalleryMosaicProps {\n /** 표시할 이미지 목록 (host 가 loaders 등에서 받아온 src/alt 매핑) */\n images: LightboxImage[];\n /** 모자이크 그리드 칸 수 (default 7). images.length > count 이면 slice. */\n count?: number;\n /** UI 라벨 i18n override. 미주입 키는 한글 fallback. */\n i18n?: Partial<Record<keyof typeof FALLBACK_I18N, string>>;\n /** 외부 className 추가 */\n className?: string;\n /** 스크롤 reveal (IntersectionObserver) 사용 여부 (default true). false 면 즉시 visible. */\n scrollReveal?: boolean;\n /** 섹션 헤더 비노출 (default false). host 가 자체 헤더를 두는 경우 true. */\n hideHeader?: boolean;\n}\n\n/**\n * 발레 home gallery 의 모자이크 + 라이트박스.\n *\n * - useGalleryLightbox 가 ESC / Arrow 키바인딩과 wrap-around 를 담당.\n * - useScrollReveal 로 fade-in (scrollReveal=true 일 때).\n * - lightbox open 시 body scroll lock (overflow:hidden).\n * - images 가 빈 배열이면 null 반환.\n */\nexport function PublicGalleryMosaic(props: PublicGalleryMosaicProps): JSX.Element | null {\n const {\n images: rawImages,\n count = 7,\n i18n: i18nOverride,\n className,\n scrollReveal = true,\n hideHeader = false,\n } = props;\n\n const i18n = { ...FALLBACK_I18N, ...(i18nOverride ?? {}) };\n\n // count 만큼 잘라 사용 (images.length > count 인 경우 super-fluous 제거)\n const images = rawImages.slice(0, count);\n\n const lightbox = useGalleryLightbox(images);\n const { ref: revealRef, isVisible } = useScrollReveal({ once: true });\n\n // body scroll lock — lightbox 가 열린 동안 host 페이지의 스크롤을 막는다.\n // SSR safe: document 가 정의된 환경에서만 동작.\n useEffect(() => {\n if (!lightbox.isOpen) return;\n if (typeof document === \"undefined\") return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [lightbox.isOpen]);\n\n if (images.length === 0) return null;\n\n const showReveal = scrollReveal ? isVisible : true;\n\n return (\n <section\n ref={revealRef}\n className={cn(\n \"gallery-public-mosaic-section\",\n showReveal && \"gallery-public-mosaic-section--visible\",\n className,\n )}\n aria-label={i18n.sectionLabel}\n >\n {!hideHeader && (\n <header className=\"gallery-public-mosaic__head\">\n <div className=\"gallery-public-mosaic__label\">{i18n.sectionLabel}</div>\n <h2 className=\"gallery-public-mosaic__title\">{i18n.moments}</h2>\n </header>\n )}\n\n <div className=\"gallery-public-mosaic\" data-count={images.length}>\n {images.map((img, i) => (\n <div\n key={`${img.src}-${i}`}\n className=\"gallery-public-mosaic__item\"\n role=\"button\"\n tabIndex={0}\n aria-label={img.alt ?? i18n.expandAria}\n onClick={() => lightbox.open(i)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n lightbox.open(i);\n }\n }}\n >\n <img\n src={img.src}\n alt={img.alt ?? \"\"}\n className=\"gallery-public-mosaic__img\"\n loading=\"lazy\"\n />\n <div className=\"gallery-public-mosaic__hover\">\n <div className=\"gallery-public-mosaic__icon\" aria-hidden=\"true\">\n +\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {lightbox.isOpen && lightbox.current && (\n <div\n className=\"gallery-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={i18n.moments}\n onClick={lightbox.close}\n >\n <button\n type=\"button\"\n className=\"gallery-lightbox__close\"\n aria-label={i18n.lbClose}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.close();\n }}\n >\n ×\n </button>\n\n {images.length > 1 && (\n <>\n <button\n type=\"button\"\n className=\"gallery-lightbox__nav gallery-lightbox__prev\"\n aria-label={i18n.lbPrev}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.prev();\n }}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className=\"gallery-lightbox__nav gallery-lightbox__next\"\n aria-label={i18n.lbNext}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.next();\n }}\n >\n ›\n </button>\n </>\n )}\n\n <div className=\"gallery-lightbox__content\" onClick={(e) => e.stopPropagation()}>\n <img\n src={lightbox.current.src}\n alt={lightbox.current.alt ?? \"\"}\n className=\"gallery-lightbox__img\"\n />\n </div>\n\n {images.length > 1 && (\n <div className=\"gallery-lightbox__counter\">\n {lightbox.currentIndex + 1} / {images.length}\n </div>\n )}\n </div>\n )}\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { PaginatedResult, GalleryConfig, GalleryCategoryItem, CreateCategoryInput, UpdateCategoryInput, GalleryListItem, GalleryDetail, CreateGalleryInput, UpdateGalleryInput, RouteHandler } from '../types/index.mjs';
|
|
2
|
+
export { ApiContext, ApiWrapper, GalleryI18nKey, GallerySlotName, PaginationMeta, PrismaLike, SortOrder } from '../types/index.mjs';
|
|
3
|
+
export { C as CategoryInUseError, a as CategoryNotFoundError, G as GalleryError, b as GalleryNotFoundError, P as PermissionDeniedError, g as getGalleryConfig, s as setGalleryConfig } from '../errors-DYrlDCzP.mjs';
|
|
4
|
+
import 'next/server';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* pagination math 를 일관되게 처리. ballet 의 `buildPaginatedResult` 와 동일 시맨틱.
|
|
9
|
+
*
|
|
10
|
+
* - total === 0 일 때 totalPages = 0 (clamp 안 함; UI 에서 "결과 없음" 로 처리)
|
|
11
|
+
* - page > totalPages 여도 결과 자체는 caller 의 query 가 결정 — meta 만 계산
|
|
12
|
+
*/
|
|
13
|
+
declare function buildPaginatedResult<T>(items: T[], page: number, limit: number, total: number): PaginatedResult<T>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* spec §9-2 의 categoryService.
|
|
17
|
+
*
|
|
18
|
+
* 호스트 독립성을 위해 Prisma delegate 는 `config.prisma[modelName]` 패턴으로 접근한다
|
|
19
|
+
* (modelName 기본값 = "galleryCategory", gallery 본체 modelName 기본값 = "gallery").
|
|
20
|
+
*
|
|
21
|
+
* remove() 는 `onDelete: Restrict` 보강 — 사전 gallery.count() 후 throw.
|
|
22
|
+
* admin UX 가 친화적 에러 메시지를 표시하도록 명시적 throw 를 사용.
|
|
23
|
+
*/
|
|
24
|
+
declare function createCategoryService(config: GalleryConfig): {
|
|
25
|
+
list: (opts?: {
|
|
26
|
+
isActive?: boolean;
|
|
27
|
+
}) => Promise<GalleryCategoryItem[]>;
|
|
28
|
+
getBySlug: (slug: string) => Promise<GalleryCategoryItem | null>;
|
|
29
|
+
getById: (id: string) => Promise<GalleryCategoryItem | null>;
|
|
30
|
+
create: (data: CreateCategoryInput) => Promise<GalleryCategoryItem>;
|
|
31
|
+
update: (id: string, data: UpdateCategoryInput) => Promise<GalleryCategoryItem>;
|
|
32
|
+
remove: (id: string) => Promise<void>;
|
|
33
|
+
reorder: (ids: string[]) => Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
type CategoryService = ReturnType<typeof createCategoryService>;
|
|
36
|
+
|
|
37
|
+
type SortKey$1 = "sortOrder" | "createdAt" | "updatedAt" | "caption";
|
|
38
|
+
/**
|
|
39
|
+
* spec §9-2 의 createGalleryService.
|
|
40
|
+
*
|
|
41
|
+
* 핵심 규칙:
|
|
42
|
+
* - Prisma delegate 는 `config.prisma[config.modelName ?? "gallery"]` 패턴.
|
|
43
|
+
* - `include: { category: true }` 만 사용. **author include 금지** (Gallery 모델에 author 관계 없음, §3 호스트 독립 원칙).
|
|
44
|
+
* - 삭제 시 `config.storage` 가 isEnabled() true 면 collectKeys → deleteKeys.
|
|
45
|
+
* - `revalidate` 는 service 에서 호출하지 않음 (route handler 책임).
|
|
46
|
+
*/
|
|
47
|
+
declare function createGalleryService(config: GalleryConfig): {
|
|
48
|
+
listFeatured: (limit?: number) => Promise<GalleryListItem[]>;
|
|
49
|
+
listPublished: (limit?: number) => Promise<GalleryListItem[]>;
|
|
50
|
+
listPublishedByCategory: (categorySlug: string, limit?: number) => Promise<GalleryListItem[]>;
|
|
51
|
+
listAll: (opts: {
|
|
52
|
+
page?: number;
|
|
53
|
+
limit?: number;
|
|
54
|
+
categoryId?: string;
|
|
55
|
+
published?: boolean;
|
|
56
|
+
search?: string;
|
|
57
|
+
sortBy?: SortKey$1;
|
|
58
|
+
}) => Promise<PaginatedResult<GalleryListItem>>;
|
|
59
|
+
getById: (id: string) => Promise<GalleryDetail | null>;
|
|
60
|
+
create: (data: CreateGalleryInput, authorId: string) => Promise<GalleryDetail>;
|
|
61
|
+
createMany: (items: CreateGalleryInput[], authorId: string) => Promise<{
|
|
62
|
+
count: number;
|
|
63
|
+
}>;
|
|
64
|
+
update: (id: string, data: UpdateGalleryInput) => Promise<GalleryDetail>;
|
|
65
|
+
remove: (id: string) => Promise<void>;
|
|
66
|
+
removeMany: (ids: string[]) => Promise<{
|
|
67
|
+
count: number;
|
|
68
|
+
}>;
|
|
69
|
+
bulkUpdatePublished: (ids: string[], published: boolean) => Promise<{
|
|
70
|
+
count: number;
|
|
71
|
+
}>;
|
|
72
|
+
bulkUpdateFeatured: (ids: string[], featured: boolean) => Promise<{
|
|
73
|
+
count: number;
|
|
74
|
+
}>;
|
|
75
|
+
togglePublish: (id: string) => Promise<GalleryDetail>;
|
|
76
|
+
count: (opts?: {
|
|
77
|
+
published?: boolean;
|
|
78
|
+
}) => Promise<number>;
|
|
79
|
+
listRecent: (limit: number) => Promise<GalleryListItem[]>;
|
|
80
|
+
};
|
|
81
|
+
type GalleryService = ReturnType<typeof createGalleryService>;
|
|
82
|
+
|
|
83
|
+
interface GalleryRoutes {
|
|
84
|
+
collection: {
|
|
85
|
+
GET: RouteHandler;
|
|
86
|
+
POST: RouteHandler;
|
|
87
|
+
DELETE: RouteHandler;
|
|
88
|
+
};
|
|
89
|
+
item: {
|
|
90
|
+
GET: RouteHandler;
|
|
91
|
+
PUT: RouteHandler;
|
|
92
|
+
DELETE: RouteHandler;
|
|
93
|
+
};
|
|
94
|
+
publishToggle: {
|
|
95
|
+
PATCH: RouteHandler;
|
|
96
|
+
};
|
|
97
|
+
bulk: {
|
|
98
|
+
POST: RouteHandler;
|
|
99
|
+
PATCH: RouteHandler;
|
|
100
|
+
};
|
|
101
|
+
categoryCollection: {
|
|
102
|
+
GET: RouteHandler;
|
|
103
|
+
POST: RouteHandler;
|
|
104
|
+
};
|
|
105
|
+
categoryItem: {
|
|
106
|
+
GET: RouteHandler;
|
|
107
|
+
PUT: RouteHandler;
|
|
108
|
+
DELETE: RouteHandler;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
declare function createGalleryRoutes(config: GalleryConfig): GalleryRoutes;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* RSC loaders — spec §9-4.
|
|
115
|
+
*
|
|
116
|
+
* Server Components 가 직접 호출하는 함수. 각 함수는 service 의 대응 메서드를
|
|
117
|
+
* 얇게 감싸기만 한다. host 가 매번 `config` 를 주입해 explicit DI 를 유지한다
|
|
118
|
+
* (setGalleryConfig 의존 회피 — host 의 layout/page 에서 config 를 명시적으로
|
|
119
|
+
* 주입하는 것이 RSC graph 캐싱과 호환성 면에서 더 안전).
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
type SortKey = "sortOrder" | "createdAt" | "updatedAt" | "caption";
|
|
123
|
+
/**
|
|
124
|
+
* spec §9-4: `getGalleryItems(config, opts) → PaginatedResult<GalleryListItem>`
|
|
125
|
+
*
|
|
126
|
+
* admin 페이지의 RSC 가 직접 호출. fetch 라운드트립 없이 service 호출.
|
|
127
|
+
*/
|
|
128
|
+
declare function getGalleryItems(config: GalleryConfig, opts: {
|
|
129
|
+
page?: number;
|
|
130
|
+
limit?: number;
|
|
131
|
+
categoryId?: string;
|
|
132
|
+
published?: boolean;
|
|
133
|
+
search?: string;
|
|
134
|
+
sortBy?: SortKey;
|
|
135
|
+
}): Promise<PaginatedResult<GalleryListItem>>;
|
|
136
|
+
/**
|
|
137
|
+
* spec §9-4: `getFeaturedGalleries(config, limit?) → GalleryListItem[]`
|
|
138
|
+
*
|
|
139
|
+
* 홈 페이지의 모자이크 그리드 등 featured 영역에서 호출.
|
|
140
|
+
*/
|
|
141
|
+
declare function getFeaturedGalleries(config: GalleryConfig, limit?: number): Promise<GalleryListItem[]>;
|
|
142
|
+
/**
|
|
143
|
+
* spec §9-4: `getRecentGalleries(config, limit) → GalleryListItem[]`
|
|
144
|
+
*
|
|
145
|
+
* 대시보드의 최근 항목 위젯 등에서 호출.
|
|
146
|
+
*/
|
|
147
|
+
declare function getRecentGalleries(config: GalleryConfig, limit: number): Promise<GalleryListItem[]>;
|
|
148
|
+
/**
|
|
149
|
+
* spec §9-4: `getGalleryCount(config, opts?) → number`
|
|
150
|
+
*
|
|
151
|
+
* 대시보드 통계 등에서 호출. `opts.published` 미지정 시 전체 count.
|
|
152
|
+
*/
|
|
153
|
+
declare function getGalleryCount(config: GalleryConfig, opts?: {
|
|
154
|
+
published?: boolean;
|
|
155
|
+
}): Promise<number>;
|
|
156
|
+
|
|
157
|
+
export { type CategoryService, CreateCategoryInput, CreateGalleryInput, GalleryCategoryItem, GalleryConfig, GalleryDetail, GalleryListItem, type GalleryRoutes, type GalleryService, PaginatedResult, RouteHandler, UpdateCategoryInput, UpdateGalleryInput, buildPaginatedResult, createCategoryService, createGalleryRoutes, createGalleryService, getFeaturedGalleries, getGalleryCount, getGalleryItems, getRecentGalleries };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { PaginatedResult, GalleryConfig, GalleryCategoryItem, CreateCategoryInput, UpdateCategoryInput, GalleryListItem, GalleryDetail, CreateGalleryInput, UpdateGalleryInput, RouteHandler } from '../types/index.js';
|
|
2
|
+
export { ApiContext, ApiWrapper, GalleryI18nKey, GallerySlotName, PaginationMeta, PrismaLike, SortOrder } from '../types/index.js';
|
|
3
|
+
export { C as CategoryInUseError, a as CategoryNotFoundError, G as GalleryError, b as GalleryNotFoundError, P as PermissionDeniedError, g as getGalleryConfig, s as setGalleryConfig } from '../errors-B9gzUsX-.js';
|
|
4
|
+
import 'next/server';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* pagination math 를 일관되게 처리. ballet 의 `buildPaginatedResult` 와 동일 시맨틱.
|
|
9
|
+
*
|
|
10
|
+
* - total === 0 일 때 totalPages = 0 (clamp 안 함; UI 에서 "결과 없음" 로 처리)
|
|
11
|
+
* - page > totalPages 여도 결과 자체는 caller 의 query 가 결정 — meta 만 계산
|
|
12
|
+
*/
|
|
13
|
+
declare function buildPaginatedResult<T>(items: T[], page: number, limit: number, total: number): PaginatedResult<T>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* spec §9-2 의 categoryService.
|
|
17
|
+
*
|
|
18
|
+
* 호스트 독립성을 위해 Prisma delegate 는 `config.prisma[modelName]` 패턴으로 접근한다
|
|
19
|
+
* (modelName 기본값 = "galleryCategory", gallery 본체 modelName 기본값 = "gallery").
|
|
20
|
+
*
|
|
21
|
+
* remove() 는 `onDelete: Restrict` 보강 — 사전 gallery.count() 후 throw.
|
|
22
|
+
* admin UX 가 친화적 에러 메시지를 표시하도록 명시적 throw 를 사용.
|
|
23
|
+
*/
|
|
24
|
+
declare function createCategoryService(config: GalleryConfig): {
|
|
25
|
+
list: (opts?: {
|
|
26
|
+
isActive?: boolean;
|
|
27
|
+
}) => Promise<GalleryCategoryItem[]>;
|
|
28
|
+
getBySlug: (slug: string) => Promise<GalleryCategoryItem | null>;
|
|
29
|
+
getById: (id: string) => Promise<GalleryCategoryItem | null>;
|
|
30
|
+
create: (data: CreateCategoryInput) => Promise<GalleryCategoryItem>;
|
|
31
|
+
update: (id: string, data: UpdateCategoryInput) => Promise<GalleryCategoryItem>;
|
|
32
|
+
remove: (id: string) => Promise<void>;
|
|
33
|
+
reorder: (ids: string[]) => Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
type CategoryService = ReturnType<typeof createCategoryService>;
|
|
36
|
+
|
|
37
|
+
type SortKey$1 = "sortOrder" | "createdAt" | "updatedAt" | "caption";
|
|
38
|
+
/**
|
|
39
|
+
* spec §9-2 의 createGalleryService.
|
|
40
|
+
*
|
|
41
|
+
* 핵심 규칙:
|
|
42
|
+
* - Prisma delegate 는 `config.prisma[config.modelName ?? "gallery"]` 패턴.
|
|
43
|
+
* - `include: { category: true }` 만 사용. **author include 금지** (Gallery 모델에 author 관계 없음, §3 호스트 독립 원칙).
|
|
44
|
+
* - 삭제 시 `config.storage` 가 isEnabled() true 면 collectKeys → deleteKeys.
|
|
45
|
+
* - `revalidate` 는 service 에서 호출하지 않음 (route handler 책임).
|
|
46
|
+
*/
|
|
47
|
+
declare function createGalleryService(config: GalleryConfig): {
|
|
48
|
+
listFeatured: (limit?: number) => Promise<GalleryListItem[]>;
|
|
49
|
+
listPublished: (limit?: number) => Promise<GalleryListItem[]>;
|
|
50
|
+
listPublishedByCategory: (categorySlug: string, limit?: number) => Promise<GalleryListItem[]>;
|
|
51
|
+
listAll: (opts: {
|
|
52
|
+
page?: number;
|
|
53
|
+
limit?: number;
|
|
54
|
+
categoryId?: string;
|
|
55
|
+
published?: boolean;
|
|
56
|
+
search?: string;
|
|
57
|
+
sortBy?: SortKey$1;
|
|
58
|
+
}) => Promise<PaginatedResult<GalleryListItem>>;
|
|
59
|
+
getById: (id: string) => Promise<GalleryDetail | null>;
|
|
60
|
+
create: (data: CreateGalleryInput, authorId: string) => Promise<GalleryDetail>;
|
|
61
|
+
createMany: (items: CreateGalleryInput[], authorId: string) => Promise<{
|
|
62
|
+
count: number;
|
|
63
|
+
}>;
|
|
64
|
+
update: (id: string, data: UpdateGalleryInput) => Promise<GalleryDetail>;
|
|
65
|
+
remove: (id: string) => Promise<void>;
|
|
66
|
+
removeMany: (ids: string[]) => Promise<{
|
|
67
|
+
count: number;
|
|
68
|
+
}>;
|
|
69
|
+
bulkUpdatePublished: (ids: string[], published: boolean) => Promise<{
|
|
70
|
+
count: number;
|
|
71
|
+
}>;
|
|
72
|
+
bulkUpdateFeatured: (ids: string[], featured: boolean) => Promise<{
|
|
73
|
+
count: number;
|
|
74
|
+
}>;
|
|
75
|
+
togglePublish: (id: string) => Promise<GalleryDetail>;
|
|
76
|
+
count: (opts?: {
|
|
77
|
+
published?: boolean;
|
|
78
|
+
}) => Promise<number>;
|
|
79
|
+
listRecent: (limit: number) => Promise<GalleryListItem[]>;
|
|
80
|
+
};
|
|
81
|
+
type GalleryService = ReturnType<typeof createGalleryService>;
|
|
82
|
+
|
|
83
|
+
interface GalleryRoutes {
|
|
84
|
+
collection: {
|
|
85
|
+
GET: RouteHandler;
|
|
86
|
+
POST: RouteHandler;
|
|
87
|
+
DELETE: RouteHandler;
|
|
88
|
+
};
|
|
89
|
+
item: {
|
|
90
|
+
GET: RouteHandler;
|
|
91
|
+
PUT: RouteHandler;
|
|
92
|
+
DELETE: RouteHandler;
|
|
93
|
+
};
|
|
94
|
+
publishToggle: {
|
|
95
|
+
PATCH: RouteHandler;
|
|
96
|
+
};
|
|
97
|
+
bulk: {
|
|
98
|
+
POST: RouteHandler;
|
|
99
|
+
PATCH: RouteHandler;
|
|
100
|
+
};
|
|
101
|
+
categoryCollection: {
|
|
102
|
+
GET: RouteHandler;
|
|
103
|
+
POST: RouteHandler;
|
|
104
|
+
};
|
|
105
|
+
categoryItem: {
|
|
106
|
+
GET: RouteHandler;
|
|
107
|
+
PUT: RouteHandler;
|
|
108
|
+
DELETE: RouteHandler;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
declare function createGalleryRoutes(config: GalleryConfig): GalleryRoutes;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* RSC loaders — spec §9-4.
|
|
115
|
+
*
|
|
116
|
+
* Server Components 가 직접 호출하는 함수. 각 함수는 service 의 대응 메서드를
|
|
117
|
+
* 얇게 감싸기만 한다. host 가 매번 `config` 를 주입해 explicit DI 를 유지한다
|
|
118
|
+
* (setGalleryConfig 의존 회피 — host 의 layout/page 에서 config 를 명시적으로
|
|
119
|
+
* 주입하는 것이 RSC graph 캐싱과 호환성 면에서 더 안전).
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
type SortKey = "sortOrder" | "createdAt" | "updatedAt" | "caption";
|
|
123
|
+
/**
|
|
124
|
+
* spec §9-4: `getGalleryItems(config, opts) → PaginatedResult<GalleryListItem>`
|
|
125
|
+
*
|
|
126
|
+
* admin 페이지의 RSC 가 직접 호출. fetch 라운드트립 없이 service 호출.
|
|
127
|
+
*/
|
|
128
|
+
declare function getGalleryItems(config: GalleryConfig, opts: {
|
|
129
|
+
page?: number;
|
|
130
|
+
limit?: number;
|
|
131
|
+
categoryId?: string;
|
|
132
|
+
published?: boolean;
|
|
133
|
+
search?: string;
|
|
134
|
+
sortBy?: SortKey;
|
|
135
|
+
}): Promise<PaginatedResult<GalleryListItem>>;
|
|
136
|
+
/**
|
|
137
|
+
* spec §9-4: `getFeaturedGalleries(config, limit?) → GalleryListItem[]`
|
|
138
|
+
*
|
|
139
|
+
* 홈 페이지의 모자이크 그리드 등 featured 영역에서 호출.
|
|
140
|
+
*/
|
|
141
|
+
declare function getFeaturedGalleries(config: GalleryConfig, limit?: number): Promise<GalleryListItem[]>;
|
|
142
|
+
/**
|
|
143
|
+
* spec §9-4: `getRecentGalleries(config, limit) → GalleryListItem[]`
|
|
144
|
+
*
|
|
145
|
+
* 대시보드의 최근 항목 위젯 등에서 호출.
|
|
146
|
+
*/
|
|
147
|
+
declare function getRecentGalleries(config: GalleryConfig, limit: number): Promise<GalleryListItem[]>;
|
|
148
|
+
/**
|
|
149
|
+
* spec §9-4: `getGalleryCount(config, opts?) → number`
|
|
150
|
+
*
|
|
151
|
+
* 대시보드 통계 등에서 호출. `opts.published` 미지정 시 전체 count.
|
|
152
|
+
*/
|
|
153
|
+
declare function getGalleryCount(config: GalleryConfig, opts?: {
|
|
154
|
+
published?: boolean;
|
|
155
|
+
}): Promise<number>;
|
|
156
|
+
|
|
157
|
+
export { type CategoryService, CreateCategoryInput, CreateGalleryInput, GalleryCategoryItem, GalleryConfig, GalleryDetail, GalleryListItem, type GalleryRoutes, type GalleryService, PaginatedResult, RouteHandler, UpdateCategoryInput, UpdateGalleryInput, buildPaginatedResult, createCategoryService, createGalleryRoutes, createGalleryService, getFeaturedGalleries, getGalleryCount, getGalleryItems, getRecentGalleries };
|