added endpoint for early delete (before file expires)

This commit is contained in:
2025-04-14 22:57:32 -04:00
parent f5e4016816
commit 350a8aaab7
3 changed files with 136 additions and 12 deletions

View File

@@ -4,7 +4,7 @@ from flask import Flask, request, jsonify, send_from_directory
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import requests import requests
import utils.mLogger as log 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 from views import views
app = Flask(__name__) app = Flask(__name__)
@@ -19,6 +19,9 @@ app.route('/upload', methods=['POST'])(upload_file)
# Route for downloading the file # Route for downloading the file
app.route('/download/<code>', methods=['GET'])(download_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) app.register_blueprint(views)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -123,6 +123,39 @@ def download_file(code):
download_name=file_info['filename'] 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(): def cleanup_expired_files():
metadata = load_metadata() metadata = load_metadata()
updated_metadata = {} updated_metadata = {}

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Download File</title> <title>Download File</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -70,6 +71,17 @@
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15); 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 { button {
width: 100%; width: 100%;
background-color: #4299e1; background-color: #4299e1;
@@ -102,6 +114,29 @@
transform: none; 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 { .message {
margin-top: 1rem; margin-top: 1rem;
padding: 0.75rem; padding: 0.75rem;
@@ -140,7 +175,8 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('form'); const form = document.querySelector('form');
const inputs = document.querySelectorAll('.code-input input'); 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 spinner = document.querySelector('.spinner');
const message = document.querySelector('.message'); const message = document.querySelector('.message');
@@ -163,7 +199,8 @@
} }
const isComplete = Array.from(inputs).every(input => input.value.length === 1); const isComplete = Array.from(inputs).every(input => input.value.length === 1);
button.disabled = !isComplete; downloadButton.disabled = !isComplete;
deleteButton.disabled = !isComplete;
}); });
input.addEventListener('keydown', (e) => { input.addEventListener('keydown', (e) => {
@@ -181,11 +218,55 @@
inputs.forEach((input, i) => { inputs.forEach((input, i) => {
input.value = pastedText[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) => { form.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const code = Array.from(inputs).map(input => input.value).join(''); const code = Array.from(inputs).map(input => input.value).join('');
@@ -195,9 +276,10 @@
return; return;
} }
button.disabled = true; downloadButton.disabled = true;
deleteButton.disabled = true;
spinner.style.display = 'block'; spinner.style.display = 'block';
button.textContent = 'Downloading...'; downloadButton.textContent = 'Downloading...';
try { try {
window.location.assign(`/download/${code}`); window.location.assign(`/download/${code}`);
@@ -206,9 +288,10 @@
showMessage('Error starting download. Please try again.', 'error'); showMessage('Error starting download. Please try again.', 'error');
} finally { } finally {
setTimeout(() => { setTimeout(() => {
button.disabled = false; downloadButton.disabled = false;
deleteButton.disabled = false;
spinner.style.display = 'none'; spinner.style.display = 'none';
button.textContent = 'Download'; downloadButton.textContent = 'Download';
}, 2000); }, 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">
<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> </div>
<button type="submit" disabled> <div class="button-group">
<button type="submit" class="download-button" disabled>
Download Download
<div class="spinner"></div> <div class="spinner"></div>
</button> </button>
<button type="button" class="delete-button" disabled title="Delete file">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<div class="message"></div> <div class="message"></div>
</form> </form>
</div> </div>