app.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. from flask import Flask, render_template, request, redirect, url_for, jsonify, session
  2. import sqlite3
  3. import string
  4. import random
  5. import os
  6. import sys
  7. import argparse
  8. from datetime import datetime, timedelta
  9. import uuid
  10. app = Flask(__name__)
  11. app.secret_key = 'tu_clave_secreta_aqui' # Cambiar en producción
  12. # Configuración de la base de datos
  13. DATABASE = 'url_shortener.db'
  14. def init_db():
  15. """Inicializar la base de datos"""
  16. conn = sqlite3.connect(DATABASE)
  17. cursor = conn.cursor()
  18. # Tabla para enlaces acortados
  19. cursor.execute('''
  20. CREATE TABLE IF NOT EXISTS urls (
  21. id INTEGER PRIMARY KEY AUTOINCREMENT,
  22. original_url TEXT NOT NULL,
  23. short_code TEXT UNIQUE NOT NULL,
  24. user_session TEXT,
  25. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  26. expires_at TIMESTAMP,
  27. clicks INTEGER DEFAULT 0
  28. )
  29. ''')
  30. # Agregar columna expires_at si no existe (para bases de datos existentes)
  31. try:
  32. cursor.execute('ALTER TABLE urls ADD COLUMN expires_at TIMESTAMP')
  33. conn.commit()
  34. except sqlite3.OperationalError:
  35. # La columna ya existe
  36. pass
  37. conn.commit()
  38. conn.close()
  39. def generate_short_code(length=6):
  40. """Generar código corto aleatorio"""
  41. characters = string.ascii_letters + string.digits
  42. return ''.join(random.choice(characters) for _ in range(length))
  43. def get_user_session():
  44. """Obtener o crear sesión de usuario anónimo"""
  45. if 'user_id' not in session:
  46. session['user_id'] = str(uuid.uuid4())
  47. return session['user_id']
  48. @app.route('/')
  49. def index():
  50. """Página principal"""
  51. return render_template('index.html')
  52. @app.route('/my-links')
  53. def my_links():
  54. """Página de enlaces del usuario"""
  55. user_session = get_user_session()
  56. # Obtener enlaces del usuario
  57. conn = sqlite3.connect(DATABASE)
  58. cursor = conn.cursor()
  59. cursor.execute('''
  60. SELECT original_url, short_code, created_at, clicks, expires_at
  61. FROM urls
  62. WHERE user_session = ?
  63. ORDER BY created_at DESC
  64. ''', (user_session,))
  65. user_urls = cursor.fetchall()
  66. conn.close()
  67. return render_template('my_links.html', user_urls=user_urls)
  68. def calculate_expiration(duration):
  69. """Calcular fecha de expiración basada en duración"""
  70. if duration == 'never':
  71. return None
  72. elif duration == '1hour':
  73. return datetime.now() + timedelta(hours=1)
  74. elif duration == '1day':
  75. return datetime.now() + timedelta(days=1)
  76. elif duration == '1week':
  77. return datetime.now() + timedelta(weeks=1)
  78. elif duration == '1month':
  79. return datetime.now() + timedelta(days=30)
  80. elif duration == '1year':
  81. return datetime.now() + timedelta(days=365)
  82. else:
  83. return None
  84. def is_url_expired(expires_at):
  85. """Verificar si un URL ha expirado"""
  86. if expires_at is None:
  87. return False
  88. return datetime.now() > datetime.fromisoformat(expires_at)
  89. @app.route('/shorten', methods=['POST'])
  90. def shorten_url():
  91. """Acortar URL"""
  92. # Obtener datos tanto de formulario como de JSON
  93. if request.is_json:
  94. original_url = request.json.get('url')
  95. duration = request.json.get('duration', 'never')
  96. else:
  97. original_url = request.form.get('url')
  98. duration = request.form.get('duration', 'never')
  99. if not original_url:
  100. return jsonify({'error': 'URL es requerida'}), 400
  101. # Agregar http:// si no tiene protocolo
  102. if not original_url.startswith(('http://', 'https://')):
  103. original_url = 'http://' + original_url
  104. user_session = get_user_session()
  105. expires_at = calculate_expiration(duration)
  106. # Generar código corto único
  107. while True:
  108. short_code = generate_short_code()
  109. conn = sqlite3.connect(DATABASE)
  110. cursor = conn.cursor()
  111. cursor.execute('SELECT id FROM urls WHERE short_code = ?', (short_code,))
  112. if not cursor.fetchone():
  113. break
  114. conn.close()
  115. # Guardar en base de datos
  116. cursor.execute('''
  117. INSERT INTO urls (original_url, short_code, user_session, expires_at)
  118. VALUES (?, ?, ?, ?)
  119. ''', (original_url, short_code, user_session, expires_at))
  120. conn.commit()
  121. conn.close()
  122. short_url = f"https://please.checkthis.space/s/{short_code}"
  123. return jsonify({
  124. 'success': True,
  125. 'short_url': short_url,
  126. 'short_code': short_code,
  127. 'original_url': original_url,
  128. 'expires_at': expires_at.isoformat() if expires_at else None
  129. })
  130. @app.route('/s/<short_code>')
  131. def redirect_url(short_code):
  132. """Redirigir a URL original"""
  133. conn = sqlite3.connect(DATABASE)
  134. cursor = conn.cursor()
  135. # Buscar URL original y verificar expiración
  136. cursor.execute('''
  137. SELECT original_url, expires_at FROM urls WHERE short_code = ?
  138. ''', (short_code,))
  139. result = cursor.fetchone()
  140. if result:
  141. original_url, expires_at = result
  142. # Verificar si el enlace ha expirado
  143. if expires_at and is_url_expired(expires_at):
  144. conn.close()
  145. return render_template('404.html', error_message='Este enlace ha expirado'), 404
  146. # Incrementar contador de clicks
  147. cursor.execute('''
  148. UPDATE urls SET clicks = clicks + 1 WHERE short_code = ?
  149. ''', (short_code,))
  150. conn.commit()
  151. conn.close()
  152. return redirect(original_url)
  153. else:
  154. conn.close()
  155. return render_template('404.html'), 404
  156. @app.route('/delete/<short_code>', methods=['POST'])
  157. def delete_url(short_code):
  158. """Eliminar URL acortada"""
  159. user_session = get_user_session()
  160. conn = sqlite3.connect(DATABASE)
  161. cursor = conn.cursor()
  162. # Verificar que el enlace pertenece al usuario
  163. cursor.execute('''
  164. DELETE FROM urls
  165. WHERE short_code = ? AND user_session = ?
  166. ''', (short_code, user_session))
  167. conn.commit()
  168. affected_rows = cursor.rowcount
  169. conn.close()
  170. if affected_rows > 0:
  171. return jsonify({'success': True})
  172. else:
  173. return jsonify({'error': 'Enlace no encontrado o no autorizado'}), 404
  174. @app.route('/stats')
  175. def stats():
  176. """Estadísticas generales"""
  177. conn = sqlite3.connect(DATABASE)
  178. cursor = conn.cursor()
  179. # Estadísticas generales
  180. cursor.execute('SELECT COUNT(*) FROM urls')
  181. total_urls = cursor.fetchone()[0]
  182. cursor.execute('SELECT SUM(clicks) FROM urls')
  183. total_clicks = cursor.fetchone()[0] or 0
  184. # URLs más populares
  185. cursor.execute('''
  186. SELECT original_url, short_code, clicks, created_at
  187. FROM urls
  188. ORDER BY clicks DESC
  189. LIMIT 10
  190. ''')
  191. popular_urls = cursor.fetchall()
  192. conn.close()
  193. return render_template('stats.html',
  194. total_urls=total_urls,
  195. total_clicks=total_clicks,
  196. popular_urls=popular_urls)
  197. if __name__ == '__main__':
  198. init_db()
  199. # Configurar argumentos de línea de comandos
  200. parser = argparse.ArgumentParser(description='Acortador de URLs')
  201. parser.add_argument('--port', '-p', type=int, default=5000,
  202. help='Puerto en el que ejecutar la aplicación (por defecto: 5000)')
  203. args = parser.parse_args()
  204. print(f"Ejecutando en puerto {args.port}")
  205. app.run(debug=True, host='0.0.0.0', port=args.port)