headless_lms_credit_registration/runtime/
manual.rs1use headless_lms_models::library::credit_registration::study_registry::RosterPerson;
5use headless_lms_utils::prelude::{DateTime, Utc};
6use headless_lms_utils::services::suotar::SuotarClient;
7use secrecy::SecretString;
8use sqlx::PgPool;
9use uuid::Uuid;
10
11use super::suotar::InteractiveSuotar;
12use crate::error::CreditRegistrationResult;
13use crate::registry::{InteractiveStudyRegistry, PersonLookupError, RegistryPerson, StudentNumber};
14use crate::use_cases::account_linking::{self, RateCapOverride, ResendAttempt};
15
16pub struct ManualActionContext<'a> {
18 pool: &'a PgPool,
19 suotar_client: &'a SuotarClient,
20 caller: &'a str,
21}
22
23impl<'a> ManualActionContext<'a> {
24 pub fn new(pool: &'a PgPool, suotar_client: &'a SuotarClient, caller: &'a str) -> Self {
27 Self {
28 pool,
29 suotar_client,
30 caller,
31 }
32 }
33}
34
35pub async fn resend_linking_mail_for_target(
39 ctx: &ManualActionContext<'_>,
40 course_id: Uuid,
41 student_number: &SecretString,
42 rate_cap_override: Option<RateCapOverride<'_>>,
43) -> CreditRegistrationResult<ResendAttempt> {
44 let registry = InteractiveSuotar::new(ctx.suotar_client, ctx.caller.to_string());
45 account_linking::resend_for_target(
46 ctx.pool,
47 ®istry,
48 course_id,
49 student_number,
50 rate_cap_override,
51 )
52 .await
53}
54
55pub async fn look_up_person(
60 ctx: &ManualActionContext<'_>,
61 student_number: &SecretString,
62) -> Result<Option<RegistryPerson>, PersonLookupError> {
63 InteractiveSuotar::new(ctx.suotar_client, ctx.caller.to_string())
64 .look_up_person(&StudentNumber::new(student_number.clone()))
65 .await
66}
67
68pub async fn list_unlinked_on_course_rosters(
73 ctx: &ManualActionContext<'_>,
74 course_id: Uuid,
75) -> CreditRegistrationResult<Option<Vec<RosterPerson>>> {
76 let registry = InteractiveSuotar::new(ctx.suotar_client, ctx.caller.to_string());
77 account_linking::unlinked_on_course_rosters(ctx.pool, ®istry, course_id).await
78}
79
80pub async fn list_unlinked_enrolled_before(
85 ctx: &ManualActionContext<'_>,
86 course_code: &str,
87 since: DateTime<Utc>,
88) -> CreditRegistrationResult<Option<Vec<RosterPerson>>> {
89 let registry = InteractiveSuotar::new(ctx.suotar_client, ctx.caller.to_string());
90 account_linking::unlinked_enrolled_before(ctx.pool, ®istry, course_code, since).await
91}