Skip to content

Commit

Permalink
Merge branch 'master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
phenobarbital authored Dec 19, 2024
2 parents 4947c91 + 763dc35 commit 5fc78cd
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 9 deletions.
23 changes: 16 additions & 7 deletions navigator/actions/odoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,30 @@ def __init__(self, *args, **kwargs):
super(Odoo, self).__init__(*args, **kwargs)
self.instance = self._kwargs.pop('instance', ODOO_HOST)
self.api_key = self._kwargs.pop('api_key', ODOO_APIKEY)

async def run(self):
pass

async def fieldservice_order(self, data):
url = f'{self.instance}api/webhook/fieldservice_order'
self.credentials = {}
self.auth = {}
self.method = 'post'
self.accept = 'application/json'
self.headers['Content-Type'] = 'application/json'
self.headers['api-key'] = self.api_key


async def run(self):
pass

async def fieldservice_order(self, data):
url = f'{self.instance}api/webhook/fieldservice_order'
result, error = await self.async_request(
url, self.method, data, use_json=True
)

return result or error['message']


async def create_lead(self, data):
url = f'{self.instance}api/webhook/lead'
result, error = await self.async_request(
url, self.method, data, use_json=True
)

return result if result is not None else error['message']
return result or error['message']
2 changes: 1 addition & 1 deletion navigator/actions/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ async def process_request(self, future, url: str):
result = filename
# getting the result, based on the Accept logic
elif self.file_buffer is True:
data = await response.read()
data = response.content
buffer = BytesIO(data)
buffer.seek(0)
result = buffer
Expand Down
96 changes: 96 additions & 0 deletions navigator/actions/zammad.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import base64
import magic
from datetime import datetime, timedelta
from urllib.parse import quote_plus
from aiohttp.web import Request, StreamResponse
from io import BytesIO
from ..exceptions import ConfigError
from ..conf import (
ZAMMAD_INSTANCE,
Expand All @@ -16,6 +19,8 @@





class Zammad(AbstractTicket, RESTAction):
"""Zammad.
Expand Down Expand Up @@ -360,6 +365,11 @@ async def get_articles(self, ticket_id: int):
self.url = f"{self.zammad_instance}/api/v1/ticket_articles/by_ticket/{ticket_id}"
self.method = 'get'
try:
"""
In the `articles` array returned by the URL `/api/v1/ticket_articles/by_ticket/{ticket_id}`,
if any item contains the `attachments` attribute, it should be destructured in the frontend
to request the images using `get_attachment_img`.
"""
result, _ = await self.request(
self.url, self.method
)
Expand All @@ -368,3 +378,89 @@ async def get_articles(self, ticket_id: int):
raise ConfigError(
f"Error Getting Zammad Ticket: {e}"
) from e


async def get_attachment_img(self, attachment: str, request: Request):
"""Retrieve an attachment from a ticket.
Args:
attachment (str): The attachment path from the ticket.
Returns:
Response: HTTP Response containing the attachment file.
Raises:
ConfigError: If an error occurs during the request or processing.
"""
# Construir la URL para obtener el adjunto
self.url = f"{self.zammad_instance}api/v1/ticket_attachment{attachment}"
self.method = 'get'
self.file_buffer = True

try:
# Realizar la solicitud al servidor
result, error = await self.request(self.url, self.method)

# Manejar errores en la respuesta
if error:
raise ConfigError(f"Error Getting Zammad Attachment: {error.get('message', 'Unknown error')}")

# Separar el cuerpo y la respuesta
image, response = result

# Validar y obtener encabezados
content_type = response.headers.get('Content-Type', 'application/octet-stream')
if not content_type.startswith('image/'):
raise ConfigError("The attachment is not a valid image file.")

content_disposition = response.headers.get('Content-Disposition')
if not content_disposition or 'filename=' not in content_disposition:
raise ConfigError("Attachment filename missing in response headers.")

# Extraer el nombre del archivo desde Content-Disposition
image_name = content_disposition.split('filename=')[-1].strip('"')

# Convertir el flujo de bytes en datos completos si es necesario
if isinstance(image, BytesIO):
image_data = image.getvalue()
else:
image_data = image # Ya es un objeto binario válido

expiring_date = datetime.now() + timedelta(days=2)
chunk_size = 16384
content_length = len(image_data)
# Crear y devolver la respuesta HTTP
response = StreamResponse(
status=200,
headers={
'Content-Type': content_type,
'Content-Disposition': f'attachment; filename="{image_name}"',
'Content-Transfer-Encoding': 'binary',
'Transfer-Encoding': 'chunked',
'Connection': 'keep-alive',
"Content-Description": "File Transfer",
"Content-Transfer-Encoding": "binary",
'Expires': expiring_date.strftime('%a, %d %b %Y %H:%M:%S GMT'),
}
)
response.headers[
"Content-Range"
] = f"bytes 0-{chunk_size}/{content_length}"
try:
i = 0
await response.prepare(request)
while True:
chunk = image_data[i: i + chunk_size]
i += chunk_size
if not chunk:
break
await response.write(chunk)
await response.drain() # deprecated
await response.write_eof()
return response
except Exception as e:
raise ConfigError(f"Error while writing attachment: {e}") from e
except KeyError as e:
raise ConfigError(f"Missing required header: {e}") from e
except Exception as e:
raise ConfigError(f"Unexpected error while fetching attachment: {e}") from e
2 changes: 1 addition & 1 deletion navigator/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__description__ = (
"Navigator Web Framework based on aiohttp, " "with batteries included."
)
__version__ = "2.12.5"
__version__ = "2.12.8"
__author__ = "Jesus Lara"
__author_email__ = "jesuslarag@gmail.com"
__license__ = "BSD"

0 comments on commit 5fc78cd

Please sign in to comment.