Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement writing users & user ratings to the database. #14

Merged
merged 12 commits into from
Feb 18, 2018
Merged
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(()));
}