| @@ -0,0 +1,100 @@ | |||
| use std::collections::HashMap; | |||
| use serde::{Deserialize, Serialize}; | |||
| use actix_web::{web, HttpRequest, HttpResponse, Responder}; | |||
| use chrono::NaiveDateTime; | |||
| use crate::models::models::Comment; | |||
| use crate::repositories::comment::{create_comment, get_issue_comments}; | |||
| use crate::repositories::session::get_username_from_session; | |||
| #[derive(Deserialize)] | |||
| pub struct AddCommentRequest { | |||
| pub content: String, | |||
| pub parent: Option<i64>, | |||
| pub issue_id: i32, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct AddCommentResponse { | |||
| pub content: String, | |||
| pub parent: Option<i64>, | |||
| pub telegram_handle: String, | |||
| pub created_at: NaiveDateTime, | |||
| } | |||
| pub async fn add_comment(req: HttpRequest, data: web::Json<AddCommentRequest>) -> impl Responder { | |||
| if let Some(session) = req.cookie("session") { | |||
| if let Some(username) = get_username_from_session(session.value().to_string()) { | |||
| let new_comment = create_comment(data.content.clone(), data.parent, username.clone(), data.issue_id); | |||
| HttpResponse::Ok().json( | |||
| AddCommentResponse { | |||
| content: new_comment.content, | |||
| parent: new_comment.parent, | |||
| telegram_handle: username.clone(), | |||
| created_at: new_comment.created_at.unwrap() | |||
| }) | |||
| } else { | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| } else { | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| } | |||
| #[derive(Deserialize)] | |||
| pub struct GetCommentsRequest { | |||
| pub issue_id: i32, | |||
| } | |||
| #[derive(Serialize, Clone)] | |||
| pub struct CommentWithChildren { | |||
| pub comment: Comment, | |||
| pub children: Vec<CommentWithChildren>, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct GetCommentsResponse { | |||
| pub comments: Vec<CommentWithChildren>, | |||
| } | |||
| fn build_comment_tree(comments: Vec<Comment>) -> Vec<CommentWithChildren> { | |||
| let mut comment_map: HashMap<i64, CommentWithChildren> = HashMap::new(); | |||
| let mut child_map: HashMap<i64, Vec<i64>> = HashMap::new(); | |||
| let mut root_ids: Vec<i64> = Vec::new(); | |||
| for comment in comments { | |||
| let id = comment.id; | |||
| let comment_with_children = CommentWithChildren { | |||
| comment: comment.clone(), | |||
| children: Vec::new(), | |||
| }; | |||
| comment_map.insert(id, comment_with_children); | |||
| if let Some(parent_id) = comment.parent { | |||
| child_map.entry(parent_id).or_default().push(id); | |||
| } else { | |||
| root_ids.push(id); | |||
| } | |||
| } | |||
| fn build_tree(id: i64, comment_map: &mut HashMap<i64, CommentWithChildren>, child_map: &HashMap<i64, Vec<i64>>) -> CommentWithChildren { | |||
| let mut comment = comment_map.remove(&id).unwrap(); | |||
| if let Some(child_ids) = child_map.get(&id) { | |||
| comment.children = child_ids.iter() | |||
| .map(|&child_id| build_tree(child_id, comment_map, child_map)) | |||
| .collect(); | |||
| } | |||
| comment | |||
| } | |||
| root_ids.into_iter() | |||
| .map(|id| build_tree(id, &mut comment_map, &child_map)) | |||
| .collect() | |||
| } | |||
| pub async fn get_comments(data: web::Query<GetCommentsRequest>) -> impl Responder { | |||
| let comments = build_comment_tree(get_issue_comments(data.issue_id)); | |||
| HttpResponse::Ok().json(GetCommentsResponse { | |||
| comments | |||
| }) | |||
| } | |||
| @@ -0,0 +1,153 @@ | |||
| use serde::{Deserialize, Serialize}; | |||
| use actix_web::{web, HttpRequest, HttpResponse, Responder}; | |||
| use crate::models::models::IssueWithSummaryAndVotes; | |||
| use crate::repositories; | |||
| use crate::repositories::issue::{get_issue_vote_equity, get_vote_for_user, record_vote_for_issue, FilterOptions}; | |||
| use crate::repositories::session::get_user_id; | |||
| #[derive(Serialize, Deserialize, Clone)] | |||
| enum IssueVote { | |||
| Positive, | |||
| Negative, | |||
| } | |||
| #[derive(Deserialize)] | |||
| pub struct CreateIssueRequest { | |||
| title: String, | |||
| paragraphs: Vec<String>, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct CreateIssueResponse { | |||
| title: String, | |||
| paragraphs: Vec<String>, | |||
| } | |||
| pub async fn add_issue(req: HttpRequest, data: web::Json<CreateIssueRequest>) -> impl Responder { | |||
| if let Some(session_id) = req.cookie("session") { | |||
| if let Some(session) = repositories::session::get_session(session_id.value().to_string()) { | |||
| repositories::issue::create_issue(&data.title, &data.paragraphs, &session.username); | |||
| let resp = CreateIssueResponse { | |||
| title: String::from(&data.title), | |||
| paragraphs: data.paragraphs.clone(), | |||
| }; | |||
| return HttpResponse::Ok().json(resp); | |||
| } | |||
| HttpResponse::Unauthorized().into() | |||
| } else { | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| } | |||
| #[derive(Deserialize)] | |||
| pub struct IssuesPaginationRequest { | |||
| min_positive_votes: Option<i64>, | |||
| min_votes: Option<i64>, | |||
| offset: i32, | |||
| limit: i16, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct IssuesPaginationResponse { | |||
| issues: Vec<IssueWithSummaryAndVotes>, | |||
| } | |||
| pub async fn list_issues(req: web::Query<IssuesPaginationRequest>) -> impl Responder { | |||
| let issues = repositories::issue::get_issues( | |||
| Option::from(FilterOptions { | |||
| min_positive_votes: req.min_positive_votes, | |||
| min_votes: req.min_votes, | |||
| }), | |||
| req.offset, | |||
| req.limit | |||
| ); | |||
| HttpResponse::Ok().json(IssuesPaginationResponse { issues }) | |||
| } | |||
| #[derive(Deserialize)] | |||
| pub struct GetParagraphsRequest { | |||
| issue_id: i32, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct GetParagraphsResponse { | |||
| paragraphs: Vec<Option<String>>, | |||
| } | |||
| pub async fn get_paragraphs(query: web::Query<GetParagraphsRequest>) -> impl Responder { | |||
| let paragraphs = repositories::issue::get_paragraphs(query.issue_id); | |||
| HttpResponse::Ok().json(GetParagraphsResponse { paragraphs }) | |||
| } | |||
| #[derive(Deserialize, Clone)] | |||
| pub struct VoteIssueRequest { | |||
| issue_id: i32, | |||
| vote: IssueVote, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct VoteIssueResponse { | |||
| issue_id: i32, | |||
| equity: i64, | |||
| positive: Option<bool>, | |||
| } | |||
| pub async fn vote_issue(req: HttpRequest, data: web::Json<VoteIssueRequest>) -> impl Responder { | |||
| let positive_vote = match data.vote { | |||
| IssueVote::Positive => true, | |||
| IssueVote::Negative => false, | |||
| }; | |||
| if let Some(session_id) = req.cookie("session") { | |||
| if let Some(user_id) = get_user_id(session_id.value().to_string()) { | |||
| record_vote_for_issue(positive_vote, data.issue_id, user_id); | |||
| let resp = VoteIssueResponse { | |||
| issue_id: data.issue_id, | |||
| equity: get_issue_vote_equity(data.issue_id), | |||
| positive: match data.vote { | |||
| IssueVote::Positive => Some(true), | |||
| IssueVote::Negative => Some(false), | |||
| }, | |||
| }; | |||
| HttpResponse::Ok().json(resp) | |||
| } else { | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| } else { | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| } | |||
| #[derive(Deserialize)] | |||
| pub struct GetUserVoteRequest { | |||
| issue_id: i32, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct GetUserVoteResponse { | |||
| vote: Option<IssueVote>, | |||
| } | |||
| pub async fn get_user_vote(req: HttpRequest, query: web::Query<GetUserVoteRequest>) -> impl Responder { | |||
| let mut resp = GetUserVoteResponse { vote: None }; | |||
| if let Some(session_cookie) = req.cookie("session") { | |||
| return if let Some(user_id) = get_user_id(session_cookie.value().to_string()) { | |||
| let vote = get_vote_for_user(query.issue_id, user_id); | |||
| resp = GetUserVoteResponse { | |||
| vote: match vote { | |||
| Some(val) => { | |||
| if val { | |||
| Some(IssueVote::Positive) | |||
| } else { | |||
| Some(IssueVote::Negative) | |||
| } | |||
| }, | |||
| None => None, | |||
| } | |||
| }; | |||
| HttpResponse::Ok().json(resp) | |||
| } else { | |||
| HttpResponse::Ok().json(GetUserVoteResponse { vote: None }) | |||
| } | |||
| } | |||
| HttpResponse::Ok().json(resp) | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| pub(crate) mod issue; | |||
| pub(crate) mod session; | |||
| pub(crate) mod comment; | |||
| @@ -0,0 +1,45 @@ | |||
| use actix_web::{web, HttpRequest, HttpResponse, Responder}; | |||
| use serde::{Deserialize, Serialize}; | |||
| use crate::repositories; | |||
| use crate::repositories::session::get_username_from_session; | |||
| #[derive(Deserialize, Clone)] | |||
| pub struct AuthenticateRequest { | |||
| user_id: i64, | |||
| auth_date: i64, | |||
| username: String, | |||
| first_name: String, | |||
| last_name: String, | |||
| photo_url: String, | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct AuthenticateResponse { | |||
| session_id: String, | |||
| } | |||
| pub async fn authenticate(req: web::Json<AuthenticateRequest>) -> impl Responder { | |||
| let session_id = repositories::session::authenticate_session( | |||
| req.clone().user_id, | |||
| req.clone().auth_date, | |||
| req.clone().username, | |||
| req.clone().first_name, | |||
| req.clone().last_name, | |||
| req.clone().photo_url | |||
| ); | |||
| let resp = AuthenticateResponse { session_id }; | |||
| HttpResponse::Ok().json(resp) | |||
| } | |||
| #[derive(Serialize)] | |||
| pub struct GetUsernameResponse { | |||
| username: Option<String>, | |||
| } | |||
| pub async fn get_username(req: HttpRequest) -> impl Responder { | |||
| if let Some(session_cookie) = req.cookie("session") { | |||
| let username = get_username_from_session(session_cookie.value().to_string()); | |||
| return HttpResponse::Ok().json(GetUsernameResponse { username }) | |||
| } | |||
| HttpResponse::Unauthorized().into() | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| use std::env; | |||
| use diesel::Connection; | |||
| use diesel::PgConnection; | |||
| use dotenvy::dotenv; | |||
| pub fn establish_connection() -> PgConnection { | |||
| dotenv().ok(); | |||
| let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); | |||
| PgConnection::establish(&database_url) | |||
| .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| pub(crate) mod db; | |||
| @@ -0,0 +1 @@ | |||
| pub mod models; | |||
| @@ -0,0 +1,149 @@ | |||
| use diesel::sql_types::Integer; | |||
| use diesel::sql_types::Nullable; | |||
| use diesel::sql_types::SmallInt; | |||
| use diesel::sql_types::Timestamp; | |||
| use diesel::sql_types::Text; | |||
| use diesel::sql_types::BigInt; | |||
| use chrono::NaiveDateTime; | |||
| use diesel::{Insertable, Queryable, QueryableByName, Selectable}; | |||
| use serde::Serialize; | |||
| #[derive(Queryable, Selectable, Serialize, Clone)] | |||
| #[diesel(table_name = crate::schema::issues)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct Issue { | |||
| pub id: i32, | |||
| pub title: String, | |||
| pub paragraph_count: Option<i16>, | |||
| pub telegram_handle: String, | |||
| pub created_at: Option<NaiveDateTime> | |||
| } | |||
| #[derive(QueryableByName, Serialize, Clone)] | |||
| pub struct IssueWithSummaryAndVotes { | |||
| #[diesel(sql_type = Integer)] | |||
| pub id: i32, | |||
| #[diesel(sql_type = Text)] | |||
| pub title: String, | |||
| #[diesel(sql_type = Text)] | |||
| pub summary: String, | |||
| #[diesel(sql_type = Nullable<SmallInt>)] | |||
| pub paragraph_count: Option<i16>, | |||
| #[diesel(sql_type = Text)] | |||
| pub telegram_handle: String, | |||
| #[diesel(sql_type = Nullable<Timestamp>)] | |||
| pub created_at: Option<NaiveDateTime>, | |||
| #[diesel(sql_type = BigInt)] | |||
| pub total_votes: i64, | |||
| #[diesel(sql_type = BigInt)] | |||
| pub positive_votes: i64, | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::issues)] | |||
| pub struct NewIssue { | |||
| pub title: String, | |||
| pub paragraph_count: i16, | |||
| pub telegram_handle: String, | |||
| } | |||
| #[derive(Queryable, Selectable)] | |||
| #[diesel(table_name = crate::schema::sessions)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct Session { | |||
| pub id: i64, | |||
| pub session_id: String, | |||
| pub auth_date: Option<i64>, | |||
| pub username: Option<String>, | |||
| pub first_name: Option<String>, | |||
| pub last_name: Option<String>, | |||
| pub photo_url: Option<String> | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::sessions)] | |||
| pub struct NewSession { | |||
| pub user_id: Option<i64>, | |||
| pub session_id: String, | |||
| pub auth_date: i64, | |||
| pub username: Option<String>, | |||
| pub first_name: Option<String>, | |||
| pub last_name: Option<String>, | |||
| pub photo_url: Option<String> | |||
| } | |||
| #[derive(Queryable, Selectable, Serialize, Clone)] | |||
| #[diesel(table_name = crate::schema::paragraphs)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct Paragraph { | |||
| pub id: i64, | |||
| pub content: String, | |||
| pub index: i32, | |||
| pub post_id: i32, | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::paragraphs)] | |||
| pub struct NewParagraph { | |||
| pub content: Option<String>, | |||
| pub index: i32, | |||
| pub post_id: i32, | |||
| } | |||
| #[derive(Queryable, Selectable, Serialize, Clone)] | |||
| #[diesel(table_name = crate::schema::issue_votes)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct IssueVote { | |||
| pub id: i64, | |||
| pub positive: Option<bool>, | |||
| pub issue_id: i32, | |||
| pub user_id: i64, | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::issue_votes)] | |||
| pub struct NewIssueVote { | |||
| pub positive: bool, | |||
| pub issue_id: i32, | |||
| pub user_id: i64, | |||
| } | |||
| #[derive(Queryable, Selectable, Serialize, Clone)] | |||
| #[diesel(table_name = crate::schema::comments)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct Comment { | |||
| pub id: i64, | |||
| pub content: String, | |||
| pub parent: Option<i64>, | |||
| pub telegram_handle: String, | |||
| pub issue_id: i32, | |||
| pub created_at: Option<NaiveDateTime> | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::comments)] | |||
| pub struct NewComment { | |||
| pub content: String, | |||
| pub parent: Option<i64>, | |||
| pub telegram_handle: String, | |||
| pub issue_id: i32, | |||
| pub created_at: NaiveDateTime | |||
| } | |||
| #[derive(Queryable, Selectable, Serialize, Clone)] | |||
| #[diesel(table_name = crate::schema::comment_votes)] | |||
| #[diesel(check_for_backend(diesel::pg::Pg))] | |||
| pub struct CommentVote { | |||
| pub id: i64, | |||
| pub positive: Option<bool>, | |||
| pub comment_id: i64, | |||
| pub user_id: i64, | |||
| } | |||
| #[derive(Insertable)] | |||
| #[diesel(table_name = crate::schema::comment_votes)] | |||
| pub struct NewCommentVote { | |||
| pub positive: Option<bool>, | |||
| pub comment_id: Option<i64>, | |||
| pub user_id: Option<i64>, | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| use crate::db::db::establish_connection; | |||
| use crate::models::models::{Comment, NewComment}; | |||
| use crate::schema::comments; | |||
| use chrono::Utc; | |||
| use diesel::{alias, ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl, SelectableHelper, NullableExpressionMethods}; | |||
| use crate::schema; | |||
| pub fn get_issue_comments(issue_id: i32) -> Vec<Comment> { | |||
| let connection = &mut establish_connection(); | |||
| let (c1, c2) = alias!(comments as c1, comments as c2); | |||
| comments::table | |||
| .left_join(c1.on(comments::parent.eq(comments::id.nullable()))) | |||
| .filter(comments::issue_id.eq(issue_id)) | |||
| .select(Comment::as_select()) | |||
| .load::<Comment>(connection) | |||
| .unwrap_or_else(|_| Vec::new()) | |||
| } | |||
| pub fn create_comment(content: String, parent: Option<i64>, telegram_handle: String, issue_id: i32) -> Comment { | |||
| let connection = &mut establish_connection(); | |||
| let new_comment = NewComment { | |||
| content, | |||
| parent, | |||
| telegram_handle, | |||
| issue_id, | |||
| created_at: Utc::now().naive_utc() | |||
| }; | |||
| let insertion = diesel::insert_into(comments::table) | |||
| .values(&new_comment) | |||
| .returning(Comment::as_select()) | |||
| .get_result(connection) | |||
| .expect("Error saving comment"); | |||
| insertion | |||
| } | |||
| @@ -0,0 +1,161 @@ | |||
| use crate::db::db::establish_connection; | |||
| use crate::models::models::{Issue, IssueWithSummaryAndVotes, NewIssue, NewIssueVote, NewParagraph, Paragraph}; | |||
| use crate::schema::issue_votes::{issue_id, positive}; | |||
| use crate::schema::paragraphs; | |||
| use crate::schema::paragraphs::{content, index}; | |||
| use crate::schema::{issue_votes, issues}; | |||
| use chrono::NaiveDateTime; | |||
| use diesel; | |||
| use diesel::associations::HasTable; | |||
| use diesel::dsl::count; | |||
| use diesel::result::DatabaseErrorKind; | |||
| use diesel::result::Error::DatabaseError; | |||
| use diesel::{sql_query, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; | |||
| use serde::Deserialize; | |||
| #[derive(Deserialize)] | |||
| pub struct FilterOptions { | |||
| pub min_positive_votes: Option<i64>, | |||
| pub min_votes: Option<i64>, | |||
| } | |||
| pub fn get_issues(filters: Option<FilterOptions>, offset: i32, limit: i16) -> Vec<IssueWithSummaryAndVotes> { | |||
| let connection = &mut establish_connection(); | |||
| let query = sql_query("select i.id, title, content as summary, i.paragraph_count, telegram_handle, i.created_at, | |||
| count(v.id) as total_votes, | |||
| sum(case when v.positive then 1 else 0 end) as positive_votes | |||
| from issues i | |||
| inner join paragraphs p on i.id = p.post_id | |||
| inner join issue_votes v on i.id = v.issue_id | |||
| where p.index = 0 | |||
| group by v.issue_id, title, telegram_handle, content, i.id;"); | |||
| let mut issues_with_content: Vec<IssueWithSummaryAndVotes> = query | |||
| .load::<IssueWithSummaryAndVotes>(connection) | |||
| .unwrap_or_default() | |||
| .into_iter() | |||
| .collect(); | |||
| if let Some(filter_info) = filters { | |||
| if let Some(positive_votes) = filter_info.min_positive_votes { | |||
| issues_with_content = issues_with_content | |||
| .iter() | |||
| .filter(|i| i.positive_votes >= positive_votes) | |||
| .map(|i| i.clone()) | |||
| .collect() | |||
| } | |||
| if let Some(min_votes) = filter_info.min_votes { | |||
| issues_with_content = issues_with_content | |||
| .iter() | |||
| .filter(|i| i.total_votes >= min_votes) | |||
| .map(|i| i.clone()) | |||
| .collect() | |||
| } | |||
| } | |||
| let len = issues_with_content.len(); | |||
| let i1 = offset.max(0) as usize; | |||
| let i2 = i1.saturating_add(limit as usize).min(len); | |||
| issues_with_content[i1..i2].to_vec() | |||
| } | |||
| pub fn create_issue(title: &String, paragraphs: &Vec<String>, telegram_handle: &Option<String>) -> Option<Issue> { | |||
| let connection = &mut establish_connection(); | |||
| if let Some(tg_handle) = telegram_handle { | |||
| let new_issue = NewIssue { | |||
| title: title.to_string(), | |||
| paragraph_count: paragraphs.len() as i16, | |||
| telegram_handle: tg_handle.to_string(), | |||
| }; | |||
| let insertion = diesel::insert_into(issues::table) | |||
| .values(&new_issue) | |||
| .returning(Issue::as_select()) | |||
| .get_result(connection) | |||
| .expect("Error saving new issue"); | |||
| let _ = diesel::insert_into(paragraphs::table) | |||
| .values(paragraphs.iter().enumerate().map(|(i, p)| | |||
| NewParagraph { | |||
| content: Some(p.clone()), | |||
| index: i as i32, | |||
| post_id: insertion.id | |||
| }).collect::<Vec<NewParagraph>>()) | |||
| .returning(Paragraph::as_select()) | |||
| .get_results(connection) | |||
| .expect("Error saving paragraphs"); | |||
| return Some(insertion) | |||
| } | |||
| None | |||
| } | |||
| pub fn get_paragraphs(other_issue_id: i32) -> Vec<Option<String>> { | |||
| let connection = &mut establish_connection(); | |||
| paragraphs::table | |||
| .filter(paragraphs::post_id.eq(other_issue_id)) | |||
| .order(index) | |||
| .select(content) | |||
| .load::<String>(connection) | |||
| .unwrap_or_default() | |||
| .into_iter() | |||
| .map(Some) | |||
| .collect::<Vec<Option<String>>>() | |||
| } | |||
| pub fn record_vote_for_issue(other_positive: bool, other_issue_id: i32, user_id: i64) -> bool { | |||
| let connection = &mut establish_connection(); | |||
| let result = diesel::insert_into(issue_votes::table) | |||
| .values(NewIssueVote { positive: other_positive, issue_id: other_issue_id, user_id }) | |||
| .execute(connection); | |||
| match result { | |||
| Ok(_) => true, | |||
| Err(DatabaseError(kind, _)) => { | |||
| if let DatabaseErrorKind::UniqueViolation = kind { | |||
| let _ = diesel::update( | |||
| issue_votes::table | |||
| .filter( | |||
| issue_votes::user_id.eq(user_id).and(issue_votes::issue_id.eq(issue_id)) | |||
| ) | |||
| ) | |||
| .set(issue_votes::positive.eq(positive)) | |||
| .execute(connection); | |||
| return true | |||
| } | |||
| false | |||
| }, | |||
| _ => false | |||
| } | |||
| } | |||
| pub fn get_issue_vote_equity(issue_identifier: i32) -> i64 { | |||
| let connection = &mut establish_connection(); | |||
| let positive_votes: i64 = issue_votes::table | |||
| .filter(issue_votes::issue_id.eq(issue_identifier).and(issue_votes::positive.eq(true))) | |||
| .count() | |||
| .get_result(connection) | |||
| .unwrap(); | |||
| let negative_votes: i64 = issue_votes::table | |||
| .filter(issue_votes::issue_id.eq(issue_identifier).and(issue_votes::positive.eq(false))) | |||
| .count() | |||
| .get_result(connection) | |||
| .unwrap(); | |||
| positive_votes - negative_votes | |||
| } | |||
| pub fn get_vote_for_user(other_issue_id: i32, other_user_id: i64) -> Option<bool> { | |||
| let connection = &mut establish_connection(); | |||
| let result: Option<bool> = issue_votes::table | |||
| .filter(issue_votes::issue_id.eq(other_issue_id).and(issue_votes::user_id.eq(other_user_id))) | |||
| .limit(1) | |||
| .select(issue_votes::positive) | |||
| .get_result::<Option<bool>>(connection) | |||
| .unwrap_or(None); | |||
| result | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| pub(crate) mod issue; | |||
| pub(crate) mod session; | |||
| pub(crate) mod comment; | |||
| @@ -0,0 +1,66 @@ | |||
| use diesel::{OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper}; | |||
| use diesel::ExpressionMethods; | |||
| use uuid::Uuid; | |||
| use crate::db::db::establish_connection; | |||
| use crate::models::models::{NewSession, Session}; | |||
| use crate::schema::sessions; | |||
| use crate::schema::sessions::dsl::sessions as sessions_dsl; | |||
| use crate::schema::sessions::session_id; | |||
| pub fn get_session(sess_id: String) -> Option<Session> { | |||
| let connection = &mut establish_connection(); | |||
| sessions_dsl | |||
| .filter(session_id.eq(sess_id)) | |||
| .select(Session::as_select()) | |||
| .first(connection) | |||
| .optional() | |||
| .expect("Error loading session") | |||
| } | |||
| pub fn authenticate_session(user_id: i64, auth_date: i64, username: String, first_name: String, last_name: String, photo_url: String) -> String { | |||
| let sess_id: Uuid = Uuid::new_v4(); | |||
| let connection = &mut establish_connection(); | |||
| let new_session = NewSession { | |||
| user_id: Some(user_id), | |||
| session_id: sess_id.clone().to_string(), | |||
| auth_date, | |||
| username: Some(username), | |||
| first_name: Some(first_name), | |||
| last_name: Some(last_name), | |||
| photo_url: Some(photo_url), | |||
| }; | |||
| let result = diesel::insert_into(sessions::table) | |||
| .values(&new_session) | |||
| .returning(Session::as_select()) | |||
| .get_result(connection) | |||
| .unwrap(); | |||
| result.session_id | |||
| } | |||
| pub fn get_user_id(sess_id: String) -> Option<i64> { | |||
| let connection = &mut establish_connection(); | |||
| let user_id: Option<i64> = sessions::table | |||
| .filter(session_id.eq(sess_id)) | |||
| .limit(1) | |||
| .select(sessions::user_id) | |||
| .get_result(connection) | |||
| .optional() | |||
| .unwrap_or(None); | |||
| user_id | |||
| } | |||
| pub fn get_username_from_session(sess_id: String) -> Option<String> { | |||
| let connection = &mut establish_connection(); | |||
| let username = sessions::table | |||
| .filter(sessions::session_id.eq(sess_id)) | |||
| .limit(1) | |||
| .select(sessions::username) | |||
| .get_result(connection) | |||
| .unwrap_or_default(); | |||
| username | |||
| } | |||