página de usuário

This commit is contained in:
JotaChina committed 2025-12-10 21:15:31 -03:00
1 parent 110a762ff9
commit 29645f3fde
9 files changed
+6975 -16

No files matched your search

+151 -14
View File
@@ -8,10 +8,12 @@ import json
import shutil
import time
import io
import threading
from flask import Flask, request, jsonify, send_file, redirect
from flask_cors import CORS
from werkzeug.utils import secure_filename
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
@@ -43,8 +45,15 @@ class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String(150), nullable=False)
# Novo campo para dizer se é chefe ou não
is_admin = db.Column(db.Boolean, default=False)
# Detalhes do usuário
bio = db.Column(db.String(240), default="")
tags = db.Column(db.String(500), default="") # Vamos salvar separado por vírgula
avatar_url = db.Column(db.String(255), default="/assets/img/default_avatar.png")
cover_url = db.Column(db.String(255), default="/assets/img/default_cover.jpg")
projects = db.relationship('Project', backref='owner', lazy=True)
class SecureModelView(ModelView):
def is_accessible(self):
@@ -67,6 +76,16 @@ class SecureIndexView(AdminIndexView):
return super(SecureIndexView, self).index()
# Tabela de Projetos
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False) # Nome do arquivo físico .mmp
display_name = db.Column(db.String(255), nullable=False) # Nome "bonito" para exibir
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Chave estrangeira ligando ao usuário
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Cria o banco na inicialização se não existir
with app.app_context():
db.create_all()
@@ -93,11 +112,17 @@ def run_jekyll_build():
print("Iniciando build do Jekyll...")
command = [BUNDLE_PATH, "exec", "jekyll", "build", "--destination", "/var/www/html/trens/mmpSearch/"]
try:
# Passamos env=env_vars para que ele ache o ruby3.2
subprocess.run(command, check=True, cwd=BASE_DATA, capture_output=True, text=True, env=env_vars)
print("Jekyll Build Sucesso!")
except subprocess.CalledProcessError as e:
print(f"ERRO no Jekyll Build: {e.stderr}")
# Redirecionamos a saída para DEVNULL para não encher o buffer e travar
subprocess.Popen(
command,
cwd=BASE_DATA,
env=env_vars,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
print("Jekyll Build iniciado em segundo plano (background).")
except Exception as e:
print(f"Erro ao iniciar Jekyll: {e}")
def load_manifest_keys():
"""Carrega as pastas de fábrica (keys do manifesto)."""
@@ -212,15 +237,38 @@ def update_xml_paths_exact(mmp_filename, replacements):
print(f"Erro crítico ao editar XML: {e}")
return False
def process_and_build(filename):
"""Encapsula a chamada do main.py"""
result = process_single_file(filename)
if result["success"]:
rebuild_indexes()
run_jekyll_build()
def run_heavy_tasks_in_background():
"""Esta função roda isolada sem travar o usuário"""
print("--- [BACKGROUND] Iniciando reconstrução de índices ---")
try:
# 1. Isso é Python puro e PESADO (aqui é onde estava travando)
rebuild_indexes()
# 2. Isso gera os manifestos (Python puro)
generate_manifests(SRC_MMPSEARCH)
# 3. Isso chama o subprocesso do Jekyll (Externo)
# Mantém sua função original que usa subprocess
run_jekyll_build()
print("--- [BACKGROUND] Tarefas concluídas com sucesso ---")
except Exception as e:
print(f"--- [BACKGROUND] Erro: {e} ---")
def process_and_build(filename):
"""Função chamada pela rota de upload"""
# Processamento inicial do arquivo (rápido)
result = process_single_file(filename)
if result["success"]:
# Em vez de chamar rebuild_indexes() direto, criamos a Thread
# O Flask vai responder o return abaixo imediatamente,
# enquanto a thread continua rodando no servidor.
task_thread = threading.Thread(target=run_heavy_tasks_in_background)
task_thread.start()
return jsonify({
"message": "Sucesso! Projeto processado.",
"message": "Sucesso! O projeto foi recebido. O site será atualizado em instantes.",
"data": result["data"]
}), 200
else:
@@ -267,6 +315,84 @@ def check_auth():
return jsonify({"logged_in": True, "user": current_user.username})
return jsonify({"logged_in": False})
# No upload_server.py, adicione esta rota
@app.route('/api/user/update', methods=['POST'])
@login_required
def update_profile():
data = request.json
new_username = data.get('username')
# Verificar disponibilidade do username se ele mudou
if new_username and new_username != current_user.username:
existing = User.query.filter_by(username=new_username).first()
if existing:
return jsonify({"error": "Nome de usuário já está em uso."}), 400
current_user.username = new_username
if 'bio' in data:
current_user.bio = data['bio'][:240] # Corta em 240 chars
if 'tags' in data:
# Limpa e formata as tags
current_user.tags = data['tags']
try:
db.session.commit()
return jsonify({"message": "Perfil atualizado!"}), 200
except Exception as e:
return jsonify({"error": "Erro ao salvar."}), 500
# Atualize a rota user_profile antiga para retornar os novos dados
@app.route('/api/user/profile', methods=['GET'])
@login_required
def user_profile():
user_projects = []
for p in current_user.projects:
user_projects.append({
"id": p.id,
"display_name": p.display_name,
"filename": p.filename,
"created_at": p.created_at.strftime("%d/%m/%Y"),
"download_link": f"/api/download/{p.filename}"
})
return jsonify({
"username": current_user.username,
"bio": current_user.bio,
"tags": current_user.tags,
"avatar": current_user.avatar_url,
"cover": current_user.cover_url,
"projects": user_projects,
"samples": [], # Placeholder enquanto não cria a tabela Samples
"recordings": [] # Placeholder enquanto não cria a tabela Recordings
})
@app.route('/api/project/<int:project_id>', methods=['DELETE'])
@login_required
def delete_project(project_id):
project = Project.query.get_or_404(project_id)
# Segurança: Só o dono ou admin pode apagar
if project.owner != current_user and not current_user.is_admin:
return jsonify({"error": "Permissão negada"}), 403
try:
# 1. Remove do Banco
db.session.delete(project)
db.session.commit()
# 2. Remove o arquivo físico (Opcional, mas recomendado para não lotar o disco)
file_path = os.path.join(MMP_FOLDER, project.filename)
if os.path.exists(file_path):
os.remove(file_path)
# Nota: Você pode querer rodar o rebuild_indexes() aqui também se apagar o arquivo
return jsonify({"message": "Projeto removido com sucesso"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# ==============================================================================
# ROTAS PRINCIPAIS
# ==============================================================================
@@ -355,7 +481,18 @@ def upload_file():
return jsonify({"error": "Falha crítica na descompactação."}), 400
elif ext == ".mmp" and upload_path != final_mmp_path:
shutil.move(upload_path, final_mmp_path)
# Registra no banco de dados vinculado ao usuário logado
new_project = Project(
filename=final_mmp_filename,
display_name=clean_name, # Ou name_without_ext se preferir o original
owner=current_user # Flask-Login pega o user atual
)
db.session.add(new_project)
db.session.commit()
print(f"Projeto {clean_name} associado ao usuário {current_user.username}")
# -----------------------------
#
is_clean, missing_files = analyze_mmp_dependencies(final_mmp_filename)
if not is_clean: