hoangthiencm commited on
Commit
2478088
·
verified ·
1 Parent(s): 4e2a4d3

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +41 -233
server.js CHANGED
@@ -1,294 +1,102 @@
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: Xóa file trên Google Drive
257
  app.delete('/api/delete/:fileId', async (req, res) => {
258
  try {
259
  const { fileId } = req.params;
260
-
261
- // Kiểm tra quyền Admin
262
- if (!adminRefreshToken) {
263
- return res.status(401).json({ error: 'Chưa đăng nhập Admin. Vui lòng đăng nhập lại.' });
264
- }
265
 
266
  await getAccessToken();
267
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
268
 
269
- // Thực hiện xóa file
270
- await drive.files.delete({
271
- fileId: fileId
272
- });
273
-
274
- res.json({ success: true, message: 'Đã xóa file trên Google Drive thành công' });
275
  } catch (error) {
276
  console.error('Delete error:', error);
277
- // Trả về lỗi chi tiết để frontend biết
278
  res.status(500).json({ error: error.message });
279
  }
280
  });
281
 
282
- // API: Kiểm tra trạng thái đăng nhập
283
- app.get('/api/oauth/status', (req, res) => {
284
- res.json({
285
- logged_in: !!adminRefreshToken
286
- });
287
- });
288
 
289
- // Hugging Face Spaces dùng port 7860, local dùng 3000
290
- const PORT = process.env.PORT || process.env.SPACE_PORT || 3000;
291
- app.listen(PORT, () => {
292
- console.log(`Backend server running on port ${PORT}`);
293
- console.log('Lưu ý: Cần đăng nhập Admin (tài khoản 2TB) trước khi upload file');
294
- });
 
1
+ // Backend API để upload/delete file trên Google Drive 2TB
 
 
 
2
  require('dotenv').config();
3
  const express = require('express');
4
  const multer = require('multer');
5
  const { google } = require('googleapis');
 
6
  const fs = require('fs');
7
  const cors = require('cors');
8
 
9
  const app = express();
10
  const upload = multer({ dest: 'uploads/' });
11
 
12
+ app.use(cors({ origin: '*', credentials: true }));
 
 
 
 
 
 
13
  app.use(express.json());
14
 
15
+ // OAuth Config
16
+ const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
17
+ const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
18
+ const REDIRECT_URI = process.env.REDIRECT_URI;
19
 
 
 
20
  let adminRefreshToken = null;
21
 
22
+ const oauth2Client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, REDIRECT_URI);
 
 
 
 
 
23
 
 
24
  async function getAccessToken() {
25
+ if (!adminRefreshToken) throw new Error('Chưa đăng nhập Admin.');
26
+ oauth2Client.setCredentials({ refresh_token: adminRefreshToken });
 
 
 
 
 
 
27
  const { credentials } = await oauth2Client.refreshAccessToken();
28
  return credentials.access_token;
29
  }
30
 
31
+ // 1. API Lấy Token từ Code (Login)
32
  app.post('/api/oauth/token', async (req, res) => {
33
  try {
34
  const { code, redirect_uri } = req.body;
35
+ const client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, redirect_uri || REDIRECT_URI);
36
+ const { tokens } = await client.getToken(code);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  if (tokens.refresh_token) {
38
  adminRefreshToken = tokens.refresh_token;
39
+ console.log('Refresh Token Updated');
 
40
  }
41
+ res.json(tokens);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  } catch (error) {
 
43
  res.status(500).json({ error: error.message });
44
  }
45
  });
46
 
47
+ // 2. API Upload File
48
+ app.post('/api/upload', upload.array('files'), async (req, res) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  try {
50
+ if (!adminRefreshToken) return res.status(401).json({ error: 'Chưa đăng nhập Admin' });
51
+ await getAccessToken();
52
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
53
 
54
+ // Tạo Folder (Đơn giản hóa: Tạo folder theo tên Document)
55
+ const { docName } = req.body;
56
  const fileMetadata = {
57
+ name: `${docName} - ${Date.now()}`,
58
+ mimeType: 'application/vnd.google-apps.folder'
 
 
 
 
 
59
  };
60
+ const folder = await drive.files.create({ resource: fileMetadata, fields: 'id' });
61
+ const folderId = folder.data.id;
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  const uploadedFiles = [];
64
  for (const file of req.files) {
65
+ const media = { mimeType: file.mimetype, body: fs.createReadStream(file.path) };
66
+ const driveFile = await drive.files.create({
67
+ resource: { name: file.originalname, parents: [folderId] },
68
+ media: media,
69
+ fields: 'id, name, webViewLink, webContentLink'
 
 
 
 
 
 
70
  });
71
+ uploadedFiles.push(driveFile.data);
72
+ fs.unlinkSync(file.path); // Xóa file tạm
 
73
  }
74
+ res.json({ folderId, files: uploadedFiles });
 
 
 
 
 
 
 
 
 
 
 
 
75
  } catch (error) {
76
+ console.error(error);
77
  res.status(500).json({ error: error.message });
78
  }
79
  });
80
 
81
+ // 3. API Xóa File (QUAN TRỌNG)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  app.delete('/api/delete/:fileId', async (req, res) => {
83
  try {
84
  const { fileId } = req.params;
85
+ if (!adminRefreshToken) return res.status(401).json({ error: 'Chưa đăng nhập Admin' });
 
 
 
 
86
 
87
  await getAccessToken();
88
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
89
 
90
+ await drive.files.delete({ fileId: fileId });
91
+ res.json({ success: true, message: 'Đã xóa file trên Drive' });
 
 
 
 
92
  } catch (error) {
93
  console.error('Delete error:', error);
 
94
  res.status(500).json({ error: error.message });
95
  }
96
  });
97
 
98
+ const PORT = process.env.PORT || 3000;
99
+ app.listen(PORT, () => console.log(`Server chạy tại port ${PORT}`));
100
+ ```
 
 
 
101
 
102
+ **Lưu ý nhỏ:** Đảm bảo bạn đã file ảnh tên là `banner.jpg` nằm cùng chỗ với `index.html` nhé!