Skip to content

Commit

Permalink
census article view count and comment count
Browse files Browse the repository at this point in the history
  • Loading branch information
sunheartGH committed Jan 10, 2018
1 parent 8dbff31 commit a12a24b
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 9 deletions.
2 changes: 2 additions & 0 deletions migrations/20180103210155_article_stats/down.sql
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE article_stats;
9 changes: 9 additions & 0 deletions migrations/20180103210155_article_stats/up.sql
@@ -0,0 +1,9 @@
-- Your SQL goes here
CREATE TABLE article_stats (
id uuid primary key default gen_random_uuid(),
article_id uuid references article (id) not null,
created_time timestamp not null default current_timestamp,
ruser_id uuid references ruser (id),
user_agent varchar,
visitor_ip varchar
);
18 changes: 14 additions & 4 deletions src/api/visitor_api.rs
Expand Up @@ -5,10 +5,11 @@ use sapper_std::{set_cookie, JsonParams, QueryParams};
use serde_json;
use uuid::Uuid;

use super::super::{LoginUser, Postgresql, RUser, Redis, RegisteredUser};
use super::super::{LoginUser, Postgresql, RUser, Redis, RegisteredUser, NewArticleStats};
use super::super::{inner_get_github_nickname_and_address, inner_get_github_token};
use super::super::models::{Article, CommentWithNickName};
use super::super::page_size;
use super::super::{get_ruser_from_session, get_real_ip_from_req, get_user_agent_from_req};

pub struct Visitor;

Expand Down Expand Up @@ -231,10 +232,19 @@ impl Visitor {

match Article::query_article_md(&pg_pool, article_id) {
Ok(data) => {
// create article view record
let article_stats = NewArticleStats {
article_id: article_id,
ruser_id: get_ruser_from_session(req).map(|user| user.id),
user_agent: get_user_agent_from_req(req),
visitor_ip: get_real_ip_from_req(req),
};
article_stats.insert(&pg_pool).unwrap();

let res = json!({
"status": true,
"data": data,
});
"status": true,
"data": data,
});

response.write_body(serde_json::to_string(&res).unwrap());
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Expand Up @@ -39,13 +39,15 @@ pub mod web_wechat;
pub mod proxy;

pub(crate) use models::{Article, ArticleBrief, DeleteArticle, EditArticle, NewArticle};
pub(crate) use models::{NewArticleStats};
pub(crate) use models::{ChangStatus, ChangePassword, ChangePermission, EditUser, LoginUser, RUser, RegisteredUser};
pub(crate) use models::{DeleteComment, NewComment};
pub(crate) use models::{InsertSection, PubNotice, Section};
pub(crate) use schema::{article, comment, ruser, section};
pub(crate) use schema::{article, comment, ruser, section, article_stats};
pub(crate) use util::{inner_get_github_nickname_and_address, inner_get_github_primary_email,
inner_get_github_token, markdown_render, random_string, send_reset_password_email, sha3_256_encode};
pub(crate) use util::{get_github_nickname_and_address, get_github_primary_email, get_github_token};
pub(crate) use util::{get_ruser_from_session, get_real_ip_from_req, get_user_agent_from_req};

pub use api::{AdminSection, AdminUser, User, Visitor};
pub use proxy::ProxyModule;
Expand Down
68 changes: 66 additions & 2 deletions src/models/articles.rs
Expand Up @@ -10,6 +10,9 @@ use diesel::prelude::*;
use serde_json;
use std::sync::Arc;
use uuid::Uuid;
use diesel::dsl::*;
use diesel::types::BigInt;
use diesel::expression::SqlLiteral;

#[derive(Queryable)]
struct RawArticles {
Expand All @@ -23,6 +26,41 @@ struct RawArticles {
stype: i32, // 0 section, 1 user blog
created_time: NaiveDateTime,
status: i16, // 0 normal, 1 frozen, 2 deleted

view_count: i64,
comment_count: i64,
}

type SelectRawArticles = (
article::id,
article::title,
article::raw_content,
article::content,
article::section_id,
article::author_id,
article::tags,
article::stype,
article::created_time,
article::status,
SqlLiteral<BigInt>,
SqlLiteral<BigInt>,
);

fn select_raw_articles() -> SelectRawArticles {
(
article::id,
article::title,
article::raw_content,
article::content,
article::section_id,
article::author_id,
article::tags,
article::stype,
article::created_time,
article::status,
sql::<BigInt>("(select (count(article_stats.id) + 1) from article_stats where article_stats.article_id = article.id)"),
sql::<BigInt>("(select count(comment.id) from comment where comment.status = 0 and comment.article_id = article.id)"),
)
}

impl RawArticles {
Expand All @@ -37,6 +75,9 @@ impl RawArticles {
created_time: self.created_time,
status: self.status,
stype: self.stype,

view_count: self.view_count,
comment_count: self.comment_count,
}
}

Expand All @@ -51,6 +92,9 @@ impl RawArticles {
created_time: self.created_time,
status: self.status,
stype: self.stype,

view_count: self.view_count,
comment_count: self.comment_count,
}
}

Expand Down Expand Up @@ -78,6 +122,9 @@ pub struct Article {
pub created_time: NaiveDateTime,
pub status: i16,
pub stype: i32,

pub view_count: i64,
pub comment_count: i64,
}

#[derive(Queryable, Debug, Clone, Deserialize, Serialize)]
Expand All @@ -89,6 +136,8 @@ pub struct ArticleBrief {
pub created_time: NaiveDateTime,

pub author_name: String,
pub view_count: i64,
pub comment_count: i64,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
Expand All @@ -110,6 +159,9 @@ pub struct BlogBrief {
pub tags: String,
pub created_time: NaiveDateTime,
pub author_name: String,

pub view_count: i64,
pub comment_count: i64,
}

#[derive(Debug)]
Expand All @@ -122,6 +174,7 @@ pub struct ArticlesWithTotal<T> {
impl Article {
pub fn query_article(conn: &PgConnection, id: Uuid) -> Result<Article, String> {
let res = all_articles
.select(select_raw_articles())
.filter(article::status.ne(2))
.filter(article::id.eq(id))
.get_result::<RawArticles>(conn);
Expand All @@ -135,6 +188,7 @@ impl Article {
let res = all_articles
.filter(article::status.ne(2))
.filter(article::id.eq(id))
.select(select_raw_articles())
.get_result::<RawArticles>(conn);
match res {
Ok(data) => Ok(data.into_markdown()),
Expand All @@ -147,6 +201,7 @@ impl Article {
.filter(article::status.ne(2))
.filter(article::id.eq(id))
.filter(article::stype.eq(1))
.select(select_raw_articles())
.get_result::<RawArticles>(conn);
match res {
Ok(data) => Ok(data.into_html()),
Expand All @@ -158,6 +213,7 @@ impl Article {
let res = all_articles
.filter(article::status.ne(2))
.filter(article::id.eq(id))
.select(select_raw_articles())
.get_result::<RawArticles>(conn);
match res {
Ok(data) => Ok(data.into_markdown()),
Expand All @@ -169,6 +225,7 @@ impl Article {
let res = all_articles
.filter(article::section_id.eq(id))
.filter(article::status.ne(2))
.select(select_raw_articles())
.order(article::created_time.desc())
.get_results::<RawArticles>(conn);
match res {
Expand Down Expand Up @@ -198,7 +255,8 @@ impl Article {
.filter(article::section_id.eq(id))
.filter(article::status.ne(2));

let res = _res.order(article::created_time.desc())
let res = _res.select(select_raw_articles())
.order(article::created_time.desc())
.offset(page_size * (page - 1) as i64)
.limit(page_size)
.get_results::<RawArticles>(conn);
Expand Down Expand Up @@ -233,6 +291,8 @@ impl Article {
article::tags,
article::created_time,
ruser::nickname,
sql::<BigInt>("(select count(article_stats.id) from article_stats where article_stats.article_id = article.id)"),
sql::<BigInt>("(select count(comment.id) from comment where comment.status = 0 and comment.article_id = article.id)"),
))
.order(article::created_time.desc())
.offset(page_size * (page - 1) as i64)
Expand Down Expand Up @@ -271,12 +331,14 @@ impl Article {
article::tags,
article::created_time,
ruser::nickname,
sql::<BigInt>("(select count(article_stats.id) from article_stats where article_stats.article_id = article.id)"),
sql::<BigInt>("(select count(comment.id) from comment where comment.status = 0 and comment.article_id = article.id)"),
))
.order(article::created_time.desc())
.offset(page_size * (page - 1) as i64)
.limit(page_size)
.get_results::<ArticleBrief>(conn);

let all_count: i64 = _res.count().get_result(conn).unwrap();

match res {
Expand Down Expand Up @@ -336,6 +398,8 @@ impl Article {
article::tags,
article::created_time,
ruser::nickname,
sql::<BigInt>("(select count(article_stats.id) from article_stats where article_stats.article_id = article.id)"),
sql::<BigInt>("(select count(comment.id) from comment where comment.status = 0 and comment.article_id = article.id)"),
))
.order(article::created_time.desc())
.offset(page_size * (page - 1) as i64)
Expand Down
48 changes: 48 additions & 0 deletions src/models/articles_stats.rs
@@ -0,0 +1,48 @@
use super::super::{article_stats};
use super::super::article_stats::dsl::article_stats as all_articles_stats;

use chrono::NaiveDateTime;
use diesel;
use diesel::PgConnection;
use diesel::prelude::*;
use uuid::Uuid;

#[derive(Queryable, Debug, Clone, Serialize, Deserialize)]
pub struct QueryArticleStats {
pub id: Uuid,
pub article_id: Uuid,
pub created_time: NaiveDateTime,
pub ruser_id: Option<Uuid>,
pub user_agent: Option<String>,
pub visitor_ip: Option<String>,
}

#[derive(Insertable, Debug, Clone)]
#[table_name = "article_stats"]
pub struct NewArticleStats {
pub article_id: Uuid,
pub ruser_id: Option<Uuid>,
pub user_agent: Option<String>,
pub visitor_ip: Option<String>,
}

impl NewArticleStats {
pub fn new(self, conn: &PgConnection) -> Result<usize, String> {
let res = diesel::insert_into(all_articles_stats)
.values(&self)
.execute(conn);
match res {
Ok(data) => Ok(data),
Err(err) => Err(format!("{}", err)),
}
}
pub fn insert(self, conn: &PgConnection) -> Result<usize, String> {
let res = diesel::insert_into(all_articles_stats)
.values(&self)
.execute(conn);
match res {
Ok(data) => Ok(data),
Err(err) => Err(format!("{}", err)),
}
}
}
2 changes: 2 additions & 0 deletions src/models/mod.rs
Expand Up @@ -2,11 +2,13 @@ pub mod articles;
pub mod comments;
pub mod sections;
pub mod rusers;
pub mod articles_stats;

pub(crate) use self::articles::{Article, ArticleBrief, DeleteArticle, EditArticle, NewArticle};
pub(crate) use self::comments::{CommentWithNickName, DeleteComment, NewComment};
pub(crate) use self::rusers::{ChangePassword, ChangePermission, EditUser, LoginUser, RUser, RegisteredUser};
pub(crate) use self::sections::{InsertSection, PubNotice, Section};
pub(crate) use self::articles_stats::{NewArticleStats};

use uuid::Uuid;

Expand Down
37 changes: 37 additions & 0 deletions src/util/mod.rs
Expand Up @@ -16,6 +16,7 @@ use comrak::{markdown_to_html, ComrakOptions};
use rand::{thread_rng, Rng};
use sapper::{Client, Key, Request};
use sapper::header::ContentType;
use sapper::header::UserAgent;
use sapper_std::{Context, SessionVal};
use serde_json;
use serde_urlencoded;
Expand Down Expand Up @@ -101,6 +102,42 @@ pub fn send_reset_password_email(new_password: &str, email: &str) {
println!("{} reset the password", email)
}

/// get user if login or none
pub fn get_ruser_from_session(req: &Request) -> Option<RUser> {
let redis_pool = req.ext().get::<Redis>().unwrap();
match req.ext().get::<SessionVal>() {
Some(cookie) => {
if redis_pool.exists(cookie) {
let redis_pool = req.ext().get::<Redis>().unwrap();
let user: RUser = serde_json::from_str(&RUser::view_with_cookie(redis_pool, cookie)).unwrap();
Some(user)
} else {
None
}
},
None => None,
}
}

/// get request's real ip when request proxyed by nginx or normal ip
pub fn get_real_ip_from_req(req: &Request) -> Option<String> {
match req.headers().get_raw("X-Real-IP") {
Some(fip) => String::from_utf8((*fip)[0].clone()).ok(),
None => {
serde_json::to_string(&req.remote_addr().ip()).ok().map(|s| {
String::from(&s[1..s.len() - 1])
})
}
}
}

/// get request's user-agent
pub fn get_user_agent_from_req(req: &Request) -> Option<String> {
req.headers().get::<UserAgent>().map(|user_agent| {
String::from(user_agent.trim())
})
}

pub struct Permissions;

impl Key for Permissions {
Expand Down
12 changes: 11 additions & 1 deletion src/web/article.rs
@@ -1,7 +1,8 @@
use super::super::{Article, Permissions, RUser, WebContext};
use super::super::{Article, Permissions, RUser, WebContext, NewArticleStats};
use super::super::Postgresql;
use super::super::models::CommentWithNickName;
use super::super::page_size;
use super::super::{get_ruser_from_session, get_real_ip_from_req, get_user_agent_from_req};
use sapper::{Request, Response, Result as SapperResult, SapperModule, SapperRouter};
use sapper_std::{render, PathParams};
use uuid::Uuid;
Expand All @@ -23,6 +24,15 @@ impl WebArticle {
let res = Article::query_article(&pg_conn, id);
match res {
Ok(r) => {
// create article view record
let article_stats = NewArticleStats {
article_id: r.id,
ruser_id: get_ruser_from_session(req).map(|user| user.id),
user_agent: get_user_agent_from_req(req),
visitor_ip: get_real_ip_from_req(req),
};
article_stats.insert(&pg_conn).unwrap();

// article
web.add("res", &r);

Expand Down

0 comments on commit a12a24b

Please sign in to comment.