This commit is contained in:
Dominic Grimm 2023-02-27 14:04:16 +01:00
parent 19c3d876a2
commit b25c20ab69
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
10 changed files with 472 additions and 365 deletions

View file

@ -26,6 +26,7 @@ LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \
RUN apt update RUN apt update
RUN apt install -y libpq5 RUN apt install -y libpq5
RUN apt install -y ca-certificates RUN apt install -y ca-certificates
RUN apt install -y tzdata
RUN apt-get clean RUN apt-get clean
RUN apt-get autoremove -y RUN apt-get autoremove -y
RUN rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN rm -rf /var/lib/{apt,dpkg,cache,log}/

View file

@ -125,7 +125,7 @@ CREATE TABLE timegrid_time_unit (
CREATE TYPE week_type AS ENUM ('a', 'b'); CREATE TYPE week_type AS ENUM ('a', 'b');
CREATE TABLE substitution_queries ( CREATE TABLE substitution_queries (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
schoolyear_id INTEGER NOT NULL REFERENCES schoolyears(id), schoolyear_id INTEGER NOT NULL REFERENCES schoolyears(id),
date DATE NOT NULL, date DATE NOT NULL,
week_type week_type NOT NULL, week_type week_type NOT NULL,
@ -153,8 +153,8 @@ CREATE TYPE substitution_type AS ENUM (
); );
CREATE TABLE substitutions( CREATE TABLE substitutions(
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
substitution_query_id INTEGER NOT NULL REFERENCES substitution_queries(id), substitution_query_id BIGINT NOT NULL REFERENCES substitution_queries(id),
subst_type substitution_type NOT NULL, subst_type substitution_type NOT NULL,
lesson_id INTEGER NOT NULL, lesson_id INTEGER NOT NULL,
start_time TIME NOT NULL, start_time TIME NOT NULL,
@ -165,8 +165,8 @@ CREATE TABLE substitutions(
); );
CREATE TABLE substitution_classes ( CREATE TABLE substitution_classes (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
substitution_id INTEGER NOT NULL REFERENCES substitutions(id), substitution_id BIGINT NOT NULL REFERENCES substitutions(id),
position SMALLINT NOT NULL, position SMALLINT NOT NULL,
class_id INTEGER NOT NULL REFERENCES classes(id), class_id INTEGER NOT NULL REFERENCES classes(id),
original_id INTEGER REFERENCES classes(id), original_id INTEGER REFERENCES classes(id),
@ -175,8 +175,8 @@ CREATE TABLE substitution_classes (
); );
CREATE TABLE substitution_teachers ( CREATE TABLE substitution_teachers (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
substitution_id INTEGER NOT NULL REFERENCES substitutions(id), substitution_id BIGINT NOT NULL REFERENCES substitutions(id),
position SMALLINT NOT NULL, position SMALLINT NOT NULL,
teacher_id INTEGER REFERENCES teachers(id), teacher_id INTEGER REFERENCES teachers(id),
original_id INTEGER REFERENCES teachers(id), original_id INTEGER REFERENCES teachers(id),
@ -185,8 +185,8 @@ CREATE TABLE substitution_teachers (
); );
CREATE TABLE substitution_subjects ( CREATE TABLE substitution_subjects (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
substitution_id INTEGER NOT NULL REFERENCES substitutions(id), substitution_id BIGINT NOT NULL REFERENCES substitutions(id),
position SMALLINT NOT NULL, position SMALLINT NOT NULL,
subject_id INTEGER NOT NULL REFERENCES subjects(id), subject_id INTEGER NOT NULL REFERENCES subjects(id),
original_id INTEGER REFERENCES subjects(id), original_id INTEGER REFERENCES subjects(id),
@ -195,8 +195,8 @@ CREATE TABLE substitution_subjects (
); );
CREATE TABLE substitution_rooms ( CREATE TABLE substitution_rooms (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
substitution_id INTEGER NOT NULL REFERENCES substitutions(id), substitution_id BIGINT NOT NULL REFERENCES substitutions(id),
position SMALLINT NOT NULL, position SMALLINT NOT NULL,
room_id INTEGER REFERENCES rooms(id), room_id INTEGER REFERENCES rooms(id),
original_id INTEGER REFERENCES rooms(id), original_id INTEGER REFERENCES rooms(id),

View file

@ -388,7 +388,7 @@ impl diesel::deserialize::FromSql<schema::sql_types::WeekType, diesel::pg::Pg> f
#[diesel(table_name = schema::substitution_queries)] #[diesel(table_name = schema::substitution_queries)]
#[diesel(belongs_to(Schoolyear))] #[diesel(belongs_to(Schoolyear))]
pub struct SubstitutionQuery { pub struct SubstitutionQuery {
pub id: i32, pub id: i64,
pub schoolyear_id: i32, pub schoolyear_id: i32,
pub date: NaiveDate, pub date: NaiveDate,
pub week_type: WeekType, pub week_type: WeekType,
@ -508,8 +508,8 @@ impl From<untis::RpcSubstitionType> for SubstitutionType {
#[diesel(table_name = schema::substitutions)] #[diesel(table_name = schema::substitutions)]
#[diesel(belongs_to(SubstitutionQuery))] #[diesel(belongs_to(SubstitutionQuery))]
pub struct Substitution { pub struct Substitution {
pub id: i32, pub id: i64,
pub substitution_query_id: i32, pub substitution_query_id: i64,
pub subst_type: SubstitutionType, pub subst_type: SubstitutionType,
pub lesson_id: i32, pub lesson_id: i32,
pub start_time: NaiveTime, pub start_time: NaiveTime,
@ -522,7 +522,7 @@ pub struct Substitution {
#[derive(Insertable, Debug)] #[derive(Insertable, Debug)]
#[diesel(table_name = schema::substitutions)] #[diesel(table_name = schema::substitutions)]
pub struct NewSubstitution<'a> { pub struct NewSubstitution<'a> {
pub substitution_query_id: i32, pub substitution_query_id: i64,
pub subst_type: SubstitutionType, pub subst_type: SubstitutionType,
pub lesson_id: i32, pub lesson_id: i32,
pub start_time: NaiveTime, pub start_time: NaiveTime,
@ -535,8 +535,8 @@ pub struct NewSubstitution<'a> {
#[diesel(belongs_to(Substitution))] #[diesel(belongs_to(Substitution))]
#[diesel(belongs_to(Class))] #[diesel(belongs_to(Class))]
pub struct SubstitutionClass { pub struct SubstitutionClass {
pub id: i32, pub id: i64,
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub class_id: i32, pub class_id: i32,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -547,7 +547,7 @@ pub struct SubstitutionClass {
#[derive(Insertable, Debug)] #[derive(Insertable, Debug)]
#[diesel(table_name = schema::substitution_classes)] #[diesel(table_name = schema::substitution_classes)]
pub struct NewSubstitutionClass { pub struct NewSubstitutionClass {
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub class_id: i32, pub class_id: i32,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -558,8 +558,8 @@ pub struct NewSubstitutionClass {
#[diesel(belongs_to(Substitution))] #[diesel(belongs_to(Substitution))]
#[diesel(belongs_to(Teacher))] #[diesel(belongs_to(Teacher))]
pub struct SubstitutionTeacher { pub struct SubstitutionTeacher {
pub id: i32, pub id: i64,
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub teacher_id: Option<i32>, pub teacher_id: Option<i32>,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -570,7 +570,7 @@ pub struct SubstitutionTeacher {
#[derive(Insertable, Debug)] #[derive(Insertable, Debug)]
#[diesel(table_name = schema::substitution_teachers)] #[diesel(table_name = schema::substitution_teachers)]
pub struct NewSubstitutionTeacher { pub struct NewSubstitutionTeacher {
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub teacher_id: Option<i32>, pub teacher_id: Option<i32>,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -581,8 +581,8 @@ pub struct NewSubstitutionTeacher {
#[diesel(belongs_to(Substitution))] #[diesel(belongs_to(Substitution))]
#[diesel(belongs_to(Subject))] #[diesel(belongs_to(Subject))]
pub struct SubstitutionSubject { pub struct SubstitutionSubject {
pub id: i32, pub id: i64,
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub subject_id: i32, pub subject_id: i32,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -593,7 +593,7 @@ pub struct SubstitutionSubject {
#[derive(Insertable, Debug)] #[derive(Insertable, Debug)]
#[diesel(table_name = schema::substitution_subjects)] #[diesel(table_name = schema::substitution_subjects)]
pub struct NewSubstitutionSubject { pub struct NewSubstitutionSubject {
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub subject_id: i32, pub subject_id: i32,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -604,8 +604,8 @@ pub struct NewSubstitutionSubject {
#[diesel(belongs_to(Substitution))] #[diesel(belongs_to(Substitution))]
#[diesel(belongs_to(Room))] #[diesel(belongs_to(Room))]
pub struct SubstitutionRoom { pub struct SubstitutionRoom {
pub id: i32, pub id: i64,
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub room_id: Option<i32>, pub room_id: Option<i32>,
pub original_id: Option<i32>, pub original_id: Option<i32>,
@ -616,7 +616,7 @@ pub struct SubstitutionRoom {
#[derive(Insertable, Debug)] #[derive(Insertable, Debug)]
#[diesel(table_name = schema::substitution_rooms)] #[diesel(table_name = schema::substitution_rooms)]
pub struct NewSubstitutionRoom { pub struct NewSubstitutionRoom {
pub substitution_id: i32, pub substitution_id: i64,
pub position: i16, pub position: i16,
pub room_id: Option<i32>, pub room_id: Option<i32>,
pub original_id: Option<i32>, pub original_id: Option<i32>,

View file

@ -152,7 +152,7 @@ diesel::table! {
use super::sql_types::*; use super::sql_types::*;
substitution_queries { substitution_queries {
id -> Integer, id -> BigInt,
schoolyear_id -> Integer, schoolyear_id -> Integer,
date -> Date, date -> Date,
week_type -> WeekType, week_type -> WeekType,
@ -168,8 +168,8 @@ diesel::table! {
use super::sql_types::*; use super::sql_types::*;
substitutions { substitutions {
id -> Integer, id -> BigInt,
substitution_query_id -> Integer, substitution_query_id -> BigInt,
subst_type -> SubstitutionType, subst_type -> SubstitutionType,
lesson_id -> Integer, lesson_id -> Integer,
start_time -> Time, start_time -> Time,
@ -182,8 +182,8 @@ diesel::table! {
diesel::table! { diesel::table! {
substitution_classes { substitution_classes {
id -> Integer, id -> BigInt,
substitution_id -> Integer, substitution_id -> BigInt,
position -> SmallInt, position -> SmallInt,
class_id -> Integer, class_id -> Integer,
original_id -> Nullable<Integer>, original_id -> Nullable<Integer>,
@ -194,8 +194,8 @@ diesel::table! {
diesel::table! { diesel::table! {
substitution_teachers { substitution_teachers {
id -> Integer, id -> BigInt,
substitution_id -> Integer, substitution_id -> BigInt,
position -> SmallInt, position -> SmallInt,
teacher_id -> Nullable<Integer>, teacher_id -> Nullable<Integer>,
original_id -> Nullable<Integer>, original_id -> Nullable<Integer>,
@ -206,8 +206,8 @@ diesel::table! {
diesel::table! { diesel::table! {
substitution_subjects { substitution_subjects {
id -> Integer, id -> BigInt,
substitution_id -> Integer, substitution_id -> BigInt,
position -> SmallInt, position -> SmallInt,
subject_id -> Integer, subject_id -> Integer,
original_id -> Nullable<Integer>, original_id -> Nullable<Integer>,
@ -218,8 +218,8 @@ diesel::table! {
diesel::table! { diesel::table! {
substitution_rooms { substitution_rooms {
id -> Integer, id -> BigInt,
substitution_id -> Integer, substitution_id -> BigInt,
position -> SmallInt, position -> SmallInt,
room_id -> Nullable<Integer>, room_id -> Nullable<Integer>,
original_id -> Nullable<Integer>, original_id -> Nullable<Integer>,

View file

@ -0,0 +1,89 @@
use anyhow::Result;
use celery::{error::TaskError, task::TaskResult};
use chrono::prelude::*;
use diesel::prelude::*;
use crate::db;
fn do_cleanup(db_conn: &mut db::Connection) -> Result<()> {
let timegrid_ids = db::schema::timegrids::table
.select(db::schema::timegrids::id)
.filter(db::schema::timegrids::active.eq(false))
.load::<i32>(db_conn)?;
let timegrid_day_ids = db::schema::timegrid_days::table
.select(db::schema::timegrid_days::id)
.filter(db::schema::timegrid_days::timegrid_id.eq_any(&timegrid_ids))
.load::<i32>(db_conn)?;
diesel::delete(
db::schema::timegrid_time_unit::table
.filter(db::schema::timegrid_time_unit::timegrid_day_id.eq_any(&timegrid_day_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::timegrid_days::table
.filter(db::schema::timegrid_days::id.eq_any(timegrid_day_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::timegrids::table.filter(db::schema::timegrids::id.eq_any(timegrid_ids)),
)
.execute(db_conn)?;
let subst_query_ids = db::schema::substitution_queries::table
.select(db::schema::substitution_queries::id)
.filter(
db::schema::substitution_queries::queried_at
.lt(Local::now().naive_local() - chrono::Duration::hours(1)),
)
.load::<i64>(db_conn)?;
let subst_ids = db::schema::substitutions::table
.select(db::schema::substitutions::id)
.filter(db::schema::substitutions::substitution_query_id.eq_any(&subst_query_ids))
.load::<i64>(db_conn)?;
diesel::delete(
db::schema::substitution_classes::table
.filter(db::schema::substitution_classes::substitution_id.eq_any(&subst_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::substitution_teachers::table
.filter(db::schema::substitution_teachers::substitution_id.eq_any(&subst_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::substitution_subjects::table
.filter(db::schema::substitution_subjects::substitution_id.eq_any(&subst_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::substitution_rooms::table
.filter(db::schema::substitution_rooms::substitution_id.eq_any(&subst_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::substitutions::table.filter(db::schema::substitutions::id.eq_any(subst_ids)),
)
.execute(db_conn)?;
diesel::delete(
db::schema::substitution_queries::table
.filter(db::schema::substitution_queries::id.eq_any(subst_query_ids)),
)
.execute(db_conn)?;
Ok(())
}
#[celery::task]
pub async fn cleanup() -> TaskResult<()> {
let db_conn = &mut match db::POOL.get() {
Ok(x) => x,
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),
};
if let Err(e) = do_cleanup(db_conn) {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
Ok(())
}

View file

@ -25,286 +25,11 @@ async fn get_schoolyear(client: &untis::Client, db_conn: &mut db::Connection) ->
.first(db_conn)?) .first(db_conn)?)
} }
async fn fetch_current_tenant(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let tenant = client.current_tenant().await?;
if diesel::select(diesel::dsl::not(diesel::dsl::exists(
db::schema::tenants::table.filter(db::schema::tenants::untis_id.eq(tenant.id)),
)))
.get_result::<bool>(db_conn)?
{
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::active)
.set(db::schema::tenants::active.eq(false))
.execute(db_conn)?;
diesel::insert_into(db::schema::tenants::table)
.values(db::models::NewTenant {
untis_id: tenant.id,
schoolyear_id,
name: &tenant.display_name,
active: true,
})
.execute(db_conn)?;
} else if diesel::select(diesel::dsl::exists(
db::schema::tenants::table
.filter(db::schema::tenants::untis_id.eq(tenant.id))
.filter(db::schema::tenants::active.eq(false)),
))
.get_result::<bool>(db_conn)?
{
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::active)
.set((
db::schema::tenants::active.eq(false),
db::schema::tenants::updated_at.eq(diesel::dsl::now),
))
.execute(db_conn)?;
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::untis_id.eq(tenant.id))
.set((
db::schema::tenants::active.eq(true),
db::schema::tenants::updated_at.eq(diesel::dsl::now),
))
.execute(db_conn)?;
}
Ok(())
}
async fn fetch_timegrid(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let days = client.timegrid().await?;
diesel::update(db::schema::timegrids::table)
.filter(db::schema::timegrids::active)
.set(db::schema::timegrids::active.eq(false))
.execute(db_conn)?;
let timegrid_id = diesel::insert_into(db::schema::timegrids::table)
.values(db::models::NewTimegrid {
schoolyear_id,
active: true,
})
.returning(db::schema::timegrids::id)
.get_result::<i32>(db_conn)?;
for day in days {
let timegrid_day_id = diesel::insert_into(db::schema::timegrid_days::table)
.values(db::models::NewTimegridDay {
timegrid_id,
weekday: day.day.try_into()?,
})
.returning(db::schema::timegrid_days::id)
.get_result::<i32>(db_conn)?;
diesel::insert_into(db::schema::timegrid_time_unit::table)
.values(
day.time_units
.into_iter()
.map(|x| db::models::NewTimegridTimeUnit {
timegrid_day_id,
start_time: x.start_time,
end_time: x.end_time,
})
.collect::<Vec<_>>(),
)
.execute(db_conn)?;
}
Ok(())
}
async fn fetch_teachers(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_teachers = db::schema::teachers::table
.select(db::schema::teachers::untis_id)
.filter(db::schema::teachers::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::teachers::table)
.values(
&client
.teachers()
.await?
.iter()
.filter(|t| !existing_teachers.contains(&t.id))
.map(|t| db::models::NewTeacher {
untis_id: t.id,
schoolyear_id,
name: &t.name,
forename: if t.forename.is_empty() {
None
} else {
Some(&t.forename)
},
display_name: &t.display_name,
})
.collect::<Vec<db::models::NewTeacher>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_classes(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::classes::table
.select(db::schema::classes::untis_id)
.filter(db::schema::classes::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::classes::table)
.values(
&client
.classes()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewClass {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
active: c.active,
})
.collect::<Vec<db::models::NewClass>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_subjects(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::subjects::table
.select(db::schema::subjects::untis_id)
.filter(db::schema::subjects::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::subjects::table)
.values(
&client
.subjects()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewSubject {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewSubject>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_rooms(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::rooms::table
.select(db::schema::rooms::untis_id)
.filter(db::schema::rooms::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::rooms::table)
.values(
&client
.rooms()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewRoom {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewRoom>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_departments(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::departments::table
.select(db::schema::departments::untis_id)
.filter(db::schema::departments::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::departments::table)
.values(
&client
.departments()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewDepartment {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewDepartment>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_holidays(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::holidays::table
.select(db::schema::holidays::untis_id)
.filter(db::schema::holidays::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::holidays::table)
.values(
&client
.holidays()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewHoliday {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
start_date: c.start_date,
end_date: c.end_date,
})
.collect::<Vec<db::models::NewHoliday>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_substitutions( async fn fetch_substitutions(
client: &untis::Client, client: &untis::Client,
db_conn: &mut PgConnection, db_conn: &mut db::Connection,
schoolyear_id: i32, schoolyear_id: i32,
) -> Result<i32> { ) -> Result<i64> {
lazy_static! { lazy_static! {
static ref TITLE_SELECTOR: scraper::Selector = static ref TITLE_SELECTOR: scraper::Selector =
scraper::Selector::parse(".mon_title").unwrap(); scraper::Selector::parse(".mon_title").unwrap();
@ -362,7 +87,7 @@ async fn fetch_substitutions(
queried_at: Utc::now().naive_utc(), queried_at: Utc::now().naive_utc(),
}) })
.returning(db::schema::substitution_queries::id) .returning(db::schema::substitution_queries::id)
.get_result::<i32>(db_conn)?; .get_result::<i64>(db_conn)?;
for substitution in client.substitutions(&date, &date, None).await? { for substitution in client.substitutions(&date, &date, None).await? {
let substitution_id = diesel::insert_into(db::schema::substitutions::table) let substitution_id = diesel::insert_into(db::schema::substitutions::table)
@ -375,7 +100,7 @@ async fn fetch_substitutions(
text: substitution.text.as_deref(), text: substitution.text.as_deref(),
}) })
.returning(db::schema::substitutions::id) .returning(db::schema::substitutions::id)
.get_result::<i32>(db_conn)?; .get_result::<i64>(db_conn)?;
diesel::insert_into(db::schema::substitution_classes::table) diesel::insert_into(db::schema::substitution_classes::table)
.values( .values(
@ -525,7 +250,7 @@ fn get_period(times: &Vec<StartEndTime>, start: bool, time: NaiveTime) -> Option
fn cache_substitutions( fn cache_substitutions(
db_conn: &mut PgConnection, db_conn: &mut PgConnection,
redis_conn: &mut cache::Connection, redis_conn: &mut cache::Connection,
substitution_query_id: i32, substitution_query_id: i64,
last_import_time: NaiveDateTime, last_import_time: NaiveDateTime,
) -> Result<()> { ) -> Result<()> {
let (queried_at, date, week_type) = db::schema::substitution_queries::table let (queried_at, date, week_type) = db::schema::substitution_queries::table
@ -717,7 +442,7 @@ fn cache_substitutions(
} }
#[celery::task] #[celery::task]
pub async fn update_info() -> TaskResult<()> { pub async fn get_substitutions() -> TaskResult<()> {
let dur = Duration::from_secs(2); let dur = Duration::from_secs(2);
thread::sleep(dur); thread::sleep(dur);
@ -744,38 +469,6 @@ pub async fn update_info() -> TaskResult<()> {
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),
}; };
thread::sleep(dur); thread::sleep(dur);
if let Err(e) = fetch_current_tenant(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_timegrid(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_teachers(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_classes(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_subjects(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_rooms(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_departments(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_holidays(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
let last_import_time = match client.last_import_time().await { let last_import_time = match client.last_import_time().await {
Ok(x) => x, Ok(x) => x,
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),

View file

@ -7,7 +7,8 @@ use stdext::duration::DurationExt;
use crate::config; use crate::config;
pub mod update_info; pub mod cleanup;
pub mod get_substitutions;
pub mod update_meta; pub mod update_meta;
pub const QUEUE_NAME: &str = "celery"; pub const QUEUE_NAME: &str = "celery";
@ -22,8 +23,9 @@ pub async fn init() {
celery::app!( celery::app!(
broker = AMQPBroker { &config::CONFIG.amqp_url }, broker = AMQPBroker { &config::CONFIG.amqp_url },
tasks = [ tasks = [
update_info::update_info, get_substitutions::get_substitutions,
update_meta::update_meta, update_meta::update_meta,
cleanup::cleanup,
], ],
task_routes = [ task_routes = [
"*" => QUEUE_NAME, "*" => QUEUE_NAME,
@ -45,15 +47,20 @@ pub fn beat() -> impl std::future::Future<
celery::beat!( celery::beat!(
broker = AMQPBroker { &config::CONFIG.amqp_url }, broker = AMQPBroker { &config::CONFIG.amqp_url },
tasks = [ tasks = [
"update_info" => { "get_substitutions" => {
update_info::update_info, get_substitutions::get_substitutions,
schedule = DeltaSchedule::new(Duration::from_minutes(999)), schedule = DeltaSchedule::new(Duration::from_minutes(5)),
args = (), args = (),
}, },
"update_meta" => { "update_meta" => {
update_meta::update_meta, update_meta::update_meta,
schedule = DeltaSchedule::new(Duration::from_hours(6)), schedule = DeltaSchedule::new(Duration::from_hours(6)),
args = (), args = (),
},
"cleanup" => {
cleanup::cleanup,
schedule = DeltaSchedule::new(Duration::from_hours(1)),
args = (),
} }
], ],
task_routes = [ task_routes = [

View file

@ -43,6 +43,281 @@ async fn fetch_schoolyears(client: &untis::Client, db_conn: &mut db::Connection)
Ok(id) Ok(id)
} }
async fn fetch_current_tenant(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let tenant = client.current_tenant().await?;
if diesel::select(diesel::dsl::not(diesel::dsl::exists(
db::schema::tenants::table.filter(db::schema::tenants::untis_id.eq(tenant.id)),
)))
.get_result::<bool>(db_conn)?
{
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::active)
.set(db::schema::tenants::active.eq(false))
.execute(db_conn)?;
diesel::insert_into(db::schema::tenants::table)
.values(db::models::NewTenant {
untis_id: tenant.id,
schoolyear_id,
name: &tenant.display_name,
active: true,
})
.execute(db_conn)?;
} else if diesel::select(diesel::dsl::exists(
db::schema::tenants::table
.filter(db::schema::tenants::untis_id.eq(tenant.id))
.filter(db::schema::tenants::active.eq(false)),
))
.get_result::<bool>(db_conn)?
{
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::active)
.set((
db::schema::tenants::active.eq(false),
db::schema::tenants::updated_at.eq(diesel::dsl::now),
))
.execute(db_conn)?;
diesel::update(db::schema::tenants::table)
.filter(db::schema::tenants::untis_id.eq(tenant.id))
.set((
db::schema::tenants::active.eq(true),
db::schema::tenants::updated_at.eq(diesel::dsl::now),
))
.execute(db_conn)?;
}
Ok(())
}
async fn fetch_timegrid(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let days = client.timegrid().await?;
diesel::update(db::schema::timegrids::table)
.filter(db::schema::timegrids::active)
.set(db::schema::timegrids::active.eq(false))
.execute(db_conn)?;
let timegrid_id = diesel::insert_into(db::schema::timegrids::table)
.values(db::models::NewTimegrid {
schoolyear_id,
active: true,
})
.returning(db::schema::timegrids::id)
.get_result::<i32>(db_conn)?;
for day in days {
let timegrid_day_id = diesel::insert_into(db::schema::timegrid_days::table)
.values(db::models::NewTimegridDay {
timegrid_id,
weekday: day.day.try_into()?,
})
.returning(db::schema::timegrid_days::id)
.get_result::<i32>(db_conn)?;
diesel::insert_into(db::schema::timegrid_time_unit::table)
.values(
day.time_units
.into_iter()
.map(|x| db::models::NewTimegridTimeUnit {
timegrid_day_id,
start_time: x.start_time,
end_time: x.end_time,
})
.collect::<Vec<_>>(),
)
.execute(db_conn)?;
}
Ok(())
}
async fn fetch_teachers(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_teachers = db::schema::teachers::table
.select(db::schema::teachers::untis_id)
.filter(db::schema::teachers::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::teachers::table)
.values(
&client
.teachers()
.await?
.iter()
.filter(|t| !existing_teachers.contains(&t.id))
.map(|t| db::models::NewTeacher {
untis_id: t.id,
schoolyear_id,
name: &t.name,
forename: if t.forename.is_empty() {
None
} else {
Some(&t.forename)
},
display_name: &t.display_name,
})
.collect::<Vec<db::models::NewTeacher>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_classes(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::classes::table
.select(db::schema::classes::untis_id)
.filter(db::schema::classes::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::classes::table)
.values(
&client
.classes()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewClass {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
active: c.active,
})
.collect::<Vec<db::models::NewClass>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_subjects(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::subjects::table
.select(db::schema::subjects::untis_id)
.filter(db::schema::subjects::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::subjects::table)
.values(
&client
.subjects()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewSubject {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewSubject>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_rooms(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::rooms::table
.select(db::schema::rooms::untis_id)
.filter(db::schema::rooms::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::rooms::table)
.values(
&client
.rooms()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewRoom {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewRoom>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_departments(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::departments::table
.select(db::schema::departments::untis_id)
.filter(db::schema::departments::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::departments::table)
.values(
&client
.departments()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewDepartment {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
})
.collect::<Vec<db::models::NewDepartment>>(),
)
.execute(db_conn)?;
Ok(())
}
async fn fetch_holidays(
client: &untis::Client,
db_conn: &mut PgConnection,
schoolyear_id: i32,
) -> Result<()> {
let existing_classes = db::schema::holidays::table
.select(db::schema::holidays::untis_id)
.filter(db::schema::holidays::schoolyear_id.eq(schoolyear_id))
.load::<i32>(db_conn)?;
diesel::insert_into(db::schema::holidays::table)
.values(
&client
.holidays()
.await?
.iter()
.filter(|c| !existing_classes.contains(&c.id))
.map(|c| db::models::NewHoliday {
untis_id: c.id,
schoolyear_id,
name: &c.name,
long_name: &c.long_name,
start_date: c.start_date,
end_date: c.end_date,
})
.collect::<Vec<db::models::NewHoliday>>(),
)
.execute(db_conn)?;
Ok(())
}
#[celery::task] #[celery::task]
pub async fn update_meta() -> TaskResult<()> { pub async fn update_meta() -> TaskResult<()> {
let dur = Duration::from_secs(2); let dur = Duration::from_secs(2);
@ -50,6 +325,11 @@ pub async fn update_meta() -> TaskResult<()> {
Ok(x) => x, Ok(x) => x,
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),
}; };
if let Err(e) = client.login().await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
let db_conn = &mut match db::POOL.get() { let db_conn = &mut match db::POOL.get() {
Ok(x) => x, Ok(x) => x,
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),
@ -60,8 +340,38 @@ pub async fn update_meta() -> TaskResult<()> {
Ok(x) => x, Ok(x) => x,
Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))),
}; };
dbg!(&schoolyear_id);
thread::sleep(dur); thread::sleep(dur);
if let Err(e) = fetch_current_tenant(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_timegrid(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_teachers(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_classes(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_subjects(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_rooms(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_departments(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
thread::sleep(dur);
if let Err(e) = fetch_holidays(&client, db_conn, schoolyear_id).await {
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
}
if let Err(e) = client.logout().await { if let Err(e) = client.logout().await {
return Err(TaskError::UnexpectedError(format!("{:?}", e))); return Err(TaskError::UnexpectedError(format!("{:?}", e)));

View file

@ -8,7 +8,7 @@
<meta name="generator" content="BVplan" /> <meta name="generator" content="BVplan" />
<script id="data" type="application/json">{{ data.substitutions.len()|json|safe }}</script> <script id="data-element-count" type="application/json">{{ data.substitutions.len()|json|safe }}</script>
<script <script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
@ -227,8 +227,8 @@
Wow Wow
</p> </p>
<p> <p>
<a href="https://git.dergrimm.net/bvplan"> <a href="https://git.dergrimm.net/dergrimm/bvplan">
https://git.dergrimm.net/bvplan https://git.dergrimm.net/dergrimm/bvplan
</a> </a>
</p> </p>
</div> </div>
@ -252,10 +252,14 @@
}, 30_000); }, 30_000);
}; };
const elementCount = JSON.parse($("#data").text()); const elementCount = JSON.parse($("#data-element-count").text());
const waitDelay = 10_000; const waitDelay = 10_000;
const scrollDuration = elementCount * 200; const scrollDuration = elementCount * 200;
function reload() {
window.location.reload();
}
function scrollUp() { function scrollUp() {
$("html, body").animate( $("html, body").animate(
{ {
@ -264,9 +268,7 @@
scrollDuration, scrollDuration,
"linear" "linear"
); );
setTimeout(function () { setTimeout(reload, scrollDuration);
window.location.reload();
}, scrollDuration);
} }
function scrollDown() { function scrollDown() {
@ -281,7 +283,7 @@
} }
window.scrollTo(0, 0); window.scrollTo(0, 0);
setTimeout(scrollDown, waitDelay); setTimeout($(window).height() > $(window).height() ? scrollDown : reload, waitDelay);
</script> </script>
</body> </body>
</html> </html>

View file

@ -83,6 +83,8 @@ services:
<<: *bvplan <<: *bvplan
command: worker command: worker
volumes: volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ./data/backups:/backups - ./data/backups:/backups
web: web:
@ -93,6 +95,9 @@ services:
- rabbitmq - rabbitmq
- worker - worker
- redis - redis
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
volumes: volumes:
db: db: