Spaces:
Build error
Build error
Upload 33 files
Browse files- .gitignore +3 -0
- Dockerfile +23 -0
- Dockerrun.aws.json +14 -0
- README.md +43 -10
- captcha_temporario.png +0 -0
- data_delete/deletar_advogado.py +57 -0
- data_delete/deletar_processo.py +57 -0
- data_insert/inserir_advogado.py +45 -0
- data_insert/inserir_processo.py +45 -0
- monitoramento.db +0 -0
- requirements.txt +68 -0
- server.py +310 -0
- src/consulta.py +203 -0
- src/enviador_de_email.py +186 -0
- src/models.py +64 -0
- src/monitoramento_processual/monitoramento_tjpr.py +336 -0
- src/monitoramento_processual/monitoramento_tjsc.py +347 -0
- src/monitoramento_processual/monitoramento_tjsp.py +155 -0
- src/monitoramento_processual/task_dispatcher.py +13 -0
- src/processamento_tjdf.py +192 -0
- src/tj_research/tj_researcher.py +24 -0
- static/consulta.html +232 -0
- static/dashboard.html +283 -0
- static/index.html +114 -0
- static/script.js +138 -0
- static/style.css +99 -0
- static/tasks.html +170 -0
- static/user_config.html +136 -0
- user_autentication/user_bd.py +21 -0
- user_autentication/user_hash.py +51 -0
- user_autentication/verificar_token.py +15 -0
- user_configuration/user_config.py +62 -0
- utils/util.py +25 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__
|
| 2 |
+
.venv
|
| 3 |
+
.vscode
|
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim-buster
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# Atualiza o sistema e instala dependências essenciais (exemplo: gcc para compilações nativas)
|
| 5 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 6 |
+
gcc \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
# Define o diretório de trabalho
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# Copia o arquivo de requisitos e instala as dependências Python
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 15 |
+
|
| 16 |
+
# Copia o restante do código da aplicação para a imagem
|
| 17 |
+
COPY . .
|
| 18 |
+
|
| 19 |
+
# Exponha a porta desejada (neste exemplo, 80)
|
| 20 |
+
EXPOSE 80
|
| 21 |
+
|
| 22 |
+
# Comando para iniciar a aplicação usando uvicorn
|
| 23 |
+
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "80"]
|
Dockerrun.aws.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"AWSEBDockerrunVersion": "1",
|
| 3 |
+
"Image": {
|
| 4 |
+
"Name": "python:3.11-slim-buster",
|
| 5 |
+
"Update": "true"
|
| 6 |
+
},
|
| 7 |
+
"Ports": [
|
| 8 |
+
{
|
| 9 |
+
"ContainerPort": 5000,
|
| 10 |
+
"HostPort": 80
|
| 11 |
+
}
|
| 12 |
+
],
|
| 13 |
+
"Logging": "/var/log"
|
| 14 |
+
}
|
README.md
CHANGED
|
@@ -1,10 +1,43 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Monitoramento de processos dos Tribunais de Justiça do Brasil. :robot:
|
| 2 |
+
|
| 3 |
+
## Introdução
|
| 4 |
+
Monitoramento de processos dos Tribunais de Justiça do Brasil - BETA
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
## Respostas da API
|
| 8 |
+
|
| 9 |
+
| HTTP CODE | `code` | Descrição |
|
| 10 |
+
| --------- | ---------- | --------- |
|
| 11 |
+
| `200` | 0 | Sucesso |
|
| 12 |
+
| `422` | 7 | Não foi possível processar |
|
| 13 |
+
| `502` | 5 | Bad Gateway |
|
| 14 |
+
| `512` | 4 | Erro ao executar parse da pagina |
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
### Para executar o bot manualmente, siga os passos abaixo:
|
| 19 |
+
|
| 20 |
+
1. Baixe o repositorio:
|
| 21 |
+
```
|
| 22 |
+
git clone https://github.com/reissbruno/monitoramento-processual
|
| 23 |
+
```
|
| 24 |
+
2. Mude de diretorio:
|
| 25 |
+
```
|
| 26 |
+
cd monitoramento-processual
|
| 27 |
+
```
|
| 28 |
+
3. Instale as bibliotecas requeridas:
|
| 29 |
+
```
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
```
|
| 32 |
+
4. Execute o bot:
|
| 33 |
+
```
|
| 34 |
+
uvicorn server:app --host 0.0.0.0
|
| 35 |
+
```
|
| 36 |
+
5. Abra o navegador a acesse a URL: http://localhost:8000/
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
| ENV VAR | Descrição |
|
| 40 |
+
| ------- | ---------- |
|
| 41 |
+
| `BOT_NAME` | Nome do bot. Util caso houver mais de um container do mesmo bot rodando. (*default: transparencia-receita-fortaleza*) |
|
| 42 |
+
| `LOG_LEVEL` | Nivel de log. Valores aceitos: (DEBUG, INFO, WARNING, ERROR - *default: INFO*) |
|
| 43 |
+
| `TIMEOUT` | Total de **segundos** que o bot espera antes de encerrar a conexao com o site. (*default: 180*) |
|
captcha_temporario.png
ADDED
|
data_delete/deletar_advogado.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manipulação do bando de dados de adição de nomes de advogados, deletar do banco de dados
|
| 2 |
+
|
| 3 |
+
import sqlite3
|
| 4 |
+
|
| 5 |
+
DATABASE_FILE = "monitoramento.db"
|
| 6 |
+
|
| 7 |
+
def initialize_db():
|
| 8 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 9 |
+
cursor = conn.cursor()
|
| 10 |
+
cursor.execute('''
|
| 11 |
+
CREATE TABLE IF NOT EXISTS monitor_advogados (
|
| 12 |
+
nome_advogado TEXT PRIMARY KEY,
|
| 13 |
+
credencial TEXT NOT NULL,
|
| 14 |
+
uf TEXT NOT NULL
|
| 15 |
+
)
|
| 16 |
+
''')
|
| 17 |
+
conn.commit()
|
| 18 |
+
conn.close()
|
| 19 |
+
|
| 20 |
+
initialize_db()
|
| 21 |
+
|
| 22 |
+
async def delete_advogado(nome_advogado: str, uf: str, credencial: str):
|
| 23 |
+
"""
|
| 24 |
+
Deleta um nome de advogado do banco de dados de monitoramento após verificar a credencial.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
nome_advogado (str): Nome do advogado a ser removido.
|
| 28 |
+
uf (str): Unidade federativa relacionada.
|
| 29 |
+
credencial (str): Credencial do responsável pela requisição.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
dict: Mensagem com o status da operação.
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 36 |
+
cursor = conn.cursor()
|
| 37 |
+
# Buscar registro pelo nome do advogado e uf
|
| 38 |
+
cursor.execute('''
|
| 39 |
+
SELECT credencial FROM monitor_advogados WHERE nome_advogado = ? AND uf = ?
|
| 40 |
+
''', (nome_advogado, uf))
|
| 41 |
+
row = cursor.fetchone()
|
| 42 |
+
if not row:
|
| 43 |
+
conn.close()
|
| 44 |
+
return {"code": 404, "message": "Advogado não encontrado."}
|
| 45 |
+
stored_credencial = row[0]
|
| 46 |
+
if stored_credencial != credencial:
|
| 47 |
+
conn.close()
|
| 48 |
+
return {"code": 403, "message": "Credencial inválida. Não autorizado para deletar este advogado."}
|
| 49 |
+
# Credencial verificada; realiza a deleção
|
| 50 |
+
cursor.execute('''
|
| 51 |
+
DELETE FROM monitor_advogados WHERE nome_advogado = ? AND uf = ?
|
| 52 |
+
''', (nome_advogado, uf))
|
| 53 |
+
conn.commit()
|
| 54 |
+
conn.close()
|
| 55 |
+
return {"code": 200, "message": "Advogado deletado com sucesso."}
|
| 56 |
+
except Exception as e:
|
| 57 |
+
return {"code": 500, "message": f"Erro ao deletar advogado: {str(e)}"}
|
data_delete/deletar_processo.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manipulação do bando de dados de adição de processos, deletar do banco de dados
|
| 2 |
+
|
| 3 |
+
import sqlite3
|
| 4 |
+
|
| 5 |
+
DATABASE_FILE = "monitoramento.db"
|
| 6 |
+
|
| 7 |
+
def initialize_db():
|
| 8 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 9 |
+
cursor = conn.cursor()
|
| 10 |
+
cursor.execute('''
|
| 11 |
+
CREATE TABLE IF NOT EXISTS monitor_processos (
|
| 12 |
+
numero_processo TEXT PRIMARY KEY,
|
| 13 |
+
credencial TEXT NOT NULL,
|
| 14 |
+
uf TEXT NOT NULL
|
| 15 |
+
)
|
| 16 |
+
''')
|
| 17 |
+
conn.commit()
|
| 18 |
+
conn.close()
|
| 19 |
+
|
| 20 |
+
initialize_db()
|
| 21 |
+
|
| 22 |
+
async def delete_processo(numero_processo: str, uf: str, credencial: str):
|
| 23 |
+
"""
|
| 24 |
+
Deleta um número de processo do banco de dados de monitoramento após verificar a credencial.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
numero_processo (str): Número do processo a ser removido.
|
| 28 |
+
uf (str): Unidade federativa relacionada.
|
| 29 |
+
credencial (str): Credencial do responsável pela requisição.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
dict: Mensagem com o status da operação.
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 36 |
+
cursor = conn.cursor()
|
| 37 |
+
# Buscar registro pelo número do processo e uf
|
| 38 |
+
cursor.execute('''
|
| 39 |
+
SELECT credencial FROM monitor_processos WHERE numero_processo = ? AND uf = ?
|
| 40 |
+
''', (numero_processo, uf))
|
| 41 |
+
row = cursor.fetchone()
|
| 42 |
+
if not row:
|
| 43 |
+
conn.close()
|
| 44 |
+
return {"code": 404, "message": "Processo não encontrado."}
|
| 45 |
+
stored_credencial = row[0]
|
| 46 |
+
if stored_credencial != credencial:
|
| 47 |
+
conn.close()
|
| 48 |
+
return {"code": 403, "message": "Credencial inválida. Não autorizado para deletar este processo."}
|
| 49 |
+
# Credencial verificada; realiza a deleção
|
| 50 |
+
cursor.execute('''
|
| 51 |
+
DELETE FROM monitor_processos WHERE numero_processo = ? AND uf = ?
|
| 52 |
+
''', (numero_processo, uf))
|
| 53 |
+
conn.commit()
|
| 54 |
+
conn.close()
|
| 55 |
+
return {"code": 200, "message": "Processo deletado com sucesso."}
|
| 56 |
+
except Exception as e:
|
| 57 |
+
return {"code": 500, "message": f"Erro ao deletar processo: {str(e)}"}
|
data_insert/inserir_advogado.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manipulacao de dados para o sistema de advogados, adicionar advogados
|
| 2 |
+
|
| 3 |
+
import sqlite3
|
| 4 |
+
|
| 5 |
+
DATABASE_FILE = "monitoramento.db"
|
| 6 |
+
|
| 7 |
+
def initialize_db():
|
| 8 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 9 |
+
cursor = conn.cursor()
|
| 10 |
+
cursor.execute('''
|
| 11 |
+
CREATE TABLE IF NOT EXISTS monitor_advogados (
|
| 12 |
+
nome_advogado TEXT PRIMARY KEY,
|
| 13 |
+
credencial TEXT NOT NULL,
|
| 14 |
+
uf TEXT NOT NULL
|
| 15 |
+
)
|
| 16 |
+
''')
|
| 17 |
+
conn.commit()
|
| 18 |
+
conn.close()
|
| 19 |
+
|
| 20 |
+
initialize_db()
|
| 21 |
+
|
| 22 |
+
async def insert_advogado(nome_advogado: str, uf: str, credencial: str):
|
| 23 |
+
"""
|
| 24 |
+
Insere ou atualiza um nome de advogado no banco de dados de monitoramento.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
nome_advogado (str): Nome do advogado a ser monitorado.
|
| 28 |
+
uf (str): Unidade federativa relacionada.
|
| 29 |
+
credencial (str): Credencial do responsável pela requisição.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
dict: Mensagem com o status da operação.
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 36 |
+
cursor = conn.cursor()
|
| 37 |
+
cursor.execute('''
|
| 38 |
+
INSERT OR REPLACE INTO monitor_advogados (nome_advogado, credencial, uf)
|
| 39 |
+
VALUES (?, ?, ?)
|
| 40 |
+
''', (nome_advogado, credencial, uf))
|
| 41 |
+
conn.commit()
|
| 42 |
+
conn.close()
|
| 43 |
+
return {"code": 200, "message": "Advogado inserido com sucesso."}
|
| 44 |
+
except Exception as e:
|
| 45 |
+
return {"code": 500, "message": f"Erro ao inserir advogado: {str(e)}"}
|
data_insert/inserir_processo.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manipulação do banco de dados de adição de processos, adicionar do banco de dados
|
| 2 |
+
|
| 3 |
+
import sqlite3
|
| 4 |
+
|
| 5 |
+
DATABASE_FILE = "monitoramento.db"
|
| 6 |
+
|
| 7 |
+
def initialize_db():
|
| 8 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 9 |
+
cursor = conn.cursor()
|
| 10 |
+
cursor.execute('''
|
| 11 |
+
CREATE TABLE IF NOT EXISTS monitor_processos (
|
| 12 |
+
numero_processo TEXT PRIMARY KEY,
|
| 13 |
+
credencial TEXT NOT NULL,
|
| 14 |
+
uf TEXT NOT NULL
|
| 15 |
+
)
|
| 16 |
+
''')
|
| 17 |
+
conn.commit()
|
| 18 |
+
conn.close()
|
| 19 |
+
|
| 20 |
+
initialize_db()
|
| 21 |
+
|
| 22 |
+
async def insert_processo(numero_processo: str, uf: str, credencial: str):
|
| 23 |
+
"""
|
| 24 |
+
Insere ou atualiza um número de processo no banco de dados de monitoramento.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
numero_processo (str): Número do processo a ser monitorado.
|
| 28 |
+
uf (str): Unidade federativa relacionada.
|
| 29 |
+
credencial (str): Credencial do responsável pela requisição.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
dict: Mensagem com o status da operação.
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
conn = sqlite3.connect(DATABASE_FILE)
|
| 36 |
+
cursor = conn.cursor()
|
| 37 |
+
cursor.execute('''
|
| 38 |
+
INSERT OR REPLACE INTO monitor_processos (numero_processo, credencial, uf)
|
| 39 |
+
VALUES (?, ?, ?)
|
| 40 |
+
''', (numero_processo, credencial, uf))
|
| 41 |
+
conn.commit()
|
| 42 |
+
conn.close()
|
| 43 |
+
return {"code": 200, "message": "Processo inserido com sucesso."}
|
| 44 |
+
except Exception as e:
|
| 45 |
+
return {"code": 500, "message": f"Erro ao inserir processo: {str(e)}"}
|
monitoramento.db
ADDED
|
Binary file (36.9 kB). View file
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiohttp==3.9.5
|
| 2 |
+
aiosignal==1.3.2
|
| 3 |
+
annotated-types==0.6.0
|
| 4 |
+
anyio==4.8.0
|
| 5 |
+
attrs==23.2.0
|
| 6 |
+
bcrypt==3.2.0
|
| 7 |
+
beautifulsoup4==4.13.3
|
| 8 |
+
boto3==1.37.17
|
| 9 |
+
botocore==1.37.17
|
| 10 |
+
capmonstercloudclient==1.6.0
|
| 11 |
+
certifi==2025.1.31
|
| 12 |
+
cffi==1.17.1
|
| 13 |
+
charset-normalizer==3.4.1
|
| 14 |
+
click==8.1.8
|
| 15 |
+
colorama==0.4.6
|
| 16 |
+
coloredlogs==15.0.1
|
| 17 |
+
ddddocr==1.5.6
|
| 18 |
+
dnspython==2.7.0
|
| 19 |
+
ecdsa==0.19.1
|
| 20 |
+
email_validator==2.2.0
|
| 21 |
+
fastapi==0.110.1
|
| 22 |
+
filelock==3.17.0
|
| 23 |
+
flatbuffers==25.2.10
|
| 24 |
+
frozenlist==1.5.0
|
| 25 |
+
fsspec==2025.2.0
|
| 26 |
+
gradio_client==1.7.1
|
| 27 |
+
greenlet==3.1.1
|
| 28 |
+
gunicorn==23.0.0
|
| 29 |
+
h11==0.14.0
|
| 30 |
+
httpcore==1.0.7
|
| 31 |
+
httpx==0.28.1
|
| 32 |
+
huggingface-hub==0.29.1
|
| 33 |
+
humanfriendly==10.0
|
| 34 |
+
idna==3.10
|
| 35 |
+
jmespath==1.0.1
|
| 36 |
+
mpmath==1.3.0
|
| 37 |
+
multidict==6.0.5
|
| 38 |
+
onnxruntime==1.20.1
|
| 39 |
+
opencv-python-headless==4.11.0.86
|
| 40 |
+
packaging==24.2
|
| 41 |
+
passlib==1.7.4
|
| 42 |
+
pillow==11.1.0
|
| 43 |
+
propcache==0.3.0
|
| 44 |
+
protobuf==5.29.3
|
| 45 |
+
psycopg2==2.9.10
|
| 46 |
+
pyasn1==0.4.8
|
| 47 |
+
pycparser==2.22
|
| 48 |
+
pydantic==2.1.1
|
| 49 |
+
pydantic_core==2.4.0
|
| 50 |
+
pyreadline3==3.5.4
|
| 51 |
+
python-dateutil==2.9.0.post0
|
| 52 |
+
python-jose==3.4.0
|
| 53 |
+
PyYAML==6.0.2
|
| 54 |
+
requests==2.32.3
|
| 55 |
+
rsa==4.9
|
| 56 |
+
s3transfer==0.11.4
|
| 57 |
+
six==1.17.0
|
| 58 |
+
sniffio==1.3.1
|
| 59 |
+
soupsieve==2.6
|
| 60 |
+
SQLAlchemy==2.0.39
|
| 61 |
+
starlette==0.37.2
|
| 62 |
+
sympy==1.13.3
|
| 63 |
+
tqdm==4.67.1
|
| 64 |
+
typing_extensions==4.12.2
|
| 65 |
+
urllib3==2.3.0
|
| 66 |
+
uvicorn==0.29.0
|
| 67 |
+
websockets==14.2
|
| 68 |
+
yarl==1.18.3
|
server.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from os import environ as env
|
| 3 |
+
from fastapi.logger import logger as fastapi_logger
|
| 4 |
+
from fastapi.staticfiles import StaticFiles
|
| 5 |
+
from fastapi.responses import FileResponse
|
| 6 |
+
from fastapi import FastAPI, Query, Depends, HTTPException, status, Request
|
| 7 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
+
|
| 9 |
+
# Local imports
|
| 10 |
+
from src import models, consulta
|
| 11 |
+
from src.monitoramento_processual.task_dispatcher import process_dispatcher
|
| 12 |
+
from data_delete.deletar_advogado import delete_advogado
|
| 13 |
+
from data_delete.deletar_processo import delete_processo
|
| 14 |
+
from data_insert.inserir_processo import insert_processo
|
| 15 |
+
from data_insert.inserir_advogado import insert_advogado
|
| 16 |
+
from user_autentication.user_hash import verificar_senha, criar_token, gerar_hash, verificar_token
|
| 17 |
+
from src.tj_research import tj_researcher
|
| 18 |
+
from src.models import UserUpdateUsername, UserUpdatePassword
|
| 19 |
+
from user_configuration.user_config import trocar_username, trocar_senha
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
import sqlite3
|
| 23 |
+
from pydantic import BaseModel
|
| 24 |
+
|
| 25 |
+
# Logger setup
|
| 26 |
+
msg_frt = "[%(asctime)s] %(levelname)s [%(name)s] - %(message)s"
|
| 27 |
+
time_frt = "%Y-%m-%d %H:%M:%S"
|
| 28 |
+
formatter = logging.Formatter(msg_frt, time_frt)
|
| 29 |
+
handler = logging.StreamHandler()
|
| 30 |
+
handler.setFormatter(formatter)
|
| 31 |
+
LOG_LEVEL = env.get('LOG_LEVEL', default='INFO')
|
| 32 |
+
fastapi_logger.addHandler(handler)
|
| 33 |
+
fastapi_logger.setLevel(LOG_LEVEL)
|
| 34 |
+
|
| 35 |
+
logger_name = env.get('BOT_NAME', default='Monitoramento Processual')
|
| 36 |
+
fastapi_logger.name = logger_name
|
| 37 |
+
|
| 38 |
+
# Dependencia para verificar token
|
| 39 |
+
def get_current_user(request: Request):
|
| 40 |
+
auth_header = request.headers.get('Authorization')
|
| 41 |
+
fastapi_logger.info(f"Authorization Header: {auth_header}")
|
| 42 |
+
|
| 43 |
+
if auth_header is None or not auth_header.startswith("Bearer "):
|
| 44 |
+
fastapi_logger.error("Authorization header ausente ou mal formado!")
|
| 45 |
+
raise HTTPException(
|
| 46 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 47 |
+
detail="Token ausente"
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
token = auth_header.split(" ")[1]
|
| 51 |
+
fastapi_logger.info(f"Token extraído: {token}")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
username = verificar_token(token)
|
| 55 |
+
fastapi_logger.info(f"Usuário autenticado: {username}")
|
| 56 |
+
return username
|
| 57 |
+
except Exception as e:
|
| 58 |
+
fastapi_logger.error(f"Erro ao verificar token: {str(e)}")
|
| 59 |
+
raise HTTPException(
|
| 60 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 61 |
+
detail="Token inválido ou expirado"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# API description e metadata
|
| 65 |
+
desc = '<a href="https://esaj.tjsp.jus.br/cpopg/search.do?conversationId=&cbPesquisa=NMADVOGADO&dadosConsulta.valorConsulta=&cdForo=-1" target="_blank">FONTE</a>'
|
| 66 |
+
|
| 67 |
+
tags_metadata = [
|
| 68 |
+
{
|
| 69 |
+
'name': 'monitoramento-processual',
|
| 70 |
+
'description': 'Consulta processual nos Tribunais de Justiça no Brasil',
|
| 71 |
+
}
|
| 72 |
+
]
|
| 73 |
+
|
| 74 |
+
responses = {
|
| 75 |
+
407: {'model': models.ResponseError, 'description': 'Proxy Authentication Required'},
|
| 76 |
+
422: {'model': models.ResponseError, 'description': 'Unprocessable Entity'},
|
| 77 |
+
500: {'model': models.ResponseError, 'description': 'Erro interno no servidor'},
|
| 78 |
+
502: {'model': models.ResponseError, 'description': 'Bad Gateway'},
|
| 79 |
+
504: {'model': models.ResponseError, 'description': 'Conexao com o site excedeu tempo limite'},
|
| 80 |
+
509: {'model': models.ResponseError, 'description': 'Nao foi possivel resolver o captcha'},
|
| 81 |
+
512: {'model': models.ResponseError, 'description': 'Erro ao executar parse da pagina'},
|
| 82 |
+
513: {'model': models.ResponseError, 'description': 'Argumentos invalidos'}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
app = FastAPI(
|
| 86 |
+
title='REST API - Monitoramento processual nos Tribunais de Justiça do Brasil',
|
| 87 |
+
description=desc,
|
| 88 |
+
debug=False,
|
| 89 |
+
openapi_tags=tags_metadata,
|
| 90 |
+
docs_url='/docs'
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
app.add_middleware(
|
| 94 |
+
CORSMiddleware,
|
| 95 |
+
allow_origins=['*'],
|
| 96 |
+
allow_credentials=True,
|
| 97 |
+
allow_methods=['*'],
|
| 98 |
+
allow_headers=['*'])
|
| 99 |
+
|
| 100 |
+
uf = Query(
|
| 101 |
+
description='UF referente ao advogado ou processo a ser consultado',
|
| 102 |
+
example='SP',
|
| 103 |
+
enum=['AC', 'AL', 'AP', 'AM', 'BA', 'CE', 'DF', 'ES', 'GO', 'MA', 'MT', 'MS', 'MG',
|
| 104 |
+
'PA', 'PB', 'PR', 'PE', 'PI', 'RJ', 'RN', 'RS', 'RO', 'RR', 'SC', 'SP',
|
| 105 |
+
'SE', 'TO'],
|
| 106 |
+
default=None,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
parametro_dummy = Query(
|
| 110 |
+
...,
|
| 111 |
+
description='parametro dummy',
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
nome_advogado = Query(
|
| 115 |
+
...,
|
| 116 |
+
description='Nome do advogado a ser inserido',
|
| 117 |
+
example='Nome do Advogado'
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
numero_processo = Query(
|
| 121 |
+
...,
|
| 122 |
+
description='Número do processo a ser monitorado o andamento',
|
| 123 |
+
example='0000000-00.0000.0.00.0000'
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# Informacoes do usuario
|
| 128 |
+
@app.get("/api/monitoramento-processual/usuario-info", tags=["monitoramento-processual"])
|
| 129 |
+
def get_user_info(username: str):
|
| 130 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 131 |
+
cursor = conn.cursor()
|
| 132 |
+
cursor.execute("SELECT nome, sobrenome FROM usuarios WHERE username = ?", (username,))
|
| 133 |
+
row = cursor.fetchone()
|
| 134 |
+
conn.close()
|
| 135 |
+
|
| 136 |
+
if row:
|
| 137 |
+
return {"nome": row[0], "sobrenome": row[1]}
|
| 138 |
+
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
| 139 |
+
|
| 140 |
+
# Criação do banco de dados e tabelas
|
| 141 |
+
@app.get("/api/monitoramento-processual/consulta/listar-advogados", tags=["monitoramento-processual"])
|
| 142 |
+
def listar_advogados(current_user: str = Depends(get_current_user)):
|
| 143 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 144 |
+
cursor = conn.cursor()
|
| 145 |
+
cursor.execute("SELECT nome_advogado, uf FROM monitor_advogados WHERE credencial = ?", (current_user,))
|
| 146 |
+
rows = cursor.fetchall()
|
| 147 |
+
conn.close()
|
| 148 |
+
return [{"nome_advogado": row[0], "uf": row[1]} for row in rows]
|
| 149 |
+
|
| 150 |
+
@app.get("/api/monitoramento-processual/consulta/listar-processos", tags=["monitoramento-processual"])
|
| 151 |
+
def listar_processos(current_user: str = Depends(get_current_user)):
|
| 152 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 153 |
+
cursor = conn.cursor()
|
| 154 |
+
cursor.execute("SELECT numero_processo, uf FROM monitor_processos WHERE credencial = ?", (current_user,))
|
| 155 |
+
rows = cursor.fetchall()
|
| 156 |
+
conn.close()
|
| 157 |
+
return [{"numero_processo": row[0], "uf": row[1]} for row in rows]
|
| 158 |
+
|
| 159 |
+
@app.get("/api/monitoramento-processual/consulta/listar-tarefas", tags=["monitoramento-processual"])
|
| 160 |
+
def listar_tarefas(current_user: str = Depends(get_current_user)):
|
| 161 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 162 |
+
cursor = conn.cursor()
|
| 163 |
+
cursor.execute("""
|
| 164 |
+
SELECT tipo, nome, uf, monitoramento, intervalo, unidade
|
| 165 |
+
FROM tarefas
|
| 166 |
+
WHERE username = ?
|
| 167 |
+
""", (current_user,))
|
| 168 |
+
rows = cursor.fetchall()
|
| 169 |
+
conn.close()
|
| 170 |
+
return [{
|
| 171 |
+
"tipo": row[0],
|
| 172 |
+
"nome": row[1],
|
| 173 |
+
"uf": row[2],
|
| 174 |
+
"monitoramento": row[3],
|
| 175 |
+
"intervalo": row[4],
|
| 176 |
+
"unidade": row[5]
|
| 177 |
+
} for row in rows]
|
| 178 |
+
|
| 179 |
+
# Retorna o HTML para o frontend
|
| 180 |
+
@app.get("/", tags=["monitoramento-processual"])
|
| 181 |
+
def serve_index():
|
| 182 |
+
return FileResponse("static/index.html")
|
| 183 |
+
|
| 184 |
+
@app.get("/dashboard", tags=["monitoramento-processual"])
|
| 185 |
+
def serve_dashboard():
|
| 186 |
+
return FileResponse("static/dashboard.html")
|
| 187 |
+
|
| 188 |
+
@app.get("/tasks", tags=["monitoramento-processual"])
|
| 189 |
+
def serve_tasks():
|
| 190 |
+
return FileResponse("static/tasks.html")
|
| 191 |
+
|
| 192 |
+
@app.get("/consulta", tags=["monitoramento-processual"])
|
| 193 |
+
def serve_consulta():
|
| 194 |
+
return FileResponse("static/consulta.html")
|
| 195 |
+
|
| 196 |
+
@app.get("/user_config", tags=["monitoramento-processual"])
|
| 197 |
+
def serve_user_config():
|
| 198 |
+
return FileResponse("static/user_config.html")
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
# Endpoints para consulta de processos e advogados
|
| 202 |
+
|
| 203 |
+
@app.delete("/api/monitoramento-processual/consulta/deletar-processo", tags=["monitoramento-processual"], responses=responses)
|
| 204 |
+
async def delete_processos(numero_processo: str = numero_processo, uf: str = uf, current_user: str = Depends(get_current_user)):
|
| 205 |
+
return await delete_processo(numero_processo, uf, current_user)
|
| 206 |
+
|
| 207 |
+
@app.delete("/api/monitoramento-processual/consulta/deletar-advogado", tags=["monitoramento-processual"], responses=responses)
|
| 208 |
+
async def delete_advogado_endpoint(nome_advogado: str = nome_advogado, uf: str = uf, current_user: str = Depends(get_current_user)):
|
| 209 |
+
return await delete_advogado(nome_advogado, uf, current_user)
|
| 210 |
+
|
| 211 |
+
@app.post("/api/monitoramento-processual/consulta/inserir-processo", tags=["monitoramento-processual"], responses=responses)
|
| 212 |
+
async def insert_processos_endpoint(numero_processo: str = numero_processo, uf: str = uf, current_user: str = Depends(get_current_user)):
|
| 213 |
+
return await insert_processo(numero_processo, uf, current_user)
|
| 214 |
+
|
| 215 |
+
@app.post("/api/monitoramento-processual/consulta/inserir-advogado", tags=["monitoramento-processual"], responses=responses)
|
| 216 |
+
async def insert_advogado_endpoint(nome_advogado: str = nome_advogado, uf: str = uf, current_user: str = Depends(get_current_user)):
|
| 217 |
+
return await insert_advogado(nome_advogado, uf, current_user)
|
| 218 |
+
|
| 219 |
+
@app.post("/api/monitoramento-processual/consulta/inserir-tarefa", tags=["monitoramento-processual"], responses=responses)
|
| 220 |
+
async def insert_tarefa(
|
| 221 |
+
tipo: str = Query(..., description="Tipo (advogado ou processo)"),
|
| 222 |
+
nome: str = Query(..., description="Nome do advogado ou número do processo"),
|
| 223 |
+
uf: str = Query(..., description="UF"),
|
| 224 |
+
monitoramento: str = Query(..., description="Deseja monitoramento? (sim ou nao)"),
|
| 225 |
+
intervalo: int = Query(..., description="Intervalo do monitoramento"),
|
| 226 |
+
unidade: str = Query(..., description="Unidade do intervalo (minutos, horas ou dias)"),
|
| 227 |
+
current_user: str = Depends(get_current_user)
|
| 228 |
+
):
|
| 229 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 230 |
+
cursor = conn.cursor()
|
| 231 |
+
cursor.execute('''
|
| 232 |
+
CREATE TABLE IF NOT EXISTS tarefas (
|
| 233 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 234 |
+
username TEXT NOT NULL,
|
| 235 |
+
tipo TEXT NOT NULL,
|
| 236 |
+
nome TEXT NOT NULL,
|
| 237 |
+
uf TEXT NOT NULL,
|
| 238 |
+
monitoramento TEXT NOT NULL,
|
| 239 |
+
intervalo INTEGER NOT NULL,
|
| 240 |
+
unidade TEXT NOT NULL
|
| 241 |
+
)
|
| 242 |
+
''')
|
| 243 |
+
cursor.execute('''
|
| 244 |
+
INSERT INTO tarefas (username, tipo, nome, uf, monitoramento, intervalo, unidade)
|
| 245 |
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 246 |
+
''', (current_user, tipo, nome, uf, monitoramento, intervalo, unidade))
|
| 247 |
+
conn.commit()
|
| 248 |
+
conn.close()
|
| 249 |
+
return {"message": "Tarefa cadastrada com sucesso"}
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
@app.get("/api/monitoramento-processual/consulta/tjs", tags=["monitoramento-processual"], responses=responses)
|
| 253 |
+
async def get_consulta_tj(
|
| 254 |
+
uf: str = Query(..., description="Unidade Federativa"),
|
| 255 |
+
process_number: str = Query(..., description="Número do processo"),
|
| 256 |
+
current_user: str = Depends(get_current_user)
|
| 257 |
+
):
|
| 258 |
+
result = await tj_researcher.research_process(uf=uf, process_number=process_number)
|
| 259 |
+
return result
|
| 260 |
+
|
| 261 |
+
@app.get("/api/monitoramento-processual/monitoramento/uf", tags=["monitoramento-processual"], responses=responses)
|
| 262 |
+
async def get_consulta_tjsc(uf: str = uf, current_user: str = Depends(get_current_user)):
|
| 263 |
+
return await process_dispatcher(current_user, uf)
|
| 264 |
+
|
| 265 |
+
app.mount("/static", StaticFiles(directory="static", html=True), name="static")
|
| 266 |
+
|
| 267 |
+
class LoginData(BaseModel):
|
| 268 |
+
username: str
|
| 269 |
+
senha: str
|
| 270 |
+
|
| 271 |
+
@app.post("/api/monitoramento-processual/login", tags=["monitoramento-processual"])
|
| 272 |
+
def login(data: LoginData):
|
| 273 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 274 |
+
cursor = conn.cursor()
|
| 275 |
+
cursor.execute("SELECT senha_hash FROM usuarios WHERE username = ?", (data.username,))
|
| 276 |
+
row = cursor.fetchone()
|
| 277 |
+
conn.close()
|
| 278 |
+
if not row or not verificar_senha(data.senha, row[0]):
|
| 279 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Usuário ou senha inválidos")
|
| 280 |
+
token = criar_token({"sub": data.username})
|
| 281 |
+
return {"access_token": token, "token_type": "bearer"}
|
| 282 |
+
|
| 283 |
+
@app.post("/api/monitoramento-processual/cadastrar-usuario", tags=["monitoramento-processual"])
|
| 284 |
+
def cadastrar_usuario(data: models.UserCreate):
|
| 285 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 286 |
+
cursor = conn.cursor()
|
| 287 |
+
senha_hash = gerar_hash(data.senha)
|
| 288 |
+
try:
|
| 289 |
+
cursor.execute(
|
| 290 |
+
"INSERT INTO usuarios (username, senha_hash, nome, sobrenome, email) VALUES (?, ?, ?, ?, ?)",
|
| 291 |
+
(data.username, senha_hash, data.nome, data.sobrenome, data.email)
|
| 292 |
+
)
|
| 293 |
+
conn.commit()
|
| 294 |
+
return {"message": "Usuário cadastrado com sucesso."}
|
| 295 |
+
except sqlite3.IntegrityError:
|
| 296 |
+
raise HTTPException(
|
| 297 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 298 |
+
detail="Usuário já existe."
|
| 299 |
+
)
|
| 300 |
+
finally:
|
| 301 |
+
conn.close()
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
@app.put("/api/monitoramento-processual/config/usuario/username", tags=["monitoramento-processual"])
|
| 305 |
+
async def atualizar_username(data: UserUpdateUsername, current_user: str = Depends(get_current_user)):
|
| 306 |
+
return await trocar_username(current_user, data.new_username)
|
| 307 |
+
|
| 308 |
+
@app.put("/api/monitoramento-processual/config/usuario/password", tags=["monitoramento-processual"])
|
| 309 |
+
async def atualizar_senha(data: UserUpdatePassword, current_user: str = Depends(get_current_user)):
|
| 310 |
+
return await trocar_senha(current_user, data.current_password, data.new_password)
|
src/consulta.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
from os import environ as env
|
| 3 |
+
from datetime import datetime, timedelta
|
| 4 |
+
import aiohttp
|
| 5 |
+
from bs4 import BeautifulSoup
|
| 6 |
+
from fastapi.logger import logger
|
| 7 |
+
from fastapi.responses import JSONResponse
|
| 8 |
+
from fastapi import status
|
| 9 |
+
|
| 10 |
+
# Local imports
|
| 11 |
+
from src.models import *
|
| 12 |
+
from src.processamento_tjdf import tjdf_request
|
| 13 |
+
from src.enviador_de_email import enviar_email
|
| 14 |
+
from utils.util import get_headers
|
| 15 |
+
|
| 16 |
+
# Captura variáveis de ambiente e cria constantes
|
| 17 |
+
TIMEOUT = env.get('TIMEOUT', default=180)
|
| 18 |
+
|
| 19 |
+
def get_advogados_by_credencial(credencial: str, uf: str) -> list:
|
| 20 |
+
"""
|
| 21 |
+
Consulta o banco de dados monitoramento.db para obter os nomes dos advogados
|
| 22 |
+
cadastrados para a credencial e UF informadas.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
list: Lista de nomes de advogados (no formato armazenado, por exemplo, com '+' para espaços, se necessário)
|
| 26 |
+
"""
|
| 27 |
+
try:
|
| 28 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 29 |
+
cursor = conn.cursor()
|
| 30 |
+
cursor.execute("SELECT nome_advogado FROM monitor_advogados WHERE credencial = ? AND uf = ?", (credencial, uf))
|
| 31 |
+
rows = cursor.fetchall()
|
| 32 |
+
conn.close()
|
| 33 |
+
return [row[0] for row in rows]
|
| 34 |
+
except Exception as e:
|
| 35 |
+
logger.error(f"Erro ao buscar advogados no banco: {str(e)}")
|
| 36 |
+
return []
|
| 37 |
+
|
| 38 |
+
async def processamento_lista_soup(soup, nome_advogado, grau):
|
| 39 |
+
""" Extrai processos do HTML do TJSP usando BeautifulSoup """
|
| 40 |
+
try:
|
| 41 |
+
processos = []
|
| 42 |
+
# Itera sobre todos os processos listados
|
| 43 |
+
for processo in soup.select("li div.row.unj-ai-c"):
|
| 44 |
+
# Número do processo
|
| 45 |
+
link_element = processo.select_one("a.linkProcesso")
|
| 46 |
+
numero_processo = link_element.text.strip() if link_element else ''
|
| 47 |
+
link_processo = f"https://esaj.tjsp.jus.br{link_element['href']}" if link_element else ''
|
| 48 |
+
|
| 49 |
+
# Nome do Advogado
|
| 50 |
+
nome_advogado_element = processo.select_one(".unj-base-alt")
|
| 51 |
+
nome_advogado = nome_advogado_element.text.strip() if nome_advogado_element else nome_advogado.replace("+", " ")
|
| 52 |
+
|
| 53 |
+
# Tipo de Processo (classe do processo)
|
| 54 |
+
tipo_processo_element = processo.select_one(".classeProcesso")
|
| 55 |
+
tipo_processo = tipo_processo_element.text.strip() if tipo_processo_element else ''
|
| 56 |
+
|
| 57 |
+
# Assunto do Processo
|
| 58 |
+
assunto_processo_element = processo.select_one(".assuntoPrincipalProcesso, .assuntoProcesso")
|
| 59 |
+
assunto_processo = assunto_processo_element.text.strip() if assunto_processo_element else ''
|
| 60 |
+
|
| 61 |
+
# Data de recebimento
|
| 62 |
+
data_recebimento_element = processo.select_one(".dataLocalDistribuicaoProcesso, .dataLocalDistribuicao")
|
| 63 |
+
data_recebimento = data_recebimento_element.text.strip() if data_recebimento_element else ''
|
| 64 |
+
|
| 65 |
+
# Foro do Processo
|
| 66 |
+
foro_element = processo.find_previous("h2", class_="unj-subtitle")
|
| 67 |
+
foro_processo = foro_element.text.strip() if foro_element else ''
|
| 68 |
+
|
| 69 |
+
processos.append({
|
| 70 |
+
"nome_advogado": nome_advogado,
|
| 71 |
+
"numero_processo": numero_processo,
|
| 72 |
+
"tipo_processo": tipo_processo,
|
| 73 |
+
"assunto_processo": assunto_processo,
|
| 74 |
+
"data_recebimento": data_recebimento,
|
| 75 |
+
"link_processo": link_processo,
|
| 76 |
+
"foro_processo": foro_processo,
|
| 77 |
+
"grau_processo": grau
|
| 78 |
+
})
|
| 79 |
+
|
| 80 |
+
return processos
|
| 81 |
+
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.error(f"Erro ao processar HTML: {str(e)}")
|
| 84 |
+
return []
|
| 85 |
+
|
| 86 |
+
#-----------------------------------------------------------------------------------------------------
|
| 87 |
+
async def fetch(parametro_dummy: str, credencial_usuario: str, uf: str):
|
| 88 |
+
"""
|
| 89 |
+
Executa uma consulta genérica (dummy) de monitoramento processual.
|
| 90 |
+
|
| 91 |
+
Ao invés de usar uma lista fixa de nomes, obtém a lista de advogados cadastrados no banco
|
| 92 |
+
para o usuário identificado pela credencial e UF informados.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
parametro_dummy (str): Parâmetro dummy que ativa a consulta.
|
| 96 |
+
credencial_usuario (str): Credencial do usuário solicitante.
|
| 97 |
+
uf (str): Unidade Federativa relacionada à consulta.
|
| 98 |
+
|
| 99 |
+
Returns:
|
| 100 |
+
dict: Resultado da consulta processual.
|
| 101 |
+
"""
|
| 102 |
+
logger.info(f"Consulta dummy iniciada para credencial: {credencial_usuario}")
|
| 103 |
+
|
| 104 |
+
# Configura os timeouts
|
| 105 |
+
timeout = aiohttp.ClientTimeout(total=TIMEOUT)
|
| 106 |
+
session = aiohttp.ClientSession(timeout=timeout)
|
| 107 |
+
|
| 108 |
+
# Configurando headers
|
| 109 |
+
session.headers.update(get_headers())
|
| 110 |
+
|
| 111 |
+
# Obtém a lista de advogados cadastrados para o usuário
|
| 112 |
+
lista_nomes = get_advogados_by_credencial(credencial_usuario, uf)
|
| 113 |
+
if not lista_nomes:
|
| 114 |
+
await session.close()
|
| 115 |
+
return JSONResponse(
|
| 116 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 117 |
+
content={"code": 404, "message": "Nenhum advogado cadastrado para essa credencial.", "datetime": datetime.now().isoformat()}
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
resultados = []
|
| 122 |
+
|
| 123 |
+
for nome_advogado in lista_nomes:
|
| 124 |
+
# URLs de consulta para TJSP (primeira e segunda instância)
|
| 125 |
+
url_primeiro_grau = f"https://esaj.tjsp.jus.br/cpopg/search.do?conversationId=&cbPesquisa=NMADVOGADO&dadosConsulta.valorConsulta={nome_advogado}&cdForo=-1"
|
| 126 |
+
url_segundo_grau = f"https://esaj.tjsp.jus.br/cposg/search.do?conversationId=&paginaConsulta=0&cbPesquisa=NMADVOGADO&dePesquisa={nome_advogado}&localPesquisa.cdLocal=-1"
|
| 127 |
+
|
| 128 |
+
logger.info(f"Consultando processos para: {nome_advogado.replace('+', ' ')}")
|
| 129 |
+
|
| 130 |
+
# Primeira Instância
|
| 131 |
+
async with session.get(url_primeiro_grau, ssl=False, allow_redirects=True) as resp1:
|
| 132 |
+
html_primeiro_grau = await resp1.read()
|
| 133 |
+
soup_primeiro_grau = BeautifulSoup(html_primeiro_grau, "html.parser")
|
| 134 |
+
if "Não existem informações disponíveis para os parâmetros informados." not in soup_primeiro_grau.text:
|
| 135 |
+
processos_primeiro_grau = await processamento_lista_soup(soup_primeiro_grau, nome_advogado.replace('+', ' '), "1º Grau")
|
| 136 |
+
resultados.extend(processos_primeiro_grau)
|
| 137 |
+
|
| 138 |
+
# Segunda Instância
|
| 139 |
+
async with session.get(url_segundo_grau, ssl=False, allow_redirects=True) as resp2:
|
| 140 |
+
html_segundo_grau = await resp2.read()
|
| 141 |
+
soup_segundo_grau = BeautifulSoup(html_segundo_grau, "html.parser")
|
| 142 |
+
if "Não existem informações disponíveis para os parâmetros informados." not in soup_segundo_grau.text:
|
| 143 |
+
processos_segundo_grau = await processamento_lista_soup(soup_segundo_grau, nome_advogado.replace('+', ' '), "2º Grau")
|
| 144 |
+
resultados.extend(processos_segundo_grau)
|
| 145 |
+
|
| 146 |
+
# Requisição ao TJDF
|
| 147 |
+
processos_tjdf = await tjdf_request(nome_advogado)
|
| 148 |
+
resultados.extend(processos_tjdf)
|
| 149 |
+
|
| 150 |
+
processos_48h = []
|
| 151 |
+
now = datetime.now()
|
| 152 |
+
|
| 153 |
+
for proc in resultados:
|
| 154 |
+
try:
|
| 155 |
+
data_str = proc['data_recebimento'].split(" - ")[0].strip()
|
| 156 |
+
dt = datetime.strptime(data_str, "%d/%m/%Y")
|
| 157 |
+
|
| 158 |
+
# Filtra processos dos últimos 2 dias
|
| 159 |
+
if dt >= now - timedelta(days=2):
|
| 160 |
+
proc['data_recebimento_dt'] = dt
|
| 161 |
+
processos_48h.append(proc)
|
| 162 |
+
except Exception as e:
|
| 163 |
+
logger.error(f"Erro ao converter data: {str(e)}")
|
| 164 |
+
continue
|
| 165 |
+
|
| 166 |
+
processos_48h.sort(key=lambda x: x['data_recebimento_dt'], reverse=True)
|
| 167 |
+
|
| 168 |
+
for proc in processos_48h:
|
| 169 |
+
proc.pop('data_recebimento_dt', None)
|
| 170 |
+
|
| 171 |
+
# Envia o e-mail mesmo se nenhum processo for encontrado
|
| 172 |
+
try:
|
| 173 |
+
sucesso = await enviar_email(processos_48h)
|
| 174 |
+
if sucesso:
|
| 175 |
+
logger.info("E-mail enviado com sucesso!")
|
| 176 |
+
else:
|
| 177 |
+
logger.error("Falha ao enviar o e-mail.")
|
| 178 |
+
except Exception as e:
|
| 179 |
+
logger.exception(f"Erro inesperado ao enviar e-mail: {str(e)}")
|
| 180 |
+
|
| 181 |
+
result = {
|
| 182 |
+
'code': 200,
|
| 183 |
+
'message': 'OK',
|
| 184 |
+
'datetime': datetime.now().isoformat(),
|
| 185 |
+
'results': processos_48h
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
except aiohttp.ClientError as e:
|
| 189 |
+
logger.exception('Erro durante a consulta API')
|
| 190 |
+
return JSONResponse(
|
| 191 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 192 |
+
content={'code': 500, 'message': f'INTERNAL_SERVER_ERROR: {str(e)}'}
|
| 193 |
+
)
|
| 194 |
+
except Exception as e:
|
| 195 |
+
logger.exception('Erro inesperado durante a consulta API')
|
| 196 |
+
return JSONResponse(
|
| 197 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 198 |
+
content={'code': 500, 'message': f'INTERNAL_SERVER_ERROR: {str(e)}'}
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
logger.info(f"Consulta finalizada: {result}")
|
| 202 |
+
await session.close()
|
| 203 |
+
return result
|
src/enviador_de_email.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import smtplib
|
| 2 |
+
import email.message
|
| 3 |
+
import asyncio
|
| 4 |
+
from fastapi.logger import logger
|
| 5 |
+
|
| 6 |
+
async def enviar_email_movimentacao(ultima_movimentacao_texto, numero_processo):
|
| 7 |
+
"""
|
| 8 |
+
Envia um e-mail informando o andamento de um processo específico,
|
| 9 |
+
mesmo quando não houver atualização.
|
| 10 |
+
|
| 11 |
+
O parâmetro 'ultima_movimentacao_texto' pode vir em dois formatos, por exemplo:
|
| 12 |
+
1) "87 15/09/2023 18:46:46 Despacho https://eprocwebcon.tjsc.jus.br/consulta1g/controlador.php?acao=acessar_documento_publico&doc=...&hash=..."
|
| 13 |
+
2) "88 15/09/2023 18:46:47 Expedida/certificada a intimação eletrônicaRefer. ao Evento 87(EXEQUENTE - MUNICÍPIO DE TIMBÓ/SC)Prazo: 30 dias Status:FECHADOData inicial da contagem do prazo: 26/09/2023 00:00:00Data final: 14/11/2023 23:59:59"
|
| 14 |
+
|
| 15 |
+
Se o texto estiver vazio ou conter apenas espaços, o e-mail informará que não houve atualização.
|
| 16 |
+
|
| 17 |
+
Caso haja conteúdo, a função realiza o parsing dos três primeiros tokens (código, data e hora)
|
| 18 |
+
e utiliza o restante como descrição. Se houver uma substring iniciada por "http", ela será extraída como link do documento.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
ultima_movimentacao_texto (str): Texto com os detalhes do último andamento.
|
| 22 |
+
numero_processo (str): Número do processo.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
bool: True se o e-mail for enviado com sucesso, False caso contrário.
|
| 26 |
+
"""
|
| 27 |
+
if not ultima_movimentacao_texto or not ultima_movimentacao_texto.strip():
|
| 28 |
+
corpo_email = f"""
|
| 29 |
+
<html>
|
| 30 |
+
<body>
|
| 31 |
+
<h2>Andamento do Processo</h2>
|
| 32 |
+
<p><strong>Número do Processo:</strong> {numero_processo}</p>
|
| 33 |
+
<p>Não houve atualização no andamento deste processo.</p>
|
| 34 |
+
<p>Atenciosamente,<br>Equipe LexAutomation</p>
|
| 35 |
+
</body>
|
| 36 |
+
</html>
|
| 37 |
+
"""
|
| 38 |
+
else:
|
| 39 |
+
try:
|
| 40 |
+
# Tenta extrair um link, se houver
|
| 41 |
+
pos_link = ultima_movimentacao_texto.find("http")
|
| 42 |
+
if pos_link != -1:
|
| 43 |
+
link = ultima_movimentacao_texto[pos_link:].strip()
|
| 44 |
+
texto_sem_link = ultima_movimentacao_texto[:pos_link].strip()
|
| 45 |
+
partes = texto_sem_link.split(maxsplit=3)
|
| 46 |
+
else:
|
| 47 |
+
link = ""
|
| 48 |
+
partes = ultima_movimentacao_texto.split(maxsplit=3)
|
| 49 |
+
|
| 50 |
+
if len(partes) < 4:
|
| 51 |
+
codigo_evento = partes[0] if len(partes) > 0 else "N/A"
|
| 52 |
+
data_evento = partes[1] if len(partes) > 1 else "N/A"
|
| 53 |
+
hora_evento = partes[2] if len(partes) > 2 else "N/A"
|
| 54 |
+
descricao_evento = ""
|
| 55 |
+
else:
|
| 56 |
+
codigo_evento, data_evento, hora_evento, descricao_evento = partes
|
| 57 |
+
except Exception as e:
|
| 58 |
+
logger.error(f"Erro ao realizar o parsing da movimentação: {e}")
|
| 59 |
+
codigo_evento = "N/A"
|
| 60 |
+
data_evento = "N/A"
|
| 61 |
+
hora_evento = "N/A"
|
| 62 |
+
descricao_evento = ultima_movimentacao_texto
|
| 63 |
+
link = ""
|
| 64 |
+
|
| 65 |
+
documento = f'<a href="{link}">Acessar Documento</a>' if link else "Nenhum documento"
|
| 66 |
+
corpo_email = f"""
|
| 67 |
+
<html>
|
| 68 |
+
<body>
|
| 69 |
+
<h2>Atualização de Andamento do Processo</h2>
|
| 70 |
+
<p><strong>Número do Processo:</strong> {numero_processo}</p>
|
| 71 |
+
<p><strong>Código do Evento:</strong> {codigo_evento}</p>
|
| 72 |
+
<p><strong>Data e Hora:</strong> {data_evento} {hora_evento}</p>
|
| 73 |
+
<p><strong>Descrição:</strong> {descricao_evento}</p>
|
| 74 |
+
<p><strong>Documento:</strong> {documento}</p>
|
| 75 |
+
<p>Atenciosamente,<br>Equipe LexAutomation</p>
|
| 76 |
+
</body>
|
| 77 |
+
</html>
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
# Configuração do e-mail
|
| 81 |
+
remetente = "processosatualizacao@gmail.com"
|
| 82 |
+
destinatarios = [
|
| 83 |
+
"brun.rreis@gmail.com",
|
| 84 |
+
"fontaosthefano@gmail.com"
|
| 85 |
+
]
|
| 86 |
+
senha = "awbz sfki ndtd afwb"
|
| 87 |
+
|
| 88 |
+
def enviar():
|
| 89 |
+
try:
|
| 90 |
+
with smtplib.SMTP("smtp.gmail.com", 587) as server:
|
| 91 |
+
server.starttls()
|
| 92 |
+
server.login(remetente, senha)
|
| 93 |
+
for destinatario in destinatarios:
|
| 94 |
+
msg = email.message.Message()
|
| 95 |
+
msg['Subject'] = f"Atualização de Andamento - Processo {numero_processo}"
|
| 96 |
+
msg['From'] = remetente
|
| 97 |
+
msg['To'] = destinatario
|
| 98 |
+
msg.add_header('Content-Type', 'text/html')
|
| 99 |
+
msg.set_payload(corpo_email)
|
| 100 |
+
|
| 101 |
+
server.sendmail(remetente, destinatario, msg.as_string().encode("utf-8"))
|
| 102 |
+
logger.info("E-mail enviado com sucesso!")
|
| 103 |
+
return True
|
| 104 |
+
except Exception as e:
|
| 105 |
+
logger.info(f"Erro ao enviar e-mail: {str(e)}")
|
| 106 |
+
return False
|
| 107 |
+
|
| 108 |
+
return await asyncio.to_thread(enviar)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
async def enviar_email(resultados):
|
| 112 |
+
""" Envia um e-mail com a lista de processos atualizada """
|
| 113 |
+
|
| 114 |
+
# Configuração do e-mail
|
| 115 |
+
remetente = "processosatualizacao@gmail.com"
|
| 116 |
+
destinatarios = [
|
| 117 |
+
"brun.rreis@gmail.com",
|
| 118 |
+
"fontaosthefano@gmail.com"
|
| 119 |
+
]
|
| 120 |
+
senha = "awbz sfki ndtd afwb"
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
if not resultados:
|
| 124 |
+
corpo_email = """
|
| 125 |
+
<html>
|
| 126 |
+
<body>
|
| 127 |
+
<h3>🤖 Monitoramento de Processos</h3>
|
| 128 |
+
<p>Olá,</p>
|
| 129 |
+
<p>Informamos que, no período de tempo selecionado, não foram identificados novos processos.</p>
|
| 130 |
+
<p>Atenciosamente,<br>
|
| 131 |
+
Equipe LexAutomation</p>
|
| 132 |
+
</body>
|
| 133 |
+
</html>
|
| 134 |
+
"""
|
| 135 |
+
else:
|
| 136 |
+
corpo_email = """
|
| 137 |
+
<html>
|
| 138 |
+
<body>
|
| 139 |
+
<h2>🤖 Monitoramento de Processos</h2>
|
| 140 |
+
<h3>Atualização diária de processos</h3>
|
| 141 |
+
<ul>
|
| 142 |
+
"""
|
| 143 |
+
for processo in resultados:
|
| 144 |
+
corpo_email += f"""
|
| 145 |
+
<li>
|
| 146 |
+
<b>Nome do Advogado:</b> {processo['nome_advogado']}<br>
|
| 147 |
+
<b>Número do Processo:</b> {processo['numero_processo']}<br>
|
| 148 |
+
<b>Tipo do Processo:</b> {processo['tipo_processo']}<br>
|
| 149 |
+
<b>Assunto:</b> {processo['assunto_processo']}<br>
|
| 150 |
+
<b>Data de Recebimento:</b> {processo.get('data_recebimento', 'N/A')}<br>
|
| 151 |
+
<b>Foro:</b> {processo['foro_processo']}<br>
|
| 152 |
+
<b>Grau:</b> {processo['grau_processo']}<br>
|
| 153 |
+
<b>Link:</b> <a href="{processo['link_processo']}">Acessar Processo</a><br>
|
| 154 |
+
</li>
|
| 155 |
+
<hr>
|
| 156 |
+
"""
|
| 157 |
+
corpo_email += """
|
| 158 |
+
</ul>
|
| 159 |
+
<p>Atenciosamente,<br>
|
| 160 |
+
Equipe LexAutomation</p>
|
| 161 |
+
</body>
|
| 162 |
+
</html>
|
| 163 |
+
"""
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def enviar():
|
| 167 |
+
try:
|
| 168 |
+
with smtplib.SMTP("smtp.gmail.com", 587) as server:
|
| 169 |
+
server.starttls()
|
| 170 |
+
server.login(remetente, senha)
|
| 171 |
+
for destinatario in destinatarios:
|
| 172 |
+
msg = email.message.Message()
|
| 173 |
+
msg['Subject'] = "Atualização diária de processos"
|
| 174 |
+
msg['From'] = remetente
|
| 175 |
+
msg['To'] = destinatario
|
| 176 |
+
msg.add_header('Content-Type', 'text/html')
|
| 177 |
+
msg.set_payload(corpo_email)
|
| 178 |
+
|
| 179 |
+
server.sendmail(remetente, destinatario, msg.as_string().encode("utf-8"))
|
| 180 |
+
logger.info("E-mail enviado com sucesso!")
|
| 181 |
+
return True
|
| 182 |
+
except Exception as e:
|
| 183 |
+
logger.info(f"Erro ao enviar e-mail: {str(e)}")
|
| 184 |
+
return False
|
| 185 |
+
|
| 186 |
+
return await asyncio.to_thread(enviar)
|
src/models.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, EmailStr
|
| 2 |
+
from typing import Optional, List, Union
|
| 3 |
+
|
| 4 |
+
# -------------------- TJPR --------------------
|
| 5 |
+
class MovimentacaoTJPR(BaseModel):
|
| 6 |
+
seq: Optional[str] = ""
|
| 7 |
+
data: Optional[str] = ""
|
| 8 |
+
evento: Optional[str] = ""
|
| 9 |
+
movimentado_por: Optional[str] = ""
|
| 10 |
+
|
| 11 |
+
class ResponseSiteTJPR(BaseModel):
|
| 12 |
+
movimentacoes: List[MovimentacaoTJPR] = []
|
| 13 |
+
|
| 14 |
+
# -------------------- TJSP --------------------
|
| 15 |
+
class MovimentacaoTJSP(BaseModel):
|
| 16 |
+
data_hora: str = ""
|
| 17 |
+
descricao: str = ""
|
| 18 |
+
documentos: str = ""
|
| 19 |
+
|
| 20 |
+
class ResponseSiteTJSP(BaseModel):
|
| 21 |
+
movimentacoes: List[MovimentacaoTJSP] = []
|
| 22 |
+
|
| 23 |
+
# -------------------- TJSC --------------------
|
| 24 |
+
class MovimentacaoTJSC(BaseModel):
|
| 25 |
+
evento: str = ""
|
| 26 |
+
data_hora: str = ""
|
| 27 |
+
descricao: str = ""
|
| 28 |
+
documentos: str = ""
|
| 29 |
+
|
| 30 |
+
class ResponseSiteTJSC(BaseModel):
|
| 31 |
+
movimentacoes: List[MovimentacaoTJSC] = []
|
| 32 |
+
|
| 33 |
+
# -------------------- Union Simplificada --------------------
|
| 34 |
+
Movimentacao = Union[MovimentacaoTJPR, MovimentacaoTJSP, MovimentacaoTJSC]
|
| 35 |
+
|
| 36 |
+
# -------------------- Genéricos --------------------
|
| 37 |
+
class UserUpdateUsername(BaseModel):
|
| 38 |
+
new_username: str
|
| 39 |
+
|
| 40 |
+
class UserUpdatePassword(BaseModel):
|
| 41 |
+
current_password: str
|
| 42 |
+
new_password: str
|
| 43 |
+
|
| 44 |
+
class Telemetria(BaseModel):
|
| 45 |
+
tentativas: int
|
| 46 |
+
tempo_total: float = 0.0
|
| 47 |
+
bytes_enviados: int = 0
|
| 48 |
+
captchas_resolvidos: int = 0
|
| 49 |
+
|
| 50 |
+
class ResponseError(BaseModel):
|
| 51 |
+
code: int
|
| 52 |
+
message: str
|
| 53 |
+
|
| 54 |
+
class ResponseDefault(BaseModel):
|
| 55 |
+
code: int
|
| 56 |
+
message: str
|
| 57 |
+
datetime: str
|
| 58 |
+
|
| 59 |
+
class UserCreate(BaseModel):
|
| 60 |
+
username: str
|
| 61 |
+
senha: str
|
| 62 |
+
nome: str
|
| 63 |
+
sobrenome: str
|
| 64 |
+
email: EmailStr
|
src/monitoramento_processual/monitoramento_tjpr.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import re
|
| 3 |
+
import logging
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from os import environ as env
|
| 6 |
+
|
| 7 |
+
import httpx
|
| 8 |
+
from fastapi import status
|
| 9 |
+
from fastapi.responses import JSONResponse
|
| 10 |
+
from bs4 import BeautifulSoup
|
| 11 |
+
from gradio_client import Client, handle_file
|
| 12 |
+
import ddddocr
|
| 13 |
+
|
| 14 |
+
# Imports locais
|
| 15 |
+
from src.models import ResponseSiteTJPR, Telemetria, MovimentacaoTJPR
|
| 16 |
+
import src.models as models
|
| 17 |
+
|
| 18 |
+
# Configuração do Logger
|
| 19 |
+
logging.basicConfig(
|
| 20 |
+
level=logging.INFO,
|
| 21 |
+
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
|
| 22 |
+
datefmt="%Y-%m-%d %H:%M:%S"
|
| 23 |
+
)
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
# Constantes
|
| 27 |
+
TEMPO_LIMITE = int(env.get('TEMPO_LIMITE', 180))
|
| 28 |
+
TENTATIVAS_MAXIMAS_CAPTCHA = int(env.get('TENTATIVAS_MAXIMAS_CAPTCHA', 30))
|
| 29 |
+
TENTATIVAS_MAXIMAS_RECURSIVAS = int(env.get('TENTATIVAS_MAXIMAS_RECURSIVAS', 30))
|
| 30 |
+
BASE_URL = "https://consulta.tjpr.jus.br"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
async def extrair_movimentacoes(soup: BeautifulSoup) -> ResponseSiteTJPR:
|
| 34 |
+
movimentacoes = []
|
| 35 |
+
tabelas = soup.find_all("table", {"class": "resultTable"})
|
| 36 |
+
for tabela in tabelas:
|
| 37 |
+
linhas = tabela.find_all("tr")[1:]
|
| 38 |
+
for linha in linhas:
|
| 39 |
+
colunas = linha.find_all("td")
|
| 40 |
+
if len(colunas) >= 5:
|
| 41 |
+
seq = colunas[1].get_text(strip=True)
|
| 42 |
+
data = colunas[2].get_text(strip=True)
|
| 43 |
+
evento = " ".join(colunas[3].stripped_strings)
|
| 44 |
+
|
| 45 |
+
nome = colunas[4].contents[2].strip() if len(colunas[4].contents) > 2 else ""
|
| 46 |
+
cargo_tag = colunas[4].find("b")
|
| 47 |
+
cargo = cargo_tag.get_text(strip=True) if cargo_tag else ""
|
| 48 |
+
|
| 49 |
+
if not nome:
|
| 50 |
+
nome_tag = colunas[4].find(text=True, recursive=False)
|
| 51 |
+
nome = nome_tag.strip() if nome_tag else ""
|
| 52 |
+
|
| 53 |
+
movimentado_por = f"{nome} - {cargo}" if nome and cargo else (nome or cargo or colunas[4].get_text(strip=True))
|
| 54 |
+
movimentacoes.append(MovimentacaoTJPR(seq=seq, data=data, evento=evento, movimentado_por=movimentado_por))
|
| 55 |
+
|
| 56 |
+
return ResponseSiteTJPR(movimentacoes=movimentacoes)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def formatar_numero_processo(numero_processo: str) -> str:
|
| 61 |
+
"""
|
| 62 |
+
Remove quaisquer caracteres não numéricos do número do processo.
|
| 63 |
+
"""
|
| 64 |
+
return ''.join(filter(str.isdigit, numero_processo))
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
async def obter_soup(client: httpx.AsyncClient, url: str) -> BeautifulSoup:
|
| 68 |
+
"""Realiza GET na URL e retorna o BeautifulSoup do conteúdo."""
|
| 69 |
+
resposta = await client.get(url)
|
| 70 |
+
if resposta.status_code != 200:
|
| 71 |
+
raise Exception(f"Falha ao acessar {url}: {resposta.status_code}")
|
| 72 |
+
return BeautifulSoup(resposta.text, "html.parser")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
async def resolver_captcha(client: httpx.AsyncClient, url_captcha: str, telemetria: Telemetria) -> str:
|
| 76 |
+
"""Faz download e resolve o CAPTCHA utilizando API e fallback OCR."""
|
| 77 |
+
resposta_captcha = await client.get(url_captcha)
|
| 78 |
+
if resposta_captcha.status_code != 200:
|
| 79 |
+
raise Exception(f"Falha ao baixar o CAPTCHA: {resposta_captcha.status_code}")
|
| 80 |
+
bytes_imagem = resposta_captcha.content
|
| 81 |
+
with open("captcha_temporario.png", "wb") as arquivo:
|
| 82 |
+
arquivo.write(bytes_imagem)
|
| 83 |
+
|
| 84 |
+
cliente_api = Client("Nischay103/captcha_recognition")
|
| 85 |
+
telemetria.captchas_resolvidos += 1
|
| 86 |
+
try:
|
| 87 |
+
resultado_ocr = cliente_api.predict(
|
| 88 |
+
input=handle_file("captcha_temporario.png"),
|
| 89 |
+
api_name="/predict"
|
| 90 |
+
).strip()
|
| 91 |
+
logger.info(f"CAPTCHA reconhecido pela API: {resultado_ocr}")
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"Erro ao usar a API captcha_recognition: {e}")
|
| 94 |
+
try:
|
| 95 |
+
motor_ocr = ddddocr.DdddOcr()
|
| 96 |
+
resultado_ocr = motor_ocr.classification(bytes_imagem)
|
| 97 |
+
logger.info(f"CAPTCHA reconhecido pelo ddddocr (fallback): {resultado_ocr}")
|
| 98 |
+
except Exception as fallback_e:
|
| 99 |
+
logger.error(f"Erro no fallback ddddocr: {fallback_e}")
|
| 100 |
+
raise
|
| 101 |
+
return resultado_ocr
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def extrair_url_token(script_tags: list, pattern_str: str) -> str:
|
| 105 |
+
"""Extrai uma URL/token utilizando regex a partir dos scripts."""
|
| 106 |
+
pattern = re.compile(pattern_str)
|
| 107 |
+
for script in script_tags:
|
| 108 |
+
if script.string:
|
| 109 |
+
match = pattern.search(script.string)
|
| 110 |
+
if match:
|
| 111 |
+
token = match.group(1)
|
| 112 |
+
if "_tj=" in token:
|
| 113 |
+
return token
|
| 114 |
+
return None
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
async def fetch(numero_processo: str, telemetria: Telemetria) -> dict:
|
| 118 |
+
"""
|
| 119 |
+
Função principal que realiza a consulta. Mantém a estrutura original de tentativas
|
| 120 |
+
e de recursão para resolver o CAPTCHA, e retorna um dicionário com os resultados e telemetria.
|
| 121 |
+
"""
|
| 122 |
+
inicio_tempo = time.time()
|
| 123 |
+
if not numero_processo or not isinstance(numero_processo, str):
|
| 124 |
+
telemetria.tempo_total = round(time.time() - inicio_tempo, 2)
|
| 125 |
+
return JSONResponse(
|
| 126 |
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
| 127 |
+
content={'code': 2, 'message': 'ERRO_ENTIDADE_NAO_PROCESSAVEL'}
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
if telemetria.tentativas >= TENTATIVAS_MAXIMAS_RECURSIVAS:
|
| 131 |
+
logger.error("Número máximo de tentativas recursivas atingido.")
|
| 132 |
+
return JSONResponse(
|
| 133 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 134 |
+
content={'code': 3, 'message': 'ERRO_SERVIDOR_INTERNO'}
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
logger.info(f'Função fetch() iniciou. Processo: {numero_processo} - Tentativa {telemetria.tentativas}')
|
| 138 |
+
headers = {
|
| 139 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
|
| 140 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
| 141 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
| 142 |
+
'Referer': f'{BASE_URL}/projudi_consulta/'
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
results = {}
|
| 146 |
+
try:
|
| 147 |
+
async with httpx.AsyncClient(timeout=TEMPO_LIMITE, headers=headers, follow_redirects=True) as client:
|
| 148 |
+
# 1º GET: Página inicial
|
| 149 |
+
url_pagina_consulta = f"{BASE_URL}/projudi_consulta/"
|
| 150 |
+
resposta_inicial = await client.get(url_pagina_consulta)
|
| 151 |
+
telemetria.bytes_enviados += len(resposta_inicial.text.encode('utf-8'))
|
| 152 |
+
if resposta_inicial.status_code != 200:
|
| 153 |
+
raise Exception(f"Falha ao acessar a página inicial: {resposta_inicial.status_code}")
|
| 154 |
+
|
| 155 |
+
soup_inicial = BeautifulSoup(resposta_inicial.text, "html.parser")
|
| 156 |
+
frame = soup_inicial.find("frame", {"id": "mainFrame"})
|
| 157 |
+
if not (frame and frame.get("src")):
|
| 158 |
+
raise Exception("Frame com id='mainFrame' não encontrado ou sem atributo 'src'")
|
| 159 |
+
src = re.sub(r';jsessionid=[^?]*', '', frame["src"])
|
| 160 |
+
url_final = f"{BASE_URL}{src}"
|
| 161 |
+
logger.info(f"URL do mainFrame: {url_final}")
|
| 162 |
+
|
| 163 |
+
# 2º GET: cabecalho.jsp
|
| 164 |
+
url_cabecalho = f"{BASE_URL}/projudi_consulta/cabecalho.jsp"
|
| 165 |
+
resposta_cabecalho = await client.get(url_cabecalho)
|
| 166 |
+
telemetria.bytes_enviados += len(resposta_cabecalho.text.encode('utf-8'))
|
| 167 |
+
if resposta_cabecalho.status_code != 200:
|
| 168 |
+
raise Exception(f"Falha ao acessar cabecalho.jsp: {resposta_cabecalho.status_code}")
|
| 169 |
+
|
| 170 |
+
client.headers.update({'Referer': url_final})
|
| 171 |
+
|
| 172 |
+
# 3º GET: Página de consulta pública (mainFrame)
|
| 173 |
+
resposta_consulta = await client.get(url_final)
|
| 174 |
+
telemetria.bytes_enviados += len(resposta_consulta.text.encode('utf-8'))
|
| 175 |
+
if resposta_consulta.status_code != 200:
|
| 176 |
+
raise Exception(f"Falha ao acessar a página de consulta: {resposta_consulta.status_code}")
|
| 177 |
+
|
| 178 |
+
soup_consulta = BeautifulSoup(resposta_consulta.text, "html.parser")
|
| 179 |
+
# Captura da imagem do CAPTCHA
|
| 180 |
+
captcha_img_elem = soup_consulta.find("img", {"id": "captchaImage"})
|
| 181 |
+
if not (captcha_img_elem and captcha_img_elem.get("src")):
|
| 182 |
+
raise Exception("Imagem do CAPTCHA não encontrada ou sem atributo 'src'!")
|
| 183 |
+
url_captcha = f"{BASE_URL}{captcha_img_elem['src']}"
|
| 184 |
+
logger.info(f"URL do CAPTCHA: {url_captcha}")
|
| 185 |
+
|
| 186 |
+
# Captura URL AJAX para Autocomplete e demais chamadas
|
| 187 |
+
script_tags = soup_consulta.find_all("script", {"type": "text/javascript"})
|
| 188 |
+
url_ajax = extrair_url_token(script_tags, r'AjaxJspTag\.Select\(\s*"([^"]+)"')
|
| 189 |
+
if not url_ajax:
|
| 190 |
+
raise Exception("URL com '_tj=' não encontrada no HTML")
|
| 191 |
+
if "codComarca" not in url_ajax:
|
| 192 |
+
url_ajax += ("&" if "?" in url_ajax else "?") + "codComarca=-1"
|
| 193 |
+
url_final_ajax = url_ajax if url_ajax.startswith("http") else f"{BASE_URL}{url_ajax}"
|
| 194 |
+
resp_ajax = await client.get(url_final_ajax)
|
| 195 |
+
telemetria.bytes_enviados += len(resp_ajax.text.encode('utf-8'))
|
| 196 |
+
if resp_ajax.status_code != 200:
|
| 197 |
+
raise Exception(f"Falha ao acessar a URL AJAX: {resp_ajax.status_code}")
|
| 198 |
+
|
| 199 |
+
url_autocomplete = extrair_url_token(script_tags, r'AjaxJspTag\.Autocomplete\(\s*"([^"]+)"')
|
| 200 |
+
if not url_autocomplete:
|
| 201 |
+
raise Exception("URL do Autocomplete com '_tj=' não encontrada no HTML")
|
| 202 |
+
segunda_url_ajax = url_autocomplete if url_autocomplete.startswith("http") else f"{BASE_URL}{url_autocomplete}"
|
| 203 |
+
|
| 204 |
+
payload_segundo_ajax = {
|
| 205 |
+
"numeroProcesso": numero_processo,
|
| 206 |
+
"flagNumeroUnico": "true",
|
| 207 |
+
"opcaoConsulta": "1",
|
| 208 |
+
"_": ""
|
| 209 |
+
}
|
| 210 |
+
ajax_headers = {
|
| 211 |
+
"x-prototype-version": "1.5.1.1",
|
| 212 |
+
"x-requested-with": "XMLHttpRequest"
|
| 213 |
+
}
|
| 214 |
+
resposta_ajax = await client.post(segunda_url_ajax, data=payload_segundo_ajax, headers=ajax_headers)
|
| 215 |
+
telemetria.bytes_enviados += len(resposta_ajax.text.encode('utf-8'))
|
| 216 |
+
if resposta_ajax.status_code != 200:
|
| 217 |
+
raise Exception(f"Falha ao acessar a URL AJAX: {resposta_ajax.status_code}")
|
| 218 |
+
|
| 219 |
+
# Resolver CAPTCHA
|
| 220 |
+
resultado_ocr = await resolver_captcha(client, url_captcha, telemetria)
|
| 221 |
+
|
| 222 |
+
# Extrair URL de submissão do formulário a partir dos scripts
|
| 223 |
+
script_tags_full = soup_consulta.find_all("script")
|
| 224 |
+
token_url = None
|
| 225 |
+
for script in script_tags_full:
|
| 226 |
+
if script.string:
|
| 227 |
+
m = re.search(r'document\.getElementById\(["\']buscaProcessoForm["\']\)\.action\s*=\s*["\']([^"\']+)["\']', script.string)
|
| 228 |
+
if m:
|
| 229 |
+
relative_url = m.group(1)
|
| 230 |
+
token_url = relative_url if relative_url.startswith("http") else f"{BASE_URL}{relative_url}"
|
| 231 |
+
break
|
| 232 |
+
if not token_url:
|
| 233 |
+
raise Exception("URL de submissão do formulário não encontrada.")
|
| 234 |
+
|
| 235 |
+
# Submissão final do formulário
|
| 236 |
+
final_headers = {
|
| 237 |
+
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
| 238 |
+
"Origin": BASE_URL,
|
| 239 |
+
"Referer": url_final,
|
| 240 |
+
"x-prototype-version": "1.5.1.1",
|
| 241 |
+
"x-requested-with": "XMLHttpRequest"
|
| 242 |
+
}
|
| 243 |
+
payload_form = {
|
| 244 |
+
"processoPageSize": "20",
|
| 245 |
+
"processoPageNumber": "1",
|
| 246 |
+
"processoSortColumn": "p.numeroUnico",
|
| 247 |
+
"processoSortOrder": "asc",
|
| 248 |
+
"codVaraEscolhida": "",
|
| 249 |
+
"opcaoConsultaPublica": "1",
|
| 250 |
+
"flagNumeroUnico": "true",
|
| 251 |
+
"numeroProcesso": numero_processo,
|
| 252 |
+
"codComarca": "-1",
|
| 253 |
+
"tipoCompetencia": "",
|
| 254 |
+
"turma": "",
|
| 255 |
+
"nomeParte": "",
|
| 256 |
+
"cpfCnpj": "",
|
| 257 |
+
"loginAdvogado": "",
|
| 258 |
+
"nomeAdvogado": "",
|
| 259 |
+
"oab": "",
|
| 260 |
+
"oabComplemento": "N",
|
| 261 |
+
"oabUF": "PR",
|
| 262 |
+
"answer": resultado_ocr
|
| 263 |
+
}
|
| 264 |
+
resposta_formulario = await client.post(token_url, data=payload_form, headers=final_headers)
|
| 265 |
+
telemetria.bytes_enviados += len(resposta_formulario.text.encode('utf-8'))
|
| 266 |
+
if resposta_formulario.status_code != 200:
|
| 267 |
+
raise Exception(f"Falha ao submeter o formulário: {resposta_formulario.status_code}")
|
| 268 |
+
|
| 269 |
+
# Chamada AJAX final para obter os resultados
|
| 270 |
+
soup_final = BeautifulSoup(resposta_formulario.text, "html.parser")
|
| 271 |
+
script_tags_final = soup_final.find_all("script", {"type": "text/javascript"})
|
| 272 |
+
token_ajax = extrair_url_token(script_tags_final, r'AjaxJspTag\.HtmlContent\(\s*"([^"]+)"')
|
| 273 |
+
if not token_ajax:
|
| 274 |
+
raise Exception("Token/URL da chamada AjaxJspTag.HtmlContent não encontrado.")
|
| 275 |
+
final_url_ajax = token_ajax if token_ajax.startswith("http") else f"{BASE_URL}{token_ajax}"
|
| 276 |
+
payload_ultimo = {"dummy": "true", "_": ""}
|
| 277 |
+
ultimo_post = await client.post(final_url_ajax, data=payload_ultimo, headers=final_headers)
|
| 278 |
+
telemetria.bytes_enviados += len(ultimo_post.text.encode('utf-8'))
|
| 279 |
+
if ultimo_post.status_code != 200:
|
| 280 |
+
raise Exception(f"Falha ao acessar a URL final: {ultimo_post.status_code}")
|
| 281 |
+
|
| 282 |
+
soup_1 = BeautifulSoup(ultimo_post.text, "html.parser")
|
| 283 |
+
# Verifica se há link para mais movimentações
|
| 284 |
+
if "Clique para visualizar as movimentações mais antigas" in soup_1.text:
|
| 285 |
+
script_tags_extra = soup_1.find_all("script", {"type": "text/javascript"})
|
| 286 |
+
url_desejada = extrair_url_token(script_tags_extra, r'AjaxJspTag\.HtmlContent\(\s*"([^"]+)"')
|
| 287 |
+
if url_desejada:
|
| 288 |
+
full_url = f"{BASE_URL}{url_desejada}"
|
| 289 |
+
resp_final = await client.post(full_url, data=payload_ultimo, headers=final_headers)
|
| 290 |
+
telemetria.bytes_enviados += len(resp_final.text.encode('utf-8'))
|
| 291 |
+
if resp_final.status_code != 200:
|
| 292 |
+
raise Exception(f"Falha ao acessar a URL final: {resp_final.status_code}")
|
| 293 |
+
soup_2 = BeautifulSoup(resp_final.text, "html.parser")
|
| 294 |
+
html_combinado = str(soup_1) + str(soup_2)
|
| 295 |
+
soup_combinado = BeautifulSoup(html_combinado, "html.parser")
|
| 296 |
+
else:
|
| 297 |
+
soup_combinado = soup_1
|
| 298 |
+
else:
|
| 299 |
+
soup_combinado = soup_1
|
| 300 |
+
|
| 301 |
+
movimentacoes = await extrair_movimentacoes(soup_combinado)
|
| 302 |
+
results = {
|
| 303 |
+
"code": 200,
|
| 304 |
+
"message": "SUCESSO",
|
| 305 |
+
"datetime": datetime.now().isoformat(),
|
| 306 |
+
"results": movimentacoes.model_dump(),
|
| 307 |
+
"telemetria": telemetria.model_dump()
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
except Exception as e:
|
| 311 |
+
logger.error(f"Erro durante a consulta: {e}")
|
| 312 |
+
if telemetria.tentativas < TENTATIVAS_MAXIMAS_CAPTCHA:
|
| 313 |
+
logger.info("Tentando resolver o CAPTCHA novamente...")
|
| 314 |
+
telemetria.tentativas += 1
|
| 315 |
+
return await fetch(numero_processo, telemetria)
|
| 316 |
+
else:
|
| 317 |
+
results = {
|
| 318 |
+
'code': 4,
|
| 319 |
+
'message': 'ERRO_SERVIDOR_INTERNO',
|
| 320 |
+
'telemetria': telemetria
|
| 321 |
+
}
|
| 322 |
+
finally:
|
| 323 |
+
telemetria.tempo_total = round(time.time() - inicio_tempo, 2)
|
| 324 |
+
if isinstance(results, dict):
|
| 325 |
+
if "telemetria" not in results:
|
| 326 |
+
results["telemetria"] = telemetria.dict()
|
| 327 |
+
return results
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
async def research_process(numero_processo: str):
|
| 331 |
+
"""
|
| 332 |
+
Interface externa para pesquisa de processo no TJPR.
|
| 333 |
+
"""
|
| 334 |
+
str_time = time.time()
|
| 335 |
+
telem = models.Telemetria(tentativas=1, tempo_total=str_time)
|
| 336 |
+
return await fetch(numero_processo, telemetria=telem)
|
src/monitoramento_processual/monitoramento_tjsc.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from os import environ as env
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
import time
|
| 4 |
+
import os
|
| 5 |
+
import certifi
|
| 6 |
+
from urllib.parse import urljoin
|
| 7 |
+
|
| 8 |
+
from fastapi.logger import logger
|
| 9 |
+
from fastapi.responses import JSONResponse
|
| 10 |
+
import base64
|
| 11 |
+
from fastapi import status
|
| 12 |
+
from bs4 import BeautifulSoup
|
| 13 |
+
import httpx
|
| 14 |
+
from capmonstercloudclient import CapMonsterClient, ClientOptions
|
| 15 |
+
from capmonstercloudclient.requests import TurnstileProxylessRequest
|
| 16 |
+
|
| 17 |
+
# Local imports
|
| 18 |
+
from src.models import ResponseSiteTJSC, Telemetria, MovimentacaoTJSC
|
| 19 |
+
import src.models as models
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# Captura variáveis de ambiente e cria constantes
|
| 23 |
+
TEMPO_LIMITE = int(env.get('TEMPO_LIMITE', 180))
|
| 24 |
+
TENTATIVAS_MAXIMAS_CAPTCHA = int(env.get('TENTATIVAS_MAXIMAS_CAPTCHA', 5))
|
| 25 |
+
TENTATIVAS_MAXIMAS_RECURSIVAS = int(env.get('TENTATIVAS_MAXIMAS_RECURSIVAS', 5))
|
| 26 |
+
CAPMONSTER_API_KEY = env.get('CAPMONSTER_API_KEY', "51391018af62079a8f9fc3233de7617b")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Função para formatar o número do processo
|
| 30 |
+
def formatar_numero_processo(numero_processo):
|
| 31 |
+
return ''.join(filter(str.isdigit, numero_processo))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Função para capturar todas as movimentações da página HTML
|
| 35 |
+
async def capturar_todas_movimentacoes(pagina_html: str) -> list[MovimentacaoTJSC]:
|
| 36 |
+
soup = BeautifulSoup(pagina_html, "html.parser")
|
| 37 |
+
tabelas = soup.find_all('table', class_='infraTable')
|
| 38 |
+
|
| 39 |
+
tabela_eventos = None
|
| 40 |
+
for tabela in tabelas:
|
| 41 |
+
cabecalhos = [th.get_text(strip=True) for th in tabela.find_all('th')]
|
| 42 |
+
if 'Evento' in cabecalhos and 'Data/Hora' in cabecalhos and 'Descrição' in cabecalhos and 'Documentos' in cabecalhos:
|
| 43 |
+
tabela_eventos = tabela
|
| 44 |
+
break
|
| 45 |
+
|
| 46 |
+
if not tabela_eventos:
|
| 47 |
+
logger.warning("Tabela de eventos não encontrada na página HTML.")
|
| 48 |
+
return []
|
| 49 |
+
|
| 50 |
+
movimentacoes = []
|
| 51 |
+
linhas = tabela_eventos.find_all('tr', class_=['infraTrClara', 'infraTrEscura'])
|
| 52 |
+
|
| 53 |
+
if not linhas:
|
| 54 |
+
logger.warning("Nenhuma linha de eventos encontrada na tabela.")
|
| 55 |
+
return []
|
| 56 |
+
|
| 57 |
+
for linha in linhas:
|
| 58 |
+
celulas = linha.find_all('td')
|
| 59 |
+
if len(celulas) >= 4:
|
| 60 |
+
evento = celulas[0].get_text(strip=True)
|
| 61 |
+
data_hora = celulas[1].get_text(strip=True)
|
| 62 |
+
descricao = celulas[2].get_text(strip=True)
|
| 63 |
+
|
| 64 |
+
link_documento_completo = ""
|
| 65 |
+
link_documento_elemento = celulas[3].find('a', class_='infraLinkDocumento')
|
| 66 |
+
if link_documento_elemento and 'href' in link_documento_elemento.attrs:
|
| 67 |
+
base_url = "https://eprocwebcon.tjsc.jus.br/consulta1g/"
|
| 68 |
+
link_documento = link_documento_elemento['href']
|
| 69 |
+
link_documento_completo = urljoin(base_url, link_documento)
|
| 70 |
+
|
| 71 |
+
movimentacao = MovimentacaoTJSC(
|
| 72 |
+
evento=evento,
|
| 73 |
+
data_hora=data_hora,
|
| 74 |
+
descricao=descricao,
|
| 75 |
+
documentos=link_documento_completo
|
| 76 |
+
)
|
| 77 |
+
movimentacoes.append(movimentacao)
|
| 78 |
+
logger.debug(f"Movimentação capturada: {movimentacao.model_dump()}")
|
| 79 |
+
|
| 80 |
+
return movimentacoes
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# Resolve o CAPTCHA Turnstile usando o CapMonster
|
| 84 |
+
async def resolver_captcha_turnstile(website_url: str, site_key: str, page_data: str) -> dict:
|
| 85 |
+
os.environ['SSL_CERT_FILE'] = certifi.where()
|
| 86 |
+
|
| 87 |
+
client_options = ClientOptions(api_key=CAPMONSTER_API_KEY)
|
| 88 |
+
cap_monster_client = CapMonsterClient(options=client_options)
|
| 89 |
+
|
| 90 |
+
turnstile_request = TurnstileProxylessRequest(
|
| 91 |
+
websiteURL=website_url,
|
| 92 |
+
websiteKey=site_key,
|
| 93 |
+
pageData=page_data
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
solution = await cap_monster_client.solve_captcha(turnstile_request)
|
| 97 |
+
return solution
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# Função principal para acessar o site do TJSC e capturar as movimentações com httpx
|
| 101 |
+
async def fetch(numero_processo: str, telemetria: Telemetria) -> dict:
|
| 102 |
+
"""
|
| 103 |
+
Função que acessa a página inicial do TJSC com httpx, resolve o CAPTCHA Turnstile com retry,
|
| 104 |
+
envia o formulário via POST e captura as movimentações do processo. Retorna os
|
| 105 |
+
resultados como objetos Movimentacao, incluindo links de documentos e a duração
|
| 106 |
+
da requisição.
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
# Validação inicial
|
| 110 |
+
if not numero_processo or not isinstance(numero_processo, str):
|
| 111 |
+
return JSONResponse(
|
| 112 |
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
| 113 |
+
content={
|
| 114 |
+
'code': 2,
|
| 115 |
+
'message': 'ERRO_ENTIDADE_NAO_PROCESSAVEL'
|
| 116 |
+
}
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
if telemetria.tentativas >= TENTATIVAS_MAXIMAS_RECURSIVAS:
|
| 120 |
+
logger.error("Número máximo de tentativas recursivas atingido.")
|
| 121 |
+
return JSONResponse(
|
| 122 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 123 |
+
content={
|
| 124 |
+
'code': 3,
|
| 125 |
+
'message': 'ERRO_SERVIDOR_INTERNO'
|
| 126 |
+
}
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
logger.info(f'Função fetch() iniciou. Processo: {numero_processo} - Tentativa {telemetria.tentativas}')
|
| 130 |
+
|
| 131 |
+
headers = {
|
| 132 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
| 133 |
+
"Host": "eprocwebcon.tjsc.jus.br",
|
| 134 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0"
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
# Criar cliente HTTP para manter cookies e sessão
|
| 138 |
+
client = httpx.Client(timeout=TEMPO_LIMITE, verify=False, headers=headers)
|
| 139 |
+
results = None
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
# Acessar a página inicial
|
| 143 |
+
url_inicial = "https://eprocwebcon.tjsc.jus.br/consulta1g/externo_controlador.php?acao=processo_consulta_publica"
|
| 144 |
+
response = client.get(url_inicial, follow_redirects=False)
|
| 145 |
+
content_length = response.headers.get('Content-Length')
|
| 146 |
+
if content_length:
|
| 147 |
+
telemetria.bytes_enviados += int(content_length)
|
| 148 |
+
else:
|
| 149 |
+
telemetria.bytes_enviados += len(response.content)
|
| 150 |
+
|
| 151 |
+
if response.status_code != 200:
|
| 152 |
+
raise Exception(f"Falha ao acessar a página inicial: {response.status_code}")
|
| 153 |
+
|
| 154 |
+
page_data = base64.b64encode(response.text.encode('utf-8')).decode('utf-8')
|
| 155 |
+
|
| 156 |
+
soup = BeautifulSoup(response.text, "html.parser")
|
| 157 |
+
|
| 158 |
+
# Verificar se o CAPTCHA Turnstile está presente
|
| 159 |
+
turnstile_div = soup.find("div", class_="cf-turnstile")
|
| 160 |
+
if not turnstile_div:
|
| 161 |
+
raise Exception("Elemento do CAPTCHA Turnstile não encontrado na página inicial.")
|
| 162 |
+
|
| 163 |
+
site_key = turnstile_div.get("data-sitekey")
|
| 164 |
+
if not site_key:
|
| 165 |
+
raise Exception("Chave do site (data-sitekey) não encontrada no elemento Turnstile.")
|
| 166 |
+
|
| 167 |
+
# Resolver o CAPTCHA Turnstile
|
| 168 |
+
telemetria.captchas_resolvidos += 1
|
| 169 |
+
solution = await resolver_captcha_turnstile(url_inicial, site_key, page_data)
|
| 170 |
+
|
| 171 |
+
token = solution.get("token")
|
| 172 |
+
|
| 173 |
+
form_data = {
|
| 174 |
+
"hdnInfraTipPagina": "1",
|
| 175 |
+
"sbmNovo": "Consultar",
|
| 176 |
+
"txtNumProcesso": numero_processo,
|
| 177 |
+
"txtNumChave": "",
|
| 178 |
+
"txtNumChaveDocumentos": "",
|
| 179 |
+
"txtParte": "",
|
| 180 |
+
"chkFonetica": "N",
|
| 181 |
+
"chkFoneticaS": "",
|
| 182 |
+
"txtStrOAB": "",
|
| 183 |
+
"rdTipo": "CPF",
|
| 184 |
+
"cf-turnstile-response": token,
|
| 185 |
+
"hdnInfraCaptcha": "0"
|
| 186 |
+
"hdnInfraSelecoes" "Infra"
|
| 187 |
+
}
|
| 188 |
+
url_post = "https://eprocwebcon.tjsc.jus.br/consulta1g/externo_controlador.php?acao=processo_consulta_publica"
|
| 189 |
+
|
| 190 |
+
response_post = client.post(url_post, data=form_data, follow_redirects=False)
|
| 191 |
+
content_length = response_post.headers.get('Content-Length')
|
| 192 |
+
if content_length:
|
| 193 |
+
telemetria.bytes_enviados += int(content_length)
|
| 194 |
+
else:
|
| 195 |
+
telemetria.bytes_enviados += len(response_post.content)
|
| 196 |
+
|
| 197 |
+
pagina_html = response_post.text
|
| 198 |
+
if "Processo não encontrado" in pagina_html:
|
| 199 |
+
results = {
|
| 200 |
+
'code': 200,
|
| 201 |
+
'message': 'Processo não encontrado',
|
| 202 |
+
'datetime': datetime.now().strftime('%d-%m-%Y %H:%M:%S'),
|
| 203 |
+
'telemetria': telemetria
|
| 204 |
+
}
|
| 205 |
+
return results
|
| 206 |
+
else:
|
| 207 |
+
# Verificar redirecionamento
|
| 208 |
+
if response_post.status_code == 302:
|
| 209 |
+
redirect_url = response_post.headers["Location"]
|
| 210 |
+
logger.info(f"URL de redirecionamento: {redirect_url}")
|
| 211 |
+
else:
|
| 212 |
+
raise Exception(f"Requisição POST não resultou em redirecionamento: {response_post.status_code}")
|
| 213 |
+
|
| 214 |
+
# Acessar a página de detalhes
|
| 215 |
+
redirect_url = urljoin(url_inicial, redirect_url)
|
| 216 |
+
response_get = client.get(redirect_url, follow_redirects=True)
|
| 217 |
+
content_length = response_get.headers.get('Content-Length')
|
| 218 |
+
if content_length:
|
| 219 |
+
telemetria.bytes_enviados += int(content_length)
|
| 220 |
+
else:
|
| 221 |
+
telemetria.bytes_enviados += len(response_get.content)
|
| 222 |
+
|
| 223 |
+
if response_get.status_code != 200:
|
| 224 |
+
raise Exception(f"Falha ao acessar a página de detalhes: {response_get.status_code}")
|
| 225 |
+
|
| 226 |
+
# Capturar as movimentações
|
| 227 |
+
pagina_html = response_get.text
|
| 228 |
+
if "Processo não encontrado" in pagina_html:
|
| 229 |
+
results = {
|
| 230 |
+
'code': 200,
|
| 231 |
+
'message': 'Processo não encontrado',
|
| 232 |
+
'datetime': datetime.now().strftime('%d-%m-%Y %H:%M:%S'),
|
| 233 |
+
'telemetria': telemetria
|
| 234 |
+
}
|
| 235 |
+
return results
|
| 236 |
+
else:
|
| 237 |
+
# Verificar redirecionamento
|
| 238 |
+
if response_post.status_code == 302:
|
| 239 |
+
redirect_url = response_post.headers["Location"]
|
| 240 |
+
logger.info(f"URL de redirecionamento: {redirect_url}")
|
| 241 |
+
else:
|
| 242 |
+
raise Exception(f"Requisição POST não resultou em redirecionamento: {response_post.status_code}")
|
| 243 |
+
|
| 244 |
+
# Acessar a página de detalhes
|
| 245 |
+
redirect_url = urljoin(url_inicial, redirect_url)
|
| 246 |
+
response_get = client.get(redirect_url, follow_redirects=True)
|
| 247 |
+
content_length = response_get.headers.get('Content-Length')
|
| 248 |
+
if content_length:
|
| 249 |
+
telemetria.bytes_enviados += int(content_length)
|
| 250 |
+
else:
|
| 251 |
+
telemetria.bytes_enviados += len(response_get.content)
|
| 252 |
+
|
| 253 |
+
if response_get.status_code != 200:
|
| 254 |
+
raise Exception(f"Falha ao acessar a página de detalhes: {response_get.status_code}")
|
| 255 |
+
|
| 256 |
+
# Capturar as movimentações
|
| 257 |
+
pagina_html = response_get.text
|
| 258 |
+
soup_novo = BeautifulSoup(pagina_html, "html.parser")
|
| 259 |
+
link = soup_novo.find(
|
| 260 |
+
'a',
|
| 261 |
+
href=lambda href: href and "externo_controlador.php?acao=processo_seleciona_publica" in href,
|
| 262 |
+
text=lambda t: t and "listar todos os eventos" in t.lower()
|
| 263 |
+
)
|
| 264 |
+
if link:
|
| 265 |
+
|
| 266 |
+
link_href = link.get("href")
|
| 267 |
+
url_base = "https://eprocwebcon.tjsc.jus.br/consulta1g/"
|
| 268 |
+
url_eventos = urljoin(url_base, link_href)
|
| 269 |
+
response_eventos = client.get(url_eventos, follow_redirects=True, timeout=TEMPO_LIMITE)
|
| 270 |
+
content_length = response_eventos.headers.get('Content-Length')
|
| 271 |
+
if content_length:
|
| 272 |
+
telemetria.bytes_enviados += int(content_length)
|
| 273 |
+
else:
|
| 274 |
+
telemetria.bytes_enviados += len(response_eventos.content)
|
| 275 |
+
|
| 276 |
+
if response_eventos.status_code != 200:
|
| 277 |
+
raise Exception(f"Falha ao acessar a página de eventos: {response_eventos.status_code}")
|
| 278 |
+
|
| 279 |
+
pagina_html = response_eventos.text
|
| 280 |
+
|
| 281 |
+
todas_movimentacoes = await capturar_todas_movimentacoes(pagina_html)
|
| 282 |
+
|
| 283 |
+
else:
|
| 284 |
+
logger.warning("Link para eventos não encontrado na página de detalhes.")
|
| 285 |
+
todas_movimentacoes = await capturar_todas_movimentacoes(pagina_html)
|
| 286 |
+
|
| 287 |
+
# Processar os resultados
|
| 288 |
+
if not todas_movimentacoes or todas_movimentacoes == ["Nenhuma movimentação encontrada na tabela"]:
|
| 289 |
+
logger.error("Nenhuma movimentação encontrada para o processo.")
|
| 290 |
+
results = {
|
| 291 |
+
'code': 200,
|
| 292 |
+
'message': 'Nenhuma movimentação encontrada para o processo.',
|
| 293 |
+
'datetime': datetime.now().strftime('%d-%m-%Y %H:%M:%S'),
|
| 294 |
+
}
|
| 295 |
+
else:
|
| 296 |
+
|
| 297 |
+
logger.info("Processo consultado com sucesso.")
|
| 298 |
+
results = {
|
| 299 |
+
"code": 200,
|
| 300 |
+
"message": "SUCESSO",
|
| 301 |
+
"datetime": datetime.now().isoformat(),
|
| 302 |
+
"results": ResponseSiteTJSC(movimentacoes=todas_movimentacoes).model_dump(),
|
| 303 |
+
"telemetria": telemetria.model_dump()
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
except httpx.RequestError as e:
|
| 307 |
+
logger.error(f"Erro de requisição: {e}")
|
| 308 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 309 |
+
results = JSONResponse(
|
| 310 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 311 |
+
content={
|
| 312 |
+
'code': 4,
|
| 313 |
+
'message': 'ERRO_SERVIDOR_INTERNO',
|
| 314 |
+
'telemetria': telemetria
|
| 315 |
+
}
|
| 316 |
+
)
|
| 317 |
+
except Exception as e:
|
| 318 |
+
logger.error(f"Erro durante a consulta: {e}")
|
| 319 |
+
if telemetria.tentativas < TENTATIVAS_MAXIMAS_CAPTCHA:
|
| 320 |
+
logger.info("Tentando resolver o CAPTCHA novamente...")
|
| 321 |
+
telemetria.tentativas += 1
|
| 322 |
+
return await fetch(numero_processo, telemetria)
|
| 323 |
+
else:
|
| 324 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 325 |
+
results = JSONResponse(
|
| 326 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 327 |
+
content={
|
| 328 |
+
'code': 4,
|
| 329 |
+
'message': 'ERRO_SERVIDOR_INTERNO',
|
| 330 |
+
'telemetria': telemetria
|
| 331 |
+
}
|
| 332 |
+
)
|
| 333 |
+
finally:
|
| 334 |
+
client.close()
|
| 335 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 336 |
+
if results is not None and isinstance(results, dict) and "telemetria" not in results:
|
| 337 |
+
results["telemetria"] = telemetria
|
| 338 |
+
|
| 339 |
+
return results
|
| 340 |
+
|
| 341 |
+
async def research_process(numero_processo: str):
|
| 342 |
+
"""
|
| 343 |
+
Interface externa para pesquisa de processo no TJPR.
|
| 344 |
+
"""
|
| 345 |
+
str_time = time.time()
|
| 346 |
+
telem = models.Telemetria(tentativas=1, tempo_total=str_time)
|
| 347 |
+
return await fetch(numero_processo, telemetria=telem)
|
src/monitoramento_processual/monitoramento_tjsp.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from os import environ as env
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
import time
|
| 4 |
+
from urllib.parse import urlparse, urlunparse, urljoin
|
| 5 |
+
|
| 6 |
+
from fastapi.logger import logger
|
| 7 |
+
from fastapi.responses import JSONResponse
|
| 8 |
+
from fastapi import status
|
| 9 |
+
from bs4 import BeautifulSoup
|
| 10 |
+
import httpx
|
| 11 |
+
|
| 12 |
+
# Local imports
|
| 13 |
+
from src.models import ResponseSiteTJSP, Telemetria, MovimentacaoTJSP
|
| 14 |
+
import src.models as models
|
| 15 |
+
|
| 16 |
+
# Captura variáveis de ambiente e cria constantes
|
| 17 |
+
TEMPO_LIMITE = int(env.get('TEMPO_LIMITE', 180))
|
| 18 |
+
TENTATIVAS_MAXIMAS_RECURSIVAS = int(env.get('TENTATIVAS_MAXIMAS_RECURSIVAS', 30))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
async def capturar_todas_movimentacoes(pagina_html: str) -> list[MovimentacaoTJSP]:
|
| 22 |
+
"""
|
| 23 |
+
Extrai todas as movimentações (visíveis e ocultas) do HTML da página.
|
| 24 |
+
Para cada movimentação, extrai a data, a descrição e o link do documento.
|
| 25 |
+
"""
|
| 26 |
+
soup = BeautifulSoup(pagina_html, "html.parser")
|
| 27 |
+
movimentacoes = []
|
| 28 |
+
tbody_ids = ["tabelaUltimasMovimentacoes", "tabelaTodasMovimentacoes"]
|
| 29 |
+
|
| 30 |
+
for tbody_id in tbody_ids:
|
| 31 |
+
tbody = soup.find("tbody", id=tbody_id)
|
| 32 |
+
if not tbody:
|
| 33 |
+
continue
|
| 34 |
+
|
| 35 |
+
for tr in tbody.find_all("tr", class_="containerMovimentacao"):
|
| 36 |
+
td_data = tr.find("td", class_="dataMovimentacao")
|
| 37 |
+
data_text = td_data.get_text(strip=True) if td_data else ""
|
| 38 |
+
td_desc = tr.find("td", class_="descricaoMovimentacao")
|
| 39 |
+
descricao = td_desc.get_text(separator=" ", strip=True) if td_desc else ""
|
| 40 |
+
link_tag = tr.find("a", class_="linkMovVincProc")
|
| 41 |
+
documentos = link_tag.get("href", "") if link_tag else ""
|
| 42 |
+
|
| 43 |
+
movimentacoes.append(
|
| 44 |
+
MovimentacaoTJSP(
|
| 45 |
+
data_hora=data_text,
|
| 46 |
+
descricao=descricao,
|
| 47 |
+
documentos=documentos
|
| 48 |
+
)
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
return ResponseSiteTJSP(movimentacoes=movimentacoes)
|
| 52 |
+
|
| 53 |
+
async def fetch(numero_processo: str, telemetria: Telemetria) -> dict:
|
| 54 |
+
"""
|
| 55 |
+
Acessa a página inicial do TJSC, resolve o CAPTCHA com retry, envia o formulário via GET e
|
| 56 |
+
captura as movimentações do processo. Retorna os resultados como objetos Movimentacao,
|
| 57 |
+
incluindo os links completos dos documentos (com ticket).
|
| 58 |
+
"""
|
| 59 |
+
if not numero_processo or not isinstance(numero_processo, str):
|
| 60 |
+
return JSONResponse(
|
| 61 |
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
| 62 |
+
content={'code': 2, 'message': 'ERRO_ENTIDADE_NAO_PROCESSAVEL'}
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
if telemetria.tentativas >= TENTATIVAS_MAXIMAS_RECURSIVAS:
|
| 66 |
+
logger.error("Número máximo de tentativas recursivas atingido.")
|
| 67 |
+
return JSONResponse(
|
| 68 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 69 |
+
content={'code': 3, 'message': 'ERRO_SERVIDOR_INTERNO'}
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
logger.info(f'Função fetch() iniciou. Processo: {numero_processo} - Tentativa {telemetria.tentativas}')
|
| 73 |
+
|
| 74 |
+
headers = {
|
| 75 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
| 76 |
+
"Host": "esaj.tjsp.jus.br",
|
| 77 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0"
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
client = httpx.Client(timeout=TEMPO_LIMITE, verify=False, headers=headers)
|
| 81 |
+
results = None
|
| 82 |
+
base_url = "https://esaj.tjsp.jus.br"
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
get_inicial = (
|
| 86 |
+
"https://esaj.tjsp.jus.br/cpopg/search.do?conversationId=&cbPesquisa=NUMPROC"
|
| 87 |
+
"&numeroDigitoAnoUnificado=&foroNumeroUnificado="
|
| 88 |
+
"&dadosConsulta.valorConsultaNuUnificado=&dadosConsulta.valorConsultaNuUnificado=UNIFICADO"
|
| 89 |
+
f"&dadosConsulta.valorConsulta={numero_processo}"
|
| 90 |
+
"&dadosConsulta.tipoNuProcesso=SAJ"
|
| 91 |
+
)
|
| 92 |
+
response = client.get(get_inicial, follow_redirects=False)
|
| 93 |
+
content_length = response.headers.get('Content-Length')
|
| 94 |
+
if content_length:
|
| 95 |
+
telemetria.bytes_enviados += int(content_length)
|
| 96 |
+
else:
|
| 97 |
+
telemetria.bytes_enviados += len(response.content)
|
| 98 |
+
|
| 99 |
+
if response.status_code == 302:
|
| 100 |
+
redirect_url = response.headers.get("Location")
|
| 101 |
+
logger.info(f"Redirecionamento encontrado para: {redirect_url}")
|
| 102 |
+
absolute_url = urljoin(base_url, redirect_url)
|
| 103 |
+
parsed = urlparse(absolute_url)
|
| 104 |
+
clean_path = parsed.path.split(";")[0]
|
| 105 |
+
clean_url = urlunparse((parsed.scheme, parsed.netloc, clean_path, parsed.params, parsed.query, parsed.fragment))
|
| 106 |
+
response = client.get(clean_url, follow_redirects=True)
|
| 107 |
+
content_length = response.headers.get('Content-Length')
|
| 108 |
+
if content_length:
|
| 109 |
+
telemetria.bytes_enviados += int(content_length)
|
| 110 |
+
else:
|
| 111 |
+
telemetria.bytes_enviados += len(response.content)
|
| 112 |
+
|
| 113 |
+
capturar_movimentacoes = await capturar_todas_movimentacoes(response.text)
|
| 114 |
+
results = {
|
| 115 |
+
"code": 200,
|
| 116 |
+
"message": "SUCESSO",
|
| 117 |
+
"datetime": datetime.now().isoformat(),
|
| 118 |
+
"results": capturar_movimentacoes.model_dump(),
|
| 119 |
+
"telemetria": telemetria.model_dump()
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
except httpx.RequestError as e:
|
| 123 |
+
logger.error(f"Erro de requisição: {e}")
|
| 124 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 125 |
+
results = JSONResponse(
|
| 126 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 127 |
+
content={'code': 4, 'message': 'ERRO_SERVIDOR_INTERNO', 'telemetria': telemetria.dict()}
|
| 128 |
+
)
|
| 129 |
+
except Exception as e:
|
| 130 |
+
logger.error(f"Erro durante a consulta: {e}")
|
| 131 |
+
if telemetria.tentativas < TENTATIVAS_MAXIMAS_RECURSIVAS:
|
| 132 |
+
logger.info("Tentando novamente...")
|
| 133 |
+
telemetria.tentativas += 1
|
| 134 |
+
return await fetch(numero_processo, telemetria)
|
| 135 |
+
else:
|
| 136 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 137 |
+
results = JSONResponse(
|
| 138 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 139 |
+
content={'code': 4, 'message': 'ERRO_SERVIDOR_INTERNO', 'telemetria': telemetria.dict()}
|
| 140 |
+
)
|
| 141 |
+
finally:
|
| 142 |
+
client.close()
|
| 143 |
+
telemetria.tempo_total = round(time.time() - telemetria.tempo_total, 2)
|
| 144 |
+
if results is not None and isinstance(results, dict) and "telemetria" not in results:
|
| 145 |
+
results["telemetria"] = telemetria.dict()
|
| 146 |
+
|
| 147 |
+
return results
|
| 148 |
+
|
| 149 |
+
async def research_process(numero_processo: str):
|
| 150 |
+
"""
|
| 151 |
+
Interface externa para pesquisa de processo no TJPR.
|
| 152 |
+
"""
|
| 153 |
+
str_time = time.time()
|
| 154 |
+
telem = models.Telemetria(tentativas=1, tempo_total=str_time)
|
| 155 |
+
return await fetch(numero_processo, telemetria=telem)
|
src/monitoramento_processual/task_dispatcher.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.monitoramento_processual import monitoramento_tjpr, monitoramento_tjsc, monitoramento_tjsp
|
| 2 |
+
|
| 3 |
+
async def process_dispatcher(uf: str, current_user: str):
|
| 4 |
+
if uf == "PR":
|
| 5 |
+
resultado = await monitoramento_tjpr.fetch_all(current_user, uf)
|
| 6 |
+
elif uf == "SC":
|
| 7 |
+
resultado = await monitoramento_tjsc.fetch_all(current_user, uf)
|
| 8 |
+
elif uf == "SP":
|
| 9 |
+
resultado = await monitoramento_tjsp.fetch_all(current_user, uf)
|
| 10 |
+
else:
|
| 11 |
+
resultado = {"error": "UF não suportada para monitoramento."}
|
| 12 |
+
|
| 13 |
+
return resultado
|
src/processamento_tjdf.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import datetime
|
| 3 |
+
import asyncio
|
| 4 |
+
import aiohttp
|
| 5 |
+
from bs4 import BeautifulSoup
|
| 6 |
+
|
| 7 |
+
HEADERS = {
|
| 8 |
+
'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
|
| 9 |
+
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
| 10 |
+
'Chrome/85.0.4183.83 Safari/537.36')
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
async def fetch(session: aiohttp.ClientSession, url: str):
|
| 14 |
+
"""
|
| 15 |
+
Realiza uma requisição GET e retorna o texto da resposta e a URL final.
|
| 16 |
+
"""
|
| 17 |
+
async with session.get(url, headers=HEADERS, ssl=False) as response:
|
| 18 |
+
text = await response.text()
|
| 19 |
+
return text, str(response.url)
|
| 20 |
+
|
| 21 |
+
async def parse_detail(session: aiohttp.ClientSession, url_proc: str, numero: str, vara: str) -> dict:
|
| 22 |
+
"""
|
| 23 |
+
Processa a página de detalhe do processo e extrai os dados necessários.
|
| 24 |
+
"""
|
| 25 |
+
html, _ = await fetch(session, url_proc)
|
| 26 |
+
proc_soup = BeautifulSoup(html, 'html.parser')
|
| 27 |
+
|
| 28 |
+
# Número do processo
|
| 29 |
+
numero_processo_tag = proc_soup.find("span", id="i_numeroProcesso14")
|
| 30 |
+
numero_processo = numero_processo_tag.get_text(strip=True) if numero_processo_tag else numero
|
| 31 |
+
|
| 32 |
+
# Tipo de processo
|
| 33 |
+
feito_codigo_tag = proc_soup.find("span", id="i_feitoCodigo")
|
| 34 |
+
feito_descricao_tag = proc_soup.find("span", id="i_feitoDescricao")
|
| 35 |
+
if feito_codigo_tag and feito_descricao_tag:
|
| 36 |
+
tipo_processo = f"{feito_codigo_tag.get_text(strip=True)} - {feito_descricao_tag.get_text(strip=True)}"
|
| 37 |
+
else:
|
| 38 |
+
tipo_processo = ""
|
| 39 |
+
|
| 40 |
+
# Nome do advogado (autor)
|
| 41 |
+
advogado_tag = proc_soup.find("span", id="i_advogadoAutor")
|
| 42 |
+
nome_advogado = advogado_tag.get_text(strip=True) if advogado_tag else ""
|
| 43 |
+
|
| 44 |
+
# Foro ou endereço da vara
|
| 45 |
+
endereco_vara_tag = proc_soup.find("span", id="i_enderecoVara")
|
| 46 |
+
foro_processo = endereco_vara_tag.get_text(strip=True) if endereco_vara_tag else vara
|
| 47 |
+
|
| 48 |
+
# Extração da data de recebimento a partir dos andamentos
|
| 49 |
+
andamentos = []
|
| 50 |
+
for data_tag in proc_soup.find_all("span", id=re.compile(r"^i_dataHoraAndamento_")):
|
| 51 |
+
num_evento = data_tag.get("id").split("_")[-1]
|
| 52 |
+
desc_tag = proc_soup.find("span", id=f"i_descricaoAndamento_{num_evento}")
|
| 53 |
+
if desc_tag:
|
| 54 |
+
desc = desc_tag.get_text(strip=True)
|
| 55 |
+
date_str = data_tag.get_text(strip=True)
|
| 56 |
+
try:
|
| 57 |
+
dt = datetime.datetime.strptime(date_str, "%d/%m/%Y - %H:%M:%S")
|
| 58 |
+
andamentos.append((dt, desc))
|
| 59 |
+
except ValueError:
|
| 60 |
+
continue
|
| 61 |
+
|
| 62 |
+
recebimento_candidates = [dt for dt, desc in andamentos
|
| 63 |
+
if "007" in desc and "Distribuidos ao cartorio" in desc]
|
| 64 |
+
if recebimento_candidates:
|
| 65 |
+
dt_recebimento = min(recebimento_candidates)
|
| 66 |
+
elif andamentos:
|
| 67 |
+
dt_recebimento = min([dt for dt, _ in andamentos])
|
| 68 |
+
else:
|
| 69 |
+
dt_recebimento = None
|
| 70 |
+
data_recebimento = dt_recebimento.strftime("%d/%m/%Y - %H:%M:%S") if dt_recebimento else ""
|
| 71 |
+
|
| 72 |
+
dados = {
|
| 73 |
+
"nome_advogado": nome_advogado,
|
| 74 |
+
"numero_processo": numero_processo,
|
| 75 |
+
"tipo_processo": tipo_processo,
|
| 76 |
+
"data_recebimento": data_recebimento,
|
| 77 |
+
"link_processo": url_proc,
|
| 78 |
+
"foro_processo": foro_processo,
|
| 79 |
+
"grau_processo": "TJDF - 1ª Instância"
|
| 80 |
+
}
|
| 81 |
+
return dados
|
| 82 |
+
|
| 83 |
+
async def tjdf_request(nome: str) -> list:
|
| 84 |
+
"""
|
| 85 |
+
Consulta os processos no TJDF usando requisições assíncronas.
|
| 86 |
+
Retorna uma lista de dicionários com os dados dos processos encontrados.
|
| 87 |
+
"""
|
| 88 |
+
dados_processos = []
|
| 89 |
+
base_url = "https://cache-internet.tjdft.jus.br/cgi-bin/tjcgi1"
|
| 90 |
+
search_params = f"?NXTPGM=tjhtml101&ORIGEM=INTER&SELECAO=9&CIRC=ZZ&CHAVE={nome}&CHAVE1=&submit=Consultar"
|
| 91 |
+
url_search = base_url + search_params
|
| 92 |
+
|
| 93 |
+
async with aiohttp.ClientSession() as session:
|
| 94 |
+
html, current_url = await fetch(session, url_search)
|
| 95 |
+
soup = BeautifulSoup(html, "html.parser")
|
| 96 |
+
|
| 97 |
+
# Se nenhum processo foi localizado, retorna lista vazia
|
| 98 |
+
if "0 processo(s) localizado(s)" in soup.text:
|
| 99 |
+
return dados_processos
|
| 100 |
+
|
| 101 |
+
# Se já é uma página de detalhe
|
| 102 |
+
if "Advogado Autor" in soup.text:
|
| 103 |
+
proc_soup = soup
|
| 104 |
+
url_proc = current_url
|
| 105 |
+
vara_tag = proc_soup.find("span", id="i_descricaoVara")
|
| 106 |
+
vara = vara_tag.get_text(strip=True) if vara_tag else ""
|
| 107 |
+
|
| 108 |
+
numero_processo_tag = proc_soup.find("span", id="i_numeroProcesso14")
|
| 109 |
+
if numero_processo_tag:
|
| 110 |
+
numero_processo = numero_processo_tag.get_text(strip=True)
|
| 111 |
+
else:
|
| 112 |
+
hidden_tag = proc_soup.find("hidden", id="detalhamentoDeProcesso")
|
| 113 |
+
if hidden_tag and hidden_tag.has_attr("value"):
|
| 114 |
+
parts = hidden_tag["value"].split(",")
|
| 115 |
+
numero_processo = parts[1].strip() if len(parts) >= 2 else nome
|
| 116 |
+
else:
|
| 117 |
+
numero_processo = nome
|
| 118 |
+
|
| 119 |
+
feito_codigo_tag = proc_soup.find("span", id="i_feitoCodigo")
|
| 120 |
+
feito_descricao_tag = proc_soup.find("span", id="i_feitoDescricao")
|
| 121 |
+
if feito_codigo_tag and feito_descricao_tag:
|
| 122 |
+
tipo_processo = f"{feito_codigo_tag.get_text(strip=True)} - {feito_descricao_tag.get_text(strip=True)}"
|
| 123 |
+
else:
|
| 124 |
+
tipo_processo = ""
|
| 125 |
+
|
| 126 |
+
advogado_tag = proc_soup.find("span", id="i_advogadoAutor")
|
| 127 |
+
nome_advogado = advogado_tag.get_text(strip=True) if advogado_tag else ""
|
| 128 |
+
|
| 129 |
+
endereco_vara_tag = proc_soup.find("span", id="i_enderecoVara")
|
| 130 |
+
foro_processo = endereco_vara_tag.get_text(strip=True) if endereco_vara_tag else vara
|
| 131 |
+
|
| 132 |
+
# Extração dos andamentos
|
| 133 |
+
andamentos = []
|
| 134 |
+
for data_tag in proc_soup.find_all("span", id=re.compile(r"^i_dataHoraAndamento_")):
|
| 135 |
+
num_evento = data_tag.get("id").split("_")[-1]
|
| 136 |
+
desc_tag = proc_soup.find("span", id=f"i_descricaoAndamento_{num_evento}")
|
| 137 |
+
if desc_tag:
|
| 138 |
+
desc = desc_tag.get_text(strip=True)
|
| 139 |
+
date_str = data_tag.get_text(strip=True)
|
| 140 |
+
try:
|
| 141 |
+
dt = datetime.datetime.strptime(date_str, "%d/%m/%Y - %H:%M:%S")
|
| 142 |
+
andamentos.append((dt, desc))
|
| 143 |
+
except ValueError:
|
| 144 |
+
continue
|
| 145 |
+
|
| 146 |
+
recebimento_candidates = [dt for dt, desc in andamentos
|
| 147 |
+
if "007" in desc and "Distribuidos ao cartorio" in desc]
|
| 148 |
+
if recebimento_candidates:
|
| 149 |
+
dt_recebimento = min(recebimento_candidates)
|
| 150 |
+
elif andamentos:
|
| 151 |
+
dt_recebimento = min([dt for dt, _ in andamentos])
|
| 152 |
+
else:
|
| 153 |
+
dt_recebimento = None
|
| 154 |
+
data_recebimento = dt_recebimento.strftime("%d/%m/%Y - %H:%M:%S") if dt_recebimento else ""
|
| 155 |
+
|
| 156 |
+
dados = {
|
| 157 |
+
"nome_advogado": nome_advogado,
|
| 158 |
+
"numero_processo": numero_processo,
|
| 159 |
+
"tipo_processo": tipo_processo,
|
| 160 |
+
"data_recebimento": data_recebimento,
|
| 161 |
+
"link_processo": url_proc,
|
| 162 |
+
"foro_processo": foro_processo,
|
| 163 |
+
"grau_processo": "TJDF - 1ª Instância"
|
| 164 |
+
}
|
| 165 |
+
dados_processos.append(dados)
|
| 166 |
+
return dados_processos
|
| 167 |
+
|
| 168 |
+
# Se for uma página com a lista de processos
|
| 169 |
+
processo_spans = soup.find_all("span", id=re.compile(r"^processo_"))
|
| 170 |
+
processos = {}
|
| 171 |
+
for span in processo_spans:
|
| 172 |
+
numero = span.get_text(strip=True)
|
| 173 |
+
if numero not in processos:
|
| 174 |
+
parent_ul = span.find_parent("ul")
|
| 175 |
+
if parent_ul:
|
| 176 |
+
vara_tag = parent_ul.find("span", id=re.compile(r"^vara_"))
|
| 177 |
+
vara = vara_tag.get_text(strip=True) if vara_tag else ""
|
| 178 |
+
else:
|
| 179 |
+
vara = ""
|
| 180 |
+
processos[numero] = vara
|
| 181 |
+
|
| 182 |
+
# Cria tarefas para buscar os detalhes de cada processo de forma assíncrona
|
| 183 |
+
tasks = []
|
| 184 |
+
for numero, vara in processos.items():
|
| 185 |
+
url_proc = f"{base_url}?NXTPGM=tjhtml105&ORIGEM=INTER&SELECAO=1&CIRCUN=1&CDNUPROC={numero}"
|
| 186 |
+
tasks.append(parse_detail(session, url_proc, numero, vara))
|
| 187 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 188 |
+
for res in results:
|
| 189 |
+
if isinstance(res, dict):
|
| 190 |
+
dados_processos.append(res)
|
| 191 |
+
|
| 192 |
+
return dados_processos
|
src/tj_research/tj_researcher.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.monitoramento_processual import monitoramento_tjpr, monitoramento_tjsc, monitoramento_tjsp
|
| 2 |
+
|
| 3 |
+
async def research_process(uf: str, process_number: str, current_user: str = None):
|
| 4 |
+
"""
|
| 5 |
+
Pesquisa um processo específico no tribunal de justiça correspondente à UF.
|
| 6 |
+
|
| 7 |
+
Args:
|
| 8 |
+
uf (str): Unidade Federativa (ex.: "PR", "SC", "SP").
|
| 9 |
+
process_number (str): Número do processo a ser pesquisado.
|
| 10 |
+
current_user (str, optional): Usuário atual, para autenticação ou logging, se necessário.
|
| 11 |
+
|
| 12 |
+
Returns:
|
| 13 |
+
dict: JSON com os dados do processo ou um erro se a UF não for suportada.
|
| 14 |
+
"""
|
| 15 |
+
if uf == "PR":
|
| 16 |
+
resultado = await monitoramento_tjpr.research_process(process_number)
|
| 17 |
+
elif uf == "SC":
|
| 18 |
+
resultado = await monitoramento_tjsc.research_process(process_number)
|
| 19 |
+
elif uf == "SP":
|
| 20 |
+
resultado = await monitoramento_tjsp.research_process(process_number)
|
| 21 |
+
else:
|
| 22 |
+
resultado = {"error": "UF não suportada para pesquisa de processos."}
|
| 23 |
+
|
| 24 |
+
return resultado
|
static/consulta.html
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-br">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Consulta de Processo - Monitoramento Judicial</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
margin: 0;
|
| 10 |
+
font-family: 'Segoe UI', sans-serif;
|
| 11 |
+
background-color: #f4f6f8;
|
| 12 |
+
color: #333;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
header {
|
| 16 |
+
background-color: #1e3a8a;
|
| 17 |
+
color: white;
|
| 18 |
+
padding: 15px 30px;
|
| 19 |
+
display: flex;
|
| 20 |
+
justify-content: space-between;
|
| 21 |
+
align-items: center;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
header h1 {
|
| 25 |
+
margin: 0;
|
| 26 |
+
font-size: 1.5rem;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
nav button {
|
| 30 |
+
background-color: white;
|
| 31 |
+
color: #1e3a8a;
|
| 32 |
+
font-size: 1rem;
|
| 33 |
+
padding: 8px 16px;
|
| 34 |
+
margin-left: 20px;
|
| 35 |
+
border: none;
|
| 36 |
+
border-radius: 4px;
|
| 37 |
+
cursor: pointer;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
nav button:hover {
|
| 41 |
+
background-color: #f0f0f0;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.container {
|
| 45 |
+
max-width: 700px;
|
| 46 |
+
margin: 50px auto;
|
| 47 |
+
padding: 20px;
|
| 48 |
+
background: white;
|
| 49 |
+
border-radius: 8px;
|
| 50 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
h2 {
|
| 54 |
+
text-align: center;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.form-group {
|
| 58 |
+
margin-bottom: 20px;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.form-group label {
|
| 62 |
+
display: block;
|
| 63 |
+
margin-bottom: 5px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.form-group input, .form-group select {
|
| 67 |
+
width: 100%;
|
| 68 |
+
padding: 10px;
|
| 69 |
+
font-size: 1rem;
|
| 70 |
+
border: 1px solid #ccc;
|
| 71 |
+
border-radius: 4px;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
button.submit {
|
| 75 |
+
background-color: #1e3a8a;
|
| 76 |
+
color: white;
|
| 77 |
+
padding: 10px 20px;
|
| 78 |
+
font-size: 1rem;
|
| 79 |
+
border: none;
|
| 80 |
+
border-radius: 4px;
|
| 81 |
+
cursor: pointer;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
button.submit:hover {
|
| 85 |
+
background-color: #163c74;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.result {
|
| 89 |
+
margin-top: 30px;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.movimentacao {
|
| 93 |
+
background-color: #eef2f7;
|
| 94 |
+
padding: 10px 15px;
|
| 95 |
+
border-radius: 6px;
|
| 96 |
+
margin-bottom: 10px;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.data {
|
| 100 |
+
font-weight: bold;
|
| 101 |
+
color: #1e3a8a;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
#verMaisBtn {
|
| 105 |
+
background-color: #d3d3d3;
|
| 106 |
+
border: none;
|
| 107 |
+
border-radius: 4px;
|
| 108 |
+
padding: 8px 16px;
|
| 109 |
+
margin: 20px auto 0;
|
| 110 |
+
display: none;
|
| 111 |
+
cursor: pointer;
|
| 112 |
+
font-size: 0.95rem;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
#carregando {
|
| 116 |
+
text-align: center;
|
| 117 |
+
margin-top: 20px;
|
| 118 |
+
font-weight: bold;
|
| 119 |
+
color: #1e3a8a;
|
| 120 |
+
}
|
| 121 |
+
</style>
|
| 122 |
+
</head>
|
| 123 |
+
<body>
|
| 124 |
+
<header>
|
| 125 |
+
<h1>Consulta de Processo</h1>
|
| 126 |
+
<nav>
|
| 127 |
+
<button onclick="window.location.href='/dashboard'">Dashboard</button>
|
| 128 |
+
<button onclick="logout()">Sair</button>
|
| 129 |
+
</nav>
|
| 130 |
+
</header>
|
| 131 |
+
|
| 132 |
+
<div class="container">
|
| 133 |
+
<h2>Consultar Processo no Tribunal</h2>
|
| 134 |
+
|
| 135 |
+
<div class="form-group">
|
| 136 |
+
<label for="uf">UF:</label>
|
| 137 |
+
<select id="uf">
|
| 138 |
+
<option value="PR">PR</option>
|
| 139 |
+
<option value="SC">SC</option>
|
| 140 |
+
<option value="SP">SP</option>
|
| 141 |
+
</select>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<div class="form-group">
|
| 145 |
+
<label for="process-number">Número do Processo:</label>
|
| 146 |
+
<input type="text" id="process-number" placeholder="0000000-00.0000.0.00.0000">
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<button class="submit" onclick="consultarProcesso()">Consultar</button>
|
| 150 |
+
|
| 151 |
+
<div id="carregando"></div>
|
| 152 |
+
|
| 153 |
+
<div id="resultado" class="result"></div>
|
| 154 |
+
<button id="verMaisBtn" onclick="alternarVerMais()">Ver mais</button>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<script>
|
| 158 |
+
const token = localStorage.getItem('token');
|
| 159 |
+
|
| 160 |
+
function logout() {
|
| 161 |
+
localStorage.clear();
|
| 162 |
+
window.location.href = '/';
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
let todasMovimentacoes = [];
|
| 166 |
+
let mostrandoTodas = false;
|
| 167 |
+
|
| 168 |
+
function renderizarMovimentacoes() {
|
| 169 |
+
const resultado = document.getElementById("resultado");
|
| 170 |
+
resultado.innerHTML = "";
|
| 171 |
+
|
| 172 |
+
const movimentacoesParaMostrar = mostrandoTodas
|
| 173 |
+
? todasMovimentacoes
|
| 174 |
+
: todasMovimentacoes.slice(0, 5);
|
| 175 |
+
|
| 176 |
+
movimentacoesParaMostrar.forEach(mov => {
|
| 177 |
+
const div = document.createElement("div");
|
| 178 |
+
div.className = "movimentacao";
|
| 179 |
+
|
| 180 |
+
const data = mov.data || mov.data_hora || "";
|
| 181 |
+
const evento = mov.evento || "";
|
| 182 |
+
const descricao = mov.descricao || evento;
|
| 183 |
+
const documentos = mov.documentos || "";
|
| 184 |
+
|
| 185 |
+
div.innerHTML = `
|
| 186 |
+
<div class="data">${data}</div>
|
| 187 |
+
<div style="white-space: pre-wrap; margin: 5px 0;">${descricao}</div>
|
| 188 |
+
${documentos ? `<a href="${documentos}" target="_blank" style="color: #1e3a8a; text-decoration: none;">📄 Ver documento</a>` : ""}
|
| 189 |
+
`;
|
| 190 |
+
resultado.appendChild(div);
|
| 191 |
+
});
|
| 192 |
+
|
| 193 |
+
const verMaisBtn = document.getElementById("verMaisBtn");
|
| 194 |
+
if (todasMovimentacoes.length > 5) {
|
| 195 |
+
verMaisBtn.style.display = "block";
|
| 196 |
+
verMaisBtn.textContent = mostrandoTodas ? "Ver menos" : "Ver mais";
|
| 197 |
+
} else {
|
| 198 |
+
verMaisBtn.style.display = "none";
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
function alternarVerMais() {
|
| 203 |
+
mostrandoTodas = !mostrandoTodas;
|
| 204 |
+
renderizarMovimentacoes();
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
async function consultarProcesso() {
|
| 208 |
+
const uf = document.getElementById('uf').value;
|
| 209 |
+
const numero = document.getElementById('process-number').value;
|
| 210 |
+
|
| 211 |
+
document.getElementById("carregando").innerText = "Consultando processo...";
|
| 212 |
+
document.getElementById("resultado").innerHTML = "";
|
| 213 |
+
document.getElementById("verMaisBtn").style.display = "none";
|
| 214 |
+
|
| 215 |
+
try {
|
| 216 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/tjs?uf=${uf}&process_number=${encodeURIComponent(numero)}`, {
|
| 217 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
const data = await res.json();
|
| 221 |
+
todasMovimentacoes = data?.results?.movimentacoes || [];
|
| 222 |
+
mostrandoTodas = false;
|
| 223 |
+
renderizarMovimentacoes();
|
| 224 |
+
} catch (e) {
|
| 225 |
+
document.getElementById("resultado").innerText = "Erro ao consultar processo.";
|
| 226 |
+
} finally {
|
| 227 |
+
document.getElementById("carregando").innerText = "";
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
</script>
|
| 231 |
+
</body>
|
| 232 |
+
</html>
|
static/dashboard.html
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-br">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>Painel - Monitoramento Judicial</title>
|
| 8 |
+
<style>
|
| 9 |
+
body {
|
| 10 |
+
margin: 0;
|
| 11 |
+
font-family: 'Segoe UI', sans-serif;
|
| 12 |
+
background-color: #f4f6f8;
|
| 13 |
+
color: #333;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
header {
|
| 17 |
+
background-color: #1e3a8a;
|
| 18 |
+
color: white;
|
| 19 |
+
padding: 15px 30px;
|
| 20 |
+
display: flex;
|
| 21 |
+
justify-content: space-between;
|
| 22 |
+
align-items: center;
|
| 23 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
header h1 {
|
| 27 |
+
margin: 0;
|
| 28 |
+
font-size: 1.5rem;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
nav {
|
| 32 |
+
position: relative;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
nav button {
|
| 36 |
+
background-color: white;
|
| 37 |
+
color: #1e3a8a;
|
| 38 |
+
font-size: 1rem;
|
| 39 |
+
padding: 8px 16px;
|
| 40 |
+
margin-left: 10px;
|
| 41 |
+
border: none;
|
| 42 |
+
border-radius: 4px;
|
| 43 |
+
cursor: pointer;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
nav button:hover {
|
| 47 |
+
background-color: #f0f0f0;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.user-menu {
|
| 51 |
+
position: absolute;
|
| 52 |
+
right: 0;
|
| 53 |
+
top: 45px;
|
| 54 |
+
background: white;
|
| 55 |
+
border: 1px solid #ccc;
|
| 56 |
+
border-radius: 4px;
|
| 57 |
+
display: none;
|
| 58 |
+
flex-direction: column;
|
| 59 |
+
z-index: 999;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.user-menu button {
|
| 63 |
+
width: 100%;
|
| 64 |
+
text-align: left;
|
| 65 |
+
padding: 10px 16px;
|
| 66 |
+
border: none;
|
| 67 |
+
background: white;
|
| 68 |
+
color: #1e3a8a;
|
| 69 |
+
cursor: pointer;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.user-menu button:hover {
|
| 73 |
+
background-color: #f0f0f0;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.container {
|
| 77 |
+
max-width: 95%;
|
| 78 |
+
margin: 40px auto;
|
| 79 |
+
padding: 20px;
|
| 80 |
+
background: white;
|
| 81 |
+
border-radius: 8px;
|
| 82 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.welcome {
|
| 86 |
+
font-size: 1.2rem;
|
| 87 |
+
margin-bottom: 20px;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.card {
|
| 91 |
+
background: #f9f9f9;
|
| 92 |
+
padding: 20px;
|
| 93 |
+
margin-bottom: 20px;
|
| 94 |
+
border-radius: 8px;
|
| 95 |
+
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.grid {
|
| 99 |
+
display: flex;
|
| 100 |
+
gap: 20px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.half {
|
| 104 |
+
flex: 1;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
table {
|
| 108 |
+
width: 100%;
|
| 109 |
+
border-collapse: collapse;
|
| 110 |
+
margin-top: 10px;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
th, td {
|
| 114 |
+
padding: 12px;
|
| 115 |
+
border-bottom: 1px solid #ddd;
|
| 116 |
+
text-align: left;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
th {
|
| 120 |
+
background-color: #f4f4f4;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.delete-icon {
|
| 124 |
+
cursor: pointer;
|
| 125 |
+
color: red;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.status-andamento {
|
| 129 |
+
color: green;
|
| 130 |
+
font-weight: bold;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.status-pausada {
|
| 134 |
+
color: red;
|
| 135 |
+
font-weight: bold;
|
| 136 |
+
}
|
| 137 |
+
</style>
|
| 138 |
+
</head>
|
| 139 |
+
|
| 140 |
+
<body>
|
| 141 |
+
<header>
|
| 142 |
+
<h1>Monitoramento Judicial</h1>
|
| 143 |
+
<nav>
|
| 144 |
+
<button onclick="window.location.href='/tasks'">Tarefas</button>
|
| 145 |
+
<button onclick="window.location.href='/consulta'">
|
| 146 |
+
<span>Consulta</span> 🔍
|
| 147 |
+
</button>
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
<button onclick="toggleUserMenu()" id="user-button">Usuário</button>
|
| 151 |
+
<div class="user-menu" id="user-menu">
|
| 152 |
+
<button onclick="abrirConfiguracoes()">Configurações</button>
|
| 153 |
+
<button onclick="verSaldo()">Saldo</button>
|
| 154 |
+
<button onclick="logout()">Sair</button>
|
| 155 |
+
</div>
|
| 156 |
+
</nav>
|
| 157 |
+
</header>
|
| 158 |
+
|
| 159 |
+
<div class="container">
|
| 160 |
+
<div class="welcome">Bem-vindo, <span id="user-welcome">usuário</span>!</div>
|
| 161 |
+
|
| 162 |
+
<div class="card">
|
| 163 |
+
<h2>Tarefas em Andamento</h2>
|
| 164 |
+
<table id="tarefas-table">
|
| 165 |
+
<thead>
|
| 166 |
+
<tr>
|
| 167 |
+
<th>Nome da Tarefa</th>
|
| 168 |
+
<th>Status</th>
|
| 169 |
+
<th>Ações</th>
|
| 170 |
+
</tr>
|
| 171 |
+
</thead>
|
| 172 |
+
<tbody></tbody>
|
| 173 |
+
</table>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<div class="grid">
|
| 177 |
+
<div class="card half">
|
| 178 |
+
<h2>Resultado da Tarefa - Advogados</h2>
|
| 179 |
+
<table id="advogados-table">
|
| 180 |
+
<thead>
|
| 181 |
+
<tr>
|
| 182 |
+
<th>Nome do Advogado</th>
|
| 183 |
+
<th>UF</th>
|
| 184 |
+
<th>Monitoramento</th>
|
| 185 |
+
<th>Processos</th>
|
| 186 |
+
<th>Ações</th>
|
| 187 |
+
</tr>
|
| 188 |
+
</thead>
|
| 189 |
+
<tbody></tbody>
|
| 190 |
+
</table>
|
| 191 |
+
</div>
|
| 192 |
+
|
| 193 |
+
<div class="card half">
|
| 194 |
+
<h2>Resultado da Tarefa - Processos</h2>
|
| 195 |
+
<table id="processos-table">
|
| 196 |
+
<thead>
|
| 197 |
+
<tr>
|
| 198 |
+
<th>Número do Processo</th>
|
| 199 |
+
<th>Data do Andamento</th>
|
| 200 |
+
<th>Descrição</th>
|
| 201 |
+
<th>Documentos</th>
|
| 202 |
+
<th>Status</th>
|
| 203 |
+
<th>Ações</th>
|
| 204 |
+
</tr>
|
| 205 |
+
</thead>
|
| 206 |
+
<tbody></tbody>
|
| 207 |
+
</table>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
<script>
|
| 213 |
+
const token = localStorage.getItem("token");
|
| 214 |
+
const username = localStorage.getItem('username');
|
| 215 |
+
const nome = localStorage.getItem('nome');
|
| 216 |
+
const sobrenome = localStorage.getItem('sobrenome');
|
| 217 |
+
|
| 218 |
+
document.getElementById('user-button').innerText = username || 'Usuário';
|
| 219 |
+
document.getElementById('user-welcome').innerText = `${nome || ''} ${sobrenome || ''}`.trim();
|
| 220 |
+
|
| 221 |
+
function toggleUserMenu() {
|
| 222 |
+
const menu = document.getElementById('user-menu');
|
| 223 |
+
menu.style.display = menu.style.display === 'flex' ? 'none' : 'flex';
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
function logout() {
|
| 227 |
+
localStorage.clear();
|
| 228 |
+
window.location.href = '/';
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
function abrirConfiguracoes() {
|
| 232 |
+
window.location.href = "/user_config";
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
function verSaldo() {
|
| 236 |
+
alert("Saldo atual: R$ 0,00 (em desenvolvimento)");
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
async function preencherNome() {
|
| 240 |
+
const res = await fetch(`/api/monitoramento-processual/usuario-info?username=${username}`, {
|
| 241 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 242 |
+
});
|
| 243 |
+
const data = await res.json();
|
| 244 |
+
document.getElementById('user-welcome').innerText = `${data.nome} ${data.sobrenome}`;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
async function carregarTarefas() {
|
| 248 |
+
const res = await fetch('/api/monitoramento-processual/consulta/listar-tarefas', {
|
| 249 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 250 |
+
});
|
| 251 |
+
const tarefas = await res.json();
|
| 252 |
+
const tbody = document.querySelector('#tarefas-table tbody');
|
| 253 |
+
tbody.innerHTML = '';
|
| 254 |
+
|
| 255 |
+
tarefas.forEach(tarefa => {
|
| 256 |
+
const statusClass = tarefa.monitoramento === 'sim' ? 'status-andamento' : 'status-pausada';
|
| 257 |
+
const statusLabel = tarefa.monitoramento === 'sim' ? 'Em andamento' : 'Pausada';
|
| 258 |
+
|
| 259 |
+
const row = document.createElement('tr');
|
| 260 |
+
row.innerHTML = `
|
| 261 |
+
<td>${tarefa.nome}</td>
|
| 262 |
+
<td class="${statusClass}">${statusLabel}</td>
|
| 263 |
+
<td><span class="delete-icon" title="Apagar tarefa">🗑️</span></td>
|
| 264 |
+
`;
|
| 265 |
+
tbody.appendChild(row);
|
| 266 |
+
});
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
window.onload = () => {
|
| 270 |
+
preencherNome();
|
| 271 |
+
carregarTarefas();
|
| 272 |
+
};
|
| 273 |
+
|
| 274 |
+
window.onclick = function (e) {
|
| 275 |
+
const menu = document.getElementById("user-menu");
|
| 276 |
+
if (!document.getElementById("user-button").contains(e.target)) {
|
| 277 |
+
menu.style.display = "none";
|
| 278 |
+
}
|
| 279 |
+
};
|
| 280 |
+
</script>
|
| 281 |
+
</body>
|
| 282 |
+
|
| 283 |
+
</html>
|
static/index.html
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-br">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Monitoramento Judicial - Login</title>
|
| 8 |
+
<link rel="stylesheet" href="/static/style.css">
|
| 9 |
+
</head>
|
| 10 |
+
|
| 11 |
+
<body>
|
| 12 |
+
<div class="container">
|
| 13 |
+
<div class="app-title">Monitoramento Judicial</div>
|
| 14 |
+
<div class="description">Consulta e monitoramento de processos judiciais com tecnologia</div>
|
| 15 |
+
|
| 16 |
+
<!-- LOGIN -->
|
| 17 |
+
<div id="login-box">
|
| 18 |
+
<h1>Login</h1>
|
| 19 |
+
<input type="text" id="username" placeholder="Usuário">
|
| 20 |
+
<input type="password" id="password" placeholder="Senha">
|
| 21 |
+
<button onclick="login()">Entrar</button>
|
| 22 |
+
<button id="show-register" onclick="showRegister()">Criar Conta</button>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<!-- CADASTRO -->
|
| 26 |
+
<div id="register-box" style="display:none;">
|
| 27 |
+
<h2>Criar Conta</h2>
|
| 28 |
+
<input type="text" id="nome" placeholder="Nome">
|
| 29 |
+
<input type="text" id="sobrenome" placeholder="Sobrenome">
|
| 30 |
+
<input type="email" id="email" placeholder="E-mail">
|
| 31 |
+
<input type="text" id="newUsername" placeholder="Usuário">
|
| 32 |
+
<input type="password" id="newPassword" placeholder="Senha">
|
| 33 |
+
<button onclick="register()">Cadastrar</button>
|
| 34 |
+
<button onclick="hideRegister()">Voltar</button>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div class="divider"></div>
|
| 38 |
+
|
| 39 |
+
<button onclick="window.location.href='/docs'">📄 Documentação API</button>
|
| 40 |
+
|
| 41 |
+
<div id="message" class="message"></div>
|
| 42 |
+
<div class="footer">© 2025 Monitoramento Judicial</div>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<script>
|
| 46 |
+
function showRegister() {
|
| 47 |
+
document.getElementById('register-box').style.display = 'block';
|
| 48 |
+
document.getElementById('login-box').style.display = 'none';
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function hideRegister() {
|
| 52 |
+
document.getElementById('register-box').style.display = 'none';
|
| 53 |
+
document.getElementById('login-box').style.display = 'block';
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
async function login() {
|
| 57 |
+
const username = document.getElementById('username').value;
|
| 58 |
+
const password = document.getElementById('password').value;
|
| 59 |
+
const response = await fetch('/api/monitoramento-processual/login', {
|
| 60 |
+
method: 'POST',
|
| 61 |
+
headers: { 'Content-Type': 'application/json' },
|
| 62 |
+
body: JSON.stringify({ username: username, senha: password })
|
| 63 |
+
});
|
| 64 |
+
const data = await response.json();
|
| 65 |
+
const msg = document.getElementById('message');
|
| 66 |
+
if (response.ok) {
|
| 67 |
+
localStorage.setItem('token', data.access_token);
|
| 68 |
+
localStorage.setItem('username', username);
|
| 69 |
+
|
| 70 |
+
// 🔄 Novo: Buscar nome e sobrenome
|
| 71 |
+
const userInfo = await fetch(`/api/monitoramento-processual/usuario-info?username=${username}`, {
|
| 72 |
+
headers: { 'Authorization': `Bearer ${data.access_token}` }
|
| 73 |
+
});
|
| 74 |
+
const user = await userInfo.json();
|
| 75 |
+
localStorage.setItem('nome', user.nome);
|
| 76 |
+
localStorage.setItem('sobrenome', user.sobrenome);
|
| 77 |
+
|
| 78 |
+
msg.style.color = 'green';
|
| 79 |
+
msg.innerText = 'Login bem-sucedido! Redirecionando...';
|
| 80 |
+
setTimeout(() => {
|
| 81 |
+
window.location.href = '/dashboard';
|
| 82 |
+
}, 1000);
|
| 83 |
+
} else {
|
| 84 |
+
msg.style.color = 'red';
|
| 85 |
+
msg.innerText = data.detail || 'Erro no login.';
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
async function register() {
|
| 90 |
+
const nome = document.getElementById('nome').value;
|
| 91 |
+
const sobrenome = document.getElementById('sobrenome').value;
|
| 92 |
+
const email = document.getElementById('email').value;
|
| 93 |
+
const username = document.getElementById('newUsername').value;
|
| 94 |
+
const password = document.getElementById('newPassword').value;
|
| 95 |
+
const response = await fetch('/api/monitoramento-processual/cadastrar-usuario', {
|
| 96 |
+
method: 'POST',
|
| 97 |
+
headers: { 'Content-Type': 'application/json' },
|
| 98 |
+
body: JSON.stringify({ username, senha: password, nome, sobrenome, email })
|
| 99 |
+
});
|
| 100 |
+
const data = await response.json();
|
| 101 |
+
const msg = document.getElementById('message');
|
| 102 |
+
if (response.ok) {
|
| 103 |
+
msg.style.color = 'green';
|
| 104 |
+
msg.innerText = 'Usuário cadastrado com sucesso!';
|
| 105 |
+
hideRegister();
|
| 106 |
+
} else {
|
| 107 |
+
msg.style.color = 'red';
|
| 108 |
+
msg.innerText = data.detail || 'Erro no cadastro.';
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
</script>
|
| 112 |
+
</body>
|
| 113 |
+
|
| 114 |
+
</html>
|
static/script.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const token = localStorage.getItem('token');
|
| 2 |
+
const username = localStorage.getItem('username');
|
| 3 |
+
|
| 4 |
+
if (username) {
|
| 5 |
+
document.getElementById('user-name').innerText = username;
|
| 6 |
+
document.getElementById('user-welcome').innerText = username;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
function logout() {
|
| 10 |
+
localStorage.clear();
|
| 11 |
+
window.location.href = '/';
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function showForm(action) {
|
| 15 |
+
const container = document.getElementById('form-container');
|
| 16 |
+
let formHTML = '';
|
| 17 |
+
|
| 18 |
+
switch (action) {
|
| 19 |
+
case 'adv-cad':
|
| 20 |
+
formHTML = `<h3>Cadastrar Advogado</h3>
|
| 21 |
+
<input type='text' id='nome-adv' placeholder='Nome Advogado'>
|
| 22 |
+
<input type='text' id='uf-adv' placeholder='UF'>
|
| 23 |
+
<button onclick='cadastrarAdvogado()'>Cadastrar</button>`;
|
| 24 |
+
break;
|
| 25 |
+
case 'adv-del':
|
| 26 |
+
formHTML = `<h3>Deletar Advogado</h3>
|
| 27 |
+
<input type='text' id='nome-adv-del' placeholder='Nome Advogado'>
|
| 28 |
+
<input type='text' id='uf-adv-del' placeholder='UF'>
|
| 29 |
+
<button onclick='deletarAdvogado()'>Deletar</button>`;
|
| 30 |
+
break;
|
| 31 |
+
case 'proc-cad':
|
| 32 |
+
formHTML = `<h3>Cadastrar Processo</h3>
|
| 33 |
+
<input type='text' id='num-proc' placeholder='Número Processo'>
|
| 34 |
+
<input type='text' id='uf-proc' placeholder='UF'>
|
| 35 |
+
<button onclick='cadastrarProcesso()'>Cadastrar</button>`;
|
| 36 |
+
break;
|
| 37 |
+
case 'proc-del':
|
| 38 |
+
formHTML = `<h3>Deletar Processo</h3>
|
| 39 |
+
<input type='text' id='num-proc-del' placeholder='Número Processo'>
|
| 40 |
+
<input type='text' id='uf-proc-del' placeholder='UF'>
|
| 41 |
+
<button onclick='deletarProcesso()'>Deletar</button>`;
|
| 42 |
+
break;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
container.innerHTML = formHTML;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async function cadastrarAdvogado() {
|
| 49 |
+
const nome = document.getElementById('nome-adv').value;
|
| 50 |
+
const uf = document.getElementById('uf-adv').value;
|
| 51 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/inserir-advogado?nome_advogado=${encodeURIComponent(nome)}&uf=${encodeURIComponent(uf)}`, {
|
| 52 |
+
method: 'POST',
|
| 53 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 54 |
+
});
|
| 55 |
+
if (res.ok) {
|
| 56 |
+
alert('Advogado cadastrado!');
|
| 57 |
+
await loadAdvogados();
|
| 58 |
+
} else {
|
| 59 |
+
alert('Erro ao cadastrar advogado.');
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
async function deletarAdvogado() {
|
| 64 |
+
const nome = document.getElementById('nome-adv-del').value;
|
| 65 |
+
const uf = document.getElementById('uf-adv-del').value;
|
| 66 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/deletar-advogado?nome_advogado=${encodeURIComponent(nome)}&uf=${encodeURIComponent(uf)}`, {
|
| 67 |
+
method: 'DELETE',
|
| 68 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 69 |
+
});
|
| 70 |
+
if (res.ok) {
|
| 71 |
+
alert('Advogado deletado!');
|
| 72 |
+
await loadAdvogados();
|
| 73 |
+
} else {
|
| 74 |
+
alert('Erro ao deletar advogado.');
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
async function cadastrarProcesso() {
|
| 79 |
+
const num = document.getElementById('num-proc').value;
|
| 80 |
+
const uf = document.getElementById('uf-proc').value;
|
| 81 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/inserir-processo?numero_processo=${encodeURIComponent(num)}&uf=${encodeURIComponent(uf)}`, {
|
| 82 |
+
method: 'POST',
|
| 83 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 84 |
+
});
|
| 85 |
+
if (res.ok) {
|
| 86 |
+
alert('Processo cadastrado!');
|
| 87 |
+
await loadProcessos();
|
| 88 |
+
} else {
|
| 89 |
+
alert('Erro ao cadastrar processo.');
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
async function deletarProcesso() {
|
| 94 |
+
const num = document.getElementById('num-proc-del').value;
|
| 95 |
+
const uf = document.getElementById('uf-proc-del').value;
|
| 96 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/deletar-processo?numero_processo=${encodeURIComponent(num)}&uf=${encodeURIComponent(uf)}`, {
|
| 97 |
+
method: 'DELETE',
|
| 98 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 99 |
+
});
|
| 100 |
+
if (res.ok) {
|
| 101 |
+
alert('Processo deletado!');
|
| 102 |
+
await loadProcessos();
|
| 103 |
+
} else {
|
| 104 |
+
alert('Erro ao deletar processo.');
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
async function loadAdvogados() {
|
| 109 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/listar-advogados`, {
|
| 110 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 111 |
+
});
|
| 112 |
+
const data = await res.json();
|
| 113 |
+
const tbody = document.querySelector('#advogados-table tbody');
|
| 114 |
+
tbody.innerHTML = '';
|
| 115 |
+
data.forEach(item => {
|
| 116 |
+
const row = `<tr><td>${item.nome_advogado}</td><td>${item.uf}</td></tr>`;
|
| 117 |
+
tbody.innerHTML += row;
|
| 118 |
+
});
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
async function loadProcessos() {
|
| 122 |
+
const res = await fetch(`/api/monitoramento-processual/consulta/listar-processos`, {
|
| 123 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 124 |
+
});
|
| 125 |
+
const data = await res.json();
|
| 126 |
+
const tbody = document.querySelector('#processos-table tbody');
|
| 127 |
+
tbody.innerHTML = '';
|
| 128 |
+
data.forEach(item => {
|
| 129 |
+
const row = `<tr><td>${item.numero_processo}</td><td>${item.uf}</td></tr>`;
|
| 130 |
+
tbody.innerHTML += row;
|
| 131 |
+
});
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// Carregar ao abrir
|
| 135 |
+
window.onload = () => {
|
| 136 |
+
loadAdvogados();
|
| 137 |
+
loadProcessos();
|
| 138 |
+
};
|
static/style.css
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: Arial, sans-serif;
|
| 3 |
+
background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
|
| 4 |
+
display: flex;
|
| 5 |
+
justify-content: center;
|
| 6 |
+
align-items: center;
|
| 7 |
+
height: 100vh;
|
| 8 |
+
margin: 0;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.container {
|
| 12 |
+
background: white;
|
| 13 |
+
padding: 40px;
|
| 14 |
+
border-radius: 12px;
|
| 15 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
| 16 |
+
width: 320px;
|
| 17 |
+
text-align: center;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.app-title {
|
| 21 |
+
font-size: 1.8rem;
|
| 22 |
+
margin-bottom: 5px;
|
| 23 |
+
color: #1e3a8a;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.description {
|
| 27 |
+
font-size: 0.9rem;
|
| 28 |
+
color: #555;
|
| 29 |
+
margin-bottom: 20px;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
h1, h2 {
|
| 33 |
+
margin-bottom: 15px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
input[type="text"], input[type="password"], input[type="email"] {
|
| 37 |
+
width: 100%;
|
| 38 |
+
padding: 10px;
|
| 39 |
+
margin: 10px 0;
|
| 40 |
+
border: 1px solid #ccc;
|
| 41 |
+
border-radius: 4px;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
button {
|
| 45 |
+
width: 100%;
|
| 46 |
+
padding: 10px;
|
| 47 |
+
background-color: #1e3a8a;
|
| 48 |
+
color: white;
|
| 49 |
+
border: none;
|
| 50 |
+
border-radius: 4px;
|
| 51 |
+
cursor: pointer;
|
| 52 |
+
margin-top: 10px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
button:hover {
|
| 56 |
+
background-color: #3749c1;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.message {
|
| 60 |
+
margin-top: 10px;
|
| 61 |
+
color: red;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.divider {
|
| 65 |
+
margin: 20px 0;
|
| 66 |
+
border-top: 1px solid #ddd;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.footer {
|
| 70 |
+
margin-top: 25px;
|
| 71 |
+
font-size: 0.8rem;
|
| 72 |
+
color: #999;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.ver-mais-btn {
|
| 76 |
+
margin: 20px auto 0;
|
| 77 |
+
background: none;
|
| 78 |
+
border: none;
|
| 79 |
+
color: #1e3a8a;
|
| 80 |
+
font-weight: bold;
|
| 81 |
+
cursor: pointer;
|
| 82 |
+
font-size: 0.95rem;
|
| 83 |
+
text-align: center;
|
| 84 |
+
display: none;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.ver-mais-btn:hover {
|
| 88 |
+
text-decoration: underline;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.movimentacao {
|
| 92 |
+
margin-bottom: 15px;
|
| 93 |
+
padding-bottom: 10px;
|
| 94 |
+
border-bottom: 1px solid #ccc;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.hidden {
|
| 98 |
+
display: none;
|
| 99 |
+
}
|
static/tasks.html
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-br">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Monitoramento Judicial - Tarefas</title>
|
| 8 |
+
<style>
|
| 9 |
+
body {
|
| 10 |
+
margin: 0;
|
| 11 |
+
font-family: 'Segoe UI', sans-serif;
|
| 12 |
+
background-color: #f4f6f8;
|
| 13 |
+
color: #333;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
header {
|
| 17 |
+
background-color: #1e3a8a;
|
| 18 |
+
color: white;
|
| 19 |
+
padding: 15px 30px;
|
| 20 |
+
display: flex;
|
| 21 |
+
justify-content: space-between;
|
| 22 |
+
align-items: center;
|
| 23 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
header h1 {
|
| 27 |
+
margin: 0;
|
| 28 |
+
font-size: 1.5rem;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
nav button {
|
| 32 |
+
background-color: white;
|
| 33 |
+
color: #1e3a8a;
|
| 34 |
+
font-size: 1rem;
|
| 35 |
+
padding: 8px 16px;
|
| 36 |
+
margin-left: 20px;
|
| 37 |
+
border: none;
|
| 38 |
+
border-radius: 4px;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
nav button:hover {
|
| 43 |
+
background-color: #f0f0f0;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.container {
|
| 47 |
+
max-width: 800px;
|
| 48 |
+
margin: 40px auto;
|
| 49 |
+
padding: 20px;
|
| 50 |
+
background: white;
|
| 51 |
+
border-radius: 8px;
|
| 52 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.welcome {
|
| 56 |
+
font-size: 1.2rem;
|
| 57 |
+
margin-bottom: 20px;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
form {
|
| 61 |
+
display: flex;
|
| 62 |
+
flex-direction: column;
|
| 63 |
+
gap: 10px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
input, select, button {
|
| 67 |
+
padding: 10px;
|
| 68 |
+
font-size: 1rem;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
button {
|
| 72 |
+
background-color: #1e3a8a;
|
| 73 |
+
color: white;
|
| 74 |
+
border: none;
|
| 75 |
+
border-radius: 4px;
|
| 76 |
+
cursor: pointer;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
button:hover {
|
| 80 |
+
background-color: #354ec4;
|
| 81 |
+
}
|
| 82 |
+
</style>
|
| 83 |
+
</head>
|
| 84 |
+
|
| 85 |
+
<body>
|
| 86 |
+
<header>
|
| 87 |
+
<h1>Monitoramento Judicial</h1>
|
| 88 |
+
<nav>
|
| 89 |
+
<span id="user-name">Usuário</span>
|
| 90 |
+
<button onclick="window.location.href='/dashboard'">Dashboard</button>
|
| 91 |
+
<button onclick="logout()">Sair</button>
|
| 92 |
+
</nav>
|
| 93 |
+
</header>
|
| 94 |
+
|
| 95 |
+
<div class="container">
|
| 96 |
+
<div class="welcome">Bem-vindo, <span id="user-welcome">Usuário</span>!</div>
|
| 97 |
+
|
| 98 |
+
<h2>Nova Tarefa</h2>
|
| 99 |
+
<form onsubmit="cadastrarTarefa(event)">
|
| 100 |
+
<label for="tipo">Tipo:</label>
|
| 101 |
+
<select id="tipo">
|
| 102 |
+
<option value="advogado">Advogado</option>
|
| 103 |
+
<option value="processo">Processo</option>
|
| 104 |
+
</select>
|
| 105 |
+
|
| 106 |
+
<label for="nome">Nome/Número:</label>
|
| 107 |
+
<input type="text" id="nome" placeholder="Nome do Advogado ou Número do Processo" required>
|
| 108 |
+
|
| 109 |
+
<label for="uf">UF:</label>
|
| 110 |
+
<input type="text" id="uf" placeholder="UF" required>
|
| 111 |
+
|
| 112 |
+
<label for="monitoramento">Deseja monitoramento?</label>
|
| 113 |
+
<select id="monitoramento">
|
| 114 |
+
<option value="sim">Sim</option>
|
| 115 |
+
<option value="nao">Não</option>
|
| 116 |
+
</select>
|
| 117 |
+
|
| 118 |
+
<label for="intervalo">Intervalo de Monitoramento:</label>
|
| 119 |
+
<input type="number" id="intervalo" placeholder="Ex: 15" required>
|
| 120 |
+
|
| 121 |
+
<label for="unidade">Unidade:</label>
|
| 122 |
+
<select id="unidade">
|
| 123 |
+
<option value="minutos">Minutos</option>
|
| 124 |
+
<option value="horas">Horas</option>
|
| 125 |
+
<option value="dias">Dias</option>
|
| 126 |
+
</select>
|
| 127 |
+
|
| 128 |
+
<button type="submit">Cadastrar Tarefa</button>
|
| 129 |
+
</form>
|
| 130 |
+
|
| 131 |
+
<div id="message"></div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<script>
|
| 135 |
+
const token = localStorage.getItem('token');
|
| 136 |
+
const username = localStorage.getItem('username');
|
| 137 |
+
document.getElementById('user-name').innerText = username;
|
| 138 |
+
document.getElementById('user-welcome').innerText = username;
|
| 139 |
+
|
| 140 |
+
function logout() {
|
| 141 |
+
localStorage.clear();
|
| 142 |
+
window.location.href = '/';
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
async function cadastrarTarefa(event) {
|
| 146 |
+
event.preventDefault();
|
| 147 |
+
const tipo = document.getElementById('tipo').value;
|
| 148 |
+
const nome = document.getElementById('nome').value;
|
| 149 |
+
const uf = document.getElementById('uf').value;
|
| 150 |
+
const monitoramento = document.getElementById('monitoramento').value;
|
| 151 |
+
const intervalo = document.getElementById('intervalo').value;
|
| 152 |
+
const unidade = document.getElementById('unidade').value;
|
| 153 |
+
|
| 154 |
+
const response = await fetch(`/api/monitoramento-processual/consulta/inserir-tarefa?tipo=${tipo}&nome=${nome}&uf=${uf}&monitoramento=${monitoramento}&intervalo=${intervalo}&unidade=${unidade}`, {
|
| 155 |
+
method: 'POST',
|
| 156 |
+
headers: { 'Authorization': `Bearer ${token}` }
|
| 157 |
+
});
|
| 158 |
+
|
| 159 |
+
const result = await response.json();
|
| 160 |
+
const msg = document.getElementById('message');
|
| 161 |
+
msg.innerText = result.message || result.detail || 'Erro ao cadastrar.';
|
| 162 |
+
msg.style.color = response.ok ? 'green' : 'red';
|
| 163 |
+
|
| 164 |
+
if (response.ok) {
|
| 165 |
+
document.querySelector('form').reset();
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
</script>
|
| 169 |
+
</body>
|
| 170 |
+
</html>
|
static/user_config.html
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-BR">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Configuração do Usuário</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
background-color: #f8f9fa;
|
| 11 |
+
margin: 0;
|
| 12 |
+
padding: 0;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.container {
|
| 16 |
+
max-width: 500px;
|
| 17 |
+
margin: 40px auto;
|
| 18 |
+
padding: 30px;
|
| 19 |
+
background-color: white;
|
| 20 |
+
border-radius: 10px;
|
| 21 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
h2 {
|
| 25 |
+
margin-bottom: 20px;
|
| 26 |
+
color: #003366;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
form {
|
| 30 |
+
margin-bottom: 30px;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
label {
|
| 34 |
+
display: block;
|
| 35 |
+
margin-bottom: 5px;
|
| 36 |
+
font-weight: bold;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
input[type="text"],
|
| 40 |
+
input[type="password"] {
|
| 41 |
+
width: 100%;
|
| 42 |
+
padding: 10px;
|
| 43 |
+
margin-bottom: 15px;
|
| 44 |
+
border: 1px solid #ccc;
|
| 45 |
+
border-radius: 5px;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
button {
|
| 49 |
+
background-color: #003366;
|
| 50 |
+
color: white;
|
| 51 |
+
padding: 10px 15px;
|
| 52 |
+
border: none;
|
| 53 |
+
border-radius: 5px;
|
| 54 |
+
cursor: pointer;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
button:hover {
|
| 58 |
+
background-color: #0052a3;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.message {
|
| 62 |
+
margin-top: 10px;
|
| 63 |
+
font-weight: bold;
|
| 64 |
+
}
|
| 65 |
+
</style>
|
| 66 |
+
</head>
|
| 67 |
+
<body>
|
| 68 |
+
<div class="container">
|
| 69 |
+
<h2>Atualizar Nome de Usuário</h2>
|
| 70 |
+
<form id="username-form">
|
| 71 |
+
<label for="newUsername">Novo Nome de Usuário:</label>
|
| 72 |
+
<input type="text" id="newUsername" required />
|
| 73 |
+
<button type="submit">Atualizar</button>
|
| 74 |
+
<div id="usernameMessage" class="message"></div>
|
| 75 |
+
</form>
|
| 76 |
+
|
| 77 |
+
<h2>Atualizar Senha</h2>
|
| 78 |
+
<form id="password-form">
|
| 79 |
+
<label for="currentPassword">Senha Atual:</label>
|
| 80 |
+
<input type="password" id="currentPassword" required />
|
| 81 |
+
|
| 82 |
+
<label for="newPassword">Nova Senha:</label>
|
| 83 |
+
<input type="password" id="newPassword" required />
|
| 84 |
+
|
| 85 |
+
<button type="submit">Atualizar</button>
|
| 86 |
+
<div id="passwordMessage" class="message"></div>
|
| 87 |
+
</form>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<script>
|
| 91 |
+
const token = localStorage.getItem("token"); ;
|
| 92 |
+
|
| 93 |
+
document.getElementById("username-form").addEventListener("submit", async (e) => {
|
| 94 |
+
e.preventDefault();
|
| 95 |
+
const newUsername = document.getElementById("newUsername").value;
|
| 96 |
+
|
| 97 |
+
const res = await fetch("/api/monitoramento-processual/config/usuario/username", {
|
| 98 |
+
method: "PUT",
|
| 99 |
+
headers: {
|
| 100 |
+
"Content-Type": "application/json",
|
| 101 |
+
Authorization: `Bearer ${token}`,
|
| 102 |
+
},
|
| 103 |
+
body: JSON.stringify({ new_username: newUsername }),
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
+
const data = await res.json();
|
| 107 |
+
const msgDiv = document.getElementById("usernameMessage");
|
| 108 |
+
msgDiv.textContent = data.message || data.detail;
|
| 109 |
+
msgDiv.style.color = res.ok ? "green" : "red";
|
| 110 |
+
});
|
| 111 |
+
|
| 112 |
+
document.getElementById("password-form").addEventListener("submit", async (e) => {
|
| 113 |
+
e.preventDefault();
|
| 114 |
+
const currentPassword = document.getElementById("currentPassword").value;
|
| 115 |
+
const newPassword = document.getElementById("newPassword").value;
|
| 116 |
+
|
| 117 |
+
const res = await fetch("/api/monitoramento-processual/config/usuario/password", {
|
| 118 |
+
method: "PUT",
|
| 119 |
+
headers: {
|
| 120 |
+
"Content-Type": "application/json",
|
| 121 |
+
Authorization: `Bearer ${token}`,
|
| 122 |
+
},
|
| 123 |
+
body: JSON.stringify({
|
| 124 |
+
current_password: currentPassword,
|
| 125 |
+
new_password: newPassword,
|
| 126 |
+
}),
|
| 127 |
+
});
|
| 128 |
+
|
| 129 |
+
const data = await res.json();
|
| 130 |
+
const msgDiv = document.getElementById("passwordMessage");
|
| 131 |
+
msgDiv.textContent = data.message || data.detail;
|
| 132 |
+
msgDiv.style.color = res.ok ? "green" : "red";
|
| 133 |
+
});
|
| 134 |
+
</script>
|
| 135 |
+
</body>
|
| 136 |
+
</html>
|
user_autentication/user_bd.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def initialize_user_table():
|
| 5 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 6 |
+
cursor = conn.cursor()
|
| 7 |
+
cursor.execute('''
|
| 8 |
+
CREATE TABLE IF NOT EXISTS usuarios (
|
| 9 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 10 |
+
username TEXT UNIQUE NOT NULL,
|
| 11 |
+
senha_hash TEXT NOT NULL,
|
| 12 |
+
nome TEXT,
|
| 13 |
+
sobrenome TEXT,
|
| 14 |
+
email TEXT
|
| 15 |
+
)
|
| 16 |
+
''')
|
| 17 |
+
conn.commit()
|
| 18 |
+
conn.close()
|
| 19 |
+
|
| 20 |
+
# Chama a função logo ao iniciar
|
| 21 |
+
initialize_user_table()
|
user_autentication/user_hash.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from passlib.context import CryptContext
|
| 3 |
+
from jose import jwt, JWTError
|
| 4 |
+
from datetime import datetime, timedelta, timezone
|
| 5 |
+
|
| 6 |
+
# Configurações
|
| 7 |
+
SECRET_KEY = "chave-super-secreta"
|
| 8 |
+
ALGORITHM = "HS256"
|
| 9 |
+
ACCESS_TOKEN_EXPIRE_MINUTES = 10
|
| 10 |
+
|
| 11 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 12 |
+
|
| 13 |
+
# Logger setup
|
| 14 |
+
logger = logging.getLogger("auth")
|
| 15 |
+
logger.setLevel(logging.INFO)
|
| 16 |
+
handler = logging.StreamHandler()
|
| 17 |
+
formatter = logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s] - %(message)s", "%Y-%m-%d %H:%M:%S")
|
| 18 |
+
handler.setFormatter(formatter)
|
| 19 |
+
logger.addHandler(handler)
|
| 20 |
+
|
| 21 |
+
def verificar_senha(senha_pura, senha_hash):
|
| 22 |
+
return pwd_context.verify(senha_pura, senha_hash)
|
| 23 |
+
|
| 24 |
+
def gerar_hash(senha):
|
| 25 |
+
return pwd_context.hash(senha)
|
| 26 |
+
|
| 27 |
+
def criar_token(data: dict):
|
| 28 |
+
to_encode = data.copy()
|
| 29 |
+
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
| 30 |
+
to_encode.update({"exp": expire})
|
| 31 |
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
| 32 |
+
logger.info(f"Token criado para usuário: {data.get('sub')}")
|
| 33 |
+
return encoded_jwt
|
| 34 |
+
|
| 35 |
+
def verificar_token(token: str):
|
| 36 |
+
logger.info(f"Token recebido para verificação: {token}")
|
| 37 |
+
try:
|
| 38 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": False})
|
| 39 |
+
logger.info(f"Payload decodificado: {payload}")
|
| 40 |
+
username = payload.get("sub")
|
| 41 |
+
if username is None:
|
| 42 |
+
logger.error("Campo 'sub' ausente no token!")
|
| 43 |
+
raise Exception("Token inválido")
|
| 44 |
+
logger.info(f"Token válido para usuário: {username}")
|
| 45 |
+
return username
|
| 46 |
+
except JWTError as e:
|
| 47 |
+
logger.error(f"Erro JWT ao decodificar: {str(e)}")
|
| 48 |
+
raise Exception("Token inválido ou expirado")
|
| 49 |
+
except Exception as e:
|
| 50 |
+
logger.error(f"Erro geral na verificação do token: {str(e)}")
|
| 51 |
+
raise Exception("Token inválido ou expirado")
|
user_autentication/verificar_token.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import Depends, HTTPException, status
|
| 2 |
+
from fastapi.security import OAuth2PasswordBearer
|
| 3 |
+
from user_autentication.user_hash import verificar_token
|
| 4 |
+
|
| 5 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/monitoramento-processual/login")
|
| 6 |
+
|
| 7 |
+
def get_current_user(token: str = Depends(oauth2_scheme)):
|
| 8 |
+
try:
|
| 9 |
+
username = verificar_token(token)
|
| 10 |
+
return username
|
| 11 |
+
except Exception as e:
|
| 12 |
+
raise HTTPException(
|
| 13 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 14 |
+
detail="Token inválido ou expirado"
|
| 15 |
+
)
|
user_configuration/user_config.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
from fastapi import HTTPException, status
|
| 3 |
+
from src.models import UserUpdatePassword, UserUpdateUsername
|
| 4 |
+
|
| 5 |
+
from user_autentication.user_hash import gerar_hash, verificar_senha
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
async def trocar_username(usuario_atual: str, novo_username: str):
|
| 9 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 10 |
+
cursor = conn.cursor()
|
| 11 |
+
|
| 12 |
+
# Verifica se o novo username já existe
|
| 13 |
+
cursor.execute("SELECT username FROM usuarios WHERE username = ?", (novo_username,))
|
| 14 |
+
if cursor.fetchone():
|
| 15 |
+
conn.close()
|
| 16 |
+
raise HTTPException(
|
| 17 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 18 |
+
detail="Nome de usuário já está em uso."
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
# Atualiza o username no banco
|
| 22 |
+
cursor.execute("UPDATE usuarios SET username = ? WHERE username = ?", (novo_username, usuario_atual))
|
| 23 |
+
if cursor.rowcount == 0:
|
| 24 |
+
conn.close()
|
| 25 |
+
raise HTTPException(
|
| 26 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 27 |
+
detail="Usuário atual não encontrado."
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
conn.commit()
|
| 31 |
+
conn.close()
|
| 32 |
+
return {"message": "Username atualizado com sucesso."}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
async def trocar_senha(usuario_atual: str, senha_atual: str, nova_senha: str):
|
| 36 |
+
conn = sqlite3.connect("monitoramento.db")
|
| 37 |
+
cursor = conn.cursor()
|
| 38 |
+
|
| 39 |
+
# Busca hash da senha atual
|
| 40 |
+
cursor.execute("SELECT senha_hash FROM usuarios WHERE username = ?", (usuario_atual,))
|
| 41 |
+
row = cursor.fetchone()
|
| 42 |
+
if not row:
|
| 43 |
+
conn.close()
|
| 44 |
+
raise HTTPException(
|
| 45 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 46 |
+
detail="Usuário não encontrado."
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
senha_hash = row[0]
|
| 50 |
+
if not verificar_senha(senha_atual, senha_hash):
|
| 51 |
+
conn.close()
|
| 52 |
+
raise HTTPException(
|
| 53 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 54 |
+
detail="Senha atual incorreta."
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Atualiza com nova senha
|
| 58 |
+
nova_hash = gerar_hash(nova_senha)
|
| 59 |
+
cursor.execute("UPDATE usuarios SET senha_hash = ? WHERE username = ?", (nova_hash, usuario_atual))
|
| 60 |
+
conn.commit()
|
| 61 |
+
conn.close()
|
| 62 |
+
return {"message": "Senha atualizada com sucesso."}
|
utils/util.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
HEADERS = [
|
| 4 |
+
{
|
| 5 |
+
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Microsoft Edge";v="122"',
|
| 6 |
+
'Sec-Ch-Ua-Mobile': '?0',
|
| 7 |
+
'Sec-Ch-Ua-Platform': '"Windows"',
|
| 8 |
+
'Upgrade-Insecure-Requests': '1' ,
|
| 9 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0',
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120", "Opera GX";v="106"',
|
| 13 |
+
'Sec-Ch-Ua-Mobile': '?0',
|
| 14 |
+
'Sec-Ch-Ua-Platform': '"Windows"',
|
| 15 |
+
'Upgrade-Insecure-Requests': '1' ,
|
| 16 |
+
'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 OPR/106.0.0.0',
|
| 17 |
+
}
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
#----------------- Headers data -----------------------------------
|
| 21 |
+
def get_headers():
|
| 22 |
+
# 0: Brave broser headers
|
| 23 |
+
# 1: Firefox browser headers
|
| 24 |
+
# 2: Google Chrome browser headers
|
| 25 |
+
return HEADERS[random.randint(0, 1)]
|