Compare commits
2 Commits
14edf5aabd
...
cb4ea96ccc
| Author | SHA1 | Date |
|---|---|---|
|
|
cb4ea96ccc | |
|
|
74186f5c6c |
3265
_data/all.yml
3265
_data/all.yml
File diff suppressed because it is too large
Load Diff
|
|
@ -2,6 +2,7 @@
|
||||||
- 2019winter-song.wav
|
- 2019winter-song.wav
|
||||||
- 43yu.wav
|
- 43yu.wav
|
||||||
- 4r3st.wav
|
- 4r3st.wav
|
||||||
|
- 618.wav
|
||||||
- 7-is-the-answer-060224.wav
|
- 7-is-the-answer-060224.wav
|
||||||
- advait.wav
|
- advait.wav
|
||||||
- aelig.wav
|
- aelig.wav
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
- dubstep-fyrebreak-the-summit.wav
|
- dubstep-fyrebreak-the-summit.wav
|
||||||
- dubstep-vespertine-murdah.wav
|
- dubstep-vespertine-murdah.wav
|
||||||
- electronic-vortexsupernova-headache-smash-120.wav
|
- electronic-vortexsupernova-headache-smash-120.wav
|
||||||
|
- estagiodocencia1.wav
|
||||||
- evolutionthreading-two.wav
|
- evolutionthreading-two.wav
|
||||||
- free-playboi-carti-x-lil-uzi-vert-type-beat-flexin-prod-hxrperr.wav
|
- free-playboi-carti-x-lil-uzi-vert-type-beat-flexin-prod-hxrperr.wav
|
||||||
- frenchcore-vespertine-merde-not-finished.wav
|
- frenchcore-vespertine-merde-not-finished.wav
|
||||||
|
|
@ -70,6 +72,7 @@
|
||||||
- new-yellow-claw-type-song.wav
|
- new-yellow-claw-type-song.wav
|
||||||
- orin-new-year-original-mix.wav
|
- orin-new-year-original-mix.wav
|
||||||
- piano-remake-the-weeknd-the-hills.wav
|
- piano-remake-the-weeknd-the-hills.wav
|
||||||
|
- porch-swing.wav
|
||||||
- prodplue-trap-beat.wav
|
- prodplue-trap-beat.wav
|
||||||
- progressive-house-popsip-electric-dancer-vortexsupernova-edit.wav
|
- progressive-house-popsip-electric-dancer-vortexsupernova-edit.wav
|
||||||
- progressive-house-vespertine-ft-xenia-fischer-thomas-linkwald-feel-the-sun.wav
|
- progressive-house-vespertine-ft-xenia-fischer-thomas-linkwald-feel-the-sun.wav
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
bpm: '123'
|
bpm: '123'
|
||||||
file: reel-2-real-i-like-to-move-it-dj-ayz-edit
|
file: reel-2-real-i-like-to-move-it-dj-ayz-edit
|
||||||
original_title: ' Reel 2 Real - I Like To Move It (DJ AYZ Edit)'
|
original_title: reel-2-real-i-like-to-move-it-dj-ayz-edit
|
||||||
src: /var/www/html/trens/src_mmpSearch/mmp/reel-2-real-i-like-to-move-it-dj-ayz-edit.mmp
|
src: /var/www/html/trens/src_mmpSearch/mmp/reel-2-real-i-like-to-move-it-dj-ayz-edit.mmp
|
||||||
tags:
|
tags:
|
||||||
TAG:
|
TAG:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
"bassdrum_acoustic01_-_Copia.ogg": {
|
"bassdrum_acoustic01_-_Copia.ogg": {
|
||||||
"_isFile": true
|
"_isFile": true
|
||||||
},
|
},
|
||||||
|
"clap02_-_Copia.ogg": {
|
||||||
|
"_isFile": true
|
||||||
|
},
|
||||||
"bassdrum03_-_Copia.ogg": {
|
"bassdrum03_-_Copia.ogg": {
|
||||||
"_isFile": true
|
"_isFile": true
|
||||||
},
|
},
|
||||||
|
|
@ -2404,6 +2407,9 @@
|
||||||
"gravacao_14-29-41.ogg": {
|
"gravacao_14-29-41.ogg": {
|
||||||
"_isFile": true
|
"_isFile": true
|
||||||
},
|
},
|
||||||
|
"teste_tempo_17-21.ogg": {
|
||||||
|
"_isFile": true
|
||||||
|
},
|
||||||
"bassdrum_acoustic02_-_Copia.ogg": {
|
"bassdrum_acoustic02_-_Copia.ogg": {
|
||||||
"_isFile": true
|
"_isFile": true
|
||||||
},
|
},
|
||||||
|
|
@ -2421,6 +2427,12 @@
|
||||||
},
|
},
|
||||||
"gravacao_22-34-01.ogg": {
|
"gravacao_22-34-01.ogg": {
|
||||||
"_isFile": true
|
"_isFile": true
|
||||||
|
},
|
||||||
|
"gravacao_17-37-38.ogg": {
|
||||||
|
"_isFile": true
|
||||||
|
},
|
||||||
|
"gravacao_17-12-29.ogg": {
|
||||||
|
"_isFile": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
BIN
_data/users.db
BIN
_data/users.db
Binary file not shown.
|
|
@ -0,0 +1,281 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Meu Perfil - MMPSearch
|
||||||
|
permalink: /perfil/
|
||||||
|
---
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="publication">
|
||||||
|
<div class="container">
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div class="tabs is-centered is-boxed is-medium mb-6">
|
||||||
|
{% include sidebar.html %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box p-0" style="overflow: hidden; border-radius: 12px; position: relative;">
|
||||||
|
<div id="cover-container" style="height: 200px; background-color: #ddd; background-size: cover; background-position: center; position: relative;">
|
||||||
|
<button class="button is-small is-white is-rounded" style="position: absolute; top: 10px; right: 10px; opacity: 0.8;" onclick="document.getElementById('cover-input').click()">
|
||||||
|
<i class="fa-solid fa-camera mr-1"></i> Alterar Capa
|
||||||
|
</button>
|
||||||
|
<input type="file" id="cover-input" class="is-hidden" accept="image/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-5" style="position: relative;">
|
||||||
|
<div style="position: absolute; top: -60px; left: 30px;">
|
||||||
|
<figure class="image is-128x128">
|
||||||
|
<img id="avatar-img" class="is-rounded" src="https://bulma.io/images/placeholders/128x128.png" style="border: 4px solid white; background: white; object-fit: cover;">
|
||||||
|
</figure>
|
||||||
|
<button class="button is-small is-rounded is-light" style="position: absolute; bottom: 0; right: 0;" onclick="document.getElementById('avatar-input').click()">
|
||||||
|
<i class="fa-solid fa-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<input type="file" id="avatar-input" class="is-hidden" accept="image/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns" style="margin-top: 50px;">
|
||||||
|
<div class="column is-8">
|
||||||
|
<h1 class="title is-3" id="display-username">Carregando...</h1>
|
||||||
|
|
||||||
|
<p class="subtitle is-6 has-text-grey" id="display-bio">...</p>
|
||||||
|
|
||||||
|
<div class="tags mt-3" id="display-tags">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-4 has-text-right">
|
||||||
|
<button class="button is-info is-outlined" id="btn-edit-profile">
|
||||||
|
<i class="fa-solid fa-edit mr-2"></i> Editar Perfil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="edit-form-box" class="box is-hidden has-background-light">
|
||||||
|
<h3 class="title is-5"><i class="fa-solid fa-user-cog"></i> Editar Informações</h3>
|
||||||
|
<form id="profile-form">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Nome de Usuário</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" name="username" id="input-username" required>
|
||||||
|
<span class="icon is-small is-left"><i class="fa-solid fa-user"></i></span>
|
||||||
|
</div>
|
||||||
|
<p class="help">Alterar seu nome pode quebrar links antigos.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Bio (Max 240)</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea class="textarea" name="bio" id="input-bio" maxlength="240" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<p class="help has-text-right" id="char-count">0/240</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Tags (separadas por vírgula)</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" name="tags" id="input-tags" placeholder="Ex: Trap, Dark, Samples, LMMS">
|
||||||
|
<span class="icon is-small is-left"><i class="fa-solid fa-hashtag"></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" class="button is-success">Salvar Alterações</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button type="button" class="button is-light" id="btn-cancel-edit">Cancelar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="update-msg" class="notification is-hidden mt-2"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box mt-5">
|
||||||
|
<div class="tabs is-boxed">
|
||||||
|
<ul>
|
||||||
|
<li class="is-active" data-tab="projects-tab"><a><span class="icon"><i class="fa-solid fa-folder"></i></span> <span>Projetos</span></a></li>
|
||||||
|
<li data-tab="samples-tab"><a><span class="icon"><i class="fa-solid fa-music"></i></span> <span>Samples</span></a></li>
|
||||||
|
<li data-tab="recordings-tab"><a><span class="icon"><i class="fa-solid fa-microphone"></i></span> <span>Gravações</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="projects-tab" class="content-tab-content">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<thead><tr><th>Nome</th><th>Data</th><th>Ações</th></tr></thead>
|
||||||
|
<tbody id="projects-list">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p id="no-projects" class="has-text-grey is-hidden has-text-centered p-4">Nenhum projeto encontrado.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="samples-tab" class="content-tab-content is-hidden">
|
||||||
|
<p class="has-text-centered has-text-grey p-5">Funcionalidade de Samples em desenvolvimento.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="recordings-tab" class="content-tab-content is-hidden">
|
||||||
|
<p class="has-text-centered has-text-grey p-5">Funcionalidade de Gravações em desenvolvimento.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Referências do DOM
|
||||||
|
const els = {
|
||||||
|
username: document.getElementById('display-username'),
|
||||||
|
bio: document.getElementById('display-bio'),
|
||||||
|
tags: document.getElementById('display-tags'),
|
||||||
|
avatar: document.getElementById('avatar-img'),
|
||||||
|
cover: document.getElementById('cover-container'),
|
||||||
|
|
||||||
|
formBox: document.getElementById('edit-form-box'),
|
||||||
|
form: document.getElementById('profile-form'),
|
||||||
|
inpUser: document.getElementById('input-username'),
|
||||||
|
inpBio: document.getElementById('input-bio'),
|
||||||
|
inpTags: document.getElementById('input-tags'),
|
||||||
|
msg: document.getElementById('update-msg'),
|
||||||
|
|
||||||
|
btnEdit: document.getElementById('btn-edit-profile'),
|
||||||
|
btnCancel: document.getElementById('btn-cancel-edit'),
|
||||||
|
|
||||||
|
projectList: document.getElementById('projects-list')
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Carregar Perfil
|
||||||
|
async function loadProfile() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/user/profile');
|
||||||
|
if (res.status === 401) window.location.href = '/login/';
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Preencher UI
|
||||||
|
els.username.textContent = data.username;
|
||||||
|
els.bio.textContent = data.bio || "Sem bio definida.";
|
||||||
|
|
||||||
|
// Preencher Tags
|
||||||
|
els.tags.innerHTML = '';
|
||||||
|
if(data.tags){
|
||||||
|
data.tags.split(',').forEach(tag => {
|
||||||
|
if(tag.trim()) {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'tag is-info is-light mr-1';
|
||||||
|
span.textContent = tag.trim();
|
||||||
|
els.tags.appendChild(span);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preencher Inputs do Form
|
||||||
|
els.inpUser.value = data.username;
|
||||||
|
els.inpBio.value = data.bio || "";
|
||||||
|
els.inpTags.value = data.tags || "";
|
||||||
|
|
||||||
|
// Imagens (Se tiver implementado URL no back)
|
||||||
|
// els.avatar.src = data.avatar;
|
||||||
|
// els.cover.style.backgroundImage = `url('${data.cover}')`;
|
||||||
|
|
||||||
|
// Listar Projetos
|
||||||
|
els.projectList.innerHTML = '';
|
||||||
|
if (data.projects && data.projects.length > 0) {
|
||||||
|
data.projects.forEach(p => {
|
||||||
|
els.projectList.innerHTML += `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${p.display_name}</strong></td>
|
||||||
|
<td>${p.created_at}</td>
|
||||||
|
<td>
|
||||||
|
<a href="${p.download_link}" class="button is-small is-primary is-outlined">
|
||||||
|
<i class="fa-solid fa-download"></i>
|
||||||
|
</a>
|
||||||
|
<button class="button is-small is-danger is-outlined" onclick="deleteProject(${p.id})">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.getElementById('no-projects').classList.remove('is-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(e) { console.error("Erro ao carregar perfil", e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfile();
|
||||||
|
|
||||||
|
// 2. Lógica de Abas
|
||||||
|
document.querySelectorAll('.tabs li').forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.tabs li').forEach(t => t.classList.remove('is-active'));
|
||||||
|
tab.classList.add('is-active');
|
||||||
|
|
||||||
|
document.querySelectorAll('.content-tab-content').forEach(c => c.classList.add('is-hidden'));
|
||||||
|
document.getElementById(tab.dataset.tab).classList.remove('is-hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Editar Perfil (Toggle)
|
||||||
|
els.btnEdit.onclick = () => els.formBox.classList.remove('is-hidden');
|
||||||
|
els.btnCancel.onclick = () => els.formBox.classList.add('is-hidden');
|
||||||
|
|
||||||
|
// 4. Salvar Alterações
|
||||||
|
els.form.onsubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
els.msg.classList.add('is-hidden');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
username: els.inpUser.value,
|
||||||
|
bio: els.inpBio.value,
|
||||||
|
tags: els.inpTags.value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/user/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
els.msg.textContent = data.message || data.error;
|
||||||
|
els.msg.classList.remove('is-hidden');
|
||||||
|
|
||||||
|
if(res.ok) {
|
||||||
|
els.msg.className = "notification is-success mt-2";
|
||||||
|
setTimeout(() => {
|
||||||
|
els.formBox.classList.add('is-hidden');
|
||||||
|
loadProfile(); // Recarrega os dados na tela
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
els.msg.className = "notification is-danger mt-2";
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
els.msg.textContent = "Erro de conexão";
|
||||||
|
els.msg.className = "notification is-danger mt-2";
|
||||||
|
els.msg.classList.remove('is-hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contador de Caracteres da Bio
|
||||||
|
els.inpBio.addEventListener('input', function() {
|
||||||
|
document.getElementById('char-count').textContent = `${this.value.length}/240`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Função Global para Deletar (opcional)
|
||||||
|
window.deleteProject = async (id) => {
|
||||||
|
if(!confirm("Tem certeza que deseja apagar este projeto?")) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/project/${id}`, { method: 'DELETE' });
|
||||||
|
if(res.ok) loadProfile();
|
||||||
|
else alert("Erro ao apagar");
|
||||||
|
} catch(e) { alert("Erro de conexão"); }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -8,10 +8,12 @@ import json
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
import io
|
import io
|
||||||
|
import threading
|
||||||
|
|
||||||
from flask import Flask, request, jsonify, send_file, redirect
|
from flask import Flask, request, jsonify, send_file, redirect
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
|
|
@ -43,8 +45,15 @@ class User(UserMixin, db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
password = db.Column(db.String(150), 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)
|
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):
|
class SecureModelView(ModelView):
|
||||||
def is_accessible(self):
|
def is_accessible(self):
|
||||||
|
|
@ -67,6 +76,16 @@ class SecureIndexView(AdminIndexView):
|
||||||
|
|
||||||
return super(SecureIndexView, self).index()
|
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
|
# Cria o banco na inicialização se não existir
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
@ -93,11 +112,17 @@ def run_jekyll_build():
|
||||||
print("Iniciando build do Jekyll...")
|
print("Iniciando build do Jekyll...")
|
||||||
command = [BUNDLE_PATH, "exec", "jekyll", "build", "--destination", "/var/www/html/trens/mmpSearch/"]
|
command = [BUNDLE_PATH, "exec", "jekyll", "build", "--destination", "/var/www/html/trens/mmpSearch/"]
|
||||||
try:
|
try:
|
||||||
# Passamos env=env_vars para que ele ache o ruby3.2
|
# Redirecionamos a saída para DEVNULL para não encher o buffer e travar
|
||||||
subprocess.run(command, check=True, cwd=BASE_DATA, capture_output=True, text=True, env=env_vars)
|
subprocess.Popen(
|
||||||
print("Jekyll Build Sucesso!")
|
command,
|
||||||
except subprocess.CalledProcessError as e:
|
cwd=BASE_DATA,
|
||||||
print(f"ERRO no Jekyll Build: {e.stderr}")
|
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():
|
def load_manifest_keys():
|
||||||
"""Carrega as pastas de fábrica (keys do manifesto)."""
|
"""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}")
|
print(f"Erro crítico ao editar XML: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def process_and_build(filename):
|
def run_heavy_tasks_in_background():
|
||||||
"""Encapsula a chamada do main.py"""
|
"""Esta função roda isolada sem travar o usuário"""
|
||||||
result = process_single_file(filename)
|
print("--- [BACKGROUND] Iniciando reconstrução de índices ---")
|
||||||
if result["success"]:
|
try:
|
||||||
rebuild_indexes()
|
# 1. Isso é Python puro e PESADO (aqui é onde estava travando)
|
||||||
run_jekyll_build()
|
rebuild_indexes()
|
||||||
|
|
||||||
|
# 2. Isso gera os manifestos (Python puro)
|
||||||
generate_manifests(SRC_MMPSEARCH)
|
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({
|
return jsonify({
|
||||||
"message": "Sucesso! Projeto processado.",
|
"message": "Sucesso! O projeto foi recebido. O site será atualizado em instantes.",
|
||||||
"data": result["data"]
|
"data": result["data"]
|
||||||
}), 200
|
}), 200
|
||||||
else:
|
else:
|
||||||
|
|
@ -267,6 +315,84 @@ def check_auth():
|
||||||
return jsonify({"logged_in": True, "user": current_user.username})
|
return jsonify({"logged_in": True, "user": current_user.username})
|
||||||
return jsonify({"logged_in": False})
|
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
|
# ROTAS PRINCIPAIS
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -355,7 +481,18 @@ def upload_file():
|
||||||
return jsonify({"error": "Falha crítica na descompactação."}), 400
|
return jsonify({"error": "Falha crítica na descompactação."}), 400
|
||||||
elif ext == ".mmp" and upload_path != final_mmp_path:
|
elif ext == ".mmp" and upload_path != final_mmp_path:
|
||||||
shutil.move(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)
|
is_clean, missing_files = analyze_mmp_dependencies(final_mmp_filename)
|
||||||
|
|
||||||
if not is_clean:
|
if not is_clean:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue