Fix login redirect — access result.data.token/user
Login/register pages now correctly unwrap API response {data: {token, user}}.
Cleaned up leftover apiFetch code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
odoo_modules/task_team_connector/services/__init__.py
Normal file
1
odoo_modules/task_team_connector/services/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# services package — imported explicitly by models / controllers
|
||||
123
odoo_modules/task_team_connector/services/api_client.py
Normal file
123
odoo_modules/task_team_connector/services/api_client.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Task Team Connector — REST API client (stdlib-only, no extra deps)
|
||||
import json
|
||||
import logging
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskTeamApiClient:
|
||||
"""Thin HTTP wrapper for the Task Team REST API."""
|
||||
|
||||
def __init__(self, base_url: str, api_key: str):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.api_key = api_key # service-account e-mail
|
||||
self._token: str | None = None # cached JWT
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Auth
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _authenticate(self):
|
||||
"""Login with the service account and cache the JWT."""
|
||||
payload = json.dumps({'email': self.api_key}).encode()
|
||||
req = urllib.request.Request(
|
||||
f'{self.base_url}/auth/login',
|
||||
data=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
method='POST',
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
self._token = data.get('data', {}).get('token')
|
||||
except urllib.error.HTTPError as exc:
|
||||
_logger.error('Task Team authentication failed: %s', exc)
|
||||
self._token = None
|
||||
|
||||
def _headers(self) -> dict:
|
||||
if not self._token:
|
||||
self._authenticate()
|
||||
hdrs = {'Content-Type': 'application/json'}
|
||||
if self._token:
|
||||
hdrs['Authorization'] = f'Bearer {self._token}'
|
||||
return hdrs
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Generic request
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _request(self, method: str, path: str, payload=None, *, retry=True):
|
||||
url = f'{self.base_url}{path}'
|
||||
data = json.dumps(payload).encode() if payload is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=self._headers(), method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 401 and retry:
|
||||
# Token expired — re-auth once
|
||||
self._token = None
|
||||
return self._request(method, path, payload, retry=False)
|
||||
body = exc.read().decode(errors='replace') if exc else ''
|
||||
_logger.error('Task Team API %s %s → %s: %s', method, path, exc.code, body[:200])
|
||||
raise
|
||||
except Exception as exc:
|
||||
_logger.error('Task Team API request error (%s %s): %s', method, path, exc)
|
||||
raise
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# User helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_or_create_user(self, email: str, name: str) -> dict:
|
||||
"""Find a Task Team user by e-mail, creating them if absent."""
|
||||
payload = json.dumps({'email': email}).encode()
|
||||
req = urllib.request.Request(
|
||||
f'{self.base_url}/auth/login',
|
||||
data=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
method='POST',
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 401:
|
||||
# Not registered yet
|
||||
reg_payload = json.dumps({'email': email, 'name': name}).encode()
|
||||
reg_req = urllib.request.Request(
|
||||
f'{self.base_url}/auth/register',
|
||||
data=reg_payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
method='POST',
|
||||
)
|
||||
with urllib.request.urlopen(reg_req, timeout=10) as resp:
|
||||
return json.loads(resp.read())
|
||||
raise
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Task CRUD
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def create_task(self, payload: dict, user_email: str | None = None) -> dict:
|
||||
return self._request('POST', '/tasks', payload)
|
||||
|
||||
def update_task(self, tt_task_id: str, payload: dict, user_email: str | None = None) -> dict:
|
||||
return self._request('PUT', f'/tasks/{tt_task_id}', payload)
|
||||
|
||||
def get_task(self, tt_task_id: str) -> dict:
|
||||
return self._request('GET', f'/tasks/{tt_task_id}')
|
||||
|
||||
def list_tasks(self, **filters) -> dict:
|
||||
qs = '&'.join(f'{k}={v}' for k, v in filters.items() if v is not None)
|
||||
path = f'/tasks?{qs}' if qs else '/tasks'
|
||||
return self._request('GET', path)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Webhook
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def send_webhook(self, event: str, payload: dict) -> dict:
|
||||
return self._request('POST', '/connectors/webhook/odoo', {'event': event, **payload})
|
||||
Reference in New Issue
Block a user