notifications view

This commit is contained in:
realaravinth 2021-07-12 21:22:26 +05:30
parent b7ec1bca22
commit 47cca5c9a7
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
14 changed files with 163 additions and 78 deletions

View file

@ -107,6 +107,21 @@
] ]
} }
}, },
"45d9e9fb6344fe3a18c2529d50c935d3837bfe25c96595beb6970d6067720578": {
"query": "insert into mcaptcha_users \n (name , password, email, secret) values ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
}
},
"47fa50aecfb1499b0a18fa9299643017a1a8d69d4e9980032e0d8f745465d14f": { "47fa50aecfb1499b0a18fa9299643017a1a8d69d4e9980032e0d8f745465d14f": {
"query": "SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE email = $1)", "query": "SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE email = $1)",
"describe": { "describe": {
@ -506,21 +521,6 @@
"nullable": [] "nullable": []
} }
}, },
"d64ed8a42ff8d8ff0db5a409c9d2ea7d61ea43c90e548a29a3a5a47679dbcd4b": {
"query": "INSERT INTO mcaptcha_users \n (name , password, email, secret) VALUES ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
}
},
"d9a097cba4552c17b410fcb8745dd9b2eae5146f7b710006a50ae6aa2add54fa": { "d9a097cba4552c17b410fcb8745dd9b2eae5146f7b710006a50ae6aa2add54fa": {
"query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n );", "query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n );",
"describe": { "describe": {

View file

@ -154,8 +154,8 @@ pub mod runners {
let res; let res;
if let Some(email) = &payload.email { if let Some(email) = &payload.email {
res = sqlx::query!( res = sqlx::query!(
"INSERT INTO mcaptcha_users "insert into mcaptcha_users
(name , password, email, secret) VALUES ($1, $2, $3, $4)", (name , password, email, secret) values ($1, $2, $3, $4)",
&username, &username,
&hash, &hash,
&email, &email,
@ -182,7 +182,7 @@ pub mod runners {
if msg.contains("mcaptcha_users_name_key") { if msg.contains("mcaptcha_users_name_key") {
return Err(ServiceError::UsernameTaken); return Err(ServiceError::UsernameTaken);
} else if msg.contains("mcaptcha_users_email_key") { } else if msg.contains("mcaptcha_users_email_key") {
return Err(ServiceError::EmailTaken); return Err(ServiceError::UsernameTaken);
} else if msg.contains("mcaptcha_users_secret_key") { } else if msg.contains("mcaptcha_users_secret_key") {
continue; continue;
} else { } else {

View file

@ -21,7 +21,7 @@ pub mod account;
pub mod auth; pub mod auth;
pub mod mcaptcha; pub mod mcaptcha;
pub mod meta; pub mod meta;
mod notifications; pub mod notifications;
pub mod pow; pub mod pow;
mod routes; mod routes;

View file

@ -63,25 +63,39 @@ pub async fn get_notification(
let receiver = id.identity().unwrap(); let receiver = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist // TODO handle error where payload.to doesnt exist
let mut notifications = sqlx::query_file_as!( let resp = runner::get_notification(&data, &receiver).await?;
Notification,
"src/api/v1/notifications/get_all_unread.sql",
&receiver
)
.fetch_all(&data.db)
.await?;
let resp: Vec<NotificationResp> = notifications
.drain(0..)
.map(|x| {
let y: NotificationResp = x.into();
y
})
.collect();
Ok(HttpResponse::Ok().json(resp)) Ok(HttpResponse::Ok().json(resp))
} }
pub mod runner {
use super::*;
pub async fn get_notification(
data: &AppData,
receiver: &str,
) -> ServiceResult<Vec<NotificationResp>> {
// TODO handle error where payload.to doesnt exist
let mut notifications = sqlx::query_file_as!(
Notification,
"src/api/v1/notifications/get_all_unread.sql",
&receiver
)
.fetch_all(&data.db)
.await?;
let resp = notifications
.drain(0..)
.map(|x| {
let y: NotificationResp = x.into();
y
})
.collect();
Ok(resp)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::http::{header, StatusCode}; use actix_web::http::{header, StatusCode};

View file

@ -15,9 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
mod add; pub mod add;
mod get; pub mod get;
mod mark_read; pub mod mark_read;
pub mod routes { pub mod routes {

View file

@ -44,11 +44,9 @@ async fn auth_works() {
confirm_password: PASSWORD.into(), confirm_password: PASSWORD.into(),
email: None, email: None,
}; };
let resp = test::call_service( let resp =
&app, test::call_service(&app, post_request!(&msg, ROUTES.auth.register).to_request())
post_request!(&msg, ROUTES.auth.register).to_request(), .await;
)
.await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
// delete user // delete user
delete_user(NAME, &data).await; delete_user(NAME, &data).await;

View file

@ -143,17 +143,19 @@ pub struct Data {
} }
impl Data { impl Data {
#[cfg(not(tarpaulin_include))] pub fn get_creds() -> Config {
/// create new instance of app data ConfigBuilder::default()
pub async fn new() -> Arc<Self> {
let creds = ConfigBuilder::default()
.username_case_mapped(true) .username_case_mapped(true)
.profanity(true) .profanity(true)
.blacklist(true) .blacklist(true)
.password_policy(PasswordPolicy::default()) .password_policy(PasswordPolicy::default())
.build() .build()
.unwrap(); .unwrap()
}
#[cfg(not(tarpaulin_include))]
/// create new instance of app data
pub async fn new() -> Arc<Self> {
let creds = Self::get_creds();
let c = creds.clone(); let c = creds.clone();
let init = thread::spawn(move || { let init = thread::spawn(move || {

View file

@ -123,11 +123,9 @@ mod tests {
let uri = format!("{}{}", DOCS.home, FILE); let uri = format!("{}{}", DOCS.home, FILE);
let resp = test::call_service( let resp =
&app, test::call_service(&app, test::TestRequest::get().uri(&uri).to_request())
test::TestRequest::get().uri(&uri).to_request(), .await;
)
.await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
} }

View file

@ -60,14 +60,13 @@ mod tests {
PAGES.home, PAGES.home,
PAGES.panel.sitekey.add, PAGES.panel.sitekey.add,
PAGES.panel.sitekey.list, PAGES.panel.sitekey.list,
PAGES.panel.notifications,
]; ];
for url in urls.iter() { for url in urls.iter() {
let resp = test::call_service( let resp =
&app, test::call_service(&app, test::TestRequest::get().uri(url).to_request())
test::TestRequest::get().uri(url).to_request(), .await;
)
.await;
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
let authenticated_resp = test::call_service( let authenticated_resp = test::call_service(
@ -91,11 +90,9 @@ mod tests {
let urls = vec![PAGES.auth.login, PAGES.auth.join]; let urls = vec![PAGES.auth.login, PAGES.auth.join];
for url in urls.iter() { for url in urls.iter() {
let resp = test::call_service( let resp =
&app, test::call_service(&app, test::TestRequest::get().uri(url).to_request())
test::TestRequest::get().uri(url).to_request(), .await;
)
.await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }

View file

@ -19,6 +19,7 @@ use actix_identity::Identity;
use actix_web::{HttpResponse, Responder}; use actix_web::{HttpResponse, Responder};
use sailfish::TemplateOnce; use sailfish::TemplateOnce;
mod notifications;
pub mod sitekey; pub mod sitekey;
use crate::errors::PageResult; use crate::errors::PageResult;
@ -51,6 +52,7 @@ async fn panel(data: AppData, id: Identity) -> PageResult<impl Responder> {
pub fn services(cfg: &mut actix_web::web::ServiceConfig) { pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(panel); cfg.service(panel);
sitekey::services(cfg); sitekey::services(cfg);
cfg.service(notifications::notifications);
} }
pub mod routes { pub mod routes {
@ -58,6 +60,7 @@ pub mod routes {
pub struct Panel { pub struct Panel {
pub home: &'static str, pub home: &'static str,
pub sitekey: Sitekey, pub sitekey: Sitekey,
pub notifications: &'static str,
} }
impl Panel { impl Panel {
@ -65,6 +68,7 @@ pub mod routes {
Panel { Panel {
home: "/", home: "/",
sitekey: Sitekey::new(), sitekey: Sitekey::new(),
notifications: "/notifications",
} }
} }
} }

View file

@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use actix_identity::Identity;
use actix_web::{HttpResponse, Responder};
use sailfish::TemplateOnce;
use crate::api::v1::notifications::get::{runner, NotificationResp};
use crate::errors::PageResult;
use crate::AppData;
#[derive(TemplateOnce)]
#[template(path = "panel/notifications/index.html")]
pub struct IndexPage {
/// notifications
n: Vec<NotificationResp>,
}
impl IndexPage {
fn new(n: Vec<NotificationResp>) -> Self {
IndexPage { n }
}
}
const PAGE: &str = "Notifications";
#[my_codegen::get(path = "crate::PAGES.panel.notifications", wrap = "crate::CheckLogin")]
pub async fn notifications(data: AppData, id: Identity) -> PageResult<impl Responder> {
let receiver = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist
let notifications = runner::get_notification(&data, &receiver).await?;
let body = IndexPage::new(notifications).render_once().unwrap();
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(body))
}

View file

@ -51,11 +51,9 @@ macro_rules! post_request {
#[macro_export] #[macro_export]
macro_rules! get_works { macro_rules! get_works {
($app:expr,$route:expr ) => { ($app:expr,$route:expr ) => {
let list_sitekey_resp = test::call_service( let list_sitekey_resp =
&$app, test::call_service(&$app, test::TestRequest::get().uri($route).to_request())
test::TestRequest::get().uri($route).to_request(), .await;
)
.await;
assert_eq!(list_sitekey_resp.status(), StatusCode::OK); assert_eq!(list_sitekey_resp.status(), StatusCode::OK);
}; };
} }
@ -116,11 +114,9 @@ pub async fn register(name: &str, email: &str, password: &str) {
confirm_password: password.into(), confirm_password: password.into(),
email: Some(email.into()), email: Some(email.into()),
}; };
let resp = test::call_service( let resp =
&app, test::call_service(&app, post_request!(&msg, ROUTES.auth.register).to_request())
post_request!(&msg, ROUTES.auth.register).to_request(), .await;
)
.await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@ -134,11 +130,9 @@ pub async fn signin(name: &str, password: &str) -> (Arc<Data>, Login, ServiceRes
login: name.into(), login: name.into(),
password: password.into(), password: password.into(),
}; };
let signin_resp = test::call_service( let signin_resp =
&app, test::call_service(&app, post_request!(&creds, ROUTES.auth.login).to_request())
post_request!(&creds, ROUTES.auth.login).to_request(), .await;
)
.await;
assert_eq!(signin_resp.status(), StatusCode::OK); assert_eq!(signin_resp.status(), StatusCode::OK);
(data, creds, signin_resp) (data, creds, signin_resp)
} }

View file

@ -16,9 +16,11 @@
</li> </li>
<li class="taskbar__action"> <li class="taskbar__action">
<a href="<.= crate::PAGES.panel.notifications .>">
<img class="taskbar__icon" src="<.= <img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>" crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>"
alt="Notifications" /> alt="Notifications" />
</a>
</li> </li>
<li class="taskbar__action"> <li class="taskbar__action">

View file

@ -0,0 +1,24 @@
<. include!("../../components/headers/index.html"); .>
<. include!("../navbar/index.html"); .>
<div class="tmp-layout">
<. include!("../header/index.html"); .>
<main class="panel-main">
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Notifications</h1>
<. for notification in n.iter() { .>
<li class="sitekey-list__item">
<h3><.= notification.heading .> </h3>
<p>From: <.= notification.name .> </p>
<p>Received: <.= notification.received .> </p>
<p>Message: <.= notification.message .> </p>
<. } .>
</ul>
</div>
<!-- end of container -->
<. include!("../../components/footers.html"); .>