From 6c144e81a394d1f83dd0010ea07ddc5b2684c59e Mon Sep 17 00:00:00 2001 From: Team Date: Tue, 10 Mar 2026 07:36:48 +0000 Subject: [PATCH] =?UTF-8?q?=E5=9B=A2=E9=98=9F=E5=B7=A5=E4=BD=9C=E8=BF=9B?= =?UTF-8?q?=E5=B1=95:=20-=20=E5=90=8E=E7=AB=AF:=20=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20API=20(=E5=88=9B=E5=BB=BA/=E9=AA=8C?= =?UTF-8?q?=E8=AF=81/=E4=B8=8B=E8=BD=BD=E5=88=86=E4=BA=AB=E9=93=BE?= =?UTF-8?q?=E6=8E=A5)=20-=20=E5=89=8D=E7=AB=AF:=20=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E7=BB=84=E4=BB=B6=20-=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93:=20=E5=90=8C=E6=AD=A5=E7=8A=B6=E6=80=81=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/db.js | 13 ++++ backend/src/index.js | 2 + backend/src/routes/share.js | 142 ++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 backend/src/routes/share.js diff --git a/backend/src/db.js b/backend/src/db.js index 83bc5c3..1947e48 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -57,3 +57,16 @@ db.exec(` `); module.exports = db; + +// Sync tables (for reference - would be in migration in production) +db.exec(` + CREATE TABLE IF NOT EXISTS sync_status ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + last_sync_time DATETIME, + status TEXT DEFAULT 'idle', + total_files INTEGER DEFAULT 0, + synced_files INTEGER DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users(id) + ); +`); diff --git a/backend/src/index.js b/backend/src/index.js index b4b386a..3e7e4e7 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -2,6 +2,7 @@ const express = require('express'); const cors = require('cors'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/files'); +const shareRoutes = require('./routes/share'); const app = express(); const PORT = process.env.PORT || 3000; @@ -12,6 +13,7 @@ app.use(express.json()); // Routes app.use('/api/auth', authRoutes); app.use('/api/files', fileRoutes); +app.use('/api/share', shareRoutes); // Health check app.get('/api/health', (req, res) => { diff --git a/backend/src/routes/share.js b/backend/src/routes/share.js new file mode 100644 index 0000000..feab155 --- /dev/null +++ b/backend/src/routes/share.js @@ -0,0 +1,142 @@ +const express = require('express'); +const crypto = require('crypto'); +const db = require('../db'); + +const router = express.Router(); + +// Create share link +router.post('/', (req, res) => { + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) return res.status(401).json({ error: 'No token' }); + + try { + const jwt = require('jsonwebtoken'); + const decoded = jwt.verify(token, process.env.JWT_SECRET || 'clouddisk-secret-key'); + + const { fileId, password, expiresIn } = req.body; + + // Verify file ownership + const file = db.query( + 'SELECT * FROM files WHERE id = ? AND user_id = ?', + [fileId, decoded.userId] + ); + + if (file.length === 0) { + return res.status(404).json({ error: 'File not found' }); + } + + // Generate share token + const shareToken = crypto.randomBytes(16).toString('hex'); + + // Calculate expiration + const expiresAt = expiresIn + ? new Date(Date.now() + expiresIn * 1000).toISOString() + : null; + + const result = db.run( + 'INSERT INTO shares (file_id, share_token, password, expires_at) VALUES (?, ?, ?, ?)', + [fileId, shareToken, password || null, expiresAt] + ); + + res.json({ + success: true, + shareToken, + shareUrl: `/share/${shareToken}` + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get share info +router.get('/:token', (req, res) => { + try { + const share = db.query( + 'SELECT * FROM shares WHERE share_token = ?', + [req.params.token] + ); + + if (share.length === 0) { + return res.status(404).json({ error: 'Share not found' }); + } + + // Check expiration + if (share[0].expires_at && new Date(share[0].expires_at) < new Date()) { + return res.status(410).json({ error: 'Share expired' }); + } + + // Get file info + const file = db.query( + 'SELECT id, name, type, size FROM files WHERE id = ?', + [share[0].file_id] + ); + + // Increment view count + db.run( + 'UPDATE shares SET view_count = view_count + 1 WHERE id = ?', + [share[0].id] + ); + + res.json({ + file: file[0], + requiresPassword: !!share[0].password, + expiresAt: share[0].expires_at, + viewCount: share[0].view_count + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Verify share password +router.post('/:token/verify', (req, res) => { + const { password } = req.body; + + const share = db.query( + 'SELECT * FROM shares WHERE share_token = ?', + [req.params.token] + ); + + if (share.length === 0) { + return res.status(404).json({ error: 'Share not found' }); + } + + if (share[0].password && share[0].password !== password) { + return res.status(401).json({ error: 'Invalid password' }); + } + + res.json({ success: true }); +}); + +// Download shared file +router.get('/:token/download', (req, res) => { + const share = db.query( + 'SELECT * FROM shares WHERE share_token = ?', + [req.params.token] + ); + + if (share.length === 0) { + return res.status(404).json({ error: 'Share not found' }); + } + + // Check if password protected + if (share[0].password) { + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) { + return res.status(401).json({ error: 'Password required' }); + } + } + + const file = db.query( + 'SELECT * FROM files WHERE id = ?', + [share[0].file_id] + ); + + if (file.length === 0) { + return res.status(404).json({ error: 'File not found' }); + } + + res.download(file[0].path, file[0].name); +}); + +module.exports = router;