#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Gerador de API PHP Cria automaticamente uma estrutura de API REST em PHP com SQLite """ import os import sqlite3 import re from pathlib import Path def obter_informacoes(): """Coleta informações do usuário para gerar a API""" print("=== GERADOR DE API PHP ===\n") # Obter localização do banco SQL while True: sql_path = input("Digite o caminho para o arquivo SQL do banco (ou 'criar' para criar novo): ").strip() if sql_path.lower() == 'criar': sql_path = None break elif os.path.exists(sql_path): break else: print("Arquivo não encontrado. Tente novamente.") # Nome da versão da API while True: api_version = input("Digite a versão da API (ex: v1, v2, v3): ").strip() if re.match(r'^v\d+$', api_version): break else: print("Formato inválido. Use o formato 'v' seguido de número (ex: v1)") # Autenticação while True: auth_choice = input("Deseja autenticação para todas as rotas? (s/n): ").strip().lower() if auth_choice in ['s', 'sim', 'y', 'yes']: needs_auth = True break elif auth_choice in ['n', 'não', 'nao', 'no']: needs_auth = False break else: print("Digite 's' para sim ou 'n' para não") # Se precisar de autenticação, pergunta a chave secreta secret_key = None admin_email = None admin_password = None if needs_auth: while True: secret_key = input("\nDigite a chave secreta para JWT (min 32 caracteres): ").strip() if len(secret_key) >= 32: break print("A chave secreta deve ter pelo menos 32 caracteres") print("\n=== CRIAR USUÁRIO ADMIN ===") admin_email = input("Email do admin: ").strip() admin_password = input("Senha do admin: ").strip() return sql_path, api_version, needs_auth, secret_key, admin_email, admin_password def extrair_tabelas_sql(sql_path): """Extrai nomes das tabelas do arquivo SQL""" if not sql_path: return [] tabelas = [] try: with open(sql_path, 'r', encoding='utf-8') as file: content = file.read().upper() # Regex para encontrar CREATE TABLE pattern = r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[\[`"]?(\w+)[\]`"]?' matches = re.findall(pattern, content) tabelas = [match.lower() for match in matches] except Exception as e: print(f"Erro ao ler arquivo SQL: {e}") return tabelas def criar_estrutura_diretorios(api_version): """Cria a estrutura de diretórios""" # Criar pasta api principal se não existir os.makedirs('api', exist_ok=True) dirs = [ f'api/{api_version}', 'api/funcoes' ] for dir_name in dirs: os.makedirs(dir_name, exist_ok=True) print(f"Diretório criado: {dir_name}/") def criar_database_php(sql_path): """Cria o arquivo database.php""" content = '''pdo = new PDO('sqlite:database.sqlite'); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } catch (PDOException $e) { die('Erro na conexão com o banco: ' . $e->getMessage()); } } public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } public function getConnection() { return $this->pdo; } public function query($sql, $params = []) { try { $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt; } catch (PDOException $e) { throw new Exception('Erro na consulta: ' . $e->getMessage()); } } public function lastInsertId() { return $this->pdo->lastInsertId(); } } ?>''' with open('api/funcoes/database.php', 'w', encoding='utf-8') as f: f.write(content) print("Criado: api/funcoes/database.php") def criar_security_php(): """Cria o arquivo security.php""" content = ''' 0; } /** * Retorna resposta JSON com status */ public static function jsonResponse($data, $status = 200) { http_response_code($status); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit(); } /** * Retorna erro JSON */ public static function jsonError($message, $status = 400) { self::jsonResponse(['error' => $message], $status); } } ?>''' with open('api/funcoes/security.php', 'w', encoding='utf-8') as f: f.write(content) print("Criado: api/funcoes/security.php") def criar_auth_php(secret_key): """Cria o arquivo auth.php para autenticação""" content = f''' 'JWT', 'alg' => 'HS256']); $payload = json_encode([ 'user_id' => $userId, 'username' => $username, 'exp' => time() + (24 * 60 * 60) // 24 horas ]); $headerEncoded = self::base64urlEncode($header); $payloadEncoded = self::base64urlEncode($payload); $signature = hash_hmac('sha256', $headerEncoded . "." . $payloadEncoded, self::$secret_key, true); $signatureEncoded = self::base64urlEncode($signature); return $headerEncoded . "." . $payloadEncoded . "." . $signatureEncoded; }} /** * Verifica se o token é válido */ public static function verifyToken($token) {{ $parts = explode('.', $token); if (count($parts) !== 3) {{ return false; }} $header = self::base64urlDecode($parts[0]); $payload = self::base64urlDecode($parts[1]); $signature = self::base64urlDecode($parts[2]); $expectedSignature = hash_hmac('sha256', $parts[0] . "." . $parts[1], self::$secret_key, true); if (!hash_equals($signature, $expectedSignature)) {{ return false; }} $payloadData = json_decode($payload, true); if ($payloadData['exp'] < time()) {{ return false; // Token expirado }} return $payloadData; }} /** * Middleware de autenticação */ public static function requireAuth() {{ $headers = getallheaders(); $token = null; if (isset($headers['Authorization'])) {{ $auth = $headers['Authorization']; if (strpos($auth, 'Bearer ') === 0) {{ $token = substr($auth, 7); }} }} if (!$token) {{ Security::jsonError('Token de acesso requerido', 401); }} $payload = self::verifyToken($token); if (!$payload) {{ Security::jsonError('Token inválido ou expirado', 401); }} return $payload; }} private static function base64urlEncode($data) {{ return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); }} private static function base64urlDecode($data) {{ return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); }} }} ?>''' with open('api/funcoes/auth.php', 'w', encoding='utf-8') as f: f.write(content) print("Criado: api/funcoes/auth.php") def criar_arquivo_tabela(tabela, api_version, needs_auth): """Cria arquivo PHP para uma tabela específica""" auth_include = "require_once '../funcoes/auth.php';" if needs_auth else "" auth_check = "Auth::requireAuth();" if needs_auth else "" content = f'''getConnection(); // Obtém método HTTP e dados $method = $_SERVER['REQUEST_METHOD']; $input = json_decode(file_get_contents('php://input'), true); $id = isset($_GET['id']) ? $_GET['id'] : null; try {{ switch ($method) {{ case 'GET': if ($id) {{ // Buscar por ID if (!Security::isValidId($id)) {{ Security::jsonError('ID inválido'); }} $stmt = $db->query("SELECT * FROM {tabela} WHERE id = ?", [$id]); $result = $stmt->fetch(); if ($result) {{ Security::jsonResponse($result); }} else {{ Security::jsonError('Registro não encontrado', 404); }} }} else {{ // Listar todos $stmt = $db->query("SELECT * FROM {tabela}"); $results = $stmt->fetchAll(); Security::jsonResponse($results); }} break; case 'POST': // Criar novo registro if (!$input) {{ Security::jsonError('Dados não fornecidos'); }} // Sanitizar dados $input = Security::sanitizeInput($input); // Construir query dinamicamente (ajuste conforme suas colunas) $columns = array_keys($input); $placeholders = array_fill(0, count($columns), '?'); $sql = "INSERT INTO {tabela} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")"; $db->query($sql, array_values($input)); Security::jsonResponse([ 'message' => 'Registro criado com sucesso', 'id' => $db->lastInsertId() ], 201); break; case 'PUT': // Atualizar registro if (!$id || !Security::isValidId($id)) {{ Security::jsonError('ID válido requerido'); }} if (!$input) {{ Security::jsonError('Dados não fornecidos'); }} // Sanitizar dados $input = Security::sanitizeInput($input); // Construir query de update $setParts = []; $values = []; foreach ($input as $key => $value) {{ $setParts[] = "$key = ?"; $values[] = $value; }} $values[] = $id; $sql = "UPDATE {tabela} SET " . implode(', ', $setParts) . " WHERE id = ?"; $stmt = $db->query($sql, $values); if ($stmt->rowCount() > 0) {{ Security::jsonResponse(['message' => 'Registro atualizado com sucesso']); }} else {{ Security::jsonError('Registro não encontrado ou nenhuma alteração feita', 404); }} break; case 'DELETE': // Deletar registro if (!$id || !Security::isValidId($id)) {{ Security::jsonError('ID válido requerido'); }} $stmt = $db->query("DELETE FROM {tabela} WHERE id = ?", [$id]); if ($stmt->rowCount() > 0) {{ Security::jsonResponse(['message' => 'Registro deletado com sucesso']); }} else {{ Security::jsonError('Registro não encontrado', 404); }} break; default: Security::jsonError('Método não permitido', 405); }} }} catch (Exception $e) {{ Security::jsonError('Erro interno do servidor: ' . $e->getMessage(), 500); }} ?>''' filename = f'api/{api_version}/{tabela}.php' with open(filename, 'w', encoding='utf-8') as f: f.write(content) print(f"Criado: {filename}") def criar_login_php(api_version): """Cria o arquivo login.php para autenticação""" content = '''query("SELECT id, nome, email, senha FROM usuarios WHERE email = ?", [$input['email']]); $user = $stmt->fetch(); // Verifica se usuário existe e senha está correta if (!$user || !password_verify($input['senha'], $user['senha'])) { Security::jsonError('Email ou senha inválidos', 401); } // Gera o token $token = Auth::generateToken($user['id'], $user['email']); // Retorna o token Security::jsonResponse([ 'token' => $token, 'user' => [ 'id' => $user['id'], 'nome' => $user['nome'], 'email' => $user['email'] ] ]); } catch (Exception $e) { Security::jsonError('Erro interno do servidor: ' . $e->getMessage(), 500); } ?>''' filename = f'api/{api_version}/login.php' with open(filename, 'w', encoding='utf-8') as f: f.write(content) print(f"Criado: {filename}") def criar_banco_sqlite(sql_path, needs_auth=False, admin_email=None, admin_password=None): """Cria o banco SQLite""" try: # Criar pasta api se não existir os.makedirs('api', exist_ok=True) conn = sqlite3.connect('api/database.sqlite') if sql_path and os.path.exists(sql_path): # Executar SQL fornecido with open(sql_path, 'r', encoding='utf-8') as f: sql_content = f.read() # Executar comandos SQL conn.executescript(sql_content) print("Banco de dados criado com base no arquivo SQL fornecido") else: # Criar banco básico de exemplo conn.execute(''' CREATE TABLE IF NOT EXISTS usuarios ( id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL, email TEXT UNIQUE NOT NULL, senha TEXT NOT NULL, is_admin INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS produtos ( id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL, preco REAL NOT NULL, descricao TEXT, estoque INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ''') print("Banco de dados exemplo criado com tabelas 'usuarios' e 'produtos'") if needs_auth and admin_email and admin_password: # Hash da senha do admin hashed_password = f"'{__import__('hashlib').sha256(admin_password.encode()).hexdigest()}'" # Inserir usuário admin conn.execute(f''' INSERT INTO usuarios (nome, email, senha, is_admin) VALUES ('Administrador', '{admin_email}', {hashed_password}, 1) ''') print(f"Usuário admin criado: {admin_email}") conn.commit(); conn.close(); print("Criado: api/database.sqlite") except Exception as e: print(f"Erro ao criar banco de dados: {e}") def criar_htaccess(api_version): """Cria arquivo .htaccess para URLs amigáveis""" content = f'''RewriteEngine On RewriteCond %{{REQUEST_FILENAME}} !-f RewriteCond %{{REQUEST_FILENAME}} !-d # Redirecionar para arquivos da API RewriteRule ^{api_version}/([^/]+)/?$ {api_version}/$1.php [L,QSA] RewriteRule ^{api_version}/([^/]+)/([0-9]+)/?$ {api_version}/$1.php?id=$2 [L,QSA] # Headers de segurança Header always set X-Content-Type-Options nosniff Header always set X-Frame-Options DENY Header always set X-XSS-Protection "1; mode=block" ''' with open('api/.htaccess', 'w', encoding='utf-8') as f: f.write(content) print("Criado: api/.htaccess") def criar_readme(): """Cria arquivo README com instruções de uso""" content = '''# 🚀 API REST PHP Esta API foi gerada automaticamente e fornece endpoints RESTful para manipulação de dados. ## 📁 Estrutura do Projeto ``` api/ ├── v1/ # Endpoints da API │ ├── login.php # Autenticação │ ├── usuarios.php # CRUD de usuários │ └── produtos.php # CRUD de produtos │ ├── funcoes/ # Funções auxiliares │ ├── database.php # Conexão com banco │ ├── security.php # Funções de segurança │ └── auth.php # Autenticação JWT │ ├── database.sqlite # Banco de dados ├── .htaccess # URLs amigáveis └── README.md # Este arquivo ``` ## 🔑 Autenticação ### 1. Login para obter token ```http POST /v1/login Content-Type: application/json { "email": "admin@email.com", "senha": "sua_senha" } ``` ### 2. Usar o token Adicione o header em todas as requisições: ```http Authorization: Bearer seu_token_aqui ``` ## 📌 Endpoints Disponíveis ### Usuários | Método | Endpoint | Descrição | |--------|----------|-----------| | GET | `/v1/usuarios` | Lista todos usuários | | GET | `/v1/usuarios/1` | Busca usuário por ID | | POST | `/v1/usuarios` | Cria novo usuário | | PUT | `/v1/usuarios/1` | Atualiza usuário | | DELETE | `/v1/usuarios/1` | Remove usuário | ### Produtos | Método | Endpoint | Descrição | |--------|----------|-----------| | GET | `/v1/produtos` | Lista todos produtos | | GET | `/v1/produtos/1` | Busca produto por ID | | POST | `/v1/produtos` | Cria novo produto | | PUT | `/v1/produtos/1` | Atualiza produto | | DELETE | `/v1/produtos/1` | Remove produto | ## 📝 Exemplos de Uso ### Criar Usuário ```http POST /v1/usuarios Content-Type: application/json Authorization: Bearer seu_token_aqui { "nome": "João Silva", "email": "joao@email.com", "senha": "123456" } ``` ### Listar Produtos ```http GET /v1/produtos Authorization: Bearer seu_token_aqui ``` ### Atualizar Produto ```http PUT /v1/produtos/1 Content-Type: application/json Authorization: Bearer seu_token_aqui { "nome": "Produto Atualizado", "preco": 29.99, "estoque": 100 } ``` ## ⚙️ Configuração ### Requisitos - PHP 7.0+ - SQLite3 - Apache com mod_rewrite ### Instalação 1. Configure o Apache para permitir .htaccess 2. Certifique-se que o PHP tem permissão de escrita na pasta 3. O banco SQLite deve ter permissão de escrita ### Segurança - Altere a chave secreta JWT em `funcoes/auth.php` - Use HTTPS em produção - Mantenha o PHP e as dependências atualizadas ## 🛡️ Headers de Segurança A API já inclui headers básicos de segurança: - `X-Content-Type-Options: nosniff` - `X-Frame-Options: DENY` - `X-XSS-Protection: 1; mode=block` ## ⚠️ Tratamento de Erros A API retorna erros no formato: ```json { "error": "Mensagem do erro" } ``` Códigos HTTP: - 200: Sucesso - 201: Criado - 400: Erro na requisição - 401: Não autorizado - 404: Não encontrado - 405: Método não permitido - 500: Erro interno ## 📚 Documentação Adicional - [PHP PDO](https://www.php.net/manual/pt_BR/book.pdo.php) - [JWT Auth](https://jwt.io/) - [REST API Best Practices](https://restfulapi.net/) ''' with open('api/README.md', 'w', encoding='utf-8') as f: f.write(content) print("Criado: api/README.md") def main(): """Função principal""" try: # Obter informações do usuário sql_path, api_version, needs_auth, secret_key, admin_email, admin_password = obter_informacoes() print("\n=== CRIANDO ESTRUTURA DA API ===") # Criar estrutura de diretórios criar_estrutura_diretorios(api_version) # Criar arquivos das funções criar_database_php(sql_path) criar_security_php() if needs_auth: criar_auth_php(secret_key) criar_login_php(api_version) # Extrair tabelas e criar arquivos tabelas = extrair_tabelas_sql(sql_path) if not tabelas: print("Nenhuma tabela encontrada no SQL. Criando exemplos...") tabelas = ['usuarios', 'produtos'] for tabela in tabelas: criar_arquivo_tabela(tabela, api_version, needs_auth) # Criar banco de dados criar_banco_sqlite(sql_path, needs_auth, admin_email, admin_password) # Criar .htaccess criar_htaccess(api_version) # Criar documentação criar_readme() print(f"\n=== API CRIADA COM SUCESSO! ===") print(f"Versão: {api_version}") print(f"Autenticação: {'Sim' if needs_auth else 'Não'}") print(f"Tabelas: {', '.join(tabelas)}") print(f"Arquivos criados: {len(tabelas) + 5} arquivos") if needs_auth: print(f"\n🔐 CREDENCIAIS DO ADMIN:") print(f"Email: {admin_email}") print(f"Senha: {admin_password}") print(f"\n📝 Use estas credenciais no endpoint POST /{api_version}/login para obter o token") print(f"\nLeia o arquivo README.md para instruções de uso.") except KeyboardInterrupt: print(f"\n\nOperação cancelada pelo usuário.") except Exception as e: print(f"\nErro: {e}") if __name__ == "__main__": main()