diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9b387bfe..c5478f9b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,24 +5,21 @@ on: types: [opened, reopened, synchronize] jobs: - call-external-service: + call-pr-ai-service: runs-on: ubuntu-latest steps: - name: Debug JSON run: | echo "Repository: ${{ github.repository }}" echo "PR number: ${{ github.event.pull_request.number }}" - echo "Email: ${{ secrets.email }}" - - name: Chamar API PR AI + - name: Chamar API run: | curl -X POST \ -H "Content-Type: application/json" \ -H "X-API-TOKEN: ${{ secrets.PRAI_API_TOKEN }}" \ -d "{ \"repository\": \"${{ github.repository }}\", - \"pr_number\": \"${{ github.event.pull_request.number }}\", - \"email\": \"${{ secrets.email }}\", - \"password\": \"${{ secrets.password }}\" + \"pr_number\": \"${{ github.event.pull_request.number }}\" }" \ https://api.softwareai.site/api/prai/gen diff --git a/Back-End/Modules/Resolvers/user_identifier.py b/Back-End/Modules/Resolvers/user_identifier.py index f341f9c3..6b265f24 100644 --- a/Back-End/Modules/Resolvers/user_identifier.py +++ b/Back-End/Modules/Resolvers/user_identifier.py @@ -72,9 +72,29 @@ def resolve_user_identifier(identifier): # fallback: tenta buscar por email ignorando espaços return User.query.filter_by(email=str(identifier).strip()).first() +def auth_user(logs_collection, app): + with app.app_context(): + header_token = None + auth_header = request.headers.get('Authorization') + if auth_header and auth_header.lower().startswith('bearer '): + header_token = auth_header.split(None, 1)[1].strip() + if not header_token: + header_token = request.headers.get('X-API-TOKEN') + + user = None + # se token informado, resolve usuário + if header_token: + try: + user = get_user_by_access_token(header_token) + except Exception as e: + log_action(logs_collection, 'dashboard_token_lookup_error', {'error': str(e)}, level='warning') + return None, None, "invalid" + - -def auth_user(email, password, logs_collection, app): + numeric_user_id = user.id + return user, numeric_user_id, "success" + +def auth_user_fallback(email, password, logs_collection, app): """ Autentica usuário por token (se já estiver em g.current_user) ou por email+senha. Retorna tupla (user, access_token, status). @@ -85,6 +105,7 @@ def auth_user(email, password, logs_collection, app): - "invalid" -> credenciais inválidas """ with app.app_context(): + # se já veio pelo decorator (token válido) if getattr(g, "current_user", None): user = g.current_user try: @@ -94,17 +115,18 @@ def auth_user(email, password, logs_collection, app): except Exception: db.session.rollback() log_action(logs_collection, 'login_success_token', {'message': 'login_success_token_by_token', 'username': user.email}, user=user.id) + # garante que retornamos o token atual do usuário return user, user.acess_token, "success" # 2) Autenticação por email + senha if not email or not password: - log_action(logs_collection, 'auth_user', {'username': email, 'message': f"email e password nao presentes"}, user=user.id, level='warning') - + logger.info("????????") return None, None, "invalid" user = resolve_user_identifier(email) + # evita usar user.id quando user é None (corrige crash no log) if not user or not user.check_password(password): - log_action(logs_collection, 'login_failed', {'message': 'login_failed in if not user or not user.check_password(password):'}, level='warning', user=user.id) + log_action(logs_collection, 'login_failed', {'message': 'login_failed in if not user or not user.check_password(password):'}, level='warning', user=(user.id if user else None)) return None, None, "invalid" # atualiza last_seen @@ -121,19 +143,10 @@ def auth_user(email, password, logs_collection, app): used = user.tokens_used or 0 remaining = limit - used - if token_needs_creation: - if remaining <= 0: - log_action(logs_collection, 'login_blocked_no_tokens', - {'message': 'login_blocked_no_tokens', 'remaining': remaining}, user=user.id, level='error') - return user, None, "token_limit" - new_token = user.create_access_token_for_user() - db.session.add(user) - db.session.commit() - return user, new_token, "success" - + # se token não precisava ser recriado, só retorna o token atual + g.current_user = user return user, user.acess_token, "success" - def is_token_revoked_or_expired(user: User): if not user: log_action(logs_collection, 'is_token_revoked_or_expired', {'message': "Usuário não encontrado"}, user=None) diff --git a/Back-End/Workflows/PullRequest/pr.yml b/Back-End/Workflows/PullRequest/pr.yml index 9b387bfe..c5478f9b 100644 --- a/Back-End/Workflows/PullRequest/pr.yml +++ b/Back-End/Workflows/PullRequest/pr.yml @@ -5,24 +5,21 @@ on: types: [opened, reopened, synchronize] jobs: - call-external-service: + call-pr-ai-service: runs-on: ubuntu-latest steps: - name: Debug JSON run: | echo "Repository: ${{ github.repository }}" echo "PR number: ${{ github.event.pull_request.number }}" - echo "Email: ${{ secrets.email }}" - - name: Chamar API PR AI + - name: Chamar API run: | curl -X POST \ -H "Content-Type: application/json" \ -H "X-API-TOKEN: ${{ secrets.PRAI_API_TOKEN }}" \ -d "{ \"repository\": \"${{ github.repository }}\", - \"pr_number\": \"${{ github.event.pull_request.number }}\", - \"email\": \"${{ secrets.email }}\", - \"password\": \"${{ secrets.password }}\" + \"pr_number\": \"${{ github.event.pull_request.number }}\" }" \ https://api.softwareai.site/api/prai/gen diff --git a/Back-End/api.py b/Back-End/api.py index d9a0338e..4ca66304 100644 --- a/Back-End/api.py +++ b/Back-End/api.py @@ -9,7 +9,7 @@ from bson.json_util import dumps from datetime import datetime, timedelta, timezone -from flask import g, Flask, Response, request, jsonify +from flask import g, Flask, Response, request, jsonify, send_file from flask_cors import CORS from asgiref.wsgi import WsgiToAsgi from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity @@ -20,7 +20,7 @@ from Modules.Resolvers.pr_process import process_pull_request from Modules.Resolvers.user_identifier import auth_user, require_user_token, resolve_user_identifier from Modules.Geters.systemsettings import * - +from Modules.Geters.user_by_access_token import get_user_by_access_token from Models.mongoDB import ( Log, @@ -92,7 +92,8 @@ def get_plans_data(): 'PR basic automation', '5 - 10 PRs/mo', 'Logs basic' - ] + ], + 'payment_link': '' }, "Premium": { 'price': 15, @@ -102,7 +103,8 @@ def get_plans_data(): '20 - 40 PRs/mo', 'Logs advanced', 'API access' - ] + ], + 'payment_link': '' }, "Pro": { 'price': 29, @@ -114,9 +116,11 @@ def get_plans_data(): 'Auto-Commit Intelligence', 'Smart Threshold Detection', 'Context-Aware Messages' - ] + ], + 'payment_link': '' } } + @app.route('/api/public/plans-features', methods=['GET']) @limiter.limit("5 per minute") def public_plans_features(): @@ -126,7 +130,7 @@ def public_plans_features(): }), 200 -@app.route('/api/plans-features//', methods=['GET']) +@app.route('/api/plans-features', methods=['GET']) def user_plan_limit(email, password): user, _, status = auth_user(email, password, logs_collection, app) if status != "success" or not user: @@ -146,7 +150,7 @@ def user_plan_limit(email, password): @app.route('/api/register', methods=['POST']) def register(): """ - Registro simples: cria usuário, seta senha e cria access_token, persiste. + Registro simples: cria usuário, seta senha e cria acess_token, persiste. """ data = request.get_json() or {} email = data.get("email") @@ -160,14 +164,14 @@ def register(): try: new_user = User(email=email) new_user.set_password(password) - access_token = new_user.create_access_token_for_user(expires_days if expires_days is not None else TOKEN_DEFAULT_EXPIRES_DAYS) + acess_token = new_user.create_access_token_for_user(expires_days if expires_days is not None else TOKEN_DEFAULT_EXPIRES_DAYS) db.session.add(new_user) db.session.commit() log_action(logs_collection, 'user_registered', {'message': "Usuário criado com sucesso", 'username': email}) return jsonify({ "message": "Usuário criado com sucesso", - "access_token": access_token, + "acess_token": acess_token, "user_id": new_user.id, "expires_at": new_user.expires_at.isoformat() if new_user.expires_at else None }), 201 @@ -188,17 +192,10 @@ def login(): if status == "invalid" or not user: return jsonify({"error": "Credenciais inválidas"}), 401 - if status == "token_limit": - return jsonify({ - "error": "Limite de tokens mensal atingido. Atualize o plano para gerar/renovar o access token.", - "remaining_tokens": (user.limit_monthly_tokens or 0) - (user.tokens_used or 0), - "plan_name": user.plan_name - }), 402 # Payment Required - log_action(logs_collection, 'login_success', {'username': email}, user=user.id) return jsonify({ "message": f"Bem-vindo, {user.email}!", - "access_token": user.acess_token, + "acess_token": user.acess_token, "user_id": user.id, "plan_name": user.plan_name, "limit_monthly_tokens": user.limit_monthly_tokens, @@ -211,8 +208,8 @@ def login(): log_action(logs_collection, f'login_error {error_login}', {'username': email}, level='error') return jsonify({"error": "Erro no login"}), 500 -@app.route('/api/health', methods=['POST']) -@require_user_token(optional=False) +@app.route('/api/health', methods=['GET']) +#@require_user_token(optional=False) def health_check(): """ Verifica a saúde do sistema. @@ -220,11 +217,8 @@ def health_check(): Usa o usuário autenticado (g.current_user). """ try: - data = request.get_json() - user_email = data.get("email") - user_senha = data.get("password") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -324,13 +318,13 @@ def health_check(): return jsonify({"error": "Erro ao executar health_check", "detail": str(e)}), 500 @app.route('/api/rate-limits', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_rate_limits(): """Obter informações de rate limits""" - user_email = request.args.get("email") - user_senha = request.args.get("password") + + - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -385,13 +379,13 @@ def get_rate_limits(): return jsonify(rate_limits) @app.route('/api/settings', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_settings(): """Recuperar configurações do sistema (com valores mascarados)""" - user_email = request.args.get("email") - user_senha = request.args.get("password") + + - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -433,14 +427,14 @@ def get_settings(): return jsonify({'error': 'Failed to fetch settings'}), 500 @app.route('/api/settings', methods=['PUT']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def update_settings(): """Atualizar configurações do sistema""" data = request.get_json() user_email = data.get("email") user_senha = data.get("password") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -485,15 +479,13 @@ def update_settings(): log_action(logs_collection, 'settings_update_error', {'error': str(e)}, user=numeric_user_id, level='error') return jsonify({'error': 'Failed to update settings'}), 500 -@app.route('/api/test-connection/', methods=['POST']) -@require_user_token(optional=False) +@app.route('/api/test-connection/', methods=['GET']) +#@require_user_token(optional=False) def test_connection(service): """Testar conexão com serviços externos""" - data = request.get_json() - user_email = data.get("email") - user_senha = data.get("password") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -614,15 +606,15 @@ def test_connection(service): }), 500 @app.route("/api/logs", methods=["GET"]) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_logs(): - user_email = request.args.get("email") - user_senha = request.args.get("password") + + level = request.args.get("level") search_term = request.args.get("searchTerm") limit = int(request.args.get("limit", 200)) - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -664,18 +656,18 @@ def get_logs(): return jsonify(adapted_logs) @app.route("/api/logs/export", methods=["GET"]) -@require_user_token(optional=False) +#@require_user_token(optional=False) def export_logs(): """ Exporta logs filtrados em formato .txt Requer `user_id` (query param). """ - user_email = request.args.get("email") - user_senha = request.args.get("password") + + level = request.args.get("level") search_term = request.args.get("searchTerm") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -721,20 +713,20 @@ def export_logs(): return Response( export_text, mimetype="text/plain", - headers={"Content-Disposition": f"attachment; filename=logs-{user_email}.txt"}, + headers={"Content-Disposition": f"attachment; filename=logs-{numeric_user_id}.txt"}, ) @app.route('/api/pull-requests', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_pull_requests(): """Listar PRs processados com filtros e busca""" - user_email = request.args.get("email") - user_senha = request.args.get("password") + + page = request.args.get("page", 1) search_term = request.args.get("searchTerm", None) limit = int(request.args.get("limit", 50)) - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -799,13 +791,13 @@ def get_pull_requests(): return jsonify({'error': 'Failed to fetch pull requests'}), 500 @app.route('/api/pull-requests/', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_pull_request_details(pr_id): """Obter detalhes completos de um PR específico""" - user_email = request.args.get("email") - user_senha = request.args.get("password") + + - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -908,14 +900,11 @@ def parse_to_aware(dt): return None @app.route('/api/dashboard-data', methods=['GET']) -@require_user_token(optional=False) +# #@require_user_token(optional=False) def get_dashboard_data(): """Obter dados agregados para o dashboard (correção focada: apenas aqui).""" - user_email = request.args.get("email") - user_senha = request.args.get("password") - - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1053,17 +1042,17 @@ def get_dashboard_data(): @app.route('/api/workflows', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def list_workflows(): """ Lista workflows (arquivos .yml/.yaml) a partir de WORKFLOWS_PATH (env) ou ./workflows. Retorna JSON: { "workflows": [ { id, name, category, createdAt, yaml, git }, ... ] } """ try: - user_email = request.args.get("email") or (request.json and request.json.get("email")) - user_senha = request.args.get("password") or (request.json and request.json.get("password")) + + - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1111,17 +1100,14 @@ def list_workflows(): return jsonify({'error': 'Failed to list workflows', 'detail': str(e)}), 500 @app.route('/api/myaccount', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def my_account(): """ Retorna informações da conta do usuário autenticado. Inclui plano, limites e status de expiração. """ try: - user_email = request.args.get("email") or (request.json and request.json.get("email")) - user_senha = request.args.get("password") or (request.json and request.json.get("password")) - - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1147,7 +1133,7 @@ def my_account(): @app.route('/api/invoices', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def list_invoices(): """ Listar faturas paginadas. @@ -1161,9 +1147,9 @@ def list_invoices(): q = request.args.get('q', '').strip() # autentica (compatível com existing pattern) - user_email = request.args.get("email") or (request.json and request.json.get("email")) - user_senha = request.args.get("password") or (request.json and request.json.get("password")) - user, _, status_auth = auth_user(user_email, user_senha, logs_collection, app) + + + user, _, status_auth = auth_user( logs_collection, app) if status_auth != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1193,15 +1179,15 @@ def list_invoices(): @app.route('/api/invoices/', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def get_invoice_detail(invoice_id): """ Detalhe da fatura (inclui linhas / itens). """ try: - user_email = request.args.get("email") or (request.json and request.json.get("email")) - user_senha = request.args.get("password") or (request.json and request.json.get("password")) - user, _, status_auth = auth_user(user_email, user_senha, logs_collection, app) + + + user, _, status_auth = auth_user( logs_collection, app) if status_auth != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1222,7 +1208,7 @@ def get_invoice_detail(invoice_id): @app.route('/api/invoices//download', methods=['GET']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def download_invoice(invoice_id): """ Download do PDF da fatura. @@ -1230,9 +1216,9 @@ def download_invoice(invoice_id): Se Invoice.pdf_path estiver configurado, serve o arquivo do diretório INVOICES_DIR. """ try: - user_email = request.args.get("email") or (request.json and request.json.get("email")) - user_senha = request.args.get("password") or (request.json and request.json.get("password")) - user, _, status_auth = auth_user(user_email, user_senha, logs_collection, app) + + + user, _, status_auth = auth_user( logs_collection, app) if status_auth != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1274,7 +1260,7 @@ def download_invoice(invoice_id): @app.route('/api/reprocess-pr/', methods=['POST']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def reprocess_pr(pr_number): """Reprocessar um Pull Request específico""" data = request.get_json() @@ -1282,7 +1268,7 @@ def reprocess_pr(pr_number): user_senha = data.get("password") redo_merge = data.get("redo_merge") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1312,15 +1298,13 @@ def reprocess_pr(pr_number): }), 202 @app.route('/api/prai/gen', methods=['POST']) -@require_user_token(optional=False) +#@require_user_token(optional=False) def prai(): data = request.get_json() - user_email = data.get("email") - user_senha = data.get("password") repository = data.get("repository") pr_number = data.get("pr_number") - user, _, status = auth_user(user_email, user_senha, logs_collection, app) + user, _, status = auth_user( logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 diff --git a/Front-End/src/App.tsx b/Front-End/src/App.tsx index a5a21647..8de1364c 100644 --- a/Front-End/src/App.tsx +++ b/Front-End/src/App.tsx @@ -39,7 +39,7 @@ const AppRoutes = () => { } /> } /> } /> - } /> + } /> } /> @@ -50,7 +50,7 @@ const AppRoutes = () => { } /> } /> - } /> + } /> )} diff --git a/Front-End/src/components/app-sidebar.tsx b/Front-End/src/components/app-sidebar.tsx index 922db12a..339f6edc 100644 --- a/Front-End/src/components/app-sidebar.tsx +++ b/Front-End/src/components/app-sidebar.tsx @@ -25,6 +25,10 @@ import { useSidebar, } from "@/components/ui/sidebar" +import { LogOut } from "lucide-react" +import { useNavigate } from "react-router-dom" +import { useAuth } from '@/contexts/AuthContext'; + const navigationItems = [ { title: "Dashboard", @@ -76,15 +80,12 @@ const navigationItems = [ }, { title: "Invoices", - url: "/invoices", + url: "/billing/invoices", icon: FileText, description: "Relatório de Faturas" }, ] -import { LogOut } from "lucide-react" -import { useNavigate } from "react-router-dom" - // Dentro do componente AppSidebar export function AppSidebar() { const { state } = useSidebar() @@ -92,6 +93,7 @@ export function AppSidebar() { const currentPath = location.pathname const isCollapsed = state === "collapsed" const navigate = useNavigate() + const { logout } = useAuth(); const isActive = (path: string) => { if (path === "/") return currentPath === "/" @@ -106,10 +108,10 @@ export function AppSidebar() { } const handleLogout = () => { - // Limpa tokens ou dados de sessão - localStorage.clear() - sessionStorage.clear() - navigate("/") + logout(); + // navigate("/") + window.location.reload() + } return ( diff --git a/Front-End/src/constants/landingpage.ts b/Front-End/src/constants/landingpage.ts index e01cc2cc..94bbb0f6 100644 --- a/Front-End/src/constants/landingpage.ts +++ b/Front-End/src/constants/landingpage.ts @@ -28,13 +28,13 @@ export const project = [ ], actions: { login: { label: "Entrar", href: "/login" }, - signup: { label: "Começar Grátis", href: "/signup" }, + signup: { label: "Começar Grátis", href: "/login" }, }, }, plans: [ { name: "Free", - checkout: "https://www.softwareai.site/signup?plan=free" + checkout: "https://www.softwareai.site/login" }, { name: "Premium", diff --git a/Front-End/src/contexts/AuthContext.tsx b/Front-End/src/contexts/AuthContext.tsx index 79181756..6c4c122c 100644 --- a/Front-End/src/contexts/AuthContext.tsx +++ b/Front-End/src/contexts/AuthContext.tsx @@ -10,18 +10,19 @@ interface AuthContextType { const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { - // Aqui usamos localStorage para persistir o token; você pode adaptar conforme precisar const [isAuthenticated, setIsAuthenticated] = useState(() => { - return !!localStorage.getItem('auth_token'); + return !!localStorage.getItem('access_token'); }); function login(token: string) { - localStorage.setItem('auth_token', token); + localStorage.setItem('access_token', token); setIsAuthenticated(true); } function logout() { - localStorage.removeItem('auth_token'); + localStorage.removeItem('user_email'); + localStorage.removeItem('user_senha'); + localStorage.removeItem('access_token'); setIsAuthenticated(false); } diff --git a/Front-End/src/pages/Billing.tsx b/Front-End/src/pages/Billing.tsx index 4278a9bf..58dacdef 100644 --- a/Front-End/src/pages/Billing.tsx +++ b/Front-End/src/pages/Billing.tsx @@ -39,21 +39,24 @@ const BillingPage = () => { const [loading, setLoading] = useState(false) const [actionLoading, setActionLoading] = useState(null) const [error, setError] = useState(null) + const [plans, setPlans] = useState([]) + const [plansLoading, setPlansLoading] = useState(false) + const backend = import.meta.env.VITE_BACK_END || '' - const email = localStorage.getItem("user_email") || "" - const password = localStorage.getItem("user_senha") || "" + const accessToken = localStorage.getItem("access_token") || "" useEffect(() => { fetchAccount() + fetchPlans() }, []) async function fetchAccount() { setLoading(true) setError(null) try { - const res = await fetch(`${backend}/api/myaccount?email=${email}&password=${password}`, { + const res = await fetch(`${backend}/api/myaccount`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -78,6 +81,28 @@ const BillingPage = () => { } } + async function fetchPlans() { + setPlansLoading(true) + try { + const res = await fetch(`${backend}/api/public/plans-features`) + if (!res.ok) throw new Error('Falha ao buscar planos') + const data = await res.json() + const plansArray = Object.entries(data.payload).map(([key, value]: any) => ({ + id: key.toLowerCase(), + name: key, + price: `R$ ${value.price}/mês`, + features: value.features, + paymentLink: value.payment_link + })) + setPlans(plansArray) + } catch (err: any) { + setError(err.message || 'Erro ao carregar planos') + } finally { + setPlansLoading(false) + } + } + + function calcPercent(used: number, limit: number) { if (limit === 0) return 0 return Math.min(100, Math.round((used / limit) * 100)) @@ -136,7 +161,6 @@ const BillingPage = () => { )}
- {/* Left column: account summary */}
@@ -205,33 +229,33 @@ const BillingPage = () => {
- {/* Right column: plans */}
-
- {samplePlans.map((p) => ( - - - {p.name} - - -
-
{p.price}
-
    - {p.features.map((f, i) => ( -
  • • {f}
  • - ))} -
-
- -
- -
-
-
- ))} -
+ {plansLoading ? ( +
Carregando planos...
+ ) : ( +
+ {plans.map((p) => ( + + + {p.name} + + +
+
{p.price}
+
    + {p.features.map((f, i) =>
  • • {f}
  • )} +
+
+
+ +
+
+
+ ))} +
+ )}
diff --git a/Front-End/src/pages/Controls.tsx b/Front-End/src/pages/Controls.tsx index 6ac70896..13352845 100644 --- a/Front-End/src/pages/Controls.tsx +++ b/Front-End/src/pages/Controls.tsx @@ -35,17 +35,13 @@ const Controls = () => { const [rateLimits, setRateLimits] = useState(null) const [redoMerge, setRedoMerge] = useState(false) - const email = localStorage.getItem('user_email') || ''; - const password = localStorage.getItem('user_senha') || ''; const access_token = localStorage.getItem("access_token") - const payload = { email, password } - const params = new URLSearchParams({ email, password }); // Fetch dos Rate Limits const fetchRateLimits = async () => { try { - const response = await fetch(`${backendUrl}/api/rate-limits?${params.toString()}`, { + const response = await fetch(`${backendUrl}/api/rate-limits`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -84,7 +80,7 @@ const Controls = () => { setIsProcessing(true) try { - const payload = { redoMerge, email, password } + const payload = { redoMerge } const response = await fetch(`${backendUrl}/api/reprocess-pr/${prNumber}`, { method: 'POST', @@ -116,15 +112,13 @@ const Controls = () => { } const testSystemHealth = async () => { - const payload = { email, password } try { const response = await fetch(`${backendUrl}/api/health`, { - method: 'POST', + method: 'GET', headers: { 'Content-Type': 'application/json', 'X-API-TOKEN': `${access_token}` }, - body: JSON.stringify(payload) }); diff --git a/Front-End/src/pages/Dashboard.tsx b/Front-End/src/pages/Dashboard.tsx index caed330f..fde2c94c 100644 --- a/Front-End/src/pages/Dashboard.tsx +++ b/Front-End/src/pages/Dashboard.tsx @@ -58,17 +58,13 @@ const Dashboard = () => { const backendUrl = import.meta.env.VITE_BACK_END - const email = localStorage.getItem('user_email') || ''; - const password = localStorage.getItem('user_senha') || ''; const access_token = localStorage.getItem("access_token") - const payload = { email, password } - const params = new URLSearchParams({ email, password }); const fetchDashboardData = async () => { - if (!email) return + if (!access_token) return try { setIsLoading(true) - const response = await fetch(`${backendUrl}/api/dashboard-data?${params.toString()}`, { + const response = await fetch(`${backendUrl}/api/dashboard-data`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/Front-End/src/pages/Invoices.tsx b/Front-End/src/pages/Invoices.tsx index a9480b5a..abd0e51c 100644 --- a/Front-End/src/pages/Invoices.tsx +++ b/Front-End/src/pages/Invoices.tsx @@ -28,8 +28,7 @@ const InvoicesPage = () => { const [selectedInvoice, setSelectedInvoice] = useState(null) const backend = import.meta.env.VITE_BACK_END || '' - const email = localStorage.getItem("user_email") || "" - const password = localStorage.getItem("user_senha") || "" + const accessToken = localStorage.getItem("access_token") || "" useEffect(() => { @@ -44,8 +43,7 @@ const InvoicesPage = () => { const params = new URLSearchParams() params.set('page', String(page)) params.set('limit', String(pageSize)) - params.set('email', String(email)) - params.set('password', String(password)) + if (filterStatus !== 'all') params.set('status', filterStatus) if (query) params.set('q', query) @@ -106,11 +104,10 @@ const InvoicesPage = () => { async function handleView(invoice: Invoice) { const params = new URLSearchParams() - params.set('email', String(email)) - params.set('password', String(password)) + if (!invoice.lines) { try { - const res = await fetch(`${backend}/api/invoices/${invoice.id}?${params.toString()}`, { + const res = await fetch(`${backend}/api/invoices/${invoice.id}`, { headers: { 'X-API-TOKEN': accessToken } diff --git a/Front-End/src/pages/Login.tsx b/Front-End/src/pages/Login.tsx index 652b4e70..d7cadbc5 100644 --- a/Front-End/src/pages/Login.tsx +++ b/Front-End/src/pages/Login.tsx @@ -22,8 +22,7 @@ const LoginForm: React.FC = () => { const backendUrl = import.meta.env.VITE_BACK_END; const access_token = localStorage.getItem("access_token") - console.log('Token access_token:', access_token) - console.log('Token access_token_fallback:', access_token_fallback) + const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); if (!email || !password) return; @@ -31,35 +30,47 @@ const LoginForm: React.FC = () => { setError(null); try { - const response = await fetch(`${backendUrl}/api/login`, { method: 'POST', headers: { - 'Content-Type': 'application/json', - 'X-API-TOKEN': `${access_token_fallback}` - }, + 'Content-Type': 'application/json' + }, body: JSON.stringify({ email, password }), }); + + // debug rápido const result = await response.json(); + console.log('login response status', response.status, result); if (!response.ok) { throw new Error(result.message || 'Erro no login'); } - + + // usa o token direto do result para salvar no localStorage + const token = result.acess_token; + if (!token) throw new Error('Resposta sem access_token'); + + localStorage.setItem('isAuthenticated', 'true'); const formattedTime = new Date().toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true, day: '2-digit', month: 'short', year: 'numeric' }); - // Armazena dados de sessão - localStorage.setItem('isAuthenticated', 'true'); localStorage.setItem('login_time', formattedTime); - setaccess_token_fallback(result.access_token) - localStorage.setItem('access_token', access_token_fallback); - login(result.access_token); + localStorage.setItem('acess_token', token); + localStorage.setItem('access_token', token); + localStorage.setItem('user_email', email); + localStorage.setItem('user_senha', email); + setaccess_token_fallback(token); + + + console.log('Token access_token:', token) + console.log('Token access_token_fallback:', token) + + login(token); navigate(`/home`); } catch (err: any) { - setError(err.message); + setError(err.message || 'Erro no login'); } finally { setIsLoading(false); } @@ -71,7 +82,7 @@ const LoginForm: React.FC = () => { setError('As senhas não coincidem.'); return; } - + setIsLoading(true); setError(null); @@ -81,26 +92,38 @@ const LoginForm: React.FC = () => { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); + const result = await response.json(); - localStorage.setItem('user_email', email); - localStorage.setItem('user_senha', password); - setaccess_token_fallback(result.access_token) - localStorage.setItem('access_token', access_token_fallback); + console.log('register response', response.status, result); + if (!response.ok) { throw new Error(result.message || 'Erro no registro'); } - - // Sucesso! Volta para a tela de login. + + // usa o token direto do result para salvar no localStorage + const token = result.acess_token; + if (!token) throw new Error('Resposta sem access_token'); + + + localStorage.setItem('acess_token', token); + localStorage.setItem('access_token', token); + localStorage.setItem('user_email', email); + localStorage.setItem('user_senha', email); + setaccess_token_fallback(token); + + + console.log('Token access_token:', token) + console.log('Token access_token_fallback:', token) + + setIsRegistering(false); - // Você pode adicionar uma notificação de sucesso aqui (usando um 'toast', por exemplo) } catch (err: any) { - setError(err.message); + setError(err.message || 'Erro no registro'); } finally { setIsLoading(false); } }; - const toggleMode = () => { setIsRegistering(!isRegistering); setError(null); // Limpa erros ao trocar de modo diff --git a/Front-End/src/pages/Logs.tsx b/Front-End/src/pages/Logs.tsx index 8683e4ad..b01d8043 100644 --- a/Front-End/src/pages/Logs.tsx +++ b/Front-End/src/pages/Logs.tsx @@ -34,14 +34,11 @@ const Logs = () => { const backendUrl = import.meta.env.VITE_BACK_END; - const email = localStorage.getItem('user_email') || ''; - const password = localStorage.getItem('user_senha') || ''; const access_token = localStorage.getItem("access_token") - const payload = { email, password } - const params = new URLSearchParams({ searchTerm, level: levelFilter, limit: "200", email, password }); + const params = new URLSearchParams({ searchTerm, level: levelFilter, limit: "200" }); const fetchLogs = async () => { - if (!email) return + if (!access_token) return try { setIsLoading(true) const response = await fetch(`${backendUrl}/api/logs?${params.toString()}`, { @@ -105,7 +102,7 @@ const Logs = () => { } const exportLogs = async () => { - if (!email) return + if (!access_token) return try { const response = await fetch(`${backendUrl}/api/logs/export?${params.toString()}`, { diff --git a/Front-End/src/pages/MyAccount.tsx b/Front-End/src/pages/MyAccount.tsx index 7c68493c..5fd9c33e 100644 --- a/Front-End/src/pages/MyAccount.tsx +++ b/Front-End/src/pages/MyAccount.tsx @@ -37,7 +37,7 @@ const MyAccount = () => { if (!email || !accessToken) return try { setIsLoading(true) - const response = await fetch(`${backendUrl}/api/myaccount?email=${email}&password=${password}`, { + const response = await fetch(`${backendUrl}/api/myaccount`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/Front-End/src/pages/PullRequests.tsx b/Front-End/src/pages/PullRequests.tsx index fadc4471..26c4b72a 100644 --- a/Front-End/src/pages/PullRequests.tsx +++ b/Front-End/src/pages/PullRequests.tsx @@ -53,18 +53,14 @@ const PullRequests = () => { const [selectedPR, setSelectedPR] = useState(null) const backendUrl = import.meta.env.VITE_BACK_END - const email = localStorage.getItem('user_email') || ''; - const password = localStorage.getItem('user_senha') || ''; const access_token = localStorage.getItem("access_token") - const payload = { email, password } - const params = new URLSearchParams({ email, password }); const [showFullDiff, setShowFullDiff] = useState(false) const fetchPRs = async () => { - if (!email) return + if (!access_token) return try { setIsLoading(true) - const res = await fetch(`${backendUrl}/api/pull-requests?${params.toString()}`, { + const res = await fetch(`${backendUrl}/api/pull-requests`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -87,9 +83,9 @@ const PullRequests = () => { } const fetchPRDetails = async (prId: string) => { - if (!email) return + if (!access_token) return try { - const res = await fetch(`${backendUrl}/api/pull-requests/${prId}?${params.toString()}`, { + const res = await fetch(`${backendUrl}/api/pull-requests/${prId}`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/Front-End/src/pages/Settings.tsx b/Front-End/src/pages/Settings.tsx index 8c690d65..ee88686e 100644 --- a/Front-End/src/pages/Settings.tsx +++ b/Front-End/src/pages/Settings.tsx @@ -52,18 +52,14 @@ const Settings = () => { }) const { toast } = useToast() const backendUrl = import.meta.env.VITE_BACK_END; - const email = localStorage.getItem('user_email') || ''; - const password = localStorage.getItem('user_senha') || ''; const access_token = localStorage.getItem("access_token") console.log('Token enviado:', access_token) - const payload = { email, password } - const params = new URLSearchParams({ email, password }); const fetchSettings = async () => { try { setIsLoading(true) - const response = await fetch(`${backendUrl}/api/settings?${params.toString()}`, { + const response = await fetch(`${backendUrl}/api/settings`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -93,7 +89,7 @@ const Settings = () => { const saveSettings = async () => { setIsSaving(true) - const payload = { ...settings, email, password } + const payload = { ...settings} try { const response = await fetch(`${backendUrl}/api/settings`, { method: 'PUT', @@ -141,12 +137,11 @@ const Settings = () => { const testConnection = async (service: "github" | "openai") => { try { const response = await fetch(`${backendUrl}/api/test-connection/${service}`, { - method: 'POST', + method: 'GET', headers: { 'Content-Type': 'application/json', 'X-API-TOKEN': `${access_token}` }, - body: JSON.stringify(payload) }); if (response.ok) { diff --git a/Front-End/src/pages/Workflows.tsx b/Front-End/src/pages/Workflows.tsx index 31570a14..ceb319ee 100644 --- a/Front-End/src/pages/Workflows.tsx +++ b/Front-End/src/pages/Workflows.tsx @@ -40,17 +40,15 @@ const Workflows = () => { const backendUrl = import.meta.env.VITE_BACK_END - const e = localStorage.getItem('user_email') ?? '' - const p = localStorage.getItem('user_senha') ?? '' + const token = localStorage.getItem('access_token') ?? '' - if (!e) { + if (!token) { throw new Error('Email do usuário não encontrado. Verifique se há user_email no localStorage.') } - const params = new URLSearchParams({ email: e, password: p }) - const res = await fetch(`${backendUrl}/api/workflows?${params.toString()}`, { + const res = await fetch(`${backendUrl}/api/workflows`, { method: 'GET', headers: { 'Content-Type': 'application/json',