use crate::db_conn::Db; use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::random_hasher::RandomHasher; use crate::rds_conn::RdsConn; use crate::rds_models::*; use crate::schema; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use rocket::http::Status; use rocket::outcome::try_outcome; use rocket::request::{FromRequest, Outcome, Request}; use rocket::response::{self, Responder}; use rocket::serde::json::{json, Value}; macro_rules! code0 { () => ( Ok(json!({"code": 0})) ); ($data:expr) => ( Ok(json!({ "code": 0, "data": $data, })) ); } /* macro_rules! code1 { ($msg:expr) => ( Ok(json!({ "code": 1, "msg": $msg, })) ); } */ macro_rules! e2s { ($e:expr) => (json!({ "code": -1, "msg": $e.to_string() })); } #[catch(401)] pub fn catch_401_error() -> &'static str { "未登录或token过期" } #[catch(403)] pub fn catch_403_error() -> &'static str { "可能被封禁了,等下次重置吧" } pub struct CurrentUser { pub id: Option, // tmp user has no id, only for block namehash: String, is_admin: bool, custom_title: String, pub auto_block_rank: u8, } #[rocket::async_trait] impl<'r> FromRequest<'r> for CurrentUser { type Error = (); async fn from_request(request: &'r Request<'_>) -> Outcome { let rh = request.rocket().state::().unwrap(); let rconn = try_outcome!(request.guard::().await); let mut id = None; let mut namehash = None; let mut is_admin = false; if let Some(token) = request.headers().get_one("User-Token") { let sp = token.split('_').collect::>(); if sp.len() == 2 && sp[0] == rh.get_tmp_token() { namehash = Some(rh.hash_with_salt(sp[1])); id = None; is_admin = false; } else { let db = try_outcome!(request.guard::().await); if let Some(u) = User::get_by_token(&db, &rconn, token).await { id = Some(u.id); namehash = Some(rh.hash_with_salt(&u.name)); is_admin = u.is_admin; } } } match namehash { Some(nh) => { if BannedUsers::has(&rconn, &nh).await.unwrap() { Outcome::Failure((Status::Forbidden, ())) } else { Outcome::Success(CurrentUser { id, custom_title: CustomTitle::get(&rconn, &nh) .await .ok() .flatten() .unwrap_or_default(), auto_block_rank: AutoBlockRank::get(&rconn, &nh).await.unwrap_or(2), namehash: nh, is_admin, }) } } None => Outcome::Failure((Status::Unauthorized, ())), } } } #[derive(Debug)] pub enum PolicyError { IsReported, IsDeleted, NotAllowed, TitleUsed, YouAreTmp, NoReason, OldApi, UnknownPushEndpoint, } #[derive(Debug)] pub enum ApiError { Db(diesel::result::Error), Rds(redis::RedisError), WebPush(web_push::WebPushError), Pc(PolicyError), IO(std::io::Error), } impl<'r> Responder<'r, 'static> for ApiError { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { match self { ApiError::Db(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::Pc(e) => json!({ "code": -1, "msg": match e { PolicyError::IsReported => "内容被举报,处理中", PolicyError::IsDeleted => "内容被删除", PolicyError::NotAllowed => "不允许的操作", PolicyError::TitleUsed => "头衔已被使用", PolicyError::YouAreTmp => "临时用户只可发布内容和进入单个洞", PolicyError::NoReason => "未填写理由", PolicyError::OldApi => "请使用最新版前端地址并检查更新", PolicyError::UnknownPushEndpoint => "未知的浏览器推送地址", } }) .respond_to(req), } } } impl From for ApiError { fn from(err: web_push::WebPushError) -> ApiError { ApiError::WebPush(err) } } impl From for ApiError { fn from(err: diesel::result::Error) -> ApiError { ApiError::Db(err) } } impl From for ApiError { fn from(err: redis::RedisError) -> ApiError { ApiError::Rds(err) } } impl From for ApiError { fn from(err: std::io::Error) -> ApiError { ApiError::IO(err) } } impl From for ApiError { fn from(err: PolicyError) -> ApiError { ApiError::Pc(err) } } pub type Api = Result; pub type JsonApi = Api; #[rocket::async_trait] pub trait Ugc { fn get_author_hash(&self) -> &str; fn get_is_deleted(&self) -> bool; fn get_is_reported(&self) -> bool; fn extra_delete_condition(&self) -> bool; async fn do_set_deleted(&mut self, db: &Db) -> Api<()>; fn check_permission(&self, user: &CurrentUser, mode: &str) -> Api<()> { if user.is_admin { return Ok(()); } if mode.contains('r') && self.get_is_deleted() { return Err(ApiError::Pc(PolicyError::IsDeleted)); } if mode.contains('o') && self.get_is_reported() { return Err(ApiError::Pc(PolicyError::IsReported)); } if mode.contains('w') && self.get_author_hash() != user.namehash { return Err(ApiError::Pc(PolicyError::NotAllowed)); } if mode.contains('d') && !self.extra_delete_condition() { return Err(ApiError::Pc(PolicyError::NotAllowed)); } Ok(()) } async fn soft_delete(&mut self, user: &CurrentUser, db: &Db) -> Api<()> { self.check_permission(user, "rwd")?; self.do_set_deleted(db).await?; Ok(()) } } #[rocket::async_trait] impl Ugc for Post { fn get_author_hash(&self) -> &str { &self.author_hash } fn get_is_reported(&self) -> bool { self.is_reported } fn get_is_deleted(&self) -> bool { self.is_deleted } fn extra_delete_condition(&self) -> bool { !self.content.starts_with("[系统自动代发]\n") } async fn do_set_deleted(&mut self, db: &Db) -> Api<()> { update!(*self, posts, db, { is_deleted, to true }); Ok(()) } } #[rocket::async_trait] impl Ugc for Comment { fn get_author_hash(&self) -> &str { &self.author_hash } fn get_is_reported(&self) -> bool { false } fn get_is_deleted(&self) -> bool { self.is_deleted } fn extra_delete_condition(&self) -> bool { true } async fn do_set_deleted(&mut self, db: &Db) -> Api<()> { update!(*self, comments, db, { is_deleted, to true }); Ok(()) } } macro_rules! look { ($s:expr) => { format!("{}...{}", &$s[..2], &$s[$s.len() - 2..]) }; } pub mod attention; pub mod comment; pub mod operation; pub mod post; pub mod search; pub mod systemlog; pub mod upload; pub mod vote;