web: expand list owner edit view
parent
69153fde3c
commit
6cae75e5ae
199
web/src/lists.rs
199
web/src/lists.rs
|
@ -17,6 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use chrono::TimeZone;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::*;
|
||||
|
@ -114,7 +115,7 @@ pub async fn list(
|
|||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListPath(list.pk().into()).to_crumb(),
|
||||
url: ListPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
];
|
||||
let context = minijinja::context! {
|
||||
|
@ -191,11 +192,11 @@ pub async fn list_post(
|
|||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListPath(list.pk().into()).to_crumb(),
|
||||
url: ListPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: format!("{} {msg_id}", subject_ref).into(),
|
||||
url: ListPostPath(list.pk().into(), msg_id.to_string()).to_crumb(),
|
||||
url: ListPostPath(list.id.to_string().into(), msg_id.to_string()).to_crumb(),
|
||||
},
|
||||
];
|
||||
let context = minijinja::context! {
|
||||
|
@ -298,7 +299,11 @@ pub async fn list_edit(
|
|||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListPath(list.pk().into()).to_crumb(),
|
||||
url: ListPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListEditPath(ListPathIdentifier::from(list.id.clone())).to_crumb(),
|
||||
},
|
||||
];
|
||||
let context = minijinja::context! {
|
||||
|
@ -580,3 +585,189 @@ pub async fn post_eml(
|
|||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn list_subscribers(
|
||||
ListEditSubscribersPath(id): ListEditSubscribersPath,
|
||||
mut session: WritableSession,
|
||||
auth: AuthContext,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Html<String>, ResponseError> {
|
||||
let db = Connection::open_db(state.conf.clone())?;
|
||||
let Some(list) = (match id {
|
||||
ListPathIdentifier::Pk(id) => db.list(id)?,
|
||||
ListPathIdentifier::Id(id) => db.list_by_id(id)?,
|
||||
}) else {
|
||||
return Err(ResponseError::new(
|
||||
"Not found".to_string(),
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
};
|
||||
let list_owners = db.list_owners(list.pk)?;
|
||||
let user_address = &auth.current_user.as_ref().unwrap().address;
|
||||
if !list_owners.iter().any(|o| &o.address == user_address) {
|
||||
return Err(ResponseError::new(
|
||||
"Not found".to_string(),
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
};
|
||||
|
||||
let subs = {
|
||||
let mut stmt = db
|
||||
.connection
|
||||
.prepare("SELECT * FROM subscription WHERE list = ?;")?;
|
||||
let iter = stmt.query_map([&list.pk], |row| {
|
||||
let address: String = row.get("address")?;
|
||||
let name: Option<String> = row.get("name")?;
|
||||
let enabled: bool = row.get("enabled")?;
|
||||
let verified: bool = row.get("verified")?;
|
||||
let digest: bool = row.get("digest")?;
|
||||
let hide_address: bool = row.get("hide_address")?;
|
||||
let receive_duplicates: bool = row.get("receive_duplicates")?;
|
||||
let receive_own_posts: bool = row.get("receive_own_posts")?;
|
||||
let receive_confirmation: bool = row.get("receive_confirmation")?;
|
||||
//let last_digest: i64 = row.get("last_digest")?;
|
||||
let created: i64 = row.get("created")?;
|
||||
let last_modified: i64 = row.get("last_modified")?;
|
||||
Ok(minijinja::context! {
|
||||
address,
|
||||
name,
|
||||
enabled,
|
||||
verified,
|
||||
digest,
|
||||
hide_address,
|
||||
receive_duplicates,
|
||||
receive_own_posts,
|
||||
receive_confirmation,
|
||||
//last_digest => chrono::Utc.timestamp_opt(last_digest, 0).unwrap().to_string(),
|
||||
created => chrono::Utc.timestamp_opt(created, 0).unwrap().to_string(),
|
||||
last_modified => chrono::Utc.timestamp_opt(last_modified, 0).unwrap().to_string(),
|
||||
})
|
||||
})?;
|
||||
let mut ret = vec![];
|
||||
for el in iter {
|
||||
let el = el?;
|
||||
ret.push(el);
|
||||
}
|
||||
ret
|
||||
};
|
||||
|
||||
let crumbs = vec![
|
||||
Crumb {
|
||||
label: "Home".into(),
|
||||
url: "/".into(),
|
||||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListEditPath(ListPathIdentifier::from(list.id.clone())).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: format!("Subscribers of {}", list.name).into(),
|
||||
url: ListEditSubscribersPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
];
|
||||
let context = minijinja::context! {
|
||||
site_title => state.site_title.as_ref(),
|
||||
site_subtitle => state.site_subtitle.as_ref(),
|
||||
canonical_url => ListEditSubscribersPath(ListPathIdentifier::from(list.id.clone())).to_crumb(),
|
||||
page_title => format!("Subscribers of {}", list.name),
|
||||
subs,
|
||||
root_url_prefix => &state.root_url_prefix,
|
||||
list => Value::from_object(MailingList::from(list)),
|
||||
current_user => auth.current_user,
|
||||
messages => session.drain_messages(),
|
||||
crumbs,
|
||||
};
|
||||
Ok(Html(
|
||||
TEMPLATES.get_template("lists/subs.html")?.render(context)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn list_candidates(
|
||||
ListEditCandidatesPath(id): ListEditCandidatesPath,
|
||||
mut session: WritableSession,
|
||||
auth: AuthContext,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Html<String>, ResponseError> {
|
||||
let db = Connection::open_db(state.conf.clone())?;
|
||||
let Some(list) = (match id {
|
||||
ListPathIdentifier::Pk(id) => db.list(id)?,
|
||||
ListPathIdentifier::Id(id) => db.list_by_id(id)?,
|
||||
}) else {
|
||||
return Err(ResponseError::new(
|
||||
"Not found".to_string(),
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
};
|
||||
let list_owners = db.list_owners(list.pk)?;
|
||||
let user_address = &auth.current_user.as_ref().unwrap().address;
|
||||
if !list_owners.iter().any(|o| &o.address == user_address) {
|
||||
return Err(ResponseError::new(
|
||||
"Not found".to_string(),
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
};
|
||||
|
||||
let subs = {
|
||||
let mut stmt = db
|
||||
.connection
|
||||
.prepare("SELECT * FROM candidate_subscription WHERE list = ?;")?;
|
||||
let iter = stmt.query_map([&list.pk], |row| {
|
||||
let address: String = row.get("address")?;
|
||||
let name: Option<String> = row.get("name")?;
|
||||
let accepted: Option<i64> = row.get("enabled")?;
|
||||
let created: i64 = row.get("created")?;
|
||||
let last_modified: i64 = row.get("last_modified")?;
|
||||
Ok(minijinja::context! {
|
||||
address,
|
||||
name,
|
||||
accepted => accepted.is_some(),
|
||||
created => chrono::Utc.timestamp_opt(created, 0).unwrap().to_string(),
|
||||
last_modified => chrono::Utc.timestamp_opt(last_modified, 0).unwrap().to_string(),
|
||||
})
|
||||
})?;
|
||||
let mut ret = vec![];
|
||||
for el in iter {
|
||||
let el = el?;
|
||||
ret.push(el);
|
||||
}
|
||||
ret
|
||||
};
|
||||
|
||||
let crumbs = vec![
|
||||
Crumb {
|
||||
label: "Home".into(),
|
||||
url: "/".into(),
|
||||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: list.name.clone().into(),
|
||||
url: ListEditPath(ListPathIdentifier::from(list.id.clone())).to_crumb(),
|
||||
},
|
||||
Crumb {
|
||||
label: format!("Requests of {}", list.name).into(),
|
||||
url: ListEditCandidatesPath(list.id.to_string().into()).to_crumb(),
|
||||
},
|
||||
];
|
||||
let context = minijinja::context! {
|
||||
site_title => state.site_title.as_ref(),
|
||||
site_subtitle => state.site_subtitle.as_ref(),
|
||||
canonical_url => ListEditCandidatesPath(ListPathIdentifier::from(list.id.clone())).to_crumb(),
|
||||
page_title => format!("Requests of {}", list.name),
|
||||
subs,
|
||||
root_url_prefix => &state.root_url_prefix,
|
||||
list => Value::from_object(MailingList::from(list)),
|
||||
current_user => auth.current_user,
|
||||
messages => session.drain_messages(),
|
||||
crumbs,
|
||||
};
|
||||
Ok(Html(
|
||||
TEMPLATES.get_template("lists/subs.html")?.render(context)?,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -83,6 +83,20 @@ async fn main() {
|
|||
Some(Arc::new("next".into())),
|
||||
)),
|
||||
)
|
||||
.typed_get(
|
||||
list_subscribers.layer(RequireAuth::login_with_role_or_redirect(
|
||||
Role::User..,
|
||||
Arc::clone(&login_url),
|
||||
Some(Arc::new("next".into())),
|
||||
)),
|
||||
)
|
||||
.typed_get(
|
||||
list_candidates.layer(RequireAuth::login_with_role_or_redirect(
|
||||
Role::User..,
|
||||
Arc::clone(&login_url),
|
||||
Some(Arc::new("next".into())),
|
||||
)),
|
||||
)
|
||||
.typed_get(help)
|
||||
.typed_get(auth::ssh_signin)
|
||||
.typed_post({
|
||||
|
|
|
@ -45,6 +45,8 @@ lazy_static::lazy_static! {
|
|||
list_path,
|
||||
list_settings_path,
|
||||
list_edit_path,
|
||||
list_subscribers_path,
|
||||
list_candidates_path,
|
||||
list_post_path,
|
||||
post_raw_path,
|
||||
post_eml_path
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
{% if list.archive_url %}
|
||||
<p><a href="{{ list.archive_url }}">{{ list.archive_url }}</a></p>
|
||||
{% endif %}
|
||||
<p>{{ subs_count }} subscription{{ subs_count|pluralize }}.</p>
|
||||
<p>{{ sub_requests_count }} subscription request{{ sub_requests_count|pluralize }}.</p>
|
||||
<p><a href="{{ root_url_prefix }}{{ list_subscribers_path(list.pk) }}">{{ subs_count }} subscription{{ subs_count|pluralize }}.</a></p>
|
||||
<p><a href="{{ root_url_prefix }}{{ list_candidates_path(list.pk) }}">{{ sub_requests_count }} subscription request{{ sub_requests_count|pluralize }}.</a></p>
|
||||
<p>{{ post_count }} post{{ post_count|pluralize }}.</p>
|
||||
<form method="post" class="settings-form">
|
||||
<fieldset>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
{% include "header.html" %}
|
||||
<div class="body body-grid">
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border: 2px solid rgb(200,200,200);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid rgb(190,190,190);
|
||||
padding: 0.1rem 1rem;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<p>{{ subs|length }} entr{{ subs|length|pluralize("y","ies") }}.</a></p>
|
||||
{% if subs %}
|
||||
<div style="overflow: scroll;">
|
||||
<table>
|
||||
<tr>
|
||||
{% for key,val in subs|first|items %}
|
||||
<th>{{ key }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for s in subs %}
|
||||
<tr>
|
||||
{% for key,val in s|items %}
|
||||
<td>{{ val }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% include "footer.html" %}
|
Loading…
Reference in New Issue