@cravery/firebase 0.0.7 → 0.0.8
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/asset/repository.d.ts +5 -1
- package/dist/asset/repository.d.ts.map +1 -1
- package/dist/asset/repository.js +20 -1
- package/dist/asset/repository.js.map +1 -1
- package/package.json +59 -59
- package/src/asset/index.ts +1 -1
- package/src/asset/repository.ts +142 -111
- package/src/recipe/converters.ts +98 -98
- package/src/recipe/utils.ts +143 -143
|
@@ -23,10 +23,14 @@ export declare class AssetRepository {
|
|
|
23
23
|
name: string;
|
|
24
24
|
notes?: string;
|
|
25
25
|
}[]>;
|
|
26
|
-
createWithLocale(slug: string, imageUrl: string, locale: string, localeData: {
|
|
26
|
+
createWithLocale(slug: string, imageUrl: string, name: string, locale: string, localeData: {
|
|
27
27
|
name: string;
|
|
28
28
|
notes?: string;
|
|
29
29
|
}): Promise<Asset>;
|
|
30
|
+
getWithPagination(page?: number, limit?: number, searchQuery?: string): Promise<{
|
|
31
|
+
assets: Asset[];
|
|
32
|
+
total: number;
|
|
33
|
+
}>;
|
|
30
34
|
}
|
|
31
35
|
export declare class IngredientRepository extends AssetRepository {
|
|
32
36
|
constructor(db: Firestore);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/asset/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,qBAAa,eAAe;IAExB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;gBADd,EAAE,EAAE,SAAS,EACb,cAAc,EAAE,MAAM;IAGhC,OAAO,KAAK,UAAU,GAErB;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAM3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAW7D,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC;IAKV,aAAa,CACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAcxD,gBAAgB,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/asset/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,qBAAa,eAAe;IAExB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;gBADd,EAAE,EAAE,SAAS,EACb,cAAc,EAAE,MAAM;IAGhC,OAAO,KAAK,UAAU,GAErB;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAM3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAW7D,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC;IAKV,aAAa,CACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAcxD,gBAAgB,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,KAAK,CAAC;IAqBX,iBAAiB,CACrB,IAAI,GAAE,MAAU,EAChB,KAAK,GAAE,MAAW,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAwB/C;AAED,qBAAa,oBAAqB,SAAQ,eAAe;gBAC3C,EAAE,EAAE,SAAS;CAG1B;AAED,qBAAa,mBAAoB,SAAQ,eAAe;gBAC1C,EAAE,EAAE,SAAS;CAG1B"}
|
package/dist/asset/repository.js
CHANGED
|
@@ -44,13 +44,14 @@ class AssetRepository {
|
|
|
44
44
|
.get();
|
|
45
45
|
return snapshot.docs.map((doc) => (Object.assign({ locale: doc.id }, doc.data())));
|
|
46
46
|
}
|
|
47
|
-
async createWithLocale(slug, imageUrl, locale, localeData) {
|
|
47
|
+
async createWithLocale(slug, imageUrl, name, locale, localeData) {
|
|
48
48
|
const now = firestore_1.Timestamp.now();
|
|
49
49
|
const batch = this.db.batch();
|
|
50
50
|
const docRef = this.collection.doc(slug);
|
|
51
51
|
const assetData = {
|
|
52
52
|
id: slug,
|
|
53
53
|
imageUrl,
|
|
54
|
+
name,
|
|
54
55
|
createdAt: now,
|
|
55
56
|
updatedAt: now,
|
|
56
57
|
};
|
|
@@ -60,6 +61,24 @@ class AssetRepository {
|
|
|
60
61
|
await batch.commit();
|
|
61
62
|
return assetData;
|
|
62
63
|
}
|
|
64
|
+
async getWithPagination(page = 1, limit = 50, searchQuery) {
|
|
65
|
+
// Build base query ordered by updatedAt descending
|
|
66
|
+
const query = this.collection.orderBy("updatedAt", "desc");
|
|
67
|
+
// Get all documents for accurate count and filtering
|
|
68
|
+
// Note: For very large datasets (10k+ docs), consider maintaining a separate counter
|
|
69
|
+
const allSnapshot = await query.get();
|
|
70
|
+
let allAssets = allSnapshot.docs.map((doc) => doc.data());
|
|
71
|
+
// Filter by search query if provided
|
|
72
|
+
if (searchQuery && searchQuery.trim()) {
|
|
73
|
+
const q = searchQuery.toLowerCase();
|
|
74
|
+
allAssets = allAssets.filter((asset) => asset.id.toLowerCase().includes(q));
|
|
75
|
+
}
|
|
76
|
+
// Calculate pagination
|
|
77
|
+
const total = allAssets.length;
|
|
78
|
+
const offset = (page - 1) * limit;
|
|
79
|
+
const paginatedAssets = allAssets.slice(offset, offset + limit);
|
|
80
|
+
return { assets: paginatedAssets, total };
|
|
81
|
+
}
|
|
63
82
|
}
|
|
64
83
|
exports.AssetRepository = AssetRepository;
|
|
65
84
|
class IngredientRepository extends AssetRepository {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/asset/repository.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/asset/repository.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@cravery/firebase",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Shared Firebase Admin SDK utilities for Cravery",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"default": "./dist/index.js"
|
|
11
|
-
},
|
|
12
|
-
"./recipe": {
|
|
13
|
-
"types": "./dist/recipe/index.d.ts",
|
|
14
|
-
"default": "./dist/recipe/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./iam": {
|
|
17
|
-
"types": "./dist/iam/index.d.ts",
|
|
18
|
-
"default": "./dist/iam/index.js"
|
|
19
|
-
},
|
|
20
|
-
"./utils": {
|
|
21
|
-
"types": "./dist/utils/index.d.ts",
|
|
22
|
-
"default": "./dist/utils/index.js"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"typesVersions": {
|
|
26
|
-
"*": {
|
|
27
|
-
"recipe": [
|
|
28
|
-
"dist/recipe/index.d.ts"
|
|
29
|
-
],
|
|
30
|
-
"iam": [
|
|
31
|
-
"dist/iam/index.d.ts"
|
|
32
|
-
],
|
|
33
|
-
"utils": [
|
|
34
|
-
"dist/utils/index.d.ts"
|
|
35
|
-
]
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsc",
|
|
40
|
-
"build:watch": "tsc --watch",
|
|
41
|
-
"clean": "rimraf dist && npm run build"
|
|
42
|
-
},
|
|
43
|
-
"keywords": [],
|
|
44
|
-
"peerDependencies": {
|
|
45
|
-
"firebase-admin": "^13.6.0"
|
|
46
|
-
},
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"firebase-admin": "^13.6.0",
|
|
49
|
-
"rimraf": "^6.1.2",
|
|
50
|
-
"typescript": "^5.9.3"
|
|
51
|
-
},
|
|
52
|
-
"files": [
|
|
53
|
-
"dist/**/*",
|
|
54
|
-
"src/**/*"
|
|
55
|
-
],
|
|
56
|
-
"dependencies": {
|
|
57
|
-
"@cravery/core": "^0.0.
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@cravery/firebase",
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "Shared Firebase Admin SDK utilities for Cravery",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./recipe": {
|
|
13
|
+
"types": "./dist/recipe/index.d.ts",
|
|
14
|
+
"default": "./dist/recipe/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./iam": {
|
|
17
|
+
"types": "./dist/iam/index.d.ts",
|
|
18
|
+
"default": "./dist/iam/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./utils": {
|
|
21
|
+
"types": "./dist/utils/index.d.ts",
|
|
22
|
+
"default": "./dist/utils/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"typesVersions": {
|
|
26
|
+
"*": {
|
|
27
|
+
"recipe": [
|
|
28
|
+
"dist/recipe/index.d.ts"
|
|
29
|
+
],
|
|
30
|
+
"iam": [
|
|
31
|
+
"dist/iam/index.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"utils": [
|
|
34
|
+
"dist/utils/index.d.ts"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"build:watch": "tsc --watch",
|
|
41
|
+
"clean": "rimraf dist && npm run build"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"firebase-admin": "^13.6.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"firebase-admin": "^13.6.0",
|
|
49
|
+
"rimraf": "^6.1.2",
|
|
50
|
+
"typescript": "^5.9.3"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist/**/*",
|
|
54
|
+
"src/**/*"
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@cravery/core": "^0.0.33"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/asset/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./repository";
|
|
1
|
+
export * from "./repository";
|
package/src/asset/repository.ts
CHANGED
|
@@ -1,111 +1,142 @@
|
|
|
1
|
-
import { Firestore, Timestamp } from "firebase-admin/firestore";
|
|
2
|
-
import { Asset } from "@cravery/core";
|
|
3
|
-
|
|
4
|
-
export class AssetRepository {
|
|
5
|
-
constructor(
|
|
6
|
-
private db: Firestore,
|
|
7
|
-
private collectionName: string,
|
|
8
|
-
) {}
|
|
9
|
-
|
|
10
|
-
private get collection() {
|
|
11
|
-
return this.db.collection(this.collectionName);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async findById(id: string): Promise<Asset | null> {
|
|
15
|
-
const doc = await this.collection.doc(id).get();
|
|
16
|
-
if (!doc.exists) return null;
|
|
17
|
-
return doc.data() as Asset;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async exists(id: string): Promise<boolean> {
|
|
21
|
-
const doc = await this.collection.doc(id).get();
|
|
22
|
-
return doc.exists;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async slugExists(slug: string): Promise<boolean> {
|
|
26
|
-
return this.exists(slug);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async update(slug: string, data: Partial<Asset>): Promise<void> {
|
|
30
|
-
await this.collection.doc(slug).update({
|
|
31
|
-
...data,
|
|
32
|
-
updatedAt: Timestamp.now(),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async getLocale(
|
|
37
|
-
slug: string,
|
|
38
|
-
locale: string,
|
|
39
|
-
): Promise<{ locale: string; name: string; notes?: string } | null> {
|
|
40
|
-
const docRef = this.collection.doc(slug).collection("locales").doc(locale);
|
|
41
|
-
const doc = await docRef.get();
|
|
42
|
-
if (!doc.exists) return null;
|
|
43
|
-
return { locale, ...doc.data() } as {
|
|
44
|
-
locale: string;
|
|
45
|
-
name: string;
|
|
46
|
-
notes?: string;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async setLocale(
|
|
51
|
-
slug: string,
|
|
52
|
-
locale: string,
|
|
53
|
-
data: { name: string; notes?: string },
|
|
54
|
-
): Promise<void> {
|
|
55
|
-
const docRef = this.collection.doc(slug).collection("locales").doc(locale);
|
|
56
|
-
await docRef.set({ locale, ...data });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async getAllLocales(
|
|
60
|
-
slug: string,
|
|
61
|
-
): Promise<{ locale: string; name: string; notes?: string }[]> {
|
|
62
|
-
const snapshot = await this.collection
|
|
63
|
-
.doc(slug)
|
|
64
|
-
.collection("locales")
|
|
65
|
-
.get();
|
|
66
|
-
return snapshot.docs.map(
|
|
67
|
-
(doc) =>
|
|
68
|
-
({
|
|
69
|
-
locale: doc.id,
|
|
70
|
-
...doc.data(),
|
|
71
|
-
}) as { locale: string; name: string; notes?: string },
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async createWithLocale(
|
|
76
|
-
slug: string,
|
|
77
|
-
imageUrl: string,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
import { Firestore, Timestamp } from "firebase-admin/firestore";
|
|
2
|
+
import { Asset } from "@cravery/core";
|
|
3
|
+
|
|
4
|
+
export class AssetRepository {
|
|
5
|
+
constructor(
|
|
6
|
+
private db: Firestore,
|
|
7
|
+
private collectionName: string,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
private get collection() {
|
|
11
|
+
return this.db.collection(this.collectionName);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async findById(id: string): Promise<Asset | null> {
|
|
15
|
+
const doc = await this.collection.doc(id).get();
|
|
16
|
+
if (!doc.exists) return null;
|
|
17
|
+
return doc.data() as Asset;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async exists(id: string): Promise<boolean> {
|
|
21
|
+
const doc = await this.collection.doc(id).get();
|
|
22
|
+
return doc.exists;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async slugExists(slug: string): Promise<boolean> {
|
|
26
|
+
return this.exists(slug);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async update(slug: string, data: Partial<Asset>): Promise<void> {
|
|
30
|
+
await this.collection.doc(slug).update({
|
|
31
|
+
...data,
|
|
32
|
+
updatedAt: Timestamp.now(),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getLocale(
|
|
37
|
+
slug: string,
|
|
38
|
+
locale: string,
|
|
39
|
+
): Promise<{ locale: string; name: string; notes?: string } | null> {
|
|
40
|
+
const docRef = this.collection.doc(slug).collection("locales").doc(locale);
|
|
41
|
+
const doc = await docRef.get();
|
|
42
|
+
if (!doc.exists) return null;
|
|
43
|
+
return { locale, ...doc.data() } as {
|
|
44
|
+
locale: string;
|
|
45
|
+
name: string;
|
|
46
|
+
notes?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async setLocale(
|
|
51
|
+
slug: string,
|
|
52
|
+
locale: string,
|
|
53
|
+
data: { name: string; notes?: string },
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const docRef = this.collection.doc(slug).collection("locales").doc(locale);
|
|
56
|
+
await docRef.set({ locale, ...data });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getAllLocales(
|
|
60
|
+
slug: string,
|
|
61
|
+
): Promise<{ locale: string; name: string; notes?: string }[]> {
|
|
62
|
+
const snapshot = await this.collection
|
|
63
|
+
.doc(slug)
|
|
64
|
+
.collection("locales")
|
|
65
|
+
.get();
|
|
66
|
+
return snapshot.docs.map(
|
|
67
|
+
(doc) =>
|
|
68
|
+
({
|
|
69
|
+
locale: doc.id,
|
|
70
|
+
...doc.data(),
|
|
71
|
+
}) as { locale: string; name: string; notes?: string },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async createWithLocale(
|
|
76
|
+
slug: string,
|
|
77
|
+
imageUrl: string,
|
|
78
|
+
name: string,
|
|
79
|
+
locale: string,
|
|
80
|
+
localeData: { name: string; notes?: string },
|
|
81
|
+
): Promise<Asset> {
|
|
82
|
+
const now = Timestamp.now();
|
|
83
|
+
const batch = this.db.batch();
|
|
84
|
+
|
|
85
|
+
const docRef = this.collection.doc(slug);
|
|
86
|
+
const assetData: Asset = {
|
|
87
|
+
id: slug,
|
|
88
|
+
imageUrl,
|
|
89
|
+
name,
|
|
90
|
+
createdAt: now,
|
|
91
|
+
updatedAt: now,
|
|
92
|
+
};
|
|
93
|
+
batch.set(docRef, assetData);
|
|
94
|
+
|
|
95
|
+
const localeRef = docRef.collection("locales").doc(locale);
|
|
96
|
+
batch.set(localeRef, { locale, ...localeData });
|
|
97
|
+
|
|
98
|
+
await batch.commit();
|
|
99
|
+
return assetData;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getWithPagination(
|
|
103
|
+
page: number = 1,
|
|
104
|
+
limit: number = 50,
|
|
105
|
+
searchQuery?: string,
|
|
106
|
+
): Promise<{ assets: Asset[]; total: number }> {
|
|
107
|
+
// Build base query ordered by updatedAt descending
|
|
108
|
+
const query = this.collection.orderBy("updatedAt", "desc");
|
|
109
|
+
|
|
110
|
+
// Get all documents for accurate count and filtering
|
|
111
|
+
// Note: For very large datasets (10k+ docs), consider maintaining a separate counter
|
|
112
|
+
const allSnapshot = await query.get();
|
|
113
|
+
let allAssets = allSnapshot.docs.map((doc) => doc.data() as Asset);
|
|
114
|
+
|
|
115
|
+
// Filter by search query if provided
|
|
116
|
+
if (searchQuery && searchQuery.trim()) {
|
|
117
|
+
const q = searchQuery.toLowerCase();
|
|
118
|
+
allAssets = allAssets.filter((asset) =>
|
|
119
|
+
asset.id.toLowerCase().includes(q),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Calculate pagination
|
|
124
|
+
const total = allAssets.length;
|
|
125
|
+
const offset = (page - 1) * limit;
|
|
126
|
+
const paginatedAssets = allAssets.slice(offset, offset + limit);
|
|
127
|
+
|
|
128
|
+
return { assets: paginatedAssets, total };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class IngredientRepository extends AssetRepository {
|
|
133
|
+
constructor(db: Firestore) {
|
|
134
|
+
super(db, "ingredients");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export class EquipmentRepository extends AssetRepository {
|
|
139
|
+
constructor(db: Firestore) {
|
|
140
|
+
super(db, "equipment");
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/recipe/converters.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FirestoreDataConverter,
|
|
3
|
-
QueryDocumentSnapshot,
|
|
4
|
-
DocumentData,
|
|
5
|
-
} from "firebase-admin/firestore";
|
|
6
|
-
import type { RecipeMeta, RecipeContent } from "@cravery/core/types";
|
|
7
|
-
import {
|
|
8
|
-
toTimestamp,
|
|
9
|
-
toOptionalTimestamp,
|
|
10
|
-
fromTimestamp,
|
|
11
|
-
stripUndefined,
|
|
12
|
-
} from "../utils";
|
|
13
|
-
|
|
14
|
-
export const recipeMetaConverter: FirestoreDataConverter<RecipeMeta> = {
|
|
15
|
-
toFirestore(recipeMeta: RecipeMeta): DocumentData {
|
|
16
|
-
const { id, createdAt, updatedAt, deletedAt, ...rest } = recipeMeta;
|
|
17
|
-
|
|
18
|
-
const convertTimestamp = (ts: any) => {
|
|
19
|
-
if (!ts) return ts;
|
|
20
|
-
// Handle Firestore internal format with _seconds
|
|
21
|
-
if (typeof ts === "object" && "_seconds" in ts) {
|
|
22
|
-
return ts;
|
|
23
|
-
}
|
|
24
|
-
// Handle Firestore Timestamp objects with seconds/nanoseconds
|
|
25
|
-
if (typeof ts === "object" && "seconds" in ts && "nanoseconds" in ts) {
|
|
26
|
-
return fromTimestamp(ts);
|
|
27
|
-
}
|
|
28
|
-
return ts;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Strip undefined values before returning to Firestore
|
|
32
|
-
return stripUndefined({
|
|
33
|
-
...rest,
|
|
34
|
-
createdAt: convertTimestamp(createdAt),
|
|
35
|
-
updatedAt: convertTimestamp(updatedAt),
|
|
36
|
-
...(deletedAt && { deletedAt: convertTimestamp(deletedAt) }),
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
fromFirestore(snapshot: QueryDocumentSnapshot): RecipeMeta {
|
|
41
|
-
const data = snapshot.data();
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
id: snapshot.id,
|
|
45
|
-
allergens: data.allergens,
|
|
46
|
-
confidence: data.confidence,
|
|
47
|
-
createdBy: data.createdBy,
|
|
48
|
-
cuisine: data.cuisine,
|
|
49
|
-
dietaryTags: data.dietaryTags,
|
|
50
|
-
difficulty: data.difficulty,
|
|
51
|
-
equipment: data.equipment,
|
|
52
|
-
imageUrl: data.imageUrl,
|
|
53
|
-
ingredientSections: data.ingredientSections,
|
|
54
|
-
instructions: data.instructions,
|
|
55
|
-
mealTypes: data.mealTypes,
|
|
56
|
-
nutrition: data.nutrition,
|
|
57
|
-
originalLocale: data.originalLocale,
|
|
58
|
-
servings: data.servings,
|
|
59
|
-
source: data.source,
|
|
60
|
-
sourceUrl: data.sourceUrl,
|
|
61
|
-
spiciness: data.spiciness,
|
|
62
|
-
status: data.status,
|
|
63
|
-
time: data.time,
|
|
64
|
-
createdAt: toTimestamp(data.createdAt),
|
|
65
|
-
updatedAt: toTimestamp(data.updatedAt),
|
|
66
|
-
deletedAt: toOptionalTimestamp(data.deletedAt),
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const recipeContentConverter: FirestoreDataConverter<RecipeContent> = {
|
|
72
|
-
toFirestore(content: RecipeContent): DocumentData {
|
|
73
|
-
// Strip undefined values before returning to Firestore
|
|
74
|
-
return stripUndefined({
|
|
75
|
-
description: content.description,
|
|
76
|
-
equipment: content.equipment,
|
|
77
|
-
ingredientSections: content.ingredientSections,
|
|
78
|
-
instructions: content.instructions,
|
|
79
|
-
locale: content.locale,
|
|
80
|
-
tips: content.tips,
|
|
81
|
-
title: content.title,
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
fromFirestore(snapshot: QueryDocumentSnapshot): RecipeContent {
|
|
86
|
-
const data = snapshot.data();
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
description: data.description,
|
|
90
|
-
equipment: data.equipment,
|
|
91
|
-
ingredientSections: data.ingredientSections,
|
|
92
|
-
instructions: data.instructions,
|
|
93
|
-
locale: data.locale,
|
|
94
|
-
tips: data.tips,
|
|
95
|
-
title: data.title,
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
FirestoreDataConverter,
|
|
3
|
+
QueryDocumentSnapshot,
|
|
4
|
+
DocumentData,
|
|
5
|
+
} from "firebase-admin/firestore";
|
|
6
|
+
import type { RecipeMeta, RecipeContent } from "@cravery/core/types";
|
|
7
|
+
import {
|
|
8
|
+
toTimestamp,
|
|
9
|
+
toOptionalTimestamp,
|
|
10
|
+
fromTimestamp,
|
|
11
|
+
stripUndefined,
|
|
12
|
+
} from "../utils";
|
|
13
|
+
|
|
14
|
+
export const recipeMetaConverter: FirestoreDataConverter<RecipeMeta> = {
|
|
15
|
+
toFirestore(recipeMeta: RecipeMeta): DocumentData {
|
|
16
|
+
const { id, createdAt, updatedAt, deletedAt, ...rest } = recipeMeta;
|
|
17
|
+
|
|
18
|
+
const convertTimestamp = (ts: any) => {
|
|
19
|
+
if (!ts) return ts;
|
|
20
|
+
// Handle Firestore internal format with _seconds
|
|
21
|
+
if (typeof ts === "object" && "_seconds" in ts) {
|
|
22
|
+
return ts;
|
|
23
|
+
}
|
|
24
|
+
// Handle Firestore Timestamp objects with seconds/nanoseconds
|
|
25
|
+
if (typeof ts === "object" && "seconds" in ts && "nanoseconds" in ts) {
|
|
26
|
+
return fromTimestamp(ts);
|
|
27
|
+
}
|
|
28
|
+
return ts;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Strip undefined values before returning to Firestore
|
|
32
|
+
return stripUndefined({
|
|
33
|
+
...rest,
|
|
34
|
+
createdAt: convertTimestamp(createdAt),
|
|
35
|
+
updatedAt: convertTimestamp(updatedAt),
|
|
36
|
+
...(deletedAt && { deletedAt: convertTimestamp(deletedAt) }),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
fromFirestore(snapshot: QueryDocumentSnapshot): RecipeMeta {
|
|
41
|
+
const data = snapshot.data();
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
id: snapshot.id,
|
|
45
|
+
allergens: data.allergens,
|
|
46
|
+
confidence: data.confidence,
|
|
47
|
+
createdBy: data.createdBy,
|
|
48
|
+
cuisine: data.cuisine,
|
|
49
|
+
dietaryTags: data.dietaryTags,
|
|
50
|
+
difficulty: data.difficulty,
|
|
51
|
+
equipment: data.equipment,
|
|
52
|
+
imageUrl: data.imageUrl,
|
|
53
|
+
ingredientSections: data.ingredientSections,
|
|
54
|
+
instructions: data.instructions,
|
|
55
|
+
mealTypes: data.mealTypes,
|
|
56
|
+
nutrition: data.nutrition,
|
|
57
|
+
originalLocale: data.originalLocale,
|
|
58
|
+
servings: data.servings,
|
|
59
|
+
source: data.source,
|
|
60
|
+
sourceUrl: data.sourceUrl,
|
|
61
|
+
spiciness: data.spiciness,
|
|
62
|
+
status: data.status,
|
|
63
|
+
time: data.time,
|
|
64
|
+
createdAt: toTimestamp(data.createdAt),
|
|
65
|
+
updatedAt: toTimestamp(data.updatedAt),
|
|
66
|
+
deletedAt: toOptionalTimestamp(data.deletedAt),
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const recipeContentConverter: FirestoreDataConverter<RecipeContent> = {
|
|
72
|
+
toFirestore(content: RecipeContent): DocumentData {
|
|
73
|
+
// Strip undefined values before returning to Firestore
|
|
74
|
+
return stripUndefined({
|
|
75
|
+
description: content.description,
|
|
76
|
+
equipment: content.equipment,
|
|
77
|
+
ingredientSections: content.ingredientSections,
|
|
78
|
+
instructions: content.instructions,
|
|
79
|
+
locale: content.locale,
|
|
80
|
+
tips: content.tips,
|
|
81
|
+
title: content.title,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
fromFirestore(snapshot: QueryDocumentSnapshot): RecipeContent {
|
|
86
|
+
const data = snapshot.data();
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
description: data.description,
|
|
90
|
+
equipment: data.equipment,
|
|
91
|
+
ingredientSections: data.ingredientSections,
|
|
92
|
+
instructions: data.instructions,
|
|
93
|
+
locale: data.locale,
|
|
94
|
+
tips: data.tips,
|
|
95
|
+
title: data.title,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|
package/src/recipe/utils.ts
CHANGED
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
import { Recipe, RecipeContent, RecipeMeta } from "@cravery/core";
|
|
2
|
-
|
|
3
|
-
export function mergeRecipe(meta: RecipeMeta, content: RecipeContent): Recipe {
|
|
4
|
-
const equipment = meta.equipment?.map((equipMeta, index) => {
|
|
5
|
-
const equipContent = content.equipment?.[index];
|
|
6
|
-
if (!equipContent) {
|
|
7
|
-
throw new Error(`Missing equipment content at index ${index}`);
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
...equipMeta,
|
|
11
|
-
...equipContent,
|
|
12
|
-
};
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const ingredientSections = meta.ingredientSections.map(
|
|
16
|
-
(sectionMeta, sectionIndex) => ({
|
|
17
|
-
slug: sectionMeta.slug,
|
|
18
|
-
title: content.ingredientSections[sectionIndex]?.title,
|
|
19
|
-
ingredients: sectionMeta.ingredients.map((ingredMeta, ingredIndex) => ({
|
|
20
|
-
...ingredMeta,
|
|
21
|
-
...(content.ingredientSections[sectionIndex]?.ingredients[
|
|
22
|
-
ingredIndex
|
|
23
|
-
] || {}),
|
|
24
|
-
})),
|
|
25
|
-
}),
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const instructions = meta.instructions.map((instrMeta, index) => ({
|
|
29
|
-
...instrMeta,
|
|
30
|
-
...(content.instructions[index] || {}),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
id: meta.id,
|
|
35
|
-
allergens: meta.allergens,
|
|
36
|
-
confidence: meta.confidence,
|
|
37
|
-
createdAt: meta.createdAt,
|
|
38
|
-
createdBy: meta.createdBy,
|
|
39
|
-
cuisine: meta.cuisine,
|
|
40
|
-
deletedAt: meta.deletedAt,
|
|
41
|
-
dietaryTags: meta.dietaryTags,
|
|
42
|
-
difficulty: meta.difficulty,
|
|
43
|
-
imageUrl: meta.imageUrl,
|
|
44
|
-
mealTypes: meta.mealTypes,
|
|
45
|
-
nutrition: meta.nutrition,
|
|
46
|
-
originalLocale: meta.originalLocale,
|
|
47
|
-
servings: meta.servings,
|
|
48
|
-
source: meta.source,
|
|
49
|
-
sourceUrl: meta.sourceUrl,
|
|
50
|
-
spiciness: meta.spiciness,
|
|
51
|
-
status: meta.status,
|
|
52
|
-
time: meta.time,
|
|
53
|
-
updatedAt: meta.updatedAt,
|
|
54
|
-
equipment,
|
|
55
|
-
ingredientSections,
|
|
56
|
-
instructions,
|
|
57
|
-
description: content.description,
|
|
58
|
-
locale: content.locale,
|
|
59
|
-
tips: content.tips,
|
|
60
|
-
title: content.title,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function splitRecipe(recipe: Recipe): {
|
|
65
|
-
meta: Omit<RecipeMeta, "id">;
|
|
66
|
-
content: RecipeContent;
|
|
67
|
-
} {
|
|
68
|
-
const {
|
|
69
|
-
id,
|
|
70
|
-
description,
|
|
71
|
-
equipment,
|
|
72
|
-
ingredientSections,
|
|
73
|
-
instructions,
|
|
74
|
-
locale,
|
|
75
|
-
tips,
|
|
76
|
-
title,
|
|
77
|
-
...metaFields
|
|
78
|
-
} = recipe;
|
|
79
|
-
|
|
80
|
-
const equipmentMeta = equipment?.map(({ required, slug }) => ({
|
|
81
|
-
required,
|
|
82
|
-
slug,
|
|
83
|
-
}));
|
|
84
|
-
const equipmentContent = equipment?.map(({ name, notes, slug }) => ({
|
|
85
|
-
name,
|
|
86
|
-
notes,
|
|
87
|
-
slug,
|
|
88
|
-
}));
|
|
89
|
-
|
|
90
|
-
const ingredientSectionsMeta = ingredientSections.map(
|
|
91
|
-
({ ingredients, slug }) => ({
|
|
92
|
-
slug,
|
|
93
|
-
ingredients: ingredients.map(({ quantity, required, slug, unit }) => ({
|
|
94
|
-
quantity,
|
|
95
|
-
required,
|
|
96
|
-
slug,
|
|
97
|
-
unit,
|
|
98
|
-
})),
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
const ingredientSectionsContent = ingredientSections.map(
|
|
102
|
-
({ ingredients, slug, title }) => ({
|
|
103
|
-
slug,
|
|
104
|
-
title,
|
|
105
|
-
ingredients: ingredients.map(({ name, notes, slug }) => ({
|
|
106
|
-
name,
|
|
107
|
-
notes,
|
|
108
|
-
slug,
|
|
109
|
-
})),
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const instructionsMeta = instructions.map(
|
|
114
|
-
({ duration, step, temperature }) => ({
|
|
115
|
-
duration,
|
|
116
|
-
step,
|
|
117
|
-
temperature,
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
const instructionsContent = instructions.map(({ step, text }) => ({
|
|
121
|
-
step,
|
|
122
|
-
text,
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
|
-
const content: RecipeContent = {
|
|
126
|
-
description,
|
|
127
|
-
equipment: equipmentContent,
|
|
128
|
-
ingredientSections: ingredientSectionsContent,
|
|
129
|
-
instructions: instructionsContent,
|
|
130
|
-
locale,
|
|
131
|
-
tips,
|
|
132
|
-
title,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const meta = {
|
|
136
|
-
...metaFields,
|
|
137
|
-
equipment: equipmentMeta,
|
|
138
|
-
ingredientSections: ingredientSectionsMeta,
|
|
139
|
-
instructions: instructionsMeta,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
return { meta, content };
|
|
143
|
-
}
|
|
1
|
+
import { Recipe, RecipeContent, RecipeMeta } from "@cravery/core";
|
|
2
|
+
|
|
3
|
+
export function mergeRecipe(meta: RecipeMeta, content: RecipeContent): Recipe {
|
|
4
|
+
const equipment = meta.equipment?.map((equipMeta, index) => {
|
|
5
|
+
const equipContent = content.equipment?.[index];
|
|
6
|
+
if (!equipContent) {
|
|
7
|
+
throw new Error(`Missing equipment content at index ${index}`);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
...equipMeta,
|
|
11
|
+
...equipContent,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ingredientSections = meta.ingredientSections.map(
|
|
16
|
+
(sectionMeta, sectionIndex) => ({
|
|
17
|
+
slug: sectionMeta.slug,
|
|
18
|
+
title: content.ingredientSections[sectionIndex]?.title,
|
|
19
|
+
ingredients: sectionMeta.ingredients.map((ingredMeta, ingredIndex) => ({
|
|
20
|
+
...ingredMeta,
|
|
21
|
+
...(content.ingredientSections[sectionIndex]?.ingredients[
|
|
22
|
+
ingredIndex
|
|
23
|
+
] || {}),
|
|
24
|
+
})),
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const instructions = meta.instructions.map((instrMeta, index) => ({
|
|
29
|
+
...instrMeta,
|
|
30
|
+
...(content.instructions[index] || {}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
id: meta.id,
|
|
35
|
+
allergens: meta.allergens,
|
|
36
|
+
confidence: meta.confidence,
|
|
37
|
+
createdAt: meta.createdAt,
|
|
38
|
+
createdBy: meta.createdBy,
|
|
39
|
+
cuisine: meta.cuisine,
|
|
40
|
+
deletedAt: meta.deletedAt,
|
|
41
|
+
dietaryTags: meta.dietaryTags,
|
|
42
|
+
difficulty: meta.difficulty,
|
|
43
|
+
imageUrl: meta.imageUrl,
|
|
44
|
+
mealTypes: meta.mealTypes,
|
|
45
|
+
nutrition: meta.nutrition,
|
|
46
|
+
originalLocale: meta.originalLocale,
|
|
47
|
+
servings: meta.servings,
|
|
48
|
+
source: meta.source,
|
|
49
|
+
sourceUrl: meta.sourceUrl,
|
|
50
|
+
spiciness: meta.spiciness,
|
|
51
|
+
status: meta.status,
|
|
52
|
+
time: meta.time,
|
|
53
|
+
updatedAt: meta.updatedAt,
|
|
54
|
+
equipment,
|
|
55
|
+
ingredientSections,
|
|
56
|
+
instructions,
|
|
57
|
+
description: content.description,
|
|
58
|
+
locale: content.locale,
|
|
59
|
+
tips: content.tips,
|
|
60
|
+
title: content.title,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function splitRecipe(recipe: Recipe): {
|
|
65
|
+
meta: Omit<RecipeMeta, "id">;
|
|
66
|
+
content: RecipeContent;
|
|
67
|
+
} {
|
|
68
|
+
const {
|
|
69
|
+
id,
|
|
70
|
+
description,
|
|
71
|
+
equipment,
|
|
72
|
+
ingredientSections,
|
|
73
|
+
instructions,
|
|
74
|
+
locale,
|
|
75
|
+
tips,
|
|
76
|
+
title,
|
|
77
|
+
...metaFields
|
|
78
|
+
} = recipe;
|
|
79
|
+
|
|
80
|
+
const equipmentMeta = equipment?.map(({ required, slug }) => ({
|
|
81
|
+
required,
|
|
82
|
+
slug,
|
|
83
|
+
}));
|
|
84
|
+
const equipmentContent = equipment?.map(({ name, notes, slug }) => ({
|
|
85
|
+
name,
|
|
86
|
+
notes,
|
|
87
|
+
slug,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const ingredientSectionsMeta = ingredientSections.map(
|
|
91
|
+
({ ingredients, slug }) => ({
|
|
92
|
+
slug,
|
|
93
|
+
ingredients: ingredients.map(({ quantity, required, slug, unit }) => ({
|
|
94
|
+
quantity,
|
|
95
|
+
required,
|
|
96
|
+
slug,
|
|
97
|
+
unit,
|
|
98
|
+
})),
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
const ingredientSectionsContent = ingredientSections.map(
|
|
102
|
+
({ ingredients, slug, title }) => ({
|
|
103
|
+
slug,
|
|
104
|
+
title,
|
|
105
|
+
ingredients: ingredients.map(({ name, notes, slug }) => ({
|
|
106
|
+
name,
|
|
107
|
+
notes,
|
|
108
|
+
slug,
|
|
109
|
+
})),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const instructionsMeta = instructions.map(
|
|
114
|
+
({ duration, step, temperature }) => ({
|
|
115
|
+
duration,
|
|
116
|
+
step,
|
|
117
|
+
temperature,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
const instructionsContent = instructions.map(({ step, text }) => ({
|
|
121
|
+
step,
|
|
122
|
+
text,
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
const content: RecipeContent = {
|
|
126
|
+
description,
|
|
127
|
+
equipment: equipmentContent,
|
|
128
|
+
ingredientSections: ingredientSectionsContent,
|
|
129
|
+
instructions: instructionsContent,
|
|
130
|
+
locale,
|
|
131
|
+
tips,
|
|
132
|
+
title,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const meta = {
|
|
136
|
+
...metaFields,
|
|
137
|
+
equipment: equipmentMeta,
|
|
138
|
+
ingredientSections: ingredientSectionsMeta,
|
|
139
|
+
instructions: instructionsMeta,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return { meta, content };
|
|
143
|
+
}
|