@@ -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 | |||||
} |