squarefi-bff-api-module 1.30.10 → 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.
- package/CHANGELOG.md +296 -1
- package/FIXED_RLS_ERROR.md +146 -0
- package/QUICK_TEST.md +127 -0
- package/README.md +87 -10
- package/STORAGE_MODULE_SUMMARY.md +228 -0
- package/TEST_INSTRUCTIONS.md +122 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFileUpload.d.ts +18 -3
- package/dist/hooks/useFileUpload.js +19 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/fileStorage.d.ts +8 -4
- package/dist/utils/fileStorage.js +8 -4
- package/docs/AUTH_TOKEN_USAGE.md +290 -0
- package/docs/BACKEND_SERVICE_URL.md +334 -0
- package/docs/FRONTEND_STORAGE_GUIDE.md +529 -0
- package/docs/READY_TO_USE_COMPONENT.tsx +395 -0
- package/docs/STORAGE_MODULE.md +490 -0
- package/docs/STORAGE_QUICK_START.md +76 -0
- package/package.json +1 -1
- package/scripts/supabase-storage-setup.sql +223 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFileUpload.ts +129 -0
- package/src/index.ts +1 -0
- package/src/utils/fileStorage.ts +367 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
-- ============================================
|
|
2
|
+
-- Supabase Storage Setup
|
|
3
|
+
-- ============================================
|
|
4
|
+
-- Создание бакетов и настройка политик безопасности для файлового хранилища
|
|
5
|
+
-- Политики обеспечивают доступ к файлам только для их создателей и суперадминов
|
|
6
|
+
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- 1. Создание бакетов
|
|
9
|
+
-- ============================================
|
|
10
|
+
|
|
11
|
+
-- Основной бакет для пользовательских файлов
|
|
12
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
13
|
+
VALUES ('user-files', 'user-files', false)
|
|
14
|
+
ON CONFLICT (id) DO NOTHING;
|
|
15
|
+
|
|
16
|
+
-- Бакет для документов
|
|
17
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
18
|
+
VALUES ('documents', 'documents', false)
|
|
19
|
+
ON CONFLICT (id) DO NOTHING;
|
|
20
|
+
|
|
21
|
+
-- Бакет для изображений
|
|
22
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
23
|
+
VALUES ('images', 'images', false)
|
|
24
|
+
ON CONFLICT (id) DO NOTHING;
|
|
25
|
+
|
|
26
|
+
-- ============================================
|
|
27
|
+
-- 2. Функция для проверки прав суперадмина
|
|
28
|
+
-- ============================================
|
|
29
|
+
-- Создаем функцию для проверки, является ли пользователь суперадмином
|
|
30
|
+
-- Примечание: измените логику в зависимости от вашей схемы пользователей
|
|
31
|
+
|
|
32
|
+
CREATE OR REPLACE FUNCTION public.is_super_admin(user_id uuid)
|
|
33
|
+
RETURNS boolean AS $$
|
|
34
|
+
BEGIN
|
|
35
|
+
-- Пример: проверяем наличие записи в таблице profiles с ролью 'super_admin'
|
|
36
|
+
-- Измените эту логику в соответствии с вашей схемой данных
|
|
37
|
+
RETURN EXISTS (
|
|
38
|
+
SELECT 1
|
|
39
|
+
FROM public.profiles
|
|
40
|
+
WHERE id = user_id
|
|
41
|
+
AND role = 'super_admin'
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
-- Альтернативный вариант: проверка по email
|
|
45
|
+
-- RETURN EXISTS (
|
|
46
|
+
-- SELECT 1
|
|
47
|
+
-- FROM auth.users
|
|
48
|
+
-- WHERE id = user_id
|
|
49
|
+
-- AND email IN ('admin@example.com', 'superadmin@example.com')
|
|
50
|
+
-- );
|
|
51
|
+
END;
|
|
52
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
53
|
+
|
|
54
|
+
-- ============================================
|
|
55
|
+
-- 3. Политики для бакета 'user-files'
|
|
56
|
+
-- ============================================
|
|
57
|
+
|
|
58
|
+
-- Пользователи могут загружать файлы только в свою папку
|
|
59
|
+
CREATE POLICY "Users can upload files to their own folder"
|
|
60
|
+
ON storage.objects
|
|
61
|
+
FOR INSERT
|
|
62
|
+
TO authenticated
|
|
63
|
+
WITH CHECK (
|
|
64
|
+
bucket_id = 'user-files'
|
|
65
|
+
AND (storage.foldername(name))[1] = auth.uid()::text
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
-- Пользователи могут просматривать свои файлы или если они суперадмины
|
|
69
|
+
CREATE POLICY "Users can view their own files or super admins can view all"
|
|
70
|
+
ON storage.objects
|
|
71
|
+
FOR SELECT
|
|
72
|
+
TO authenticated
|
|
73
|
+
USING (
|
|
74
|
+
bucket_id = 'user-files'
|
|
75
|
+
AND (
|
|
76
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
77
|
+
OR public.is_super_admin(auth.uid())
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
-- Пользователи могут обновлять свои файлы или суперадмины могут обновлять любые
|
|
82
|
+
CREATE POLICY "Users can update their own files or super admins can update all"
|
|
83
|
+
ON storage.objects
|
|
84
|
+
FOR UPDATE
|
|
85
|
+
TO authenticated
|
|
86
|
+
USING (
|
|
87
|
+
bucket_id = 'user-files'
|
|
88
|
+
AND (
|
|
89
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
90
|
+
OR public.is_super_admin(auth.uid())
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
-- Пользователи могут удалять свои файлы или суперадмины могут удалять любые
|
|
95
|
+
CREATE POLICY "Users can delete their own files or super admins can delete all"
|
|
96
|
+
ON storage.objects
|
|
97
|
+
FOR DELETE
|
|
98
|
+
TO authenticated
|
|
99
|
+
USING (
|
|
100
|
+
bucket_id = 'user-files'
|
|
101
|
+
AND (
|
|
102
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
103
|
+
OR public.is_super_admin(auth.uid())
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
-- ============================================
|
|
108
|
+
-- 4. Политики для бакета 'documents'
|
|
109
|
+
-- ============================================
|
|
110
|
+
|
|
111
|
+
-- Аналогичные политики для бакета documents
|
|
112
|
+
CREATE POLICY "Users can upload documents to their own folder"
|
|
113
|
+
ON storage.objects
|
|
114
|
+
FOR INSERT
|
|
115
|
+
TO authenticated
|
|
116
|
+
WITH CHECK (
|
|
117
|
+
bucket_id = 'documents'
|
|
118
|
+
AND (storage.foldername(name))[1] = auth.uid()::text
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
CREATE POLICY "Users can view their own documents or super admins can view all"
|
|
122
|
+
ON storage.objects
|
|
123
|
+
FOR SELECT
|
|
124
|
+
TO authenticated
|
|
125
|
+
USING (
|
|
126
|
+
bucket_id = 'documents'
|
|
127
|
+
AND (
|
|
128
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
129
|
+
OR public.is_super_admin(auth.uid())
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE POLICY "Users can update their own documents or super admins can update all"
|
|
134
|
+
ON storage.objects
|
|
135
|
+
FOR UPDATE
|
|
136
|
+
TO authenticated
|
|
137
|
+
USING (
|
|
138
|
+
bucket_id = 'documents'
|
|
139
|
+
AND (
|
|
140
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
141
|
+
OR public.is_super_admin(auth.uid())
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
CREATE POLICY "Users can delete their own documents or super admins can delete all"
|
|
146
|
+
ON storage.objects
|
|
147
|
+
FOR DELETE
|
|
148
|
+
TO authenticated
|
|
149
|
+
USING (
|
|
150
|
+
bucket_id = 'documents'
|
|
151
|
+
AND (
|
|
152
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
153
|
+
OR public.is_super_admin(auth.uid())
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
-- ============================================
|
|
158
|
+
-- 5. Политики для бакета 'images'
|
|
159
|
+
-- ============================================
|
|
160
|
+
|
|
161
|
+
-- Аналогичные политики для бакета images
|
|
162
|
+
CREATE POLICY "Users can upload images to their own folder"
|
|
163
|
+
ON storage.objects
|
|
164
|
+
FOR INSERT
|
|
165
|
+
TO authenticated
|
|
166
|
+
WITH CHECK (
|
|
167
|
+
bucket_id = 'images'
|
|
168
|
+
AND (storage.foldername(name))[1] = auth.uid()::text
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
CREATE POLICY "Users can view their own images or super admins can view all"
|
|
172
|
+
ON storage.objects
|
|
173
|
+
FOR SELECT
|
|
174
|
+
TO authenticated
|
|
175
|
+
USING (
|
|
176
|
+
bucket_id = 'images'
|
|
177
|
+
AND (
|
|
178
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
179
|
+
OR public.is_super_admin(auth.uid())
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
CREATE POLICY "Users can update their own images or super admins can update all"
|
|
184
|
+
ON storage.objects
|
|
185
|
+
FOR UPDATE
|
|
186
|
+
TO authenticated
|
|
187
|
+
USING (
|
|
188
|
+
bucket_id = 'images'
|
|
189
|
+
AND (
|
|
190
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
191
|
+
OR public.is_super_admin(auth.uid())
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE POLICY "Users can delete their own images or super admins can delete all"
|
|
196
|
+
ON storage.objects
|
|
197
|
+
FOR DELETE
|
|
198
|
+
TO authenticated
|
|
199
|
+
USING (
|
|
200
|
+
bucket_id = 'images'
|
|
201
|
+
AND (
|
|
202
|
+
(storage.foldername(name))[1] = auth.uid()::text
|
|
203
|
+
OR public.is_super_admin(auth.uid())
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
-- ============================================
|
|
208
|
+
-- 6. Включение RLS
|
|
209
|
+
-- ============================================
|
|
210
|
+
|
|
211
|
+
-- Убедитесь, что RLS включен для таблицы storage.objects
|
|
212
|
+
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
|
|
213
|
+
|
|
214
|
+
-- ============================================
|
|
215
|
+
-- ПРИМЕЧАНИЯ
|
|
216
|
+
-- ============================================
|
|
217
|
+
-- 1. Замените функцию is_super_admin() на вашу реализацию проверки прав
|
|
218
|
+
-- 2. Структура папок: {userId}/{fileName}
|
|
219
|
+
-- 3. Политики работают только для authenticated пользователей
|
|
220
|
+
-- 4. Для публичного доступа нужно создать отдельные бакеты с public = true
|
|
221
|
+
-- 5. Подписанные URL (signed URLs) работают независимо от RLS политик
|
|
222
|
+
|
|
223
|
+
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { uploadFile, UploadFileOptions, UploadFileResult } from '../utils/fileStorage';
|
|
3
|
+
|
|
4
|
+
interface UseFileUploadOptions {
|
|
5
|
+
bucket: string;
|
|
6
|
+
folder?: string; // Папка внутри бакета (например, 'documents', 'images/avatars'). Создается автоматически, если не существует
|
|
7
|
+
authToken?: string; // JWT token для авторизации
|
|
8
|
+
onSuccess?: (result: UploadFileResult) => void;
|
|
9
|
+
onError?: (error: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface UseFileUploadReturn {
|
|
13
|
+
upload: (file: File, fileName?: string) => Promise<UploadFileResult>;
|
|
14
|
+
uploading: boolean;
|
|
15
|
+
progress: number;
|
|
16
|
+
error: string | null;
|
|
17
|
+
result: UploadFileResult | null;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* React хук для загрузки файлов в Supabase Storage
|
|
23
|
+
*
|
|
24
|
+
* Папки создаются автоматически при загрузке файла, если их не существует.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* // Загрузка в корень бакета
|
|
29
|
+
* const { upload, uploading, error, result } = useFileUpload({
|
|
30
|
+
* bucket: 'user-files',
|
|
31
|
+
* onSuccess: (result) => console.log('Загружено:', result.path),
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Загрузка в конкретную папку (папка создастся автоматически)
|
|
35
|
+
* const { upload } = useFileUpload({
|
|
36
|
+
* bucket: 'documents',
|
|
37
|
+
* folder: 'invoices', // файл будет загружен в invoices/
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Загрузка во вложенную папку (все папки создадутся автоматически)
|
|
41
|
+
* const { upload } = useFileUpload({
|
|
42
|
+
* bucket: 'images',
|
|
43
|
+
* folder: 'avatars/2024', // файл будет загружен в avatars/2024/
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
47
|
+
* const file = e.target.files?.[0];
|
|
48
|
+
* if (file) await upload(file);
|
|
49
|
+
* };
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const useFileUpload = (options: UseFileUploadOptions): UseFileUploadReturn => {
|
|
53
|
+
const { bucket, folder, authToken, onSuccess, onError } = options;
|
|
54
|
+
|
|
55
|
+
const [uploading, setUploading] = useState(false);
|
|
56
|
+
const [progress, setProgress] = useState(0);
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
const [result, setResult] = useState<UploadFileResult | null>(null);
|
|
59
|
+
|
|
60
|
+
const upload = useCallback(
|
|
61
|
+
async (file: File, customFileName?: string): Promise<UploadFileResult> => {
|
|
62
|
+
setUploading(true);
|
|
63
|
+
setProgress(0);
|
|
64
|
+
setError(null);
|
|
65
|
+
setResult(null);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Симулируем прогресс (Supabase не предоставляет реальный progress)
|
|
69
|
+
const progressInterval = setInterval(() => {
|
|
70
|
+
setProgress((prev) => Math.min(prev + 10, 90));
|
|
71
|
+
}, 100);
|
|
72
|
+
|
|
73
|
+
const uploadOptions: UploadFileOptions = {
|
|
74
|
+
file,
|
|
75
|
+
fileName: customFileName || `${Date.now()}-${file.name}`,
|
|
76
|
+
bucket,
|
|
77
|
+
folder,
|
|
78
|
+
contentType: file.type,
|
|
79
|
+
authToken,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const uploadResult = await uploadFile(uploadOptions);
|
|
83
|
+
|
|
84
|
+
clearInterval(progressInterval);
|
|
85
|
+
setProgress(100);
|
|
86
|
+
setResult(uploadResult);
|
|
87
|
+
|
|
88
|
+
if (uploadResult.success) {
|
|
89
|
+
onSuccess?.(uploadResult);
|
|
90
|
+
} else {
|
|
91
|
+
const errorMsg = uploadResult.error || 'Ошибка загрузки файла';
|
|
92
|
+
setError(errorMsg);
|
|
93
|
+
onError?.(errorMsg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return uploadResult;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const errorMsg = err instanceof Error ? err.message : 'Непредвиденная ошибка';
|
|
99
|
+
setError(errorMsg);
|
|
100
|
+
onError?.(errorMsg);
|
|
101
|
+
setProgress(0);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: errorMsg,
|
|
106
|
+
};
|
|
107
|
+
} finally {
|
|
108
|
+
setUploading(false);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[bucket, folder, authToken, onSuccess, onError],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const reset = useCallback(() => {
|
|
115
|
+
setUploading(false);
|
|
116
|
+
setProgress(0);
|
|
117
|
+
setError(null);
|
|
118
|
+
setResult(null);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
upload,
|
|
123
|
+
uploading,
|
|
124
|
+
progress,
|
|
125
|
+
error,
|
|
126
|
+
result,
|
|
127
|
+
reset,
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { squarefi_bff_api_client } from './api';
|
|
2
2
|
export * from './utils/apiClientFactory';
|
|
3
3
|
export * from './utils/tokensFactory';
|
|
4
|
+
export * from './utils/fileStorage';
|
|
4
5
|
export * from './constants';
|
|
5
6
|
export * from './hooks';
|
|
6
7
|
// Also export types if you have any
|