hoangthiencm commited on
Commit
fdb7ab1
·
verified ·
1 Parent(s): 9f45509

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +46 -210
server.js CHANGED
@@ -1,268 +1,104 @@
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 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
- });
 
 
 
 
 
1
  require('dotenv').config();
2
  const express = require('express');
3
  const multer = require('multer');
4
  const { google } = require('googleapis');
 
5
  const fs = require('fs');
6
  const cors = require('cors');
7
 
8
  const app = express();
9
  const upload = multer({ dest: 'uploads/' });
10
 
11
+ // Cho phép CORS
12
+ app.use(cors({ origin: '*', credentials: true, allowedHeaders: ['Content-Type', 'Authorization', 'x-refresh-token'] }));
 
 
 
 
 
13
  app.use(express.json());
14
 
15
+ const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
16
+ const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
17
+ const REDIRECT_URI = process.env.REDIRECT_URI;
 
18
 
19
+ // Biến lưu token tạm thời (sẽ mất khi server restart)
 
20
  let adminRefreshToken = null;
21
 
22
+ const oauth2Client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, REDIRECT_URI);
 
 
 
 
 
23
 
24
+ // Hàm lấy Access Token (Tự động khôi phục từ Header nếu biến local bị mất)
25
+ async function getAccessToken(req) {
26
+ // 1. Nếu biến server null, thử lấy từ Header của request
27
+ if (!adminRefreshToken && req && req.headers['x-refresh-token']) {
28
+ console.log('Server restart detected. Restoring session from client header...');
29
+ adminRefreshToken = req.headers['x-refresh-token'];
30
  }
31
+
32
+ if (!adminRefreshToken) throw new Error('Chưa đăng nhập Admin (Session expired)');
33
 
34
+ oauth2Client.setCredentials({ refresh_token: adminRefreshToken });
 
 
 
35
  const { credentials } = await oauth2Client.refreshAccessToken();
36
  return credentials.access_token;
37
  }
38
 
39
+ // 1. Login
40
  app.post('/api/oauth/token', async (req, res) => {
41
  try {
42
  const { code, redirect_uri } = req.body;
43
+ const client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, redirect_uri || REDIRECT_URI);
44
+ const { tokens } = await client.getToken(code);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  if (tokens.refresh_token) {
46
  adminRefreshToken = tokens.refresh_token;
 
 
47
  }
48
+ res.json(tokens);
 
 
 
 
 
49
  } catch (error) {
 
50
  res.status(500).json({ error: error.message });
51
  }
52
  });
53
 
54
+ // 2. Upload
55
+ app.post('/api/upload', upload.array('files'), async (req, res) => {
56
  try {
57
+ // Truyền req vào để hàm getAccessToken tự tìm token trong header
58
+ await getAccessToken(req);
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
61
+ const { docName } = req.body;
62
 
63
+ // Tạo Folder
64
  const fileMetadata = {
65
+ name: `${docName} - ${Date.now()}`,
66
+ mimeType: 'application/vnd.google-apps.folder'
 
 
 
 
 
67
  };
68
+ const folder = await drive.files.create({ resource: fileMetadata, fields: 'id' });
69
+ const folderId = folder.data.id;
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  const uploadedFiles = [];
72
  for (const file of req.files) {
73
+ const media = { mimeType: file.mimetype, body: fs.createReadStream(file.path) };
74
+ const driveFile = await drive.files.create({
75
+ resource: { name: file.originalname, parents: [folderId] },
76
+ media: media,
77
+ fields: 'id, name, webViewLink, webContentLink'
 
 
 
 
 
 
78
  });
79
+ uploadedFiles.push(driveFile.data);
 
80
  fs.unlinkSync(file.path);
81
  }
82
+ res.json({ folderId, files: uploadedFiles });
 
 
 
 
 
 
 
 
 
 
 
 
83
  } catch (error) {
84
+ console.error(error);
85
  res.status(500).json({ error: error.message });
86
  }
87
  });
88
 
89
+ // 3. Delete
90
+ app.delete('/api/delete/:fileId', async (req, res) => {
91
  try {
92
+ await getAccessToken(req); // Truyền req để khôi phục token
 
 
 
 
93
 
 
94
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
95
+ await drive.files.delete({ fileId: req.params.fileId });
96
+ res.json({ success: true });
 
 
 
 
97
  } catch (error) {
98
+ console.error(error);
99
  res.status(500).json({ error: error.message });
100
  }
101
  });
102
 
103
+ const PORT = process.env.PORT || 3000;
104
+ app.listen(PORT, () => console.log(`Server running on port ${PORT}`));