test: sim7 URL shortener — end-to-end V3 simulation (4 files)

Flask URL shortener with base62 encoding, POST /shorten, GET /<code>
redirect, input validation, and HTML templates (index + error page).
Multi-file extraction correctly handled nested templates/ directory.
This commit is contained in:
2026-03-13 14:28:43 +05:30
parent 527fccdf6b
commit ac1e17b8bd
5 changed files with 441 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
name: local/url-shortener
description: >
Build a URL shortener service in Python using Flask. Include endpoints
to shorten a URL (POST /shorten) and redirect via short code (GET /<code>).
Use an in-memory dictionary for storage. Generate short codes using
base62 encoding of an auto-incrementing counter. Include input validation
for URLs, a simple HTML landing page, and requirements.txt.
strategy_actor: anthropic/claude-sonnet-4-20250514
execution_actor: anthropic/claude-sonnet-4-20250514
definition_of_done: >
An app.py has Flask routes for POST /shorten and GET /<code>.
Short codes are generated via base62 encoding.
URL validation rejects malformed inputs.
An index.html landing page allows users to paste and shorten URLs.
In-memory dict stores mappings. A requirements.txt lists dependencies.
+155
View File
@@ -0,0 +1,155 @@
from flask import Flask, request, redirect, render_template, jsonify, url_for
from urllib.parse import urlparse
import re
import threading
app = Flask(__name__)
# In-memory storage
url_storage = {}
counter = 0
counter_lock = threading.Lock()
# Base62 alphabet
BASE62_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def base62_encode(number):
"""Convert a number to base62 string."""
if number == 0:
return BASE62_ALPHABET[0]
result = ""
while number > 0:
result = BASE62_ALPHABET[number % 62] + result
number //= 62
return result
def base62_decode(string):
"""Convert a base62 string to number."""
number = 0
for char in string:
number = number * 62 + BASE62_ALPHABET.index(char)
return number
def is_valid_url(url):
"""Validate URL format and structure."""
try:
result = urlparse(url)
# Check if URL has scheme and netloc
if not all([result.scheme, result.netloc]):
return False
# Check for valid scheme
if result.scheme not in ['http', 'https']:
return False
# Basic domain validation
domain_pattern = re.compile(
r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
)
return bool(domain_pattern.match(result.netloc.split(':')[0]))
except Exception:
return False
def generate_short_code():
"""Generate next short code using thread-safe counter."""
global counter
with counter_lock:
counter += 1
return base62_encode(counter)
@app.route('/')
def index():
"""Serve the main landing page."""
return render_template('index.html')
@app.route('/shorten', methods=['POST'])
def shorten_url():
"""
Shorten a URL endpoint.
Accepts JSON or form data with 'url' field.
"""
# Handle both JSON and form data
if request.is_json:
data = request.get_json()
url = data.get('url', '').strip()
else:
url = request.form.get('url', '').strip()
# Validate URL presence
if not url:
if request.is_json:
return jsonify({'error': 'URL is required'}), 400
return render_template('index.html', error='URL is required'), 400
# Validate URL format
if not is_valid_url(url):
error_msg = 'Invalid URL format. Please enter a valid HTTP or HTTPS URL.'
if request.is_json:
return jsonify({'error': error_msg}), 400
return render_template('index.html', error=error_msg, url=url), 400
# Generate short code and store
short_code = generate_short_code()
url_storage[short_code] = url
# Build shortened URL
shortened_url = url_for('redirect_url', code=short_code, _external=True)
if request.is_json:
return jsonify({
'shortened_url': shortened_url,
'short_code': short_code,
'original_url': url
}), 200
return render_template('index.html',
shortened_url=shortened_url,
original_url=url,
success=True)
@app.route('/<code>')
def redirect_url(code):
"""
Redirect to original URL using short code.
Returns 404 if code not found.
"""
# Validate short code format (base62)
if not code or not all(c in BASE62_ALPHABET for c in code):
return render_template('error.html',
message='Invalid short code format'), 404
# Look up original URL
original_url = url_storage.get(code)
if not original_url:
return render_template('error.html',
message='Short code not found'), 404
return redirect(original_url, code=302)
@app.errorhandler(404)
def not_found(error):
"""Handle 404 errors."""
return render_template('error.html',
message='Page not found'), 404
@app.errorhandler(500)
def internal_error(error):
"""Handle 500 errors."""
return render_template('error.html',
message='Internal server error'), 500
# Health check endpoint
@app.route('/health')
def health_check():
"""Simple health check endpoint."""
return jsonify({
'status': 'healthy',
'urls_stored': len(url_storage)
}), 200
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
+1
View File
@@ -0,0 +1 @@
Flask>=2.0.0
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error - URL Shortener</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
max-width: 500px;
margin: 100px auto;
padding: 20px;
text-align: center;
color: #333;
}
.error-container {
background: #f8f9fa;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #dc3545;
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 18px;
margin-bottom: 30px;
color: #666;
}
.home-link {
display: inline-block;
background: #007bff;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 4px;
font-weight: 500;
}
.home-link:hover {
background: #0056b3;
}
</style>
</head>
<body>
<div class="error-container">
<h1></h1>
<p>{{ message }}</p>
<a href="/" class="home-link">← Go Back Home</a>
</div>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Shortener</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
line-height: 1.6;
color: #333;
}
.container {
background: #f8f9fa;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input[type="url"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
input[type="url"]:focus {
outline: none;
border-color: #007bff;
}
button {
background: #007bff;
color: white;
padding: 12px 30px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background: #0056b3;
}
.alert {
padding: 15px;
border-radius: 4px;
margin: 20px 0;
}
.alert-success {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.alert-error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.result-url {
background: #fff;
padding: 15px;
border-radius: 4px;
border: 2px solid #007bff;
margin: 10px 0;
word-break: break-all;
}
.result-url a {
color: #007bff;
text-decoration: none;
font-weight: 500;
}
.result-url a:hover {
text-decoration: underline;
}
.copy-button {
background: #28a745;
padding: 8px 16px;
font-size: 14px;
margin-top: 10px;
width: auto;
}
.copy-button:hover {
background: #218838;
}
.footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #ddd;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>🔗 URL Shortener</h1>
{% if error %}
<div class="alert alert-error">
<strong>Error:</strong> {{ error }}
</div>
{% endif %}
{% if success and shortened_url %}
<div class="alert alert-success">
<strong>Success!</strong> Your URL has been shortened.
<div class="result-url">
<strong>Shortened URL:</strong><br>
<a href="{{ shortened_url }}" target="_blank" id="shortenedUrl">{{ shortened_url }}</a>
<br>
<button type="button" class="copy-button" onclick="copyToClipboard()">
📋 Copy URL
</button>
</div>
<div style="margin-top: 10px; font-size: 14px;">
<strong>Original URL:</strong>
<a href="{{ original_url }}" target="_blank">{{ original_url }}</a>
</div>
</div>
{% endif %}
<form method="POST" action="/shorten">
<div class="form-group">
<label for="url">Enter URL to shorten:</label>
<input type="url"
id="url"
name="url"
placeholder="https://example.com/your-long-url"
value="{{ url if url else '' }}"
required>
</div>
<button type="submit">Shorten URL</button>
</form>
<div class="footer">
<p>Enter any HTTP or HTTPS URL to create a shortened version.</p>
<p>Shortened URLs will redirect to your original link.</p>
</div>
</div>
<script>
function copyToClipboard() {
const urlElement = document.getElementById('shortenedUrl');
const url = urlElement.textContent;
navigator.clipboard.writeText(url).then(function() {
const button = document.querySelector('.copy-button');
const originalText = button.textContent;
button.textContent = '✅ Copied!';
button.style.background = '#28a745';
setTimeout(function() {
button.textContent = originalText;
button.style.background = '#28a745';
}, 2000);
}).catch(function(err) {
console.error('Failed to copy: ', err);
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = url;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
const button = document.querySelector('.copy-button');
button.textContent = '✅ Copied!';
setTimeout(function() {
button.textContent = '📋 Copy URL';
}, 2000);
});
}
</script>
</body>
</html>