página de usuário

This commit is contained in:
JotaChina committed 2025-12-10 21:52:33 -03:00
1 parent 0a8538e5f2
commit 00f001aa6d
3 files changed
+211 -113

No files matched your search

+108 -28
View File
@@ -40,6 +40,10 @@ bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# ==============================================================================
# CLASSES UTILIZADAS
# ==============================================================================
# --- MODELO DE USUÁRIO ---
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
@@ -54,6 +58,8 @@ class User(UserMixin, db.Model):
cover_url = db.Column(db.String(255), default="/assets/img/default_cover.jpg")
projects = db.relationship('Project', backref='owner', lazy=True)
samples = db.relationship('Sample', backref='owner', lazy=True)
recordings = db.relationship('Recording', backref='owner', lazy=True)
class SecureModelView(ModelView):
def is_accessible(self):
@@ -75,6 +81,10 @@ class SecureIndexView(AdminIndexView):
# return "<h1>Acesso Negado</h1><p>Você precisa ser admin.</p>", 403
return super(SecureIndexView, self).index()
# ==============================================================================
# CLASSES DE ARTEFATOS
# ==============================================================================
# Tabela de Projetos
class Project(db.Model):
@@ -86,6 +96,21 @@ class Project(db.Model):
# Chave estrangeira ligando ao usuário
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
class Sample(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
display_name = db.Column(db.String(255), nullable=False)
category = db.Column(db.String(50), default="Uncategorized") # Ex: drums, bass
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
class Recording(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
display_name = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
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()
@@ -322,30 +347,25 @@ 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
if User.query.filter_by(username=new_username).first():
return jsonify({"error": "Nome 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']
if 'bio' in data: current_user.bio = data['bio'][:240]
if 'tags' in data: current_user.tags = data['tags']
try:
db.session.commit()
return jsonify({"message": "Perfil atualizado!"}), 200
except Exception as e:
except Exception:
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():
# Projetos
user_projects = []
for p in current_user.projects:
user_projects.append({
@@ -356,6 +376,22 @@ def user_profile():
"download_link": f"/api/download/{p.filename}"
})
# Samples
user_samples = []
for s in current_user.samples:
# Define a URL pública baseada na estrutura do seu Jekyll/Apache
# Ex: se SAMPLE_SRC é mapeado para /samples/ no navegador
# Ajuste o prefixo '/samples/' conforme sua configuração de pastas públicas
public_url = f"/samples/{s.category}/{s.filename}"
user_samples.append({
"id": s.id,
"display_name": s.display_name,
"category": s.category,
"created_at": s.created_at.strftime("%d/%m/%Y"),
"download_link": public_url
})
return jsonify({
"username": current_user.username,
"bio": current_user.bio,
@@ -363,12 +399,9 @@ def user_profile():
"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
"samples": user_samples
})
@app.route('/api/project/<int:project_id>', methods=['DELETE'])
@login_required
def delete_project(project_id):
@@ -393,6 +426,7 @@ def delete_project(project_id):
return jsonify({"message": "Projeto removido com sucesso"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# ==============================================================================
# ROTAS PRINCIPAIS
# ==============================================================================
@@ -540,22 +574,70 @@ def resolve_samples():
@app.route('/api/upload/sample', methods=['POST'])
@login_required # <--- PROTEGIDO
@login_required
def upload_sample_standalone():
if 'sample_file' not in request.files: return jsonify({'error': 'Nenhum arquivo'}), 400
if 'sample_file' not in request.files:
return jsonify({'error': 'Nenhum arquivo'}), 400
file = request.files['sample_file']
subfolder = request.form.get('subfolder', '').strip()
# Pega a pasta escolhida pelo usuário, padrão 'uncategorized'
raw_subfolder = request.form.get('subfolder', 'uncategorized').strip()
# Limpa o nome da pasta para evitar ../../etc/passwd
safe_subfolder = secure_filename(raw_subfolder)
if not safe_subfolder:
safe_subfolder = 'uncategorized'
if file and allowed_sample(file.filename):
filename = secure_filename(file.filename)
safe_subfolder = subfolder.replace('..', '').strip('/')
# Cria o caminho físico final: SAMPLE_SRC + Categoria + Arquivo
target_dir = os.path.join(SAMPLE_SRC, safe_subfolder)
os.makedirs(target_dir, exist_ok=True)
file.save(os.path.join(target_dir, filename))
generate_manifests(SRC_MMPSEARCH)
run_jekyll_build()
return jsonify({'message': 'Sample enviado!'}), 200
return jsonify({'error': 'Erro no arquivo'}), 400
file_path = os.path.join(target_dir, filename)
file.save(file_path)
# Salva no Banco
new_sample = Sample(
filename=filename,
display_name=filename,
category=safe_subfolder,
owner=current_user
)
db.session.add(new_sample)
db.session.commit()
# Opcional: Atualizar índices se necessário
# generate_manifests(SRC_MMPSEARCH)
# run_jekyll_build()
return jsonify({'message': 'Sample enviado com sucesso!'}), 200
return jsonify({'error': 'Tipo de arquivo inválido'}), 400
@app.route('/api/sample/<int:sample_id>', methods=['DELETE'])
@login_required
def delete_sample(sample_id):
sample = Sample.query.get_or_404(sample_id)
if sample.owner != current_user and not current_user.is_admin:
return jsonify({"error": "Proibido"}), 403
try:
# 1. Tenta remover o arquivo físico
file_path = os.path.join(SAMPLE_SRC, sample.category, sample.filename)
if os.path.exists(file_path):
os.remove(file_path)
# 2. Remove do banco
db.session.delete(sample)
db.session.commit()
return jsonify({"message": "Sample apagado"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# Inicializa o Flask-Admin
admin = Admin(
app,
@@ -568,8 +650,6 @@ admin = Admin(
admin.add_view(SecureModelView(User, db.session, name="Gerenciar Usuários"))
if __name__ == "__main__":
# DEBUG: Isso vai mostrar no log todas as rotas que o Flask reconheceu
print(app.url_map)
print("Iniciando servidor na porta 33002 (HTTP Mode)...")
admin = Admin(app, name='MMP Admin', url='/api/admin', index_view=SecureIndexView())
admin.add_view(SecureModelView(User, db.session, name="Gerenciar Usuários"))
app.run(host="0.0.0.0", port=33002, debug=True)