summaryrefslogtreecommitdiff
path: root/migration/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'migration/src/main.rs')
-rw-r--r--migration/src/main.rs131
1 files changed, 131 insertions, 0 deletions
diff --git a/migration/src/main.rs b/migration/src/main.rs
new file mode 100644
index 0000000..72252a9
--- /dev/null
+++ b/migration/src/main.rs
@@ -0,0 +1,131 @@
+extern crate clap;
+extern crate serde;
+extern crate serde_json;
+#[macro_use]
+extern crate serde_derive;
+#[macro_use]
+extern crate log;
+extern crate env_logger;
+extern crate rusqlite;
+extern crate regex;
+
+use std::fs::File;
+use std::io::Read;
+use clap::{App, Arg};
+use std::collections::HashMap;
+use regex::Regex;
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Key {
+ id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Quote {
+ timestamp: String,
+ author: String,
+ ip: String,
+ content: String,
+ quote_date: String,
+ #[serde(rename = "__key__")]
+ key: Key,
+ #[serde(default = "Vec::new")]
+ votes: Vec<Vote>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Vote {
+ ip: String,
+ rating: String,
+ quote_id: String,
+ timestamp: String,
+}
+
+fn import<'a, I: Iterator<Item = &'a Quote>>(conn: &rusqlite::Connection, quotes: I) {
+ let r = Regex::new(r".*(\d{4})-(\d{2})-(\d{2}).*").unwrap();
+ for quote in quotes {
+ let quote_date = r.replace(&quote.quote_date, "$1-$2-$3").to_string();
+ info!("Inserting: {:?}, quote_date({})", quote, quote_date);
+ conn.execute("INSERT INTO quotes (approved, quote_date, author, content, ip, timestamp) VALUES (1, ?1, ?2, ?3, ?4, ?5)",
+ &[&quote_date, &quote.author, &quote.content, &quote.ip, &quote.timestamp]).unwrap();
+
+ let quote_id = conn.last_insert_rowid();
+ let mut user_id = 1;
+ for vote in quote.votes.iter() {
+ info!("quote({}): Add vote {:?}", quote_id, vote);
+ let score: i32 = vote.rating.parse().unwrap();
+ conn.execute(r"INSERT INTO votes (user_id, quote_id, score, ip, timestamp) VALUES (?1, ?2, ?3, ?4, ?5)",
+ &[&user_id, &quote_id, &score, &vote.ip, &vote.timestamp]).unwrap();
+ user_id += 1;
+ }
+ }
+}
+
+fn main() {
+ env_logger::init().unwrap();
+
+ let matches = App::new("migration")
+ .arg(Arg::with_name("db").required(true))
+ .arg(Arg::with_name("quotes_json").required(true))
+ .arg(Arg::with_name("votes_json").required(true))
+ .get_matches();
+
+ let mut quotes = "".to_string();
+ File::open(matches.value_of("quotes_json").unwrap())
+ .unwrap()
+ .read_to_string(&mut quotes)
+ .unwrap();
+
+ fn parse_all<'a, T: serde::Deserialize<'a>>(s: &'a str) -> Vec<T> {
+ s.lines()
+ .filter(|s| !s.is_empty())
+ .map(|s| {
+ debug!("deserializing this: {:?}", s);
+ serde_json::from_str(s).unwrap()
+ })
+ .collect::<Vec<T>>()
+ }
+ let quotes: Vec<Quote> = parse_all(&quotes);
+ let mut quotes: HashMap<String, Quote> =
+ quotes.into_iter().map(|q| (q.key.id.clone(), q)).collect();
+
+ let mut votes = "".to_string();
+ File::open(matches.value_of("votes_json").unwrap())
+ .unwrap()
+ .read_to_string(&mut votes)
+ .unwrap();
+ let votes: Vec<Vote> = parse_all(&votes);
+
+ let mut v1 = 0;
+ for vote in votes.into_iter() {
+ if let Some(quote) = quotes.get_mut(&vote.quote_id) {
+ quote.votes.push(vote);
+ }
+ v1 += 1;
+ }
+
+ let mut v2 = 0;
+ for (_, quote) in quotes.iter() {
+ println!(
+ "{}/{}: {} votes",
+ quote.key.id,
+ quote.quote_date,
+ quote.votes.len()
+ );
+ v2 += quote.votes.len();
+ }
+ println!("votes before {} after {}", v1, v2);
+
+ let mut quotes: Vec<Quote> = quotes.into_iter().map(|(_, v)| v).collect();
+ quotes.sort_by_key(|q| {
+ (q.quote_date.clone(), q.timestamp.clone(), q.key.id.clone())
+ });
+
+ let mut conn = rusqlite::Connection::open(matches.value_of("db").unwrap()).unwrap();
+ let tx = conn.transaction().unwrap();
+ import(&tx, quotes.iter());
+ tx.commit().unwrap();
+}