@voyantjs/products 0.3.1 → 0.4.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/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/routes-public.d.ts +167 -12
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +13 -1
- package/dist/routes.d.ts +669 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +117 -1
- package/dist/schema-itinerary.d.ts +51 -0
- package/dist/schema-itinerary.d.ts.map +1 -1
- package/dist/schema-itinerary.js +3 -0
- package/dist/schema-relations.d.ts +14 -0
- package/dist/schema-relations.d.ts.map +1 -1
- package/dist/schema-relations.js +28 -1
- package/dist/schema-taxonomy.d.ts +435 -0
- package/dist/schema-taxonomy.d.ts.map +1 -1
- package/dist/schema-taxonomy.js +47 -0
- package/dist/service-catalog.d.ts +237 -0
- package/dist/service-catalog.d.ts.map +1 -0
- package/dist/service-catalog.js +478 -0
- package/dist/service-public.d.ts +136 -12
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +146 -260
- package/dist/service.d.ts +292 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +388 -2
- package/dist/tasks/brochure-printers.d.ts +29 -0
- package/dist/tasks/brochure-printers.d.ts.map +1 -0
- package/dist/tasks/brochure-printers.js +94 -0
- package/dist/tasks/brochure-templates.d.ts +36 -0
- package/dist/tasks/brochure-templates.d.ts.map +1 -0
- package/dist/tasks/brochure-templates.js +98 -0
- package/dist/tasks/brochures.d.ts +42 -0
- package/dist/tasks/brochures.d.ts.map +1 -0
- package/dist/tasks/brochures.js +69 -0
- package/dist/tasks/index.d.ts +3 -0
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +3 -0
- package/dist/validation-catalog.d.ts +388 -0
- package/dist/validation-catalog.d.ts.map +1 -0
- package/dist/validation-catalog.js +54 -0
- package/dist/validation-content.d.ts +109 -0
- package/dist/validation-content.d.ts.map +1 -1
- package/dist/validation-content.js +63 -1
- package/dist/validation-public.d.ts +208 -19
- package/dist/validation-public.d.ts.map +1 -1
- package/dist/validation-public.js +39 -2
- package/dist/validation-shared.d.ts +6 -0
- package/dist/validation-shared.d.ts.map +1 -1
- package/dist/validation-shared.js +1 -0
- package/package.json +6 -4
package/dist/service-public.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
-
import type { PublicCatalogCategoryListQuery, PublicCatalogProductListQuery, PublicCatalogProductLookupBySlugQuery, PublicCatalogTagListQuery } from "./validation-public.js";
|
|
2
|
+
import type { PublicCatalogCategoryListQuery, PublicCatalogDestinationListQuery, PublicCatalogProductListQuery, PublicCatalogProductLookupBySlugQuery, PublicCatalogTagListQuery } from "./validation-public.js";
|
|
3
3
|
export declare const publicProductsService: {
|
|
4
4
|
listCatalogProducts(db: PostgresJsDatabase, query: PublicCatalogProductListQuery): Promise<{
|
|
5
5
|
data: ({
|
|
@@ -38,6 +38,28 @@ export declare const publicProductsService: {
|
|
|
38
38
|
name: string;
|
|
39
39
|
}[];
|
|
40
40
|
capabilities: string[];
|
|
41
|
+
destinations: {
|
|
42
|
+
id: string;
|
|
43
|
+
parentId: string | null;
|
|
44
|
+
slug: string;
|
|
45
|
+
name: string;
|
|
46
|
+
description: string | null;
|
|
47
|
+
seoTitle: string | null;
|
|
48
|
+
seoDescription: string | null;
|
|
49
|
+
destinationType: string;
|
|
50
|
+
sortOrder: number;
|
|
51
|
+
}[];
|
|
52
|
+
locations: {
|
|
53
|
+
id: string;
|
|
54
|
+
locationType: "other" | "start" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
|
|
55
|
+
title: string;
|
|
56
|
+
address: string | null;
|
|
57
|
+
city: string | null;
|
|
58
|
+
countryCode: string | null;
|
|
59
|
+
latitude: number | null;
|
|
60
|
+
longitude: number | null;
|
|
61
|
+
sortOrder: number;
|
|
62
|
+
}[];
|
|
41
63
|
coverMedia: {
|
|
42
64
|
id: string;
|
|
43
65
|
mediaType: "image" | "video" | "document";
|
|
@@ -47,9 +69,25 @@ export declare const publicProductsService: {
|
|
|
47
69
|
altText: string | null;
|
|
48
70
|
sortOrder: number;
|
|
49
71
|
isCover: boolean;
|
|
72
|
+
isBrochure: boolean;
|
|
73
|
+
isBrochureCurrent: boolean;
|
|
74
|
+
brochureVersion: number | null;
|
|
50
75
|
} | null;
|
|
51
76
|
isFeatured: boolean;
|
|
52
77
|
} | {
|
|
78
|
+
brochure: {
|
|
79
|
+
id: string;
|
|
80
|
+
mediaType: "image" | "video" | "document";
|
|
81
|
+
name: string;
|
|
82
|
+
url: string;
|
|
83
|
+
mimeType: string | null;
|
|
84
|
+
altText: string | null;
|
|
85
|
+
sortOrder: number;
|
|
86
|
+
isCover: boolean;
|
|
87
|
+
isBrochure: boolean;
|
|
88
|
+
isBrochureCurrent: boolean;
|
|
89
|
+
brochureVersion: number | null;
|
|
90
|
+
} | null;
|
|
53
91
|
media: {
|
|
54
92
|
id: string;
|
|
55
93
|
mediaType: "image" | "video" | "document";
|
|
@@ -59,6 +97,9 @@ export declare const publicProductsService: {
|
|
|
59
97
|
altText: string | null;
|
|
60
98
|
sortOrder: number;
|
|
61
99
|
isCover: boolean;
|
|
100
|
+
isBrochure: boolean;
|
|
101
|
+
isBrochureCurrent: boolean;
|
|
102
|
+
brochureVersion: number | null;
|
|
62
103
|
}[];
|
|
63
104
|
features: {
|
|
64
105
|
id: string;
|
|
@@ -73,17 +114,6 @@ export declare const publicProductsService: {
|
|
|
73
114
|
answer: string;
|
|
74
115
|
sortOrder: number;
|
|
75
116
|
}[];
|
|
76
|
-
locations: {
|
|
77
|
-
id: string;
|
|
78
|
-
locationType: "other" | "start" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
|
|
79
|
-
title: string;
|
|
80
|
-
address: string | null;
|
|
81
|
-
city: string | null;
|
|
82
|
-
countryCode: string | null;
|
|
83
|
-
latitude: number | null;
|
|
84
|
-
longitude: number | null;
|
|
85
|
-
sortOrder: number;
|
|
86
|
-
}[];
|
|
87
117
|
id: string;
|
|
88
118
|
name: string;
|
|
89
119
|
description: string | null;
|
|
@@ -119,6 +149,28 @@ export declare const publicProductsService: {
|
|
|
119
149
|
name: string;
|
|
120
150
|
}[];
|
|
121
151
|
capabilities: string[];
|
|
152
|
+
destinations: {
|
|
153
|
+
id: string;
|
|
154
|
+
parentId: string | null;
|
|
155
|
+
slug: string;
|
|
156
|
+
name: string;
|
|
157
|
+
description: string | null;
|
|
158
|
+
seoTitle: string | null;
|
|
159
|
+
seoDescription: string | null;
|
|
160
|
+
destinationType: string;
|
|
161
|
+
sortOrder: number;
|
|
162
|
+
}[];
|
|
163
|
+
locations: {
|
|
164
|
+
id: string;
|
|
165
|
+
locationType: "other" | "start" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
|
|
166
|
+
title: string;
|
|
167
|
+
address: string | null;
|
|
168
|
+
city: string | null;
|
|
169
|
+
countryCode: string | null;
|
|
170
|
+
latitude: number | null;
|
|
171
|
+
longitude: number | null;
|
|
172
|
+
sortOrder: number;
|
|
173
|
+
}[];
|
|
122
174
|
coverMedia: {
|
|
123
175
|
id: string;
|
|
124
176
|
mediaType: "image" | "video" | "document";
|
|
@@ -128,6 +180,9 @@ export declare const publicProductsService: {
|
|
|
128
180
|
altText: string | null;
|
|
129
181
|
sortOrder: number;
|
|
130
182
|
isCover: boolean;
|
|
183
|
+
isBrochure: boolean;
|
|
184
|
+
isBrochureCurrent: boolean;
|
|
185
|
+
brochureVersion: number | null;
|
|
131
186
|
} | null;
|
|
132
187
|
isFeatured: boolean;
|
|
133
188
|
})[];
|
|
@@ -173,6 +228,28 @@ export declare const publicProductsService: {
|
|
|
173
228
|
name: string;
|
|
174
229
|
}[];
|
|
175
230
|
capabilities: string[];
|
|
231
|
+
destinations: {
|
|
232
|
+
id: string;
|
|
233
|
+
parentId: string | null;
|
|
234
|
+
slug: string;
|
|
235
|
+
name: string;
|
|
236
|
+
description: string | null;
|
|
237
|
+
seoTitle: string | null;
|
|
238
|
+
seoDescription: string | null;
|
|
239
|
+
destinationType: string;
|
|
240
|
+
sortOrder: number;
|
|
241
|
+
}[];
|
|
242
|
+
locations: {
|
|
243
|
+
id: string;
|
|
244
|
+
locationType: "other" | "start" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
|
|
245
|
+
title: string;
|
|
246
|
+
address: string | null;
|
|
247
|
+
city: string | null;
|
|
248
|
+
countryCode: string | null;
|
|
249
|
+
latitude: number | null;
|
|
250
|
+
longitude: number | null;
|
|
251
|
+
sortOrder: number;
|
|
252
|
+
}[];
|
|
176
253
|
coverMedia: {
|
|
177
254
|
id: string;
|
|
178
255
|
mediaType: "image" | "video" | "document";
|
|
@@ -182,6 +259,9 @@ export declare const publicProductsService: {
|
|
|
182
259
|
altText: string | null;
|
|
183
260
|
sortOrder: number;
|
|
184
261
|
isCover: boolean;
|
|
262
|
+
isBrochure: boolean;
|
|
263
|
+
isBrochureCurrent: boolean;
|
|
264
|
+
brochureVersion: number | null;
|
|
185
265
|
} | null;
|
|
186
266
|
isFeatured: boolean;
|
|
187
267
|
} | null>;
|
|
@@ -221,6 +301,28 @@ export declare const publicProductsService: {
|
|
|
221
301
|
name: string;
|
|
222
302
|
}[];
|
|
223
303
|
capabilities: string[];
|
|
304
|
+
destinations: {
|
|
305
|
+
id: string;
|
|
306
|
+
parentId: string | null;
|
|
307
|
+
slug: string;
|
|
308
|
+
name: string;
|
|
309
|
+
description: string | null;
|
|
310
|
+
seoTitle: string | null;
|
|
311
|
+
seoDescription: string | null;
|
|
312
|
+
destinationType: string;
|
|
313
|
+
sortOrder: number;
|
|
314
|
+
}[];
|
|
315
|
+
locations: {
|
|
316
|
+
id: string;
|
|
317
|
+
locationType: "other" | "start" | "end" | "meeting_point" | "pickup" | "dropoff" | "point_of_interest";
|
|
318
|
+
title: string;
|
|
319
|
+
address: string | null;
|
|
320
|
+
city: string | null;
|
|
321
|
+
countryCode: string | null;
|
|
322
|
+
latitude: number | null;
|
|
323
|
+
longitude: number | null;
|
|
324
|
+
sortOrder: number;
|
|
325
|
+
}[];
|
|
224
326
|
coverMedia: {
|
|
225
327
|
id: string;
|
|
226
328
|
mediaType: "image" | "video" | "document";
|
|
@@ -230,9 +332,15 @@ export declare const publicProductsService: {
|
|
|
230
332
|
altText: string | null;
|
|
231
333
|
sortOrder: number;
|
|
232
334
|
isCover: boolean;
|
|
335
|
+
isBrochure: boolean;
|
|
336
|
+
isBrochureCurrent: boolean;
|
|
337
|
+
brochureVersion: number | null;
|
|
233
338
|
} | null;
|
|
234
339
|
isFeatured: boolean;
|
|
235
340
|
} | null>;
|
|
341
|
+
getCatalogProductBrochure(db: PostgresJsDatabase, productId: string, query?: {
|
|
342
|
+
languageTag?: string | null;
|
|
343
|
+
}): Promise<unknown>;
|
|
236
344
|
listCatalogCategories(db: PostgresJsDatabase, query: PublicCatalogCategoryListQuery): Promise<{
|
|
237
345
|
data: {
|
|
238
346
|
parentId: string | null;
|
|
@@ -255,5 +363,21 @@ export declare const publicProductsService: {
|
|
|
255
363
|
limit: number;
|
|
256
364
|
offset: number;
|
|
257
365
|
}>;
|
|
366
|
+
listCatalogDestinations(db: PostgresJsDatabase, query: PublicCatalogDestinationListQuery): Promise<{
|
|
367
|
+
data: {
|
|
368
|
+
id: string;
|
|
369
|
+
parentId: string | null;
|
|
370
|
+
slug: string;
|
|
371
|
+
name: string;
|
|
372
|
+
description: string | null;
|
|
373
|
+
seoTitle: string | null;
|
|
374
|
+
seoDescription: string | null;
|
|
375
|
+
destinationType: string;
|
|
376
|
+
sortOrder: number;
|
|
377
|
+
}[];
|
|
378
|
+
total: number;
|
|
379
|
+
limit: number;
|
|
380
|
+
offset: number;
|
|
381
|
+
}>;
|
|
258
382
|
};
|
|
259
383
|
//# sourceMappingURL=service-public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"service-public.d.ts","sourceRoot":"","sources":["../src/service-public.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgBjE,OAAO,KAAK,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,6BAA6B,EAC7B,qCAAqC,EACrC,yBAAyB,EAC1B,MAAM,wBAAwB,CAAA;AAgI/B,eAAO,MAAM,qBAAqB;4BACF,kBAAkB,SAAS,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BA2HhF,kBAAkB,MAClB,MAAM,UACH;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCA2BlC,kBAAkB,QAChB,MAAM,UACL,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAoCxC,kBAAkB,aACX,MAAM,UACV;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;8BAMR,kBAAkB,SAAS,8BAA8B;;;;;;;;;;;;;wBAkD/D,kBAAkB,SAAS,yBAAyB;;;;;;;;;gCA+B5C,kBAAkB,SAAS,iCAAiC;;;;;;;;;;;;;;;;CA8F/F,CAAA"}
|
package/dist/service-public.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { and, asc, desc, eq, ilike, inArray, notInArray, or, sql } from "drizzle-orm";
|
|
2
|
-
import {
|
|
2
|
+
import { destinations, destinationTranslations, productCategories, productCategoryProducts, productDestinations, productLocations, products, productTagProducts, productTags, productTranslations, productVisibilitySettings, } from "./schema.js";
|
|
3
|
+
import { catalogProductsService } from "./service-catalog.js";
|
|
3
4
|
function impossibleCondition() {
|
|
4
5
|
return sql `1 = 0`;
|
|
5
6
|
}
|
|
6
|
-
function normalizeDate(value) {
|
|
7
|
-
if (!value) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
return value instanceof Date ? value.toISOString() : value;
|
|
11
|
-
}
|
|
12
7
|
function normalizeLanguageTag(value) {
|
|
13
8
|
const normalized = value?.trim().toLowerCase();
|
|
14
9
|
return normalized || null;
|
|
@@ -28,6 +23,21 @@ async function listProductIdsForTag(db, tagId) {
|
|
|
28
23
|
.where(eq(productTagProducts.tagId, tagId));
|
|
29
24
|
return rows.map((row) => row.productId);
|
|
30
25
|
}
|
|
26
|
+
async function listProductIdsForDestinationId(db, destinationId) {
|
|
27
|
+
const rows = await db
|
|
28
|
+
.select({ productId: productDestinations.productId })
|
|
29
|
+
.from(productDestinations)
|
|
30
|
+
.where(eq(productDestinations.destinationId, destinationId));
|
|
31
|
+
return rows.map((row) => row.productId);
|
|
32
|
+
}
|
|
33
|
+
async function listProductIdsForDestinationSlug(db, slug) {
|
|
34
|
+
const rows = await db
|
|
35
|
+
.select({ productId: productDestinations.productId })
|
|
36
|
+
.from(productDestinations)
|
|
37
|
+
.innerJoin(destinations, eq(destinations.id, productDestinations.destinationId))
|
|
38
|
+
.where(eq(destinations.slug, slug));
|
|
39
|
+
return rows.map((row) => row.productId);
|
|
40
|
+
}
|
|
31
41
|
async function listFeaturedProductIds(db) {
|
|
32
42
|
const rows = await db
|
|
33
43
|
.select({ productId: productVisibilitySettings.productId })
|
|
@@ -35,260 +45,39 @@ async function listFeaturedProductIds(db) {
|
|
|
35
45
|
.where(eq(productVisibilitySettings.isFeatured, true));
|
|
36
46
|
return rows.map((row) => row.productId);
|
|
37
47
|
}
|
|
48
|
+
async function listProductIdsForLocationTitle(db, title) {
|
|
49
|
+
const rows = await db
|
|
50
|
+
.select({ productId: productLocations.productId })
|
|
51
|
+
.from(productLocations)
|
|
52
|
+
.where(ilike(productLocations.title, title));
|
|
53
|
+
return rows.map((row) => row.productId);
|
|
54
|
+
}
|
|
55
|
+
async function listProductIdsForLocationCity(db, city) {
|
|
56
|
+
const rows = await db
|
|
57
|
+
.select({ productId: productLocations.productId })
|
|
58
|
+
.from(productLocations)
|
|
59
|
+
.where(ilike(productLocations.city, city));
|
|
60
|
+
return rows.map((row) => row.productId);
|
|
61
|
+
}
|
|
62
|
+
async function listProductIdsForLocationCountryCode(db, countryCode) {
|
|
63
|
+
const rows = await db
|
|
64
|
+
.select({ productId: productLocations.productId })
|
|
65
|
+
.from(productLocations)
|
|
66
|
+
.where(eq(productLocations.countryCode, countryCode));
|
|
67
|
+
return rows.map((row) => row.productId);
|
|
68
|
+
}
|
|
69
|
+
async function listProductIdsForLocationType(db, locationType) {
|
|
70
|
+
const rows = await db
|
|
71
|
+
.select({ productId: productLocations.productId })
|
|
72
|
+
.from(productLocations)
|
|
73
|
+
.where(eq(productLocations.locationType, locationType));
|
|
74
|
+
return rows.map((row) => row.productId);
|
|
75
|
+
}
|
|
38
76
|
async function hydrateCatalogProducts(db, productRows, options) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const productTypeIds = Array.from(new Set(productRows
|
|
44
|
-
.map((product) => product.productTypeId)
|
|
45
|
-
.filter((value) => Boolean(value))));
|
|
46
|
-
const [categoryRows, tagRows, translationRows, typeRows, capabilityRows, mediaRows, featuredRows, featureRows, faqRows, locationRows,] = await Promise.all([
|
|
47
|
-
db
|
|
48
|
-
.select({
|
|
49
|
-
productId: productCategoryProducts.productId,
|
|
50
|
-
id: productCategories.id,
|
|
51
|
-
parentId: productCategories.parentId,
|
|
52
|
-
name: productCategories.name,
|
|
53
|
-
slug: productCategories.slug,
|
|
54
|
-
description: productCategories.description,
|
|
55
|
-
sortOrder: productCategories.sortOrder,
|
|
56
|
-
})
|
|
57
|
-
.from(productCategoryProducts)
|
|
58
|
-
.innerJoin(productCategories, eq(productCategories.id, productCategoryProducts.categoryId))
|
|
59
|
-
.where(and(inArray(productCategoryProducts.productId, productIds), eq(productCategories.active, true)))
|
|
60
|
-
.orderBy(asc(productCategoryProducts.sortOrder), asc(productCategories.sortOrder), asc(productCategories.name)),
|
|
61
|
-
db
|
|
62
|
-
.select({
|
|
63
|
-
productId: productTagProducts.productId,
|
|
64
|
-
id: productTags.id,
|
|
65
|
-
name: productTags.name,
|
|
66
|
-
})
|
|
67
|
-
.from(productTagProducts)
|
|
68
|
-
.innerJoin(productTags, eq(productTags.id, productTagProducts.tagId))
|
|
69
|
-
.where(inArray(productTagProducts.productId, productIds))
|
|
70
|
-
.orderBy(asc(productTags.name)),
|
|
71
|
-
options?.languageTag
|
|
72
|
-
? db
|
|
73
|
-
.select({
|
|
74
|
-
productId: productTranslations.productId,
|
|
75
|
-
languageTag: productTranslations.languageTag,
|
|
76
|
-
slug: productTranslations.slug,
|
|
77
|
-
name: productTranslations.name,
|
|
78
|
-
shortDescription: productTranslations.shortDescription,
|
|
79
|
-
description: productTranslations.description,
|
|
80
|
-
seoTitle: productTranslations.seoTitle,
|
|
81
|
-
seoDescription: productTranslations.seoDescription,
|
|
82
|
-
})
|
|
83
|
-
.from(productTranslations)
|
|
84
|
-
.where(and(inArray(productTranslations.productId, productIds), eq(productTranslations.languageTag, options.languageTag)))
|
|
85
|
-
: Promise.resolve([]),
|
|
86
|
-
productTypeIds.length > 0
|
|
87
|
-
? db
|
|
88
|
-
.select({
|
|
89
|
-
id: productTypes.id,
|
|
90
|
-
code: productTypes.code,
|
|
91
|
-
name: productTypes.name,
|
|
92
|
-
description: productTypes.description,
|
|
93
|
-
})
|
|
94
|
-
.from(productTypes)
|
|
95
|
-
.where(and(inArray(productTypes.id, productTypeIds), eq(productTypes.active, true)))
|
|
96
|
-
: Promise.resolve([]),
|
|
97
|
-
db
|
|
98
|
-
.select({
|
|
99
|
-
productId: productCapabilities.productId,
|
|
100
|
-
capability: productCapabilities.capability,
|
|
101
|
-
})
|
|
102
|
-
.from(productCapabilities)
|
|
103
|
-
.where(and(inArray(productCapabilities.productId, productIds), eq(productCapabilities.enabled, true)))
|
|
104
|
-
.orderBy(asc(productCapabilities.capability)),
|
|
105
|
-
db
|
|
106
|
-
.select({
|
|
107
|
-
productId: productMedia.productId,
|
|
108
|
-
id: productMedia.id,
|
|
109
|
-
mediaType: productMedia.mediaType,
|
|
110
|
-
name: productMedia.name,
|
|
111
|
-
url: productMedia.url,
|
|
112
|
-
mimeType: productMedia.mimeType,
|
|
113
|
-
altText: productMedia.altText,
|
|
114
|
-
sortOrder: productMedia.sortOrder,
|
|
115
|
-
isCover: productMedia.isCover,
|
|
116
|
-
})
|
|
117
|
-
.from(productMedia)
|
|
118
|
-
.where(inArray(productMedia.productId, productIds))
|
|
119
|
-
.orderBy(desc(productMedia.isCover), asc(productMedia.sortOrder), asc(productMedia.createdAt)),
|
|
120
|
-
db
|
|
121
|
-
.select({ productId: productVisibilitySettings.productId })
|
|
122
|
-
.from(productVisibilitySettings)
|
|
123
|
-
.where(and(inArray(productVisibilitySettings.productId, productIds), eq(productVisibilitySettings.isFeatured, true))),
|
|
124
|
-
options?.includeContent
|
|
125
|
-
? db
|
|
126
|
-
.select({
|
|
127
|
-
productId: productFeatures.productId,
|
|
128
|
-
id: productFeatures.id,
|
|
129
|
-
featureType: productFeatures.featureType,
|
|
130
|
-
title: productFeatures.title,
|
|
131
|
-
description: productFeatures.description,
|
|
132
|
-
sortOrder: productFeatures.sortOrder,
|
|
133
|
-
})
|
|
134
|
-
.from(productFeatures)
|
|
135
|
-
.where(inArray(productFeatures.productId, productIds))
|
|
136
|
-
.orderBy(asc(productFeatures.sortOrder), asc(productFeatures.createdAt))
|
|
137
|
-
: Promise.resolve([]),
|
|
138
|
-
options?.includeContent
|
|
139
|
-
? db
|
|
140
|
-
.select({
|
|
141
|
-
productId: productFaqs.productId,
|
|
142
|
-
id: productFaqs.id,
|
|
143
|
-
question: productFaqs.question,
|
|
144
|
-
answer: productFaqs.answer,
|
|
145
|
-
sortOrder: productFaqs.sortOrder,
|
|
146
|
-
})
|
|
147
|
-
.from(productFaqs)
|
|
148
|
-
.where(inArray(productFaqs.productId, productIds))
|
|
149
|
-
.orderBy(asc(productFaqs.sortOrder), asc(productFaqs.createdAt))
|
|
150
|
-
: Promise.resolve([]),
|
|
151
|
-
options?.includeContent
|
|
152
|
-
? db
|
|
153
|
-
.select({
|
|
154
|
-
productId: productLocations.productId,
|
|
155
|
-
id: productLocations.id,
|
|
156
|
-
locationType: productLocations.locationType,
|
|
157
|
-
title: productLocations.title,
|
|
158
|
-
address: productLocations.address,
|
|
159
|
-
city: productLocations.city,
|
|
160
|
-
countryCode: productLocations.countryCode,
|
|
161
|
-
latitude: productLocations.latitude,
|
|
162
|
-
longitude: productLocations.longitude,
|
|
163
|
-
sortOrder: productLocations.sortOrder,
|
|
164
|
-
})
|
|
165
|
-
.from(productLocations)
|
|
166
|
-
.where(inArray(productLocations.productId, productIds))
|
|
167
|
-
.orderBy(asc(productLocations.sortOrder), asc(productLocations.createdAt))
|
|
168
|
-
: Promise.resolve([]),
|
|
169
|
-
]);
|
|
170
|
-
const categoriesByProduct = new Map();
|
|
171
|
-
for (const row of categoryRows) {
|
|
172
|
-
const existing = categoriesByProduct.get(row.productId) ?? [];
|
|
173
|
-
existing.push(row);
|
|
174
|
-
categoriesByProduct.set(row.productId, existing);
|
|
175
|
-
}
|
|
176
|
-
const tagsByProduct = new Map();
|
|
177
|
-
for (const row of tagRows) {
|
|
178
|
-
const existing = tagsByProduct.get(row.productId) ?? [];
|
|
179
|
-
existing.push(row);
|
|
180
|
-
tagsByProduct.set(row.productId, existing);
|
|
181
|
-
}
|
|
182
|
-
const translationByProduct = new Map(translationRows.map((row) => [row.productId, row]));
|
|
183
|
-
const capabilitiesByProduct = new Map();
|
|
184
|
-
for (const row of capabilityRows) {
|
|
185
|
-
const existing = capabilitiesByProduct.get(row.productId) ?? [];
|
|
186
|
-
existing.push(row.capability);
|
|
187
|
-
capabilitiesByProduct.set(row.productId, existing);
|
|
188
|
-
}
|
|
189
|
-
const mediaByProduct = new Map();
|
|
190
|
-
for (const row of mediaRows) {
|
|
191
|
-
const existing = mediaByProduct.get(row.productId) ?? [];
|
|
192
|
-
existing.push(row);
|
|
193
|
-
mediaByProduct.set(row.productId, existing);
|
|
194
|
-
}
|
|
195
|
-
const featuresByProduct = new Map();
|
|
196
|
-
for (const row of featureRows) {
|
|
197
|
-
const existing = featuresByProduct.get(row.productId) ?? [];
|
|
198
|
-
existing.push(row);
|
|
199
|
-
featuresByProduct.set(row.productId, existing);
|
|
200
|
-
}
|
|
201
|
-
const faqsByProduct = new Map();
|
|
202
|
-
for (const row of faqRows) {
|
|
203
|
-
const existing = faqsByProduct.get(row.productId) ?? [];
|
|
204
|
-
existing.push(row);
|
|
205
|
-
faqsByProduct.set(row.productId, existing);
|
|
206
|
-
}
|
|
207
|
-
const locationsByProduct = new Map();
|
|
208
|
-
for (const row of locationRows) {
|
|
209
|
-
const existing = locationsByProduct.get(row.productId) ?? [];
|
|
210
|
-
existing.push(row);
|
|
211
|
-
locationsByProduct.set(row.productId, existing);
|
|
212
|
-
}
|
|
213
|
-
const typeById = new Map(typeRows.map((row) => [row.id, row]));
|
|
214
|
-
const featuredIds = new Set(featuredRows.map((row) => row.productId));
|
|
215
|
-
return productRows.map((product) => {
|
|
216
|
-
const translation = translationByProduct.get(product.id) ?? null;
|
|
217
|
-
const media = (mediaByProduct.get(product.id) ?? []).map((row) => ({
|
|
218
|
-
id: row.id,
|
|
219
|
-
mediaType: row.mediaType,
|
|
220
|
-
name: row.name,
|
|
221
|
-
url: row.url,
|
|
222
|
-
mimeType: row.mimeType ?? null,
|
|
223
|
-
altText: row.altText ?? null,
|
|
224
|
-
sortOrder: row.sortOrder,
|
|
225
|
-
isCover: row.isCover,
|
|
226
|
-
}));
|
|
227
|
-
const base = {
|
|
228
|
-
id: product.id,
|
|
229
|
-
name: translation?.name ?? product.name,
|
|
230
|
-
description: translation?.description ?? product.description ?? null,
|
|
231
|
-
contentLanguageTag: translation?.languageTag ?? null,
|
|
232
|
-
slug: translation?.slug ?? null,
|
|
233
|
-
shortDescription: translation?.shortDescription ?? null,
|
|
234
|
-
seoTitle: translation?.seoTitle ?? null,
|
|
235
|
-
seoDescription: translation?.seoDescription ?? null,
|
|
236
|
-
bookingMode: product.bookingMode,
|
|
237
|
-
capacityMode: product.capacityMode,
|
|
238
|
-
visibility: product.visibility,
|
|
239
|
-
sellCurrency: product.sellCurrency,
|
|
240
|
-
sellAmountCents: product.sellAmountCents ?? null,
|
|
241
|
-
startDate: normalizeDate(product.startDate),
|
|
242
|
-
endDate: normalizeDate(product.endDate),
|
|
243
|
-
pax: product.pax ?? null,
|
|
244
|
-
productType: product.productTypeId ? (typeById.get(product.productTypeId) ?? null) : null,
|
|
245
|
-
categories: (categoriesByProduct.get(product.id) ?? []).map((row) => ({
|
|
246
|
-
id: row.id,
|
|
247
|
-
parentId: row.parentId ?? null,
|
|
248
|
-
name: row.name,
|
|
249
|
-
slug: row.slug,
|
|
250
|
-
description: row.description ?? null,
|
|
251
|
-
sortOrder: row.sortOrder,
|
|
252
|
-
})),
|
|
253
|
-
tags: (tagsByProduct.get(product.id) ?? []).map((row) => ({
|
|
254
|
-
id: row.id,
|
|
255
|
-
name: row.name,
|
|
256
|
-
})),
|
|
257
|
-
capabilities: capabilitiesByProduct.get(product.id) ?? [],
|
|
258
|
-
coverMedia: media.find((item) => item.isCover) ?? media[0] ?? null,
|
|
259
|
-
isFeatured: featuredIds.has(product.id),
|
|
260
|
-
};
|
|
261
|
-
if (!options?.includeContent) {
|
|
262
|
-
return base;
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
...base,
|
|
266
|
-
media,
|
|
267
|
-
features: (featuresByProduct.get(product.id) ?? []).map((row) => ({
|
|
268
|
-
id: row.id,
|
|
269
|
-
featureType: row.featureType,
|
|
270
|
-
title: row.title,
|
|
271
|
-
description: row.description ?? null,
|
|
272
|
-
sortOrder: row.sortOrder,
|
|
273
|
-
})),
|
|
274
|
-
faqs: (faqsByProduct.get(product.id) ?? []).map((row) => ({
|
|
275
|
-
id: row.id,
|
|
276
|
-
question: row.question,
|
|
277
|
-
answer: row.answer,
|
|
278
|
-
sortOrder: row.sortOrder,
|
|
279
|
-
})),
|
|
280
|
-
locations: (locationsByProduct.get(product.id) ?? []).map((row) => ({
|
|
281
|
-
id: row.id,
|
|
282
|
-
locationType: row.locationType,
|
|
283
|
-
title: row.title,
|
|
284
|
-
address: row.address ?? null,
|
|
285
|
-
city: row.city ?? null,
|
|
286
|
-
countryCode: row.countryCode ?? null,
|
|
287
|
-
latitude: row.latitude ?? null,
|
|
288
|
-
longitude: row.longitude ?? null,
|
|
289
|
-
sortOrder: row.sortOrder,
|
|
290
|
-
})),
|
|
291
|
-
};
|
|
77
|
+
return catalogProductsService.hydrateProducts(db, productRows, {
|
|
78
|
+
includeContent: options?.includeContent,
|
|
79
|
+
languageTag: options?.languageTag,
|
|
80
|
+
fallbackLanguageTags: options?.languageTag ? [options.languageTag] : [],
|
|
292
81
|
});
|
|
293
82
|
}
|
|
294
83
|
function orderProducts(query) {
|
|
@@ -335,6 +124,14 @@ export const publicProductsService = {
|
|
|
335
124
|
const productIds = await listProductIdsForTag(db, query.tagId);
|
|
336
125
|
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
337
126
|
}
|
|
127
|
+
if (query.destinationId) {
|
|
128
|
+
const productIds = await listProductIdsForDestinationId(db, query.destinationId);
|
|
129
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
130
|
+
}
|
|
131
|
+
if (query.destinationSlug) {
|
|
132
|
+
const productIds = await listProductIdsForDestinationSlug(db, query.destinationSlug);
|
|
133
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
134
|
+
}
|
|
338
135
|
if (query.featured !== undefined) {
|
|
339
136
|
const productIds = await listFeaturedProductIds(db);
|
|
340
137
|
conditions.push(query.featured
|
|
@@ -345,6 +142,22 @@ export const publicProductsService = {
|
|
|
345
142
|
? notInArray(products.id, productIds)
|
|
346
143
|
: sql `1 = 1`);
|
|
347
144
|
}
|
|
145
|
+
if (query.locationTitle) {
|
|
146
|
+
const productIds = await listProductIdsForLocationTitle(db, query.locationTitle);
|
|
147
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
148
|
+
}
|
|
149
|
+
if (query.locationCity) {
|
|
150
|
+
const productIds = await listProductIdsForLocationCity(db, query.locationCity);
|
|
151
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
152
|
+
}
|
|
153
|
+
if (query.locationCountryCode) {
|
|
154
|
+
const productIds = await listProductIdsForLocationCountryCode(db, query.locationCountryCode.toUpperCase());
|
|
155
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
156
|
+
}
|
|
157
|
+
if (query.locationType) {
|
|
158
|
+
const productIds = await listProductIdsForLocationType(db, query.locationType);
|
|
159
|
+
conditions.push(productIds.length > 0 ? inArray(products.id, productIds) : impossibleCondition());
|
|
160
|
+
}
|
|
348
161
|
const where = and(...conditions);
|
|
349
162
|
const [rows, countResult] = await Promise.all([
|
|
350
163
|
db
|
|
@@ -409,6 +222,10 @@ export const publicProductsService = {
|
|
|
409
222
|
languageTag: normalizedLanguageTag ?? row.languageTag,
|
|
410
223
|
});
|
|
411
224
|
},
|
|
225
|
+
async getCatalogProductBrochure(db, productId, query = {}) {
|
|
226
|
+
const product = await this.getCatalogProductById(db, productId, query);
|
|
227
|
+
return product && "brochure" in product ? product.brochure : null;
|
|
228
|
+
},
|
|
412
229
|
async listCatalogCategories(db, query) {
|
|
413
230
|
const conditions = [eq(productCategories.active, true)];
|
|
414
231
|
if (query.parentId) {
|
|
@@ -476,4 +293,73 @@ export const publicProductsService = {
|
|
|
476
293
|
offset: query.offset,
|
|
477
294
|
};
|
|
478
295
|
},
|
|
296
|
+
async listCatalogDestinations(db, query) {
|
|
297
|
+
const conditions = [];
|
|
298
|
+
if (query.parentId) {
|
|
299
|
+
conditions.push(eq(destinations.parentId, query.parentId));
|
|
300
|
+
}
|
|
301
|
+
if (query.active !== undefined) {
|
|
302
|
+
conditions.push(eq(destinations.active, query.active));
|
|
303
|
+
}
|
|
304
|
+
if (query.destinationType) {
|
|
305
|
+
conditions.push(eq(destinations.destinationType, query.destinationType));
|
|
306
|
+
}
|
|
307
|
+
if (query.search) {
|
|
308
|
+
const translationRows = await db
|
|
309
|
+
.select({ destinationId: destinationTranslations.destinationId })
|
|
310
|
+
.from(destinationTranslations)
|
|
311
|
+
.where(and(...(query.languageTag
|
|
312
|
+
? [eq(destinationTranslations.languageTag, query.languageTag)]
|
|
313
|
+
: []), or(ilike(destinationTranslations.name, `%${query.search}%`), ilike(destinationTranslations.description, `%${query.search}%`))));
|
|
314
|
+
const destinationIds = translationRows.map((row) => row.destinationId);
|
|
315
|
+
conditions.push(destinationIds.length > 0
|
|
316
|
+
? inArray(destinations.id, destinationIds)
|
|
317
|
+
: impossibleCondition());
|
|
318
|
+
}
|
|
319
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
320
|
+
const [rows, countResult] = await Promise.all([
|
|
321
|
+
db
|
|
322
|
+
.select()
|
|
323
|
+
.from(destinations)
|
|
324
|
+
.where(where)
|
|
325
|
+
.limit(query.limit)
|
|
326
|
+
.offset(query.offset)
|
|
327
|
+
.orderBy(asc(destinations.sortOrder), asc(destinations.slug)),
|
|
328
|
+
db.select({ count: sql `count(*)::int` }).from(destinations).where(where),
|
|
329
|
+
]);
|
|
330
|
+
const destinationIds = rows.map((row) => row.id);
|
|
331
|
+
const translations = destinationIds.length > 0
|
|
332
|
+
? await db
|
|
333
|
+
.select()
|
|
334
|
+
.from(destinationTranslations)
|
|
335
|
+
.where(and(inArray(destinationTranslations.destinationId, destinationIds), ...(query.languageTag
|
|
336
|
+
? [eq(destinationTranslations.languageTag, query.languageTag)]
|
|
337
|
+
: [])))
|
|
338
|
+
: [];
|
|
339
|
+
const translationByDestination = new Map();
|
|
340
|
+
for (const row of translations) {
|
|
341
|
+
if (!translationByDestination.has(row.destinationId)) {
|
|
342
|
+
translationByDestination.set(row.destinationId, row);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
data: rows.map((row) => {
|
|
347
|
+
const translation = translationByDestination.get(row.id);
|
|
348
|
+
return {
|
|
349
|
+
id: row.id,
|
|
350
|
+
parentId: row.parentId ?? null,
|
|
351
|
+
slug: row.slug,
|
|
352
|
+
name: translation?.name ?? row.slug,
|
|
353
|
+
description: translation?.description ?? null,
|
|
354
|
+
seoTitle: translation?.seoTitle ?? null,
|
|
355
|
+
seoDescription: translation?.seoDescription ?? null,
|
|
356
|
+
destinationType: row.destinationType,
|
|
357
|
+
sortOrder: row.sortOrder,
|
|
358
|
+
};
|
|
359
|
+
}),
|
|
360
|
+
total: countResult[0]?.count ?? 0,
|
|
361
|
+
limit: query.limit,
|
|
362
|
+
offset: query.offset,
|
|
363
|
+
};
|
|
364
|
+
},
|
|
479
365
|
};
|