hoangthiencm commited on
Commit
4865997
·
verified ·
1 Parent(s): 8cc19f2

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +268 -260
server.js CHANGED
@@ -1,260 +1,268 @@
1
- // Backend API để upload file lên Google Drive 2TB
2
- // Sử dụng OAuth thay vì Service Account (phù hợp với tài khoản edu)
3
- // Chạy: node server.js hoặc npm start
4
-
5
- require('dotenv').config();
6
- const express = require('express');
7
- const multer = require('multer');
8
- const { google } = require('googleapis');
9
- const path = require('path');
10
- const fs = require('fs');
11
- const cors = require('cors');
12
-
13
- const app = express();
14
- const upload = multer({ dest: 'uploads/' });
15
-
16
- // Middleware - CORS cho phép frontend từ Vercel và local
17
- app.use(cors({
18
- origin: process.env.ALLOWED_ORIGINS ?
19
- process.env.ALLOWED_ORIGINS.split(',') :
20
- '*', // Cho phép tất cả khi không set (chỉ dùng khi dev)
21
- credentials: true
22
- }));
23
- app.use(express.json());
24
-
25
- // OAuth Config - Lấy từ Google Cloud Console
26
- const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || 'YOUR_CLIENT_ID';
27
- const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
28
- const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost/popup.html';
29
-
30
- // Lưu refresh token của admin (tài khoản 2TB)
31
- // Trong production, nên lưu vào database hoặc file bảo mật
32
- let adminRefreshToken = null;
33
-
34
- // Khởi tạo OAuth2 client
35
- const oauth2Client = new google.auth.OAuth2(
36
- GOOGLE_CLIENT_ID,
37
- GOOGLE_CLIENT_SECRET,
38
- REDIRECT_URI
39
- );
40
-
41
- // Lấy access token từ refresh token
42
- async function getAccessToken() {
43
- if (!adminRefreshToken) {
44
- throw new Error('Chưa có refresh token. Vui lòng đăng nhập Admin trước.');
45
- }
46
-
47
- oauth2Client.setCredentials({
48
- refresh_token: adminRefreshToken
49
- });
50
-
51
- const { credentials } = await oauth2Client.refreshAccessToken();
52
- return credentials.access_token;
53
- }
54
-
55
- // API: Đổi OAuth code thành token
56
- app.post('/api/oauth/token', async (req, res) => {
57
- try {
58
- const { code, redirect_uri } = req.body;
59
-
60
- if (!code) {
61
- return res.status(400).json({ error: 'Missing authorization code' });
62
- }
63
-
64
- const { tokens } = await oauth2Client.getToken(code);
65
-
66
- // Lưu refresh token
67
- if (tokens.refresh_token) {
68
- adminRefreshToken = tokens.refresh_token;
69
- // Trong production, nên lưu vào database
70
- console.log('Admin refresh token đã được lưu');
71
- }
72
-
73
- res.json({
74
- access_token: tokens.access_token,
75
- refresh_token: tokens.refresh_token,
76
- expires_in: tokens.expiry_date
77
- });
78
- } catch (error) {
79
- console.error('OAuth token error:', error);
80
- res.status(500).json({ error: error.message });
81
- }
82
- });
83
-
84
- // API: Set refresh token trực tiếp (nếu đã có)
85
- app.post('/api/oauth/set-token', async (req, res) => {
86
- try {
87
- const { refresh_token } = req.body;
88
-
89
- if (!refresh_token) {
90
- return res.status(400).json({ error: 'Missing refresh_token' });
91
- }
92
-
93
- adminRefreshToken = refresh_token;
94
- res.json({ success: true, message: 'Refresh token đã được lưu' });
95
- } catch (error) {
96
- console.error('Set token error:', error);
97
- res.status(500).json({ error: error.message });
98
- }
99
- });
100
-
101
- // Tìm hoặc tạo folder
102
- async function findOrCreateFolder(name, parentId = 'root') {
103
- try {
104
- const accessToken = await getAccessToken();
105
- const drive = google.drive({ version: 'v3', auth: oauth2Client });
106
-
107
- const response = await drive.files.list({
108
- q: `mimeType='application/vnd.google-apps.folder' and name='${name}' and '${parentId}' in parents and trashed=false`,
109
- fields: 'files(id, name)',
110
- });
111
-
112
- if (response.data.files.length > 0) {
113
- return response.data.files[0];
114
- }
115
-
116
- // Tạo folder mới
117
- const folder = await drive.files.create({
118
- requestBody: {
119
- name: name,
120
- mimeType: 'application/vnd.google-apps.folder',
121
- parents: [parentId],
122
- },
123
- fields: 'id, name',
124
- });
125
-
126
- return folder.data;
127
- } catch (error) {
128
- console.error('Error in findOrCreateFolder:', error);
129
- throw error;
130
- }
131
- }
132
-
133
- // Upload file
134
- async function uploadFile(filePath, fileName, parentId) {
135
- try {
136
- const accessToken = await getAccessToken();
137
- const drive = google.drive({ version: 'v3', auth: oauth2Client });
138
-
139
- const fileMetadata = {
140
- name: fileName,
141
- parents: [parentId],
142
- };
143
-
144
- const media = {
145
- mimeType: 'application/octet-stream',
146
- body: fs.createReadStream(filePath),
147
- };
148
-
149
- const file = await drive.files.create({
150
- requestBody: fileMetadata,
151
- media: media,
152
- fields: 'id, name, webViewLink, webContentLink, iconLink',
153
- });
154
-
155
- return file.data;
156
- } catch (error) {
157
- console.error('Error uploading file:', error);
158
- throw error;
159
- }
160
- }
161
-
162
- // API: Upload files
163
- app.post('/api/upload', upload.array('files'), async (req, res) => {
164
- try {
165
- const { docName, type, month } = req.body; // type: 'incoming' hoặc 'outgoing'
166
-
167
- if (!req.files || req.files.length === 0) {
168
- return res.status(400).json({ error: 'No files uploaded' });
169
- }
170
-
171
- if (!adminRefreshToken) {
172
- return res.status(401).json({ error: 'Chưa đăng nhập Admin. Vui lòng đăng nhập tài khoản Google 2TB trước.' });
173
- }
174
-
175
- // Tạo cấu trúc folder
176
- const qlvbFolder = await findOrCreateFolder('QLVB-DATA');
177
- const typeFolder = await findOrCreateFolder(
178
- type === 'incoming' ? 'VanBanDen' : 'VanBanDi',
179
- qlvbFolder.id
180
- );
181
- const monthFolder = await findOrCreateFolder(month || `Tháng ${String(new Date().getMonth() + 1).padStart(2, '0')}`, typeFolder.id);
182
- const docFolder = await findOrCreateFolder(
183
- `${docName} - ${Date.now()}`,
184
- monthFolder.id
185
- );
186
-
187
- // Upload tất cả files
188
- const uploadedFiles = [];
189
- for (const file of req.files) {
190
- const uploadedFile = await uploadFile(
191
- file.path,
192
- file.originalname,
193
- docFolder.id
194
- );
195
- uploadedFiles.push({
196
- id: uploadedFile.id,
197
- name: uploadedFile.name,
198
- iconLink: `https://drive.google.com/file/d/${uploadedFile.id}/view`,
199
- webViewLink: uploadedFile.webViewLink,
200
- webContentLink: uploadedFile.webContentLink,
201
- });
202
-
203
- // Xóa file tạm
204
- fs.unlinkSync(file.path);
205
- }
206
-
207
- // Lấy danh sách files trong folder
208
- const drive = google.drive({ version: 'v3', auth: oauth2Client });
209
- const filesList = await drive.files.list({
210
- q: `'${docFolder.id}' in parents and trashed=false`,
211
- fields: 'files(id, name, iconLink, webViewLink, webContentLink)',
212
- });
213
-
214
- res.json({
215
- folderId: docFolder.id,
216
- folderUrl: `https://drive.google.com/drive/folders/${docFolder.id}`,
217
- files: filesList.data.files || [],
218
- });
219
- } catch (error) {
220
- console.error('Upload error:', error);
221
- res.status(500).json({ error: error.message });
222
- }
223
- });
224
-
225
- // API: Download file từ Google Drive (để tóm tắt)
226
- app.get('/api/download/:fileId', async (req, res) => {
227
- try {
228
- const { fileId } = req.params;
229
-
230
- if (!adminRefreshToken) {
231
- return res.status(401).json({ error: 'Chưa đăng nhập Admin' });
232
- }
233
-
234
- await getAccessToken();
235
- const drive = google.drive({ version: 'v3', auth: oauth2Client });
236
-
237
- const response = await drive.files.get(
238
- { fileId, alt: 'media' },
239
- { responseType: 'stream' }
240
- );
241
- response.data.pipe(res);
242
- } catch (error) {
243
- console.error('Download error:', error);
244
- res.status(500).json({ error: error.message });
245
- }
246
- });
247
-
248
- // API: Kiểm tra trạng thái đăng nhập
249
- app.get('/api/oauth/status', (req, res) => {
250
- res.json({
251
- logged_in: !!adminRefreshToken
252
- });
253
- });
254
-
255
- // Hugging Face Spaces dùng port 7860, local dùng 3000
256
- const PORT = process.env.PORT || process.env.SPACE_PORT || 3000;
257
- app.listen(PORT, () => {
258
- console.log(`Backend server running on port ${PORT}`);
259
- console.log('Lưu ý: Cần đăng nhập Admin (tài khoản 2TB) trước khi upload file');
260
- });
 
 
 
 
 
 
 
 
 
1
+ // Backend API để upload file lên Google Drive 2TB
2
+ // Sử dụng OAuth thay vì Service Account (phù hợp với tài khoản edu)
3
+ // Chạy: node server.js hoặc npm start
4
+
5
+ require('dotenv').config();
6
+ const express = require('express');
7
+ const multer = require('multer');
8
+ const { google } = require('googleapis');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const cors = require('cors');
12
+
13
+ const app = express();
14
+ const upload = multer({ dest: 'uploads/' });
15
+
16
+ // Middleware - CORS cho phép frontend từ Vercel và local
17
+ app.use(cors({
18
+ origin: process.env.ALLOWED_ORIGINS ?
19
+ process.env.ALLOWED_ORIGINS.split(',') :
20
+ '*', // Cho phép tất cả khi không set (chỉ dùng khi dev)
21
+ credentials: true
22
+ }));
23
+ app.use(express.json());
24
+
25
+ // OAuth Config - Lấy từ Google Cloud Console
26
+ const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || 'YOUR_CLIENT_ID';
27
+ const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
28
+ const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost/popup.html';
29
+
30
+ // Lưu refresh token của admin (tài khoản 2TB)
31
+ // Trong production, nên lưu vào database hoặc file bảo mật
32
+ let adminRefreshToken = null;
33
+
34
+ // Khởi tạo OAuth2 client
35
+ const oauth2Client = new google.auth.OAuth2(
36
+ GOOGLE_CLIENT_ID,
37
+ GOOGLE_CLIENT_SECRET,
38
+ REDIRECT_URI
39
+ );
40
+
41
+ // Lấy access token từ refresh token
42
+ async function getAccessToken() {
43
+ if (!adminRefreshToken) {
44
+ throw new Error('Chưa có refresh token. Vui lòng đăng nhập Admin trước.');
45
+ }
46
+
47
+ oauth2Client.setCredentials({
48
+ refresh_token: adminRefreshToken
49
+ });
50
+
51
+ const { credentials } = await oauth2Client.refreshAccessToken();
52
+ return credentials.access_token;
53
+ }
54
+
55
+ // API: Đổi OAuth code thành token
56
+ app.post('/api/oauth/token', async (req, res) => {
57
+ try {
58
+ const { code, redirect_uri } = req.body;
59
+
60
+ if (!code) {
61
+ return res.status(400).json({ error: 'Missing authorization code' });
62
+ }
63
+
64
+ // Sử dụng redirect_uri từ request (từ frontend) thay vì từ env
65
+ // Tạo OAuth2 client mới với redirect_uri từ request
66
+ const requestOAuth2Client = new google.auth.OAuth2(
67
+ GOOGLE_CLIENT_ID,
68
+ GOOGLE_CLIENT_SECRET,
69
+ redirect_uri || REDIRECT_URI // Dùng redirect_uri từ request, fallback về env
70
+ );
71
+
72
+ const { tokens } = await requestOAuth2Client.getToken(code);
73
+
74
+ // Lưu refresh token
75
+ if (tokens.refresh_token) {
76
+ adminRefreshToken = tokens.refresh_token;
77
+ // Trong production, nên lưu vào database
78
+ console.log('Admin refresh token đã được lưu');
79
+ }
80
+
81
+ res.json({
82
+ access_token: tokens.access_token,
83
+ refresh_token: tokens.refresh_token,
84
+ expires_in: tokens.expiry_date
85
+ });
86
+ } catch (error) {
87
+ console.error('OAuth token error:', error);
88
+ res.status(500).json({ error: error.message });
89
+ }
90
+ });
91
+
92
+ // API: Set refresh token trực tiếp (nếu đã có)
93
+ app.post('/api/oauth/set-token', async (req, res) => {
94
+ try {
95
+ const { refresh_token } = req.body;
96
+
97
+ if (!refresh_token) {
98
+ return res.status(400).json({ error: 'Missing refresh_token' });
99
+ }
100
+
101
+ adminRefreshToken = refresh_token;
102
+ res.json({ success: true, message: 'Refresh token đã được lưu' });
103
+ } catch (error) {
104
+ console.error('Set token error:', error);
105
+ res.status(500).json({ error: error.message });
106
+ }
107
+ });
108
+
109
+ // Tìm hoặc tạo folder
110
+ async function findOrCreateFolder(name, parentId = 'root') {
111
+ try {
112
+ const accessToken = await getAccessToken();
113
+ const drive = google.drive({ version: 'v3', auth: oauth2Client });
114
+
115
+ const response = await drive.files.list({
116
+ q: `mimeType='application/vnd.google-apps.folder' and name='${name}' and '${parentId}' in parents and trashed=false`,
117
+ fields: 'files(id, name)',
118
+ });
119
+
120
+ if (response.data.files.length > 0) {
121
+ return response.data.files[0];
122
+ }
123
+
124
+ // Tạo folder mới
125
+ const folder = await drive.files.create({
126
+ requestBody: {
127
+ name: name,
128
+ mimeType: 'application/vnd.google-apps.folder',
129
+ parents: [parentId],
130
+ },
131
+ fields: 'id, name',
132
+ });
133
+
134
+ return folder.data;
135
+ } catch (error) {
136
+ console.error('Error in findOrCreateFolder:', error);
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ // Upload file
142
+ async function uploadFile(filePath, fileName, parentId) {
143
+ try {
144
+ const accessToken = await getAccessToken();
145
+ const drive = google.drive({ version: 'v3', auth: oauth2Client });
146
+
147
+ const fileMetadata = {
148
+ name: fileName,
149
+ parents: [parentId],
150
+ };
151
+
152
+ const media = {
153
+ mimeType: 'application/octet-stream',
154
+ body: fs.createReadStream(filePath),
155
+ };
156
+
157
+ const file = await drive.files.create({
158
+ requestBody: fileMetadata,
159
+ media: media,
160
+ fields: 'id, name, webViewLink, webContentLink, iconLink',
161
+ });
162
+
163
+ return file.data;
164
+ } catch (error) {
165
+ console.error('Error uploading file:', error);
166
+ throw error;
167
+ }
168
+ }
169
+
170
+ // API: Upload files
171
+ app.post('/api/upload', upload.array('files'), async (req, res) => {
172
+ try {
173
+ const { docName, type, month } = req.body; // type: 'incoming' hoặc 'outgoing'
174
+
175
+ if (!req.files || req.files.length === 0) {
176
+ return res.status(400).json({ error: 'No files uploaded' });
177
+ }
178
+
179
+ if (!adminRefreshToken) {
180
+ return res.status(401).json({ error: 'Chưa đăng nhập Admin. Vui lòng đăng nhập tài khoản Google 2TB trước.' });
181
+ }
182
+
183
+ // Tạo cấu trúc folder
184
+ const qlvbFolder = await findOrCreateFolder('QLVB-DATA');
185
+ const typeFolder = await findOrCreateFolder(
186
+ type === 'incoming' ? 'VanBanDen' : 'VanBanDi',
187
+ qlvbFolder.id
188
+ );
189
+ const monthFolder = await findOrCreateFolder(month || `Tháng ${String(new Date().getMonth() + 1).padStart(2, '0')}`, typeFolder.id);
190
+ const docFolder = await findOrCreateFolder(
191
+ `${docName} - ${Date.now()}`,
192
+ monthFolder.id
193
+ );
194
+
195
+ // Upload tất cả files
196
+ const uploadedFiles = [];
197
+ for (const file of req.files) {
198
+ const uploadedFile = await uploadFile(
199
+ file.path,
200
+ file.originalname,
201
+ docFolder.id
202
+ );
203
+ uploadedFiles.push({
204
+ id: uploadedFile.id,
205
+ name: uploadedFile.name,
206
+ iconLink: `https://drive.google.com/file/d/${uploadedFile.id}/view`,
207
+ webViewLink: uploadedFile.webViewLink,
208
+ webContentLink: uploadedFile.webContentLink,
209
+ });
210
+
211
+ // Xóa file tạm
212
+ fs.unlinkSync(file.path);
213
+ }
214
+
215
+ // Lấy danh sách files trong folder
216
+ const drive = google.drive({ version: 'v3', auth: oauth2Client });
217
+ const filesList = await drive.files.list({
218
+ q: `'${docFolder.id}' in parents and trashed=false`,
219
+ fields: 'files(id, name, iconLink, webViewLink, webContentLink)',
220
+ });
221
+
222
+ res.json({
223
+ folderId: docFolder.id,
224
+ folderUrl: `https://drive.google.com/drive/folders/${docFolder.id}`,
225
+ files: filesList.data.files || [],
226
+ });
227
+ } catch (error) {
228
+ console.error('Upload error:', error);
229
+ res.status(500).json({ error: error.message });
230
+ }
231
+ });
232
+
233
+ // API: Download file từ Google Drive (để tóm tắt)
234
+ app.get('/api/download/:fileId', async (req, res) => {
235
+ try {
236
+ const { fileId } = req.params;
237
+
238
+ if (!adminRefreshToken) {
239
+ return res.status(401).json({ error: 'Chưa đăng nhập Admin' });
240
+ }
241
+
242
+ await getAccessToken();
243
+ const drive = google.drive({ version: 'v3', auth: oauth2Client });
244
+
245
+ const response = await drive.files.get(
246
+ { fileId, alt: 'media' },
247
+ { responseType: 'stream' }
248
+ );
249
+ response.data.pipe(res);
250
+ } catch (error) {
251
+ console.error('Download error:', error);
252
+ res.status(500).json({ error: error.message });
253
+ }
254
+ });
255
+
256
+ // API: Kiểm tra trạng thái đăng nhập
257
+ app.get('/api/oauth/status', (req, res) => {
258
+ res.json({
259
+ logged_in: !!adminRefreshToken
260
+ });
261
+ });
262
+
263
+ // Hugging Face Spaces dùng port 7860, local dùng 3000
264
+ const PORT = process.env.PORT || process.env.SPACE_PORT || 3000;
265
+ app.listen(PORT, () => {
266
+ console.log(`Backend server running on port ${PORT}`);
267
+ console.log('Lưu ý: Cần đăng nhập Admin (tài khoản 2TB) trước khi upload file');
268
+ });