studium/src/api/models.rs
Antoine Pelletier 5e160ae11c wip
2026-09-29 09:47:27 +02:00

173 lines
4.4 KiB
Rust

//! Request bodies and path parameters, kept apart from the domain models so the public
//! contract does not change every time a domain model does.
use chrono::{DateTime, NaiveTime, Utc};
use schemars::JsonSchema;
use serde::Deserialize;
use crate::core::models::{
course::{CourseData, CourseId, CourseKind},
course_file::CourseFileId,
focus::TimetaggerRecord,
grade::{GradeData, GradeId},
tag::TagId,
task::{TaskData, TaskId},
time_slot::{TimeSlotData, TimeSlotId},
};
#[derive(Deserialize, JsonSchema)]
pub struct CoursePath {
pub id: CourseId,
}
#[derive(Deserialize, JsonSchema)]
pub struct SlotPath {
/// Id of the course
pub id: CourseId,
pub slot_id: TimeSlotId,
}
#[derive(Deserialize, JsonSchema)]
pub struct GradePath {
pub id: GradeId,
}
#[derive(Deserialize, JsonSchema)]
pub struct TaskPath {
pub id: TaskId,
}
#[derive(Deserialize, JsonSchema)]
pub struct LectureNotePath {
pub id: CourseFileId,
}
/// Period `[from, to[`, at most 62 days
#[derive(Deserialize, JsonSchema)]
pub struct PeriodQuery {
pub from: DateTime<Utc>,
pub to: DateTime<Utc>,
}
/// A course or an association, without its slots: they are added and removed one by one
#[derive(Deserialize, JsonSchema)]
pub struct CourseBody {
/// `course` by default. Cannot be changed once created.
pub kind: Option<CourseKind>,
pub name: String,
pub code: Option<String>,
/// At least one for a course
#[serde(default)]
pub teachers: Vec<String>,
/// http(s) url of the main page of the course
pub main_page_url: Option<String>,
}
#[derive(Deserialize, JsonSchema)]
pub struct TimeSlotBody {
pub tag_id: TagId,
/// ISO weekday: 1 = monday ... 5 = friday
pub weekday: u8,
/// Between 08:00 and 19:00, before `end_time`
pub start_time: NaiveTime,
pub end_time: NaiveTime,
}
/// A grade on any scale, e.g. 20 out of 100
#[derive(Deserialize, JsonSchema)]
pub struct GradeBody {
pub name: String,
/// Between 0 and `max_score`
pub score: f64,
/// Positive
pub max_score: f64,
/// Positive, relative to the other grades of the course
pub weight: f64,
}
#[derive(Deserialize, JsonSchema)]
pub struct TaskBody {
pub name: String,
pub description: Option<String>,
/// http(s) urls
#[serde(default)]
pub links: Vec<String>,
pub deadline: Option<DateTime<Utc>>,
#[serde(default)]
pub done: bool,
/// At least one for a task, optional for a subtask
#[serde(default)]
pub tag_ids: Vec<TagId>,
}
#[derive(Deserialize, JsonSchema)]
pub struct CreateTaskBody {
/// Creates a subtask of this task, which must be a task of the same course
pub parent_id: Option<TaskId>,
#[serde(flatten)]
pub task: TaskBody,
}
/// End (or pause) of a focus session
#[derive(Deserialize, JsonSchema)]
pub struct FocusStopBody {
/// Length of the session, at most 24 hours
pub seconds: u32,
/// Record returned when the session started, to stop it
pub timetagger: Option<TimetaggerRecord>,
}
#[derive(Deserialize, JsonSchema)]
pub struct LectureNoteBody {
/// Subtitle of the pdf, e.g. "Lecture 2 Notes: Memory subsystem"
pub title: String,
/// Markdown, without front matter: it is generated from the course
pub content: String,
}
impl From<CourseBody> for CourseData {
fn from(value: CourseBody) -> Self {
CourseData {
kind: value.kind.unwrap_or(CourseKind::Course),
name: value.name,
code: value.code,
teachers: value.teachers,
main_page_url: value.main_page_url,
}
}
}
impl From<TimeSlotBody> for TimeSlotData {
fn from(value: TimeSlotBody) -> Self {
TimeSlotData {
tag_id: value.tag_id,
weekday: value.weekday,
start_time: value.start_time,
end_time: value.end_time,
}
}
}
impl From<GradeBody> for GradeData {
fn from(value: GradeBody) -> Self {
GradeData {
name: value.name,
score: value.score,
max_score: value.max_score,
weight: value.weight,
}
}
}
impl From<TaskBody> for TaskData {
fn from(value: TaskBody) -> Self {
TaskData {
name: value.name,
description: value.description,
links: value.links,
deadline: value.deadline,
done: value.done,
tag_ids: value.tag_ids,
}
}
}