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.
@@ -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",
@@ -4,6 +4,7 @@ version = "0.1.0"
4
4
  edition = "2024"
5
5
 
6
6
  [dependencies]
7
+ futures = "0.3"
7
8
  regex = "1.12.3"
8
9
  serde = { version = "1.0.228", features = ["derive"] }
9
10
  serde_json = "1.0.149"
@@ -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, thiserror::Error)]
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
- impl IntoResponse for ApiError {
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
- impl IntoResponse for NotFoundError {
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
- impl IntoResponse for ValidationError {
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)]