Build a WeTransfer-inspired file sharing app with tracking in 45 minutes
Welcome to another official UVF IT build. Today we are constructing a minimal, working clone of Campsend, the open-source alternative to WeTransfer, focusing on the core value proposition: sending files via expiring links with delivery tracking.
This guide strips away the complex frontend animations and marketing fluff to reveal the engineering skeleton. You will build a backend that handles file uploads, generates secure temporary URLs, and logs when a recipient views or downloads the asset, all using a free-tier Python stack.
Initialize the project directory and install the necessary Python libraries. We use Flask for the web server, SQLite3 for the database (built-in), and uuid for generating unique file IDs.
Creating a virtual environment ensures that your system Python remains clean and that dependencies are isolated to this specific project. This is a best practice for any Python development workflow.
setup.sh
#!/bin/bash
# Create project directory and navigate into it
mkdir campsend-clone
cd campsend-clone
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate
# Install Flask
pip install flask
# Create necessary directories
mkdir uploads
# Verify installation
python -c "import flask; print(f'Flask {flask.__version__} installed')"Create the SQLite database schema. We need two tables: 'files' to store metadata (filename, original_name, expiry) and 'events' to track when files are viewed or downloaded. This mimics Campsend's tracking feature.
The 'files' table stores the unique ID used in the URL, the original filename for display, and the expiry timestamp. The 'events' table logs every access attempt, allowing us to notify the sender later. We use a simple Python script to initialize this schema.
db.py
import sqlite3
import os
DB_NAME = 'campsend.db'
def init_db():
"""Initialize the database and create tables if they don't exist."""
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
# Create files table
cursor.execute('''
CREATE TABLE IF NOT EXISTS files (
id TEXT PRIMARY KEY,
original_filename TEXT NOT NULL,
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
)
''')
# Create events table for tracking
cursor.execute('''
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_id TEXT NOT NULL,
event_type TEXT NOT NULL CHECK(event_type IN ('upload', 'download', 'view')),
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (file_id) REFERENCES files(id)
)
''')
conn.commit()
conn.close()
print(f"Database '{DB_NAME}' initialized successfully.")
if __name__ == '__main__':
init_db()Now we implement the core Flask application logic that ties our database and file system together. We will create two main endpoints: a POST route for uploading files and a GET route for downloading them by their unique ID.
The upload endpoint saves the file to a local 'uploads' directory, generates a unique UUID, and records the metadata in our SQLite database. It then returns a JSON response containing the shareable download link, mimicking the Campsend user experience.
The download endpoint retrieves the file metadata from the database. If the file exists and hasn't expired, it logs a 'download' event to the 'events' table to track usage, then streams the file back to the user. This ensures every access is recorded for the sender.
We also include a helper function to check if a file link is valid and not expired, ensuring that old links automatically become inaccessible after their designated lifetime.
app.py
import os
import uuid
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, send_file, abort
from db import init_db, get_db
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
# Ensure upload folder exists
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
# Generate unique ID and save file
file_id = str(uuid.uuid4())
original_filename = file.filename
file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_id)
file.save(file_path)
# Set expiry to 24 hours from now
expiry_date = datetime.now() + timedelta(hours=24)
# Save metadata to database
db = get_db()
db.execute(
'INSERT INTO files (id, original_name, file_path, expiry_date) VALUES (?, ?, ?, ?)',
(file_id, original_filename, file_path, expiry_date)
)
db.commit()
# Construct download link
download_link = f"http://127.0.0.1:5000/download/{file_id}"
return jsonify({
'message': 'File uploaded successfully',
'link': download_link,
'expires_in': '24 hours'
}), 201
@app.route('/download/<file_id>', methods=['GET'])
def download_file(file_id):
db = get_db()
# Fetch file metadata
file_row = db.execute('SELECT * FROM files WHERE id = ?', (file_id,)).fetchone()
if file_row is None:
abort(404, description='File not found')
# Check if file has expired
expiry_date = datetime.fromisoformat(file_row['expiry_date'])
if datetime.now() > expiry_date:
abort(410, description='Link has expired')
# Log the download event
db.execute(
'INSERT INTO events (file_id, event_type, timestamp) VALUES (?, ?, ?)',
(file_id, 'download', datetime.now().isoformat())
)
db.commit()
# Send the file
return send_file(file_row['file_path'], as_attachment=True, download_name=file_row['original_name'])
@app.route('/status/<file_id>', methods=['GET'])
def get_file_status(file_id):
db = get_db()
# Fetch file metadata
file_row = db.execute('SELECT * FROM files WHERE id = ?', (file_id,)).fetchone()
if file_row is None:
return jsonify({'error': 'File not found'}), 404
# Get download count
event_count = db.execute(
'SELECT COUNT(*) as count FROM events WHERE file_id = ? AND event_type = ?',
(file_id, 'download')
).fetchone()['count']
# Check if expired
expiry_date = datetime.fromisoformat(file_row['expiry_date'])
is_expired = datetime.now() > expiry_date
return jsonify({
'file_name': file_row['original_name'],
'download_count': event_count,
'expired': is_expired,
'expiry_date': file_row['expiry_date']
})
if __name__ == '__main__':
init_db()
app.run(debug=True, host='0.0.0.0', port=5000)With the application code complete, we can now start the Flask server and test the entire workflow. We will use curl to simulate file uploads and downloads, verifying that the endpoints respond correctly.
First, we start the Flask server in the background. Then, we create a dummy text file to upload. This simulates a user sending a file through the Campsend clone interface.
Next, we send the file to the /upload endpoint. The server should respond with a JSON object containing the unique download link. We extract this link to use in the next step.
Finally, we use the download link to retrieve the file and check the database to confirm that the download event was logged. This proves our tracking system is working as intended.
test.sh
#!/bin/bash
# Start the Flask server in the background
python app.py &
SERVER_PID=$!
sleep 2 # Wait for server to start
# Create a dummy file to upload
echo "This is a test file for Campsend clone" > test_file.txt
# Upload the file
echo "Uploading file..."
RESPONSE=$(curl -s -X POST -F "file=@test_file.txt" http://127.0.0.1:5000/upload)
echo "Upload Response: $RESPONSE"
# Extract the download link from the JSON response
DOWNLOAD_LINK=$(echo $RESPONSE | python -c "import sys, json; print(json.load(sys.stdin)['link'])")
echo "Download Link: $DOWNLOAD_LINK"
# Download the file using the link
echo "Downloading file..."
curl -s -o downloaded_file.txt $DOWNLOAD_LINK
# Verify the downloaded content
echo "Downloaded Content:"
cat downloaded_file.txt
# Check the database for the download event
echo "Checking database for events..."
sqlite3 campsend.db "SELECT * FROM events;"
# Clean up
kill $SERVER_PID
rm test_file.txt downloaded_file.txt
echo "Test complete."| Check | Command / Why it matters |
|---|---|
| Server starts without errors | python app.py should output 'Running on http://127.0.0.1:5000' in the terminal |
| File upload returns a JSON link | curl -X POST -F 'file=@test.txt' http://127.0.0.1:5000/upload returns {"link": "http://..."} |
| Download link serves the file | curl -O http://127.0.0.1:5000/download/<id> saves the file content correctly to disk |
| Event is logged in SQLite | sqlite3 campsend.db 'SELECT * FROM events;' shows a row with event_type='download' |
| Expired links return 410 | Accessing a link after its expiry_date returns HTTP 410 Gone status |
To extend this clone, add a simple HTML frontend for the upload form, implement email notifications for the sender when the link is clicked, and replace local storage with AWS S3 for scalability.