@umituz/react-native-ai-generation-content 1.65.12 → 1.66.2
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 +2 -2
- package/src/domains/creations/domain/utils/creation-display.util.ts +67 -0
- package/src/domains/creations/domain/utils/creation-format.util.ts +12 -0
- package/src/domains/creations/domain/utils/creation-helpers.ts +8 -136
- package/src/domains/creations/domain/utils/creation-id.util.ts +13 -0
- package/src/domains/creations/domain/utils/creation-search.util.ts +24 -0
- package/src/domains/creations/domain/utils/creation-sort.util.ts +41 -0
- package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +11 -4
- package/src/domains/creations/infrastructure/repositories/CreationsQuery.ts +5 -4
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +14 -10
- package/src/domains/creations/infrastructure/repositories/CreationsSubscription.ts +12 -66
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +13 -10
- package/src/domains/creations/infrastructure/repositories/creation-create.operations.ts +4 -3
- package/src/domains/creations/infrastructure/repositories/creation-delete.operations.ts +27 -63
- package/src/domains/creations/infrastructure/repositories/creation-error-handler.util.ts +36 -0
- package/src/domains/creations/infrastructure/repositories/creation-update.operations.ts +3 -3
- package/src/domains/creations/infrastructure/repositories/creations-state-operations.ts +7 -7
- package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +52 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.66.2",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
71
71
|
"@typescript-eslint/parser": "^8.54.0",
|
|
72
72
|
"@umituz/react-native-design-system": "^4.23.79",
|
|
73
|
-
"@umituz/react-native-firebase": "^1.13.
|
|
73
|
+
"@umituz/react-native-firebase": "^1.13.161",
|
|
74
74
|
"@umituz/react-native-subscription": "^2.27.23",
|
|
75
75
|
"eslint": "^9.39.2",
|
|
76
76
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Display Utilities
|
|
3
|
+
* Single Responsibility: UI display helpers (icons, titles, text)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CreationTypeId } from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Icon name type for design system
|
|
10
|
+
*/
|
|
11
|
+
export type IconName = "image" | "film" | "mic" | "sparkles" | "color-palette" | "brush";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get icon name for creation type
|
|
15
|
+
*/
|
|
16
|
+
export function getTypeIcon(type: CreationTypeId): IconName {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "text-to-video":
|
|
19
|
+
case "image-to-video":
|
|
20
|
+
return "film";
|
|
21
|
+
case "style-transfer":
|
|
22
|
+
case "colorization":
|
|
23
|
+
return "color-palette";
|
|
24
|
+
case "ai-brush":
|
|
25
|
+
case "inpainting":
|
|
26
|
+
return "brush";
|
|
27
|
+
default:
|
|
28
|
+
return "image";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get i18n key for creation type
|
|
34
|
+
*/
|
|
35
|
+
export function getTypeTextKey(type: CreationTypeId): string {
|
|
36
|
+
return `creations.types.${type}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get formatted type text (fallback)
|
|
41
|
+
*/
|
|
42
|
+
export function getTypeText(type: CreationTypeId): string {
|
|
43
|
+
return type.split("-").map(word =>
|
|
44
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
45
|
+
).join(" ");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get creation title from prompt or type
|
|
50
|
+
*/
|
|
51
|
+
export function getCreationTitle(
|
|
52
|
+
prompt?: string,
|
|
53
|
+
type?: CreationTypeId,
|
|
54
|
+
maxLength: number = 50
|
|
55
|
+
): string {
|
|
56
|
+
if (prompt && prompt.length > 0) {
|
|
57
|
+
return prompt.length > maxLength
|
|
58
|
+
? prompt.substring(0, maxLength) + "..."
|
|
59
|
+
: prompt;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (type) {
|
|
63
|
+
return getTypeText(type);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return "Untitled Creation";
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Format Utilities
|
|
3
|
+
* Single Responsibility: Text formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncate text with ellipsis
|
|
8
|
+
*/
|
|
9
|
+
export function truncateText(text: string, maxLength: number): string {
|
|
10
|
+
if (text.length <= maxLength) return text;
|
|
11
|
+
return text.substring(0, maxLength - 3) + "...";
|
|
12
|
+
}
|
|
@@ -1,140 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creation Helpers
|
|
3
|
-
*
|
|
3
|
+
* Central export point for creation utility functions
|
|
4
|
+
* @deprecated Individual utilities split into focused modules for better maintainability
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
export function generateCreationId(): string {
|
|
13
|
-
return generateUUID();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Icon name type for design system
|
|
18
|
-
*/
|
|
19
|
-
export type IconName = "image" | "film" | "mic" | "sparkles" | "color-palette" | "brush";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Get icon name for creation type
|
|
23
|
-
*/
|
|
24
|
-
export function getTypeIcon(type: CreationTypeId): IconName {
|
|
25
|
-
switch (type) {
|
|
26
|
-
case "text-to-video":
|
|
27
|
-
case "image-to-video":
|
|
28
|
-
return "film";
|
|
29
|
-
case "style-transfer":
|
|
30
|
-
case "colorization":
|
|
31
|
-
return "color-palette";
|
|
32
|
-
case "ai-brush":
|
|
33
|
-
case "inpainting":
|
|
34
|
-
return "brush";
|
|
35
|
-
default:
|
|
36
|
-
return "image";
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get i18n key for creation type
|
|
42
|
-
*/
|
|
43
|
-
export function getTypeTextKey(type: CreationTypeId): string {
|
|
44
|
-
return `creations.types.${type}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get formatted type text (fallback)
|
|
49
|
-
*/
|
|
50
|
-
export function getTypeText(type: CreationTypeId): string {
|
|
51
|
-
return type.split("-").map(word =>
|
|
52
|
-
word.charAt(0).toUpperCase() + word.slice(1)
|
|
53
|
-
).join(" ");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get creation title from prompt or type
|
|
58
|
-
*/
|
|
59
|
-
export function getCreationTitle(
|
|
60
|
-
prompt?: string,
|
|
61
|
-
type?: CreationTypeId,
|
|
62
|
-
maxLength: number = 50
|
|
63
|
-
): string {
|
|
64
|
-
if (prompt && prompt.length > 0) {
|
|
65
|
-
return prompt.length > maxLength
|
|
66
|
-
? prompt.substring(0, maxLength) + "..."
|
|
67
|
-
: prompt;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (type) {
|
|
71
|
-
return getTypeText(type);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return "Untitled Creation";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Filter creations by search query (client-side)
|
|
79
|
-
*/
|
|
80
|
-
export function filterBySearch<T extends { prompt?: string; type?: string; provider?: string }>(
|
|
81
|
-
items: T[],
|
|
82
|
-
searchQuery?: string,
|
|
83
|
-
): T[] {
|
|
84
|
-
if (!searchQuery || searchQuery.trim().length === 0) {
|
|
85
|
-
return items;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const query = searchQuery.toLowerCase().trim();
|
|
89
|
-
|
|
90
|
-
return items.filter((item) =>
|
|
91
|
-
item.prompt?.toLowerCase().includes(query) ||
|
|
92
|
-
item.type?.toLowerCase().includes(query) ||
|
|
93
|
-
item.provider?.toLowerCase().includes(query)
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Sort creations by field
|
|
99
|
-
*/
|
|
100
|
-
export function sortCreations<T extends Record<string, unknown>>(
|
|
101
|
-
items: T[],
|
|
102
|
-
field: keyof T,
|
|
103
|
-
order: "asc" | "desc" = "desc"
|
|
104
|
-
): T[] {
|
|
105
|
-
return [...items].sort((a, b) => {
|
|
106
|
-
const aVal = a[field] as unknown;
|
|
107
|
-
const bVal = b[field] as unknown;
|
|
108
|
-
|
|
109
|
-
if (aVal === undefined && bVal === undefined) return 0;
|
|
110
|
-
if (aVal === undefined) return 1;
|
|
111
|
-
if (bVal === undefined) return -1;
|
|
112
|
-
|
|
113
|
-
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
114
|
-
return order === "desc"
|
|
115
|
-
? bVal.localeCompare(aVal)
|
|
116
|
-
: aVal.localeCompare(bVal);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
120
|
-
return order === "desc" ? bVal - aVal : aVal - bVal;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Handle Date objects
|
|
124
|
-
if (aVal instanceof Date && bVal instanceof Date) {
|
|
125
|
-
return order === "desc"
|
|
126
|
-
? bVal.getTime() - aVal.getTime()
|
|
127
|
-
: aVal.getTime() - bVal.getTime();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return 0;
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Truncate text with ellipsis
|
|
136
|
-
*/
|
|
137
|
-
export function truncateText(text: string, maxLength: number): string {
|
|
138
|
-
if (text.length <= maxLength) return text;
|
|
139
|
-
return text.substring(0, maxLength - 3) + "...";
|
|
140
|
-
}
|
|
7
|
+
// Re-export all utilities for backward compatibility
|
|
8
|
+
export * from "./creation-id.util";
|
|
9
|
+
export * from "./creation-display.util";
|
|
10
|
+
export * from "./creation-search.util";
|
|
11
|
+
export * from "./creation-sort.util";
|
|
12
|
+
export * from "./creation-format.util";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation ID Utilities
|
|
3
|
+
* Single Responsibility: ID generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateUUID } from "@umituz/react-native-design-system";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a unique creation ID using UUID v4
|
|
10
|
+
*/
|
|
11
|
+
export function generateCreationId(): string {
|
|
12
|
+
return generateUUID();
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Search Utilities
|
|
3
|
+
* Single Responsibility: Filtering and searching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Filter creations by search query (client-side)
|
|
8
|
+
*/
|
|
9
|
+
export function filterBySearch<T extends { prompt?: string; type?: string; provider?: string }>(
|
|
10
|
+
items: T[],
|
|
11
|
+
searchQuery?: string,
|
|
12
|
+
): T[] {
|
|
13
|
+
if (!searchQuery || searchQuery.trim().length === 0) {
|
|
14
|
+
return items;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const query = searchQuery.toLowerCase().trim();
|
|
18
|
+
|
|
19
|
+
return items.filter((item) =>
|
|
20
|
+
item.prompt?.toLowerCase().includes(query) ||
|
|
21
|
+
item.type?.toLowerCase().includes(query) ||
|
|
22
|
+
item.provider?.toLowerCase().includes(query)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Sort Utilities
|
|
3
|
+
* Single Responsibility: Sorting operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sort creations by field
|
|
8
|
+
*/
|
|
9
|
+
export function sortCreations<T extends Record<string, unknown>>(
|
|
10
|
+
items: T[],
|
|
11
|
+
field: keyof T,
|
|
12
|
+
order: "asc" | "desc" = "desc"
|
|
13
|
+
): T[] {
|
|
14
|
+
return [...items].sort((a, b) => {
|
|
15
|
+
const aVal = a[field] as unknown;
|
|
16
|
+
const bVal = b[field] as unknown;
|
|
17
|
+
|
|
18
|
+
if (aVal === undefined && bVal === undefined) return 0;
|
|
19
|
+
if (aVal === undefined) return 1;
|
|
20
|
+
if (bVal === undefined) return -1;
|
|
21
|
+
|
|
22
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
23
|
+
return order === "desc"
|
|
24
|
+
? bVal.localeCompare(aVal)
|
|
25
|
+
: aVal.localeCompare(bVal);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
29
|
+
return order === "desc" ? bVal - aVal : aVal - bVal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle Date objects
|
|
33
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
34
|
+
return order === "desc"
|
|
35
|
+
? bVal.getTime() - aVal.getTime()
|
|
36
|
+
: aVal.getTime() - bVal.getTime();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 0;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CollectionReference, DocumentReference, DocumentData } from "firebase/firestore";
|
|
2
2
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
3
3
|
import type { Creation } from "../../domain/entities/Creation";
|
|
4
4
|
import type { CreationsSubscriptionCallback, UnsubscribeFunction } from "../../domain/repositories/ICreationsRepository";
|
|
5
5
|
import { CreationsQuery } from "./CreationsQuery";
|
|
6
6
|
import { CreationsSubscription } from "./CreationsSubscription";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Path resolver functions from BaseRepository
|
|
10
|
+
*/
|
|
11
|
+
export type GetUserCollection = (userId: string) => CollectionReference<DocumentData> | null;
|
|
12
|
+
export type GetDocRef = (userId: string, documentId: string) => DocumentReference<DocumentData> | null;
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* CreationsFetcher
|
|
10
16
|
* Orchestrates read operations for creations
|
|
@@ -19,11 +25,12 @@ export class CreationsFetcher {
|
|
|
19
25
|
private readonly subscription: CreationsSubscription;
|
|
20
26
|
|
|
21
27
|
constructor(
|
|
22
|
-
|
|
28
|
+
getUserCollection: GetUserCollection,
|
|
29
|
+
getDocRef: GetDocRef,
|
|
23
30
|
documentMapper: DocumentMapper,
|
|
24
31
|
) {
|
|
25
|
-
this.query = new CreationsQuery(
|
|
26
|
-
this.subscription = new CreationsSubscription(
|
|
32
|
+
this.query = new CreationsQuery(getUserCollection, getDocRef, documentMapper);
|
|
33
|
+
this.subscription = new CreationsSubscription(getUserCollection, getDocRef, documentMapper);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { getDocs, getDoc, query, orderBy, where } from "firebase/firestore";
|
|
8
|
-
import {
|
|
8
|
+
import type { GetUserCollection, GetDocRef } from "./CreationsFetcher";
|
|
9
9
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
10
10
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
11
11
|
import { CREATION_FIELDS } from "../../domain/constants";
|
|
@@ -17,7 +17,8 @@ declare const __DEV__: boolean;
|
|
|
17
17
|
*/
|
|
18
18
|
export class CreationsQuery {
|
|
19
19
|
constructor(
|
|
20
|
-
private readonly
|
|
20
|
+
private readonly getUserCollection: GetUserCollection,
|
|
21
|
+
private readonly getDocRef: GetDocRef,
|
|
21
22
|
private readonly documentMapper: DocumentMapper,
|
|
22
23
|
) { }
|
|
23
24
|
|
|
@@ -26,7 +27,7 @@ export class CreationsQuery {
|
|
|
26
27
|
* Optimized query: Server-side filtering for non-deleted items
|
|
27
28
|
*/
|
|
28
29
|
async getAll(userId: string): Promise<Creation[]> {
|
|
29
|
-
const userCollection = this.
|
|
30
|
+
const userCollection = this.getUserCollection(userId);
|
|
30
31
|
if (!userCollection) return [];
|
|
31
32
|
|
|
32
33
|
try {
|
|
@@ -62,7 +63,7 @@ export class CreationsQuery {
|
|
|
62
63
|
* Get a single creation by ID
|
|
63
64
|
*/
|
|
64
65
|
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
65
|
-
const docRef = this.
|
|
66
|
+
const docRef = this.getDocRef(userId, id);
|
|
66
67
|
if (!docRef) return null;
|
|
67
68
|
|
|
68
69
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { BaseRepository
|
|
2
|
+
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
3
3
|
import type {
|
|
4
4
|
ICreationsRepository,
|
|
5
5
|
CreationsSubscriptionCallback,
|
|
@@ -22,10 +22,9 @@ export interface RepositoryOptions {
|
|
|
22
22
|
/**
|
|
23
23
|
* Creations Repository Implementation
|
|
24
24
|
* Delegates to specialized classes for different responsibilities
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* Architecture:
|
|
27
|
-
* - Extends BaseRepository for
|
|
28
|
-
* - Uses FirestorePathResolver for path resolution
|
|
27
|
+
* - Extends BaseRepository for database access and path resolution
|
|
29
28
|
* - Uses CreationsFetcher for read operations
|
|
30
29
|
* - Uses CreationsWriter for write operations
|
|
31
30
|
* - Standard path: users/{userId}/{collectionName}
|
|
@@ -33,7 +32,6 @@ export interface RepositoryOptions {
|
|
|
33
32
|
export class CreationsRepository
|
|
34
33
|
extends BaseRepository
|
|
35
34
|
implements ICreationsRepository {
|
|
36
|
-
private readonly pathResolver: FirestorePathResolver;
|
|
37
35
|
private readonly fetcher: CreationsFetcher;
|
|
38
36
|
private readonly writer: CreationsWriter;
|
|
39
37
|
|
|
@@ -41,14 +39,20 @@ export class CreationsRepository
|
|
|
41
39
|
collectionName: string,
|
|
42
40
|
options?: RepositoryOptions,
|
|
43
41
|
) {
|
|
44
|
-
super();
|
|
42
|
+
super(collectionName);
|
|
45
43
|
|
|
46
44
|
const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
47
45
|
|
|
48
|
-
//
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// Pass BaseRepository methods directly to dependencies
|
|
47
|
+
this.fetcher = new CreationsFetcher(
|
|
48
|
+
(userId) => this.getUserCollection(userId),
|
|
49
|
+
(userId, docId) => this.getDocRef(userId, docId),
|
|
50
|
+
documentMapper
|
|
51
|
+
);
|
|
52
|
+
this.writer = new CreationsWriter(
|
|
53
|
+
(userId) => this.getUserCollection(userId),
|
|
54
|
+
(userId, docId) => this.getDocRef(userId, docId)
|
|
55
|
+
);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
async getAll(userId: string): Promise<Creation[]> {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { query, orderBy, onSnapshot, where } from "firebase/firestore";
|
|
8
|
-
import {
|
|
8
|
+
import type { GetUserCollection } from "./CreationsFetcher";
|
|
9
9
|
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
10
10
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
11
11
|
import type { CreationsSubscriptionCallback, UnsubscribeFunction } from "../../domain/repositories/ICreationsRepository";
|
|
@@ -15,76 +15,30 @@ declare const __DEV__: boolean;
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Handles realtime subscriptions for creations
|
|
18
|
+
* Optimized with server-side filtering (80% data reduction)
|
|
18
19
|
*/
|
|
19
20
|
export class CreationsSubscription {
|
|
20
21
|
constructor(
|
|
21
|
-
private readonly
|
|
22
|
+
private readonly getUserCollection: GetUserCollection,
|
|
23
|
+
_getDocRef: unknown, // Not used in subscription, but accepted for consistent interface
|
|
22
24
|
private readonly documentMapper: DocumentMapper,
|
|
23
25
|
) { }
|
|
24
26
|
|
|
25
|
-
/**
|
|
26
|
-
* Subscribes to realtime updates for user's creations
|
|
27
|
-
*
|
|
28
|
-
* PERFORMANCE OPTIMIZATION:
|
|
29
|
-
* - Server-side filtering with where clause (80% data reduction)
|
|
30
|
-
* - No client-side filtering needed
|
|
31
|
-
* - Requires Firestore composite index: (deletedAt ASC, createdAt DESC)
|
|
32
|
-
*
|
|
33
|
-
* @param userId - User ID to query
|
|
34
|
-
* @param onData - Callback for data updates
|
|
35
|
-
* @param onError - Optional error callback
|
|
36
|
-
* @returns Unsubscribe function
|
|
37
|
-
*/
|
|
38
27
|
subscribeToAll(
|
|
39
28
|
userId: string,
|
|
40
29
|
onData: CreationsSubscriptionCallback,
|
|
41
30
|
onError?: (error: Error) => void,
|
|
42
31
|
): UnsubscribeFunction {
|
|
43
|
-
const userCollection = this.
|
|
32
|
+
const userCollection = this.getUserCollection(userId);
|
|
44
33
|
|
|
45
34
|
if (!userCollection) {
|
|
46
|
-
|
|
35
|
+
const error = new Error(`[CreationsSubscription] Invalid user collection: ${userId}`);
|
|
36
|
+
if (__DEV__) console.error(error.message);
|
|
37
|
+
onData([]);
|
|
38
|
+
onError?.(error);
|
|
39
|
+
return () => { if (__DEV__) console.log("[CreationsSubscription] No-op unsubscribe"); };
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
return this.createRealtimeListener(userCollection, onData, onError);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Handles case when user collection is invalid
|
|
54
|
-
*/
|
|
55
|
-
private handleInvalidCollection(
|
|
56
|
-
userId: string,
|
|
57
|
-
onData: CreationsSubscriptionCallback,
|
|
58
|
-
onError?: (error: Error) => void,
|
|
59
|
-
): UnsubscribeFunction {
|
|
60
|
-
const error = new Error(`[CreationsSubscription] Cannot subscribe: Invalid user collection for userId: ${userId}`);
|
|
61
|
-
|
|
62
|
-
if (__DEV__) {
|
|
63
|
-
console.error(error.message);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Return empty array immediately
|
|
67
|
-
onData([]);
|
|
68
|
-
|
|
69
|
-
// Report error to callback
|
|
70
|
-
onError?.(error);
|
|
71
|
-
|
|
72
|
-
// Return no-op unsubscribe function
|
|
73
|
-
return () => {
|
|
74
|
-
if (__DEV__) {
|
|
75
|
-
console.log("[CreationsSubscription] No-op unsubscribe called (no listener was created)");
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Creates the realtime listener with optimized query
|
|
82
|
-
*/
|
|
83
|
-
private createRealtimeListener(
|
|
84
|
-
userCollection: any,
|
|
85
|
-
onData: CreationsSubscriptionCallback,
|
|
86
|
-
onError?: (error: Error) => void,
|
|
87
|
-
): UnsubscribeFunction {
|
|
88
42
|
const q = query(
|
|
89
43
|
userCollection,
|
|
90
44
|
where(CREATION_FIELDS.DELETED_AT, "==", null),
|
|
@@ -101,22 +55,14 @@ export class CreationsSubscription {
|
|
|
101
55
|
});
|
|
102
56
|
|
|
103
57
|
if (__DEV__) {
|
|
104
|
-
console.log("[CreationsSubscription]
|
|
105
|
-
count: creations.length,
|
|
106
|
-
serverFiltered: true,
|
|
107
|
-
hasChanges: snapshot.docChanges().length,
|
|
108
|
-
});
|
|
58
|
+
console.log("[CreationsSubscription] Sync:", creations.length, "items");
|
|
109
59
|
}
|
|
110
60
|
|
|
111
61
|
onData(creations);
|
|
112
62
|
},
|
|
113
63
|
(error: Error) => {
|
|
114
64
|
if (__DEV__) {
|
|
115
|
-
console.error("[CreationsSubscription]
|
|
116
|
-
error: error.message,
|
|
117
|
-
code: (error as { code?: string }).code,
|
|
118
|
-
userId,
|
|
119
|
-
});
|
|
65
|
+
console.error("[CreationsSubscription] Error:", error.message);
|
|
120
66
|
}
|
|
121
67
|
onError?.(error);
|
|
122
68
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Main class that orchestrates all creation write operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type { GetUserCollection, GetDocRef } from "./CreationsFetcher";
|
|
7
7
|
import type { Creation } from "../../domain/entities/Creation";
|
|
8
8
|
import * as operations from "./creations-operations";
|
|
9
9
|
import * as stateOperations from "./creations-state-operations";
|
|
@@ -12,37 +12,40 @@ import * as stateOperations from "./creations-state-operations";
|
|
|
12
12
|
* Handles write operations for creations
|
|
13
13
|
*/
|
|
14
14
|
export class CreationsWriter {
|
|
15
|
-
constructor(
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly getUserCollection: GetUserCollection,
|
|
17
|
+
private readonly getDocRef: GetDocRef,
|
|
18
|
+
) {}
|
|
16
19
|
|
|
17
20
|
async create(userId: string, creation: Creation): Promise<void> {
|
|
18
|
-
return operations.createCreation(this.
|
|
21
|
+
return operations.createCreation(this.getUserCollection, this.getDocRef, userId, creation);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
async update(userId: string, id: string, updates: Partial<Creation>): Promise<boolean> {
|
|
22
|
-
return operations.updateCreation(this.
|
|
25
|
+
return operations.updateCreation(this.getDocRef, userId, id, updates);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
async delete(userId: string, creationId: string): Promise<boolean> {
|
|
26
|
-
return operations.deleteCreation(this.
|
|
29
|
+
return operations.deleteCreation(this.getDocRef, userId, creationId);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
async hardDelete(userId: string, creationId: string): Promise<boolean> {
|
|
30
|
-
return operations.hardDeleteCreation(this.
|
|
33
|
+
return operations.hardDeleteCreation(this.getDocRef, userId, creationId);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
async restore(userId: string, creationId: string): Promise<boolean> {
|
|
34
|
-
return operations.restoreCreation(this.
|
|
37
|
+
return operations.restoreCreation(this.getDocRef, userId, creationId);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
async updateShared(userId: string, creationId: string, isShared: boolean): Promise<boolean> {
|
|
38
|
-
return stateOperations.updateCreationShared(this.
|
|
41
|
+
return stateOperations.updateCreationShared(this.getDocRef, userId, creationId, isShared);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
async updateFavorite(userId: string, creationId: string, isFavorite: boolean): Promise<boolean> {
|
|
42
|
-
return stateOperations.updateCreationFavorite(this.
|
|
45
|
+
return stateOperations.updateCreationFavorite(this.getDocRef, userId, creationId, isFavorite);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
async rate(userId: string, creationId: string, rating: number, description?: string): Promise<boolean> {
|
|
46
|
-
return stateOperations.rateCreation(this.
|
|
49
|
+
return stateOperations.rateCreation(this.getDocRef, userId, creationId, rating, description);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { setDoc } from "firebase/firestore";
|
|
6
|
-
import {
|
|
6
|
+
import type { GetUserCollection, GetDocRef } from "./CreationsFetcher";
|
|
7
7
|
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Creates a new creation document
|
|
11
11
|
*/
|
|
12
12
|
export async function createCreation(
|
|
13
|
-
|
|
13
|
+
_getUserCollection: GetUserCollection,
|
|
14
|
+
getDocRef: GetDocRef,
|
|
14
15
|
userId: string,
|
|
15
16
|
creation: Creation
|
|
16
17
|
): Promise<void> {
|
|
17
|
-
const docRef =
|
|
18
|
+
const docRef = getDocRef(userId, creation.id);
|
|
18
19
|
if (!docRef) throw new Error("Firestore not initialized");
|
|
19
20
|
|
|
20
21
|
const data: CreationDocument = {
|
|
@@ -1,122 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creation Delete Operations
|
|
3
|
+
* Single Responsibility: Delete/restore operations with centralized error handling
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { updateDoc, deleteDoc } from "firebase/firestore";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
declare const __DEV__: boolean;
|
|
7
|
+
import type { GetDocRef } from "./CreationsFetcher";
|
|
8
|
+
import { logOperationError, logOperationSuccess, logInvalidRef } from "./creation-error-handler.util";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Soft deletes a creation
|
|
11
|
+
* Soft deletes a creation by setting deletedAt timestamp
|
|
12
12
|
*/
|
|
13
13
|
export async function deleteCreation(
|
|
14
|
-
|
|
14
|
+
getDocRef: GetDocRef,
|
|
15
15
|
userId: string,
|
|
16
16
|
creationId: string
|
|
17
17
|
): Promise<boolean> {
|
|
18
|
-
const docRef =
|
|
18
|
+
const docRef = getDocRef(userId, creationId);
|
|
19
|
+
const context = { userId, creationId };
|
|
20
|
+
|
|
19
21
|
if (!docRef) {
|
|
20
|
-
|
|
21
|
-
console.error("[CreationDelete] Cannot delete: Invalid document reference", {
|
|
22
|
-
userId,
|
|
23
|
-
creationId,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
22
|
+
logInvalidRef("Delete", context);
|
|
26
23
|
return false;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
try {
|
|
30
27
|
await updateDoc(docRef, { deletedAt: new Date() });
|
|
31
|
-
|
|
32
|
-
console.log("[CreationDelete] Soft deleted successfully", { userId, creationId });
|
|
33
|
-
}
|
|
28
|
+
logOperationSuccess("Delete", context);
|
|
34
29
|
return true;
|
|
35
30
|
} catch (error) {
|
|
36
|
-
|
|
37
|
-
console.error("[CreationDelete] Soft delete failed", {
|
|
38
|
-
userId,
|
|
39
|
-
creationId,
|
|
40
|
-
error: error instanceof Error ? error.message : String(error),
|
|
41
|
-
code: (error as { code?: string })?.code,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
31
|
+
logOperationError("Delete", context, error);
|
|
44
32
|
return false;
|
|
45
33
|
}
|
|
46
34
|
}
|
|
47
35
|
|
|
48
36
|
/**
|
|
49
|
-
*
|
|
37
|
+
* Permanently deletes a creation from Firestore
|
|
50
38
|
*/
|
|
51
39
|
export async function hardDeleteCreation(
|
|
52
|
-
|
|
40
|
+
getDocRef: GetDocRef,
|
|
53
41
|
userId: string,
|
|
54
42
|
creationId: string
|
|
55
43
|
): Promise<boolean> {
|
|
56
|
-
const docRef =
|
|
44
|
+
const docRef = getDocRef(userId, creationId);
|
|
45
|
+
const context = { userId, creationId };
|
|
46
|
+
|
|
57
47
|
if (!docRef) {
|
|
58
|
-
|
|
59
|
-
console.error("[CreationDelete] Cannot hard delete: Invalid document reference", {
|
|
60
|
-
userId,
|
|
61
|
-
creationId,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
48
|
+
logInvalidRef("HardDelete", context);
|
|
64
49
|
return false;
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
try {
|
|
68
53
|
await deleteDoc(docRef);
|
|
69
|
-
|
|
70
|
-
console.log("[CreationDelete] Hard deleted successfully", { userId, creationId });
|
|
71
|
-
}
|
|
54
|
+
logOperationSuccess("HardDelete", context);
|
|
72
55
|
return true;
|
|
73
56
|
} catch (error) {
|
|
74
|
-
|
|
75
|
-
console.error("[CreationDelete] Hard delete failed", {
|
|
76
|
-
userId,
|
|
77
|
-
creationId,
|
|
78
|
-
error: error instanceof Error ? error.message : String(error),
|
|
79
|
-
code: (error as { code?: string })?.code,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
57
|
+
logOperationError("HardDelete", context, error);
|
|
82
58
|
return false;
|
|
83
59
|
}
|
|
84
60
|
}
|
|
85
61
|
|
|
86
62
|
/**
|
|
87
|
-
* Restores a soft-deleted creation
|
|
63
|
+
* Restores a soft-deleted creation by clearing deletedAt
|
|
88
64
|
*/
|
|
89
65
|
export async function restoreCreation(
|
|
90
|
-
|
|
66
|
+
getDocRef: GetDocRef,
|
|
91
67
|
userId: string,
|
|
92
68
|
creationId: string
|
|
93
69
|
): Promise<boolean> {
|
|
94
|
-
const docRef =
|
|
70
|
+
const docRef = getDocRef(userId, creationId);
|
|
71
|
+
const context = { userId, creationId };
|
|
72
|
+
|
|
95
73
|
if (!docRef) {
|
|
96
|
-
|
|
97
|
-
console.error("[CreationDelete] Cannot restore: Invalid document reference", {
|
|
98
|
-
userId,
|
|
99
|
-
creationId,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
74
|
+
logInvalidRef("Restore", context);
|
|
102
75
|
return false;
|
|
103
76
|
}
|
|
104
77
|
|
|
105
78
|
try {
|
|
106
79
|
await updateDoc(docRef, { deletedAt: null });
|
|
107
|
-
|
|
108
|
-
console.log("[CreationDelete] Restored successfully", { userId, creationId });
|
|
109
|
-
}
|
|
80
|
+
logOperationSuccess("Restore", context);
|
|
110
81
|
return true;
|
|
111
82
|
} catch (error) {
|
|
112
|
-
|
|
113
|
-
console.error("[CreationDelete] Restore failed", {
|
|
114
|
-
userId,
|
|
115
|
-
creationId,
|
|
116
|
-
error: error instanceof Error ? error.message : String(error),
|
|
117
|
-
code: (error as { code?: string })?.code,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
83
|
+
logOperationError("Restore", context, error);
|
|
120
84
|
return false;
|
|
121
85
|
}
|
|
122
86
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Error Handler Utility
|
|
3
|
+
* Single Responsibility: Centralized error logging
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare const __DEV__: boolean;
|
|
7
|
+
|
|
8
|
+
export function logOperationError(
|
|
9
|
+
operation: string,
|
|
10
|
+
context: { userId: string; creationId: string },
|
|
11
|
+
error: unknown
|
|
12
|
+
): void {
|
|
13
|
+
if (!__DEV__) return;
|
|
14
|
+
|
|
15
|
+
console.error(`[Creation${operation}] Failed:`, {
|
|
16
|
+
...context,
|
|
17
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18
|
+
code: (error as { code?: string })?.code,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function logOperationSuccess(
|
|
23
|
+
operation: string,
|
|
24
|
+
context: { userId: string; creationId: string }
|
|
25
|
+
): void {
|
|
26
|
+
if (!__DEV__) return;
|
|
27
|
+
console.log(`[Creation${operation}] Success:`, context);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function logInvalidRef(
|
|
31
|
+
operation: string,
|
|
32
|
+
context: { userId: string; creationId: string }
|
|
33
|
+
): void {
|
|
34
|
+
if (!__DEV__) return;
|
|
35
|
+
console.error(`[Creation${operation}] Invalid document reference:`, context);
|
|
36
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { updateDoc } from "firebase/firestore";
|
|
6
|
-
import {
|
|
6
|
+
import type { GetDocRef } from "./CreationsFetcher";
|
|
7
7
|
import type { Creation } from "../../domain/entities/Creation";
|
|
8
8
|
import { CREATION_FIELDS, type CreationFieldName } from "../../domain/constants";
|
|
9
9
|
|
|
@@ -34,12 +34,12 @@ export const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
|
|
|
34
34
|
* Updates a creation document
|
|
35
35
|
*/
|
|
36
36
|
export async function updateCreation(
|
|
37
|
-
|
|
37
|
+
getDocRef: GetDocRef,
|
|
38
38
|
userId: string,
|
|
39
39
|
id: string,
|
|
40
40
|
updates: Partial<Creation>
|
|
41
41
|
): Promise<boolean> {
|
|
42
|
-
const docRef =
|
|
42
|
+
const docRef = getDocRef(userId, id);
|
|
43
43
|
|
|
44
44
|
if (!docRef) {
|
|
45
45
|
throw new Error(
|
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { updateDoc } from "firebase/firestore";
|
|
7
|
-
import {
|
|
7
|
+
import type { GetDocRef } from "./CreationsFetcher";
|
|
8
8
|
import { submitFeedback } from "@umituz/react-native-subscription";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Updates the shared status of a creation
|
|
12
12
|
*/
|
|
13
13
|
export async function updateCreationShared(
|
|
14
|
-
|
|
14
|
+
getDocRef: GetDocRef,
|
|
15
15
|
userId: string,
|
|
16
16
|
creationId: string,
|
|
17
17
|
isShared: boolean
|
|
18
18
|
): Promise<boolean> {
|
|
19
|
-
const docRef =
|
|
19
|
+
const docRef = getDocRef(userId, creationId);
|
|
20
20
|
if (!docRef) return false;
|
|
21
21
|
|
|
22
22
|
try {
|
|
@@ -31,12 +31,12 @@ export async function updateCreationShared(
|
|
|
31
31
|
* Updates the favorite status of a creation
|
|
32
32
|
*/
|
|
33
33
|
export async function updateCreationFavorite(
|
|
34
|
-
|
|
34
|
+
getDocRef: GetDocRef,
|
|
35
35
|
userId: string,
|
|
36
36
|
creationId: string,
|
|
37
37
|
isFavorite: boolean
|
|
38
38
|
): Promise<boolean> {
|
|
39
|
-
const docRef =
|
|
39
|
+
const docRef = getDocRef(userId, creationId);
|
|
40
40
|
if (!docRef) return false;
|
|
41
41
|
|
|
42
42
|
try {
|
|
@@ -51,13 +51,13 @@ export async function updateCreationFavorite(
|
|
|
51
51
|
* Rates a creation and optionally submits feedback
|
|
52
52
|
*/
|
|
53
53
|
export async function rateCreation(
|
|
54
|
-
|
|
54
|
+
getDocRef: GetDocRef,
|
|
55
55
|
userId: string,
|
|
56
56
|
creationId: string,
|
|
57
57
|
rating: number,
|
|
58
58
|
description?: string
|
|
59
59
|
): Promise<boolean> {
|
|
60
|
-
const docRef =
|
|
60
|
+
const docRef = getDocRef(userId, creationId);
|
|
61
61
|
if (!docRef) return false;
|
|
62
62
|
|
|
63
63
|
try {
|
|
@@ -49,57 +49,56 @@ export function useProcessingJobsPoller(
|
|
|
49
49
|
[creations],
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const status = await provider.getJobStatus(creation.model, creation.requestId);
|
|
68
|
-
|
|
69
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
70
|
-
console.log("[ProcessingJobsPoller] Status:", creation.id, status.status);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (status.status === QUEUE_STATUS.COMPLETED) {
|
|
74
|
-
const result = await provider.getJobResult<FalResult>(creation.model, creation.requestId);
|
|
75
|
-
const urls = extractResultUrl(result);
|
|
76
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Completed:", creation.id, urls);
|
|
77
|
-
|
|
78
|
-
const uri = urls.videoUrl || urls.imageUrl || "";
|
|
79
|
-
await repository.update(userId, creation.id, {
|
|
80
|
-
status: CREATION_STATUS.COMPLETED,
|
|
81
|
-
uri,
|
|
82
|
-
output: urls,
|
|
83
|
-
});
|
|
84
|
-
} else if (status.status === QUEUE_STATUS.FAILED) {
|
|
85
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Failed:", creation.id);
|
|
86
|
-
|
|
87
|
-
await repository.update(userId, creation.id, {
|
|
88
|
-
status: CREATION_STATUS.FAILED,
|
|
89
|
-
metadata: { error: "Generation failed" },
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
// If still IN_PROGRESS or IN_QUEUE, we'll check again next interval
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
|
-
console.error("[ProcessingJobsPoller] Poll error:", creation.id, error);
|
|
96
|
-
}
|
|
97
|
-
} finally {
|
|
98
|
-
pollingRef.current.delete(creation.id);
|
|
52
|
+
// Use ref for stable function reference to prevent effect re-runs
|
|
53
|
+
const pollJobRef = useRef<(creation: Creation) => Promise<void>>();
|
|
54
|
+
|
|
55
|
+
pollJobRef.current = async (creation: Creation) => {
|
|
56
|
+
if (!userId || !creation.requestId || !creation.model) return;
|
|
57
|
+
if (pollingRef.current.has(creation.id)) return;
|
|
58
|
+
|
|
59
|
+
const provider = providerRegistry.getActiveProvider();
|
|
60
|
+
if (!provider || !provider.isInitialized()) return;
|
|
61
|
+
|
|
62
|
+
pollingRef.current.add(creation.id);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
+
console.log("[ProcessingJobsPoller] Checking status:", creation.id);
|
|
99
67
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
68
|
+
|
|
69
|
+
const status = await provider.getJobStatus(creation.model, creation.requestId);
|
|
70
|
+
|
|
71
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
72
|
+
console.log("[ProcessingJobsPoller] Status:", creation.id, status.status);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (status.status === QUEUE_STATUS.COMPLETED) {
|
|
76
|
+
const result = await provider.getJobResult<FalResult>(creation.model, creation.requestId);
|
|
77
|
+
const urls = extractResultUrl(result);
|
|
78
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Completed:", creation.id, urls);
|
|
79
|
+
|
|
80
|
+
const uri = urls.videoUrl || urls.imageUrl || "";
|
|
81
|
+
await repository.update(userId, creation.id, {
|
|
82
|
+
status: CREATION_STATUS.COMPLETED,
|
|
83
|
+
uri,
|
|
84
|
+
output: urls,
|
|
85
|
+
});
|
|
86
|
+
} else if (status.status === QUEUE_STATUS.FAILED) {
|
|
87
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Failed:", creation.id);
|
|
88
|
+
|
|
89
|
+
await repository.update(userId, creation.id, {
|
|
90
|
+
status: CREATION_STATUS.FAILED,
|
|
91
|
+
metadata: { error: "Generation failed" },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
96
|
+
console.error("[ProcessingJobsPoller] Poll error:", creation.id, error);
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
pollingRef.current.delete(creation.id);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
103
102
|
|
|
104
103
|
useEffect(() => {
|
|
105
104
|
if (!enabled || !userId || processingJobs.length === 0) {
|
|
@@ -111,11 +110,11 @@ export function useProcessingJobsPoller(
|
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
// Initial poll
|
|
114
|
-
processingJobs.forEach((job) =>
|
|
113
|
+
processingJobs.forEach((job) => pollJobRef.current?.(job));
|
|
115
114
|
|
|
116
115
|
// Set up interval polling
|
|
117
116
|
intervalRef.current = setInterval(() => {
|
|
118
|
-
processingJobs.forEach((job) =>
|
|
117
|
+
processingJobs.forEach((job) => pollJobRef.current?.(job));
|
|
119
118
|
}, DEFAULT_POLL_INTERVAL_MS);
|
|
120
119
|
|
|
121
120
|
return () => {
|
|
@@ -128,7 +127,7 @@ export function useProcessingJobsPoller(
|
|
|
128
127
|
intervalRef.current = null;
|
|
129
128
|
}
|
|
130
129
|
};
|
|
131
|
-
}, [enabled, userId, processingJobs
|
|
130
|
+
}, [enabled, userId, processingJobs]);
|
|
132
131
|
|
|
133
132
|
return {
|
|
134
133
|
processingCount: processingJobs.length,
|