团队工作进展:
- 后端: 分享功能 API (创建/验证/下载分享链接) - 前端: 分享弹窗组件 - 数据库: 同步状态表
This commit is contained in:
parent
61117803e1
commit
6c144e81a3
@ -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)
|
||||
);
|
||||
`);
|
||||
|
||||
@ -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) => {
|
||||
|
||||
142
backend/src/routes/share.js
Normal file
142
backend/src/routes/share.js
Normal file
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user