pull/1/head
Manos Pitsidianakis 2022-08-29 14:13:13 +03:00
parent b97ab650ce
commit 9de95559d6
11 changed files with 124 additions and 42 deletions

View File

@ -34,7 +34,7 @@ struct ListsTemplate<'a> {
title: &'a str,
description: &'a str,
lists_len: usize,
lists: Vec<ListTemplate<'a>>,
lists: Vec<DbVal<MailingList>>,
}
#[derive(Template)]
@ -50,7 +50,12 @@ struct ListTemplate<'a> {
impl<'a> Into<ListTemplate<'a>> for (&'a DbVal<MailingList>, &'a Database) {
fn into(self: (&'a DbVal<MailingList>, &'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::<Vec<ListTemplate<'_>>>();
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).

View File

@ -2,7 +2,7 @@
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
{{body | safe}}
</div>
</div>
{% include "footer.html" %}

View File

@ -1,8 +1,7 @@
{% include "header.html" %}
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
{{body|safe}}
<h2>Months</h2>
<ul>
{% for month in months %}
@ -17,4 +16,3 @@
</ul>
</div>
</div>
{% include "footer.html" %}

View File

@ -2,9 +2,11 @@
<div class="entry">
<p>{{lists_len}} lists</p>
<div class="body">
{% for list in lists %}
{{ list }}
<ul>
{% for list in lists.as_slice() -%}
<li><a href="/lists/{{ list.pk }}">{{ list }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% include "footer.html" %}

View File

@ -2,8 +2,35 @@
<div class="entry">
<h1>{{subject}}</h1>
<div class="body">
<table>
<tbody>
<tr>
<td>From</td>
<td><code>{{_from}}</code></td>
</tr>
<tr>
<td>To</td>
<td><code>{{_to}}</code></td>
</tr>
<tr>
<td>Subject</td>
<td>{{subject}}</td>
</tr>
{% if _in_reply_to.is_some() %}
<tr>
<td>In-&shy;Reply-&shy;To</td>
<td><code>{{_in_reply_to.as_ref().unwrap() }}</code></td>
</tr>
{% endif %}
<tr>
<td>References</td>
<td>{% for reference in _references %}<code>{{reference}}</code>, {% endfor %}</td>
</tr>
</tbody>
</table>
<pre>
{{body}}
{{body|safe}}
</pre>
</div>
</div>

View File

@ -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();

View File

@ -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<Vec<DbVal<Post>>> {
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::<Post>::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::<Post>::try_from(row)?)
})?;
for post in iter {
let post = post?;
ret.push(post);
}
};
trace!("list_posts {:?}.", &ret);
Ok(ret)

View File

@ -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<Post> {
type Error = rusqlite::Error;
fn try_from(row: &'r rusqlite::Row<'s>) -> std::result::Result<Self, Self::Error> {
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,
))
}
}

View File

@ -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);

View File

@ -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);

Binary file not shown.