vikunja-python 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.
- vikunja_python/__init__.py +0 -0
- vikunja_python/cli/__init__.py +0 -0
- vikunja_python/cli/main.py +264 -0
- vikunja_python/core/__init__.py +0 -0
- vikunja_python/core/client.py +106 -0
- vikunja_python/core/models/__init__.py +377 -0
- vikunja_python/core/models/api_token.py +131 -0
- vikunja_python/core/models/auth.py +34 -0
- vikunja_python/core/models/base.py +193 -0
- vikunja_python/core/models/bulk_assignees.py +98 -0
- vikunja_python/core/models/filter.py +134 -0
- vikunja_python/core/models/label.py +230 -0
- vikunja_python/core/models/link_sharing.py +138 -0
- vikunja_python/core/models/migration.py +404 -0
- vikunja_python/core/models/phase6_medium.py +74 -0
- vikunja_python/core/models/project.py +217 -0
- vikunja_python/core/models/relation.py +199 -0
- vikunja_python/core/models/task.py +261 -0
- vikunja_python/core/models/task_expansion.py +252 -0
- vikunja_python/core/models/user.py +838 -0
- vikunja_python/core/models/webhook.py +270 -0
- vikunja_python/mcp/__init__.py +0 -0
- vikunja_python/mcp/server.py +678 -0
- vikunja_python-0.1.0.dist-info/METADATA +16 -0
- vikunja_python-0.1.0.dist-info/RECORD +28 -0
- vikunja_python-0.1.0.dist-info/WHEEL +5 -0
- vikunja_python-0.1.0.dist-info/entry_points.txt +3 -0
- vikunja_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User and Team Models for Vikunja API
|
|
3
|
+
|
|
4
|
+
Covers: user.User, v1.UserSettings, models.ProjectUser, models.Team, etc.
|
|
5
|
+
API endpoints: /user, /teams, /projects/{id}/users
|
|
6
|
+
|
|
7
|
+
User Management:
|
|
8
|
+
- User profiles with settings
|
|
9
|
+
- Authentication (login, register, password reset)
|
|
10
|
+
- TOTP 2FA setup and verification
|
|
11
|
+
|
|
12
|
+
Team Management:
|
|
13
|
+
- Team creation and membership
|
|
14
|
+
- Project-team associations with permissions
|
|
15
|
+
- Team-based access control
|
|
16
|
+
|
|
17
|
+
Permission Levels (3 levels):
|
|
18
|
+
- 0 (Read): Read-only access
|
|
19
|
+
- 1 (Write): Read + Create/Update/Delete own items
|
|
20
|
+
- 2 (Admin): Full access including member management
|
|
21
|
+
|
|
22
|
+
Usage Examples:
|
|
23
|
+
# Login and get token
|
|
24
|
+
login = LoginRequest(username="alice", password="secret", long_token=True)
|
|
25
|
+
|
|
26
|
+
# Create a team
|
|
27
|
+
team = TeamCreateRequest(
|
|
28
|
+
name="Engineering",
|
|
29
|
+
description="Core engineering team",
|
|
30
|
+
is_public=True
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Add user to project with write permission
|
|
34
|
+
project_user = ProjectUserCreateRequest(
|
|
35
|
+
username="bob",
|
|
36
|
+
permission=Permission.WRITE # or 1
|
|
37
|
+
)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from pydantic import Field, field_validator
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from typing import Optional, Dict, Any, Literal
|
|
43
|
+
from .base import VikunjaBaseModel
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Permission Enum (3 levels)
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
Permission = Literal[0, 1, 2]
|
|
51
|
+
"""Permission level for project/team access.
|
|
52
|
+
|
|
53
|
+
API spec: models.Permission
|
|
54
|
+
|
|
55
|
+
Values:
|
|
56
|
+
0 (Read): Read-only access
|
|
57
|
+
1 (Write): Read + Create/Update/Delete own items
|
|
58
|
+
2 (Admin): Full access including member management
|
|
59
|
+
|
|
60
|
+
Usage:
|
|
61
|
+
from models.user import Permission
|
|
62
|
+
|
|
63
|
+
# Grant write access to a user
|
|
64
|
+
project_user = ProjectUserCreateRequest(
|
|
65
|
+
username="bob",
|
|
66
|
+
permission=Permission.WRITE # or simply 1
|
|
67
|
+
)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Named constants for clarity
|
|
71
|
+
Permission.READ: Permission = 0
|
|
72
|
+
Permission.WRITE: Permission = 1
|
|
73
|
+
Permission.ADMIN: Permission = 2
|
|
74
|
+
|
|
75
|
+
PERMISSION_DESCRIPTIONS: Dict[Permission, str] = {
|
|
76
|
+
0: "Read-only access - can view but not modify",
|
|
77
|
+
1: "Write access - can create, update, delete own items",
|
|
78
|
+
2: "Admin access - full control including member management",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ============================================================================
|
|
83
|
+
# Core User Models
|
|
84
|
+
# ============================================================================
|
|
85
|
+
|
|
86
|
+
class User(VikunjaBaseModel):
|
|
87
|
+
"""
|
|
88
|
+
Basic user information.
|
|
89
|
+
|
|
90
|
+
API endpoint: Referenced in many endpoints (assignees, created_by, etc.)
|
|
91
|
+
|
|
92
|
+
This is the minimal user object returned in nested contexts like task
|
|
93
|
+
assignees or label creators. For full user profiles, use UserWithSettings.
|
|
94
|
+
|
|
95
|
+
Fields from API spec (user.User):
|
|
96
|
+
- id: Unique user identifier
|
|
97
|
+
- username: Unique username (1-250 chars)
|
|
98
|
+
- name: Full display name
|
|
99
|
+
- email: User's email address (max 250 chars)
|
|
100
|
+
- created: Account creation timestamp
|
|
101
|
+
- updated: Last update timestamp
|
|
102
|
+
|
|
103
|
+
Example from API response:
|
|
104
|
+
{
|
|
105
|
+
"id": 1,
|
|
106
|
+
"username": "alice",
|
|
107
|
+
"name": "Alice Johnson",
|
|
108
|
+
"email": "alice@example.com",
|
|
109
|
+
"created": "2024-01-15T10:30:00Z",
|
|
110
|
+
"updated": "2024-01-20T14:45:00Z"
|
|
111
|
+
}
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# Core Identification (2 fields)
|
|
115
|
+
id: Optional[int] = Field(None, description="Unique user ID")
|
|
116
|
+
username: str = Field(
|
|
117
|
+
...,
|
|
118
|
+
min_length=1,
|
|
119
|
+
max_length=250,
|
|
120
|
+
description="Unique username"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Profile Information (2 fields)
|
|
124
|
+
name: Optional[str] = Field(None, description="Full display name")
|
|
125
|
+
email: Optional[str] = Field(
|
|
126
|
+
None,
|
|
127
|
+
max_length=250,
|
|
128
|
+
description="User's email address"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Timestamps (2 fields - auto-managed)
|
|
132
|
+
created: Optional[datetime] = Field(None, description="Account creation timestamp")
|
|
133
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class UserSettings(VikunjaBaseModel):
|
|
137
|
+
"""
|
|
138
|
+
User settings and preferences.
|
|
139
|
+
|
|
140
|
+
API endpoint: Part of UserWithSettings response
|
|
141
|
+
|
|
142
|
+
Contains user-specific configuration for the Vikunja instance.
|
|
143
|
+
|
|
144
|
+
Fields from API spec (v1.UserSettings):
|
|
145
|
+
- default_project_id: Default project for new tasks
|
|
146
|
+
- language: User's language preference
|
|
147
|
+
- timezone: Timezone for reminders and displays
|
|
148
|
+
- week_start: Day when week starts (0=Sunday, 1=Monday)
|
|
149
|
+
- discoverable_by_email/name: Privacy settings
|
|
150
|
+
- email_reminders_enabled: Email notification toggle
|
|
151
|
+
- overdue_tasks_reminders_enabled/Time: Daily summary settings
|
|
152
|
+
- extra_settings_links: OpenID-provided links
|
|
153
|
+
- frontend_settings: Frontend-specific settings
|
|
154
|
+
|
|
155
|
+
Example from API response:
|
|
156
|
+
{
|
|
157
|
+
"default_project_id": 1,
|
|
158
|
+
"language": "en",
|
|
159
|
+
"timezone": "America/New_York",
|
|
160
|
+
"week_start": 1,
|
|
161
|
+
"discoverable_by_email": true,
|
|
162
|
+
"discoverable_by_name": true,
|
|
163
|
+
"email_reminders_enabled": true,
|
|
164
|
+
"overdue_tasks_reminders_enabled": true,
|
|
165
|
+
"overdue_tasks_reminders_time": "09:00"
|
|
166
|
+
}
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
# Project Settings (1 field)
|
|
170
|
+
default_project_id: Optional[int] = Field(
|
|
171
|
+
None,
|
|
172
|
+
description="Default project ID for new tasks without specified project"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Localization (3 fields)
|
|
176
|
+
language: Optional[str] = Field(None, description="User's language preference")
|
|
177
|
+
timezone: Optional[str] = Field(None, description="Timezone for reminders/displays")
|
|
178
|
+
week_start: Optional[int] = Field(
|
|
179
|
+
None,
|
|
180
|
+
ge=0,
|
|
181
|
+
le=6,
|
|
182
|
+
description="Day when week starts (0=Sunday, 1=Monday, etc.)"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Privacy Settings (2 fields)
|
|
186
|
+
discoverable_by_email: Optional[bool] = Field(
|
|
187
|
+
None,
|
|
188
|
+
description="Can be found by searching exact email"
|
|
189
|
+
)
|
|
190
|
+
discoverable_by_name: Optional[bool] = Field(
|
|
191
|
+
None,
|
|
192
|
+
description="Can be found by searching name/partial name"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Notification Settings (3 fields)
|
|
196
|
+
email_reminders_enabled: Optional[bool] = Field(
|
|
197
|
+
None,
|
|
198
|
+
description="Enable email reminders for tasks"
|
|
199
|
+
)
|
|
200
|
+
overdue_tasks_reminders_enabled: Optional[bool] = Field(
|
|
201
|
+
None,
|
|
202
|
+
description="Enable daily overdue task summaries"
|
|
203
|
+
)
|
|
204
|
+
overdue_tasks_reminders_time: Optional[str] = Field(
|
|
205
|
+
None,
|
|
206
|
+
description="Time for daily overdue summary (e.g., '09:00')"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Extended Settings (2 fields)
|
|
210
|
+
extra_settings_links: Optional[Dict[str, Any]] = Field(
|
|
211
|
+
None,
|
|
212
|
+
description="Additional settings links from OpenID"
|
|
213
|
+
)
|
|
214
|
+
frontend_settings: Optional[Dict[str, Any]] = Field(
|
|
215
|
+
None,
|
|
216
|
+
description="Frontend-specific settings"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class UserWithSettings(VikunjaBaseModel):
|
|
221
|
+
"""
|
|
222
|
+
Full user profile with settings.
|
|
223
|
+
|
|
224
|
+
API endpoint: GET /user
|
|
225
|
+
|
|
226
|
+
Combines basic user info with their complete settings.
|
|
227
|
+
|
|
228
|
+
Fields from API spec (v1.UserWithSettings):
|
|
229
|
+
- All User fields (id, username, name, email, created, updated)
|
|
230
|
+
- settings: Complete UserSettings object
|
|
231
|
+
- auth_provider: Authentication provider type
|
|
232
|
+
- is_local_user: Whether user is locally authenticated
|
|
233
|
+
- deletion_scheduled_at: Account deletion timestamp (if scheduled)
|
|
234
|
+
|
|
235
|
+
Example from API response:
|
|
236
|
+
{
|
|
237
|
+
"id": 1,
|
|
238
|
+
"username": "alice",
|
|
239
|
+
"name": "Alice Johnson",
|
|
240
|
+
"email": "alice@example.com",
|
|
241
|
+
"created": "2024-01-15T10:30:00Z",
|
|
242
|
+
"updated": "2024-01-20T14:45:00Z",
|
|
243
|
+
"settings": {...},
|
|
244
|
+
"auth_provider": "local",
|
|
245
|
+
"is_local_user": true
|
|
246
|
+
}
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
# Core User Fields (6 fields)
|
|
250
|
+
id: Optional[int] = Field(None, description="Unique user ID")
|
|
251
|
+
username: str = Field(
|
|
252
|
+
...,
|
|
253
|
+
min_length=1,
|
|
254
|
+
max_length=250,
|
|
255
|
+
description="Unique username"
|
|
256
|
+
)
|
|
257
|
+
name: Optional[str] = Field(None, description="Full display name")
|
|
258
|
+
email: Optional[str] = Field(
|
|
259
|
+
None,
|
|
260
|
+
max_length=250,
|
|
261
|
+
description="User's email address"
|
|
262
|
+
)
|
|
263
|
+
created: Optional[datetime] = Field(None, description="Account creation timestamp")
|
|
264
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
265
|
+
|
|
266
|
+
# Settings (1 field)
|
|
267
|
+
settings: Optional[UserSettings] = Field(None, description="User settings and preferences")
|
|
268
|
+
|
|
269
|
+
# Authentication Info (3 fields)
|
|
270
|
+
auth_provider: Optional[str] = Field(None, description="Authentication provider type")
|
|
271
|
+
is_local_user: Optional[bool] = Field(None, description="Whether user is locally authenticated")
|
|
272
|
+
deletion_scheduled_at: Optional[datetime] = Field(
|
|
273
|
+
None,
|
|
274
|
+
description="Account deletion timestamp (if scheduled)"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ============================================================================
|
|
279
|
+
# Authentication Request Models
|
|
280
|
+
# ============================================================================
|
|
281
|
+
|
|
282
|
+
class LoginRequest(VikunjaBaseModel):
|
|
283
|
+
"""
|
|
284
|
+
Request body for user login.
|
|
285
|
+
|
|
286
|
+
API endpoint: POST /user/login
|
|
287
|
+
|
|
288
|
+
Required fields: username, password
|
|
289
|
+
Optional fields: totp_passcode (if 2FA enabled), long_token
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
req = LoginRequest(
|
|
293
|
+
username="alice",
|
|
294
|
+
password="secret123",
|
|
295
|
+
totp_passcode="123456", # If 2FA enabled
|
|
296
|
+
long_token=True # "Remember me" style login
|
|
297
|
+
)
|
|
298
|
+
"""
|
|
299
|
+
username: str = Field(..., description="Username for login")
|
|
300
|
+
password: str = Field(..., description="User's password")
|
|
301
|
+
totp_passcode: Optional[str] = Field(None, description="TOTP passcode if 2FA enabled")
|
|
302
|
+
long_token: Optional[bool] = Field(
|
|
303
|
+
None,
|
|
304
|
+
description="Long-lived token for 'remember me' style login"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def model_dump_for_api(self) -> dict:
|
|
308
|
+
"""Convert to dict for API submission."""
|
|
309
|
+
return self.model_dump(exclude_none=True)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class RegisterRequest(VikunjaBaseModel):
|
|
313
|
+
"""
|
|
314
|
+
Request body for user registration.
|
|
315
|
+
|
|
316
|
+
API endpoint: POST /user
|
|
317
|
+
|
|
318
|
+
Note: Full structure may vary based on Vikunja configuration.
|
|
319
|
+
This is a placeholder - verify with actual API response.
|
|
320
|
+
|
|
321
|
+
UNKNOWN: Full register request structure requires API testing
|
|
322
|
+
"""
|
|
323
|
+
# Placeholder - needs verification from actual API
|
|
324
|
+
username: Optional[str] = Field(None, description="Desired username")
|
|
325
|
+
email: Optional[str] = Field(None, description="User's email address")
|
|
326
|
+
password: Optional[str] = Field(None, description="Desired password")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class EmailUpdateRequest(VikunjaBaseModel):
|
|
330
|
+
"""
|
|
331
|
+
Request body for updating email address.
|
|
332
|
+
|
|
333
|
+
API endpoint: POST /user/email
|
|
334
|
+
|
|
335
|
+
Required fields: new_email, password (for confirmation)
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
req = EmailUpdateRequest(
|
|
339
|
+
new_email="alice@newdomain.com",
|
|
340
|
+
password="current_password"
|
|
341
|
+
)
|
|
342
|
+
"""
|
|
343
|
+
new_email: str = Field(..., description="New email address (must be valid)")
|
|
344
|
+
password: str = Field(..., description="Current password for confirmation")
|
|
345
|
+
|
|
346
|
+
def model_dump_for_api(self) -> dict:
|
|
347
|
+
"""Convert to dict for API submission."""
|
|
348
|
+
return self.model_dump(exclude_none=True)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class PasswordResetRequest(VikunjaBaseModel):
|
|
352
|
+
"""
|
|
353
|
+
Request body for password reset.
|
|
354
|
+
|
|
355
|
+
API endpoint: POST /user/password/reset
|
|
356
|
+
|
|
357
|
+
Required fields: token, new_password
|
|
358
|
+
|
|
359
|
+
Password requirements: 8-72 characters
|
|
360
|
+
|
|
361
|
+
Example:
|
|
362
|
+
req = PasswordResetRequest(
|
|
363
|
+
token="reset_token_from_email",
|
|
364
|
+
new_password="new_secure_password123"
|
|
365
|
+
)
|
|
366
|
+
"""
|
|
367
|
+
token: str = Field(..., description="Password reset token from email")
|
|
368
|
+
new_password: str = Field(
|
|
369
|
+
...,
|
|
370
|
+
min_length=8,
|
|
371
|
+
max_length=72,
|
|
372
|
+
description="New password (8-72 characters)"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
@field_validator('new_password')
|
|
376
|
+
@classmethod
|
|
377
|
+
def validate_password_strength(cls, v):
|
|
378
|
+
"""Basic password validation."""
|
|
379
|
+
if len(v) < 8:
|
|
380
|
+
raise ValueError("Password must be at least 8 characters")
|
|
381
|
+
return v
|
|
382
|
+
|
|
383
|
+
def model_dump_for_api(self) -> dict:
|
|
384
|
+
"""Convert to dict for API submission."""
|
|
385
|
+
return self.model_dump(exclude_none=True)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# ============================================================================
|
|
389
|
+
# TOTP (2FA) Models
|
|
390
|
+
# ============================================================================
|
|
391
|
+
|
|
392
|
+
class TOTPSetupResponse(VikunjaBaseModel):
|
|
393
|
+
"""
|
|
394
|
+
Response for TOTP setup initiation.
|
|
395
|
+
|
|
396
|
+
API endpoint: POST /user/totp
|
|
397
|
+
|
|
398
|
+
Returns the secret and URL needed to configure TOTP authenticator app.
|
|
399
|
+
|
|
400
|
+
Fields from API spec (models.TOTP):
|
|
401
|
+
- secret: TOTP secret key
|
|
402
|
+
- url: OTPAuth URL for QR code generation
|
|
403
|
+
- enabled: Whether TOTP is enabled (false until verified)
|
|
404
|
+
|
|
405
|
+
Example from API response:
|
|
406
|
+
{
|
|
407
|
+
"secret": "JBSWY3DPEHPK3PXP",
|
|
408
|
+
"url": "otpauth://totp/Vikunja:alice?secret=JBSWY3DPEHPK3PXP...",
|
|
409
|
+
"enabled": false
|
|
410
|
+
}
|
|
411
|
+
"""
|
|
412
|
+
secret: Optional[str] = Field(None, description="TOTP secret key")
|
|
413
|
+
url: Optional[str] = Field(None, description="OTPAuth URL for QR code")
|
|
414
|
+
enabled: Optional[bool] = Field(None, description="Whether TOTP is enabled")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class TOTPPasscodeRequest(VikunjaBaseModel):
|
|
418
|
+
"""
|
|
419
|
+
Request body for TOTP passcode verification.
|
|
420
|
+
|
|
421
|
+
API endpoint: POST /user/totp/verify
|
|
422
|
+
|
|
423
|
+
Required fields: passcode
|
|
424
|
+
|
|
425
|
+
Example:
|
|
426
|
+
req = TOTPPasscodeRequest(passcode="123456")
|
|
427
|
+
"""
|
|
428
|
+
passcode: str = Field(..., description="TOTP passcode from authenticator app")
|
|
429
|
+
|
|
430
|
+
def model_dump_for_api(self) -> dict:
|
|
431
|
+
"""Convert to dict for API submission."""
|
|
432
|
+
return self.model_dump(exclude_none=True)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# ============================================================================
|
|
436
|
+
# Project User Models (Project Membership)
|
|
437
|
+
# ============================================================================
|
|
438
|
+
|
|
439
|
+
class ProjectUser(VikunjaBaseModel):
|
|
440
|
+
"""
|
|
441
|
+
User's membership in a project with permission level.
|
|
442
|
+
|
|
443
|
+
API endpoint: GET /projects/{id}/users
|
|
444
|
+
|
|
445
|
+
Represents the relationship between a user and a project.
|
|
446
|
+
|
|
447
|
+
Fields from API spec (models.ProjectUser):
|
|
448
|
+
- id: Unique relation identifier
|
|
449
|
+
- username: Username of the member
|
|
450
|
+
- permission: Permission level (0=Read, 1=Write, 2=Admin)
|
|
451
|
+
- created: Relation creation timestamp
|
|
452
|
+
- updated: Last update timestamp
|
|
453
|
+
|
|
454
|
+
Example from API response:
|
|
455
|
+
{
|
|
456
|
+
"id": 5,
|
|
457
|
+
"username": "bob",
|
|
458
|
+
"permission": 1,
|
|
459
|
+
"created": "2024-01-15T10:30:00Z",
|
|
460
|
+
"updated": "2024-01-15T10:30:00Z"
|
|
461
|
+
}
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
# Core Identification (2 fields)
|
|
465
|
+
id: Optional[int] = Field(None, description="Unique relation ID")
|
|
466
|
+
username: str = Field(..., description="Username of the project member")
|
|
467
|
+
|
|
468
|
+
# Access Control (1 field)
|
|
469
|
+
permission: Optional[Permission] = Field(
|
|
470
|
+
0,
|
|
471
|
+
description="Permission level: 0=Read, 1=Write, 2=Admin"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Timestamps (2 fields - auto-managed)
|
|
475
|
+
created: Optional[datetime] = Field(None, description="Relation creation timestamp")
|
|
476
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class ProjectUserCreateRequest(VikunjaBaseModel):
|
|
480
|
+
"""
|
|
481
|
+
Request body for adding a user to a project.
|
|
482
|
+
|
|
483
|
+
API endpoint: POST /projects/{id}/users
|
|
484
|
+
|
|
485
|
+
Required fields: username
|
|
486
|
+
Optional fields: permission (defaults to 0 = Read)
|
|
487
|
+
|
|
488
|
+
Example:
|
|
489
|
+
req = ProjectUserCreateRequest(
|
|
490
|
+
username="bob",
|
|
491
|
+
permission=Permission.WRITE # or 1
|
|
492
|
+
)
|
|
493
|
+
"""
|
|
494
|
+
username: str = Field(..., description="Username to add to project")
|
|
495
|
+
permission: Optional[Permission] = Field(
|
|
496
|
+
0,
|
|
497
|
+
description="Permission level (default: 0=Read)"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
def model_dump_for_api(self) -> dict:
|
|
501
|
+
"""Convert to dict for API submission."""
|
|
502
|
+
return self.model_dump(exclude_none=True)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class ProjectUserUpdateRequest(VikunjaBaseModel):
|
|
506
|
+
"""
|
|
507
|
+
Request body for updating project membership.
|
|
508
|
+
|
|
509
|
+
API endpoint: POST /projects/{id}/users/{username}
|
|
510
|
+
|
|
511
|
+
Only provided fields are updated.
|
|
512
|
+
|
|
513
|
+
Example:
|
|
514
|
+
req = ProjectUserUpdateRequest(
|
|
515
|
+
permission=Permission.ADMIN # Promote to admin
|
|
516
|
+
)
|
|
517
|
+
"""
|
|
518
|
+
permission: Optional[Permission] = Field(None, description="New permission level")
|
|
519
|
+
|
|
520
|
+
def model_dump_for_api(self) -> dict:
|
|
521
|
+
"""Convert to dict, excluding None values."""
|
|
522
|
+
return self.model_dump(exclude_none=True)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# ============================================================================
|
|
526
|
+
# Team Models
|
|
527
|
+
# ============================================================================
|
|
528
|
+
|
|
529
|
+
class TeamUser(VikunjaBaseModel):
|
|
530
|
+
"""
|
|
531
|
+
User information within a team context.
|
|
532
|
+
|
|
533
|
+
API endpoint: Part of Team response (members array)
|
|
534
|
+
|
|
535
|
+
Similar to basic User but used specifically in team membership lists.
|
|
536
|
+
|
|
537
|
+
Fields from API spec (models.TeamUser):
|
|
538
|
+
- id: User ID
|
|
539
|
+
- username: Unique username
|
|
540
|
+
- name: Full display name
|
|
541
|
+
- email: User's email
|
|
542
|
+
- admin: Whether user is team admin
|
|
543
|
+
- created/updated: Timestamps
|
|
544
|
+
|
|
545
|
+
Example from API response:
|
|
546
|
+
{
|
|
547
|
+
"id": 1,
|
|
548
|
+
"username": "alice",
|
|
549
|
+
"name": "Alice Johnson",
|
|
550
|
+
"email": "alice@example.com",
|
|
551
|
+
"admin": true,
|
|
552
|
+
"created": "2024-01-15T10:30:00Z",
|
|
553
|
+
"updated": "2024-01-15T10:30:00Z"
|
|
554
|
+
}
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
# Core Identification (2 fields)
|
|
558
|
+
id: Optional[int] = Field(None, description="User ID")
|
|
559
|
+
username: str = Field(
|
|
560
|
+
...,
|
|
561
|
+
min_length=1,
|
|
562
|
+
max_length=250,
|
|
563
|
+
description="Unique username"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Profile Information (2 fields)
|
|
567
|
+
name: Optional[str] = Field(None, description="Full display name")
|
|
568
|
+
email: Optional[str] = Field(
|
|
569
|
+
None,
|
|
570
|
+
max_length=250,
|
|
571
|
+
description="User's email address"
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# Team Role (1 field)
|
|
575
|
+
admin: Optional[bool] = Field(
|
|
576
|
+
None,
|
|
577
|
+
description="Whether member is team admin"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
# Timestamps (2 fields - auto-managed)
|
|
581
|
+
created: Optional[datetime] = Field(None, description="Membership creation timestamp")
|
|
582
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class TeamMember(VikunjaBaseModel):
|
|
586
|
+
"""
|
|
587
|
+
Team membership relation.
|
|
588
|
+
|
|
589
|
+
API endpoint: GET /teams/{id}/members
|
|
590
|
+
|
|
591
|
+
Represents a user's membership in a team (without full user details).
|
|
592
|
+
|
|
593
|
+
Fields from API spec (models.TeamMember):
|
|
594
|
+
- id: Unique relation identifier
|
|
595
|
+
- username: Username of the member
|
|
596
|
+
- admin: Whether member is team admin
|
|
597
|
+
- created: Membership creation timestamp
|
|
598
|
+
|
|
599
|
+
Example from API response:
|
|
600
|
+
{
|
|
601
|
+
"id": 10,
|
|
602
|
+
"username": "bob",
|
|
603
|
+
"admin": false,
|
|
604
|
+
"created": "2024-01-15T10:30:00Z"
|
|
605
|
+
}
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
# Core Identification (2 fields)
|
|
609
|
+
id: Optional[int] = Field(None, description="Unique relation ID")
|
|
610
|
+
username: str = Field(
|
|
611
|
+
...,
|
|
612
|
+
min_length=1,
|
|
613
|
+
max_length=250,
|
|
614
|
+
description="Username of the team member"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Role (1 field)
|
|
618
|
+
admin: Optional[bool] = Field(None, description="Whether member is team admin")
|
|
619
|
+
|
|
620
|
+
# Timestamp (1 field - auto-managed)
|
|
621
|
+
created: Optional[datetime] = Field(None, description="Membership creation timestamp")
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class Team(VikunjaBaseModel):
|
|
625
|
+
"""
|
|
626
|
+
A team of users for collaborative access control.
|
|
627
|
+
|
|
628
|
+
API endpoint: GET /teams
|
|
629
|
+
|
|
630
|
+
Teams allow grouping users for shared project permissions.
|
|
631
|
+
|
|
632
|
+
Fields from API spec (models.Team):
|
|
633
|
+
- id: Unique team identifier
|
|
634
|
+
- name: Team name (1-250 chars)
|
|
635
|
+
- description: Optional team description
|
|
636
|
+
- is_public: Whether team is discoverable when sharing projects
|
|
637
|
+
- external_id: External ID from OpenID/LDAP provider
|
|
638
|
+
- members: Array of TeamUser objects
|
|
639
|
+
- created_by: User who created the team
|
|
640
|
+
- created/updated: Timestamps
|
|
641
|
+
|
|
642
|
+
Example from API response:
|
|
643
|
+
{
|
|
644
|
+
"id": 1,
|
|
645
|
+
"name": "Engineering",
|
|
646
|
+
"description": "Core engineering team",
|
|
647
|
+
"is_public": true,
|
|
648
|
+
"external_id": null,
|
|
649
|
+
"members": [...],
|
|
650
|
+
"created_by": {...},
|
|
651
|
+
"created": "2024-01-15T10:30:00Z",
|
|
652
|
+
"updated": "2024-01-20T14:45:00Z"
|
|
653
|
+
}
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
# Core Identification (2 fields)
|
|
657
|
+
id: Optional[int] = Field(None, description="Unique team ID")
|
|
658
|
+
name: str = Field(
|
|
659
|
+
...,
|
|
660
|
+
min_length=1,
|
|
661
|
+
max_length=250,
|
|
662
|
+
description="Team name"
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# Team Configuration (3 fields)
|
|
666
|
+
description: Optional[str] = Field(None, description="Team description")
|
|
667
|
+
is_public: Optional[bool] = Field(
|
|
668
|
+
None,
|
|
669
|
+
description="Whether team is discoverable when sharing projects"
|
|
670
|
+
)
|
|
671
|
+
external_id: Optional[str] = Field(
|
|
672
|
+
None,
|
|
673
|
+
max_length=250,
|
|
674
|
+
description="External ID from OpenID/LDAP provider"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# Members (1 field)
|
|
678
|
+
members: Optional[list[TeamUser]] = Field(
|
|
679
|
+
None,
|
|
680
|
+
description="Array of team members"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# Metadata (3 fields)
|
|
684
|
+
created_by: Optional[User] = Field(None, description="User who created this team")
|
|
685
|
+
created: Optional[datetime] = Field(None, description="Team creation timestamp")
|
|
686
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class TeamCreateRequest(VikunjaBaseModel):
|
|
690
|
+
"""
|
|
691
|
+
Request body for creating a team.
|
|
692
|
+
|
|
693
|
+
API endpoint: POST /teams
|
|
694
|
+
|
|
695
|
+
Required fields: name
|
|
696
|
+
Optional fields: description, is_public
|
|
697
|
+
|
|
698
|
+
Example:
|
|
699
|
+
req = TeamCreateRequest(
|
|
700
|
+
name="Engineering",
|
|
701
|
+
description="Core engineering team",
|
|
702
|
+
is_public=True
|
|
703
|
+
)
|
|
704
|
+
"""
|
|
705
|
+
name: str = Field(
|
|
706
|
+
...,
|
|
707
|
+
min_length=1,
|
|
708
|
+
max_length=250,
|
|
709
|
+
description="Team name (required)"
|
|
710
|
+
)
|
|
711
|
+
description: Optional[str] = Field(None, description="Team description")
|
|
712
|
+
is_public: Optional[bool] = Field(
|
|
713
|
+
None,
|
|
714
|
+
description="Whether team is discoverable when sharing projects"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
def model_dump_for_api(self) -> dict:
|
|
718
|
+
"""Convert to dict for API submission."""
|
|
719
|
+
return self.model_dump(exclude_none=True)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
class TeamUpdateRequest(VikunjaBaseModel):
|
|
723
|
+
"""
|
|
724
|
+
Request body for updating a team.
|
|
725
|
+
|
|
726
|
+
API endpoint: POST /teams/{id}
|
|
727
|
+
|
|
728
|
+
All fields are optional - only provided fields are updated.
|
|
729
|
+
|
|
730
|
+
Example:
|
|
731
|
+
req = TeamUpdateRequest(
|
|
732
|
+
description="Updated engineering team description"
|
|
733
|
+
)
|
|
734
|
+
"""
|
|
735
|
+
name: Optional[str] = Field(None, min_length=1, max_length=250, description="New team name")
|
|
736
|
+
description: Optional[str] = Field(None, description="New team description")
|
|
737
|
+
is_public: Optional[bool] = Field(None, description="New public visibility setting")
|
|
738
|
+
|
|
739
|
+
def model_dump_for_api(self) -> dict:
|
|
740
|
+
"""Convert to dict, excluding None values."""
|
|
741
|
+
return self.model_dump(exclude_none=True)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
class TeamWithPermission(Team):
|
|
745
|
+
"""
|
|
746
|
+
Team with an additional permission level.
|
|
747
|
+
|
|
748
|
+
API endpoint: Used in team-project contexts
|
|
749
|
+
|
|
750
|
+
Extends Team with a permission field for project-team relationships.
|
|
751
|
+
|
|
752
|
+
Fields from API spec (models.TeamWithPermission):
|
|
753
|
+
- All Team fields
|
|
754
|
+
- permission: Permission level for the context (0=Read, 1=Write, 2=Admin)
|
|
755
|
+
"""
|
|
756
|
+
permission: Optional[Permission] = Field(
|
|
757
|
+
None,
|
|
758
|
+
description="Permission level in this context"
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
class TeamProject(VikunjaBaseModel):
|
|
763
|
+
"""
|
|
764
|
+
Team's membership in a project with permission level.
|
|
765
|
+
|
|
766
|
+
API endpoint: GET /projects/{id}/teams
|
|
767
|
+
|
|
768
|
+
Represents the relationship between a team and a project.
|
|
769
|
+
|
|
770
|
+
Fields from API spec (models.TeamProject):
|
|
771
|
+
- id: Unique relation identifier
|
|
772
|
+
- team_id: Team ID
|
|
773
|
+
- permission: Permission level (0=Read, 1=Write, 2=Admin)
|
|
774
|
+
- created/updated: Timestamps
|
|
775
|
+
|
|
776
|
+
Example from API response:
|
|
777
|
+
{
|
|
778
|
+
"id": 15,
|
|
779
|
+
"team_id": 1,
|
|
780
|
+
"permission": 2,
|
|
781
|
+
"created": "2024-01-15T10:30:00Z",
|
|
782
|
+
"updated": "2024-01-15T10:30:00Z"
|
|
783
|
+
}
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
# Core Identification (2 fields)
|
|
787
|
+
id: Optional[int] = Field(None, description="Unique relation ID")
|
|
788
|
+
team_id: int = Field(..., description="Team ID")
|
|
789
|
+
|
|
790
|
+
# Access Control (1 field)
|
|
791
|
+
permission: Optional[Permission] = Field(
|
|
792
|
+
0,
|
|
793
|
+
description="Permission level: 0=Read, 1=Write, 2=Admin"
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Timestamps (2 fields - auto-managed)
|
|
797
|
+
created: Optional[datetime] = Field(None, description="Relation creation timestamp")
|
|
798
|
+
updated: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
# ============================================================================
|
|
802
|
+
# Response Models
|
|
803
|
+
# ============================================================================
|
|
804
|
+
|
|
805
|
+
class UserGetResponse(VikunjaBaseModel):
|
|
806
|
+
"""Response for getting current user."""
|
|
807
|
+
success: bool = Field(True, description="Request succeeded")
|
|
808
|
+
user: Optional[UserWithSettings] = Field(None, description="Current user with settings")
|
|
809
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
class LoginResponse(VikunjaBaseModel):
|
|
813
|
+
"""Response for login request."""
|
|
814
|
+
success: bool = Field(True, description="Request succeeded")
|
|
815
|
+
token: Optional[str] = Field(None, description="Authentication token")
|
|
816
|
+
user: Optional[User] = Field(None, description="Logged in user")
|
|
817
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
class TeamListResponse(VikunjaBaseModel):
|
|
821
|
+
"""Response for listing teams."""
|
|
822
|
+
success: bool = Field(True, description="Request succeeded")
|
|
823
|
+
teams: list[Team] = Field(default_factory=list, description="List of teams")
|
|
824
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
class TeamGetResponse(VikunjaBaseModel):
|
|
828
|
+
"""Response for getting a single team."""
|
|
829
|
+
success: bool = Field(True, description="Request succeeded")
|
|
830
|
+
team: Optional[Team] = Field(None, description="The requested team")
|
|
831
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
class ProjectUserListResponse(VikunjaBaseModel):
|
|
835
|
+
"""Response for listing project users."""
|
|
836
|
+
success: bool = Field(True, description="Request succeeded")
|
|
837
|
+
users: list[ProjectUser] = Field(default_factory=list, description="List of project users")
|
|
838
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|