Browse Source

feat: prepare for push notification II

master
hole-thu 3 years ago
parent
commit
dbb5732938
  1. 3
      .gitignore
  2. 5
      Cargo.toml
  3. 61
      src/api/attention.rs
  4. 12
      src/api/mod.rs
  5. 1
      src/main.rs

3
.gitignore vendored

@ -1,3 +1,6 @@
# private key
keys
# --> sqlite3 # --> sqlite3
*.db *.db

5
Cargo.toml

@ -8,7 +8,7 @@ license = "WTFPL-2.0"
[features] [features]
default = ["mastlogin"] default = ["mastlogin"]
mastlogin = ["url", "reqwest"] mastlogin = ["reqwest"]
[dependencies] [dependencies]
rocket = { version = "=0.5.0-rc.1", features = ["json"] } rocket = { version = "=0.5.0-rc.1", features = ["json"] }
@ -26,6 +26,7 @@ dotenv = "0.15.0"
sha2 = "0.10.2" sha2 = "0.10.2"
log = "0.4.16" log = "0.4.16"
env_logger = "0.9.0" env_logger = "0.9.0"
web-push = "0.9.2"
url = "2.2.2"
url = { version="2.2.2",optional = true }
reqwest = { version = "0.11.10", features = ["json"], optional = true } reqwest = { version = "0.11.10", features = ["json"], optional = true }

61
src/api/attention.rs

@ -9,6 +9,13 @@ use crate::schema;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
use rocket::form::Form; use rocket::form::Form;
use rocket::serde::json::json; use rocket::serde::json::json;
use rocket::serde::json::serde_json;
use rocket::serde::Serialize;
use std::fs::File;
use url::Url;
use web_push::{
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
};
#[derive(FromForm)] #[derive(FromForm)]
pub struct AttentionInput { pub struct AttentionInput {
@ -76,3 +83,57 @@ pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi
code0!(ps_data) code0!(ps_data)
} }
#[derive(FromForm)]
pub struct NotificatinInput {
enable: bool,
endpoint: String,
auth: String,
p256dh: String,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct PushData {
title: String,
pid: i32,
text: String,
}
#[post("/post/<pid>/notification", data = "<ni>")]
pub async fn set_notification(pid: i32, ni: Form<NotificatinInput>, _user: CurrentUser) -> JsonApi {
let url_host = Url::parse(&ni.endpoint)
.map_err(|_| UnknownPushEndpoint)?
.host()
.ok_or(UnknownPushEndpoint)?
.to_string();
(url_host.ends_with("googleapis.com") || url_host.ends_with("mozilla.com"))
.then(|| ())
.ok_or(UnknownPushEndpoint)?;
if ni.enable {
let subscription_info = SubscriptionInfo::new(&ni.endpoint, &ni.p256dh, &ni.auth);
let file = File::open("keys/private.pem").unwrap();
let sig_builder = VapidSignatureBuilder::from_pem(file, &subscription_info)
.unwrap()
.build()
.unwrap();
let mut builder = WebPushMessageBuilder::new(&subscription_info).unwrap();
let data = PushData {
title: "测试".to_owned(),
pid,
text: format!("#{} 开启提醒测试成功,消息提醒功能即将正式上线", &pid),
};
let content = serde_json::to_string(&data).unwrap();
builder.set_payload(ContentEncoding::Aes128Gcm, content.as_bytes());
builder.set_vapid_signature(sig_builder);
let client = WebPushClient::new()?;
client.send(builder.build()?).await?;
}
code0!()
}

12
src/api/mod.rs

@ -119,12 +119,14 @@ pub enum PolicyError {
YouAreTmp, YouAreTmp,
NoReason, NoReason,
OldApi, OldApi,
UnknownPushEndpoint,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ApiError { pub enum ApiError {
Db(diesel::result::Error), Db(diesel::result::Error),
Rds(redis::RedisError), Rds(redis::RedisError),
WebPush(web_push::WebPushError),
Pc(PolicyError), Pc(PolicyError),
IO(std::io::Error), IO(std::io::Error),
} }
@ -134,6 +136,7 @@ impl<'r> Responder<'r, 'static> for ApiError {
match self { match self {
ApiError::Db(e) => e2s!(e).respond_to(req), ApiError::Db(e) => e2s!(e).respond_to(req),
ApiError::Rds(e) => e2s!(e).respond_to(req), ApiError::Rds(e) => e2s!(e).respond_to(req),
ApiError::WebPush(e) => e2s!(e).respond_to(req),
ApiError::IO(e) => e2s!(e).respond_to(req), ApiError::IO(e) => e2s!(e).respond_to(req),
ApiError::Pc(e) => json!({ ApiError::Pc(e) => json!({
"code": -1, "code": -1,
@ -144,7 +147,8 @@ impl<'r> Responder<'r, 'static> for ApiError {
PolicyError::TitleUsed => "头衔已被使用", PolicyError::TitleUsed => "头衔已被使用",
PolicyError::YouAreTmp => "临时用户只可发布内容和进入单个洞", PolicyError::YouAreTmp => "临时用户只可发布内容和进入单个洞",
PolicyError::NoReason => "未填写理由", PolicyError::NoReason => "未填写理由",
PolicyError::OldApi => "请使用最新版前端地址并检查更新" PolicyError::OldApi => "请使用最新版前端地址并检查更新",
PolicyError::UnknownPushEndpoint => "未知的浏览器推送地址",
} }
}) })
.respond_to(req), .respond_to(req),
@ -152,6 +156,12 @@ impl<'r> Responder<'r, 'static> for ApiError {
} }
} }
impl From<web_push::WebPushError> for ApiError {
fn from(err: web_push::WebPushError) -> ApiError {
ApiError::WebPush(err)
}
}
impl From<diesel::result::Error> for ApiError { impl From<diesel::result::Error> for ApiError {
fn from(err: diesel::result::Error) -> ApiError { fn from(err: diesel::result::Error) -> ApiError {
ApiError::Db(err) ApiError::Db(err)

1
src/main.rs

@ -90,6 +90,7 @@ async fn main() -> Result<(), rocket::Error> {
api::comment::add_comment, api::comment::add_comment,
api::upload::local_upload, api::upload::local_upload,
cors::options_handler, cors::options_handler,
api::attention::set_notification,
], ],
) )
.mount( .mount(

Loading…
Cancel
Save