Skip to content

Commit

Permalink
Implement writing users & user ratings to the database. (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
mayberryrfd authored Feb 18, 2018
1 parent 279a1b1 commit 2bc63fe
Show file tree
Hide file tree
Showing 21 changed files with 486 additions and 31 deletions.
239 changes: 220 additions & 19 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ path = "src/main.rs"
test = false

[dependencies]
diesel = { version = "*", features = ["chrono", "postgres", "serde_json", "huge-tables", "uuid"] }
bigdecimal = "*"
diesel = { version = "*", features = ["chrono", "postgres", "serde_json", "huge-tables", "numeric", "uuid"] }
diesel_cli = { features = ["postgres"] }
diesel_codegen = { version = "*", features = ["postgres"] }
env_logger = "~0.4"
kankyo = "~0.1"
Expand Down
Empty file added migrations/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions migrations/00000000000000_diesel_initial_setup/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();
36 changes: 36 additions & 0 deletions migrations/00000000000000_diesel_initial_setup/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.




-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
1 change: 1 addition & 0 deletions migrations/2018-02-11-215552_create_users/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
drop table users cascade;
6 changes: 6 additions & 0 deletions migrations/2018-02-11-215552_create_users/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
create table users (
user_id serial primary key,
bot bool not null default false,
discriminator varchar not null,
name varchar not null
);
1 change: 1 addition & 0 deletions migrations/2018-02-11-220031_create_user_ratings/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
drop table user_ratings cascade;
5 changes: 5 additions & 0 deletions migrations/2018-02-11-220031_create_user_ratings/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
create table user_ratings (
id serial primary key,
user_id integer references users,
rating numeric(2) not null
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table user_ratings alter column user_id drop not null;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table user_ratings alter column user_id set not null;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table users drop constraint "users_discriminator_name_key";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table users add unique(discriminator, name);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table users alter column discriminator type varchar;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table users alter column discriminator type integer using discriminator::integer;
5 changes: 0 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
#![feature(const_fn)]

extern crate env_logger;
extern crate kankyo;
extern crate pugbot;
extern crate rand;
extern crate typemap;

fn main() {
// This will load the environment variables located at `./.env`, relative to
Expand Down
71 changes: 71 additions & 0 deletions src/pugbot/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use diesel::{ Connection, RunQueryDsl, PgConnection };
use diesel::insert_into;
use r2d2;
use r2d2_diesel::ConnectionManager;
use serenity::model::user::User;
use std::env;
use std::ops::Deref;
use typemap::Key;

use schema::users::dsl::*;
use schema::user_ratings::dsl::*;
use tables::insert::{ Users as IUsers, UserRatings as IUserRatings };
use tables::query::{ Users as QUsers, UserRatings as QUserRatings };

// Connection request guard type: a wrapper around an r2d2 pooled connection.
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);

// For the convenience of using an &DbConn as an &SqliteConnection.
impl Deref for DbConn {
type Target = PgConnection;

fn deref(&self) -> &Self::Target {
&self.0
}
}

pub struct Pool;

impl Key for Pool {
type Value = r2d2::Pool<ConnectionManager<PgConnection>>;
}

/// Initializes a database pool.
pub fn init_pool(db_url: Option<String>) -> r2d2::Pool<ConnectionManager<PgConnection>> {
let database_url = match db_url {
Some(url) => url,
_ => env::var("DATABASE_URL").expect("DATABASE_URL must be defined")
};
let manager = ConnectionManager::<PgConnection>::new(database_url);
r2d2::Pool::builder().build(manager).expect("Failed to create pool.")
}

pub fn create_user_and_ratings(
conn: r2d2::PooledConnection<ConnectionManager<PgConnection>>,
user: User
) -> Result<(), String> {

match insert_into(users).values(&IUsers::from(user)).get_result::<QUsers>(&*conn) {
Ok(user_record) => if let Ok(_ratings_record) = insert_into(user_ratings)
.values(&IUserRatings::from(user_record))
.get_result::<QUserRatings>(&*conn) {
Ok(())
} else {
Err("No user_ratings reckid created".to_string())
},
Err(e) => Err(format!("{:?}", e))
}

// if let Ok(user_record) = insert_into(users).values(&IUsers::from(user)).get_result::<QUsers>(&*conn) {
// if let Ok(_ratings_record) = insert_into(user_ratings)
// .values(&IUserRatings::from(user_record))
// .get_result::<QUserRatings>(&*conn)
// {
// Ok(())
// } else {
// Err("Could not create user_ratings record".to_string())
// }
// } else {
// Err("Could not create users record".to_string())
// }
}
38 changes: 36 additions & 2 deletions src/pugbot/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
#![feature(const_fn)]

#![feature(const_fn, custom_attribute, plugin)]
#![allow(unused_attributes)]
#[macro_use] extern crate log;
#[macro_use] extern crate serenity;
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_codegen;

extern crate bigdecimal;
extern crate env_logger;
extern crate kankyo;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate rand;
extern crate typemap;

pub mod commands;
pub mod db;
pub mod models;
pub mod schema;
pub mod tables;
pub mod traits;

use bigdecimal::BigDecimal;
use models::draft_pool::DraftPool;
use models::game::Game;
use serenity::builder::CreateEmbed;
Expand All @@ -20,11 +29,17 @@ use serenity::model::channel::{ Embed, Message };
use serenity::model::event::ResumedEvent;
use serenity::model::gateway::Ready;
use serenity::model::id::UserId;
use serenity::model::user::User;
use serenity::prelude::*;
use serenity::http;
use std::collections::HashSet;
use std::convert::From;
use std::env;
use std::ops::Range;
use std::str::FromStr;
use tables::insert::{ Users as IUsers };
use tables::query::{ Users as QUsers };
use tables::insert::{ UserRatings as IUserRatings };

struct Handler;

Expand Down Expand Up @@ -92,6 +107,7 @@ pub fn client_setup() -> Client {
let draft_pool = DraftPool::new(Vec::new());
let game = Game::new(None, draft_pool);
data.insert::<Game>(game);
data.insert::<db::Pool>(db::init_pool(None));
}

client.with_framework(
Expand Down Expand Up @@ -127,3 +143,21 @@ fn bot_owners() -> HashSet<UserId> {
}
}

impl From<User> for IUsers {
fn from(user: User) -> IUsers {
IUsers {
bot: user.bot,
discriminator: user.discriminator as i32,
name: user.name
}
}
}

impl From<QUsers> for IUserRatings {
fn from(record: QUsers) -> IUserRatings {
IUserRatings {
user_id: record.user_id,
rating: BigDecimal::from_str("0.00").unwrap()
}
}
}
18 changes: 18 additions & 0 deletions src/pugbot/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
table! {
user_ratings (id) {
id -> Int4,
user_id -> Int4,
rating -> Numeric,
}
}

table! {
users (user_id) {
user_id -> Int4,
bot -> Bool,
discriminator -> Int4,
name -> Varchar,
}
}

joinable!(user_ratings -> users (user_id));
43 changes: 43 additions & 0 deletions src/pugbot/tables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
pub mod query {
use bigdecimal::BigDecimal;

#[primary_key(user_id)]
#[table_name="users"]
#[derive(Queryable, Associations)]
pub struct Users {
pub user_id: i32,
pub bot: bool,
pub discriminator: i32,
pub name: String
}

#[table_name = "user_ratings"]
#[derive(Queryable, Associations)]
#[belongs_to(Users)]
pub struct UserRatings {
pub id: i32,
pub user_id: i32,
pub rating: BigDecimal
}
}

pub mod insert {
use bigdecimal::BigDecimal;
use schema::*;

#[table_name="users"]
#[derive(Insertable)]
pub struct Users {
pub bot: bool,
pub discriminator: i32,
pub name: String
}

#[table_name = "user_ratings"]
#[derive(Insertable)]
#[belongs_to(Users)]
pub struct UserRatings {
pub user_id: i32,
pub rating: BigDecimal
}
}
42 changes: 38 additions & 4 deletions tests/test_commands.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
extern crate bigdecimal;
extern crate diesel;
extern crate kankyo;
extern crate pugbot;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate serde;
extern crate serde_json;
extern crate serenity;

use diesel::prelude::*;
use diesel::sql_query;
use diesel::RunQueryDsl;
use pugbot::commands;
use pugbot::db::init_pool;
use pugbot::models::game::{ Game, Phases };
use pugbot::models::draft_pool::DraftPool;
use pugbot::traits::has_members::HasMembers;
use pugbot::traits::phased::Phased;
use r2d2_diesel::ConnectionManager;
use serde::de::Deserialize;
use serde_json::Value;
use serenity::model::channel::{ Message };
use serenity::model::id::UserId;
use serenity::model::user::User;
use std::env::set_var;
use std::env;
use std::fs::File;

use pugbot::db::*;

macro_rules! p {
($s:ident, $filename:expr) => ({
let f = File::open(concat!("./tests/resources/", $filename, ".json")).unwrap();
Expand All @@ -39,14 +51,14 @@ fn gen_test_user() -> User {
fn update_members() {
let message = p!(Message, "message");
let key = "TEAM_SIZE";
set_var(key, "1");
env::set_var(key, "1");
let game = &mut Game::new(None, DraftPool::new(vec![gen_test_user()]));
assert_eq!(game.phase, Some(Phases::PlayerRegistration));
let users = commands::add::update_members(game, &message, false);
let members = commands::add::update_members(game, &message, false);
// There should be one member in the members vec to start with: our test user.
// `update_members` above should add an additional user, the author of the message (which is
// defined in ./resources/message.json).
assert_eq!(users.len(), 2);
assert_eq!(members.len(), 2);
assert_eq!(game.phase, Some(Phases::CaptainSelection));
}

Expand All @@ -67,3 +79,25 @@ fn select_captains() {
// There should now be two teams built.
assert_eq!(game.teams.clone().unwrap().len(), 2);
}

pub fn connection() -> r2d2::PooledConnection<ConnectionManager<PgConnection>> {
let pool = init_pool(Some("postgres://pugbot:pugbot@localhost:5432/test_pugbot".to_string()));
let conn = pool.get().unwrap();
conn.begin_test_transaction().unwrap();
sql_query("DROP TABLE IF EXISTS users CASCADE").execute(&*conn).unwrap();
sql_query("create table users (\
user_id serial primary key,\
bot bool not null default false,\
discriminator varchar not null,\
name varchar not null\
)")
.execute(&*conn)
.unwrap();
conn
}

#[test]
#[allow(unused_must_use)]
fn write_to_db() {
assert_eq!(create_user_and_ratings(connection(), gen_test_user()), Ok(()));
}

0 comments on commit 2bc63fe

Please sign in to comment.