mirror of
https://github.com/mattintech/simplefileupload-server.git
synced 2026-07-11 10:41:54 +00:00
added endpoint for early delete (before file expires)
This commit is contained in:
@@ -4,7 +4,7 @@ from flask import Flask, request, jsonify, send_from_directory
|
||||
from werkzeug.utils import secure_filename
|
||||
import requests
|
||||
import utils.mLogger as log
|
||||
from controllers.file_controller import upload_file, download_file
|
||||
from controllers.file_controller import upload_file, download_file, delete_file
|
||||
from views import views
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -19,6 +19,9 @@ app.route('/upload', methods=['POST'])(upload_file)
|
||||
# Route for downloading the file
|
||||
app.route('/download/<code>', methods=['GET'])(download_file)
|
||||
|
||||
# Route for deleting the file
|
||||
app.route('/delete/<code>', methods=['DELETE'])(delete_file)
|
||||
|
||||
app.register_blueprint(views)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -123,6 +123,39 @@ def download_file(code):
|
||||
download_name=file_info['filename']
|
||||
)
|
||||
|
||||
def delete_file(code):
|
||||
metadata = load_metadata()
|
||||
|
||||
if code not in metadata:
|
||||
return jsonify({'error': 'Invalid code'}), 404
|
||||
|
||||
file_info = metadata[code]
|
||||
file_path = file_info['path']
|
||||
expiry_time = datetime.fromisoformat(file_info['expiry_time'])
|
||||
|
||||
# Check expiry
|
||||
if datetime.now() > expiry_time:
|
||||
del metadata[code]
|
||||
save_metadata(metadata)
|
||||
return jsonify({'error': 'File already expired'}), 404
|
||||
|
||||
# Check if still downloadable
|
||||
if file_info['downloads'] > file_info['max_downloads']:
|
||||
del metadata[code]
|
||||
save_metadata(metadata)
|
||||
return jsonify({'error': 'File no longer available'}), 404
|
||||
|
||||
# Delete the file
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
# Remove from metadata
|
||||
del metadata[code]
|
||||
save_metadata(metadata)
|
||||
log.i(f"File deleted: {code}")
|
||||
|
||||
return jsonify({'message': 'File deleted successfully'}), 200
|
||||
|
||||
def cleanup_expired_files():
|
||||
metadata = load_metadata()
|
||||
updated_metadata = {}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Download File</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -70,6 +71,17 @@
|
||||
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
background-color: #4299e1;
|
||||
@@ -102,6 +114,29 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: transparent;
|
||||
color: #718096;
|
||||
width: auto;
|
||||
padding: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: #e53e3e;
|
||||
background-color: transparent !important;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.delete-button:disabled {
|
||||
color: #CBD5E0;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.delete-button i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
@@ -140,7 +175,8 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.querySelector('form');
|
||||
const inputs = document.querySelectorAll('.code-input input');
|
||||
const button = document.querySelector('button');
|
||||
const downloadButton = document.querySelector('.download-button');
|
||||
const deleteButton = document.querySelector('.delete-button');
|
||||
const spinner = document.querySelector('.spinner');
|
||||
const message = document.querySelector('.message');
|
||||
|
||||
@@ -163,7 +199,8 @@
|
||||
}
|
||||
|
||||
const isComplete = Array.from(inputs).every(input => input.value.length === 1);
|
||||
button.disabled = !isComplete;
|
||||
downloadButton.disabled = !isComplete;
|
||||
deleteButton.disabled = !isComplete;
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
@@ -181,11 +218,55 @@
|
||||
inputs.forEach((input, i) => {
|
||||
input.value = pastedText[i] || '';
|
||||
});
|
||||
button.disabled = false;
|
||||
downloadButton.disabled = false;
|
||||
deleteButton.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
deleteButton.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const code = Array.from(inputs).map(input => input.value).join('');
|
||||
|
||||
if (code.length !== 6) {
|
||||
showMessage('Please enter a valid 6-character code.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to delete this file? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadButton.disabled = true;
|
||||
deleteButton.disabled = true;
|
||||
spinner.style.display = 'block';
|
||||
const originalIcon = deleteButton.innerHTML;
|
||||
deleteButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/delete/${code}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showMessage('File deleted successfully!', 'success');
|
||||
form.reset();
|
||||
} else {
|
||||
showMessage(data.error || 'Error deleting file. Please try again.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('Error deleting file. Please try again.', 'error');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
downloadButton.disabled = false;
|
||||
deleteButton.disabled = false;
|
||||
spinner.style.display = 'none';
|
||||
deleteButton.innerHTML = originalIcon;
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const code = Array.from(inputs).map(input => input.value).join('');
|
||||
@@ -195,9 +276,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
downloadButton.disabled = true;
|
||||
deleteButton.disabled = true;
|
||||
spinner.style.display = 'block';
|
||||
button.textContent = 'Downloading...';
|
||||
downloadButton.textContent = 'Downloading...';
|
||||
|
||||
try {
|
||||
window.location.assign(`/download/${code}`);
|
||||
@@ -206,9 +288,10 @@
|
||||
showMessage('Error starting download. Please try again.', 'error');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
downloadButton.disabled = false;
|
||||
deleteButton.disabled = false;
|
||||
spinner.style.display = 'none';
|
||||
button.textContent = 'Download';
|
||||
downloadButton.textContent = 'Download';
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
@@ -227,10 +310,15 @@
|
||||
<input type="text" maxlength="1" required pattern="[A-Za-z0-9]" inputmode="text" autocomplete="off">
|
||||
<input type="text" maxlength="1" required pattern="[A-Za-z0-9]" inputmode="text" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" disabled>
|
||||
Download
|
||||
<div class="spinner"></div>
|
||||
</button>
|
||||
<div class="button-group">
|
||||
<button type="submit" class="download-button" disabled>
|
||||
Download
|
||||
<div class="spinner"></div>
|
||||
</button>
|
||||
<button type="button" class="delete-button" disabled title="Delete file">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="message"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user