Fix CSV upload
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Julius 2022-12-23 20:22:38 +01:00
parent a38b47c223
commit 5303be8aca
Signed by: j00lz
GPG key ID: AF241B0AA237BBA2
5 changed files with 483 additions and 270 deletions

653
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,8 @@ sqlx = { version = "0.5.13", features = ["runtime-tokio-rustls", "postgres", "ch
tokio = { version = "1.19.2", features = ["full"] } tokio = { version = "1.19.2", features = ["full"] }
uuid = { version = "0.8", features = ["serde", "v4"] } uuid = { version = "0.8", features = ["serde", "v4"] }
tower-http = { version = "0.3.4", features = ["fs", "trace"] } tower-http = { version = "0.3.4", features = ["fs", "trace"] }
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
rand = "0.8.5" rand = "0.8.5"
argon2 = "0.4.0" argon2 = "0.4.0"
axum-extra = { version = "0.3.4", features = ["cookie"] } axum-extra = { version = "0.3.4", features = ["cookie"] }
regex = "1.7.0"

View file

@ -14,5 +14,3 @@ services:
restart: always restart: always
ports: ports:
- "4567:8080" - "4567:8080"
links:
- db

View file

@ -13,6 +13,7 @@ use axum::{
use axum_extra::extract::{cookie::Cookie, CookieJar}; use axum_extra::extract::{cookie::Cookie, CookieJar};
use db::{Database, Db, Party, Ticket, User}; use db::{Database, Db, Party, Ticket, User};
use rand::Rng; use rand::Rng;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -405,6 +406,50 @@ async fn stats(
Json(StatResponse { total, inside }) Json(StatResponse { total, inside })
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CSVScan {
party: i32,
csv: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CSVScanResponse {
scanned: i32,
with_coins: i32,
total: i32,
}
async fn tickets_csv(
_: User,
Extension(pool): Extension<Database>,
Json(csv_scan): Json<CSVScan>,
) -> impl IntoResponse {
let llnr_regex = Regex::new(r".*(\d{6}).*").unwrap();
let coins_regex = Regex::new(r".*COINS.*").unwrap();
let party = csv_scan.party;
let mut total = 0;
let mut with_coins = 0;
let mut scanned = 0;
for l in csv_scan.csv.lines() {
total += 1;
if let Some(captures) = llnr_regex.captures(l) {
if let Some(llnr) = captures.get(1) {
let has_coins = coins_regex.is_match(l);
pool.add_ticket(party, llnr.as_str(), has_coins).await;
scanned += 1;
if has_coins {
with_coins += 1;
}
}
}
}
Json(CSVScanResponse {
scanned,
with_coins,
total,
})
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let url = if let Some(url) = std::env::var("DATABASE_URL").ok() { let url = if let Some(url) = std::env::var("DATABASE_URL").ok() {
@ -438,6 +483,7 @@ async fn main() {
.route("/party/:id/export", get(export_party)) .route("/party/:id/export", get(export_party))
.route("/party/:id/lijst", get(party_goers)) .route("/party/:id/lijst", get(party_goers))
.route("/api/ticket", get(get_tickets).post(scan_card)) .route("/api/ticket", get(get_tickets).post(scan_card))
.route("/api/csv", post(tickets_csv))
.route("/api/ticket/delete", post(remove_ticket)) .route("/api/ticket/delete", post(remove_ticket))
.route("/api/party", get(get_party_by_name).post(create_party)) .route("/api/party", get(get_party_by_name).post(create_party))
.route("/api/party/list", get(get_parties)) .route("/api/party/list", get(get_parties))

View file

@ -19,7 +19,7 @@
onclick="reselect()">Start onclick="reselect()">Start
Scannen</button> </p> Scannen</button> </p>
<div class="columns"> <div class="columns">
<video autoplay playsinline controls="false" class="column col-12"></video> <video autoplay playsinline controls="false" class="column col-12" id="webcam_holder" hidden></video>
</div> </div>
<h4><span id="last-scanned">Nog geen gescand</span></h4> <h4><span id="last-scanned">Nog geen gescand</span></h4>
<h3><span id="scan-status">Nog geen gescand</span></h3> <h3><span id="scan-status">Nog geen gescand</span></h3>
@ -184,6 +184,7 @@
const videoSelect = document.querySelector("select#videoSource"); const videoSelect = document.querySelector("select#videoSource");
function start_scanning() { function start_scanning() {
document.getElementById("webcam_holder").hidden = false;
navigator.mediaDevices navigator.mediaDevices
.enumerateDevices() .enumerateDevices()
.then(gotDevices) .then(gotDevices)
@ -272,30 +273,28 @@
function upload_csv() { function upload_csv() {
let file = document.getElementById("file-selector").files[0]; let file = document.getElementById("file-selector").files[0];
if (file) { if (file) {
const reader = new FileReader(); set_scan_status("CSV uploaden...");
let num = 0; // upload file contents to server
reader.onload = (evt) => { let reader = new FileReader();
let result = evt.target.result; reader.onload = function (e) {
result = result.replace("\r\n", "\n").replace("\r", "\n"); let contents = e.target.result;
for (let line of result.split("\n")) { let party_id = +document.getElementById("party_id").value;
let llnr_a = ""; fetch("/api/csv", {
let coins = false; method: "POST",
for (let llnr of line.split(",")) { headers: {
if (llnr.length === 6 && !isNaN(llnr)) { "Content-Type": "application/json"
llnr_a = llnr; },
} body: JSON.stringify({
if (llnr === "COINS") { party: party_id,
coins = true; csv: contents,
} })
} }).then(function (response) {
console.log(llnr_a, coins); return response.json();
if (llnr_a !== "") { }).then(function (data) {
num++; console.log(data)
scan_ticket(llnr_a, coins); set_scan_status(`CSV geupload: ${data.total} regels, waarvan ${data.scanned} leerlingen, ${data.with_coins} met muntjes`);
} });
} };
document.getElementById("import-result").innerHTML = `${num} leerlingen geïmporteerd!`
}
reader.readAsText(file); reader.readAsText(file);
} }