fluidattacks_zoho_sdk 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fluidattacks_zoho_sdk/__init__.py +8 -0
- fluidattacks_zoho_sdk/_decoders.py +177 -0
- fluidattacks_zoho_sdk/auth/__init__.py +51 -0
- fluidattacks_zoho_sdk/auth/_access.py +86 -0
- fluidattacks_zoho_sdk/auth/_core.py +39 -0
- fluidattacks_zoho_sdk/auth/_decode.py +62 -0
- fluidattacks_zoho_sdk/auth/_refresh.py +123 -0
- fluidattacks_zoho_sdk/auth/_revoke.py +98 -0
- fluidattacks_zoho_sdk/ids.py +59 -0
- fluidattacks_zoho_sdk/py.typed +0 -0
- fluidattacks_zoho_sdk/zoho_desk/__init__.py +0 -0
- fluidattacks_zoho_sdk/zoho_desk/_decode.py +192 -0
- fluidattacks_zoho_sdk/zoho_desk/core.py +117 -0
- fluidattacks_zoho_sdk-1.0.0.dist-info/METADATA +9 -0
- fluidattacks_zoho_sdk-1.0.0.dist-info/RECORD +16 -0
- fluidattacks_zoho_sdk-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
IO,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from fa_purity import Maybe, ResultE, Unsafe
|
|
6
|
+
from fa_purity.date_time import DatetimeUTC
|
|
7
|
+
from fa_purity.json import (
|
|
8
|
+
JsonObj,
|
|
9
|
+
JsonPrimitiveUnfolder,
|
|
10
|
+
JsonUnfolder,
|
|
11
|
+
UnfoldedFactory,
|
|
12
|
+
Unfolder,
|
|
13
|
+
)
|
|
14
|
+
from fluidattacks_etl_utils.decode import DecodeUtils
|
|
15
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
16
|
+
|
|
17
|
+
from fluidattacks_zoho_sdk.auth import Credentials
|
|
18
|
+
from fluidattacks_zoho_sdk.ids import (
|
|
19
|
+
AccountId,
|
|
20
|
+
ContactId,
|
|
21
|
+
CrmId,
|
|
22
|
+
DeparmentId,
|
|
23
|
+
ProductId,
|
|
24
|
+
ProfileId,
|
|
25
|
+
RoleId,
|
|
26
|
+
TeamId,
|
|
27
|
+
TicketId,
|
|
28
|
+
UserId,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _decode_zoho_creds(raw: JsonObj) -> ResultE[Credentials]:
|
|
33
|
+
client_id = JsonUnfolder.require(raw, "client_id", Unfolder.to_primitive).bind(
|
|
34
|
+
JsonPrimitiveUnfolder.to_str,
|
|
35
|
+
)
|
|
36
|
+
client_secret = JsonUnfolder.require(raw, "client_secret", Unfolder.to_primitive).bind(
|
|
37
|
+
JsonPrimitiveUnfolder.to_str,
|
|
38
|
+
)
|
|
39
|
+
refresh_token = JsonUnfolder.require(raw, "refresh_token", Unfolder.to_primitive).bind(
|
|
40
|
+
JsonPrimitiveUnfolder.to_str,
|
|
41
|
+
)
|
|
42
|
+
scopes_result = JsonUnfolder.require(
|
|
43
|
+
raw,
|
|
44
|
+
"scopes",
|
|
45
|
+
lambda i: Unfolder.to_list_of(
|
|
46
|
+
i,
|
|
47
|
+
lambda x: Unfolder.to_primitive(x).bind(JsonPrimitiveUnfolder.to_str),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
return client_id.bind(
|
|
51
|
+
lambda cid: client_secret.bind(
|
|
52
|
+
lambda secret: refresh_token.bind(
|
|
53
|
+
lambda token: scopes_result.map(
|
|
54
|
+
lambda scopes: Credentials(cid, secret, token, frozenset(scopes)),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_sub_json(raw: JsonObj, key: str) -> JsonObj:
|
|
62
|
+
return (
|
|
63
|
+
JsonUnfolder.require(raw, key, lambda v: Unfolder.to_json(v))
|
|
64
|
+
.alt(Unsafe.raise_exception)
|
|
65
|
+
.to_union()
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def decode_optional_date(raw: JsonObj, key: str) -> ResultE[Maybe[DatetimeUTC]]:
|
|
70
|
+
return JsonUnfolder.optional(raw, key, DecodeUtils.to_opt_date_time).map(
|
|
71
|
+
lambda v: v.bind(lambda j: j),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def decode_require_date(raw: JsonObj, key: str) -> ResultE[DatetimeUTC]:
|
|
76
|
+
return JsonUnfolder.require(raw, key, DecodeUtils.to_date_time)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def decode_maybe_str(raw: JsonObj, key: str) -> ResultE[Maybe[str]]:
|
|
80
|
+
return JsonUnfolder.optional(raw, key, DecodeUtils.to_opt_str).map(
|
|
81
|
+
lambda v: v.bind(lambda j: j),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def decode_zoho_creds(auth_file: IO[str]) -> ResultE[Credentials]:
|
|
86
|
+
return UnfoldedFactory.load(auth_file).bind(_decode_zoho_creds)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def decode_user_id(raw: JsonObj) -> ResultE[UserId]:
|
|
90
|
+
return (
|
|
91
|
+
JsonUnfolder.require(raw, "id", DecodeUtils.to_str)
|
|
92
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
93
|
+
.map(UserId)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def decode_rol_id(raw: JsonObj) -> ResultE[RoleId]:
|
|
98
|
+
return (
|
|
99
|
+
JsonUnfolder.require(raw, "roleId", DecodeUtils.to_str)
|
|
100
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
101
|
+
.map(RoleId)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def decode_profile_id(raw: JsonObj) -> ResultE[ProfileId]:
|
|
106
|
+
return (
|
|
107
|
+
JsonUnfolder.require(raw, "profileId", DecodeUtils.to_str)
|
|
108
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
109
|
+
.map(ProfileId)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def decode_account_id(raw: JsonObj) -> ResultE[AccountId]:
|
|
114
|
+
return (
|
|
115
|
+
JsonUnfolder.require(raw, "accountId", DecodeUtils.to_str)
|
|
116
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
117
|
+
.map(AccountId)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def decode_crm_id(raw: JsonObj) -> ResultE[CrmId]:
|
|
122
|
+
return (
|
|
123
|
+
JsonUnfolder.require(get_sub_json(raw, "zohoCRMContact"), "id", DecodeUtils.to_str)
|
|
124
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
125
|
+
.map(lambda obj: CrmId(obj))
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def decode_deparment_id(raw: JsonObj) -> ResultE[DeparmentId]:
|
|
130
|
+
return (
|
|
131
|
+
JsonUnfolder.optional(raw, "departmentId", DecodeUtils.to_opt_str)
|
|
132
|
+
.map(
|
|
133
|
+
lambda v: v.bind(lambda x: x).map(
|
|
134
|
+
lambda j: Natural.from_int(int(j)).alt(Unsafe.raise_exception).to_union(),
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
.map(lambda obj: DeparmentId(obj))
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def decode_product_id(raw: JsonObj) -> ResultE[ProductId]:
|
|
142
|
+
return (
|
|
143
|
+
JsonUnfolder.optional(raw, "productId", DecodeUtils.to_opt_str)
|
|
144
|
+
.map(
|
|
145
|
+
lambda v: v.bind(lambda x: x).map(
|
|
146
|
+
lambda j: Natural.from_int(int(j)).alt(Unsafe.raise_exception).to_union(),
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
.map(lambda obj: ProductId(obj))
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def decode_team_id(raw: JsonObj) -> ResultE[TeamId]:
|
|
154
|
+
return JsonUnfolder.require(raw, "teamId", DecodeUtils.to_str).bind(
|
|
155
|
+
lambda v: Natural.from_int(int(v)).map(lambda obj: TeamId(obj)),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def decode_ticket_id(raw: JsonObj) -> ResultE[TicketId]:
|
|
160
|
+
return JsonUnfolder.require(raw, "id", DecodeUtils.to_str).bind(
|
|
161
|
+
lambda v: Natural.from_int(int(v)).map(lambda obj: TicketId(obj)),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def decode_contact_id(raw: JsonObj) -> ResultE[ContactId]:
|
|
166
|
+
return JsonUnfolder.require(get_sub_json(raw, "contact"), "id", DecodeUtils.to_str).bind(
|
|
167
|
+
lambda v: Natural.from_int(int(v)).map(lambda obj: ContactId(obj)),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def decode_account_id_ticket(raw: JsonObj) -> ResultE[AccountId]:
|
|
172
|
+
account_obj = get_sub_json(raw, "contact")
|
|
173
|
+
return JsonUnfolder.require(
|
|
174
|
+
get_sub_json(account_obj, "account"),
|
|
175
|
+
"id",
|
|
176
|
+
DecodeUtils.to_str,
|
|
177
|
+
).bind(lambda v: Natural.from_int(int(v)).map(lambda obj: AccountId(obj)))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import (
|
|
3
|
+
dataclass,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from fa_purity import (
|
|
7
|
+
Cmd,
|
|
8
|
+
)
|
|
9
|
+
from fa_purity.json import (
|
|
10
|
+
JsonObj,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from . import (
|
|
14
|
+
_access,
|
|
15
|
+
_refresh,
|
|
16
|
+
_revoke,
|
|
17
|
+
)
|
|
18
|
+
from ._core import (
|
|
19
|
+
Credentials,
|
|
20
|
+
RefreshToken,
|
|
21
|
+
Token,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
|
|
25
|
+
LOG = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class AuthApi:
|
|
30
|
+
new_access_token: Cmd[Token]
|
|
31
|
+
manual_new_refresh_token: Cmd[RefreshToken]
|
|
32
|
+
manual_revoke_token: Cmd[JsonObj]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class AuthApiFactory:
|
|
37
|
+
@staticmethod
|
|
38
|
+
def auth_api(creds: Credentials) -> AuthApi:
|
|
39
|
+
return AuthApi(
|
|
40
|
+
_access.new_access_token(creds),
|
|
41
|
+
_refresh.generate_refresh_token(creds),
|
|
42
|
+
_revoke.revoke_refresh_token(),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__version__ = "1.0.0"
|
|
47
|
+
__all__ = [
|
|
48
|
+
"Credentials",
|
|
49
|
+
"RefreshToken",
|
|
50
|
+
"Token",
|
|
51
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from fa_purity import (
|
|
5
|
+
Cmd,
|
|
6
|
+
FrozenDict,
|
|
7
|
+
ResultE,
|
|
8
|
+
cast_exception,
|
|
9
|
+
)
|
|
10
|
+
from fa_purity.json import (
|
|
11
|
+
JsonObj,
|
|
12
|
+
JsonPrimitiveUnfolder,
|
|
13
|
+
JsonUnfolder,
|
|
14
|
+
Primitive,
|
|
15
|
+
UnfoldedFactory,
|
|
16
|
+
Unfolder,
|
|
17
|
+
)
|
|
18
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
19
|
+
from pure_requests.basic import (
|
|
20
|
+
Data,
|
|
21
|
+
Endpoint,
|
|
22
|
+
HttpClientFactory,
|
|
23
|
+
Params,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from ._core import (
|
|
27
|
+
Credentials,
|
|
28
|
+
Token,
|
|
29
|
+
)
|
|
30
|
+
from ._decode import (
|
|
31
|
+
decode_json,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
|
|
35
|
+
LOG = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _decode(raw: JsonObj) -> ResultE[Token]:
|
|
39
|
+
return JsonUnfolder.require(
|
|
40
|
+
raw,
|
|
41
|
+
"access_token",
|
|
42
|
+
lambda v: Unfolder.to_primitive(v).bind(JsonPrimitiveUnfolder.to_str),
|
|
43
|
+
).map(Token)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def new_access_token(credentials: Credentials) -> Cmd[Token]:
|
|
47
|
+
LOG.info("Generating access token")
|
|
48
|
+
endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token")
|
|
49
|
+
params: dict[str, Primitive] = {
|
|
50
|
+
"refresh_token": credentials.refresh_token,
|
|
51
|
+
"client_id": credentials.client_id,
|
|
52
|
+
"client_secret": credentials.client_secret,
|
|
53
|
+
"grant_type": "refresh_token",
|
|
54
|
+
}
|
|
55
|
+
empty: JsonObj = FrozenDict({})
|
|
56
|
+
client = HttpClientFactory.new_client(None, None, None)
|
|
57
|
+
response = client.post(endpoint, Params(UnfoldedFactory.from_dict(params)), Data(empty)).map(
|
|
58
|
+
lambda r: Bug.assume_success(
|
|
59
|
+
"NewTokenError.response",
|
|
60
|
+
inspect.currentframe(),
|
|
61
|
+
(str(client), endpoint.raw, str(params)),
|
|
62
|
+
r.alt(cast_exception),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
data = response.map(
|
|
66
|
+
lambda r: Bug.assume_success(
|
|
67
|
+
"NewTokenError.decode_json",
|
|
68
|
+
inspect.currentframe(),
|
|
69
|
+
(str(client), endpoint.raw, str(params)),
|
|
70
|
+
decode_json(r).alt(
|
|
71
|
+
lambda c: c.map(
|
|
72
|
+
cast_exception,
|
|
73
|
+
lambda v: v.map(cast_exception, cast_exception),
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return data.map(
|
|
80
|
+
lambda j: Bug.assume_success(
|
|
81
|
+
"NewTokenError.decode_json",
|
|
82
|
+
inspect.currentframe(),
|
|
83
|
+
(JsonUnfolder.dumps(j),),
|
|
84
|
+
_decode(j),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from dataclasses import (
|
|
3
|
+
dataclass,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class Token:
|
|
11
|
+
raw_token: str
|
|
12
|
+
|
|
13
|
+
def __repr__(self) -> str:
|
|
14
|
+
return "[masked]"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class RefreshToken:
|
|
19
|
+
raw_token: str
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
signature = hashlib.sha256(self.raw_token.encode("utf-8")).hexdigest()[:128]
|
|
23
|
+
return f"[masked] signature={signature}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class Credentials:
|
|
28
|
+
client_id: str
|
|
29
|
+
client_secret: str
|
|
30
|
+
refresh_token: str
|
|
31
|
+
scopes: frozenset[str]
|
|
32
|
+
|
|
33
|
+
def __repr__(self) -> str:
|
|
34
|
+
return f"Creds(client_id={self.client_id}, scopes={self.scopes})"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Token",
|
|
39
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
TypeVar,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from fa_purity import (
|
|
6
|
+
Coproduct,
|
|
7
|
+
CoproductFactory,
|
|
8
|
+
FrozenList,
|
|
9
|
+
Result,
|
|
10
|
+
ResultE,
|
|
11
|
+
ResultFactory,
|
|
12
|
+
)
|
|
13
|
+
from fa_purity.json import (
|
|
14
|
+
JsonObj,
|
|
15
|
+
)
|
|
16
|
+
from pure_requests.response import (
|
|
17
|
+
handle_status,
|
|
18
|
+
json_decode,
|
|
19
|
+
)
|
|
20
|
+
from requests import (
|
|
21
|
+
HTTPError,
|
|
22
|
+
JSONDecodeError,
|
|
23
|
+
Response,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_T = TypeVar("_T")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decode_json(
|
|
30
|
+
response: Response,
|
|
31
|
+
) -> Result[JsonObj, Coproduct[HTTPError, Coproduct[JSONDecodeError, TypeError]]]:
|
|
32
|
+
_factory: CoproductFactory[HTTPError, Coproduct[JSONDecodeError, TypeError]] = (
|
|
33
|
+
CoproductFactory()
|
|
34
|
+
)
|
|
35
|
+
_factory_2: CoproductFactory[JSONDecodeError, TypeError] = CoproductFactory()
|
|
36
|
+
_factory_3: ResultFactory[
|
|
37
|
+
JsonObj,
|
|
38
|
+
Coproduct[HTTPError, Coproduct[JSONDecodeError, TypeError]],
|
|
39
|
+
] = ResultFactory()
|
|
40
|
+
return (
|
|
41
|
+
handle_status(response)
|
|
42
|
+
.alt(lambda x: _factory.inl(x))
|
|
43
|
+
.bind(
|
|
44
|
+
lambda r: json_decode(r)
|
|
45
|
+
.alt(lambda e: _factory.inr(_factory_2.inl(e)))
|
|
46
|
+
.bind(
|
|
47
|
+
lambda c: c.map(
|
|
48
|
+
lambda j: _factory_3.success(j),
|
|
49
|
+
lambda _: _factory_3.failure(
|
|
50
|
+
_factory.inr(_factory_2.inr(TypeError("Expected non-list"))),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def require_index(items: FrozenList[_T], index: int) -> ResultE[_T]:
|
|
59
|
+
try:
|
|
60
|
+
return Result.success(items[index])
|
|
61
|
+
except IndexError as err:
|
|
62
|
+
return Result.failure(Exception(err))
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from getpass import (
|
|
4
|
+
getpass,
|
|
5
|
+
)
|
|
6
|
+
from urllib.parse import (
|
|
7
|
+
urlencode,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from fa_purity import (
|
|
11
|
+
Cmd,
|
|
12
|
+
FrozenDict,
|
|
13
|
+
Result,
|
|
14
|
+
ResultE,
|
|
15
|
+
cast_exception,
|
|
16
|
+
)
|
|
17
|
+
from fa_purity.json import (
|
|
18
|
+
JsonObj,
|
|
19
|
+
JsonPrimitiveUnfolder,
|
|
20
|
+
JsonUnfolder,
|
|
21
|
+
Primitive,
|
|
22
|
+
UnfoldedFactory,
|
|
23
|
+
Unfolder,
|
|
24
|
+
)
|
|
25
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
26
|
+
from pure_requests.basic import (
|
|
27
|
+
Data,
|
|
28
|
+
Endpoint,
|
|
29
|
+
HttpClientFactory,
|
|
30
|
+
Params,
|
|
31
|
+
)
|
|
32
|
+
from requests import (
|
|
33
|
+
RequestException,
|
|
34
|
+
Response,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
from ._core import (
|
|
38
|
+
ACCOUNTS_URL,
|
|
39
|
+
Credentials,
|
|
40
|
+
RefreshToken,
|
|
41
|
+
)
|
|
42
|
+
from ._decode import (
|
|
43
|
+
decode_json,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
LOG = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _decode(raw: JsonObj) -> ResultE[RefreshToken]:
|
|
50
|
+
LOG.debug("raw token response: %s", JsonUnfolder.dumps(raw))
|
|
51
|
+
return JsonUnfolder.require(
|
|
52
|
+
raw,
|
|
53
|
+
"refresh_token",
|
|
54
|
+
lambda v: Unfolder.to_primitive(v).bind(JsonPrimitiveUnfolder.to_str),
|
|
55
|
+
).map(RefreshToken)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _user_input_code() -> Cmd[str]:
|
|
59
|
+
def _action() -> str:
|
|
60
|
+
LOG.info("Paste grant token:")
|
|
61
|
+
token = getpass()
|
|
62
|
+
LOG.debug(token)
|
|
63
|
+
return token
|
|
64
|
+
|
|
65
|
+
return Cmd.wrap_impure(_action)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _gen_refresh_token(
|
|
69
|
+
credentials: Credentials,
|
|
70
|
+
code: str,
|
|
71
|
+
) -> Cmd[Result[Response, RequestException]]:
|
|
72
|
+
endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token")
|
|
73
|
+
raw: dict[str, Primitive] = {
|
|
74
|
+
"grant_type": "authorization_code",
|
|
75
|
+
"client_id": credentials.client_id,
|
|
76
|
+
"client_secret": credentials.client_secret,
|
|
77
|
+
"code": code,
|
|
78
|
+
}
|
|
79
|
+
empty: JsonObj = FrozenDict({})
|
|
80
|
+
encoded = urlencode(raw)
|
|
81
|
+
headers: dict[str, Primitive] = {
|
|
82
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
83
|
+
"Content-Length": str(len(encoded)),
|
|
84
|
+
}
|
|
85
|
+
client = HttpClientFactory.new_client(None, UnfoldedFactory.from_dict(headers), None)
|
|
86
|
+
return client.post(endpoint, Params(empty), Data(encoded))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _decode_refresh(result: Result[Response, RequestException]) -> ResultE[RefreshToken]:
|
|
90
|
+
response = Bug.assume_success(
|
|
91
|
+
"AuthRefreshError.response",
|
|
92
|
+
inspect.currentframe(),
|
|
93
|
+
(),
|
|
94
|
+
result.alt(cast_exception),
|
|
95
|
+
)
|
|
96
|
+
json_data = Bug.assume_success(
|
|
97
|
+
"AuthRefreshError.decode_json",
|
|
98
|
+
inspect.currentframe(),
|
|
99
|
+
(),
|
|
100
|
+
decode_json(response).alt(
|
|
101
|
+
lambda e: e.map(cast_exception, lambda f: f.map(cast_exception, cast_exception)),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
return _decode(json_data)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def generate_refresh_token(
|
|
108
|
+
credentials: Credentials,
|
|
109
|
+
) -> Cmd[RefreshToken]:
|
|
110
|
+
msg = Cmd.wrap_impure(
|
|
111
|
+
lambda: LOG.info(
|
|
112
|
+
"Generating refresh token with scopes: %s",
|
|
113
|
+
",".join(credentials.scopes),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
return msg + _user_input_code().bind(lambda c: _gen_refresh_token(credentials, c)).map(
|
|
117
|
+
lambda r: Bug.assume_success(
|
|
118
|
+
"AuthRefreshError.decode_token",
|
|
119
|
+
inspect.currentframe(),
|
|
120
|
+
(str(r),),
|
|
121
|
+
_decode_refresh(r),
|
|
122
|
+
),
|
|
123
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from getpass import (
|
|
4
|
+
getpass,
|
|
5
|
+
)
|
|
6
|
+
from urllib.parse import (
|
|
7
|
+
urlencode,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from fa_purity import (
|
|
11
|
+
Cmd,
|
|
12
|
+
FrozenDict,
|
|
13
|
+
cast_exception,
|
|
14
|
+
)
|
|
15
|
+
from fa_purity.json import (
|
|
16
|
+
JsonObj,
|
|
17
|
+
Primitive,
|
|
18
|
+
UnfoldedFactory,
|
|
19
|
+
)
|
|
20
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
21
|
+
from pure_requests.basic import (
|
|
22
|
+
Data,
|
|
23
|
+
Endpoint,
|
|
24
|
+
HttpClientFactory,
|
|
25
|
+
Params,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from ._core import (
|
|
29
|
+
RefreshToken,
|
|
30
|
+
)
|
|
31
|
+
from ._decode import (
|
|
32
|
+
decode_json,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
ACCOUNTS_URL = "https://accounts.zoho.com" # for US region
|
|
36
|
+
LOG = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _user_input() -> Cmd[RefreshToken]:
|
|
40
|
+
def _action() -> RefreshToken:
|
|
41
|
+
LOG.info("Refresh token to revoke:")
|
|
42
|
+
return RefreshToken(getpass())
|
|
43
|
+
|
|
44
|
+
return Cmd.wrap_impure(_action)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _revoke_refresh_token(token: RefreshToken) -> Cmd[JsonObj]:
|
|
48
|
+
endpoint = Endpoint(f"{ACCOUNTS_URL}/oauth/v2/token/revoke")
|
|
49
|
+
empty: JsonObj = FrozenDict({})
|
|
50
|
+
raw: dict[str, Primitive] = {
|
|
51
|
+
"token": token.raw_token,
|
|
52
|
+
}
|
|
53
|
+
encoded = urlencode(raw)
|
|
54
|
+
headers: dict[str, Primitive] = {
|
|
55
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
56
|
+
"Content-Length": str(len(encoded)),
|
|
57
|
+
}
|
|
58
|
+
client = HttpClientFactory.new_client(None, UnfoldedFactory.from_dict(headers), None)
|
|
59
|
+
return (
|
|
60
|
+
client.post(
|
|
61
|
+
endpoint,
|
|
62
|
+
Params(empty),
|
|
63
|
+
Data(encoded),
|
|
64
|
+
)
|
|
65
|
+
.map(
|
|
66
|
+
lambda r: Bug.assume_success(
|
|
67
|
+
"AuthRevokeError.response",
|
|
68
|
+
inspect.currentframe(),
|
|
69
|
+
(endpoint.raw, str(token)),
|
|
70
|
+
r.alt(cast_exception),
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
.map(
|
|
74
|
+
lambda r: Bug.assume_success(
|
|
75
|
+
"AuthRevokeError.decode",
|
|
76
|
+
inspect.currentframe(),
|
|
77
|
+
(endpoint.raw, str(token), str(r), str(decode_json(r))),
|
|
78
|
+
decode_json(r).alt(
|
|
79
|
+
lambda e: e.map(
|
|
80
|
+
cast_exception,
|
|
81
|
+
lambda x: x.map(cast_exception, cast_exception),
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def revoke_refresh_token() -> Cmd[JsonObj]:
|
|
90
|
+
return (
|
|
91
|
+
_user_input()
|
|
92
|
+
.bind(_revoke_refresh_token)
|
|
93
|
+
.bind(
|
|
94
|
+
lambda j: Cmd.wrap_impure(lambda: LOG.debug("revoke_refresh_token: %s", j)).map(
|
|
95
|
+
lambda _: j,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from fa_purity import Maybe
|
|
4
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class UserId:
|
|
9
|
+
user_id: Natural
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class AccountId:
|
|
14
|
+
accound_id: Natural
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class CrmId:
|
|
19
|
+
crm_id: Natural
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class DeparmentId:
|
|
24
|
+
id_deparment: Maybe[Natural]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class TeamId:
|
|
29
|
+
id_team: Natural
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class AgentId:
|
|
34
|
+
agent_id: Natural
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class ProfileId:
|
|
39
|
+
id_profile: Natural
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class RoleId:
|
|
44
|
+
id_role: Natural
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class ProductId:
|
|
49
|
+
id_product: Maybe[Natural]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class TicketId:
|
|
54
|
+
id_ticket: Natural
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class ContactId:
|
|
59
|
+
id_contact: Natural
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from fa_purity import ResultE
|
|
2
|
+
from fa_purity.json import (
|
|
3
|
+
JsonObj,
|
|
4
|
+
JsonUnfolder,
|
|
5
|
+
)
|
|
6
|
+
from fluidattacks_etl_utils import smash
|
|
7
|
+
from fluidattacks_etl_utils.decode import DecodeUtils
|
|
8
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
9
|
+
|
|
10
|
+
from fluidattacks_zoho_sdk._decoders import (
|
|
11
|
+
decode_account_id,
|
|
12
|
+
decode_account_id_ticket,
|
|
13
|
+
decode_contact_id,
|
|
14
|
+
decode_crm_id,
|
|
15
|
+
decode_deparment_id,
|
|
16
|
+
decode_maybe_str,
|
|
17
|
+
decode_optional_date,
|
|
18
|
+
decode_product_id,
|
|
19
|
+
decode_profile_id,
|
|
20
|
+
decode_require_date,
|
|
21
|
+
decode_rol_id,
|
|
22
|
+
decode_team_id,
|
|
23
|
+
decode_ticket_id,
|
|
24
|
+
decode_user_id,
|
|
25
|
+
)
|
|
26
|
+
from fluidattacks_zoho_sdk.ids import UserId
|
|
27
|
+
from fluidattacks_zoho_sdk.zoho_desk.core import (
|
|
28
|
+
AgentObj,
|
|
29
|
+
ContactAddres,
|
|
30
|
+
ContactDates,
|
|
31
|
+
ContactInfo,
|
|
32
|
+
ContactObj,
|
|
33
|
+
TicketDates,
|
|
34
|
+
TicketObj,
|
|
35
|
+
TicketProperties,
|
|
36
|
+
UserObj,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def decode_user(raw: JsonObj) -> ResultE[UserObj]:
|
|
41
|
+
return smash.smash_result_3(
|
|
42
|
+
decode_user_id(raw),
|
|
43
|
+
JsonUnfolder.require(raw, "firstName", DecodeUtils.to_str),
|
|
44
|
+
JsonUnfolder.require(raw, "lastName", DecodeUtils.to_str),
|
|
45
|
+
).map(lambda obj: UserObj(*obj))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def decode_agent(raw: JsonObj) -> ResultE[AgentObj]:
|
|
49
|
+
values = smash.smash_result_5(
|
|
50
|
+
decode_user(raw),
|
|
51
|
+
JsonUnfolder.require(raw, "isConfirmed", DecodeUtils.to_bool),
|
|
52
|
+
JsonUnfolder.require(raw, "emailId", DecodeUtils.to_str),
|
|
53
|
+
decode_profile_id(raw),
|
|
54
|
+
decode_rol_id(raw),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
smash.bind_chain(
|
|
59
|
+
values,
|
|
60
|
+
lambda vals: JsonUnfolder.require(raw, "status", DecodeUtils.to_str).map(
|
|
61
|
+
lambda status: (*vals, status),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
.alt(lambda v: v.map(lambda le: le, lambda r: r))
|
|
65
|
+
.map(lambda obj: AgentObj(*obj))
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def decode_contact_addres(raw: JsonObj) -> ResultE[ContactAddres]:
|
|
70
|
+
return smash.smash_result_3(
|
|
71
|
+
JsonUnfolder.optional(raw, "city", DecodeUtils.to_opt_str).map(
|
|
72
|
+
lambda v: v.bind(lambda x: x),
|
|
73
|
+
),
|
|
74
|
+
JsonUnfolder.optional(raw, "country", DecodeUtils.to_opt_str).map(
|
|
75
|
+
lambda v: v.bind(lambda x: x),
|
|
76
|
+
),
|
|
77
|
+
JsonUnfolder.optional(raw, "street", DecodeUtils.to_opt_str).map(
|
|
78
|
+
lambda v: v.bind(lambda x: x),
|
|
79
|
+
),
|
|
80
|
+
).map(lambda obj: ContactAddres(*obj))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def decode_contact_dates(raw: JsonObj) -> ResultE[ContactDates]:
|
|
84
|
+
return smash.smash_result_2(
|
|
85
|
+
JsonUnfolder.require(raw, "createdTime", DecodeUtils.to_date_time),
|
|
86
|
+
JsonUnfolder.require(raw, "modifiedTime", DecodeUtils.to_date_time),
|
|
87
|
+
).map(lambda obj: ContactDates(*obj))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def decode_contact_info(raw: JsonObj) -> ResultE[ContactInfo]:
|
|
91
|
+
return smash.smash_result_5(
|
|
92
|
+
JsonUnfolder.require(raw, "email", DecodeUtils.to_str),
|
|
93
|
+
JsonUnfolder.optional(raw, "facebook", DecodeUtils.to_opt_str).map(
|
|
94
|
+
lambda v: v.bind(lambda x: x),
|
|
95
|
+
),
|
|
96
|
+
JsonUnfolder.require(raw, "phone", DecodeUtils.to_str),
|
|
97
|
+
JsonUnfolder.optional(raw, "mobile", DecodeUtils.to_opt_str).map(
|
|
98
|
+
lambda v: v.bind(lambda x: x),
|
|
99
|
+
),
|
|
100
|
+
JsonUnfolder.optional(raw, "secondaryEmail", DecodeUtils.to_opt_str).map(
|
|
101
|
+
lambda v: v.bind(lambda x: x),
|
|
102
|
+
),
|
|
103
|
+
).map(lambda obj: ContactInfo(*obj))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def decode_contact_obj(raw: JsonObj) -> ResultE[ContactObj]:
|
|
107
|
+
first = smash.smash_result_5(
|
|
108
|
+
decode_user(raw),
|
|
109
|
+
decode_contact_info(raw),
|
|
110
|
+
decode_contact_addres(raw),
|
|
111
|
+
decode_contact_dates(raw),
|
|
112
|
+
decode_account_id(raw),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
second = smash.smash_result_4(
|
|
116
|
+
JsonUnfolder.require(raw, "ownerId", DecodeUtils.to_str)
|
|
117
|
+
.bind(lambda v: Natural.from_int(int(v)))
|
|
118
|
+
.map(lambda j: UserId(j)),
|
|
119
|
+
decode_crm_id(raw),
|
|
120
|
+
JsonUnfolder.optional(raw, "description", DecodeUtils.to_opt_str).map(
|
|
121
|
+
lambda v: v.bind(lambda j: j),
|
|
122
|
+
),
|
|
123
|
+
JsonUnfolder.optional(raw, "state", DecodeUtils.to_opt_str).map(
|
|
124
|
+
lambda v: v.bind(lambda j: j),
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
three = smash.smash_result_3(
|
|
129
|
+
JsonUnfolder.optional(raw, "title", DecodeUtils.to_opt_str).map(
|
|
130
|
+
lambda v: v.bind(lambda j: j),
|
|
131
|
+
),
|
|
132
|
+
JsonUnfolder.optional(raw, "type", DecodeUtils.to_opt_str).map(
|
|
133
|
+
lambda v: v.bind(lambda j: j),
|
|
134
|
+
),
|
|
135
|
+
JsonUnfolder.optional(raw, "zip", DecodeUtils.to_opt_str).map(
|
|
136
|
+
lambda v: v.bind(lambda j: j),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return smash.smash_result_3(first, second, three).map(lambda v: ContactObj(*v[0], *v[1], *v[2]))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def decode_ticket_dates(raw: JsonObj) -> ResultE[TicketDates]:
|
|
144
|
+
return smash.smash_result_4(
|
|
145
|
+
decode_optional_date(raw, "modifiedTime"),
|
|
146
|
+
decode_require_date(raw, "createdTime"),
|
|
147
|
+
decode_optional_date(raw, "closedTime"),
|
|
148
|
+
decode_optional_date(raw, "customerResponseTime"),
|
|
149
|
+
).map(lambda v: TicketDates(*v))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def decode_ticket_properties(raw: JsonObj) -> ResultE[TicketProperties]:
|
|
153
|
+
first = smash.smash_result_5(
|
|
154
|
+
decode_ticket_id(raw),
|
|
155
|
+
JsonUnfolder.require(raw, "ticketNumber", DecodeUtils.to_str),
|
|
156
|
+
decode_maybe_str(raw, "subject"),
|
|
157
|
+
decode_maybe_str(raw, "channel"),
|
|
158
|
+
decode_maybe_str(raw, "status"),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
second = smash.smash_result_4(
|
|
162
|
+
decode_maybe_str(raw, "category"),
|
|
163
|
+
JsonUnfolder.require(raw, "isEscalated", DecodeUtils.to_bool),
|
|
164
|
+
JsonUnfolder.require(raw, "priority", DecodeUtils.to_str),
|
|
165
|
+
decode_maybe_str(raw, "resolution"),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
three = smash.smash_result_2(
|
|
169
|
+
decode_maybe_str(raw, "clasification"),
|
|
170
|
+
decode_maybe_str(raw, "description"),
|
|
171
|
+
)
|
|
172
|
+
return smash.smash_result_3(first, second, three).map(
|
|
173
|
+
lambda v: TicketProperties(*v[0], *v[1], *v[2]),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def decode_ticket_obj(raw: JsonObj) -> ResultE[TicketObj]:
|
|
178
|
+
first = smash.smash_result_5(
|
|
179
|
+
decode_account_id_ticket(raw),
|
|
180
|
+
decode_deparment_id(raw),
|
|
181
|
+
decode_team_id(raw),
|
|
182
|
+
decode_product_id(raw),
|
|
183
|
+
decode_ticket_dates(raw),
|
|
184
|
+
)
|
|
185
|
+
second = smash.smash_result_5(
|
|
186
|
+
decode_ticket_properties(raw),
|
|
187
|
+
decode_contact_id(raw),
|
|
188
|
+
JsonUnfolder.require(raw, "email", DecodeUtils.to_str),
|
|
189
|
+
JsonUnfolder.require(raw, "phone", DecodeUtils.to_str),
|
|
190
|
+
decode_maybe_str(raw, "onholdTime"),
|
|
191
|
+
)
|
|
192
|
+
return smash.smash_result_2(first, second).map(lambda v: TicketObj(*v[0], *v[1]))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from fa_purity import Maybe
|
|
4
|
+
from fa_purity.date_time import DatetimeUTC
|
|
5
|
+
|
|
6
|
+
from fluidattacks_zoho_sdk.ids import (
|
|
7
|
+
AccountId,
|
|
8
|
+
ContactId,
|
|
9
|
+
CrmId,
|
|
10
|
+
DeparmentId,
|
|
11
|
+
ProductId,
|
|
12
|
+
ProfileId,
|
|
13
|
+
RoleId,
|
|
14
|
+
TeamId,
|
|
15
|
+
TicketId,
|
|
16
|
+
UserId,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class UserObj:
|
|
22
|
+
id_user: UserId
|
|
23
|
+
first_name: str
|
|
24
|
+
last_name: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class AgentObj:
|
|
29
|
+
user: UserObj
|
|
30
|
+
is_confirmed: bool
|
|
31
|
+
email: str
|
|
32
|
+
profile: ProfileId
|
|
33
|
+
role: RoleId
|
|
34
|
+
status: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class ContactDates:
|
|
39
|
+
created_time: DatetimeUTC
|
|
40
|
+
modified_time: DatetimeUTC
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class ContactInfo:
|
|
45
|
+
email: str
|
|
46
|
+
facebook: Maybe[str]
|
|
47
|
+
phone: str
|
|
48
|
+
mobile: Maybe[str]
|
|
49
|
+
secondary_email: Maybe[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class ContactAddres:
|
|
54
|
+
city: Maybe[str]
|
|
55
|
+
country: Maybe[str]
|
|
56
|
+
street: Maybe[str]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class ContactObj:
|
|
61
|
+
user: UserObj
|
|
62
|
+
contact_info: ContactInfo
|
|
63
|
+
contact_addres: ContactAddres
|
|
64
|
+
contact_dates: ContactDates
|
|
65
|
+
account_id: AccountId
|
|
66
|
+
contact_owner: UserId
|
|
67
|
+
crm_id: CrmId
|
|
68
|
+
description: Maybe[str]
|
|
69
|
+
state: Maybe[str]
|
|
70
|
+
title: Maybe[str]
|
|
71
|
+
type_contact: Maybe[str]
|
|
72
|
+
zip_contact: Maybe[str]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class TeamObj:
|
|
77
|
+
id_deparment: DeparmentId
|
|
78
|
+
id_team: TeamId
|
|
79
|
+
description: str
|
|
80
|
+
name: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class TicketDates:
|
|
85
|
+
modified_time: Maybe[DatetimeUTC]
|
|
86
|
+
created_time: DatetimeUTC
|
|
87
|
+
closed_time: Maybe[DatetimeUTC]
|
|
88
|
+
customer_response_time: Maybe[DatetimeUTC]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class TicketProperties:
|
|
93
|
+
id_ticket: TicketId
|
|
94
|
+
ticket_number: str
|
|
95
|
+
suject: Maybe[str]
|
|
96
|
+
channel: Maybe[str]
|
|
97
|
+
status: Maybe[str]
|
|
98
|
+
category: Maybe[str]
|
|
99
|
+
is_escalated: bool
|
|
100
|
+
priority: str
|
|
101
|
+
resolution: Maybe[str]
|
|
102
|
+
classification: Maybe[str]
|
|
103
|
+
description: Maybe[str]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class TicketObj:
|
|
108
|
+
account_id: AccountId
|
|
109
|
+
deparment_id: DeparmentId
|
|
110
|
+
team_id: TeamId
|
|
111
|
+
product_id: ProductId
|
|
112
|
+
ticket_date: TicketDates
|
|
113
|
+
ticket_properties: TicketProperties
|
|
114
|
+
contact_id: ContactId
|
|
115
|
+
email: str
|
|
116
|
+
phone: str
|
|
117
|
+
onhold_time: Maybe[str]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fluidattacks_zoho_sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: zoho SDK
|
|
5
|
+
Author-email: Product Team <development@fluidattacks.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: fa-purity >=2.5.0, <3.0.0
|
|
8
|
+
Requires-Dist: fluidattacks-etl-utils >=1.0.0, <2.0.0
|
|
9
|
+
Requires-Dist: pure-requests >=3.0.0, <4.0.0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
fluidattacks_zoho_sdk/__init__.py,sha256=gJQK6zYoY-Lb4Z7_DeCB5U1U7YvfKqoGLqnpLTK65cA,146
|
|
2
|
+
fluidattacks_zoho_sdk/_decoders.py,sha256=-el9TJI9n6pE2bmwuYK22V9iZa14QyKclw6SkbPpB_g,5272
|
|
3
|
+
fluidattacks_zoho_sdk/ids.py,sha256=mWqttCK4__D5TWayl_vf7q_H8zHSxSCcYuAIqD6K2sA,837
|
|
4
|
+
fluidattacks_zoho_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
fluidattacks_zoho_sdk/auth/__init__.py,sha256=jHqC0muxwU-loTAtd4n94tyoBUfRjXuLHKyePWxZjzE,901
|
|
6
|
+
fluidattacks_zoho_sdk/auth/_access.py,sha256=JTF8qUvTMvkwDOAn_m-vIyssteEzOM_INR_sxWoQ_24,2229
|
|
7
|
+
fluidattacks_zoho_sdk/auth/_core.py,sha256=-r-yYX6M5ocNjqnYJkZEO8gLLiyPg-bWGU4wrdCVl7I,738
|
|
8
|
+
fluidattacks_zoho_sdk/auth/_decode.py,sha256=oYxxSABGOrVGUHiWGhGTmKMqHTCiw_PR-EVymAKly3Q,1552
|
|
9
|
+
fluidattacks_zoho_sdk/auth/_refresh.py,sha256=Tgfy7K4SbuGW4uFB-kWNJAa8pdz4rjs0vV-WKSP6sRg,3031
|
|
10
|
+
fluidattacks_zoho_sdk/auth/_revoke.py,sha256=Urv3fY0kOSAKArImz-C5EDIe44cm_L5ZNjkYxIWqL2E,2412
|
|
11
|
+
fluidattacks_zoho_sdk/zoho_desk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
fluidattacks_zoho_sdk/zoho_desk/_decode.py,sha256=E7nthNHKHtsVgnPtnhU1xahCs2Aza6ajPO2jjUNhkTU,6387
|
|
13
|
+
fluidattacks_zoho_sdk/zoho_desk/core.py,sha256=3ItHmjEGhl86G0sF1X440KbwWtIKB0-dGIiSjVxfrcA,2215
|
|
14
|
+
fluidattacks_zoho_sdk-1.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
15
|
+
fluidattacks_zoho_sdk-1.0.0.dist-info/METADATA,sha256=wGlydBuRr-CcAsCF0W3QagY3qrmK02tLP-7SLdMVEDM,305
|
|
16
|
+
fluidattacks_zoho_sdk-1.0.0.dist-info/RECORD,,
|