applika-cli 0.1.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.
- applika/__init__.py +0 -0
- applika/app.py +57 -0
- applika/commands/__init__.py +0 -0
- applika/commands/applications/__init__.py +25 -0
- applika/commands/applications/commands.py +405 -0
- applika/commands/applications/filter.py +73 -0
- applika/commands/applications/payloads.py +213 -0
- applika/commands/auth.py +93 -0
- applika/commands/skill.py +149 -0
- applika/config.py +27 -0
- applika/lib/__init__.py +0 -0
- applika/lib/api.py +168 -0
- applika/lib/loopback.py +85 -0
- applika/lib/session.py +59 -0
- applika/main.py +5 -0
- applika/schemas/__init__.py +0 -0
- applika/schemas/application.py +74 -0
- applika/schemas/enums.py +58 -0
- applika/skills/applika-cli/SKILL.md +243 -0
- applika/utils/__init__.py +0 -0
- applika/utils/dates.py +10 -0
- applika/utils/output.py +54 -0
- applika_cli-0.1.0.dist-info/METADATA +340 -0
- applika_cli-0.1.0.dist-info/RECORD +26 -0
- applika_cli-0.1.0.dist-info/WHEEL +4 -0
- applika_cli-0.1.0.dist-info/entry_points.txt +2 -0
applika/__init__.py
ADDED
|
File without changes
|
applika/app.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from applika.commands.applications import applications_app
|
|
9
|
+
from applika.commands.auth import login, logout, whoami
|
|
10
|
+
from applika.commands.skill import skill
|
|
11
|
+
from applika.config import AppConfig, resolve_api_base_url
|
|
12
|
+
from applika.lib.session import SessionStore
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
name='applika',
|
|
16
|
+
help='Job application tracker CLI for Applika.dev.',
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
app.add_typer(applications_app, name='applications')
|
|
21
|
+
app.command('skill')(skill)
|
|
22
|
+
app.command('login')(login)
|
|
23
|
+
app.command('logout')(logout)
|
|
24
|
+
app.command('whoami')(whoami)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.callback()
|
|
28
|
+
def _root(
|
|
29
|
+
ctx: typer.Context,
|
|
30
|
+
api_base_url: Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
typer.Option(
|
|
33
|
+
'--api-base-url',
|
|
34
|
+
help='Override the API base URL (default: https://applika.dev/api).',
|
|
35
|
+
envvar='APPLIKA_API_BASE_URL',
|
|
36
|
+
show_default=False,
|
|
37
|
+
),
|
|
38
|
+
] = None,
|
|
39
|
+
_version: Annotated[
|
|
40
|
+
bool,
|
|
41
|
+
typer.Option(
|
|
42
|
+
'--version',
|
|
43
|
+
'-v',
|
|
44
|
+
help='Show the version and exit.',
|
|
45
|
+
is_eager=True,
|
|
46
|
+
),
|
|
47
|
+
] = False,
|
|
48
|
+
) -> None:
|
|
49
|
+
if _version:
|
|
50
|
+
typer.echo(version('applika-cli'))
|
|
51
|
+
raise typer.Exit()
|
|
52
|
+
store = SessionStore()
|
|
53
|
+
ctx.ensure_object(dict)
|
|
54
|
+
ctx.obj = AppConfig(
|
|
55
|
+
api_base_url=resolve_api_base_url(api_base_url, store),
|
|
56
|
+
store=store,
|
|
57
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from applika.commands.applications.commands import (
|
|
4
|
+
edit_application,
|
|
5
|
+
list_applications,
|
|
6
|
+
new_application,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
applications_app = typer.Typer(
|
|
10
|
+
name='applications',
|
|
11
|
+
help='Manage job applications.',
|
|
12
|
+
no_args_is_help=True,
|
|
13
|
+
invoke_without_command=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@applications_app.callback()
|
|
18
|
+
def _default(ctx: typer.Context) -> None:
|
|
19
|
+
if ctx.invoked_subcommand is None:
|
|
20
|
+
ctx.invoke(list_applications)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
applications_app.command('list')(list_applications)
|
|
24
|
+
applications_app.command('new')(new_application)
|
|
25
|
+
applications_app.command('edit')(edit_application)
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from applika.config import AppConfig
|
|
10
|
+
from applika.lib.api import ApiClient, require_session
|
|
11
|
+
from applika.schemas.application import ApplicationCreate, ApplicationUpdate
|
|
12
|
+
from applika.schemas.enums import (
|
|
13
|
+
ApplicationMode,
|
|
14
|
+
Currency,
|
|
15
|
+
ExperienceLevel,
|
|
16
|
+
ModeFilter,
|
|
17
|
+
OutputFormat,
|
|
18
|
+
SalaryPeriod,
|
|
19
|
+
StatusFilter,
|
|
20
|
+
WorkMode,
|
|
21
|
+
)
|
|
22
|
+
from applika.utils.output import (
|
|
23
|
+
print_application_summary,
|
|
24
|
+
render_application_table,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .filter import filter_applications
|
|
28
|
+
from .payloads import ApplicationArgs, build_application_payload
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def list_applications(
|
|
32
|
+
ctx: typer.Context,
|
|
33
|
+
cycle_id: Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option('--cycle-id', help='Filter by cycle ID.'),
|
|
36
|
+
] = None,
|
|
37
|
+
search: Annotated[
|
|
38
|
+
str | None,
|
|
39
|
+
typer.Option(
|
|
40
|
+
'--search',
|
|
41
|
+
help='Search in company name or role (case-insensitive).',
|
|
42
|
+
),
|
|
43
|
+
] = None,
|
|
44
|
+
mode: Annotated[
|
|
45
|
+
ModeFilter,
|
|
46
|
+
typer.Option('--mode', help='Filter by application mode.'),
|
|
47
|
+
] = ModeFilter.ALL,
|
|
48
|
+
status: Annotated[
|
|
49
|
+
StatusFilter,
|
|
50
|
+
typer.Option('--status', help='Filter by application status.'),
|
|
51
|
+
] = StatusFilter.ALL,
|
|
52
|
+
platform: Annotated[
|
|
53
|
+
str | None,
|
|
54
|
+
typer.Option(
|
|
55
|
+
'--platform', help='Filter by platform name (e.g. LinkedIn).'
|
|
56
|
+
),
|
|
57
|
+
] = None,
|
|
58
|
+
from_date: Annotated[
|
|
59
|
+
str | None,
|
|
60
|
+
typer.Option(
|
|
61
|
+
'--from',
|
|
62
|
+
help='Include applications from this date (YYYY-MM-DD, inclusive).',
|
|
63
|
+
),
|
|
64
|
+
] = None,
|
|
65
|
+
to_date: Annotated[
|
|
66
|
+
str | None,
|
|
67
|
+
typer.Option(
|
|
68
|
+
'--to',
|
|
69
|
+
help='Include applications up to this date (YYYY-MM-DD, inclusive).',
|
|
70
|
+
),
|
|
71
|
+
] = None,
|
|
72
|
+
output_format: Annotated[
|
|
73
|
+
OutputFormat,
|
|
74
|
+
typer.Option(
|
|
75
|
+
'--output-format', help='Output format: table (default) or json.'
|
|
76
|
+
),
|
|
77
|
+
] = OutputFormat.TABLE,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""List job applications with optional filters, sorted by date descending."""
|
|
80
|
+
config: AppConfig = ctx.obj
|
|
81
|
+
session = require_session(config.store)
|
|
82
|
+
client = ApiClient(session, config.store)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
params = {'cycle_id': cycle_id} if cycle_id else None
|
|
86
|
+
applications = client.get_json('/applications', params=params)
|
|
87
|
+
supports = client.get_json('/supports')
|
|
88
|
+
filtered = filter_applications(
|
|
89
|
+
applications,
|
|
90
|
+
supports,
|
|
91
|
+
search=search,
|
|
92
|
+
mode=str(mode),
|
|
93
|
+
status=str(status),
|
|
94
|
+
platform=platform,
|
|
95
|
+
from_date=from_date,
|
|
96
|
+
to_date=to_date,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if output_format == OutputFormat.JSON:
|
|
100
|
+
print(json.dumps(filtered, indent=2, sort_keys=True))
|
|
101
|
+
else:
|
|
102
|
+
render_application_table(filtered, supports)
|
|
103
|
+
finally:
|
|
104
|
+
client.close()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def new_application(
|
|
108
|
+
ctx: typer.Context,
|
|
109
|
+
company: Annotated[
|
|
110
|
+
str,
|
|
111
|
+
typer.Option('--company', help='Company name.'),
|
|
112
|
+
],
|
|
113
|
+
role: Annotated[
|
|
114
|
+
str,
|
|
115
|
+
typer.Option('--role', help='Job title or role.'),
|
|
116
|
+
],
|
|
117
|
+
platform: Annotated[
|
|
118
|
+
str,
|
|
119
|
+
typer.Option(
|
|
120
|
+
'--platform', help='Platform name (e.g. LinkedIn, Indeed).'
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
mode: Annotated[
|
|
124
|
+
ApplicationMode,
|
|
125
|
+
typer.Option('--mode', help='Application mode: active or passive.'),
|
|
126
|
+
],
|
|
127
|
+
application_date: Annotated[
|
|
128
|
+
str,
|
|
129
|
+
typer.Option('--date', help='Application date (YYYY-MM-DD).'),
|
|
130
|
+
],
|
|
131
|
+
company_url: Annotated[
|
|
132
|
+
str | None,
|
|
133
|
+
typer.Option('--company-url', help='Company website URL.'),
|
|
134
|
+
] = None,
|
|
135
|
+
job_url: Annotated[
|
|
136
|
+
str | None,
|
|
137
|
+
typer.Option('--job-url', help='Link to the job posting.'),
|
|
138
|
+
] = None,
|
|
139
|
+
observation: Annotated[
|
|
140
|
+
str | None,
|
|
141
|
+
typer.Option(
|
|
142
|
+
'--observation', help='Notes or observations about the application.'
|
|
143
|
+
),
|
|
144
|
+
] = None,
|
|
145
|
+
expected_salary: Annotated[
|
|
146
|
+
float | None,
|
|
147
|
+
typer.Option('--expected-salary', help='Expected salary amount.'),
|
|
148
|
+
] = None,
|
|
149
|
+
salary_min: Annotated[
|
|
150
|
+
float | None,
|
|
151
|
+
typer.Option('--salary-min', help='Minimum salary range.'),
|
|
152
|
+
] = None,
|
|
153
|
+
salary_max: Annotated[
|
|
154
|
+
float | None,
|
|
155
|
+
typer.Option('--salary-max', help='Maximum salary range.'),
|
|
156
|
+
] = None,
|
|
157
|
+
currency: Annotated[
|
|
158
|
+
Currency | None,
|
|
159
|
+
typer.Option(
|
|
160
|
+
'--currency', help='Salary currency (required when salary is set).'
|
|
161
|
+
),
|
|
162
|
+
] = None,
|
|
163
|
+
salary_period: Annotated[
|
|
164
|
+
SalaryPeriod | None,
|
|
165
|
+
typer.Option(
|
|
166
|
+
'--salary-period',
|
|
167
|
+
help='Salary period (required when salary is set).',
|
|
168
|
+
),
|
|
169
|
+
] = None,
|
|
170
|
+
experience_level: Annotated[
|
|
171
|
+
ExperienceLevel | None,
|
|
172
|
+
typer.Option('--experience-level', help='Required experience level.'),
|
|
173
|
+
] = None,
|
|
174
|
+
work_mode: Annotated[
|
|
175
|
+
WorkMode | None,
|
|
176
|
+
typer.Option(
|
|
177
|
+
'--work-mode', help='Work mode: remote, hybrid, or on_site.'
|
|
178
|
+
),
|
|
179
|
+
] = None,
|
|
180
|
+
country: Annotated[
|
|
181
|
+
str | None,
|
|
182
|
+
typer.Option('--country', help='Country where the job is located.'),
|
|
183
|
+
] = None,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Create a new job application."""
|
|
186
|
+
try:
|
|
187
|
+
ApplicationCreate(
|
|
188
|
+
company=company,
|
|
189
|
+
role=role,
|
|
190
|
+
platform=platform,
|
|
191
|
+
mode=mode,
|
|
192
|
+
application_date=application_date, # type: ignore[arg-type]
|
|
193
|
+
company_url=company_url,
|
|
194
|
+
job_url=job_url,
|
|
195
|
+
observation=observation,
|
|
196
|
+
expected_salary=expected_salary,
|
|
197
|
+
salary_min=salary_min,
|
|
198
|
+
salary_max=salary_max,
|
|
199
|
+
currency=currency,
|
|
200
|
+
salary_period=salary_period,
|
|
201
|
+
experience_level=experience_level,
|
|
202
|
+
work_mode=work_mode,
|
|
203
|
+
country=country,
|
|
204
|
+
)
|
|
205
|
+
except ValidationError as exc:
|
|
206
|
+
for err in exc.errors():
|
|
207
|
+
field = '.'.join(str(loc) for loc in err['loc'])
|
|
208
|
+
typer.echo(f'Error [{field}]: {err["msg"]}', err=True)
|
|
209
|
+
raise typer.Exit(1)
|
|
210
|
+
|
|
211
|
+
config: AppConfig = ctx.obj
|
|
212
|
+
session = require_session(config.store)
|
|
213
|
+
client = ApiClient(session, config.store)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
args = ApplicationArgs(
|
|
217
|
+
company=company,
|
|
218
|
+
company_url=company_url,
|
|
219
|
+
role=role,
|
|
220
|
+
platform=platform,
|
|
221
|
+
mode=str(mode),
|
|
222
|
+
application_date=application_date,
|
|
223
|
+
job_url=job_url,
|
|
224
|
+
observation=observation,
|
|
225
|
+
expected_salary=expected_salary,
|
|
226
|
+
salary_min=salary_min,
|
|
227
|
+
salary_max=salary_max,
|
|
228
|
+
currency=str(currency) if currency else None,
|
|
229
|
+
salary_period=str(salary_period) if salary_period else None,
|
|
230
|
+
experience_level=str(experience_level)
|
|
231
|
+
if experience_level
|
|
232
|
+
else None,
|
|
233
|
+
work_mode=str(work_mode) if work_mode else None,
|
|
234
|
+
country=country,
|
|
235
|
+
)
|
|
236
|
+
payload = build_application_payload(client, args, existing=None)
|
|
237
|
+
created = client.post_json('/applications', payload)
|
|
238
|
+
print_application_summary(created, 'Created application')
|
|
239
|
+
finally:
|
|
240
|
+
client.close()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def edit_application(
|
|
244
|
+
ctx: typer.Context,
|
|
245
|
+
application_id: Annotated[
|
|
246
|
+
str,
|
|
247
|
+
typer.Argument(help='ID of the application to edit.'),
|
|
248
|
+
],
|
|
249
|
+
company: Annotated[
|
|
250
|
+
str | None,
|
|
251
|
+
typer.Option('--company', help='New company name.'),
|
|
252
|
+
] = None,
|
|
253
|
+
role: Annotated[
|
|
254
|
+
str | None,
|
|
255
|
+
typer.Option('--role', help='New job title or role.'),
|
|
256
|
+
] = None,
|
|
257
|
+
platform: Annotated[
|
|
258
|
+
str | None,
|
|
259
|
+
typer.Option('--platform', help='New platform name.'),
|
|
260
|
+
] = None,
|
|
261
|
+
mode: Annotated[
|
|
262
|
+
ApplicationMode | None,
|
|
263
|
+
typer.Option('--mode', help='New application mode.'),
|
|
264
|
+
] = None,
|
|
265
|
+
application_date: Annotated[
|
|
266
|
+
str | None,
|
|
267
|
+
typer.Option('--date', help='New application date (YYYY-MM-DD).'),
|
|
268
|
+
] = None,
|
|
269
|
+
company_url: Annotated[
|
|
270
|
+
str | None,
|
|
271
|
+
typer.Option('--company-url', help='New company website URL.'),
|
|
272
|
+
] = None,
|
|
273
|
+
job_url: Annotated[
|
|
274
|
+
str | None,
|
|
275
|
+
typer.Option('--job-url', help='New link to the job posting.'),
|
|
276
|
+
] = None,
|
|
277
|
+
observation: Annotated[
|
|
278
|
+
str | None,
|
|
279
|
+
typer.Option('--observation', help='New notes or observations.'),
|
|
280
|
+
] = None,
|
|
281
|
+
expected_salary: Annotated[
|
|
282
|
+
float | None,
|
|
283
|
+
typer.Option('--expected-salary', help='New expected salary amount.'),
|
|
284
|
+
] = None,
|
|
285
|
+
salary_min: Annotated[
|
|
286
|
+
float | None,
|
|
287
|
+
typer.Option('--salary-min', help='New minimum salary range.'),
|
|
288
|
+
] = None,
|
|
289
|
+
salary_max: Annotated[
|
|
290
|
+
float | None,
|
|
291
|
+
typer.Option('--salary-max', help='New maximum salary range.'),
|
|
292
|
+
] = None,
|
|
293
|
+
currency: Annotated[
|
|
294
|
+
Currency | None,
|
|
295
|
+
typer.Option('--currency', help='New salary currency.'),
|
|
296
|
+
] = None,
|
|
297
|
+
salary_period: Annotated[
|
|
298
|
+
SalaryPeriod | None,
|
|
299
|
+
typer.Option('--salary-period', help='New salary period.'),
|
|
300
|
+
] = None,
|
|
301
|
+
experience_level: Annotated[
|
|
302
|
+
ExperienceLevel | None,
|
|
303
|
+
typer.Option(
|
|
304
|
+
'--experience-level', help='New required experience level.'
|
|
305
|
+
),
|
|
306
|
+
] = None,
|
|
307
|
+
work_mode: Annotated[
|
|
308
|
+
WorkMode | None,
|
|
309
|
+
typer.Option('--work-mode', help='New work mode.'),
|
|
310
|
+
] = None,
|
|
311
|
+
country: Annotated[
|
|
312
|
+
str | None,
|
|
313
|
+
typer.Option('--country', help='New country where the job is located.'),
|
|
314
|
+
] = None,
|
|
315
|
+
clear_job_url: Annotated[
|
|
316
|
+
bool,
|
|
317
|
+
typer.Option('--clear-job-url', help='Remove the job URL.'),
|
|
318
|
+
] = False,
|
|
319
|
+
clear_observation: Annotated[
|
|
320
|
+
bool,
|
|
321
|
+
typer.Option(
|
|
322
|
+
'--clear-observation', help='Remove the observation note.'
|
|
323
|
+
),
|
|
324
|
+
] = False,
|
|
325
|
+
clear_country: Annotated[
|
|
326
|
+
bool,
|
|
327
|
+
typer.Option('--clear-country', help='Remove the country.'),
|
|
328
|
+
] = False,
|
|
329
|
+
clear_salary: Annotated[
|
|
330
|
+
bool,
|
|
331
|
+
typer.Option('--clear-salary', help='Remove all salary fields.'),
|
|
332
|
+
] = False,
|
|
333
|
+
) -> None:
|
|
334
|
+
"""Edit an existing job application. Unspecified fields keep their current values."""
|
|
335
|
+
try:
|
|
336
|
+
ApplicationUpdate(
|
|
337
|
+
company=company,
|
|
338
|
+
role=role,
|
|
339
|
+
platform=platform,
|
|
340
|
+
mode=mode,
|
|
341
|
+
application_date=application_date, # type: ignore[arg-type]
|
|
342
|
+
company_url=company_url,
|
|
343
|
+
job_url=job_url,
|
|
344
|
+
observation=observation,
|
|
345
|
+
expected_salary=expected_salary,
|
|
346
|
+
salary_min=salary_min,
|
|
347
|
+
salary_max=salary_max,
|
|
348
|
+
currency=currency,
|
|
349
|
+
salary_period=salary_period,
|
|
350
|
+
experience_level=experience_level,
|
|
351
|
+
work_mode=work_mode,
|
|
352
|
+
country=country,
|
|
353
|
+
)
|
|
354
|
+
except ValidationError as exc:
|
|
355
|
+
for err in exc.errors():
|
|
356
|
+
field = '.'.join(str(loc) for loc in err['loc'])
|
|
357
|
+
typer.echo(f'Error [{field}]: {err["msg"]}', err=True)
|
|
358
|
+
raise typer.Exit(1)
|
|
359
|
+
|
|
360
|
+
config: AppConfig = ctx.obj
|
|
361
|
+
session = require_session(config.store)
|
|
362
|
+
client = ApiClient(session, config.store)
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
applications = client.get_json('/applications')
|
|
366
|
+
existing = next(
|
|
367
|
+
(app for app in applications if str(app['id']) == application_id),
|
|
368
|
+
None,
|
|
369
|
+
)
|
|
370
|
+
if existing is None:
|
|
371
|
+
typer.echo('Application not found in the current cycle', err=True)
|
|
372
|
+
raise typer.Exit(1)
|
|
373
|
+
if existing.get('finalized'):
|
|
374
|
+
typer.echo('Finalized applications cannot be edited', err=True)
|
|
375
|
+
raise typer.Exit(1)
|
|
376
|
+
|
|
377
|
+
args = ApplicationArgs(
|
|
378
|
+
company=company,
|
|
379
|
+
company_url=company_url,
|
|
380
|
+
role=role,
|
|
381
|
+
platform=platform,
|
|
382
|
+
mode=str(mode) if mode else None,
|
|
383
|
+
application_date=application_date,
|
|
384
|
+
job_url=job_url,
|
|
385
|
+
observation=observation,
|
|
386
|
+
expected_salary=expected_salary,
|
|
387
|
+
salary_min=salary_min,
|
|
388
|
+
salary_max=salary_max,
|
|
389
|
+
currency=str(currency) if currency else None,
|
|
390
|
+
salary_period=str(salary_period) if salary_period else None,
|
|
391
|
+
experience_level=str(experience_level)
|
|
392
|
+
if experience_level
|
|
393
|
+
else None,
|
|
394
|
+
work_mode=str(work_mode) if work_mode else None,
|
|
395
|
+
country=country,
|
|
396
|
+
clear_job_url=clear_job_url,
|
|
397
|
+
clear_observation=clear_observation,
|
|
398
|
+
clear_country=clear_country,
|
|
399
|
+
clear_salary=clear_salary,
|
|
400
|
+
)
|
|
401
|
+
payload = build_application_payload(client, args, existing=existing)
|
|
402
|
+
updated = client.put_json(f'/applications/{application_id}', payload)
|
|
403
|
+
print_application_summary(updated, 'Updated application')
|
|
404
|
+
finally:
|
|
405
|
+
client.close()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from applika.utils.dates import parse_date
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def filter_applications(
|
|
7
|
+
applications: list[dict[str, Any]],
|
|
8
|
+
supports: dict[str, Any],
|
|
9
|
+
*,
|
|
10
|
+
search: str | None = None,
|
|
11
|
+
mode: str = 'all',
|
|
12
|
+
status: str = 'all',
|
|
13
|
+
platform: str | None = None,
|
|
14
|
+
from_date: str | None = None,
|
|
15
|
+
to_date: str | None = None,
|
|
16
|
+
) -> list[dict[str, Any]]:
|
|
17
|
+
platform_id = None
|
|
18
|
+
if platform:
|
|
19
|
+
platform_id = resolve_platform_id(supports, platform)
|
|
20
|
+
|
|
21
|
+
filtered = []
|
|
22
|
+
search_term = (search or '').strip().lower()
|
|
23
|
+
date_from = parse_date(from_date) if from_date else None
|
|
24
|
+
date_to = parse_date(to_date) if to_date else None
|
|
25
|
+
|
|
26
|
+
for application in applications:
|
|
27
|
+
if search_term:
|
|
28
|
+
company_name = (application.get('company_name') or '').lower()
|
|
29
|
+
role = (application.get('role') or '').lower()
|
|
30
|
+
if search_term not in company_name and search_term not in role:
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if mode != 'all' and application.get('mode') != mode:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
if status == 'active' and application.get('finalized'):
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
if status == 'finalized' and not application.get('finalized'):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
if platform_id and str(application.get('platform_id')) != platform_id:
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
app_date = parse_date(application['application_date'])
|
|
46
|
+
if date_from and app_date < date_from:
|
|
47
|
+
continue
|
|
48
|
+
if date_to and app_date > date_to:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
filtered.append(application)
|
|
52
|
+
|
|
53
|
+
filtered.sort(key=lambda item: item['application_date'], reverse=True)
|
|
54
|
+
return filtered
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_platform_id(supports: dict[str, Any], platform_name: str) -> str:
|
|
58
|
+
normalized_name = platform_name.strip().lower()
|
|
59
|
+
match = next(
|
|
60
|
+
(
|
|
61
|
+
platform
|
|
62
|
+
for platform in supports['platforms']
|
|
63
|
+
if platform['name'].strip().lower() == normalized_name
|
|
64
|
+
),
|
|
65
|
+
None,
|
|
66
|
+
)
|
|
67
|
+
if match is None:
|
|
68
|
+
valid = ', '.join(
|
|
69
|
+
sorted(platform['name'] for platform in supports['platforms'])
|
|
70
|
+
)
|
|
71
|
+
raise ValueError(f'Unknown platform. Valid options: {valid}')
|
|
72
|
+
|
|
73
|
+
return str(match['id'])
|