Edoruin commited on
Commit
f7f0a1a
·
1 Parent(s): abddf87

sustitude flet by flask

Browse files
app/main.py CHANGED
@@ -8,8 +8,10 @@ import time
8
  import base64
9
  import requests
10
  import datetime
11
- from flask import Flask, render_template, request, jsonify, redirect, url_for
12
  from flask_socketio import SocketIO, emit
 
 
13
  import gitlab
14
  import telebot
15
  from telebot import types
@@ -18,7 +20,50 @@ from dotenv import load_dotenv
18
 
19
  load_dotenv()
20
 
21
- # --- CLASE PARA PERSISTENCIA ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  class LoanManager:
23
  def __init__(self, filename="prestamos.json"):
24
  self.filename = filename
@@ -54,12 +99,7 @@ class LoanManager:
54
 
55
  loan_mgr = LoanManager()
56
 
57
- # --- APP CONFIG ---
58
- app = Flask(__name__)
59
- app.config['SECRET_KEY'] = 'maker-secret-key'
60
- socketio = SocketIO(app, cors_allowed_origins="*")
61
-
62
- # --- CONFIGURACIÓN DE VARIABLES GLOBALES ---
63
  TG_TOKEN = os.getenv("TELEGRAM_TOKEN")
64
  TG_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
65
  try:
@@ -69,14 +109,12 @@ except:
69
 
70
  GOOGLE_PROXY_URL = os.getenv("GOOGLE_PROXY_URL") or "https://script.google.com/macros/s/AKfycbz7z1Jb0vsur42GmmqrL3PVXeRkN2WxSojFDIleEDoLOg6MnrmJjb_uuPcQ15CTwyzD/exec"
71
 
72
- # --- CONFIGURACIÓN DE PROXY PARA EL BOT ---
73
  if TG_TOKEN and GOOGLE_PROXY_URL:
74
  base_url = GOOGLE_PROXY_URL.split('?')[0]
75
  telebot.apihelper.API_URL = base_url + "?path={1}&token={0}"
76
  telebot.apihelper.CONNECT_TIMEOUT = 60
77
  telebot.apihelper.READ_TIMEOUT = 60
78
 
79
- # Inicializar bot
80
  bot = telebot.TeleBot(TG_TOKEN) if TG_TOKEN else None
81
 
82
  def escape_md(text):
@@ -154,6 +192,43 @@ if bot:
154
  def index():
155
  return render_template('index.html', title="MAKER STATION")
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  @app.route('/prestamos')
158
  def prestamos():
159
  loans = loan_mgr.get_all()
@@ -207,6 +282,7 @@ def api_prestamo():
207
  return jsonify({"status": "success", "id": loan_id})
208
 
209
  @app.route('/repos')
 
210
  def repos():
211
  GITLAB_URL = "https://gitlab.com"
212
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
@@ -217,10 +293,12 @@ def repos():
217
  try:
218
  gl = gitlab.Gitlab(GITLAB_URL, private_token=GIT_TOKEN)
219
  projects = gl.groups.get(GIT_GROUP).projects.list(all=True)
220
- except: pass
 
221
  return render_template('repos.html', title="Proyectos", projects=projects)
222
 
223
  @app.route('/ver/<pid>/<pname>')
 
224
  def ver_repo(pid, pname):
225
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
226
  GITLAB_URL = "https://gitlab.com"
 
8
  import base64
9
  import requests
10
  import datetime
11
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
12
  from flask_socketio import SocketIO, emit
13
+ from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
14
+ import mysql.connector
15
  import gitlab
16
  import telebot
17
  from telebot import types
 
20
 
21
  load_dotenv()
22
 
23
+ # --- APP CONFIG ---
24
+ app = Flask(__name__)
25
+ app.config['SECRET_KEY'] = os.getenv("SECRET_KEY", "maker-secret-key")
26
+ socketio = SocketIO(app, cors_allowed_origins="*")
27
+
28
+ # --- LOGIN MANAGER ---
29
+ login_manager = LoginManager()
30
+ login_manager.init_app(app)
31
+ login_manager.login_view = 'login'
32
+ login_manager.login_message = "Por favor, inicia sesión para acceder."
33
+ login_manager.login_message_category = "red"
34
+
35
+ class User(UserMixin):
36
+ def __init__(self, id, username):
37
+ self.id = id
38
+ self.username = username
39
+
40
+ # --- MYSQL CONFIG ---
41
+ def get_db_connection():
42
+ try:
43
+ conn = mysql.connector.connect(
44
+ host=os.getenv("MYSQL_HOST", "localhost"),
45
+ user=os.getenv("MYSQL_USER", "root"),
46
+ password=os.getenv("MYSQL_PASSWORD", ""),
47
+ database=os.getenv("MYSQL_DB", "makerpage")
48
+ )
49
+ return conn
50
+ except Exception as e:
51
+ print(f"Error MySQL: {e}")
52
+ return None
53
+
54
+ @login_manager.user_loader
55
+ def load_user(user_id):
56
+ conn = get_db_connection()
57
+ if not conn: return None
58
+ cursor = conn.cursor(dictionary=True)
59
+ cursor.execute("SELECT id, username FROM users WHERE id = %s", (user_id,))
60
+ user_data = cursor.fetchone()
61
+ conn.close()
62
+ if user_data:
63
+ return User(str(user_data['id']), user_data['username'])
64
+ return None
65
+
66
+ # --- CLASE PARA PERSISTENCIA DE PRÉSTAMOS ---
67
  class LoanManager:
68
  def __init__(self, filename="prestamos.json"):
69
  self.filename = filename
 
99
 
100
  loan_mgr = LoanManager()
101
 
102
+ # --- CONFIGURACIÓN DE TELEGRAM ---
 
 
 
 
 
103
  TG_TOKEN = os.getenv("TELEGRAM_TOKEN")
104
  TG_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
105
  try:
 
109
 
110
  GOOGLE_PROXY_URL = os.getenv("GOOGLE_PROXY_URL") or "https://script.google.com/macros/s/AKfycbz7z1Jb0vsur42GmmqrL3PVXeRkN2WxSojFDIleEDoLOg6MnrmJjb_uuPcQ15CTwyzD/exec"
111
 
 
112
  if TG_TOKEN and GOOGLE_PROXY_URL:
113
  base_url = GOOGLE_PROXY_URL.split('?')[0]
114
  telebot.apihelper.API_URL = base_url + "?path={1}&token={0}"
115
  telebot.apihelper.CONNECT_TIMEOUT = 60
116
  telebot.apihelper.READ_TIMEOUT = 60
117
 
 
118
  bot = telebot.TeleBot(TG_TOKEN) if TG_TOKEN else None
119
 
120
  def escape_md(text):
 
192
  def index():
193
  return render_template('index.html', title="MAKER STATION")
194
 
195
+ @app.route('/login', methods=['GET', 'POST'])
196
+ def login():
197
+ if current_user.is_authenticated:
198
+ return redirect(url_for('repos'))
199
+
200
+ if request.method == 'POST':
201
+ username = request.form.get('username')
202
+ password = request.form.get('password')
203
+
204
+ conn = get_db_connection()
205
+ if not conn:
206
+ flash("Error de conexión a la base de datos", "red")
207
+ return render_template('login.html')
208
+
209
+ cursor = conn.cursor(dictionary=True)
210
+ # NOTA: En producción usar hashing (e.g. werkzeug.security)
211
+ cursor.execute("SELECT id, username FROM users WHERE username = %s AND password = %s", (username, password))
212
+ user_data = cursor.fetchone()
213
+ conn.close()
214
+
215
+ if user_data:
216
+ user = User(str(user_data['id']), user_data['username'])
217
+ login_user(user)
218
+ flash(f"¡Bienvenido, {username}!", "green")
219
+ return redirect(url_for('repos'))
220
+ else:
221
+ flash("Usuario o contraseña incorrectos", "red")
222
+
223
+ return render_template('login.html', title="Login")
224
+
225
+ @app.route('/logout')
226
+ @login_required
227
+ def logout():
228
+ logout_user()
229
+ flash("Has cerrado sesión", "blue")
230
+ return redirect(url_for('index'))
231
+
232
  @app.route('/prestamos')
233
  def prestamos():
234
  loans = loan_mgr.get_all()
 
282
  return jsonify({"status": "success", "id": loan_id})
283
 
284
  @app.route('/repos')
285
+ @login_required
286
  def repos():
287
  GITLAB_URL = "https://gitlab.com"
288
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
 
293
  try:
294
  gl = gitlab.Gitlab(GITLAB_URL, private_token=GIT_TOKEN)
295
  projects = gl.groups.get(GIT_GROUP).projects.list(all=True)
296
+ except Exception as e:
297
+ print(f"GitLab Error: {e}")
298
  return render_template('repos.html', title="Proyectos", projects=projects)
299
 
300
  @app.route('/ver/<pid>/<pname>')
301
+ @login_required
302
  def ver_repo(pid, pname):
303
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
304
  GITLAB_URL = "https://gitlab.com"
app/requirements.txt CHANGED
@@ -7,3 +7,5 @@ python-dotenv
7
  gunicorn
8
  eventlet
9
  markdown
 
 
 
7
  gunicorn
8
  eventlet
9
  markdown
10
+ flask-login
11
+ mysql-connector-python
app/static/manifest.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Maker Station",
3
+ "short_name": "MakerStation",
4
+ "description": "Panel de control para Maker Station",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#0f172a",
8
+ "theme_color": "#38bdf8",
9
+ "icons": [
10
+ {
11
+ "src": "/static/assets/icon192x192.png",
12
+ "sizes": "192x192",
13
+ "type": "image/png"
14
+ },
15
+ {
16
+ "src": "/static/assets/icon-512x512.png",
17
+ "sizes": "512x512",
18
+ "type": "image/png"
19
+ },
20
+ {
21
+ "src": "/static/assets/apple-touch-icon.png",
22
+ "sizes": "180x180",
23
+ "type": "image/png",
24
+ "purpose": "maskable"
25
+ }
26
+ ]
27
+ }
app/static/sw.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CACHE_NAME = 'maker-station-v1';
2
+ const ASSETS = [
3
+ '/',
4
+ '/static/css/style.css',
5
+ '/static/js/script.js',
6
+ '/static/assets/favicon.png',
7
+ '/static/assets/icon192x192.png'
8
+ ];
9
+
10
+ self.addEventListener('install', (event) => {
11
+ event.waitUntil(
12
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
13
+ );
14
+ });
15
+
16
+ self.addEventListener('fetch', (event) => {
17
+ event.respondWith(
18
+ caches.match(event.request).then((response) => {
19
+ return response || fetch(event.request);
20
+ })
21
+ );
22
+ });
app/templates/base.html CHANGED
@@ -5,6 +5,15 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>{{ title or "MAKERSTATION" }}</title>
 
 
 
 
 
 
 
 
 
8
  <link rel="icon" href="{{ url_for('static', filename='assets/favicon.png') }}">
9
  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Outfit:wght@500;700&display=swap"
@@ -21,16 +30,34 @@
21
  <span>MAKER STATION</span>
22
  </a>
23
  <div class="nav-links">
24
- <a href="/repos" class="nav-item">PROYECTOS</a>
25
  <a href="/prestamos" class="nav-item">PRÉSTAMOS</a>
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
27
- <button id="notif-btn" class="btn-icon">
28
- <i class="fas fa-bell"></i>
29
- </button>
30
  </div>
31
  </nav>
32
 
33
  <main class="content-wrapper">
 
 
 
 
 
 
 
 
 
 
34
  {% block content %}{% endblock %}
35
  </main>
36
 
@@ -38,12 +65,8 @@
38
 
39
  <script src="{{ url_for('static', filename='js/script.js') }}"></script>
40
  <script>
 
41
  const socket = io();
42
-
43
- socket.on('connect', () => {
44
- console.log('Static Connection established');
45
- });
46
-
47
  socket.on('notification', (data) => {
48
  showNotification(data.text, data.color || 'blue');
49
  });
@@ -56,7 +79,10 @@
56
  container.appendChild(notif);
57
 
58
  if ("Notification" in window && Notification.permission === "granted") {
59
- new Notification("MAKER STATION", { body: text });
 
 
 
60
  }
61
 
62
  setTimeout(() => {
@@ -65,11 +91,44 @@
65
  }, 5000);
66
  }
67
 
 
68
  document.getElementById('notif-btn').addEventListener('click', () => {
69
  if ("Notification" in window) {
70
  Notification.requestPermission();
71
  }
72
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  </script>
74
  </body>
75
 
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>{{ title or "MAKERSTATION" }}</title>
8
+
9
+ <!-- PWA Meta Tags -->
10
+ <link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
11
+ <meta name="theme-color" content="#38bdf8">
12
+ <link rel="apple-touch-icon" href="{{ url_for('static', filename='assets/apple-touch-icon.png') }}">
13
+ <meta name="apple-mobile-web-app-capable" content="yes">
14
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
15
+ <meta name="apple-mobile-web-app-title" content="Maker Station">
16
+
17
  <link rel="icon" href="{{ url_for('static', filename='assets/favicon.png') }}">
18
  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
19
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Outfit:wght@500;700&display=swap"
 
30
  <span>MAKER STATION</span>
31
  </a>
32
  <div class="nav-links">
 
33
  <a href="/prestamos" class="nav-item">PRÉSTAMOS</a>
34
+ <a href="/repos" class="nav-item">PROYECTOS</a>
35
+ {% if current_user.is_authenticated %}
36
+ <a href="/logout" class="nav-item" style="color: #ef4444;"><i class="fas fa-sign-out-alt"></i></a>
37
+ {% endif %}
38
+ </div>
39
+ <div style="display: flex; gap: 1rem; align-items: center;">
40
+ <button id="install-btn" class="btn-icon" style="display: none;">
41
+ <i class="fas fa-download"></i>
42
+ </button>
43
+ <button id="notif-btn" class="btn-icon">
44
+ <i class="fas fa-bell"></i>
45
+ </button>
46
  </div>
 
 
 
47
  </div>
48
  </nav>
49
 
50
  <main class="content-wrapper">
51
+ {% with messages = get_flashed_messages(with_categories=true) %}
52
+ {% if messages %}
53
+ {% for category, message in messages %}
54
+ <div class="notification glass {{ category }}" style="margin-bottom: 2rem;">
55
+ <i class="fas fa-exclamation-circle"></i> <span>{{ message }}</span>
56
+ </div>
57
+ {% endfor %}
58
+ {% endif %}
59
+ {% endwith %}
60
+
61
  {% block content %}{% endblock %}
62
  </main>
63
 
 
65
 
66
  <script src="{{ url_for('static', filename='js/script.js') }}"></script>
67
  <script>
68
+ // Socket.IO
69
  const socket = io();
 
 
 
 
 
70
  socket.on('notification', (data) => {
71
  showNotification(data.text, data.color || 'blue');
72
  });
 
79
  container.appendChild(notif);
80
 
81
  if ("Notification" in window && Notification.permission === "granted") {
82
+ new Notification("MAKER STATION", {
83
+ body: text,
84
+ icon: "/static/assets/icon192x192.png"
85
+ });
86
  }
87
 
88
  setTimeout(() => {
 
91
  }, 5000);
92
  }
93
 
94
+ // Notification Permission
95
  document.getElementById('notif-btn').addEventListener('click', () => {
96
  if ("Notification" in window) {
97
  Notification.requestPermission();
98
  }
99
  });
100
+
101
+ // PWA Service Worker Registration
102
+ if ('serviceWorker' in navigator) {
103
+ window.addEventListener('load', () => {
104
+ navigator.serviceWorker.register('/static/sw.js').then((reg) => {
105
+ console.log('SW Registered', reg);
106
+ }).catch((err) => {
107
+ console.log('SW Registration failed', err);
108
+ });
109
+ });
110
+ }
111
+
112
+ // PWA Install Prompt
113
+ let deferredPrompt;
114
+ const installBtn = document.getElementById('install-btn');
115
+
116
+ window.addEventListener('beforeinstallprompt', (e) => {
117
+ e.preventDefault();
118
+ deferredPrompt = e;
119
+ installBtn.style.display = 'block';
120
+ });
121
+
122
+ installBtn.addEventListener('click', async () => {
123
+ if (deferredPrompt) {
124
+ deferredPrompt.prompt();
125
+ const { outcome } = await deferredPrompt.userChoice;
126
+ if (outcome === 'accepted') {
127
+ installBtn.style.display = 'none';
128
+ }
129
+ deferredPrompt = null;
130
+ }
131
+ });
132
  </script>
133
  </body>
134
 
app/templates/login.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="hero section" style="text-align: center; margin-bottom: 3rem;">
5
+ <h1>INICIO DE SESIÓN</h1>
6
+ <p class="text-dim">Accede a los proyectos de Maker Station.</p>
7
+ </div>
8
+
9
+ <div class="glass" style="max-width: 400px; margin: 0 auto; padding: 2.5rem; border-radius: 24px;">
10
+ <form method="POST">
11
+ <div class="form-group">
12
+ <label for="username">Usuario</label>
13
+ <input type="text" id="username" name="username" placeholder="Tu usuario" required autofocus>
14
+ </div>
15
+ <div class="form-group">
16
+ <label for="password">Contraseña</label>
17
+ <input type="password" id="password" name="password" placeholder="••••••••" required>
18
+ </div>
19
+ <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;">
20
+ ENTRAR <i class="fas fa-sign-in-alt" style="margin-left: 0.5rem;"></i>
21
+ </button>
22
+ </form>
23
+
24
+ <div style="margin-top: 2rem; text-align: center; font-size: 0.85rem;" class="text-dim">
25
+ <p>Esta sección está protegida para miembros de Maker Station.</p>
26
+ </div>
27
+ </div>
28
+ {% endblock %}