Update
This commit is contained in:
parent
d9be1b6783
commit
c9b11aa165
12 changed files with 474 additions and 251 deletions
72
bvplan/Cargo.lock
generated
72
bvplan/Cargo.lock
generated
|
@ -183,6 +183,18 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-static-files"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"static-files",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.19.0"
|
||||
|
@ -316,6 +328,17 @@ dependencies = [
|
|||
"askama_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_actix"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c52f74f8382a142ecfc052100b21abc33f2c069e20fe345808e7ed914b179449"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"askama",
|
||||
"askama_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.11.2"
|
||||
|
@ -603,8 +626,10 @@ name = "bvplan"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"actix-web-static-files",
|
||||
"anyhow",
|
||||
"askama",
|
||||
"askama_actix",
|
||||
"celery",
|
||||
"chrono",
|
||||
"diesel",
|
||||
|
@ -620,6 +645,7 @@ dependencies = [
|
|||
"scraper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static-files",
|
||||
"stdext",
|
||||
"tikv-jemallocator",
|
||||
"tokio",
|
||||
|
@ -711,6 +737,16 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "change-detection"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a"
|
||||
dependencies = [
|
||||
"path-matchers",
|
||||
"path-slash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
|
@ -1366,6 +1402,12 @@ version = "0.27.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
|
@ -1381,9 +1423,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -2037,6 +2079,21 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||
|
||||
[[package]]
|
||||
name = "path-matchers"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
|
@ -2836,6 +2893,17 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static-files"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64712ea1e3e140010e1d9605872ba205afa2ab5bd38191cc6ebd248ae1f6a06b"
|
||||
dependencies = [
|
||||
"change-detection",
|
||||
"mime_guess",
|
||||
"path-slash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdext"
|
||||
version = "0.3.1"
|
||||
|
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
|
||||
[[bin]]
|
||||
name = "web"
|
||||
build = "build.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "worker"
|
||||
|
@ -17,8 +18,10 @@ panic = "abort"
|
|||
|
||||
[dependencies]
|
||||
actix-web = "4.2.1"
|
||||
actix-web-static-files = "4.0.1"
|
||||
anyhow = { version = "1.0.66", features = ["backtrace"] }
|
||||
askama = { version = "0.11.1", features = ["serde-json"] }
|
||||
askama_actix = "0.13.0"
|
||||
celery = { git = "https://github.com/rusty-celery/rusty-celery.git", branch = "main" }
|
||||
chrono = "0.4.23"
|
||||
diesel = { version = "2.0.2", features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes", "postgres", "chrono", "r2d2"] }
|
||||
|
@ -34,6 +37,7 @@ reqwest = "0.11.13"
|
|||
scraper = "0.14.0"
|
||||
serde = "1.0.148"
|
||||
serde_json = "1.0.93"
|
||||
static-files = "0.2.3"
|
||||
stdext = "0.3.1"
|
||||
tokio = { version = "1.22.0", features = ["full"] }
|
||||
untis = { git = "https://git.dergrimm.net/dergrimm/untis.rs.git", branch = "main" }
|
||||
|
@ -41,3 +45,6 @@ url = "2.3.1"
|
|||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = "0.5"
|
||||
|
||||
[build-dependencies]
|
||||
static-files = "0.2.3"
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
FROM codycraven/sassc:latest as css
|
||||
WORKDIR /usr/src/scss
|
||||
RUN mkdir dist
|
||||
WORKDIR /usr/src/scss/src
|
||||
COPY ./scss .
|
||||
RUN find . -name "*.scss" -type f | xargs -I % sh -c 'sassc % > ../dist/$(basename -- "%" .scss).css'
|
||||
|
||||
FROM tdewolff/minify:latest as static
|
||||
WORKDIR /usr/src/static
|
||||
COPY --from=css /usr/src/scss/dist ./css
|
||||
RUN minify . -r -o .
|
||||
|
||||
FROM docker.io/lukemathwalker/cargo-chef:latest-rust-1.65.0 as chef
|
||||
|
||||
FROM chef as diesel
|
||||
|
@ -13,6 +25,9 @@ FROM chef as builder
|
|||
WORKDIR /usr/src/backend
|
||||
COPY --from=planner /usr/src/backend/recipe.json .
|
||||
RUN cargo chef cook --recipe-path recipe.json
|
||||
RUN rm -rf ./src
|
||||
COPY ./build.rs .
|
||||
COPY --from=static /usr/src/static ./static
|
||||
COPY ./templates ./templates
|
||||
COPY ./src ./src
|
||||
RUN cargo build
|
||||
|
|
5
bvplan/build.rs
Normal file
5
bvplan/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use static_files::resource_dir;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
resource_dir("./static").build()
|
||||
}
|
102
bvplan/scss/bvplan.scss
Normal file
102
bvplan/scss/bvplan.scss
Normal file
|
@ -0,0 +1,102 @@
|
|||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
margin: 0 5vw;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#info {
|
||||
margin-top: 2vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
& > .column {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#title-wrapper {
|
||||
color: #ee7f00;
|
||||
|
||||
& > * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tenant-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#plan {
|
||||
width: 100%;
|
||||
margin-top: 1%;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
|
||||
caption {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: thin solid black;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:not(:nth-child(even)) {
|
||||
background-color: #fad3a6;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #fdecd9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#footer {
|
||||
margin: auto 0 2vh;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: thin solid black;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.element {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
}
|
|
@ -5,14 +5,28 @@ use tikv_jemallocator::Jemalloc;
|
|||
#[global_allocator]
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
use actix_web::{get, middleware, web::Data, App, HttpResponse, HttpServer};
|
||||
use actix_web::{get, http, middleware, web, App, HttpResponse, HttpServer};
|
||||
use actix_web_static_files::ResourceFiles;
|
||||
use r2d2_redis::redis;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use bvplan::*;
|
||||
use templates::TemplateToResponseWithStatusCode;
|
||||
|
||||
pub mod static_dir {
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
}
|
||||
|
||||
async fn not_found() -> HttpResponse {
|
||||
templates::StatusCode {
|
||||
status_code: http::StatusCode::NOT_FOUND,
|
||||
message: None,
|
||||
}
|
||||
.to_response_with_status_code(http::StatusCode::NOT_FOUND)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index(redis_pool: Data<cache::RedisPool>) -> HttpResponse {
|
||||
async fn index(redis_pool: web::Data<cache::RedisPool>) -> HttpResponse {
|
||||
let redis_conn = &mut match redis_pool.get() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
||||
|
@ -37,12 +51,19 @@ async fn index(redis_pool: Data<cache::RedisPool>) -> HttpResponse {
|
|||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
HttpServer::new(move || {
|
||||
let generated = static_dir::generate();
|
||||
|
||||
App::new()
|
||||
.app_data(Data::new(cache::pool().unwrap()))
|
||||
.app_data(web::Data::new(cache::pool().unwrap()))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
});
|
||||
server.bind("0.0.0.0:80").unwrap().run().await
|
||||
.service(ResourceFiles::new("/static", generated))
|
||||
.default_service(web::route().to(not_found))
|
||||
})
|
||||
.bind("0.0.0.0:80")
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -37,16 +37,16 @@ pub mod keys {
|
|||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Substitution {
|
||||
#[serde(rename = "tm")]
|
||||
pub time: (usize, Option<usize>),
|
||||
#[serde(rename = "p")]
|
||||
pub period: usize,
|
||||
#[serde(rename = "cl")]
|
||||
pub classes: Vec<String>,
|
||||
#[serde(rename = "ps")]
|
||||
pub prev_subject: String,
|
||||
pub prev_subject: Option<String>,
|
||||
#[serde(rename = "s")]
|
||||
pub subject: Option<String>,
|
||||
#[serde(rename = "t")]
|
||||
pub teachers: Vec<String>,
|
||||
pub teachers: Option<Vec<String>>,
|
||||
#[serde(rename = "pr")]
|
||||
pub prev_room: Option<String>,
|
||||
#[serde(rename = "r")]
|
||||
|
|
|
@ -1,9 +1,51 @@
|
|||
use actix_web::{body::BoxBody, http, HttpResponse, HttpResponseBuilder, ResponseError};
|
||||
use askama::Template;
|
||||
use std::fmt;
|
||||
|
||||
use crate::cache;
|
||||
|
||||
struct ActixError(askama::Error);
|
||||
|
||||
impl fmt::Debug for ActixError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<askama::Error as fmt::Debug>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ActixError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
<askama::Error as fmt::Display>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ActixError {}
|
||||
|
||||
pub trait TemplateToResponseWithStatusCode {
|
||||
fn to_response_with_status_code(&self, code: http::StatusCode) -> HttpResponse<BoxBody>;
|
||||
}
|
||||
|
||||
impl<T: askama_actix::Template> TemplateToResponseWithStatusCode for T {
|
||||
fn to_response_with_status_code(&self, code: http::StatusCode) -> HttpResponse<BoxBody> {
|
||||
match self.render() {
|
||||
Ok(buffer) => HttpResponseBuilder::new(code)
|
||||
.content_type(http::header::HeaderValue::from_static(T::MIME_TYPE))
|
||||
.body(buffer),
|
||||
Err(err) => HttpResponse::from_error(ActixError(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "bvplan.html")]
|
||||
pub struct BVplan {
|
||||
pub data: cache::keys::substitutions::SubstitutionQuery,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "status_code.html")]
|
||||
pub struct StatusCode {
|
||||
pub status_code: http::StatusCode,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ fn get_period(times: &Vec<StartEndTime>, start: bool, time: NaiveTime) -> Option
|
|||
fn cache_substitutions(
|
||||
db_conn: &mut PgConnection,
|
||||
redis_conn: &mut cache::Connection,
|
||||
schoolyear_id: i32,
|
||||
substitution_query_id: i64,
|
||||
last_import_time: NaiveDateTime,
|
||||
) -> Result<()> {
|
||||
|
@ -287,6 +288,147 @@ fn cache_substitutions(
|
|||
.order(db::schema::timegrid_time_unit::start_time.asc())
|
||||
.load::<StartEndTime>(db_conn)?;
|
||||
|
||||
let mut substitutions = db::schema::substitutions::table
|
||||
.filter(db::schema::substitutions::substitution_query_id.eq(substitution_query_id))
|
||||
.load::<db::models::Substitution>(db_conn)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
if let Some(subst_subject) = db::schema::substitution_subjects::table
|
||||
.filter(db::schema::substitution_subjects::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_subjects::position.asc())
|
||||
.first::<db::models::SubstitutionSubject>(db_conn)
|
||||
.optional()?
|
||||
{
|
||||
let subst_class_ids = db::schema::substitution_classes::table
|
||||
.select(db::schema::substitution_classes::class_id)
|
||||
.filter(db::schema::substitution_classes::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_classes::position.asc())
|
||||
.load::<i32>(db_conn)?;
|
||||
let classes = db::schema::classes::table
|
||||
.select(db::schema::classes::name)
|
||||
.filter(db::schema::classes::id.eq_any(subst_class_ids))
|
||||
.load::<String>(db_conn)?;
|
||||
|
||||
let subst_teachers = db::schema::substitution_teachers::table
|
||||
.filter(db::schema::substitution_teachers::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_teachers::position.asc())
|
||||
.load::<db::models::SubstitutionTeacher>(db_conn)?;
|
||||
|
||||
let (prev_room, room) = if let Some(r) = db::schema::substitution_rooms::table
|
||||
.filter(db::schema::substitution_rooms::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_rooms::position.asc())
|
||||
.first::<db::models::SubstitutionRoom>(db_conn)
|
||||
.optional()?
|
||||
{
|
||||
let name = if let Some(id) = r.room_id {
|
||||
Some(
|
||||
db::schema::rooms::table
|
||||
.select(db::schema::rooms::name)
|
||||
.filter(db::schema::rooms::id.eq(id))
|
||||
.first::<String>(db_conn)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => (name, None),
|
||||
_ => (
|
||||
if let Some(id) = r.original_id {
|
||||
Some(
|
||||
db::schema::rooms::table
|
||||
.select(db::schema::rooms::name)
|
||||
.filter(db::schema::rooms::id.eq(id))
|
||||
.first::<String>(db_conn)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
name,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let (prev_subject, subject) = {
|
||||
let name = db::schema::subjects::table
|
||||
.select(db::schema::subjects::name)
|
||||
.filter(db::schema::subjects::id.eq(subst_subject.subject_id))
|
||||
.first::<String>(db_conn)?;
|
||||
|
||||
match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => (Some(name), None),
|
||||
_ => {
|
||||
if let Some(prev_id) = subst_subject.original_id {
|
||||
(
|
||||
Some(
|
||||
db::schema::subjects::table
|
||||
.select(db::schema::subjects::name)
|
||||
.filter(db::schema::subjects::id.eq(prev_id))
|
||||
.first::<String>(db_conn)?,
|
||||
),
|
||||
Some(name),
|
||||
)
|
||||
} else {
|
||||
(Some(name.to_owned()), Some(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(cache::keys::substitutions::Substitution {
|
||||
period: get_period(×, true, s.start_time)
|
||||
.context("Could not find period from start time")?,
|
||||
classes,
|
||||
prev_subject,
|
||||
subject,
|
||||
teachers: match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => None,
|
||||
_ => {
|
||||
let x = db::schema::teachers::table
|
||||
.select(db::schema::teachers::display_name)
|
||||
.filter(
|
||||
db::schema::teachers::id.eq_any(
|
||||
subst_teachers
|
||||
.iter()
|
||||
.filter_map(|t| t.teacher_id)
|
||||
.collect::<Vec<i32>>(),
|
||||
),
|
||||
)
|
||||
.load::<String>(db_conn)?;
|
||||
|
||||
if x.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(x)
|
||||
}
|
||||
}
|
||||
},
|
||||
prev_room,
|
||||
room,
|
||||
text: s.text,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter_map(|s| s)
|
||||
.collect::<Vec<_>>();
|
||||
substitutions.sort_by_key(|x| {
|
||||
(
|
||||
x.classes.get(0).and_then(|x| {
|
||||
x.split_once(' ')
|
||||
.and_then(|y| y.0.parse::<u32>().map_or(None, |s| Some(s)))
|
||||
}),
|
||||
x.period,
|
||||
)
|
||||
});
|
||||
|
||||
let query = cache::keys::substitutions::SubstitutionQuery {
|
||||
date: format!(
|
||||
"{} {}",
|
||||
|
@ -304,123 +446,15 @@ fn cache_substitutions(
|
|||
week_type,
|
||||
queried_at: queried_at.format("%d.%m.%Y %R").to_string(),
|
||||
last_import_time: last_import_time.format("%d.%m.%Y %R").to_string(),
|
||||
schoolyear: "schoolyear".to_string(),
|
||||
tenant: "OHG Furtwangen".to_string(),
|
||||
substitutions: db::schema::substitutions::table
|
||||
.filter(db::schema::substitutions::substitution_query_id.eq(substitution_query_id))
|
||||
.load::<db::models::Substitution>(db_conn)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
if let Some(subst_subject) = db::schema::substitution_subjects::table
|
||||
.filter(db::schema::substitution_subjects::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_subjects::position.asc())
|
||||
.first::<db::models::SubstitutionSubject>(db_conn)
|
||||
.optional()?
|
||||
{
|
||||
let subst_class_ids = db::schema::substitution_classes::table
|
||||
.select(db::schema::substitution_classes::class_id)
|
||||
.filter(db::schema::substitution_classes::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_classes::position.asc())
|
||||
.load::<i32>(db_conn)?;
|
||||
let classes = db::schema::classes::table
|
||||
.select(db::schema::classes::name)
|
||||
.filter(db::schema::classes::id.eq_any(subst_class_ids))
|
||||
.load::<String>(db_conn)?;
|
||||
|
||||
let subst_teachers = db::schema::substitution_teachers::table
|
||||
.filter(db::schema::substitution_teachers::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_teachers::position.asc())
|
||||
.load::<db::models::SubstitutionTeacher>(db_conn)?;
|
||||
|
||||
let (prev_room, room) = if let Some(r) = db::schema::substitution_rooms::table
|
||||
.filter(db::schema::substitution_rooms::substitution_id.eq(s.id))
|
||||
.order(db::schema::substitution_rooms::position.asc())
|
||||
.first::<db::models::SubstitutionRoom>(db_conn)
|
||||
.optional()?
|
||||
{
|
||||
let name = if let Some(id) = r.room_id {
|
||||
Some(
|
||||
db::schema::rooms::table
|
||||
.select(db::schema::rooms::name)
|
||||
.filter(db::schema::rooms::id.eq(id))
|
||||
.first::<String>(db_conn)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => (name, None),
|
||||
_ => (
|
||||
if let Some(id) = r.original_id {
|
||||
Some(
|
||||
db::schema::rooms::table
|
||||
.select(db::schema::rooms::name)
|
||||
.filter(db::schema::rooms::id.eq(id))
|
||||
.first::<String>(db_conn)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
name,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let prev_subject = db::schema::subjects::table
|
||||
.select(db::schema::subjects::name)
|
||||
.filter(db::schema::subjects::id.eq(subst_subject.subject_id))
|
||||
.first::<String>(db_conn)?;
|
||||
|
||||
let start_period = get_period(×, true, s.start_time)
|
||||
.context("Could not find period from start time")?;
|
||||
|
||||
Ok(Some(cache::keys::substitutions::Substitution {
|
||||
time: (start_period, None),
|
||||
classes,
|
||||
prev_subject: prev_subject.to_owned(),
|
||||
subject: match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => None,
|
||||
_ => match subst_subject.original_id {
|
||||
Some(id) => Some(
|
||||
db::schema::subjects::table
|
||||
.select(db::schema::subjects::name)
|
||||
.filter(db::schema::subjects::id.eq(id))
|
||||
.first::<String>(db_conn)?,
|
||||
),
|
||||
None => Some(prev_subject),
|
||||
},
|
||||
},
|
||||
teachers: match s.subst_type {
|
||||
db::models::SubstitutionType::Cancel => vec![],
|
||||
_ => db::schema::teachers::table
|
||||
.select(db::schema::teachers::display_name)
|
||||
.filter(
|
||||
db::schema::teachers::id.eq_any(
|
||||
subst_teachers
|
||||
.iter()
|
||||
.filter_map(|t| t.teacher_id)
|
||||
.collect::<Vec<i32>>(),
|
||||
),
|
||||
)
|
||||
.load::<String>(db_conn)?,
|
||||
},
|
||||
prev_room,
|
||||
room,
|
||||
text: s.text,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter_map(|s| s)
|
||||
.collect::<Vec<_>>(),
|
||||
schoolyear: db::schema::schoolyears::table
|
||||
.select(db::schema::schoolyears::name)
|
||||
.filter(db::schema::schoolyears::id.eq(schoolyear_id))
|
||||
.first::<String>(db_conn)?,
|
||||
tenant: db::schema::tenants::table
|
||||
.select(db::schema::tenants::name)
|
||||
.filter(db::schema::tenants::active)
|
||||
.first::<String>(db_conn)?,
|
||||
substitutions,
|
||||
};
|
||||
|
||||
fs::write(
|
||||
|
@ -497,9 +531,13 @@ pub async fn get_substitutions() -> TaskResult<()> {
|
|||
}
|
||||
// thread::sleep(dur);
|
||||
|
||||
if let Err(e) =
|
||||
cache_substitutions(db_conn, redis_conn, substitution_query_id, last_import_time)
|
||||
{
|
||||
if let Err(e) = cache_substitutions(
|
||||
db_conn,
|
||||
redis_conn,
|
||||
schoolyear_id,
|
||||
substitution_query_id,
|
||||
last_import_time,
|
||||
) {
|
||||
return Err(TaskError::UnexpectedError(format!("{:?}", e)));
|
||||
}
|
||||
|
||||
|
|
0
bvplan/static/.keep
Normal file
0
bvplan/static/.keep
Normal file
|
@ -4,9 +4,10 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="generator" content="BVplan" />
|
||||
<title>BVplan | dergrimm.net</title>
|
||||
|
||||
<meta name="generator" content="BVplan" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/bvplan.css" />
|
||||
|
||||
<script id="data-element-count" type="application/json">{{ data.substitutions.len()|json|safe }}</script>
|
||||
|
||||
|
@ -16,114 +17,6 @@
|
|||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
margin: 0 5vw;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#info {
|
||||
margin-top: 1vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#info > .column {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
#title-wrapper {
|
||||
color: #ee7f00;
|
||||
}
|
||||
|
||||
#title-wrapper > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tenant-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#plan {
|
||||
width: 100%;
|
||||
margin-top: 1%;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
#plan thead,
|
||||
#plan tbody {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#plan > caption {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#plan th,
|
||||
#plan td {
|
||||
border: thin solid black;
|
||||
}
|
||||
|
||||
#plan thead {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#plan th {
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
#plan td {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
#plan tbody {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#plan tbody tr:not(:nth-child(even)) {
|
||||
background-color: #fad3a6;
|
||||
}
|
||||
|
||||
#plan tbody tr:nth-child(even) {
|
||||
background-color: #fdecd9;
|
||||
}
|
||||
|
||||
#footer {
|
||||
margin: auto 0 1vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#footer p {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
#footer > hr {
|
||||
border: thin solid black;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
#footer .message,
|
||||
#footer .powered-by {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header id="info">
|
||||
|
@ -173,16 +66,16 @@
|
|||
<tbody>
|
||||
{% for subst in data.substitutions %}
|
||||
<tr>
|
||||
<td>{{ subst.period }}</td>
|
||||
<td>{{ subst.classes|join(", ") }}</td>
|
||||
<td>
|
||||
{% match subst.time.1 %}
|
||||
{% match subst.prev_subject %}
|
||||
{% when Some with (x) %}
|
||||
{{ subst.time.0 }} - {{ x }}
|
||||
{{ x }}
|
||||
{% when None %}
|
||||
{{ subst.time.0 }}
|
||||
---
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>{{ subst.classes|join(", ") }}</td>
|
||||
<td>{{ subst.prev_subject }}</td>
|
||||
<td>
|
||||
{% match subst.subject %}
|
||||
{% when Some with (x) %}
|
||||
|
@ -191,7 +84,14 @@
|
|||
---
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>{{ subst.teachers|join(", ") }}</td>
|
||||
<td>
|
||||
{% match subst.teachers %}
|
||||
{% when Some with (x) %}
|
||||
{{ x|join(", ") }}
|
||||
{% when None %}
|
||||
---
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>
|
||||
{% match subst.prev_room %}
|
||||
{% when Some with (x) %}
|
||||
|
@ -222,7 +122,7 @@
|
|||
</main>
|
||||
|
||||
<footer id="footer">
|
||||
<div class="message">
|
||||
<div class="element">
|
||||
<p>
|
||||
BVplan - der bessre Vertretungsplan sogar mit UTF-8 Support!
|
||||
Wow
|
||||
|
@ -234,12 +134,12 @@
|
|||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<p class="powered-by">
|
||||
<p class="element">
|
||||
<code>
|
||||
Powered by Dominic Grimm <<a
|
||||
href="mailto:dominic@dergrimm.net"
|
||||
>dominic@dergrimm.net</a
|
||||
>>
|
||||
>>, Untis sucks
|
||||
</code>
|
||||
</p>
|
||||
</footer>
|
||||
|
@ -284,7 +184,7 @@
|
|||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
setTimeout($(window).height() > $(window).height() ? scrollDown : reload, waitDelay);
|
||||
setTimeout($(document).height() > $(window).height() ? scrollDown : reload, waitDelay);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
25
bvplan/templates/status_code.html
Normal file
25
bvplan/templates/status_code.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de-DE">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="generator" content="BVplan" />
|
||||
<title>BVplan | dergrimm.net</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ status_code }}</h1>
|
||||
{% match message %}
|
||||
{% when Some with (x) %}
|
||||
<hr />
|
||||
<p>{{ x }}</p>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue