@umituz/web-firebase 1.0.4 → 2.0.1

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 (75) hide show
  1. package/README.md +555 -0
  2. package/dist/application/index.d.mts +273 -0
  3. package/dist/application/index.d.ts +273 -0
  4. package/dist/application/index.js +490 -0
  5. package/dist/application/index.mjs +19 -0
  6. package/dist/chunk-34DL2QWQ.mjs +87 -0
  7. package/dist/chunk-4FP2ELQ5.mjs +96 -0
  8. package/dist/chunk-7TX3OU3O.mjs +721 -0
  9. package/dist/chunk-I6WGBPFB.mjs +439 -0
  10. package/dist/chunk-RZ4QR6TB.mjs +96 -0
  11. package/dist/chunk-U2XI4MGO.mjs +397 -0
  12. package/dist/domain/index.d.mts +325 -0
  13. package/dist/domain/index.d.ts +325 -0
  14. package/dist/domain/index.js +662 -0
  15. package/dist/domain/index.mjs +36 -0
  16. package/dist/file.repository.interface-v5vHgVsZ.d.mts +241 -0
  17. package/dist/file.repository.interface-v5vHgVsZ.d.ts +241 -0
  18. package/dist/firebase.entity-xvfEPjXZ.d.mts +15 -0
  19. package/dist/firebase.entity-xvfEPjXZ.d.ts +15 -0
  20. package/dist/index.d.mts +14 -96
  21. package/dist/index.d.ts +14 -96
  22. package/dist/index.js +1717 -78
  23. package/dist/index.mjs +88 -175
  24. package/dist/infrastructure/index.d.mts +170 -0
  25. package/dist/infrastructure/index.d.ts +170 -0
  26. package/dist/infrastructure/index.js +856 -0
  27. package/dist/infrastructure/index.mjs +46 -0
  28. package/dist/presentation/index.d.mts +25 -0
  29. package/dist/presentation/index.d.ts +25 -0
  30. package/dist/presentation/index.js +105 -0
  31. package/dist/presentation/index.mjs +6 -0
  32. package/dist/user.repository.interface-DS74TsJ5.d.mts +298 -0
  33. package/dist/user.repository.interface-DS74TsJ5.d.ts +298 -0
  34. package/package.json +39 -12
  35. package/src/application/dto/auth.dto.ts +69 -0
  36. package/src/application/dto/index.ts +7 -0
  37. package/src/application/dto/user.dto.ts +64 -0
  38. package/src/application/index.ts +7 -0
  39. package/src/application/use-cases/auth/reset-password.use-case.ts +66 -0
  40. package/src/application/use-cases/auth/sign-in-with-google.use-case.ts +86 -0
  41. package/src/application/use-cases/auth/sign-in.use-case.ts +77 -0
  42. package/src/application/use-cases/auth/sign-out.use-case.ts +22 -0
  43. package/src/application/use-cases/auth/sign-up.use-case.ts +99 -0
  44. package/src/application/use-cases/index.ts +12 -0
  45. package/src/application/use-cases/user/delete-account.use-case.ts +77 -0
  46. package/src/application/use-cases/user/update-profile.use-case.ts +98 -0
  47. package/src/domain/entities/file.entity.ts +151 -0
  48. package/src/domain/entities/firebase.entity.ts +13 -0
  49. package/src/domain/entities/timestamp.entity.ts +116 -0
  50. package/src/domain/entities/user.entity.ts +193 -0
  51. package/src/domain/errors/auth.errors.ts +115 -0
  52. package/src/domain/errors/repository.errors.ts +121 -0
  53. package/src/domain/index.ts +30 -0
  54. package/src/domain/interfaces/auth.repository.interface.ts +83 -0
  55. package/src/domain/interfaces/file.repository.interface.ts +143 -0
  56. package/src/domain/interfaces/repository.interface.ts +11 -0
  57. package/src/domain/interfaces/user.repository.interface.ts +75 -0
  58. package/src/domain/value-objects/email.vo.ts +105 -0
  59. package/src/domain/value-objects/file-path.vo.ts +184 -0
  60. package/src/domain/value-objects/user-id.vo.ts +87 -0
  61. package/src/index.ts +23 -0
  62. package/src/infrastructure/firebase/auth.adapter.ts +220 -0
  63. package/src/infrastructure/firebase/client.ts +141 -0
  64. package/src/infrastructure/firebase/firestore.adapter.ts +190 -0
  65. package/src/infrastructure/firebase/storage.adapter.ts +323 -0
  66. package/src/infrastructure/index.ts +13 -0
  67. package/src/infrastructure/services/firebase.service.ts +30 -0
  68. package/src/infrastructure/services/firestore.repository.ts +66 -0
  69. package/src/infrastructure/utils/storage.util.ts +46 -0
  70. package/src/presentation/hooks/useAuth.ts +153 -0
  71. package/src/presentation/hooks/useFirebaseAuth.ts +122 -0
  72. package/src/presentation/hooks/useFirestore.ts +125 -0
  73. package/src/presentation/hooks/useStorage.ts +141 -0
  74. package/src/presentation/index.ts +6 -0
  75. package/src/presentation/providers/FirebaseProvider.tsx +40 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Firebase Client
3
+ * @description Firebase initialization and singleton instances
4
+ * Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/services/client.ts
5
+ */
6
+
7
+ import { initializeApp, getApps, FirebaseApp } from 'firebase/app'
8
+ import { initializeAuth, getAuth, Auth, browserLocalPersistence } from 'firebase/auth'
9
+ import { getFirestore, Firestore } from 'firebase/firestore'
10
+ import { getStorage, FirebaseStorage } from 'firebase/storage'
11
+ import { getAnalytics, Analytics } from 'firebase/analytics'
12
+ import { getFunctions, Functions } from 'firebase/functions'
13
+
14
+ const firebaseConfig = {
15
+ apiKey: process.env.VITE_FIREBASE_API_KEY,
16
+ authDomain: process.env.VITE_FIREBASE_AUTH_DOMAIN,
17
+ projectId: process.env.VITE_FIREBASE_PROJECT_ID,
18
+ storageBucket: process.env.VITE_FIREBASE_STORAGE_BUCKET,
19
+ messagingSenderId: process.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
20
+ appId: process.env.VITE_FIREBASE_APP_ID,
21
+ measurementId: process.env.VITE_FIREBASE_MEASUREMENT_ID,
22
+ }
23
+
24
+ // Singleton instances
25
+ let app: FirebaseApp
26
+ let auth: Auth
27
+ let db: Firestore
28
+ let storage: FirebaseStorage
29
+ let functions: Functions
30
+ let analytics: Analytics | null = null
31
+
32
+ /**
33
+ * Initialize Firebase App
34
+ */
35
+ export function initializeFirebase(): FirebaseApp {
36
+ if (!getApps().length) {
37
+ app = initializeApp(firebaseConfig)
38
+ } else {
39
+ app = getApps()[0]
40
+ }
41
+ return app
42
+ }
43
+
44
+ /**
45
+ * Get Firebase App instance
46
+ */
47
+ export function getFirebaseApp(): FirebaseApp {
48
+ return app || initializeFirebase()
49
+ }
50
+
51
+ /**
52
+ * Get Firebase Auth instance
53
+ */
54
+ export function getFirebaseAuth(): Auth {
55
+ if (!auth) {
56
+ const firebaseApp = getFirebaseApp()
57
+ if (typeof window !== 'undefined') {
58
+ try {
59
+ auth = getAuth(firebaseApp)
60
+ } catch (e) {
61
+ console.warn('getAuth failed, trying initializeAuth...', e)
62
+ auth = initializeAuth(firebaseApp, {
63
+ persistence: browserLocalPersistence,
64
+ })
65
+ }
66
+ } else {
67
+ auth = getAuth(firebaseApp)
68
+ }
69
+ }
70
+ return auth
71
+ }
72
+
73
+ /**
74
+ * Get Firestore instance
75
+ */
76
+ export function getFirebaseDB(): Firestore {
77
+ if (!db) {
78
+ db = getFirestore(getFirebaseApp())
79
+ }
80
+ return db
81
+ }
82
+
83
+ /**
84
+ * Get Firebase Storage instance
85
+ */
86
+ export function getFirebaseStorage(): FirebaseStorage {
87
+ if (!storage) {
88
+ storage = getStorage(getFirebaseApp())
89
+ }
90
+ return storage
91
+ }
92
+
93
+ /**
94
+ * Get Firebase Functions instance
95
+ */
96
+ export function getFirebaseFunctions(): Functions {
97
+ if (!functions) {
98
+ functions = getFunctions(getFirebaseApp())
99
+ }
100
+ return functions
101
+ }
102
+
103
+ /**
104
+ * Get Firebase Analytics instance
105
+ */
106
+ export function getFirebaseAnalytics(): Analytics | null {
107
+ if (!analytics && typeof window !== 'undefined') {
108
+ analytics = getAnalytics(getFirebaseApp())
109
+ }
110
+ return analytics
111
+ }
112
+
113
+ /**
114
+ * Firebase Instances
115
+ * All Firebase service instances
116
+ */
117
+ export interface FirebaseInstances {
118
+ app: FirebaseApp
119
+ auth: Auth
120
+ db: Firestore
121
+ storage: FirebaseStorage
122
+ functions: Functions
123
+ analytics: Analytics | null
124
+ }
125
+
126
+ /**
127
+ * Get all Firebase instances
128
+ */
129
+ export function getFirebaseInstances(): FirebaseInstances {
130
+ return {
131
+ app: getFirebaseApp(),
132
+ auth: getFirebaseAuth(),
133
+ db: getFirebaseDB(),
134
+ storage: getFirebaseStorage(),
135
+ functions: getFirebaseFunctions(),
136
+ analytics: getFirebaseAnalytics(),
137
+ }
138
+ }
139
+
140
+ // Export singleton instances for convenience
141
+ export { app, auth, db, storage, functions, analytics }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Firestore Adapter
3
+ * @description Firebase Firestore implementation of IUserRepository
4
+ * Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/services/firestore.ts
5
+ */
6
+
7
+ import {
8
+ doc,
9
+ getDoc,
10
+ setDoc,
11
+ updateDoc,
12
+ deleteDoc,
13
+ query,
14
+ where,
15
+ onSnapshot,
16
+ collection,
17
+ getDocs,
18
+ } from 'firebase/firestore'
19
+ import { getFirebaseDB } from './client'
20
+ import type { IUserRepository } from '../../domain/interfaces/user.repository.interface'
21
+ import type { User } from '../../domain/entities/user.entity'
22
+ import { createRepositoryError, RepositoryErrorCode } from '../../domain/errors/repository.errors'
23
+
24
+ export class FirestoreAdapter implements IUserRepository {
25
+ private get db() {
26
+ return getFirebaseDB()
27
+ }
28
+
29
+ private readonly USERS_COLLECTION = 'users'
30
+
31
+ async getUser(userId: string): Promise<User | null> {
32
+ try {
33
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
34
+ const snap = await getDoc(docRef)
35
+
36
+ if (!snap.exists()) {
37
+ return null
38
+ }
39
+
40
+ return snap.data() as User
41
+ } catch (error) {
42
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'User not found', error)
43
+ }
44
+ }
45
+
46
+ async getUserByEmail(email: string): Promise<User | null> {
47
+ try {
48
+ const q = query(collection(this.db, this.USERS_COLLECTION), where('profile.email', '==', email))
49
+ const snap = await getDocs(q)
50
+
51
+ if (snap.empty) {
52
+ return null
53
+ }
54
+
55
+ const doc = snap.docs[0]
56
+ return doc.data() as User
57
+ } catch (error) {
58
+ throw createRepositoryError(RepositoryErrorCode.QUERY_FAILED, 'Failed to query user', error)
59
+ }
60
+ }
61
+
62
+ async createUser(userId: string, data: Partial<User>): Promise<void> {
63
+ try {
64
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
65
+ await setDoc(docRef, data, { merge: true })
66
+ } catch (error) {
67
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_INVALID, 'Failed to create user', error)
68
+ }
69
+ }
70
+
71
+ async updateUser(userId: string, data: Partial<User>): Promise<void> {
72
+ try {
73
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
74
+ await updateDoc(docRef, {
75
+ ...data,
76
+ 'profile.updatedAt': Date.now(),
77
+ } as any)
78
+ } catch (error) {
79
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to update user', error)
80
+ }
81
+ }
82
+
83
+ async deleteUser(userId: string): Promise<void> {
84
+ try {
85
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
86
+ await deleteDoc(docRef)
87
+ } catch (error) {
88
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to delete user', error)
89
+ }
90
+ }
91
+
92
+ async updateProfile(
93
+ userId: string,
94
+ updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL' | 'phoneNumber'>>
95
+ ): Promise<void> {
96
+ try {
97
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
98
+ const updateData: any = {
99
+ 'profile.updatedAt': Date.now(),
100
+ }
101
+
102
+ if (updates.displayName !== undefined) {
103
+ updateData['profile.displayName'] = updates.displayName
104
+ }
105
+ if (updates.photoURL !== undefined) {
106
+ updateData['profile.photoURL'] = updates.photoURL
107
+ }
108
+ if (updates.phoneNumber !== undefined) {
109
+ updateData['profile.phoneNumber'] = updates.phoneNumber
110
+ }
111
+
112
+ await updateDoc(docRef, updateData)
113
+ } catch (error) {
114
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to update profile', error)
115
+ }
116
+ }
117
+
118
+ async updateSettings(userId: string, settings: Partial<User['settings']>): Promise<void> {
119
+ try {
120
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
121
+ await updateDoc(docRef, {
122
+ settings: {
123
+ ...settings,
124
+ updatedAt: Date.now(),
125
+ },
126
+ } as any)
127
+ } catch (error) {
128
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to update settings', error)
129
+ }
130
+ }
131
+
132
+ async updateSubscription(userId: string, subscription: Partial<User['subscription']>): Promise<void> {
133
+ try {
134
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
135
+ await updateDoc(docRef, {
136
+ subscription: {
137
+ ...subscription,
138
+ updatedAt: Date.now(),
139
+ },
140
+ } as any)
141
+ } catch (error) {
142
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to update subscription', error)
143
+ }
144
+ }
145
+
146
+ async updateLastLogin(userId: string): Promise<void> {
147
+ try {
148
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
149
+ await updateDoc(docRef, {
150
+ 'profile.lastLoginAt': Date.now(),
151
+ } as any)
152
+ } catch (error) {
153
+ throw createRepositoryError(RepositoryErrorCode.DOCUMENT_NOT_FOUND, 'Failed to update last login', error)
154
+ }
155
+ }
156
+
157
+ async queryUsers(constraints: any[]): Promise<User[]> {
158
+ try {
159
+ const q = query(collection(this.db, this.USERS_COLLECTION), ...constraints)
160
+ const snap = await getDocs(q)
161
+ return snap.docs.map((doc) => doc.data() as User)
162
+ } catch (error) {
163
+ throw createRepositoryError(RepositoryErrorCode.QUERY_FAILED, 'Failed to query users', error)
164
+ }
165
+ }
166
+
167
+ subscribeToUser(
168
+ userId: string,
169
+ callback: (user: User | null) => void,
170
+ onError?: (error: Error) => void
171
+ ): () => void {
172
+ const docRef = doc(this.db, this.USERS_COLLECTION, userId)
173
+
174
+ const unsubscribe = onSnapshot(
175
+ docRef,
176
+ (snap) => {
177
+ if (snap.exists()) {
178
+ callback(snap.data() as User)
179
+ } else {
180
+ callback(null)
181
+ }
182
+ },
183
+ (error) => {
184
+ onError?.(error as Error)
185
+ }
186
+ )
187
+
188
+ return unsubscribe
189
+ }
190
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Storage Adapter
3
+ * @description Firebase Storage implementation of IFileRepository
4
+ * Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/services/storage.ts
5
+ */
6
+
7
+ import {
8
+ ref,
9
+ uploadBytes,
10
+ uploadBytesResumable,
11
+ getDownloadURL,
12
+ deleteObject,
13
+ listAll,
14
+ getMetadata,
15
+ } from 'firebase/storage'
16
+ import { getFirebaseStorage } from './client'
17
+ import type { IFileRepository } from '../../domain/interfaces/file.repository.interface'
18
+ import type {
19
+ FileMetadata,
20
+ UploadProgress,
21
+ UploadResult,
22
+ UploadOptions,
23
+ StorageStats,
24
+ } from '../../domain/entities/file.entity'
25
+ import { createRepositoryError, RepositoryErrorCode } from '../../domain/errors/repository.errors'
26
+
27
+ export class StorageAdapter implements IFileRepository {
28
+ private get storage() {
29
+ return getFirebaseStorage()
30
+ }
31
+
32
+ // Upload Methods
33
+
34
+ async uploadFile(
35
+ userId: string,
36
+ path: string,
37
+ file: File | Blob,
38
+ options?: UploadOptions
39
+ ): Promise<UploadResult> {
40
+ const storageRef = ref(this.storage, `users/${userId}/${path}`)
41
+ const uploadTask = uploadBytesResumable(storageRef, file)
42
+
43
+ return new Promise((resolve, reject) => {
44
+ uploadTask.on(
45
+ 'state_changed',
46
+ (snapshot) => {
47
+ if (options?.onProgress) {
48
+ const progress: UploadProgress = {
49
+ bytesTransferred: snapshot.bytesTransferred,
50
+ totalBytes: snapshot.totalBytes,
51
+ progress: (snapshot.bytesTransferred / snapshot.totalBytes) * 100,
52
+ state: 'running',
53
+ }
54
+ options.onProgress(progress)
55
+ }
56
+ },
57
+ (error) => reject(createRepositoryError(RepositoryErrorCode.STORAGE_ERROR, 'Upload failed', error)),
58
+ async () => {
59
+ const downloadURL = await getDownloadURL(uploadTask.snapshot.ref)
60
+ const metadata = await getMetadata(uploadTask.snapshot.ref)
61
+
62
+ resolve({
63
+ id: uploadTask.snapshot.ref.name,
64
+ name: metadata.name || uploadTask.snapshot.ref.name,
65
+ fullPath: metadata.fullPath || uploadTask.snapshot.ref.fullPath,
66
+ downloadURL,
67
+ contentType: metadata.contentType || '',
68
+ size: metadata.size || 0,
69
+ createdAt: metadata.timeCreated ? new Date(metadata.timeCreated).getTime() : Date.now(),
70
+ })
71
+ }
72
+ )
73
+ })
74
+ }
75
+
76
+ async uploadImage(userId: string, file: File, filename?: string): Promise<UploadResult> {
77
+ const name = filename || `${Date.now()}_${file.name}`
78
+ return this.uploadFile(userId, `images/${name}`, file)
79
+ }
80
+
81
+ async uploadVideo(userId: string, file: File, filename?: string): Promise<UploadResult> {
82
+ const name = filename || `${Date.now()}_${file.name}`
83
+ return this.uploadFile(userId, `videos/${name}`, file)
84
+ }
85
+
86
+ async uploadDocument(userId: string, file: File, filename?: string): Promise<UploadResult> {
87
+ const name = filename || `${Date.now()}_${file.name}`
88
+ return this.uploadFile(userId, `documents/${name}`, file)
89
+ }
90
+
91
+ async uploadProfilePicture(userId: string, file: File): Promise<UploadResult> {
92
+ const storageRef = ref(this.storage, `users/${userId}/profile/${Date.now()}_${file.name}`)
93
+ await uploadBytes(storageRef, file)
94
+ const downloadURL = await getDownloadURL(storageRef)
95
+
96
+ return {
97
+ id: storageRef.name,
98
+ name: file.name,
99
+ fullPath: storageRef.fullPath,
100
+ downloadURL,
101
+ contentType: file.type,
102
+ size: file.size,
103
+ createdAt: Date.now(),
104
+ }
105
+ }
106
+
107
+ // Download Methods
108
+
109
+ async getDownloadURL(path: string): Promise<string> {
110
+ try {
111
+ const storageRef = ref(this.storage, path)
112
+ return await getDownloadURL(storageRef)
113
+ } catch (error) {
114
+ throw createRepositoryError(RepositoryErrorCode.FILE_NOT_FOUND, 'File not found', error)
115
+ }
116
+ }
117
+
118
+ // Delete Methods
119
+
120
+ async deleteFile(path: string): Promise<void> {
121
+ try {
122
+ const storageRef = ref(this.storage, path)
123
+ await deleteObject(storageRef)
124
+ } catch (error) {
125
+ throw createRepositoryError(RepositoryErrorCode.FILE_NOT_FOUND, 'File not found', error)
126
+ }
127
+ }
128
+
129
+ async deleteUserFiles(userId: string): Promise<void> {
130
+ try {
131
+ const userRef = ref(this.storage, `users/${userId}`)
132
+ const result = await listAll(userRef)
133
+
134
+ // Delete all files in all prefixes
135
+ for (const prefix of result.prefixes) {
136
+ const prefixResult = await listAll(prefix)
137
+ await Promise.all(prefixResult.items.map((item) => deleteObject(item)))
138
+ }
139
+
140
+ // Delete all files in root
141
+ await Promise.all(result.items.map((item) => deleteObject(item)))
142
+ } catch (error) {
143
+ throw createRepositoryError(RepositoryErrorCode.STORAGE_ERROR, 'Failed to delete user files', error)
144
+ }
145
+ }
146
+
147
+ async deleteImage(userId: string, filename: string): Promise<void> {
148
+ await this.deleteFile(`users/${userId}/images/${filename}`)
149
+ }
150
+
151
+ async deleteVideo(userId: string, filename: string): Promise<void> {
152
+ await this.deleteFile(`users/${userId}/videos/${filename}`)
153
+ }
154
+
155
+ async deleteProfilePicture(userId: string, filename: string): Promise<void> {
156
+ await this.deleteFile(`users/${userId}/profile/${filename}`)
157
+ }
158
+
159
+ // List Methods
160
+
161
+ async listUserFiles(userId: string, path?: string): Promise<string[]> {
162
+ const userRef = ref(this.storage, path ? `users/${userId}/${path}` : `users/${userId}`)
163
+ const result = await listAll(userRef)
164
+
165
+ const urls = await Promise.all(result.items.map((item) => getDownloadURL(item)))
166
+ return urls
167
+ }
168
+
169
+ async listUserImages(userId: string): Promise<string[]> {
170
+ return this.listUserFiles(userId, 'images')
171
+ }
172
+
173
+ async listUserVideos(userId: string): Promise<string[]> {
174
+ return this.listUserFiles(userId, 'videos')
175
+ }
176
+
177
+ // Metadata
178
+
179
+ async getFileMetadata(path: string): Promise<FileMetadata> {
180
+ try {
181
+ const storageRef = ref(this.storage, path)
182
+ const metadata = await getMetadata(storageRef)
183
+
184
+ return {
185
+ id: storageRef.name,
186
+ name: metadata.name,
187
+ fullPath: metadata.fullPath,
188
+ contentType: metadata.contentType || 'application/octet-stream',
189
+ size: metadata.size,
190
+ createdAt: metadata.timeCreated ? new Date(metadata.timeCreated).getTime() : Date.now(),
191
+ updatedAt: metadata.updated ? new Date(metadata.updated).getTime() : Date.now(),
192
+ userId: this.extractUserId(path) || 'unknown',
193
+ type: this.extractFileType(metadata.contentType || ''),
194
+ }
195
+ } catch (error) {
196
+ throw createRepositoryError(RepositoryErrorCode.FILE_NOT_FOUND, 'File not found', error)
197
+ }
198
+ }
199
+
200
+ async queryFiles(userId: string, _filters?: any): Promise<{ files: FileMetadata[]; totalCount: number; hasMore: boolean }> {
201
+ const userRef = ref(this.storage, `users/${userId}`)
202
+ const result = await listAll(userRef)
203
+
204
+ const files = await Promise.all(
205
+ result.items.map(async (item) => {
206
+ const metadata = await getMetadata(item)
207
+ return {
208
+ id: item.name,
209
+ name: metadata.name,
210
+ fullPath: metadata.fullPath,
211
+ contentType: metadata.contentType || 'application/octet-stream',
212
+ size: metadata.size,
213
+ createdAt: metadata.timeCreated ? new Date(metadata.timeCreated).getTime() : Date.now(),
214
+ updatedAt: metadata.updated ? new Date(metadata.updated).getTime() : Date.now(),
215
+ userId,
216
+ type: this.extractFileType(metadata.contentType || ''),
217
+ }
218
+ })
219
+ )
220
+
221
+ return {
222
+ files,
223
+ totalCount: files.length,
224
+ hasMore: false,
225
+ }
226
+ }
227
+
228
+ async getStorageStats(userId: string): Promise<StorageStats> {
229
+ const { files, totalCount } = await this.queryFiles(userId)
230
+
231
+ const stats: StorageStats = {
232
+ totalFiles: totalCount,
233
+ totalSize: files.reduce((sum, file) => sum + file.size, 0),
234
+ filesByType: {
235
+ image: 0,
236
+ video: 0,
237
+ audio: 0,
238
+ document: 0,
239
+ other: 0,
240
+ },
241
+ filesByCategory: {
242
+ profile: 0,
243
+ content: 0,
244
+ document: 0,
245
+ attachment: 0,
246
+ backup: 0,
247
+ },
248
+ }
249
+
250
+ files.forEach((file) => {
251
+ stats.filesByType[file.type]++
252
+ stats.lastUploadAt = Math.max(stats.lastUploadAt || 0, file.createdAt)
253
+ })
254
+
255
+ return stats
256
+ }
257
+
258
+ // Validation
259
+
260
+ validateFile(file: File, options?: { maxSizeBytes?: number; maxSizeMB?: number; allowedTypes?: string[] }): boolean {
261
+ const maxSizeBytes = options?.maxSizeBytes || (options?.maxSizeMB ? options.maxSizeMB * 1024 * 1024 : 10 * 1024 * 1024)
262
+
263
+ if (file.size > maxSizeBytes) {
264
+ return false
265
+ }
266
+
267
+ if (options?.allowedTypes && !options.allowedTypes.includes(file.type)) {
268
+ return false
269
+ }
270
+
271
+ return true
272
+ }
273
+
274
+ isImageFile(file: File): boolean {
275
+ return file.type.startsWith('image/')
276
+ }
277
+
278
+ isVideoFile(file: File): boolean {
279
+ return file.type.startsWith('video/')
280
+ }
281
+
282
+ isDocumentFile(file: File): boolean {
283
+ const docTypes = [
284
+ 'application/pdf',
285
+ 'application/msword',
286
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
287
+ 'application/vnd.ms-excel',
288
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
289
+ 'text/plain',
290
+ ]
291
+ return docTypes.includes(file.type)
292
+ }
293
+
294
+ // Utility Methods
295
+
296
+ generateUniqueFilename(originalName: string): string {
297
+ const timestamp = Date.now()
298
+ const random = Math.random().toString(36).substring(2, 8)
299
+ const extension = this.getFileExtension(originalName)
300
+ return `${timestamp}_${random}.${extension}`
301
+ }
302
+
303
+ getFileExtension(filename: string): string {
304
+ return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2)
305
+ }
306
+
307
+ // Helper Methods
308
+
309
+ private extractUserId(path: string): string {
310
+ const match = path.match(/users\/([^\/]+)/)
311
+ return match ? match[1] : ''
312
+ }
313
+
314
+ private extractFileType(contentType: string): 'image' | 'video' | 'audio' | 'document' | 'other' {
315
+ if (contentType.startsWith('image/')) return 'image'
316
+ if (contentType.startsWith('video/')) return 'video'
317
+ if (contentType.startsWith('audio/')) return 'audio'
318
+ if (contentType.includes('pdf') || contentType.includes('document') || contentType.includes('text')) {
319
+ return 'document'
320
+ }
321
+ return 'other'
322
+ }
323
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Infrastructure Layer Public API
3
+ * @description Exports all Firebase adapters and repositories
4
+ */
5
+
6
+ // Firebase
7
+ export * from './firebase/client'
8
+ export * from './firebase/auth.adapter'
9
+ export * from './firebase/firestore.adapter'
10
+ export * from './firebase/storage.adapter'
11
+
12
+ // Utils
13
+ export * from './utils/storage.util'
@@ -0,0 +1,30 @@
1
+ import { initializeApp, getApps, type FirebaseOptions, type FirebaseApp } from 'firebase/app';
2
+ import { getAuth, type Auth } from 'firebase/auth';
3
+ import { getFirestore, type Firestore } from 'firebase/firestore';
4
+ import { getStorage, type FirebaseStorage } from 'firebase/storage';
5
+ import { getFunctions, type Functions } from 'firebase/functions';
6
+
7
+ /**
8
+ * Firebase Service Infrastructure
9
+ * @description Initialization and instance management for Firebase
10
+ */
11
+
12
+ export interface FirebaseInstances {
13
+ app: FirebaseApp;
14
+ auth: Auth;
15
+ db: Firestore;
16
+ storage: FirebaseStorage;
17
+ functions: Functions;
18
+ }
19
+
20
+ export function initializeFirebase(config: FirebaseOptions, appName?: string): FirebaseInstances {
21
+ const existing = getApps().find((a) => a.name === (appName ?? '[DEFAULT]'));
22
+ const app = existing ?? initializeApp(config, appName);
23
+ return {
24
+ app,
25
+ auth: getAuth(app),
26
+ db: getFirestore(app),
27
+ storage: getStorage(app),
28
+ functions: getFunctions(app),
29
+ };
30
+ }