squarefi-bff-api-module 1.30.9 → 1.31.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.
@@ -0,0 +1,367 @@
1
+ import { supabaseClient } from './supabase';
2
+
3
+ /**
4
+ * Типы для работы с файловым хранилищем
5
+ */
6
+ export interface UploadFileOptions {
7
+ file: File | Blob;
8
+ fileName: string;
9
+ bucket: string;
10
+ folder?: string; // Папка внутри бакета (например, 'documents', 'images/avatars'). Создается автоматически, если не существует
11
+ contentType?: string;
12
+ cacheControl?: string;
13
+ upsert?: boolean;
14
+ authToken?: string; // JWT token для авторизации
15
+ }
16
+
17
+ export interface UploadFileResult {
18
+ success: boolean;
19
+ publicUrl?: string;
20
+ signedUrl?: string;
21
+ path?: string;
22
+ error?: string;
23
+ }
24
+
25
+ export interface GetFileUrlOptions {
26
+ path: string;
27
+ bucket: string;
28
+ expiresIn?: number; // в секундах
29
+ authToken?: string; // JWT token для авторизации
30
+ }
31
+
32
+ /**
33
+ * Названия бакетов по умолчанию
34
+ */
35
+ export const DEFAULT_BUCKET = 'user-files';
36
+ export const DOCUMENTS_BUCKET = 'documents';
37
+ export const IMAGES_BUCKET = 'images';
38
+
39
+ /**
40
+ * Загружает файл в Supabase Storage
41
+ * Файл сохраняется по пути: {folder}/{fileName} или {fileName}
42
+ *
43
+ * Папки создаются автоматически при загрузке файла, если их не существует.
44
+ * Можно указывать вложенные папки через слэш: 'images/avatars/2024'
45
+ *
46
+ * @param options - параметры загрузки файла
47
+ * @param options.folder - опциональная папка внутри бакета (например, 'documents', 'images/avatars')
48
+ * @returns результат загрузки с ссылкой на файл
49
+ */
50
+ export const uploadFile = async (options: UploadFileOptions): Promise<UploadFileResult> => {
51
+ const { file, fileName, bucket, folder, contentType, cacheControl = '3600', upsert = false, authToken } = options;
52
+
53
+ if (!supabaseClient) {
54
+ return {
55
+ success: false,
56
+ error: 'Supabase client is not initialized',
57
+ };
58
+ }
59
+
60
+ try {
61
+ // Если передан authToken, создаем клиент с токеном
62
+ let client = supabaseClient;
63
+ if (authToken) {
64
+ const { createClient } = await import('@supabase/supabase-js');
65
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
66
+ global: {
67
+ headers: {
68
+ Authorization: `Bearer ${authToken}`,
69
+ },
70
+ },
71
+ });
72
+ }
73
+
74
+ // Формируем путь к файлу: folder/fileName или fileName
75
+ const filePath = folder ? `${folder}/${fileName}` : fileName;
76
+
77
+ const { data, error } = await client.storage.from(bucket).upload(filePath, file, {
78
+ contentType,
79
+ cacheControl,
80
+ upsert,
81
+ });
82
+
83
+ if (error) {
84
+ console.error('Error uploading file:', error);
85
+ return {
86
+ success: false,
87
+ error: error.message,
88
+ };
89
+ }
90
+
91
+ // Получаем публичный URL
92
+ const { data: urlData } = client.storage.from(bucket).getPublicUrl(data.path);
93
+
94
+ // Получаем подписанный URL (действителен 1 час по умолчанию)
95
+ const { data: signedUrlData, error: signedUrlError } = await client.storage
96
+ .from(bucket)
97
+ .createSignedUrl(data.path, 3600);
98
+
99
+ return {
100
+ success: true,
101
+ publicUrl: urlData.publicUrl,
102
+ signedUrl: signedUrlError ? undefined : signedUrlData.signedUrl,
103
+ path: data.path,
104
+ };
105
+ } catch (error) {
106
+ console.error('Unexpected error uploading file:', error);
107
+ return {
108
+ success: false,
109
+ error: error instanceof Error ? error.message : 'Unknown error',
110
+ };
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Получает подписанный URL для доступа к файлу
116
+ *
117
+ * @param options - параметры получения URL
118
+ * @returns подписанный URL или null при ошибке
119
+ */
120
+ export const getSignedUrl = async (options: GetFileUrlOptions): Promise<string | null> => {
121
+ const { path, bucket = DEFAULT_BUCKET, expiresIn = 3600, authToken } = options;
122
+
123
+ if (!supabaseClient) {
124
+ console.error('Supabase client is not initialized');
125
+ return null;
126
+ }
127
+
128
+ try {
129
+ // Если передан authToken, создаем клиент с токеном
130
+ let client = supabaseClient;
131
+ if (authToken) {
132
+ const { createClient } = await import('@supabase/supabase-js');
133
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
134
+ global: {
135
+ headers: {
136
+ Authorization: `Bearer ${authToken}`,
137
+ },
138
+ },
139
+ });
140
+ }
141
+
142
+ const { data, error } = await client.storage.from(bucket).createSignedUrl(path, expiresIn);
143
+
144
+ if (error) {
145
+ console.error('Error creating signed URL:', error);
146
+ return null;
147
+ }
148
+
149
+ return data.signedUrl;
150
+ } catch (error) {
151
+ console.error('Unexpected error creating signed URL:', error);
152
+ return null;
153
+ }
154
+ };
155
+
156
+ /**
157
+ * Получает публичный URL для файла
158
+ *
159
+ * Для ПРИВАТНЫХ бакетов:
160
+ * - URL постоянный (не истекает)
161
+ * - Требует Authorization header с service role key для доступа
162
+ * - Используется на backend для суперадмина
163
+ *
164
+ * Для ПУБЛИЧНЫХ бакетов:
165
+ * - URL доступен всем без аутентификации
166
+ *
167
+ * @example Backend usage for private buckets:
168
+ * ```typescript
169
+ * const url = getPublicUrl(filePath, bucket);
170
+ *
171
+ * // Access with service role key:
172
+ * fetch(url, {
173
+ * headers: {
174
+ * 'Authorization': `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`
175
+ * }
176
+ * })
177
+ * ```
178
+ *
179
+ * @param path - путь к файлу
180
+ * @param bucket - название бакета
181
+ * @returns постоянный URL
182
+ */
183
+ export const getPublicUrl = (path: string, bucket: string = DEFAULT_BUCKET): string | null => {
184
+ if (!supabaseClient) {
185
+ console.error('Supabase client is not initialized');
186
+ return null;
187
+ }
188
+
189
+ const { data } = supabaseClient.storage.from(bucket).getPublicUrl(path);
190
+ return data.publicUrl;
191
+ };
192
+
193
+ /**
194
+ * Удаляет файл из хранилища
195
+ *
196
+ * @param path - путь к файлу
197
+ * @param bucket - название бакета
198
+ * @param authToken - JWT token для авторизации
199
+ * @returns true при успешном удалении
200
+ */
201
+ export const deleteFile = async (
202
+ path: string,
203
+ bucket: string = DEFAULT_BUCKET,
204
+ authToken?: string,
205
+ ): Promise<boolean> => {
206
+ if (!supabaseClient) {
207
+ console.error('Supabase client is not initialized');
208
+ return false;
209
+ }
210
+
211
+ try {
212
+ let client = supabaseClient;
213
+ if (authToken) {
214
+ const { createClient } = await import('@supabase/supabase-js');
215
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
216
+ global: {
217
+ headers: {
218
+ Authorization: `Bearer ${authToken}`,
219
+ },
220
+ },
221
+ });
222
+ }
223
+
224
+ const { error } = await client.storage.from(bucket).remove([path]);
225
+
226
+ if (error) {
227
+ console.error('Error deleting file:', error);
228
+ return false;
229
+ }
230
+
231
+ return true;
232
+ } catch (error) {
233
+ console.error('Unexpected error deleting file:', error);
234
+ return false;
235
+ }
236
+ };
237
+
238
+ /**
239
+ * Удаляет несколько файлов из хранилища
240
+ *
241
+ * @param paths - массив путей к файлам
242
+ * @param bucket - название бакета
243
+ * @param authToken - JWT token для авторизации
244
+ * @returns true при успешном удалении всех файлов
245
+ */
246
+ export const deleteFiles = async (
247
+ paths: string[],
248
+ bucket: string = DEFAULT_BUCKET,
249
+ authToken?: string,
250
+ ): Promise<boolean> => {
251
+ if (!supabaseClient) {
252
+ console.error('Supabase client is not initialized');
253
+ return false;
254
+ }
255
+
256
+ try {
257
+ let client = supabaseClient;
258
+ if (authToken) {
259
+ const { createClient } = await import('@supabase/supabase-js');
260
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
261
+ global: {
262
+ headers: {
263
+ Authorization: `Bearer ${authToken}`,
264
+ },
265
+ },
266
+ });
267
+ }
268
+
269
+ const { error } = await client.storage.from(bucket).remove(paths);
270
+
271
+ if (error) {
272
+ console.error('Error deleting files:', error);
273
+ return false;
274
+ }
275
+
276
+ return true;
277
+ } catch (error) {
278
+ console.error('Unexpected error deleting files:', error);
279
+ return false;
280
+ }
281
+ };
282
+
283
+ /**
284
+ * Получает список файлов пользователя
285
+ *
286
+ * @param userId - ID пользователя
287
+ * @param bucket - название бакета
288
+ * @param authToken - JWT token для авторизации
289
+ * @returns список файлов или пустой массив при ошибке
290
+ */
291
+ export const listUserFiles = async (userId: string, bucket: string = DEFAULT_BUCKET, authToken?: string) => {
292
+ if (!supabaseClient) {
293
+ console.error('Supabase client is not initialized');
294
+ return [];
295
+ }
296
+
297
+ try {
298
+ let client = supabaseClient;
299
+ if (authToken) {
300
+ const { createClient } = await import('@supabase/supabase-js');
301
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
302
+ global: {
303
+ headers: {
304
+ Authorization: `Bearer ${authToken}`,
305
+ },
306
+ },
307
+ });
308
+ }
309
+
310
+ const { data, error } = await client.storage.from(bucket).list(userId);
311
+
312
+ if (error) {
313
+ console.error('Error listing files:', error);
314
+ return [];
315
+ }
316
+
317
+ return data || [];
318
+ } catch (error) {
319
+ console.error('Unexpected error listing files:', error);
320
+ return [];
321
+ }
322
+ };
323
+
324
+ /**
325
+ * Скачивает файл из хранилища
326
+ *
327
+ * @param path - путь к файлу
328
+ * @param bucket - название бакета
329
+ * @param authToken - JWT token для авторизации
330
+ * @returns Blob файла или null при ошибке
331
+ */
332
+ export const downloadFile = async (
333
+ path: string,
334
+ bucket: string = DEFAULT_BUCKET,
335
+ authToken?: string,
336
+ ): Promise<Blob | null> => {
337
+ if (!supabaseClient) {
338
+ console.error('Supabase client is not initialized');
339
+ return null;
340
+ }
341
+
342
+ try {
343
+ let client = supabaseClient;
344
+ if (authToken) {
345
+ const { createClient } = await import('@supabase/supabase-js');
346
+ client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLIC_KEY!, {
347
+ global: {
348
+ headers: {
349
+ Authorization: `Bearer ${authToken}`,
350
+ },
351
+ },
352
+ });
353
+ }
354
+
355
+ const { data, error } = await client.storage.from(bucket).download(path);
356
+
357
+ if (error) {
358
+ console.error('Error downloading file:', error);
359
+ return null;
360
+ }
361
+
362
+ return data;
363
+ } catch (error) {
364
+ console.error('Unexpected error downloading file:', error);
365
+ return null;
366
+ }
367
+ };