typespec-rust-emitter 0.10.2 → 0.10.5
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.
- package/AGENTS.md +65 -0
- package/CHANGELOG.md +24 -0
- package/dist/src/emitter.js +63 -7
- package/dist/src/emitter.js.map +1 -1
- package/dist/test/hello.test.js +67 -0
- package/dist/test/hello.test.js.map +1 -1
- package/dist/test/test-host.js +6 -1
- package/dist/test/test-host.js.map +1 -1
- package/example/lib/learning/models.tsp +43 -0
- package/example/lib/learning/operations.tsp +31 -0
- package/example/output-rust/Cargo.lock +29 -0
- package/example/output-rust/Cargo.toml +1 -0
- package/example/output-rust/src/generated/server.rs +99 -0
- package/example/output-rust/src/generated/types.rs +96 -48
- package/example/package-lock.json +170 -127
- package/example/package.json +3 -1
- package/example/tspconfig.yaml +1 -1
- package/package.json +4 -1
- package/src/emitter.ts +77 -8
- package/test/hello.test.ts +79 -0
- package/test/test-host.ts +6 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import "typespec-rust-emitter";
|
|
2
|
+
import "@typespec/events";
|
|
3
|
+
|
|
4
|
+
using TypeSpec.Events;
|
|
2
5
|
|
|
3
6
|
namespace Mtlkms.Learning.Models {
|
|
4
7
|
// ── COMMON ─────────────────────────────────────────────────────────────────
|
|
@@ -55,6 +58,11 @@ namespace Mtlkms.Learning.Models {
|
|
|
55
58
|
durationMonth: int64;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
model CalendarHeatmapItem {
|
|
62
|
+
date: string;
|
|
63
|
+
duration: int64;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
// ── MODELS: GROUP ─────────────────────────────────────────────────────────
|
|
59
67
|
|
|
60
68
|
model Group {
|
|
@@ -172,6 +180,41 @@ namespace Mtlkms.Learning.Models {
|
|
|
172
180
|
durationInSeconds: int64;
|
|
173
181
|
}
|
|
174
182
|
|
|
183
|
+
model LogWithTimelogs {
|
|
184
|
+
...Log;
|
|
185
|
+
timelogs: Timelog[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@doc("Event payload for learning data changes.")
|
|
189
|
+
model SessionEvent {
|
|
190
|
+
eventType: "session" | "session_pause" | "session_resume" | "session_stop";
|
|
191
|
+
session: LogWithTimelogs;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@doc("Event payload for group CRUD.")
|
|
195
|
+
model GroupEvent {
|
|
196
|
+
eventType: "group_created" | "group_updated" | "group_deleted";
|
|
197
|
+
group: Group;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@doc("Event payload for subject CRUD.")
|
|
201
|
+
model SubjectEvent {
|
|
202
|
+
eventType:
|
|
203
|
+
| "subject_created"
|
|
204
|
+
| "subject_updated"
|
|
205
|
+
| "subject_deleted"
|
|
206
|
+
| "subject_moved";
|
|
207
|
+
subject: Subject;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@doc("Union of all learning events for SSE.")
|
|
211
|
+
@events
|
|
212
|
+
union LearningEvents {
|
|
213
|
+
sessionEvent: SessionEvent,
|
|
214
|
+
groupEvent: GroupEvent,
|
|
215
|
+
subjectEvent: SubjectEvent,
|
|
216
|
+
}
|
|
217
|
+
|
|
175
218
|
@doc("Response after start/resume — returns the log and the newly opened timelog segment.")
|
|
176
219
|
model SessionOpenedResponse {
|
|
177
220
|
log: Log;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import "@typespec/http";
|
|
2
|
+
import "@typespec/sse";
|
|
2
3
|
import "./models.tsp";
|
|
3
4
|
|
|
4
5
|
using TypeSpec.Http;
|
|
6
|
+
using TypeSpec.SSE;
|
|
5
7
|
using Mtlkms.Learning.Models;
|
|
6
8
|
|
|
7
9
|
@route("/accounts/{accountId}/learning")
|
|
@@ -20,6 +22,23 @@ namespace Groups {
|
|
|
20
22
|
@body error: ApiError;
|
|
21
23
|
};
|
|
22
24
|
|
|
25
|
+
@get
|
|
26
|
+
@route("/calendar")
|
|
27
|
+
@doc("Get calendar heatmap data for a specific group by month.")
|
|
28
|
+
op calendar(
|
|
29
|
+
@path accountId: Uuid,
|
|
30
|
+
@path groupId: Uuid,
|
|
31
|
+
@query userTimezone: string,
|
|
32
|
+
@query year: int32,
|
|
33
|
+
@query month: int32,
|
|
34
|
+
): {
|
|
35
|
+
@statusCode statusCode: 200;
|
|
36
|
+
@body body: CalendarHeatmapItem[];
|
|
37
|
+
} | {
|
|
38
|
+
@statusCode statusCode: 401;
|
|
39
|
+
@body error: ApiError;
|
|
40
|
+
};
|
|
41
|
+
|
|
23
42
|
@post
|
|
24
43
|
@useAuth(BearerAuth)
|
|
25
44
|
@doc("Create a new group.")
|
|
@@ -331,3 +350,15 @@ namespace Sessions {
|
|
|
331
350
|
error: ConflictError;
|
|
332
351
|
};
|
|
333
352
|
}
|
|
353
|
+
|
|
354
|
+
@route("/accounts/{accountId}")
|
|
355
|
+
@tag("Account")
|
|
356
|
+
namespace Accounts {
|
|
357
|
+
@get
|
|
358
|
+
@route("/events")
|
|
359
|
+
@doc("Server-Sent Events stream for learning data changes.")
|
|
360
|
+
op events(
|
|
361
|
+
@path accountId: Uuid,
|
|
362
|
+
@header accept: string = "text/event-stream",
|
|
363
|
+
): SSEStream<LearningEvents>;
|
|
364
|
+
}
|
|
@@ -393,6 +393,21 @@ dependencies = [
|
|
|
393
393
|
"percent-encoding",
|
|
394
394
|
]
|
|
395
395
|
|
|
396
|
+
[[package]]
|
|
397
|
+
name = "futures"
|
|
398
|
+
version = "0.3.32"
|
|
399
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
400
|
+
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
|
401
|
+
dependencies = [
|
|
402
|
+
"futures-channel",
|
|
403
|
+
"futures-core",
|
|
404
|
+
"futures-executor",
|
|
405
|
+
"futures-io",
|
|
406
|
+
"futures-sink",
|
|
407
|
+
"futures-task",
|
|
408
|
+
"futures-util",
|
|
409
|
+
]
|
|
410
|
+
|
|
396
411
|
[[package]]
|
|
397
412
|
name = "futures-channel"
|
|
398
413
|
version = "0.3.32"
|
|
@@ -437,6 +452,17 @@ version = "0.3.32"
|
|
|
437
452
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
438
453
|
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
|
439
454
|
|
|
455
|
+
[[package]]
|
|
456
|
+
name = "futures-macro"
|
|
457
|
+
version = "0.3.32"
|
|
458
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
459
|
+
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
|
460
|
+
dependencies = [
|
|
461
|
+
"proc-macro2",
|
|
462
|
+
"quote",
|
|
463
|
+
"syn",
|
|
464
|
+
]
|
|
465
|
+
|
|
440
466
|
[[package]]
|
|
441
467
|
name = "futures-sink"
|
|
442
468
|
version = "0.3.32"
|
|
@@ -455,8 +481,10 @@ version = "0.3.32"
|
|
|
455
481
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
456
482
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
|
457
483
|
dependencies = [
|
|
484
|
+
"futures-channel",
|
|
458
485
|
"futures-core",
|
|
459
486
|
"futures-io",
|
|
487
|
+
"futures-macro",
|
|
460
488
|
"futures-sink",
|
|
461
489
|
"futures-task",
|
|
462
490
|
"memchr",
|
|
@@ -979,6 +1007,7 @@ dependencies = [
|
|
|
979
1007
|
"axum",
|
|
980
1008
|
"chrono",
|
|
981
1009
|
"eyre",
|
|
1010
|
+
"futures",
|
|
982
1011
|
"regex",
|
|
983
1012
|
"serde",
|
|
984
1013
|
"serde_json",
|
|
@@ -16,6 +16,8 @@ pub trait Server: Send + Sync {
|
|
|
16
16
|
|
|
17
17
|
/// Retrieve a list of all groups for the current account (from view_learning_group).
|
|
18
18
|
async fn groups_list(&self, account_id: uuid::Uuid) -> Result<GroupsListResponse>;
|
|
19
|
+
/// Get calendar heatmap data for a specific group by month.
|
|
20
|
+
async fn groups_calendar(&self, account_id: uuid::Uuid, group_id: uuid::Uuid, user_timezone: String, year: i32, month: i32) -> Result<GroupsCalendarResponse>;
|
|
19
21
|
/// Create a new group.
|
|
20
22
|
async fn groups_create(&self, claims: Self::Claims, account_id: uuid::Uuid, body: CreateGroupBody) -> Result<GroupsCreateResponse>;
|
|
21
23
|
/// Get the details of a group by its ID.
|
|
@@ -50,7 +52,10 @@ pub trait Server: Send + Sync {
|
|
|
50
52
|
/// The server closes the current Timelog if it is running, and updates Log.stoppedAt.
|
|
51
53
|
/// Returns 409 if the session is already Stopped.
|
|
52
54
|
async fn sessions_stop(&self, claims: Self::Claims, account_id: uuid::Uuid, subject_id: i64, log_id: i64, body: SessionNoteBody) -> Result<SessionsStopResponse>;
|
|
55
|
+
/// Server-Sent Events stream for learning data changes.
|
|
56
|
+
async fn accounts_events(&self, account_id: uuid::Uuid) -> Result<AccountsEventsResponse>;
|
|
53
57
|
}
|
|
58
|
+
#[allow(clippy::type_complexity)]
|
|
54
59
|
pub enum GroupsListResponse {
|
|
55
60
|
Ok(Json<Vec<GroupStatistics>>),
|
|
56
61
|
Unauthorized(Json<ApiError>),
|
|
@@ -66,6 +71,23 @@ impl IntoResponse for GroupsListResponse {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
#[allow(clippy::type_complexity)]
|
|
75
|
+
pub enum GroupsCalendarResponse {
|
|
76
|
+
Ok(Json<Vec<CalendarHeatmapItem>>),
|
|
77
|
+
Unauthorized(Json<ApiError>),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl IntoResponse for GroupsCalendarResponse {
|
|
81
|
+
fn into_response(self) -> axum::response::Response {
|
|
82
|
+
match self {
|
|
83
|
+
|
|
84
|
+
GroupsCalendarResponse::Ok(body) => (StatusCode::OK, body).into_response(),
|
|
85
|
+
GroupsCalendarResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[allow(clippy::type_complexity)]
|
|
69
91
|
pub enum GroupsCreateResponse {
|
|
70
92
|
Created(Json<Group>),
|
|
71
93
|
BadRequest(Json<ValidationError>),
|
|
@@ -83,6 +105,7 @@ impl IntoResponse for GroupsCreateResponse {
|
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
|
|
108
|
+
#[allow(clippy::type_complexity)]
|
|
86
109
|
pub enum GroupsGetByIdResponse {
|
|
87
110
|
Ok(Json<GroupStatistics>),
|
|
88
111
|
Unauthorized(Json<ApiError>),
|
|
@@ -100,6 +123,7 @@ impl IntoResponse for GroupsGetByIdResponse {
|
|
|
100
123
|
}
|
|
101
124
|
}
|
|
102
125
|
|
|
126
|
+
#[allow(clippy::type_complexity)]
|
|
103
127
|
pub enum GroupsUpdateResponse {
|
|
104
128
|
Ok(Json<Group>),
|
|
105
129
|
BadRequest(Json<ValidationError>),
|
|
@@ -119,6 +143,7 @@ impl IntoResponse for GroupsUpdateResponse {
|
|
|
119
143
|
}
|
|
120
144
|
}
|
|
121
145
|
|
|
146
|
+
#[allow(clippy::type_complexity)]
|
|
122
147
|
pub enum GroupsDeleteResponse {
|
|
123
148
|
NoContent,
|
|
124
149
|
Unauthorized(Json<ApiError>),
|
|
@@ -136,6 +161,7 @@ impl IntoResponse for GroupsDeleteResponse {
|
|
|
136
161
|
}
|
|
137
162
|
}
|
|
138
163
|
|
|
164
|
+
#[allow(clippy::type_complexity)]
|
|
139
165
|
pub enum SubjectsListResponse {
|
|
140
166
|
Ok(Json<Vec<SubjectStatistics>>),
|
|
141
167
|
Unauthorized(Json<ApiError>),
|
|
@@ -153,6 +179,7 @@ impl IntoResponse for SubjectsListResponse {
|
|
|
153
179
|
}
|
|
154
180
|
}
|
|
155
181
|
|
|
182
|
+
#[allow(clippy::type_complexity)]
|
|
156
183
|
pub enum SubjectsCreateResponse {
|
|
157
184
|
Created(Json<Subject>),
|
|
158
185
|
BadRequest(Json<ValidationError>),
|
|
@@ -172,6 +199,7 @@ impl IntoResponse for SubjectsCreateResponse {
|
|
|
172
199
|
}
|
|
173
200
|
}
|
|
174
201
|
|
|
202
|
+
#[allow(clippy::type_complexity)]
|
|
175
203
|
pub enum SubjectsGetByIdResponse {
|
|
176
204
|
Ok(Json<SubjectStatistics>),
|
|
177
205
|
Unauthorized(Json<ApiError>),
|
|
@@ -189,6 +217,7 @@ impl IntoResponse for SubjectsGetByIdResponse {
|
|
|
189
217
|
}
|
|
190
218
|
}
|
|
191
219
|
|
|
220
|
+
#[allow(clippy::type_complexity)]
|
|
192
221
|
pub enum SubjectsUpdateResponse {
|
|
193
222
|
Ok(Json<Subject>),
|
|
194
223
|
BadRequest(Json<ValidationError>),
|
|
@@ -208,6 +237,7 @@ impl IntoResponse for SubjectsUpdateResponse {
|
|
|
208
237
|
}
|
|
209
238
|
}
|
|
210
239
|
|
|
240
|
+
#[allow(clippy::type_complexity)]
|
|
211
241
|
pub enum SubjectsDeleteResponse {
|
|
212
242
|
NoContent,
|
|
213
243
|
Unauthorized(Json<ApiError>),
|
|
@@ -225,6 +255,7 @@ impl IntoResponse for SubjectsDeleteResponse {
|
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
|
|
258
|
+
#[allow(clippy::type_complexity)]
|
|
228
259
|
pub enum SessionsStartResponse {
|
|
229
260
|
Created(Json<SessionOpenedResponse>),
|
|
230
261
|
Unauthorized(Json<ApiError>),
|
|
@@ -244,6 +275,7 @@ impl IntoResponse for SessionsStartResponse {
|
|
|
244
275
|
}
|
|
245
276
|
}
|
|
246
277
|
|
|
278
|
+
#[allow(clippy::type_complexity)]
|
|
247
279
|
pub enum SessionsPauseResponse {
|
|
248
280
|
Ok(Json<SessionClosedResponse>),
|
|
249
281
|
Unauthorized(Json<ApiError>),
|
|
@@ -263,6 +295,7 @@ impl IntoResponse for SessionsPauseResponse {
|
|
|
263
295
|
}
|
|
264
296
|
}
|
|
265
297
|
|
|
298
|
+
#[allow(clippy::type_complexity)]
|
|
266
299
|
pub enum SessionsResumeResponse {
|
|
267
300
|
Ok(Json<SessionOpenedResponse>),
|
|
268
301
|
Unauthorized(Json<ApiError>),
|
|
@@ -282,6 +315,7 @@ impl IntoResponse for SessionsResumeResponse {
|
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
317
|
|
|
318
|
+
#[allow(clippy::type_complexity)]
|
|
285
319
|
pub enum SessionsStopResponse {
|
|
286
320
|
Ok(Json<SessionClosedResponse>),
|
|
287
321
|
Unauthorized(Json<ApiError>),
|
|
@@ -301,10 +335,34 @@ impl IntoResponse for SessionsStopResponse {
|
|
|
301
335
|
}
|
|
302
336
|
}
|
|
303
337
|
|
|
338
|
+
#[allow(clippy::type_complexity)]
|
|
339
|
+
pub enum AccountsEventsResponse {
|
|
340
|
+
Ok(axum::response::sse::Sse<std::pin::Pin<Box<dyn futures::stream::Stream<Item = Result<axum::response::sse::Event, std::convert::Infallible>> + Send>>>),
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
impl IntoResponse for AccountsEventsResponse {
|
|
344
|
+
fn into_response(self) -> axum::response::Response {
|
|
345
|
+
match self {
|
|
346
|
+
|
|
347
|
+
AccountsEventsResponse::Ok(body) => body.into_response(),
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
use axum::extract::Query;
|
|
304
353
|
use axum::routing::{delete, get, patch, post};
|
|
305
354
|
use axum::Router;
|
|
306
355
|
|
|
307
356
|
|
|
357
|
+
#[derive(Debug, Clone, serde::Deserialize)]
|
|
358
|
+
pub struct GroupsCalendarQuery {
|
|
359
|
+
pub user_timezone: String,
|
|
360
|
+
pub year: i32,
|
|
361
|
+
pub month: i32
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
308
366
|
pub async fn groups_list_handler<S>(
|
|
309
367
|
axum::extract::State(service): axum::extract::State<S>,
|
|
310
368
|
Path(account_id): Path<uuid::Uuid>,
|
|
@@ -324,6 +382,26 @@ where
|
|
|
324
382
|
}
|
|
325
383
|
}
|
|
326
384
|
|
|
385
|
+
pub async fn groups_calendar_handler<S>(
|
|
386
|
+
axum::extract::State(service): axum::extract::State<S>,
|
|
387
|
+
Path((account_id, group_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
|
388
|
+
Query(params): Query<GroupsCalendarQuery>,
|
|
389
|
+
) -> impl axum::response::IntoResponse
|
|
390
|
+
where
|
|
391
|
+
S: Server + Clone + Send + Sync + 'static,
|
|
392
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
393
|
+
{
|
|
394
|
+
let result = service.groups_calendar(account_id, group_id, params.user_timezone, params.year, params.month).await;
|
|
395
|
+
match result {
|
|
396
|
+
Ok(response) => response.into_response(),
|
|
397
|
+
Err(e) => (
|
|
398
|
+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
|
399
|
+
format!("Internal error: {e}"),
|
|
400
|
+
)
|
|
401
|
+
.into_response(),
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
327
405
|
pub async fn groups_create_handler<S>(
|
|
328
406
|
axum::extract::State(service): axum::extract::State<S>,
|
|
329
407
|
Extension(claims): Extension<S::Claims>,
|
|
@@ -587,6 +665,25 @@ where
|
|
|
587
665
|
}
|
|
588
666
|
}
|
|
589
667
|
|
|
668
|
+
pub async fn accounts_events_handler<S>(
|
|
669
|
+
axum::extract::State(service): axum::extract::State<S>,
|
|
670
|
+
Path(account_id): Path<uuid::Uuid>,
|
|
671
|
+
) -> impl axum::response::IntoResponse
|
|
672
|
+
where
|
|
673
|
+
S: Server + Clone + Send + Sync + 'static,
|
|
674
|
+
S::Claims: Send + Sync + Clone + 'static,
|
|
675
|
+
{
|
|
676
|
+
let result = service.accounts_events(account_id).await;
|
|
677
|
+
match result {
|
|
678
|
+
Ok(response) => response.into_response(),
|
|
679
|
+
Err(e) => (
|
|
680
|
+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
|
681
|
+
format!("Internal error: {e}"),
|
|
682
|
+
)
|
|
683
|
+
.into_response(),
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
590
687
|
pub fn create_router<S, M>(service: S, middleware: M) -> Router
|
|
591
688
|
where
|
|
592
689
|
S: Server + Clone + Send + Sync + 'static,
|
|
@@ -596,9 +693,11 @@ where
|
|
|
596
693
|
let mut router = Router::new();
|
|
597
694
|
let public = Router::new()
|
|
598
695
|
.route("/accounts/{accountId}/learning/groups", get(groups_list_handler::<S>))
|
|
696
|
+
.route("/accounts/{accountId}/learning/groups/calendar", get(groups_calendar_handler::<S>))
|
|
599
697
|
.route("/accounts/{accountId}/learning/groups/{id}", get(groups_get_by_id_handler::<S>))
|
|
600
698
|
.route("/accounts/{accountId}/learning/groups/{groupId}/subjects", get(subjects_list_handler::<S>))
|
|
601
699
|
.route("/accounts/{accountId}/learning/groups/{groupId}/subjects/{id}", get(subjects_get_by_id_handler::<S>))
|
|
700
|
+
.route("/accounts/{accountId}/learning/accounts/{accountId}/events", get(accounts_events_handler::<S>))
|
|
602
701
|
;
|
|
603
702
|
router = router.merge(public);
|
|
604
703
|
let protected = Router::new()
|
|
@@ -5,8 +5,7 @@ use axum::http::StatusCode;
|
|
|
5
5
|
use axum::response::IntoResponse;
|
|
6
6
|
use axum::Json;
|
|
7
7
|
|
|
8
|
-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize
|
|
9
|
-
#[error("{code}: {message}")]
|
|
8
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
10
9
|
pub struct ApiError {
|
|
11
10
|
/// Machine-readable error code.
|
|
12
11
|
pub code: String,
|
|
@@ -14,70 +13,27 @@ pub struct ApiError {
|
|
|
14
13
|
pub message: String,
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
fn into_response(self) -> axum::response::Response {
|
|
19
|
-
(
|
|
20
|
-
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
21
|
-
Json(self),
|
|
22
|
-
)
|
|
23
|
-
.into_response()
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
|
|
28
|
-
#[error("{code}: {message}")]
|
|
16
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
29
17
|
pub struct NotFoundError {
|
|
30
18
|
pub code: String,
|
|
31
19
|
/// Human-readable message.
|
|
32
20
|
pub message: String,
|
|
33
21
|
}
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
fn into_response(self) -> axum::response::Response {
|
|
37
|
-
(
|
|
38
|
-
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
39
|
-
Json(self),
|
|
40
|
-
)
|
|
41
|
-
.into_response()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
|
|
46
|
-
#[error("{code}: {message}")]
|
|
23
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
47
24
|
pub struct ValidationError {
|
|
48
25
|
pub code: String,
|
|
49
26
|
/// Human-readable message.
|
|
50
27
|
pub message: String,
|
|
51
28
|
}
|
|
52
29
|
|
|
53
|
-
|
|
54
|
-
fn into_response(self) -> axum::response::Response {
|
|
55
|
-
(
|
|
56
|
-
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
57
|
-
Json(self),
|
|
58
|
-
)
|
|
59
|
-
.into_response()
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
|
|
64
|
-
#[error("{code}: {message}")]
|
|
30
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
65
31
|
pub struct ConflictError {
|
|
66
32
|
pub code: String,
|
|
67
33
|
/// Human-readable message.
|
|
68
34
|
pub message: String,
|
|
69
35
|
}
|
|
70
36
|
|
|
71
|
-
impl IntoResponse for ConflictError {
|
|
72
|
-
fn into_response(self) -> axum::response::Response {
|
|
73
|
-
(
|
|
74
|
-
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
75
|
-
Json(self),
|
|
76
|
-
)
|
|
77
|
-
.into_response()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
37
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
82
38
|
pub struct DurationStats {
|
|
83
39
|
#[serde(rename = "durationTotal")]
|
|
@@ -90,6 +46,12 @@ pub struct DurationStats {
|
|
|
90
46
|
pub duration_month: i64,
|
|
91
47
|
}
|
|
92
48
|
|
|
49
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
50
|
+
pub struct CalendarHeatmapItem {
|
|
51
|
+
pub date: String,
|
|
52
|
+
pub duration: i64,
|
|
53
|
+
}
|
|
54
|
+
|
|
93
55
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
94
56
|
pub struct Group {
|
|
95
57
|
pub id: i64,
|
|
@@ -263,6 +225,44 @@ pub struct Timelog {
|
|
|
263
225
|
pub duration_in_seconds: i64,
|
|
264
226
|
}
|
|
265
227
|
|
|
228
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
229
|
+
pub struct LogWithTimelogs {
|
|
230
|
+
pub id: i64,
|
|
231
|
+
#[serde(rename = "accountId")]
|
|
232
|
+
pub account_id: uuid::Uuid,
|
|
233
|
+
#[serde(rename = "subjectId")]
|
|
234
|
+
pub subject_id: i64,
|
|
235
|
+
#[serde(rename = "startedAt")]
|
|
236
|
+
pub started_at: chrono::DateTime<chrono::Utc>,
|
|
237
|
+
#[serde(rename = "stoppedAt")]
|
|
238
|
+
pub stopped_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
239
|
+
#[serde(rename = "logContent")]
|
|
240
|
+
pub log_content: Option<String>,
|
|
241
|
+
pub status: StudyStatus,
|
|
242
|
+
pub timelogs: Vec<Timelog>,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
246
|
+
pub struct SessionEvent {
|
|
247
|
+
#[serde(rename = "eventType")]
|
|
248
|
+
pub event_type: EnumSessionSessionpause4,
|
|
249
|
+
pub session: LogWithTimelogs,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
253
|
+
pub struct GroupEvent {
|
|
254
|
+
#[serde(rename = "eventType")]
|
|
255
|
+
pub event_type: EnumGroupcreatedGroupupdated3,
|
|
256
|
+
pub group: Group,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
260
|
+
pub struct SubjectEvent {
|
|
261
|
+
#[serde(rename = "eventType")]
|
|
262
|
+
pub event_type: EnumSubjectcreatedSubjectupdated4,
|
|
263
|
+
pub subject: Subject,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
266
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
267
267
|
pub struct SessionOpenedResponse {
|
|
268
268
|
pub log: Log,
|
|
@@ -282,6 +282,46 @@ pub struct SessionNoteBody {
|
|
|
282
282
|
pub log_content: Option<String>,
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|
|
286
|
+
#[allow(clippy::enum_variant_names)]
|
|
287
|
+
pub enum EnumSessionSessionpause4 {
|
|
288
|
+
#[default]
|
|
289
|
+
#[serde(rename = "session")]
|
|
290
|
+
Session,
|
|
291
|
+
#[serde(rename = "session_pause")]
|
|
292
|
+
SessionPause,
|
|
293
|
+
#[serde(rename = "session_resume")]
|
|
294
|
+
SessionResume,
|
|
295
|
+
#[serde(rename = "session_stop")]
|
|
296
|
+
SessionStop,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|
|
300
|
+
#[allow(clippy::enum_variant_names)]
|
|
301
|
+
pub enum EnumGroupcreatedGroupupdated3 {
|
|
302
|
+
#[default]
|
|
303
|
+
#[serde(rename = "group_created")]
|
|
304
|
+
GroupCreated,
|
|
305
|
+
#[serde(rename = "group_updated")]
|
|
306
|
+
GroupUpdated,
|
|
307
|
+
#[serde(rename = "group_deleted")]
|
|
308
|
+
GroupDeleted,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|
|
312
|
+
#[allow(clippy::enum_variant_names)]
|
|
313
|
+
pub enum EnumSubjectcreatedSubjectupdated4 {
|
|
314
|
+
#[default]
|
|
315
|
+
#[serde(rename = "subject_created")]
|
|
316
|
+
SubjectCreated,
|
|
317
|
+
#[serde(rename = "subject_updated")]
|
|
318
|
+
SubjectUpdated,
|
|
319
|
+
#[serde(rename = "subject_deleted")]
|
|
320
|
+
SubjectDeleted,
|
|
321
|
+
#[serde(rename = "subject_moved")]
|
|
322
|
+
SubjectMoved,
|
|
323
|
+
}
|
|
324
|
+
|
|
285
325
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize, sqlx::Type)]
|
|
286
326
|
#[sqlx(type_name = "study_status")]
|
|
287
327
|
pub enum StudyStatus {
|
|
@@ -307,6 +347,14 @@ pub enum ChartGranularity {
|
|
|
307
347
|
Year,
|
|
308
348
|
}
|
|
309
349
|
|
|
350
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
351
|
+
#[serde(untagged)]
|
|
352
|
+
pub enum LearningEvents {
|
|
353
|
+
Variant1(SessionEvent),
|
|
354
|
+
Variant2(GroupEvent),
|
|
355
|
+
Variant3(SubjectEvent),
|
|
356
|
+
}
|
|
357
|
+
|
|
310
358
|
pub type Uuid = uuid::Uuid;
|
|
311
359
|
|
|
312
360
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|