Skip to content

Adiciona endpoint que recebe dados de coleção do core.#341

Open
samuelveigarangel wants to merge 17 commits intoscieloorg:masterfrom
samuelveigarangel:issue-339
Open

Adiciona endpoint que recebe dados de coleção do core.#341
samuelveigarangel wants to merge 17 commits intoscieloorg:masterfrom
samuelveigarangel:issue-339

Conversation

@samuelveigarangel
Copy link
Contributor

@samuelveigarangel samuelveigarangel commented Sep 25, 2025

O que esse PR faz?

  • Adiciona uma estrutura de decode token JWT, baseado em RS256. Não há necessidade de user para autenticar (Chave pública, chave privada).
  • Adiciona um endpoint (/api/v1/update_collection) para receber os dados de collection do core.
  • Adiciona funções para completar os dados de collection e sponsor.

Onde a revisão poderia começar?

pelos commits

Como este poderia ser testado manualmente?

Pré-requisitos:

  1. Gerar par de chaves (caso ainda não existam)
# Gerar novo par de chaves se necessário
openssl genrsa -out jwt_private.pem 2048
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem
  1. Configurar variáveis de ambiente
  • No opac_5
    • Defina JWT_PUBLIC_KEY_PATH com o caminho absoluto da chave pública (jwt_public.pem).
    • Defina OPAC_SERVER_NAME com o endereço acessível do opac_webapp_local (IP do gateway do container) e a porta 8000. Ex.: 172.18.0.1:8000
  • No core:
    • Defina JWT_PRIVATE_KEY_PATH com o caminho absoluto da chave privada (jwt_private.pem).
  1. Configurar coleção no core
  • Adicione uma coleção no core usando:
    • domain: exatamente o mesmo valor de OPAC_SERVER_NAME (por exemplo, 172.18.0.1:8000)
    • Demais campos: iguais aos da coleção existente no opac_5.
  1. Validar o fluxo
  • Ao salvar a coleção no core, será disparado o trigger post_save, que enviará uma requisição HTTP para o domain configurado (valor de OPAC_SERVER_NAME).
  • Verifique nos logs do opac_5 se a requisição foi recebida e processada com sucesso.
  • Se aplicável, valide o efeito esperado no opac (por exemplo, sincronização/atualização dos dados da coleção).

Algum cenário de contexto que queira dar?

Não aprovar esta PR antes de realizar o merge de scieloorg/core#1156 e estar em produção para gerar os par de chaves a partir do ambiente de produção e ser adicionado a chave publicar nesta PR

Screenshots

N/A

Quais são tickets relevantes?

#339

Referências

N/A

Comment on lines +192 to +195
list_logo = []
list_logo.append(logos.get("homepage", ""))
list_logo.append(logos.get("header", ""))
list_logo.append(logos.get("menu", ""))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel otimize: list_logo = [logos.get(key, "") for key in ["homepage", "header", "menu"]]

if main_name and collection.name != main_name:
collection.name = main_name

sponsors = handler_collection_sponsos(json_data.get("supporting_organizations"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel Existe uma ordem desejável de apresentação dos logos dos sponsors. Garanta que a ordem seja preservada.

logo = handler_with_logo(logo_url=logo_url, folder="img/sponsors")

# Por causa do order unique, evitar error ao mudar a ordem
temp_order = -int(time.time()*1000) # incrementa um valor aleatório em order.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel isso pode fazer parte do dado registrado no core. Assim evita processamento excessivo e a ordem é garantida na origem.

Copy link
Member

@robertatakenaka robertatakenaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel fazer as correções

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds JWT-based authentication and an endpoint to receive collection data updates from the core service. The implementation enables the core service to push collection and sponsor data to OPAC using RS256 JWT tokens for authentication.

Key changes:

  • Implements JWT token validation using RS256 algorithm with public/private key pairs
  • Adds /api/v1/update_collection endpoint to receive collection data from core
  • Creates utility functions for downloading and handling logo files from external URLs

Reviewed Changes

Copilot reviewed 6 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
opac/webapp/utils/handler_with_logo.py New utility for downloading and storing logo files from URLs
opac/webapp/main/views.py Adds update_collection endpoint and cleans up imports
opac/webapp/main/helper.py JWT token extraction and verification functions
opac/webapp/main/decorators.py JWT authentication decorator for API endpoints
opac/webapp/controllers.py Collection and sponsor data processing functions
opac/webapp/config/default.py JWT configuration settings

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +29 to +30
for chuck in resp.iter_content(chunk_size=8192):
f.write(chuck)
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'chuck' to 'chunk'.

Suggested change
for chuck in resp.iter_content(chunk_size=8192):
f.write(chuck)
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)

Copilot uses AI. Check for mistakes.
try:
g.jwt = verify_jwt(token)
except PyJWTError as e:
print("Erro na validação JWT:", str(e))
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using print() for error logging is not recommended. Replace with proper logging using the logger module.

Copilot uses AI. Check for mistakes.
return data


def set_atributtes_logos(collection, logos, name_logos=["home_logo", "logo_menu", "header_logo"], langs=["pt", "en", "es"]):
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'atributtes' to 'attributes'.

Suggested change
def set_atributtes_logos(collection, logos, name_logos=["home_logo", "logo_menu", "header_logo"], langs=["pt", "en", "es"]):
def set_attributes_logos(collection, logos, name_logos=["home_logo", "logo_menu", "header_logo"], langs=["pt", "en", "es"]):

Copilot uses AI. Check for mistakes.
return obj


def handler_collection_sponsos(data):
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'sponsos' to 'sponsors'.

Suggested change
def handler_collection_sponsos(data):
def handler_collection_sponsors(data):

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +203
for logo in list_logo:
if not logo:
continue
for name_logo, lang in zip(name_logos, langs):
if hasattr(collection, f"{name_logo}_{lang}"):
logo = handler_with_logo(logo_url=logo.get(lang), folder=f"img/{name_logo}")
if logo.get("rel_path"):
collection_logo = f"http://{current_app.config['SERVER_NAME']}{logo.get('rel_path')}"
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable name collision: 'logo' is used both for the outer loop variable and the return value from handler_with_logo. This creates confusion and potential bugs.

Suggested change
for logo in list_logo:
if not logo:
continue
for name_logo, lang in zip(name_logos, langs):
if hasattr(collection, f"{name_logo}_{lang}"):
logo = handler_with_logo(logo_url=logo.get(lang), folder=f"img/{name_logo}")
if logo.get("rel_path"):
collection_logo = f"http://{current_app.config['SERVER_NAME']}{logo.get('rel_path')}"
for logo_data in list_logo:
if not logo_data:
continue
for name_logo, lang in zip(name_logos, langs):
if hasattr(collection, f"{name_logo}_{lang}"):
logo_info = handler_with_logo(logo_url=logo_data.get(lang), folder=f"img/{name_logo}")
if logo_info.get("rel_path"):
collection_logo = f"http://{current_app.config['SERVER_NAME']}{logo_info.get('rel_path')}"

Copilot uses AI. Check for mistakes.
Comment on lines +695 to +696
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
JWT_PUBLIC_KEY_PEM = f.read()
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File operations at module level should include proper error handling. If the JWT public key file doesn't exist or is unreadable, this will cause the application to fail during import.

Suggested change
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
JWT_PUBLIC_KEY_PEM = f.read()
try:
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
JWT_PUBLIC_KEY_PEM = f.read()
except (FileNotFoundError, PermissionError, OSError) as e:
JWT_PUBLIC_KEY_PEM = None
import warnings
warnings.warn(
f"Could not read JWT public key from '{JWT_PUBLIC_KEY_PATH}': {e}. "
"JWT_PUBLIC_KEY_PEM is set to None."
)

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 6 out of 9 changed files in this pull request and generated 6 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

# abs_path /app/opac/webapp/static/img/sponsors/screenshot_from_2024-10-14_11-20-16.png
abs_path = os.path.join(current_app.static_folder, rel_path)
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
print(os.path.join(current_app.static_url_path, rel_path))
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement should be removed or replaced with proper logging.

Suggested change
print(os.path.join(current_app.static_url_path, rel_path))
logging.info(f"Logo static URL path: {os.path.join(current_app.static_url_path, rel_path)}")

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +101
return jwt.decode(
token,
public_key,
algorithms=[current_app.config["JWT_ALG"]],
audience=current_app.config["JWT_AUD"],
issuer=current_app.config["JWT_ISS"],
# options={"require": ["exp", "iat", "nbf", "iss", "aud"]},
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code should be removed if not needed, or uncommented with proper explanation if required for JWT validation.

Suggested change
return jwt.decode(
token,
public_key,
algorithms=[current_app.config["JWT_ALG"]],
audience=current_app.config["JWT_AUD"],
issuer=current_app.config["JWT_ISS"],
# options={"require": ["exp", "iat", "nbf", "iss", "aud"]},
# Enforce presence of standard claims for robust JWT validation
return jwt.decode(
token,
public_key,
algorithms=[current_app.config["JWT_ALG"]],
audience=current_app.config["JWT_AUD"],
issuer=current_app.config["JWT_ISS"],
options={"require": ["exp", "iat", "nbf", "iss", "aud"]},

Copilot uses AI. Check for mistakes.
try:
g.jwt = verify_jwt(token)
except PyJWTError as e:
logging.error("Erro na validação JWT:", str(e))
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message should be in English to maintain consistency with codebase.

Suggested change
logging.error("Erro na validação JWT:", str(e))
logging.error("Error validating JWT:", str(e))

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +179
Atribuí os logos do modelo collection. (home_logo, logo_menu, header_logo)
Ex:
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring should be in English and has Portuguese text.

Suggested change
Atribuí os logos do modelo collection. (home_logo, logo_menu, header_logo)
Ex:
Assigns the logos to the collection model. (home_logo, logo_menu, header_logo)
Example:

Copilot uses AI. Check for mistakes.
"logo_url": "https://core.scielo.org/media/original_images/logo.png"
}
"""
import time
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import statement should be at the top of the file, not inside a function.

Copilot uses AI. Check for mistakes.
Comment on lines +695 to +697
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
JWT_PUBLIC_KEY_PEM = f.read()

Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opening the JWT public key file at import time will cause the application to fail if the file doesn't exist. This should be handled with proper error handling or moved to a function that's called when needed.

Suggested change
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
JWT_PUBLIC_KEY_PEM = f.read()
def get_jwt_public_key_pem():
"""
Reads the JWT public key from the configured path.
Returns the key contents as bytes.
Raises FileNotFoundError with a clear message if the file is missing.
"""
try:
with open(JWT_PUBLIC_KEY_PATH, "rb") as f:
return f.read()
except FileNotFoundError:
raise FileNotFoundError(
f"JWT public key file not found at '{JWT_PUBLIC_KEY_PATH}'. "
"Please ensure the file exists and the path is correct."
)
except Exception as e:
raise RuntimeError(
f"An error occurred while reading the JWT public key file at '{JWT_PUBLIC_KEY_PATH}': {e}"
)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants