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

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +210 -41
server.js CHANGED
@@ -1,99 +1,268 @@
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}`));
 
 
 
 
 
 
 
 
 
 
 
 
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
+ });