Skip to content

Commit ee26d5e

Browse files
Allow2CEOruvnet
andcommitted
Add request, voice code, feedback, and HTTP modules
RequestManager (more time, day type change, ban lift with temp token flow), VoiceCode (HMAC-SHA256 challenge-response for offline approval), FeedbackManager (submit, load, reply) -- all ported from PHP reference. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 50ab254 commit ee26d5e

3 files changed

Lines changed: 570 additions & 0 deletions

File tree

allow2_service/feedback.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"""Manages feedback submission and discussion threads via the Allow2 API.
2+
3+
Feedback allows users to report issues, request features, or communicate
4+
with the Allow2 Parental Freedom support team.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import json
10+
from typing import Any, Dict, List
11+
from urllib.parse import quote
12+
13+
from allow2_service.exceptions.api_error import ApiError
14+
from allow2_service.exceptions.unpaired import UnpairedError
15+
from allow2_service.http_response import HttpResponse
16+
from allow2_service.models.feedback_category import FeedbackCategory
17+
18+
19+
class FeedbackManager:
20+
"""Manages feedback submission and discussion threads via the Allow2 API."""
21+
22+
def __init__(
23+
self,
24+
http_client: Any,
25+
api_host: str,
26+
) -> None:
27+
self._http_client = http_client
28+
self._api_host = api_host
29+
30+
def submit(
31+
self,
32+
access_token: str,
33+
user_id: str,
34+
category: FeedbackCategory,
35+
message: str,
36+
) -> str:
37+
"""Submit new feedback.
38+
39+
Args:
40+
access_token: Valid OAuth2 access token.
41+
user_id: The integrating application's user ID.
42+
category: The feedback category.
43+
message: The feedback message.
44+
45+
Returns:
46+
The discussion ID for the created feedback thread.
47+
48+
Raises:
49+
UnpairedError: If the API returns 401/403.
50+
ApiError: On other API failures.
51+
"""
52+
response: HttpResponse = self._http_client.post(
53+
self._api_host + "/feedback/submit",
54+
{
55+
"access_token": access_token,
56+
"category": category.value,
57+
"message": message,
58+
},
59+
)
60+
61+
if response.is_unauthorized():
62+
raise UnpairedError(user_id)
63+
64+
if not response.is_success():
65+
body = None
66+
try:
67+
body = response.json()
68+
except (json.JSONDecodeError, ValueError):
69+
pass
70+
raise ApiError(
71+
message="Failed to submit feedback: HTTP %d" % response.status_code,
72+
http_status_code=response.status_code,
73+
response_body=body,
74+
)
75+
76+
data = response.json()
77+
return str(
78+
data.get("discussionId", data.get("feedbackId", data.get("id", "")))
79+
)
80+
81+
def load(self, access_token: str, user_id: str) -> List[Dict[str, Any]]:
82+
"""Load all feedback discussions for the authenticated user.
83+
84+
Args:
85+
access_token: Valid OAuth2 access token.
86+
user_id: The integrating application's user ID.
87+
88+
Returns:
89+
List of feedback discussion threads.
90+
91+
Raises:
92+
UnpairedError: If the API returns 401/403.
93+
ApiError: On other API failures.
94+
"""
95+
response: HttpResponse = self._http_client.get(
96+
self._api_host + "/feedback/load",
97+
headers={"Authorization": "Bearer " + access_token},
98+
)
99+
100+
if response.is_unauthorized():
101+
raise UnpairedError(user_id)
102+
103+
if not response.is_success():
104+
body = None
105+
try:
106+
body = response.json()
107+
except (json.JSONDecodeError, ValueError):
108+
pass
109+
raise ApiError(
110+
message="Failed to load feedback: HTTP %d" % response.status_code,
111+
http_status_code=response.status_code,
112+
response_body=body,
113+
)
114+
115+
data = response.json()
116+
return data.get("discussions", data.get("feedback", []))
117+
118+
def reply(
119+
self,
120+
access_token: str,
121+
user_id: str,
122+
discussion_id: str,
123+
message: str,
124+
) -> None:
125+
"""Reply to an existing feedback discussion thread.
126+
127+
Args:
128+
access_token: Valid OAuth2 access token.
129+
user_id: The integrating application's user ID.
130+
discussion_id: The discussion thread ID to reply to.
131+
message: The reply message.
132+
133+
Raises:
134+
UnpairedError: If the API returns 401/403.
135+
ApiError: On other API failures.
136+
"""
137+
response: HttpResponse = self._http_client.post(
138+
self._api_host + "/feedback/" + quote(discussion_id, safe="") + "/reply",
139+
{
140+
"access_token": access_token,
141+
"message": message,
142+
},
143+
)
144+
145+
if response.is_unauthorized():
146+
raise UnpairedError(user_id)
147+
148+
if not response.is_success():
149+
body = None
150+
try:
151+
body = response.json()
152+
except (json.JSONDecodeError, ValueError):
153+
pass
154+
raise ApiError(
155+
message="Failed to reply to feedback: HTTP %d" % response.status_code,
156+
http_status_code=response.status_code,
157+
response_body=body,
158+
)

0 commit comments

Comments
 (0)