@umituz/react-native-ai-generation-content 1.12.36 → 1.12.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.12.36",
3
+ "version": "1.12.38",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -68,7 +68,7 @@
68
68
  "@typescript-eslint/parser": "^7.0.0",
69
69
  "@umituz/react-native-animation": "*",
70
70
  "@umituz/react-native-design-system": "^2.3.33",
71
- "@umituz/react-native-firebase": "*",
71
+ "@umituz/react-native-firebase": "^1.13.20",
72
72
  "@umituz/react-native-haptics": "^1.0.2",
73
73
  "@umituz/react-native-image": "*",
74
74
  "@umituz/react-native-offline": "*",
@@ -25,17 +25,6 @@ export interface CreationsTranslations {
25
25
  readonly filterTitle: string;
26
26
  }
27
27
 
28
- /**
29
- * Path builder function type
30
- * Allows apps to define custom Firestore path structures
31
- * @example
32
- * // Default: users/{userId}/creations
33
- * pathBuilder: (userId) => ["users", userId, "creations"]
34
- * // Alternative: creations/{userId}/items
35
- * pathBuilder: (userId) => ["creations", userId, "items"]
36
- */
37
- export type PathBuilder = (userId: string) => string[];
38
-
39
28
  /**
40
29
  * Document mapper function type
41
30
  * Allows apps to map their specific document structure to Creation
@@ -49,7 +38,6 @@ export interface CreationsConfig {
49
38
  readonly translations: CreationsTranslations;
50
39
  readonly maxThumbnails?: number;
51
40
  readonly gridColumns?: number;
52
- readonly pathBuilder?: PathBuilder;
53
41
  readonly documentMapper?: DocumentMapper;
54
42
  }
55
43
 
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Architecture:
6
6
  * - Factory pattern for repository creation
7
- * - Supports dynamic path structure per app
7
+ * - Standard path: users/{userId}/{collectionName}
8
8
  * - Supports custom document mapping per app
9
9
  * - App-agnostic: No Firestore instance needed (BaseRepository handles it)
10
10
  *
@@ -25,16 +25,10 @@ import type { ICreationsRepository } from "../../domain/repositories/ICreationsR
25
25
  * @returns ICreationsRepository instance
26
26
  *
27
27
  * @example
28
- * // Basic usage with default path (users/{userId}/photos)
28
+ * // Basic usage (path: users/{userId}/photos)
29
29
  * const repo = createCreationsRepository("photos");
30
30
  *
31
31
  * @example
32
- * // Custom path structure
33
- * const repo = createCreationsRepository("creations", {
34
- * pathBuilder: (userId) => ["gallery", userId, "items"],
35
- * });
36
- *
37
- * @example
38
32
  * // Custom document mapper
39
33
  * const repo = createCreationsRepository("photos", {
40
34
  * documentMapper: (id, data) => ({
@@ -43,6 +37,7 @@ import type { ICreationsRepository } from "../../domain/repositories/ICreationsR
43
37
  * type: data.category,
44
38
  * createdAt: data.timestamp?.toDate() || new Date(),
45
39
  * isShared: data.public ?? false,
40
+ * isFavorite: data.favorite ?? false,
46
41
  * }),
47
42
  * });
48
43
  */
@@ -0,0 +1,79 @@
1
+ import { getDocs, getDoc, query, orderBy } from "firebase/firestore";
2
+ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
3
+ import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
4
+ import type { Creation, CreationDocument } from "../../domain/entities/Creation";
5
+
6
+ declare const __DEV__: boolean;
7
+
8
+ /**
9
+ * Handles fetching creations from Firestore
10
+ * Single Responsibility: Read operations
11
+ */
12
+ export class CreationsFetcher {
13
+ constructor(
14
+ private readonly pathResolver: FirestorePathResolver,
15
+ private readonly documentMapper: DocumentMapper,
16
+ ) { }
17
+
18
+ async getAll(userId: string): Promise<Creation[]> {
19
+ if (__DEV__) {
20
+ // eslint-disable-next-line no-console
21
+ console.log("[CreationsRepository] getAll()", { userId });
22
+ }
23
+
24
+ const userCollection = this.pathResolver.getUserCollection(userId);
25
+ if (!userCollection) return [];
26
+
27
+ try {
28
+ const q = query(userCollection, orderBy("createdAt", "desc"));
29
+ const snapshot = await getDocs(q);
30
+
31
+ if (__DEV__) {
32
+ // eslint-disable-next-line no-console
33
+ console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
34
+ }
35
+
36
+ return snapshot.docs.map((docSnap) => {
37
+ const data = docSnap.data() as CreationDocument;
38
+ return this.documentMapper(docSnap.id, data);
39
+ });
40
+ } catch (error) {
41
+ if (__DEV__) {
42
+ // eslint-disable-next-line no-console
43
+ console.error("[CreationsRepository] getAll() ERROR", error);
44
+ }
45
+ return [];
46
+ }
47
+ }
48
+
49
+ async getById(userId: string, id: string): Promise<Creation | null> {
50
+ if (__DEV__) {
51
+ // eslint-disable-next-line no-console
52
+ console.log("[CreationsRepository] getById()", { userId, id });
53
+ }
54
+
55
+ const docRef = this.pathResolver.getDocRef(userId, id);
56
+ if (!docRef) return null;
57
+
58
+ try {
59
+ const docSnap = await getDoc(docRef);
60
+
61
+ if (!docSnap.exists()) {
62
+ if (__DEV__) {
63
+ // eslint-disable-next-line no-console
64
+ console.log("[CreationsRepository] Document not found");
65
+ }
66
+ return null;
67
+ }
68
+
69
+ const data = docSnap.data() as CreationDocument;
70
+ return this.documentMapper(docSnap.id, data);
71
+ } catch (error) {
72
+ if (__DEV__) {
73
+ // eslint-disable-next-line no-console
74
+ console.error("[CreationsRepository] getById() ERROR", error);
75
+ }
76
+ return null;
77
+ }
78
+ }
79
+ }
@@ -1,171 +1,60 @@
1
- /**
2
- * Creations Repository Implementation
3
- * Extends BaseRepository from @umituz/react-native-firestore
4
- *
5
- * Architecture:
6
- * - Extends BaseRepository for centralized database access
7
- * - Fully dynamic path structure (configurable per app)
8
- * - Fully dynamic document mapping (configurable per app)
9
- * - App-agnostic: Works with any app, no app-specific code
10
- *
11
- * This class is designed to be used across hundreds of apps.
12
- */
13
-
14
- declare const __DEV__: boolean;
15
-
16
- import {
17
- collection,
18
- doc,
19
- getDocs,
20
- getDoc,
21
- deleteDoc,
22
- updateDoc,
23
- query,
24
- orderBy,
25
- setDoc,
26
- } from "firebase/firestore";
27
- import { BaseRepository } from "@umituz/react-native-firebase";
1
+ import { BaseRepository, FirestorePathResolver } from "@umituz/react-native-firebase";
28
2
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
29
- import type { Creation, CreationDocument } from "../../domain/entities/Creation";
3
+ import type { Creation } from "../../domain/entities/Creation";
30
4
  import { mapDocumentToCreation } from "../../domain/entities/Creation";
31
- import type {
32
- PathBuilder,
33
- DocumentMapper,
34
- } from "../../domain/value-objects/CreationsConfig";
5
+ import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
6
+ import { CreationsFetcher } from "./CreationsFetcher";
7
+ import { CreationsWriter } from "./CreationsWriter";
35
8
 
36
9
  /**
37
10
  * Repository options for dynamic configuration
38
- * Apps can customize path structure and document mapping
11
+ * Apps can customize document mapping
39
12
  */
40
13
  export interface RepositoryOptions {
41
- /**
42
- * Custom path builder function
43
- * @example (userId) => ["users", userId, "photos"]
44
- * @example (userId) => ["creations", userId, "items"]
45
- */
46
- readonly pathBuilder?: PathBuilder;
47
-
48
- /**
49
- * Custom document mapper function
50
- * Maps Firestore documents to Creation entity
51
- */
52
14
  readonly documentMapper?: DocumentMapper;
53
15
  }
54
16
 
55
17
  /**
56
- * Default path builder: users/{userId}/{collectionName}
18
+ * Creations Repository Implementation
19
+ * Delegates to specialized classes for different responsibilities
20
+ *
21
+ * Architecture:
22
+ * - Extends BaseRepository for centralized database access
23
+ * - Uses FirestorePathResolver for path resolution
24
+ * - Uses CreationsFetcher for read operations
25
+ * - Uses CreationsWriter for write operations
26
+ * - Standard path: users/{userId}/{collectionName}
57
27
  */
58
- const createDefaultPathBuilder =
59
- (collectionName: string): PathBuilder =>
60
- (userId: string) =>
61
- ["users", userId, collectionName];
62
-
63
28
  export class CreationsRepository
64
29
  extends BaseRepository
65
30
  implements ICreationsRepository {
66
- private readonly pathBuilder: PathBuilder;
67
- private readonly documentMapper: DocumentMapper;
31
+ private readonly pathResolver: FirestorePathResolver;
32
+ private readonly fetcher: CreationsFetcher;
33
+ private readonly writer: CreationsWriter;
68
34
 
69
35
  constructor(
70
- private readonly _collectionName: string,
36
+ collectionName: string,
71
37
  options?: RepositoryOptions,
72
38
  ) {
73
39
  super();
74
- this.pathBuilder =
75
- options?.pathBuilder ?? createDefaultPathBuilder(_collectionName);
76
- this.documentMapper = options?.documentMapper ?? mapDocumentToCreation;
77
- }
78
40
 
79
- private getUserCollection(userId: string) {
80
- const db = this.getDb();
81
- if (!db) return null;
82
- const pathSegments = this.pathBuilder(userId);
83
- return collection(db, pathSegments[0], ...pathSegments.slice(1));
84
- }
41
+ const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
85
42
 
86
- private getDocRef(userId: string, creationId: string) {
87
- const db = this.getDb();
88
- if (!db) return null;
89
- const pathSegments = this.pathBuilder(userId);
90
- return doc(db, pathSegments[0], ...pathSegments.slice(1), creationId);
43
+ this.pathResolver = new FirestorePathResolver(collectionName, this.getDb());
44
+ this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
45
+ this.writer = new CreationsWriter(this.pathResolver);
91
46
  }
92
47
 
93
48
  async getAll(userId: string): Promise<Creation[]> {
94
- if (__DEV__) {
95
- // eslint-disable-next-line no-console
96
- console.log("[CreationsRepository] getAll()", { userId });
97
- }
98
-
99
- const userCollection = this.getUserCollection(userId);
100
- if (!userCollection) return [];
101
-
102
- try {
103
- const q = query(userCollection, orderBy("createdAt", "desc"));
104
- const snapshot = await getDocs(q);
105
-
106
- if (__DEV__) {
107
- // eslint-disable-next-line no-console
108
- console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
109
- }
110
-
111
- return snapshot.docs.map((docSnap) => {
112
- const data = docSnap.data() as CreationDocument;
113
- return this.documentMapper(docSnap.id, data);
114
- });
115
- } catch (error) {
116
- if (__DEV__) {
117
- // eslint-disable-next-line no-console
118
- console.error("[CreationsRepository] getAll() ERROR", error);
119
- }
120
- return [];
121
- }
49
+ return this.fetcher.getAll(userId);
122
50
  }
123
51
 
124
52
  async getById(userId: string, id: string): Promise<Creation | null> {
125
- if (__DEV__) {
126
- // eslint-disable-next-line no-console
127
- console.log("[CreationsRepository] getById()", { userId, id });
128
- }
129
-
130
- const docRef = this.getDocRef(userId, id);
131
- if (!docRef) return null;
132
-
133
- try {
134
- const docSnap = await getDoc(docRef);
135
-
136
- if (!docSnap.exists()) {
137
- if (__DEV__) {
138
- // eslint-disable-next-line no-console
139
- console.log("[CreationsRepository] Document not found");
140
- }
141
- return null;
142
- }
143
-
144
- const data = docSnap.data() as CreationDocument;
145
- return this.documentMapper(docSnap.id, data);
146
- } catch (error) {
147
- if (__DEV__) {
148
- // eslint-disable-next-line no-console
149
- console.error("[CreationsRepository] getById() ERROR", error);
150
- }
151
- return null;
152
- }
53
+ return this.fetcher.getById(userId, id);
153
54
  }
154
55
 
155
56
  async create(userId: string, creation: Creation): Promise<void> {
156
- const docRef = this.getDocRef(userId, creation.id);
157
- if (!docRef) throw new Error("Firestore not initialized");
158
-
159
- const data: CreationDocument = {
160
- type: creation.type,
161
- prompt: creation.prompt,
162
- uri: creation.uri, // Use uri
163
- createdAt: creation.createdAt,
164
- metadata: creation.metadata || {},
165
- isShared: creation.isShared || false,
166
- };
167
-
168
- await setDoc(docRef, data);
57
+ return this.writer.create(userId, creation);
169
58
  }
170
59
 
171
60
  async update(
@@ -173,54 +62,11 @@ export class CreationsRepository
173
62
  id: string,
174
63
  updates: Partial<Creation>,
175
64
  ): Promise<boolean> {
176
- if (__DEV__) {
177
- // eslint-disable-next-line no-console
178
- console.log("[CreationsRepository] update()", { userId, id, updates });
179
- }
180
-
181
- const docRef = this.getDocRef(userId, id);
182
- if (!docRef) return false;
183
-
184
- try {
185
- const updateData: Record<string, unknown> = {};
186
-
187
- if (updates.metadata !== undefined) {
188
- updateData.metadata = updates.metadata;
189
- }
190
- if (updates.isShared !== undefined) {
191
- updateData.isShared = updates.isShared;
192
- }
193
- if (updates.uri !== undefined) {
194
- updateData.uri = updates.uri;
195
- }
196
- if (updates.type !== undefined) {
197
- updateData.type = updates.type;
198
- }
199
- if (updates.prompt !== undefined) {
200
- updateData.prompt = updates.prompt;
201
- }
202
-
203
- await updateDoc(docRef, updateData);
204
- return true;
205
- } catch (error) {
206
- if (__DEV__) {
207
- // eslint-disable-next-line no-console
208
- console.error("[CreationsRepository] update() ERROR", error);
209
- }
210
- return false;
211
- }
65
+ return this.writer.update(userId, id, updates);
212
66
  }
213
67
 
214
68
  async delete(userId: string, creationId: string): Promise<boolean> {
215
- const docRef = this.getDocRef(userId, creationId);
216
- if (!docRef) return false;
217
-
218
- try {
219
- await deleteDoc(docRef);
220
- return true;
221
- } catch {
222
- return false;
223
- }
69
+ return this.writer.delete(userId, creationId);
224
70
  }
225
71
 
226
72
  async updateShared(
@@ -228,15 +74,7 @@ export class CreationsRepository
228
74
  creationId: string,
229
75
  isShared: boolean,
230
76
  ): Promise<boolean> {
231
- const docRef = this.getDocRef(userId, creationId);
232
- if (!docRef) return false;
233
-
234
- try {
235
- await updateDoc(docRef, { isShared });
236
- return true;
237
- } catch {
238
- return false;
239
- }
77
+ return this.writer.updateShared(userId, creationId, isShared);
240
78
  }
241
79
 
242
80
  async updateFavorite(
@@ -244,14 +82,6 @@ export class CreationsRepository
244
82
  creationId: string,
245
83
  isFavorite: boolean,
246
84
  ): Promise<boolean> {
247
- const docRef = this.getDocRef(userId, creationId);
248
- if (!docRef) return false;
249
-
250
- try {
251
- await updateDoc(docRef, { isFavorite });
252
- return true;
253
- } catch {
254
- return false;
255
- }
85
+ return this.writer.updateFavorite(userId, creationId, isFavorite);
256
86
  }
257
87
  }
@@ -0,0 +1,117 @@
1
+ import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
2
+ import { type FirestorePathResolver } from "@umituz/react-native-firebase";
3
+ import type { Creation, CreationDocument } from "../../domain/entities/Creation";
4
+
5
+ declare const __DEV__: boolean;
6
+
7
+ /**
8
+ * Handles write operations for creations
9
+ * Single Responsibility: Write operations
10
+ */
11
+ export class CreationsWriter {
12
+ constructor(private readonly pathResolver: FirestorePathResolver) { }
13
+
14
+ async create(userId: string, creation: Creation): Promise<void> {
15
+ const docRef = this.pathResolver.getDocRef(userId, creation.id);
16
+ if (!docRef) throw new Error("Firestore not initialized");
17
+
18
+ const data: CreationDocument = {
19
+ type: creation.type,
20
+ prompt: creation.prompt,
21
+ uri: creation.uri,
22
+ createdAt: creation.createdAt,
23
+ metadata: creation.metadata || {},
24
+ isShared: creation.isShared || false,
25
+ isFavorite: creation.isFavorite || false,
26
+ };
27
+
28
+ await setDoc(docRef, data);
29
+ }
30
+
31
+ async update(
32
+ userId: string,
33
+ id: string,
34
+ updates: Partial<Creation>,
35
+ ): Promise<boolean> {
36
+ if (__DEV__) {
37
+ // eslint-disable-next-line no-console
38
+ console.log("[CreationsRepository] update()", { userId, id, updates });
39
+ }
40
+
41
+ const docRef = this.pathResolver.getDocRef(userId, id);
42
+ if (!docRef) return false;
43
+
44
+ try {
45
+ const updateData: Record<string, unknown> = {};
46
+
47
+ if (updates.metadata !== undefined) {
48
+ updateData.metadata = updates.metadata;
49
+ }
50
+ if (updates.isShared !== undefined) {
51
+ updateData.isShared = updates.isShared;
52
+ }
53
+ if (updates.uri !== undefined) {
54
+ updateData.uri = updates.uri;
55
+ }
56
+ if (updates.type !== undefined) {
57
+ updateData.type = updates.type;
58
+ }
59
+ if (updates.prompt !== undefined) {
60
+ updateData.prompt = updates.prompt;
61
+ }
62
+
63
+ await updateDoc(docRef, updateData);
64
+ return true;
65
+ } catch (error) {
66
+ if (__DEV__) {
67
+ // eslint-disable-next-line no-console
68
+ console.error("[CreationsRepository] update() ERROR", error);
69
+ }
70
+ return false;
71
+ }
72
+ }
73
+
74
+ async delete(userId: string, creationId: string): Promise<boolean> {
75
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
76
+ if (!docRef) return false;
77
+
78
+ try {
79
+ await deleteDoc(docRef);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ async updateShared(
87
+ userId: string,
88
+ creationId: string,
89
+ isShared: boolean,
90
+ ): Promise<boolean> {
91
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
92
+ if (!docRef) return false;
93
+
94
+ try {
95
+ await updateDoc(docRef, { isShared });
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ async updateFavorite(
103
+ userId: string,
104
+ creationId: string,
105
+ isFavorite: boolean,
106
+ ): Promise<boolean> {
107
+ const docRef = this.pathResolver.getDocRef(userId, creationId);
108
+ if (!docRef) return false;
109
+
110
+ try {
111
+ await updateDoc(docRef, { isFavorite });
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+ }
@@ -98,12 +98,14 @@ export function CreationsGalleryScreen({
98
98
  }, []);
99
99
 
100
100
  // Handle favorite toggle
101
- const handleFavorite = useCallback(async (creation: Creation, isFavorite: boolean) => {
102
- if (!userId) return;
103
- const success = await repository.updateFavorite(userId, creation.id, isFavorite);
104
- if (success) {
105
- void refetch();
106
- }
101
+ const handleFavorite = useCallback((creation: Creation, isFavorite: boolean) => {
102
+ void (async () => {
103
+ if (!userId) return;
104
+ const success = await repository.updateFavorite(userId, creation.id, isFavorite);
105
+ if (success) {
106
+ void refetch();
107
+ }
108
+ })();
107
109
  }, [userId, repository, refetch]);
108
110
 
109
111
  const styles = useStyles(tokens);