diff --git a/archive-http/src/main.rs b/archive-http/src/main.rs index 1b4cad8..dc2c5e9 100644 --- a/archive-http/src/main.rs +++ b/archive-http/src/main.rs @@ -34,7 +34,7 @@ struct ListsTemplate<'a> { title: &'a str, description: &'a str, lists_len: usize, - lists: Vec>, + lists: Vec>, } #[derive(Template)] @@ -50,7 +50,12 @@ struct ListTemplate<'a> { impl<'a> Into> for (&'a DbVal, &'a Database) { fn into(self: (&'a DbVal, &'a Database)) -> ListTemplate<'a> { let (list, db) = self; - let months = db.months(list.pk).unwrap(); + let months = db + .months(list.pk) + .unwrap() + .into_iter() + .map(|s| s.replace('-', "/").to_string()) + .collect(); let posts = db.list_posts(list.pk, None).unwrap(); ListTemplate { title: &list.name, @@ -86,7 +91,12 @@ async fn main() { let list_handler = warp::path!("lists" / i64).map(|list_pk: i64| { let db = Database::open_or_create_db().unwrap(); let list = db.get_list(list_pk).unwrap().unwrap(); - let months = db.months(list_pk).unwrap(); + let months = db + .months(list_pk) + .unwrap() + .into_iter() + .map(|s| s.replace('-', "/").to_string()) + .collect(); let posts = db.list_posts(list_pk, None).unwrap(); let template = ListTemplate { title: &list.name, @@ -98,6 +108,25 @@ async fn main() { let res = template.render().unwrap(); Ok(warp::reply::html(res)) }); + let month_handler = + warp::path!("list" / i64 / i64 / i64).map(|list_pk: i64, year: i64, month: i64| { + let db = Database::open_or_create_db().unwrap(); + let list = db.get_list(list_pk).unwrap().unwrap(); + let mut posts = db.list_posts(list_pk, Some((year, month))).unwrap(); + let template = ListTemplate { + title: &list.name, + list: &list, + posts, + months: vec![month.to_string()], + body: &list + .description + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_default(), + }; + let res = template.render().unwrap(); + Ok(warp::reply::html(res)) + }); let post_handler = warp::path!("list" / i64 / String).map(|list_pk: i64, message_id: String| { let message_id = percent_decode_str(&message_id).decode_utf8().unwrap(); @@ -133,15 +162,12 @@ async fn main() { let index_handler = warp::path::end().map(|| { let db = Database::open_or_create_db().unwrap(); let lists_values = db.list_lists().unwrap(); - let lists = lists_values - .iter() - .map(|list| (list, &db).into()) - .collect::>>(); + let lists = lists_values; let template = ListsTemplate { title: "mailing list archive", description: "", lists_len: lists.len(), - lists: lists, + lists, }; let res = template.render().unwrap(); Ok(warp::reply::html(res)) @@ -149,6 +175,7 @@ async fn main() { let routes = warp::get() .and(index_handler) .or(list_handler) + .or(month_handler) .or(post_handler); // Note that composing filters for many routes may increase compile times (because it uses a lot of generics). diff --git a/archive-http/templates/index.html b/archive-http/templates/index.html index ca60f33..04bec12 100644 --- a/archive-http/templates/index.html +++ b/archive-http/templates/index.html @@ -2,7 +2,7 @@

{{title}}

- {{body}} + {{body | safe}}
{% include "footer.html" %} diff --git a/archive-http/templates/list.html b/archive-http/templates/list.html index f56bf9e..2ee9681 100644 --- a/archive-http/templates/list.html +++ b/archive-http/templates/list.html @@ -1,8 +1,7 @@ -{% include "header.html" %}

{{title}}

- {{body}} + {{body|safe}}

Months

    {% for month in months %} @@ -17,4 +16,3 @@
-{% include "footer.html" %} diff --git a/archive-http/templates/lists.html b/archive-http/templates/lists.html index caca741..7c01d7f 100644 --- a/archive-http/templates/lists.html +++ b/archive-http/templates/lists.html @@ -2,9 +2,11 @@

{{lists_len}} lists

- {% for list in lists %} - {{ list }} +
    + {% for list in lists.as_slice() -%} +
  • {{ list }}
  • {% endfor %} +
{% include "footer.html" %} diff --git a/archive-http/templates/post.html b/archive-http/templates/post.html index 3902aa2..1ff890d 100644 --- a/archive-http/templates/post.html +++ b/archive-http/templates/post.html @@ -2,8 +2,35 @@

{{subject}}

+ + + + + + + + + + + + + + + {% if _in_reply_to.is_some() %} + + + + + {% endif %} + + + + + +
From{{_from}}
To{{_to}}
Subject{{subject}}
In-­Reply-­To{{_in_reply_to.as_ref().unwrap() }}
References{% for reference in _references %}{{reference}}, {% endfor %}
+
-        {{body}}
+        {{body|safe}}
       
diff --git a/core/build.rs b/core/build.rs index 26e6cab..418cf5a 100644 --- a/core/build.rs +++ b/core/build.rs @@ -45,8 +45,9 @@ fn main() { let exit = verify.wait_with_output().unwrap(); if !exit.status.success() { panic!( - "sqlite3 could not read SQL schema: {}", - String::from_utf8_lossy(&exit.stdout) + "sqlite3 could not read SQL schema: {}\n{}", + String::from_utf8_lossy(&exit.stdout), + String::from_utf8_lossy(&exit.stderr) ); } let mut file = std::fs::File::create("./src/schema.sql").unwrap(); diff --git a/core/src/db.rs b/core/src/db.rs index ba50a89..35e951e 100644 --- a/core/src/db.rs +++ b/core/src/db.rs @@ -271,31 +271,32 @@ impl Database { pub fn list_posts( &self, list_pk: i64, - _date_range: Option<(String, String)>, + date_range: Option<(i64, i64)>, ) -> Result>> { - let mut stmt = self - .connection - .prepare("SELECT * FROM post WHERE list = ?;")?; - let iter = stmt.query_map(rusqlite::params![&list_pk,], |row| { - let pk = row.get("pk")?; - Ok(DbVal( - Post { - pk, - list: row.get("list")?, - address: row.get("address")?, - message_id: row.get("message_id")?, - message: row.get("message")?, - timestamp: row.get("timestamp")?, - datetime: row.get("datetime")?, - }, - pk, - )) - })?; let mut ret = vec![]; - for post in iter { - let post = post?; - ret.push(post); - } + if let Some((year, month)) = date_range.as_ref() { + let mut stmt = self + .connection + .prepare("SELECT * FROM post WHERE list = ? AND year = ? AND month = ?;")?; + let iter = stmt.query_map(rusqlite::params![list_pk, year, month], |row| { + Ok(DbVal::::try_from(row)?) + })?; + for post in iter { + let post = post?; + ret.push(post); + } + } else { + let mut stmt = self + .connection + .prepare("SELECT * FROM post WHERE list = ?;")?; + let iter = stmt.query_map(rusqlite::params![list_pk,], |row| { + Ok(DbVal::::try_from(row)?) + })?; + for post in iter { + let post = post?; + ret.push(post); + } + }; trace!("list_posts {:?}.", &ret); Ok(ret) diff --git a/core/src/models.rs b/core/src/models.rs index ab622c2..ff05aaa 100644 --- a/core/src/models.rs +++ b/core/src/models.rs @@ -281,3 +281,22 @@ impl std::fmt::Display for Post { write!(fmt, "{:?}", self) } } + +impl<'r, 's> std::convert::TryFrom<&'r rusqlite::Row<'s>> for DbVal { + type Error = rusqlite::Error; + fn try_from(row: &'r rusqlite::Row<'s>) -> std::result::Result { + let pk = row.get("pk")?; + Ok(DbVal( + Post { + pk, + list: row.get("list")?, + address: row.get("address")?, + message_id: row.get("message_id")?, + message: row.get("message")?, + timestamp: row.get("timestamp")?, + datetime: row.get("datetime")?, + }, + pk, + )) + } +} diff --git a/core/src/schema.sql b/core/src/schema.sql index 2a8f576..3dfe2c8 100644 --- a/core/src/schema.sql +++ b/core/src/schema.sql @@ -70,7 +70,9 @@ CREATE TABLE IF NOT EXISTS post ( message_id TEXT NOT NULL, message BLOB NOT NULL, timestamp INTEGER NOT NULL DEFAULT (unixepoch()), - datetime TEXT NOT NULL DEFAULT (datetime()) + datetime TEXT NOT NULL DEFAULT (datetime()), + year INTEGER AS (CAST(substr(datetime,0,5) AS INTEGER)) NOT NULL, + month INTEGER AS (CAST(substr(datetime,6,7) AS INTEGER)) NOT NULL ); CREATE TABLE IF NOT EXISTS post_event ( @@ -97,3 +99,4 @@ CREATE INDEX IF NOT EXISTS post_listpk_idx ON post(list); CREATE INDEX IF NOT EXISTS post_msgid_idx ON post(message_id); CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id); CREATE INDEX IF NOT EXISTS membership_idx ON membership(address); +CREATE INDEX IF NOT EXISTS post_date_idx ON post(year ASC, month ASC); diff --git a/core/src/schema.sql.m4 b/core/src/schema.sql.m4 index 27a8c02..c405a26 100644 --- a/core/src/schema.sql.m4 +++ b/core/src/schema.sql.m4 @@ -2,6 +2,7 @@ define(xor, `(($1) OR ($2)) AND NOT (($1) AND ($2))')dnl define(BOOLEAN_TYPE, `$1 BOOLEAN CHECK ($1 in (0, 1)) NOT NULL')dnl define(BOOLEAN_FALSE, `0')dnl define(BOOLEAN_TRUE, `1')dnl +undefine(substr)dnl PRAGMA foreign_keys = true; PRAGMA encoding = 'UTF-8'; @@ -74,7 +75,9 @@ CREATE TABLE IF NOT EXISTS post ( message_id TEXT NOT NULL, message BLOB NOT NULL, timestamp INTEGER NOT NULL DEFAULT (unixepoch()), - datetime TEXT NOT NULL DEFAULT (datetime()) + datetime TEXT NOT NULL DEFAULT (datetime()), + year INTEGER AS (CAST(substr(datetime,0,5) AS INTEGER)) NOT NULL, + month INTEGER AS (CAST(substr(datetime,6,7) AS INTEGER)) NOT NULL ); CREATE TABLE IF NOT EXISTS post_event ( @@ -101,3 +104,4 @@ CREATE INDEX IF NOT EXISTS post_listpk_idx ON post(list); CREATE INDEX IF NOT EXISTS post_msgid_idx ON post(message_id); CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id); CREATE INDEX IF NOT EXISTS membership_idx ON membership(address); +CREATE INDEX IF NOT EXISTS post_date_idx ON post(year ASC, month ASC); diff --git a/imap-protocol.tar.xz b/imap-protocol.tar.xz new file mode 100644 index 0000000..465b5d8 Binary files /dev/null and b/imap-protocol.tar.xz differ