diff --git a/ass-email/README.md b/ass-email/README.md deleted file mode 100644 index 9b5d454..0000000 --- a/ass-email/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Ass Email - GoHorse Jobs - -This directory handles the email templating and dispatchers, specifically configured for notification processing and mail tasks. - -## 🚨 AI Rules Warning 🚨 -Before making ANY changes to infrastructure deployment manifests or Kubernetes files, please refer to the project-wide AI Rules located at `.agent/rules.md`. -**Do NOT touch `k3s` or `k8s` files.** Do not alter raw RSA/base64 authentication keys. diff --git a/ass-email/assinatura-exemplo.html b/ass-email/assinatura-exemplo.html deleted file mode 100644 index 35ce533..0000000 --- a/ass-email/assinatura-exemplo.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Assinatura de Email - Exemplo - - - -
-

Exemplo Preenchido

- - - - - - - -
- - Moobz - - - - - - - - - - - - - - - - - - - - - - -
- João Silva -
- Desenvolvedor Full Stack -
- - - - - -
- 📞 - - +55 (11) 99999-9999 -
-
- - - - - -
- 📍 - - Av. Paulista, 1000 - São Paulo, SP -
-
- - - - - -
- ✉️ - - joao.silva@moobz.com.br -
-
- - - - - -
- 📸 - - @moobz.oficial -
-
-
-
- - - \ No newline at end of file diff --git a/ass-email/assinatura-template.html b/ass-email/assinatura-template.html deleted file mode 100644 index 5462ce1..0000000 --- a/ass-email/assinatura-template.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - Assinatura de Email - Moobz - - - -
-

Prévia da Assinatura de Email

- - - - - - - -
- - Moobz - - - - - - - - - - - - - - - - - - - - - - -
- {{NOME_SOBRENOME}} -
- {{CARGO}} -
- - - - - -
- Telefone - - {{TELEFONE}} -
-
- - - - - -
- Endereço - - {{ENDERECO}} -
-
- - - - - -
- Email - - {{EMAIL}} -
-
- - - - - -
- Instagram - - @{{INSTAGRAM}} -
-
-
- - -
- -
- Como usar:

- 1. Substitua os campos entre {{CHAVES}} pelos seus dados:
-    • {{NOME_SOBRENOME}} → Seu nome completo
-    • {{CARGO}} → Seu cargo
-    • {{TELEFONE}} → Seu telefone
-    • {{ENDERECO}} → Endereço da empresa
-    • {{EMAIL}} → Seu email
-    • {{INSTAGRAM}} → Usuário do Instagram (sem @)

- 2. Copie o HTML e cole nas configurações de assinatura do seu email. -
-
- - diff --git a/ass-email/assinaturademailmoobz.png b/ass-email/assinaturademailmoobz.png deleted file mode 100644 index 39661fb..0000000 Binary files a/ass-email/assinaturademailmoobz.png and /dev/null differ diff --git a/AGENT.md b/docs/context/AGENT.md similarity index 100% rename from AGENT.md rename to docs/context/AGENT.md diff --git a/DOMAIN.md b/docs/context/DOMAIN.md similarity index 100% rename from DOMAIN.md rename to docs/context/DOMAIN.md diff --git a/job-scraper-multisite/.dockerignore b/job-scraper-multisite/.dockerignore deleted file mode 100644 index 090e9b3..0000000 --- a/job-scraper-multisite/.dockerignore +++ /dev/null @@ -1,13 +0,0 @@ -# Python scraper .dockerignore -__pycache__/ -*.pyc -*.pyo -.venv/ -venv/ -.env -.git/ -.gitignore -README.md -*.md -output/*.csv -.DS_Store diff --git a/job-scraper-multisite/.env.example b/job-scraper-multisite/.env.example deleted file mode 100644 index 1478338..0000000 --- a/job-scraper-multisite/.env.example +++ /dev/null @@ -1,20 +0,0 @@ -# ============================================================================= -# GoHorse Jobs Scraper - Environment Variables Example -# ============================================================================= -# Copy this file to .env and update the values - -# Output Configuration -OUTPUT_DIR=./output -OUTPUT_FORMAT=csv - -# API Configuration (to send scraped data to backend) -BACKEND_API_URL=http://localhost:8521/api/v1 -API_TOKEN=your-api-token-here - -# Scraping Configuration -SCRAPE_DELAY_SECONDS=2 -MAX_PAGES_PER_SITE=10 -USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 - -# Logging -LOG_LEVEL=INFO diff --git a/job-scraper-multisite/.gitignore b/job-scraper-multisite/.gitignore deleted file mode 100644 index 1384c0a..0000000 --- a/job-scraper-multisite/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -.venv/ -venv/ -ENV/ - -# Output (keep .gitkeep) -output/*.csv - -# Environment -.env -.env.* -!.env.example - -# IDE -.idea/ -.vscode/ - -# OS -.DS_Store -Thumbs.db - -# Distribution -dist/ -build/ -*.egg-info/ diff --git a/job-scraper-multisite/Dockerfile b/job-scraper-multisite/Dockerfile deleted file mode 100644 index 31989a7..0000000 --- a/job-scraper-multisite/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# ============================================================================= -# GoHorse Jobs Scraper - Python Dockerfile -# ============================================================================= - -FROM mirror.gcr.io/library/python:3.12-slim - -WORKDIR /app - -# Install dependencies -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt - -# Copy source -COPY . . - -# Security: Run as non-root -RUN useradd -m -u 1001 scraper && \ - chown -R scraper:scraper /app - -USER scraper - -# Create output directory -RUN mkdir -p /app/output - -CMD ["python", "main_scraper.py"] diff --git a/job-scraper-multisite/README.md b/job-scraper-multisite/README.md deleted file mode 100644 index b76dda1..0000000 --- a/job-scraper-multisite/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# 🐴 JobScraper MultiSite - -Raspador de vagas de emprego multi-plataforma para sites de tecnologia brasileiros. - -## 📁 Estrutura do Projeto - -``` -JobScraper_MultiSite/ -├── main_scraper.py # Arquivo principal -├── scrapers/ # Módulos de raspagem por site -│ ├── __init__.py -│ ├── programathor_scraper.py -│ └── geekhunter_scraper.py -├── output/ # Arquivos CSV gerados -│ └── vagas_consolidadas.csv -├── requirements.txt -└── README.md -``` - -## 🚀 Instalação - -```bash -# Criar ambiente virtual (recomendado) -python -m venv venv -source venv/bin/activate # Linux/Mac -# ou: venv\Scripts\activate # Windows - -# Instalar dependências -pip install -r requirements.txt - -# Ou instalar manualmente: -pip install requests beautifulsoup4 pandas -``` - -## ▶️ Execução - -```bash -# Executar raspagem de todos os sites -python main_scraper.py - -# Testar um scraper individual -python -m scrapers.programathor_scraper -``` - -## 📊 Output - -Os resultados são salvos na pasta `output/`: -- `vagas_consolidadas.csv` - Versão mais recente -- `vagas_consolidadas_YYYYMMDD_HHMMSS.csv` - Versões com timestamp - -### Campos extraídos: -| Campo | Descrição | -|-------------|------------------------------| -| titulo | Título da vaga | -| empresa | Nome da empresa | -| localizacao | Localização/Modalidade | -| link | URL da vaga | -| fonte | Site de origem | - -## ➕ Adicionando Novos Sites - -1. Crie um novo arquivo em `scrapers/` (ex: `novosite_scraper.py`) -2. Implemente a função `scrape_novosite()` seguindo o padrão existente -3. Adicione ao dicionário `SITES` em `main_scraper.py`: - -```python -from scrapers.novosite_scraper import scrape_novosite - -SITES = { - 'programathor': scrape_programathor, - 'geekhunter': scrape_geekhunter, - 'novosite': scrape_novosite, # Novo! -} -``` - -## ⚠️ Boas Práticas Anti-Bloqueio - -- ✅ Sempre use `time.sleep()` entre requisições (mínimo 2s) -- ✅ Use headers que simulem um navegador real -- ✅ Não faça muitas requisições em sequência rápida -- ✅ Respeite o `robots.txt` de cada site -- ✅ Considere usar proxies para grandes volumes - -## 📝 Licença - -Uso educacional. Respeite os Termos de Serviço de cada site. diff --git a/job-scraper-multisite/main_scraper.py b/job-scraper-multisite/main_scraper.py deleted file mode 100644 index 9136ec9..0000000 --- a/job-scraper-multisite/main_scraper.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -""" -🐴 JobScraper MultiSite - Raspador de Vagas Multi-Plataforma -============================================================= -Consolida vagas de emprego de múltiplos sites de tecnologia. -""" - -import pandas as pd -from datetime import datetime -import os - -# Imports dos scrapers -from scrapers.programathor_scraper import scrape_programathor -from scrapers.geekhunter_scraper import scrape_geekhunter - -# Configuração: Sites a serem raspados -SITES = { - 'programathor': scrape_programathor, - 'geekhunter': scrape_geekhunter, -} - -# Pasta de output -OUTPUT_DIR = 'output' - - -def ensure_output_dir(): - """Cria a pasta de output se não existir.""" - if not os.path.exists(OUTPUT_DIR): - os.makedirs(OUTPUT_DIR) - print(f"📁 Pasta '{OUTPUT_DIR}' criada.") - - -def run_scrapers(sites_to_run: list = None) -> pd.DataFrame: - """ - Executa os scrapers configurados e consolida os resultados. - - Args: - sites_to_run: Lista de sites para raspar. Se None, raspa todos. - - Returns: - DataFrame consolidado com todas as vagas. - """ - if sites_to_run is None: - sites_to_run = list(SITES.keys()) - - all_vagas = [] - - print("=" * 60) - print("🐴 JobScraper MultiSite - Iniciando raspagem...") - print("=" * 60) - - for site_name in sites_to_run: - if site_name not in SITES: - print(f"⚠️ Site '{site_name}' não configurado. Pulando...") - continue - - print(f"\n📡 Processando: {site_name.upper()}") - print("-" * 40) - - scraper_func = SITES[site_name] - df = scraper_func() - - if not df.empty: - all_vagas.append(df) - - if all_vagas: - consolidated_df = pd.concat(all_vagas, ignore_index=True) - # Remover duplicatas baseado no link - consolidated_df.drop_duplicates(subset=['link'], inplace=True) - return consolidated_df - - return pd.DataFrame() - - -def save_results(df: pd.DataFrame, filename: str = None) -> str: - """ - Salva o DataFrame consolidado em CSV. - - Args: - df: DataFrame com as vagas. - filename: Nome do arquivo (opcional). - - Returns: - Caminho do arquivo salvo. - """ - ensure_output_dir() - - if filename is None: - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - filename = f'vagas_consolidadas_{timestamp}.csv' - - filepath = os.path.join(OUTPUT_DIR, filename) - df.to_csv(filepath, index=False, encoding='utf-8-sig') - - return filepath - - -def main(): - """Função principal.""" - # Executar scrapers - df = run_scrapers() - - print("\n" + "=" * 60) - - if df.empty: - print("❌ Nenhuma vaga encontrada.") - return - - # Estatísticas - print(f"📊 Total de vagas coletadas: {len(df)}") - print(f"📊 Fontes: {df['fonte'].value_counts().to_dict()}") - - # Salvar resultados - filepath = save_results(df) - print(f"\n💾 Vagas salvas em: {filepath}") - - # Também salvar versão "latest" - latest_path = save_results(df, 'vagas_consolidadas.csv') - print(f"💾 Versão atual: {latest_path}") - - print("=" * 60) - print("✅ Raspagem concluída com sucesso!") - - # Preview - print("\n📋 Preview das vagas:") - print(df[['titulo', 'empresa', 'fonte']].head(10).to_string(index=False)) - - -if __name__ == "__main__": - main() diff --git a/job-scraper-multisite/output/.gitkeep b/job-scraper-multisite/output/.gitkeep deleted file mode 100644 index b8cc9fd..0000000 --- a/job-scraper-multisite/output/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# Keep this folder in git diff --git a/job-scraper-multisite/requirements.txt b/job-scraper-multisite/requirements.txt deleted file mode 100644 index d36f2b3..0000000 --- a/job-scraper-multisite/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -requests>=2.28.0 -beautifulsoup4>=4.11.0 -pandas>=1.5.0 -lxml>=4.9.0 diff --git a/job-scraper-multisite/scrapers/__init__.py b/job-scraper-multisite/scrapers/__init__.py deleted file mode 100644 index ed9cf4e..0000000 --- a/job-scraper-multisite/scrapers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Scrapers package -# Each site has its own scraper module diff --git a/job-scraper-multisite/scrapers/geekhunter_scraper.py b/job-scraper-multisite/scrapers/geekhunter_scraper.py deleted file mode 100644 index c599f46..0000000 --- a/job-scraper-multisite/scrapers/geekhunter_scraper.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Scraper para GeekHunter - https://www.geekhunter.com.br/vagas -""" - -import requests -from bs4 import BeautifulSoup -import pandas as pd -import time - -# Headers para simular navegador e evitar bloqueios -HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7', -} - -def scrape_geekhunter(delay: float = 2.0) -> pd.DataFrame: - """ - Raspa vagas do site GeekHunter. - - Args: - delay: Tempo de espera antes da requisição (anti-bloqueio) - - Returns: - DataFrame com colunas: titulo, empresa, localizacao, link - """ - url = "https://www.geekhunter.com.br/vagas" - vagas = [] - - try: - # Delay anti-bloqueio - time.sleep(delay) - - print(f"🔍 Raspando vagas de: {url}") - response = requests.get(url, headers=HEADERS, timeout=30) - response.raise_for_status() - - soup = BeautifulSoup(response.text, 'html.parser') - - # Encontrar cards de vagas (ajustar seletores conforme estrutura do site) - job_cards = soup.select('.job-card') or soup.select('[class*="job"]') or soup.select('article') - - for card in job_cards: - try: - # Extrair título - titulo_elem = card.select_one('h2') or card.select_one('h3') or card.select_one('.title') - titulo = titulo_elem.get_text(strip=True) if titulo_elem else "N/A" - - # Extrair empresa - empresa_elem = card.select_one('.company') or card.select_one('[class*="company"]') - empresa = empresa_elem.get_text(strip=True) if empresa_elem else "N/A" - - # Extrair localização - loc_elem = card.select_one('.location') or card.select_one('[class*="location"]') - localizacao = loc_elem.get_text(strip=True) if loc_elem else "Remoto" - - # Extrair link - link_elem = card.select_one('a[href*="/vagas/"]') or card.select_one('a') - if link_elem: - href = link_elem.get('href', '') - link = f"https://www.geekhunter.com.br{href}" if href.startswith('/') else href - else: - link = url - - vagas.append({ - 'titulo': titulo, - 'empresa': empresa, - 'localizacao': localizacao, - 'link': link, - 'fonte': 'GeekHunter' - }) - except Exception as e: - print(f"⚠️ Erro ao processar card: {e}") - continue - - print(f"✅ {len(vagas)} vagas encontradas no GeekHunter") - - except requests.exceptions.RequestException as e: - print(f"❌ Erro na requisição ao GeekHunter: {e}") - except Exception as e: - print(f"❌ Erro inesperado no GeekHunter: {e}") - - return pd.DataFrame(vagas) - - -if __name__ == "__main__": - # Teste individual do scraper - df = scrape_geekhunter() - print(df.head()) diff --git a/job-scraper-multisite/scrapers/programathor_scraper.py b/job-scraper-multisite/scrapers/programathor_scraper.py deleted file mode 100644 index dc1bf88..0000000 --- a/job-scraper-multisite/scrapers/programathor_scraper.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Scraper para ProgramaThor - https://programathor.com.br/jobs -""" - -import requests -from bs4 import BeautifulSoup -import pandas as pd -import time - -# Headers para simular navegador e evitar bloqueios -HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7', -} - -def scrape_programathor(delay: float = 2.0) -> pd.DataFrame: - """ - Raspa vagas do site ProgramaThor. - - Args: - delay: Tempo de espera antes da requisição (anti-bloqueio) - - Returns: - DataFrame com colunas: titulo, empresa, localizacao, link - """ - url = "https://programathor.com.br/jobs" - vagas = [] - - try: - # Delay anti-bloqueio - time.sleep(delay) - - print(f"🔍 Raspando vagas de: {url}") - response = requests.get(url, headers=HEADERS, timeout=30) - response.raise_for_status() - - soup = BeautifulSoup(response.text, 'html.parser') - - # Encontrar cards de vagas (ajustar seletores conforme estrutura do site) - job_cards = soup.select('.cell-list') - - for card in job_cards: - try: - # Extrair título - titulo_elem = card.select_one('h3') or card.select_one('.title') - titulo = titulo_elem.get_text(strip=True) if titulo_elem else "N/A" - - # Extrair empresa - empresa_elem = card.select_one('.company-name') or card.select_one('h4') - empresa = empresa_elem.get_text(strip=True) if empresa_elem else "N/A" - - # Extrair localização - loc_elem = card.select_one('.location') or card.select_one('.info') - localizacao = loc_elem.get_text(strip=True) if loc_elem else "Remoto" - - # Extrair link - link_elem = card.select_one('a[href*="/jobs/"]') - if link_elem: - href = link_elem.get('href', '') - link = f"https://programathor.com.br{href}" if href.startswith('/') else href - else: - link = url - - vagas.append({ - 'titulo': titulo, - 'empresa': empresa, - 'localizacao': localizacao, - 'link': link, - 'fonte': 'ProgramaThor' - }) - except Exception as e: - print(f"⚠️ Erro ao processar card: {e}") - continue - - print(f"✅ {len(vagas)} vagas encontradas no ProgramaThor") - - except requests.exceptions.RequestException as e: - print(f"❌ Erro na requisição ao ProgramaThor: {e}") - except Exception as e: - print(f"❌ Erro inesperado no ProgramaThor: {e}") - - return pd.DataFrame(vagas) - - -if __name__ == "__main__": - # Teste individual do scraper - df = scrape_programathor() - print(df.head())