Skip to content

Commit 3bcb12d

Browse files
committed
General Update
Adds functional parsing tool, updates project libraries, general optimizations and fixes to match database schema and constraints
1 parent cc93ce9 commit 3bcb12d

File tree

10 files changed

+1261
-600
lines changed

10 files changed

+1261
-600
lines changed

backend/Cargo.lock

Lines changed: 414 additions & 457 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/Cargo.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ edition = "2021"
1010
serde = { version = "1.0.99", features = ["derive"] }
1111
csv = "1.1.1"
1212
clap = { version = "4.3.10", features = ["cargo", "derive"] }
13-
sqlx = { version = "0.7.0", features = ["runtime-tokio-native-tls", "postgres", "chrono", "macros"] }
13+
sqlx = { version = "0.7.0", features = [
14+
"runtime-tokio-native-tls",
15+
"postgres",
16+
"chrono",
17+
"macros",
18+
] }
1419
actix-cors = "0.6.4"
1520
actix-multipart = "0.6.0"
1621
actix-test = "0.1.1"
@@ -20,7 +25,11 @@ serde_json = { version = "1.0.102", features = ["preserve_order"] }
2025
dotenvy = "0.15.7"
2126
typenum = { version = "1.16.0", features = ["force_unix_path_separator"] }
2227
lazy_static = "1.4.0"
23-
utoipa = { version = "4.2.0", features = ["actix_extras", "openapi_extensions", "chrono"] }
28+
utoipa = { version = "4.2.0", features = [
29+
"actix_extras",
30+
"openapi_extensions",
31+
"chrono",
32+
] }
2433
utoipa-swagger-ui = { version = "6.0.0", features = ["actix-web"] }
2534
itertools = "0.12.0"
2635
chrono = { version = "0.4.33", features = ["serde"] }

backend/src/api/departments.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use actix_web::{HttpResponse, Responder, get, web::Data};
2-
use sqlx::query_as;
3-
use std::format;
4-
use log::{log, Level};
51
use crate::api::AppState;
62
use crate::model::Department;
7-
use crate::db::log_query_as;
3+
use actix_web::{get, web::Data, HttpResponse, Responder};
4+
use log::{log, Level};
5+
use sqlx::query_as;
6+
use std::format;
87

98
#[utoipa::path(
109
context_path = "/api",
@@ -17,18 +16,13 @@ use crate::db::log_query_as;
1716
pub async fn get_departments(state: Data<AppState>) -> impl Responder {
1817
log!(Level::Info, "GET /departments");
1918

20-
match log_query_as(
21-
query_as!(
19+
match query_as!(
2220
Department,
2321
"SELECT DISTINCT d.code AS dept_code, d.title AS dept_title, s.code AS school_code, s.title AS school_title FROM departments d, schools s WHERE d.school = s.id",
2422
)
2523
.fetch_all(&state.db)
26-
.await,
27-
None,
28-
)
2924
.await {
30-
Ok((_, depts)) => HttpResponse::Ok().json(depts),
31-
Err(e) => return e,
25+
Ok(depts) => HttpResponse::Ok().json(depts),
26+
Err(e) => HttpResponse::InternalServerError().json(e.to_string()),
3227
}
3328
}
34-

backend/src/api/get_course_options.rs

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
use actix_web::{post, web::{Json, Data}, HttpResponse, Responder};
2-
use sqlx::{QueryBuilder, Postgres};
1+
use crate::{
2+
api::AppState,
3+
model::{Building, CourseOption, Search, SectionInfo, SectionTimeInfo, Time, WeekDay},
4+
};
5+
use actix_web::{
6+
post,
7+
web::{Data, Json},
8+
HttpResponse, Responder,
9+
};
310
use log::{log, Level};
4-
use crate::api::AppState;
5-
use crate::model::{Search, SectionInfo, CourseOption, SectionTimeInfo, Time, Building, WeekDay};
6-
use crate::db::log_query_as;
11+
use sqlx::{Postgres, QueryBuilder};
712

813
#[utoipa::path(
914
context_path = "/api",
@@ -14,14 +19,12 @@ use crate::db::log_query_as;
1419
)
1520
)]
1621
#[post("/generate/getCourseOpts")]
17-
pub async fn get_course_options(
18-
state: Data<AppState>,
19-
options: Json<Search>,
20-
) -> impl Responder {
22+
pub async fn get_course_options(state: Data<AppState>, options: Json<Search>) -> impl Responder {
2123
log!(Level::Info, "GET /generate/getCourseOpts");
2224
let course_number = format!("{}%", options.course).to_uppercase();
2325

24-
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new("
26+
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
27+
"
2528
SELECT c.id AS course_id,
2629
c.course,
2730
c.title,
@@ -38,20 +41,19 @@ pub async fn get_course_options(
3841
c.department = d.id AND
3942
s.status != 'X' AND
4043
c.term = $1 AND
41-
(d.code || '-' || c.course || '-' || s.section) LIKE '$2'");
44+
(d.code || '-' || c.course || '-' || s.section) LIKE '$2'",
45+
);
4246

4347
query_builder.push_bind(options.term);
4448
query_builder.push_bind(course_number);
4549

46-
let sections: Vec<SectionInfo>;
47-
match log_query_as(
48-
query_builder.build_query_as::<SectionInfo>()
49-
.fetch_all(&state.db)
50-
.await,
51-
None,
52-
).await {
53-
Ok((_, secs)) => { sections = secs; },
54-
Err(e) => return e,
50+
let sections: Vec<SectionInfo> = match query_builder
51+
.build_query_as::<SectionInfo>()
52+
.fetch_all(&state.db)
53+
.await
54+
{
55+
Ok(sections) => sections,
56+
Err(e) => return HttpResponse::InternalServerError().json(e.to_string()),
5557
};
5658

5759
let stem = "
@@ -75,7 +77,7 @@ pub async fn get_course_options(
7577
}
7678

7779
if let Some(credit_hours) = options.credit_hours {
78-
if credit_hours >= 0 && credit_hours <= 12 {
80+
if (0..=12).contains(&credit_hours) {
7981
qb.push(" AND c.credits = $1");
8082
qb.push_bind(credit_hours);
8183
}
@@ -111,10 +113,10 @@ pub async fn get_course_options(
111113
qb.push_bind(section.section_id);
112114

113115
if let Some(days) = &options.days {
114-
for idx in 0..7 {
115-
if days[idx] {
116+
for (i, day) in days.iter().enumerate() {
117+
if *day {
116118
qb.push(" AND t.day = $1");
117-
qb.push_bind(idx as i32);
119+
qb.push_bind(i as i32);
118120
}
119121
}
120122
}
@@ -127,16 +129,11 @@ pub async fn get_course_options(
127129
qb.push(" AND NOT b.code = 'OFFC'");
128130
}
129131

130-
let section_times: Vec<SectionTimeInfo>;
131-
match futures::executor::block_on(log_query_as(
132-
futures::executor::block_on(
133-
qb.build_query_as::<SectionTimeInfo>()
134-
.fetch_all(&state.db)
135-
),
136-
None,
137-
)) {
138-
Ok((_, t)) => { section_times = t; },
139-
Err(_) => { section_times = vec![] },
132+
let section_times: Vec<SectionTimeInfo> = match futures::executor::block_on(
133+
qb.build_query_as::<SectionTimeInfo>().fetch_all(&state.db),
134+
) {
135+
Ok(times) => times,
136+
Err(_) => vec![],
140137
};
141138

142139
let times: Vec<Time> = section_times
@@ -159,7 +156,7 @@ pub async fn get_course_options(
159156
};
160157

161158
Time {
162-
building: building,
159+
building,
163160
day: weekday,
164161
start: sec_time.start_time as u32,
165162
end: sec_time.end_time as u32,
@@ -180,11 +177,10 @@ pub async fn get_course_options(
180177
enrollment_capacity: section.max_enroll,
181178
instructor_name: section.instructor,
182179
online: times[0].building.code == "ON",
183-
times: times,
180+
times,
184181
}
185182
})
186183
.collect::<Vec<CourseOption>>();
187184

188185
HttpResponse::Ok().json(courses)
189186
}
190-

backend/src/api/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use actix_web::{
33
web::{self, scope, Data},
44
App, HttpResponse, HttpServer,
55
};
6+
mod departments;
67
mod get_course_options;
78
mod terms;
8-
mod departments;
99
use actix_cors::Cors;
10+
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
11+
use std::env;
1012
use utoipa::OpenApi;
1113
use utoipa_swagger_ui::SwaggerUi;
12-
use sqlx::{Pool, Postgres, postgres::PgPoolOptions};
13-
use std::env;
1414

1515
use crate::model;
1616

@@ -83,9 +83,5 @@ pub async fn get_app_data() -> Data<AppState> {
8383
.connect(&env::var("DATABASE_URL").expect("DATABASE_URL not set"))
8484
.await
8585
.expect("Could not connect to database");
86-
println!("Successfully opened db connection");
87-
Data::new(AppState {
88-
db: db_pool,
89-
})
86+
Data::new(AppState { db: db_pool })
9087
}
91-

backend/src/api/terms.rs

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use actix_web::{HttpResponse, Responder, get, web::Data};
2-
use sqlx::query_as;
1+
use crate::{api::AppState, model::Term};
2+
use actix_web::{get, web::Data, HttpResponse, Responder};
33
use log::{log, Level};
4-
use crate::api::AppState;
5-
use crate::model::Term;
6-
use crate::db::log_query_as;
4+
use sqlx::query_as;
75

86
#[utoipa::path(
97
context_path = "/api",
@@ -16,18 +14,14 @@ use crate::db::log_query_as;
1614
pub async fn get_terms(state: Data<AppState>) -> impl Responder {
1715
log!(Level::Info, "GET /terms");
1816

19-
match log_query_as(
20-
query_as!(
21-
Term,
22-
"SELECT DISTINCT term FROM academicterms ORDER BY term DESC",
23-
)
24-
.fetch_all(&state.db)
25-
.await,
26-
None,
17+
match query_as!(
18+
Term,
19+
"SELECT DISTINCT term FROM academicterms ORDER BY term DESC",
2720
)
28-
.await {
29-
Ok((_, terms)) => HttpResponse::Ok().json(terms),
30-
Err(e) => return e,
21+
.fetch_all(&state.db)
22+
.await
23+
{
24+
Ok(terms) => HttpResponse::Ok().json(terms),
25+
Err(e) => HttpResponse::InternalServerError().json(e.to_string()),
3126
}
3227
}
33-

backend/src/db.rs

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use sqlx::{Pool, Postgres, Transaction, Error};
2-
use tokio::sync::OnceCell;
1+
use actix_web::HttpResponse;
32
use log::{log, Level};
4-
use actix_web::{HttpResponse};
3+
use sqlx::{Error, Pool, Postgres, Transaction};
4+
use tokio::sync::OnceCell;
55

66
static POOL: OnceCell<Pool<Postgres>> = OnceCell::const_new();
77

@@ -23,48 +23,3 @@ pub async fn open_transaction(db: &Pool<Postgres>) -> Result<Transaction<Postgre
2323
}
2424
}
2525
}
26-
27-
pub async fn log_query_as<T>(
28-
query: Result<Vec<T>, Error>,
29-
tx: Option<Transaction<'_, Postgres>>,
30-
) -> Result<(Option<Transaction<'_, Postgres>>, Vec<T>), HttpResponse> {
31-
match query {
32-
Ok(v) => Ok((tx, v)),
33-
Err(e) => {
34-
log!(Level::Warn, "DB Query failed: {}", e);
35-
if let Some(tx) = tx {
36-
match tx.rollback().await {
37-
Ok(_) => {}
38-
Err(tx_e) => {
39-
log!(Level::Error, "Transaction failed to rollback: {}", tx_e);
40-
return Err(HttpResponse::InternalServerError().body("Internal DB Error"));
41-
}
42-
}
43-
}
44-
return Err(HttpResponse::InternalServerError().body("Internal DB Error"));
45-
}
46-
}
47-
}
48-
49-
pub async fn log_query(
50-
query: Result<(), Error>,
51-
tx: Option<Transaction<'_, Postgres>>,
52-
) -> Result<Option<Transaction<'_, Postgres>>, HttpResponse> {
53-
match query {
54-
Ok(_) => Ok(tx),
55-
Err(e) => {
56-
log!(Level::Warn, "DB Query failed: {}", e);
57-
if let Some(tx) = tx {
58-
match tx.rollback().await {
59-
Ok(_) => {}
60-
Err(tx_e) => {
61-
log!(Level::Error, "Transaction failed to rollback: {}", tx_e);
62-
return Err(HttpResponse::InternalServerError().body("Internal DB Error"));
63-
}
64-
}
65-
}
66-
return Err(HttpResponse::InternalServerError().body("Internal DB Error"));
67-
}
68-
}
69-
}
70-

backend/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod dat;
55
mod db;
66
mod import;
77
mod model;
8+
mod parse;
89
use crate::api::serve;
910

1011
#[derive(Parser)]
@@ -34,7 +35,10 @@ async fn main() {
3435

3536
let cli = Cli::parse();
3637
match cli.command {
37-
Subcommands::Import => import::import(pool).await.expect("Couldn't import :("),
38+
Subcommands::Import => {
39+
import::import(pool).await.expect("Couldn't import :(");
40+
parse::parse(pool).await.expect("Couldn't parse :(");
41+
}
3842
Subcommands::Serve => serve().await,
3943
}
4044
}

0 commit comments

Comments
 (0)