@umituz/web-firebase 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +12 -39
  2. package/src/domains/auth/entities/index.ts +60 -0
  3. package/src/domains/auth/index.ts +13 -0
  4. package/src/domains/auth/services/auth.service.ts +245 -0
  5. package/src/domains/auth/services/index.ts +7 -0
  6. package/src/domains/auth/types/auth-service.interface.ts +72 -0
  7. package/src/domains/auth/types/index.ts +5 -0
  8. package/src/domains/firestore/entities/index.ts +82 -0
  9. package/src/domains/firestore/index.ts +13 -0
  10. package/src/domains/firestore/services/firestore.service.ts +191 -0
  11. package/src/domains/firestore/services/index.ts +7 -0
  12. package/src/domains/firestore/types/firestore-service.interface.ts +64 -0
  13. package/src/domains/firestore/types/index.ts +5 -0
  14. package/src/domains/storage/entities/index.ts +94 -0
  15. package/src/domains/storage/index.ts +13 -0
  16. package/src/domains/storage/services/index.ts +7 -0
  17. package/src/domains/storage/services/storage.service.ts +223 -0
  18. package/src/domains/storage/types/index.ts +5 -0
  19. package/src/domains/storage/types/storage-service.interface.ts +120 -0
  20. package/src/index.ts +12 -16
  21. package/src/presentation/hooks/useAuth.ts +69 -26
  22. package/src/presentation/providers/FirebaseProvider.tsx +9 -14
  23. package/dist/application/index.d.mts +0 -273
  24. package/dist/application/index.d.ts +0 -273
  25. package/dist/application/index.js +0 -490
  26. package/dist/application/index.mjs +0 -19
  27. package/dist/chunk-34DL2QWQ.mjs +0 -87
  28. package/dist/chunk-4FP2ELQ5.mjs +0 -96
  29. package/dist/chunk-7TX3OU3O.mjs +0 -721
  30. package/dist/chunk-I6WGBPFB.mjs +0 -439
  31. package/dist/chunk-RZ4QR6TB.mjs +0 -96
  32. package/dist/chunk-U2XI4MGO.mjs +0 -397
  33. package/dist/domain/index.d.mts +0 -325
  34. package/dist/domain/index.d.ts +0 -325
  35. package/dist/domain/index.js +0 -662
  36. package/dist/domain/index.mjs +0 -36
  37. package/dist/file.repository.interface-v5vHgVsZ.d.mts +0 -241
  38. package/dist/file.repository.interface-v5vHgVsZ.d.ts +0 -241
  39. package/dist/firebase.entity-xvfEPjXZ.d.mts +0 -15
  40. package/dist/firebase.entity-xvfEPjXZ.d.ts +0 -15
  41. package/dist/index.d.mts +0 -14
  42. package/dist/index.d.ts +0 -14
  43. package/dist/index.js +0 -1833
  44. package/dist/index.mjs +0 -98
  45. package/dist/infrastructure/index.d.mts +0 -170
  46. package/dist/infrastructure/index.d.ts +0 -170
  47. package/dist/infrastructure/index.js +0 -856
  48. package/dist/infrastructure/index.mjs +0 -46
  49. package/dist/presentation/index.d.mts +0 -25
  50. package/dist/presentation/index.d.ts +0 -25
  51. package/dist/presentation/index.js +0 -105
  52. package/dist/presentation/index.mjs +0 -6
  53. package/dist/user.repository.interface-DS74TsJ5.d.mts +0 -298
  54. package/dist/user.repository.interface-DS74TsJ5.d.ts +0 -298
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Firestore Service
3
+ * @description Firebase Firestore implementation of IFirestoreService
4
+ */
5
+
6
+ import {
7
+ doc,
8
+ getDoc,
9
+ setDoc,
10
+ updateDoc,
11
+ deleteDoc,
12
+ query,
13
+ where,
14
+ onSnapshot,
15
+ collection,
16
+ getDocs,
17
+ } from 'firebase/firestore'
18
+ import { getFirebaseDB } from '../../../infrastructure/firebase/client'
19
+ import type { IFirestoreService } from '../types'
20
+ import type { User } from '../entities'
21
+
22
+ class FirestoreService implements IFirestoreService {
23
+ private get db() {
24
+ return getFirebaseDB()
25
+ }
26
+
27
+ private readonly USERS_COLLECTION = 'users'
28
+
29
+ async getUser(userId: string): Promise<User | null> {
30
+ try {
31
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
32
+ const snap = await getDoc(docRef)
33
+
34
+ if (!snap.exists()) {
35
+ return null
36
+ }
37
+
38
+ return snap.data() as User
39
+ } catch (error) {
40
+ throw new Error('User not found')
41
+ }
42
+ }
43
+
44
+ async getUserByEmail(email: string): Promise<User | null> {
45
+ try {
46
+ const q = query(collection(this.db, this.USERS_COLLECTION), where('profile.email', '==', email))
47
+ const snap = await getDocs(q)
48
+
49
+ if (snap.empty) {
50
+ return null
51
+ }
52
+
53
+ const doc = snap.docs[0]
54
+ return doc.data() as User
55
+ } catch (error) {
56
+ throw new Error('Failed to query user')
57
+ }
58
+ }
59
+
60
+ async createUser(userId: string, data: Partial<User>): Promise<void> {
61
+ try {
62
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
63
+ await setDoc(docRef, data, { merge: true })
64
+ } catch (error) {
65
+ throw new Error('Failed to create user')
66
+ }
67
+ }
68
+
69
+ async updateUser(userId: string, data: Partial<User>): Promise<void> {
70
+ try {
71
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
72
+ await updateDoc(docRef, {
73
+ ...data,
74
+ 'profile.updatedAt': Date.now(),
75
+ } as any)
76
+ } catch (error) {
77
+ throw new Error('Failed to update user')
78
+ }
79
+ }
80
+
81
+ async deleteUser(userId: string): Promise<void> {
82
+ try {
83
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
84
+ await deleteDoc(docRef)
85
+ } catch (error) {
86
+ throw new Error('Failed to delete user')
87
+ }
88
+ }
89
+
90
+ async updateProfile(
91
+ userId: string,
92
+ updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL' | 'phoneNumber'>>
93
+ ): Promise<void> {
94
+ try {
95
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
96
+ const updateData: any = {
97
+ 'profile.updatedAt': Date.now(),
98
+ }
99
+
100
+ if (updates.displayName !== undefined) {
101
+ updateData['profile.displayName'] = updates.displayName
102
+ }
103
+ if (updates.photoURL !== undefined) {
104
+ updateData['profile.photoURL'] = updates.photoURL
105
+ }
106
+ if (updates.phoneNumber !== undefined) {
107
+ updateData['profile.phoneNumber'] = updates.phoneNumber
108
+ }
109
+
110
+ await updateDoc(docRef, updateData)
111
+ } catch (error) {
112
+ throw new Error('Failed to update profile')
113
+ }
114
+ }
115
+
116
+ async updateSettings(userId: string, settings: Partial<User['settings']>): Promise<void> {
117
+ try {
118
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
119
+ await updateDoc(docRef, {
120
+ settings: {
121
+ ...settings,
122
+ updatedAt: Date.now(),
123
+ },
124
+ } as any)
125
+ } catch (error) {
126
+ throw new Error('Failed to update settings')
127
+ }
128
+ }
129
+
130
+ async updateSubscription(userId: string, subscription: Partial<User['subscription']>): Promise<void> {
131
+ try {
132
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
133
+ await updateDoc(docRef, {
134
+ subscription: {
135
+ ...subscription,
136
+ updatedAt: Date.now(),
137
+ },
138
+ } as any)
139
+ } catch (error) {
140
+ throw new Error('Failed to update subscription')
141
+ }
142
+ }
143
+
144
+ async updateLastLogin(userId: string): Promise<void> {
145
+ try {
146
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
147
+ await updateDoc(docRef, {
148
+ 'profile.lastLoginAt': Date.now(),
149
+ } as any)
150
+ } catch (error) {
151
+ throw new Error('Failed to update last login')
152
+ }
153
+ }
154
+
155
+ async queryUsers(constraints: any[]): Promise<User[]> {
156
+ try {
157
+ const q = query(collection(this.db, this.USERS_COLLECTION), ...constraints)
158
+ const snap = await getDocs(q)
159
+ return snap.docs.map((doc) => doc.data() as User)
160
+ } catch (error) {
161
+ throw new Error('Failed to query users')
162
+ }
163
+ }
164
+
165
+ subscribeToUser(
166
+ userId: string,
167
+ callback: (user: User | null) => void,
168
+ onError?: (error: Error) => void
169
+ ): () => void {
170
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
171
+
172
+ const unsubscribe = onSnapshot(
173
+ docRef,
174
+ (snap) => {
175
+ if (snap.exists()) {
176
+ callback(snap.data() as User)
177
+ } else {
178
+ callback(null)
179
+ }
180
+ },
181
+ (error) => {
182
+ onError?.(error as Error)
183
+ }
184
+ )
185
+
186
+ return unsubscribe
187
+ }
188
+ }
189
+
190
+ // Export class and singleton instance
191
+ export const firestoreService = new FirestoreService()
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Firestore Domain Services
3
+ * Subpath: @umituz/web-firebase/firestore
4
+ */
5
+
6
+ export { firestoreService } from './firestore.service'
7
+ export type { IFirestoreService } from '../types'
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Firestore Service Interface
3
+ * @description Abstract interface for Firestore operations
4
+ */
5
+
6
+ import type { QueryConstraint } from 'firebase/firestore'
7
+ import type { User } from '../entities'
8
+
9
+ export interface IFirestoreService {
10
+ /**
11
+ * Get user by ID
12
+ */
13
+ getUser(userId: string): Promise<User | null>
14
+
15
+ /**
16
+ * Get user by email
17
+ */
18
+ getUserByEmail(email: string): Promise<User | null>
19
+
20
+ /**
21
+ * Create user
22
+ */
23
+ createUser(userId: string, data: Partial<User>): Promise<void>
24
+
25
+ /**
26
+ * Update user
27
+ */
28
+ updateUser(userId: string, data: Partial<User>): Promise<void>
29
+
30
+ /**
31
+ * Delete user
32
+ */
33
+ deleteUser(userId: string): Promise<void>
34
+
35
+ /**
36
+ * Update user profile
37
+ */
38
+ updateProfile(userId: string, updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL' | 'phoneNumber'>>): Promise<void>
39
+
40
+ /**
41
+ * Update user settings
42
+ */
43
+ updateSettings(userId: string, settings: Partial<User['settings']>): Promise<void>
44
+
45
+ /**
46
+ * Update user subscription
47
+ */
48
+ updateSubscription(userId: string, subscription: Partial<User['subscription']>): Promise<void>
49
+
50
+ /**
51
+ * Update last login timestamp
52
+ */
53
+ updateLastLogin(userId: string): Promise<void>
54
+
55
+ /**
56
+ * Query users with constraints
57
+ */
58
+ queryUsers(constraints: QueryConstraint[]): Promise<User[]>
59
+
60
+ /**
61
+ * Subscribe to user document changes
62
+ */
63
+ subscribeToUser(userId: string, callback: (user: User | null) => void, onError?: (error: Error) => void): () => void
64
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Firestore Domain Types
3
+ */
4
+
5
+ export * from './firestore-service.interface'
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Storage Domain Entities
3
+ * @description File and storage-related entities
4
+ */
5
+
6
+ /**
7
+ * File Metadata Entity
8
+ * Contains metadata about uploaded files
9
+ */
10
+ export interface FileMetadata {
11
+ readonly id: string
12
+ readonly name: string
13
+ readonly fullPath: string
14
+ readonly contentType: string
15
+ readonly size: number
16
+ readonly createdAt: number
17
+ readonly updatedAt: number
18
+ readonly userId: string
19
+ readonly type: FileType
20
+ category?: FileCategory
21
+ description?: string
22
+ tags?: string[]
23
+ }
24
+
25
+ /**
26
+ * File Types
27
+ */
28
+ export type FileType = 'image' | 'video' | 'audio' | 'document' | 'other'
29
+
30
+ /**
31
+ * File Categories
32
+ */
33
+ export type FileCategory = 'profile' | 'content' | 'document' | 'attachment' | 'backup'
34
+
35
+ /**
36
+ * Upload Progress Entity
37
+ * Tracks upload progress for resumable uploads
38
+ */
39
+ export interface UploadProgress {
40
+ bytesTransferred: number
41
+ totalBytes: number
42
+ progress: number // 0-100
43
+ state: UploadState
44
+ speed?: number // bytes per second
45
+ remaining?: number // seconds remaining
46
+ }
47
+
48
+ /**
49
+ * Upload States
50
+ */
51
+ export type UploadState =
52
+ | 'paused'
53
+ | 'running'
54
+ | 'success'
55
+ | 'canceled'
56
+ | 'error'
57
+
58
+ /**
59
+ * File Upload Result Entity
60
+ * Result of a file upload operation
61
+ */
62
+ export interface FileUploadResult {
63
+ readonly id: string
64
+ readonly name: string
65
+ readonly fullPath: string
66
+ readonly downloadURL: string
67
+ readonly contentType: string
68
+ readonly size: number
69
+ readonly createdAt: number
70
+ }
71
+
72
+ // Export as UploadResult for backward compatibility
73
+ export type UploadResult = FileUploadResult
74
+
75
+ /**
76
+ * Upload Options
77
+ * Configuration for file uploads
78
+ */
79
+ export interface UploadOptions {
80
+ onProgress?: (progress: UploadProgress) => void
81
+ metadata?: FileMetadata
82
+ customMetadata?: Record<string, string>
83
+ }
84
+
85
+ /**
86
+ * Storage Statistics
87
+ */
88
+ export interface StorageStats {
89
+ totalFiles: number
90
+ totalSize: number
91
+ filesByType: Record<FileType, number>
92
+ filesByCategory: Record<FileCategory, number>
93
+ lastUploadAt?: number
94
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Storage Domain
3
+ * Complete Firebase Storage integration
4
+ */
5
+
6
+ // Entities
7
+ export * from './entities'
8
+
9
+ // Types
10
+ export * from './types'
11
+
12
+ // Services
13
+ export * from './services'
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage Domain Services
3
+ * Subpath: @umituz/web-firebase/storage
4
+ */
5
+
6
+ export { storageService } from './storage.service'
7
+ export type { IStorageService } from '../types'
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Storage Service
3
+ * @description Firebase Storage implementation of IStorageService
4
+ */
5
+
6
+ import {
7
+ ref,
8
+ uploadBytes,
9
+ uploadBytesResumable,
10
+ getDownloadURL,
11
+ deleteObject,
12
+ listAll,
13
+ getMetadata,
14
+ } from 'firebase/storage'
15
+ import { getFirebaseStorage } from '../../../infrastructure/firebase/client'
16
+ import type { IStorageService } from '../types'
17
+ import type {
18
+ UploadResult,
19
+ UploadOptions,
20
+ } from '../entities'
21
+
22
+ class StorageService implements IStorageService {
23
+ private get storage() {
24
+ return getFirebaseStorage()
25
+ }
26
+
27
+ // Upload Methods
28
+
29
+ async uploadFile(
30
+ userId: string,
31
+ path: string,
32
+ file: File | Blob,
33
+ options?: UploadOptions
34
+ ): Promise<UploadResult> {
35
+ const storageRef = ref(this.storage, `users/${userId}/${path}`)
36
+ const uploadTask = uploadBytesResumable(storageRef, file)
37
+
38
+ return new Promise((resolve, reject) => {
39
+ uploadTask.on(
40
+ 'state_changed',
41
+ (snapshot) => {
42
+ if (options?.onProgress) {
43
+ const progress = {
44
+ bytesTransferred: snapshot.bytesTransferred,
45
+ totalBytes: snapshot.totalBytes,
46
+ progress: (snapshot.bytesTransferred / snapshot.totalBytes) * 100,
47
+ state: 'running' as const,
48
+ }
49
+ options.onProgress(progress)
50
+ }
51
+ },
52
+ () => reject(new Error('Upload failed')),
53
+ async () => {
54
+ const downloadURL = await getDownloadURL(uploadTask.snapshot.ref)
55
+ const metadata = await getMetadata(uploadTask.snapshot.ref)
56
+
57
+ resolve({
58
+ id: uploadTask.snapshot.ref.name,
59
+ name: metadata.name || uploadTask.snapshot.ref.name,
60
+ fullPath: metadata.fullPath || uploadTask.snapshot.ref.fullPath,
61
+ downloadURL,
62
+ contentType: metadata.contentType || '',
63
+ size: metadata.size || 0,
64
+ createdAt: metadata.timeCreated ? new Date(metadata.timeCreated).getTime() : Date.now(),
65
+ })
66
+ }
67
+ )
68
+ })
69
+ }
70
+
71
+ async uploadImage(userId: string, file: File, filename?: string): Promise<UploadResult> {
72
+ const name = filename || `${Date.now()}_${file.name}`
73
+ return this.uploadFile(userId, `images/${name}`, file)
74
+ }
75
+
76
+ async uploadVideo(userId: string, file: File, filename?: string): Promise<UploadResult> {
77
+ const name = filename || `${Date.now()}_${file.name}`
78
+ return this.uploadFile(userId, `videos/${name}`, file)
79
+ }
80
+
81
+ async uploadDocument(userId: string, file: File, filename?: string): Promise<UploadResult> {
82
+ const name = filename || `${Date.now()}_${file.name}`
83
+ return this.uploadFile(userId, `documents/${name}`, file)
84
+ }
85
+
86
+ async uploadProfilePicture(userId: string, file: File): Promise<UploadResult> {
87
+ const storageRef = ref(this.storage, `users/${userId}/profile/${Date.now()}_${file.name}`)
88
+ await uploadBytes(storageRef, file)
89
+ const downloadURL = await getDownloadURL(storageRef)
90
+
91
+ return {
92
+ id: storageRef.name,
93
+ name: file.name,
94
+ fullPath: storageRef.fullPath,
95
+ downloadURL,
96
+ contentType: file.type,
97
+ size: file.size,
98
+ createdAt: Date.now(),
99
+ }
100
+ }
101
+
102
+ // Download Methods
103
+
104
+ async getDownloadURL(path: string): Promise<string> {
105
+ try {
106
+ const storageRef = ref(this.storage, path)
107
+ return await getDownloadURL(storageRef)
108
+ } catch {
109
+ throw new Error('File not found')
110
+ }
111
+ }
112
+
113
+ // Delete Methods
114
+
115
+ async deleteFile(path: string): Promise<void> {
116
+ try {
117
+ const storageRef = ref(this.storage, path)
118
+ await deleteObject(storageRef)
119
+ } catch {
120
+ throw new Error('File not found')
121
+ }
122
+ }
123
+
124
+ async deleteUserFiles(userId: string): Promise<void> {
125
+ try {
126
+ const userRef = ref(this.storage, `users/${userId}`)
127
+ const result = await listAll(userRef)
128
+
129
+ // Delete all files in all prefixes
130
+ for (const prefix of result.prefixes) {
131
+ const prefixResult = await listAll(prefix)
132
+ await Promise.all(prefixResult.items.map((item) => deleteObject(item)))
133
+ }
134
+
135
+ // Delete all files in root
136
+ await Promise.all(result.items.map((item) => deleteObject(item)))
137
+ } catch {
138
+ throw new Error('Failed to delete user files')
139
+ }
140
+ }
141
+
142
+ async deleteImage(userId: string, filename: string): Promise<void> {
143
+ await this.deleteFile(`users/${userId}/images/${filename}`)
144
+ }
145
+
146
+ async deleteVideo(userId: string, filename: string): Promise<void> {
147
+ await this.deleteFile(`users/${userId}/videos/${filename}`)
148
+ }
149
+
150
+ async deleteProfilePicture(userId: string, filename: string): Promise<void> {
151
+ await this.deleteFile(`users/${userId}/profile/${filename}`)
152
+ }
153
+
154
+ // List Methods
155
+
156
+ async listUserFiles(userId: string, path?: string): Promise<string[]> {
157
+ const userRef = ref(this.storage, path ? `users/${userId}/${path}` : `users/${userId}`)
158
+ const result = await listAll(userRef)
159
+
160
+ const urls = await Promise.all(result.items.map((item) => getDownloadURL(item)))
161
+ return urls
162
+ }
163
+
164
+ async listUserImages(userId: string): Promise<string[]> {
165
+ return this.listUserFiles(userId, 'images')
166
+ }
167
+
168
+ async listUserVideos(userId: string): Promise<string[]> {
169
+ return this.listUserFiles(userId, 'videos')
170
+ }
171
+
172
+ // Validation
173
+
174
+ validateFile(file: File, options?: { maxSizeBytes?: number; maxSizeMB?: number; allowedTypes?: string[] }): boolean {
175
+ const maxSizeBytes = options?.maxSizeBytes || (options?.maxSizeMB ? options.maxSizeMB * 1024 * 1024 : 10 * 1024 * 1024)
176
+
177
+ if (file.size > maxSizeBytes) {
178
+ return false
179
+ }
180
+
181
+ if (options?.allowedTypes && !options.allowedTypes.includes(file.type)) {
182
+ return false
183
+ }
184
+
185
+ return true
186
+ }
187
+
188
+ isImageFile(file: File): boolean {
189
+ return file.type.startsWith('image/')
190
+ }
191
+
192
+ isVideoFile(file: File): boolean {
193
+ return file.type.startsWith('video/')
194
+ }
195
+
196
+ isDocumentFile(file: File): boolean {
197
+ const docTypes = [
198
+ 'application/pdf',
199
+ 'application/msword',
200
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
201
+ 'application/vnd.ms-excel',
202
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
203
+ 'text/plain',
204
+ ]
205
+ return docTypes.includes(file.type)
206
+ }
207
+
208
+ // Utility Methods
209
+
210
+ generateUniqueFilename(originalName: string): string {
211
+ const timestamp = Date.now()
212
+ const random = Math.random().toString(36).substring(2, 8)
213
+ const extension = this.getFileExtension(originalName)
214
+ return `${timestamp}_${random}.${extension}`
215
+ }
216
+
217
+ getFileExtension(filename: string): string {
218
+ return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2)
219
+ }
220
+ }
221
+
222
+ // Export class and singleton instance
223
+ export const storageService = new StorageService()
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Storage Domain Types
3
+ */
4
+
5
+ export * from './storage-service.interface'