Author SHA1 Message Date
Edwin Perez 4b9f0169bc Added units to the user time input on the log 2013-10-19 15:43:53 -04:00
Edwin Perez b7ad63a2e5 Finished enhancing the user log enter time form 2013-10-19 14:06:41 -04:00
Edwin Perez 62310e7a25 Created a custom class for the adding an item in the user log grid 2013-10-19 12:20:48 -04:00
Jason Denney cfeb6fd1d2 Update README.md
Remove mailcatcher instructions since no emails are sent currently.
2013-10-15 16:21:57 -04:00
Jason Denney ec5db646a0 Use models to sort data when possible 2013-09-02 18:24:34 -04:00
Jason Denney de266e72d9 Merge pull request #62 from spacemunkay/denney-css-user-sign-up
Make user signup easier to read & enter
2013-09-02 15:08:59 -07:00
Jason Denney 67c51068cc Make user signup easier to read & enter 2013-09-02 18:08:05 -04:00
Jason Denney 8c6513099d Merge pull request #61 from spacemunkay/denney-move-model-to-bike
Moving bike model to the bikes table
2013-09-02 14:28:06 -07:00
Jason Denney 47f616b7b0 Moving bike model to the bikes table
Leaving the bike_models table and bikes.bike_model_id column as a backup.
2013-09-02 17:26:07 -04:00
Jason Denney 087325c444 Whoops, only create once… not for every update... 2013-09-02 16:23:58 -04:00
Jason Denney 3c4e5c21c2 Merge pull request #59 from spacemunkay/denney-remove-bike-log-end-date
Bike log events are instanteous, remove end_date
2013-09-02 12:28:59 -07:00
Jason Denney 69c01b8225 Bike log events are instanteous, remove end_date 2013-09-02 15:26:08 -04:00
Jason Denney 0aa9c16dbd Merge pull request #58 from spacemunkay/denney-default-sort-order
Set default sort order on users, bikes, userroles
2013-09-02 11:17:08 -07:00
Jason Denney e491c0530c Set default sort order on users, bikes, userroles 2013-09-02 14:16:39 -04:00
Jason Denney 3599cc2bbb Merge pull request #56 from spacemunkay/denney-bike-status-to-purpose
Changing bike_status to bike_purporse
2013-09-02 11:03:21 -07:00
Jason Denney cecb3b1a32 Changing bike_status to bike_purporse 2013-09-02 14:02:18 -04:00
Jason Denney 611b1b1151 Merge pull request #55 from spacemunkay/denney-disable-username-edit
set username to read only
2013-09-02 09:23:32 -07:00
Jason Denney 5e80c8866a set username to read only 2013-09-02 12:22:26 -04:00
Jason Denney 835e73ea97 Merge branch 'master' of github.com:spacemunkay/velocipede 2013-08-28 23:06:14 -04:00
Jason Denney cb2acd19ba ignore public/assets 2013-08-28 23:06:02 -04:00
Jason Denney ee12bfee8a Merge pull request #54 from spacemunkay/denney-autocomplete-max-chars
Changing min_chars for autocomplete to 1 char
2013-08-28 20:03:21 -07:00
Jason Denney 4d008163e1 Changing min_chars for autocomplete to 1 char 2013-08-28 23:01:29 -04:00
Jason Denney 06856f115c Merge pull request #52 from spacemunkay/denney-remove-bike-serial-constraint
Remove unique constraint on bike_serial
2013-08-17 21:56:07 -07:00
Jason Denney a9fb3487a1 Remove unique constraint on bike_serial
bike serial index was already removed.
2013-08-18 00:51:45 -04:00
Jason Denney 869b67d3e4 Merge pull request #50 from spacemunkay/denney-user-roles-by-full-name
User roles added by full name instead of username
2013-08-17 21:04:02 -07:00
Jason Denney cd4f891c95 User roles added by full name instead of username 2013-08-18 00:00:53 -04:00
Jason Denney 71a4bb0af4 Merge pull request #49 from spacemunkay/denney-remove-hours-bike
remove hours, created_at, updated_at in bike logs
2013-08-17 20:43:22 -07:00
Jason Denney f1214ab7e6 remove hours, created_at, updated_at in bike logs 2013-08-17 23:38:07 -04:00
Jason Denney aaa6fda90c Merge pull request #48 from spacemunkay/denney-add-end-date-user-timesheet
Added end date to user timesheet, removed created & updated
2013-08-17 20:23:17 -07:00
Jason Denney 4a7751959c Added end date to user timesheet, removed created & updated 2013-08-17 23:22:07 -04:00
Jason Denney 028a45702e More comments 2013-08-17 14:07:35 -04:00
Jason Denney 1a8b9c9247 Merge pull request #47 from spacemunkay/denney-create-user-sign-in
Changed so that user gets checked in after create.
2013-08-17 11:03:00 -07:00
Jason Denney 4e7f3a1761 Changed so that user gets checked in after create. 2013-08-17 14:01:40 -04:00
Jason Denney a6b41b5560 Merge pull request #46 from spacemunkay/denney-fix-search
Fixing search form for bikes updated to netzke 8.4
2013-08-15 19:07:01 -07:00
Jason Denney 4d7634605c Fixing search form for bikes updated to netzke 8.4
Updated Netzke 8.4, better search styling, case insensitive search, can search associations
Fixed mistake where bikes.condition_id was a string… durrrrrr needs to be integer
2013-08-15 22:05:29 -04:00
Jason Denney cf922be1b1 Quick workflow short term fix 2013-07-28 15:24:02 -04:00
Jason Denney d755f9971e Update README.md 2013-06-29 17:20:53 -04:00
Jason Denney 52e67a1182 Adding Made in Bmore to Login 2013-06-29 17:18:51 -04:00
Jason Denney 7736ac5c34 Added credit tracking and stats
Added credit_conversions table so conversions can change over time
Added default credit conversion fixture
Added User#transaction_logs
Added User#total_credits (earned minus spent)
Added User#total_credits_spent (through transaction_logs)
Added User#total_earned_credits (complicated query which finds the conversion rate for a log depending on its created_at date)
2013-06-29 15:19:38 -04:00
Jason Denney bccd5f10d7 Updated seeds.rb for PG 2013-06-29 11:45:19 -04:00
Jason Denney f0ce8b7171 Switch DB from sqlite to Postgres
-Postgres for production
-Migrations don't translate well between sqlite and postgres, better to just develop using the same database as production.
-Fixed migrations to work with Postgres
-Updated Readme for PG install instructions
-database config moved to .example
2013-06-25 23:50:34 -04:00
Jason Denney 238a3af7d5 minor change to check for seeding admin 2013-06-25 22:31:29 -04:00
Jason Denney fee421cabe not optimal production config, but it works 2013-06-25 22:24:21 -04:00
Jason Denney f713c6bacd Forgot to remove nickname from registration form 2013-06-25 22:06:43 -04:00
Jason Denney ac24711289 Rename User History to User Timesheet 2013-06-13 21:14:42 -04:00
Jason Denney b89873d0e5 Updating schema file w/ removed user nick 2013-06-13 21:12:34 -04:00
Jason Denney d431024f01 Adding bike_admin fixture, fixed seeds.rb 2013-06-13 21:11:54 -04:00
Jason Denney ff60fdd67f Removing nickname from user 2013-06-13 20:59:01 -04:00
Jason Denney 03abb963ac Merging Master 2013-06-11 00:38:20 -04:00
Jason Denney cd41735cee Bug in User#completed_build_bikes query. inner 2013-06-11 00:35:15 -04:00
Jason Denney 0f89cf057a Display User roles by username, then full name
Also set the default_fields_for_forms
2013-06-11 00:34:45 -04:00
Jason Denney 8172b04805 2013-06-11 00:34:01 -04:00
Jason Denney 9cb4ed42dc Update README.md 2013-06-10 23:49:39 -04:00
Jason Denney 7c85e2901f Changes needed to run in produciton 2013-06-02 21:50:22 -04:00
Jason Denney 2c548ff63c User#completed_build_bikes method, and stats page list 2013-06-01 17:11:57 -04:00
Jason Denney 1defca3668 Fixing some bike logs permissions 2013-06-01 16:32:33 -04:00
Jason Denney 88d3160e29 Fixing logs add_in_form and search 2013-06-01 14:47:20 -04:00
Jason Denney edff478204 Reordering Admin tab panel, putting logs last 2013-06-01 14:46:59 -04:00
Jason Denney 84ca3a6f96 Search form requires :field_label when overriding default_fields_for_forms 2013-06-01 14:10:18 -04:00
Jason Denney 51caa93afa Some js wasn't getting loaded at login. and other fixes 2013-06-01 11:39:42 -04:00
jnm 7a935c250b Merge pull request #27 from spacemunkay/denney-fix-some-cancan
Fixing some cancan permission issues… i think
2013-05-30 19:21:37 -07:00
Jason Denney 854c8efaa9 only admins can see checkins 2013-05-30 22:19:00 -04:00
Jason Denney b450baa3dc merging master 2013-05-30 22:17:18 -04:00
jnm fa51dd25f3 Merge pull request #24 from spacemunkay/denney-reset-password
Denney reset password
2013-05-30 19:13:49 -07:00
Jason Denney c9e94d8e30 Merge pull request #21 from spacemunkay/denney-new-user-form
Denney new user form
2013-05-30 19:09:10 -07:00
John N. Milner c5b16139f5 Merge branch 'master' into denney-new-user-form
Conflicts:
	db/schema.rb
2013-05-30 22:03:53 -04:00
Jason Denney 6d76385d11 Merge pull request #20 from spacemunkay/jnm-check-ins-view
New view to show the current day's check ins
2013-05-30 18:39:20 -07:00
Jason Denney f6f932f63a Fixing some cancan permission issues… i think 2013-05-30 21:35:39 -04:00
jnm a18e34c8f1 Merge pull request #22 from spacemunkay/denney-color-picker
Denney color picker
2013-05-30 17:21:35 -07:00
John N. Milner b25a4520f9 Correct log criteria for Check Ins view 2013-05-30 19:52:57 -04:00
John N. Milner adeca0e08a Corrected Check Ins view to show the checked-in user, not the user who logged the check in 2013-05-30 19:30:40 -04:00
Jason Denney db1732f7bf Bug in user_stats queries 2013-05-28 01:34:55 -04:00
Jason Denney d5662284ab Added link for users to update email/password 2013-05-28 00:27:34 -04:00
Jason Denney 0ef4e81474 Added reset password action column to users comp. 2013-05-28 00:27:03 -04:00
Jason Denney 761ab81b56 Added reset password api method 2013-05-28 00:25:23 -04:00
Jason Denney 687cb33974 Added color blocks to grid view 2013-05-26 19:52:40 -04:00
Jason Denney 7f029f2a83 Removing commented js 2013-05-26 13:33:35 -04:00
Jason Denney c7974c52e8 Adding color picker for bikes view/forms 2013-05-26 13:31:52 -04:00
Jason Denney 8c864e2006 Measurements will be in inches for the time being 2013-05-25 12:43:55 -04:00
Jason Denney 6d2e7686d6 Remove unneeded if 2013-05-25 11:13:52 -04:00
Jason Denney 9021294c4b Auth by username instead of email
Added username to User table/model
2013-05-25 11:08:58 -04:00
Jason Denney a07d6345e8 Mildly better looking signup page w/ errors. 2013-05-25 10:25:12 -04:00
Jason Denney db6b7ff7da Adjusting how javascript is loaded
Now js is loaded per controller
2013-05-25 10:12:03 -04:00
Jason Denney 75734a075c Moving new_user.js to devise/registrations.s 2013-05-25 09:59:49 -04:00
Jason Denney 73bb35de88 Moving bootstrap.js 2013-05-25 09:58:43 -04:00
Jason Denney 8c5132814f New User Sign up now annoys user to input contact info 2013-05-25 09:02:26 -04:00
Jason Denney 2e08f655ea Registration form now remembers field values for nested form errors 2013-05-25 09:00:12 -04:00
John N. Milner b67062fb32 New view to show the current day's check ins 2013-05-23 23:56:18 -04:00
jnm 3c90c13c3b Merge pull request #15 from spacemunkay/denney-add-users-roles
Changing UserRole to UserRoleJoin
2013-05-23 18:03:59 -07:00
jnm 19adc354a1 Merge pull request #18 from spacemunkay/denney-check-in
Denney check in
2013-05-23 18:01:07 -07:00
Jason Denney 145a731ed2 Remove puts 2013-05-19 16:44:28 -04:00
Jason Denney a00c1ed925 Finished Checkin/out feature for now
-Checkin/out api methods can be used while signed in
-Added checkout button in Netzke App, which also signs out in addition to checking out.
2013-05-19 16:41:43 -04:00
Jason Denney 14973e8334 Merge pull request #17 from spacemunkay/denney-bootstrap-css
Add bootstrap css to login page
2013-05-19 12:45:00 -07:00
Jason Denney d7755f00ed Initial stab at check-in feature 2013-05-19 15:43:44 -04:00
Jason Denney c504252541 Add bootstrap css to login page 2013-05-16 23:40:36 -04:00
Jason Denney 65c8cf8cf1 Fix setting default log action 2013-05-16 22:44:27 -04:00
Jason Denney 81217f1ea8 Give better names to columns, add last name col 2013-05-16 22:41:31 -04:00
Jason Denney bc1eeea16e Make the title of user roles tab 2013-05-16 22:41:00 -04:00
darmock f34def2c09 Store role_id as an integer instead of a string 2013-05-10 03:30:54 -04:00
John N. Milner 8f988b2cf1 Use self.table_name = instead of set_table_name to avoid deprecation warning 2013-05-10 03:21:13 -04:00
Jason Denney 1e589322f4 Update README.md
Adding link to ExtJS 4.1 download
2013-05-08 10:52:46 -03:00
Jason Denney be1f52568a Update README.md
Adding link to license
2013-05-08 10:48:34 -03:00
Jason Denney 3f1093bca5 ExtJS version is important 2013-05-07 21:34:38 -04:00
Jason Denney a614b28558 Make it possible to limit roles by date 2013-04-25 21:38:55 -04:00
Jason Denney 6f6902dd82 Update Ability to get user roles, not role 2013-04-25 21:25:49 -04:00
Jason Denney a083ed715b Renamed UserRoles to UserRole Joins, Needed ID column in user_role_joins table 2013-04-24 07:56:02 -04:00
Jason Denney 91bb2ea43e Changing UserRole to UserRoleJoin
Beware, this breaks the view for some reason
2013-04-23 23:21:17 -04:00
75 changed files with 1298 additions and 327 deletions
+2
View File
@@ -7,4 +7,6 @@ tmp/
.rbenv-version
.DS_Store
public/extjs
public/assets
public/images/icons
config/database.yml
+1 -2
View File
@@ -14,13 +14,12 @@ gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4'
gem 'haml-rails', '~> 0.3.4'
gem 'jquery-rails', '~> 2.0'
gem 'sqlite3', '~> 1.3.5'
gem 'pg'
gem 'will_paginate', '~> 3.0.3'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem "twitter-bootstrap-rails", "~> 2.0.3"
+9 -13
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 6033fe5a44878bb374f73fe473fed9a3396ddc74
revision: 49e264849ed9018445fb999cacfc55e1e28d7faa
specs:
acts_as_loggable (0.0.7)
activerecord (>= 3.0)
@@ -122,17 +122,18 @@ GEM
treetop (~> 1.4.8)
method_source (0.8.1)
mime-types (1.22)
multi_json (1.7.2)
netzke-basepack (0.8.2)
multi_json (1.7.9)
netzke-basepack (0.8.4)
netzke-core (~> 0.8.2)
netzke-cancan (0.8.2)
cancan
netzke-core
netzke-core (0.8.3)
netzke-core (0.8.4)
execjs
uglifier
nokogiri (1.5.9)
orm_adapter (0.0.7)
pg (0.15.1)
polyglot (0.3.3)
pry (0.9.12)
coderay (~> 1.0.5)
@@ -161,6 +162,7 @@ GEM
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.4)
rb-fsevent (0.9.3)
rdoc (3.12.2)
json (~> 1.4)
rspec (2.8.0)
@@ -177,11 +179,6 @@ GEM
railties (>= 3.0)
rspec (~> 2.8.0)
rubyzip (0.9.9)
sass (3.2.7)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.31.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
@@ -195,7 +192,6 @@ GEM
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.7)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thor (0.18.1)
@@ -212,7 +208,7 @@ GEM
railties (>= 3.1)
therubyracer (~> 0.10.1)
tzinfo (0.3.37)
uglifier (1.3.0)
uglifier (2.1.2)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
warden (1.1.1)
@@ -243,13 +239,13 @@ DEPENDENCIES
netzke-basepack (~> 0.8.0)
netzke-cancan
netzke-core (~> 0.8.0)
pg
pry (~> 0.9.8)
rails (= 3.2.13)
rb-fsevent
rspec-rails (~> 2.8.1)
sass-rails (~> 3.2.3)
shoulda-matchers (~> 1.0.0)
spork
sqlite3 (~> 1.3.5)
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3)
uglifier (>= 1.0.3)
+18 -6
View File
@@ -4,19 +4,31 @@
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
1. `rake db:create db:migrate db:seed`
1. Download extJS (http://www.sencha.com/products/extjs), unzip, and place where ever you like.
1. Install Postgres (Mac OSX instructions below)
1. `rake db:create db:migrate`
1. `rake db:seed`
1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like.
1. Link to your extJS folder path under `public/extjs`: (From app root) `ln -s /MY/PATH/extjs/ public/extjs`
1. `rails s`
# Postgres 9.2 Mac OSX Install
1. Install homebrew `ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"`
1. `brew install postgres`
1. First time db initialization `initdb /usr/local/var/postgres -E utf8`
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
# Optional
Add icons
1. Download icons from http://www.famfamfam.com/lab/icons/silk/
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
# License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
## Mailcatcher
Use mailcatcher to see what emails look like in development.
Follow the instructions from http://mailcatcher.me/ to create an rvm wrapper so you don't install it all over your gemsets
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
+4
View File
@@ -12,6 +12,8 @@ require File.expand_path('../config/application', __FILE__)
Velocipede::Application.load_tasks
unless Rails.env.production?
namespace :spec do
desc 'Run all acceptance specs'
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
@@ -25,3 +27,5 @@ desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
t.pattern = '**/*{_spec.rb,.feature}'
end
end
+1 -2
View File
@@ -12,5 +12,4 @@
//
//= require jquery
//= require jquery_ujs
//= require twitter/bootstrap
//= require_tree .
//= require custom_netzke_helpers
@@ -3,3 +3,148 @@ Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status === 401) { window.location = '/users/sign_in'; }
}, this);
Ext.define('Ext.ux.form.field.ColorCombo', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xcolorcombo',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
// properties
colorValue: null,
/**
* @property dateField
* @type Ext.form.field.Date
*/
colorField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.colorField = Ext.create('Ext.form.field.Trigger', {
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
readOnly: me.readOnly,
onTriggerClick: function() {
//show renders, so put first
me.picker.show();
me.picker.alignTo(me.colorField.inputEl);
}
});
me.items.push(me.colorField);
me.picker = Ext.create('Ext.picker.Color', {
renderTo: document.body,
floating: true,
hidden: true,
style: {
backgroundColor: "#fff"
},
listeners: {
scope:this,
select: function(field, value, opts){
me.setValue(value);
me.picker.hide();
}
}
});
me.items.push(me.picker);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.colorField.focus();
var me = this;
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.picker.hide();
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,color = this.colorField.getSubmitValue();
if (color){
value = this.colorField.getValue();
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,value = me.getValue();
return value;
},
setValue: function(value){
this.colorField.setValue(value);
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
@@ -0,0 +1,51 @@
$(document).ready(function(){
var MIN_LEN = 3;
var MAX_SUBMITS = 3;
var submit_count = 0;
$("input[name=commit]").click( function(e){
console.log("clicked");
submit_count += 1;
//IDs of contact info
var contact_info_ids = [
"user_email",
"user_user_profiles_attributes_0_addrStreet1",
"user_user_profiles_attributes_0_addrCity",
"user_user_profiles_attributes_0_addrState",
"user_user_profiles_attributes_0_addrZip",
"user_user_profiles_attributes_0_phone"
];
var contact_vals = "";
var index = 0;
//see if any contact info exists
for( var index in contact_info_ids){
contact_vals += $("#"+contact_info_ids[index]).val();
}
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
if( submit_count > MAX_SUBMITS ){
alert("Fine.");
}
return true;
}else{
switch(submit_count){
case 1:
alert("It appears you have not entered any contact information. " +
"Please do.");
break;
case 2:
alert("It is highly recommended that you enter at least one form of" +
" contact information. It is in your best interest.");
break;
case 3:
alert("If something happens to your bicycle, we will not be able to" +
" notify you. Please enter at least one form of contact.");
break;
default:
alert("Please enter at least one form of contact.");
}
return false;
}
});
});
+45
View File
@@ -0,0 +1,45 @@
$(document).ready(function(){
$("#checkin_menu").show();
$("#checkin").click( function(e){
var username = $("#user_email").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
url: '/api/v1/checkin',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"username": username, "password": password }),
complete: function() { },
success: function(data) {
alert("Checked IN!");
$("#user_email").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
alert( "Error: " + JSON.parse(data.responseText)["error"]);
}
})
});
$("#checkout").click( function(e){
var username = $("#user_email").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
url: '/api/v1/checkout',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"username": username, "password": password }),
complete: function() { },
success: function(data) {
alert("Checked OUT!");
$("#user_email").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
alert( "Error: " + JSON.parse(data.responseText)["error"]);
}
})
});
});
View File
+31 -18
View File
@@ -1,9 +1,18 @@
class AppTabPanel < Netzke::Basepack::TabPanel
action :sign_out do |c|
c.icon = :door_out
c.text = "Sign out #{controller.current_user.email}" if controller.current_user
c.text = "Exit"
end
action :check_out do |c|
c.icon = :door_out
c.text = "CHECK OUT" if controller.current_user
end
action :change_account_info do |c|
c.icon = :user_edit
c.text = "Change Email/Password"
end
def configure(c)
@@ -16,30 +25,36 @@ class AppTabPanel < Netzke::Basepack::TabPanel
title: "Brands/Models"}
]
#for users
if controller.current_user.role?(:user)
# (had to use hash for borders to get the title to display properly)
#for users only
if not controller.current_user.role?(:admin)
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :user_profile_border,
title: "Profile"},
title: "My Profile"},
{ layout: :fit,
wrappedComponent: :user_transactions_border,
title: "Transactions"}
title: "My Transactions"}
]
end
#for admins
if controller.current_user.role?(:admin)
# (had to use hash for borders to get the title to display properly)
if can? :manage, User
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :users_and_profiles_border,
title: "Users/Profiles"},
{ layout: :fit,
title: "Users/Profiles"}]
end
if can? :manage, Transaction
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :transactions_border,
title: "Transactions"},
:logs,
:user_roles]
title: "Users/Transactions"}]
end
if can? :manage, Role
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :user_role_joins,
title: "User Roles"},
]
end
if can? :manage, ::ActsAsLoggable::Log.all
@@app_tab_panel_items.concat [:check_ins, :logs]
end
@@app_tab_panel_items.each do |item|
if item.kind_of?(Symbol)
self.class.component item
@@ -47,9 +62,8 @@ class AppTabPanel < Netzke::Basepack::TabPanel
self.class.component item[:wrappedComponent]
end
end
c.prevent_header = true
c.tbar = [:sign_out]
c.tbar = [:sign_out, :check_out, :change_account_info]
c.items = @@app_tab_panel_items
super
end
@@ -58,6 +72,5 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#gets js from app_tab_panel/javascripts/sign_out.js
c.mixin :sign_out
end
end
@@ -5,5 +5,20 @@
url: '/users/sign_out',
method: 'DELETE'
});
},
onCheckOut: function(){
Ext.Ajax.request({
url: '/api/v1/checkout',
method: 'POST',
success: function(response, opts) {
Ext.Ajax.request({
url: '/users/sign_out',
method: 'DELETE'
});
}
});
},
onChangeAccountInfo: function(){
window.location.href="users/edit";
}
}
+8 -7
View File
@@ -19,11 +19,8 @@ class BikeLogs < Netzke::Basepack::Grid
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
:description,
{ :name => :bike_action__action, :text => 'Action'},
{ :name => :created_at, :read_only => true},
{ :name => :updated_at, :read_only => true},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -31,26 +28,30 @@ class BikeLogs < Netzke::Basepack::Grid
}
]
@bike_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Bike").all
c.prohibit_update = true if cannot? :update, @bike_logs
c.prohibit_create = true if cannot? :create, @bike_logs
c.prohibit_delete = true if cannot? :delete, @bike_logs
=begin
#TODO: fix GUI so it actually respects this
current_bike = Bike.find_by_id(session[:selected_bike_id])
if cannot? :update, current_bike
# if you can't update the bike, you can't do anything to the log
c.prohibit_update = c.prohibit_create = c.prohibit_delete = true
end
=end
end
def default_fields_for_forms
[
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action'}
]
end
=begin
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
@@ -58,5 +59,5 @@ class BikeLogs < Netzke::Basepack::Grid
bbar.concat [ :add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar
end
=end
end
+33 -10
View File
@@ -3,11 +3,13 @@ class Bikes < Netzke::Basepack::Grid
super
c.model = "Bike"
# columns with :id set, have :min_chars set in init_component
# See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering
c.columns = [
{ :name => :shop_id, :text => 'Shop ID'},
:serial_number,
{ :name => :bike_brand__brand, :text => 'Brand' },
{ :name => :bike_model__model, :text => 'Model',
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
{ :name => :model, :text => 'Model',
:scope => lambda { |rel|
if session[:selected_bike_brand_id]
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
@@ -16,22 +18,43 @@ class Bikes < Netzke::Basepack::Grid
end
}
},
:color,
{ :name => :bike_style__style, :text => 'Style' },
:seat_tube_height,
:top_tube_length,
:wheel_size,
#needs to have type :action or else won't work in grid, because... netzke
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block},
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style' },
{ :name => :seat_tube_height, :text => 'Seat Tube (in)'},
{ :name => :top_tube_length, :text => 'Top Tube (in)'},
{ :name => :wheel_size, :text => 'Wheel Size (in)'},
:value,
{ :name => :bike_condition__condition, :text => 'Condition'},
{ :name => :bike_status__status, :text => 'Status'},
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition'},
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose'},
{ :name => :owner, :getter => lambda { |rec|
user = rec.owner
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
}
]
@bike = Bike.all
c.prohibit_update = true if cannot? :update, @bike
c.prohibit_create = true if cannot? :create, @bike
c.prohibit_delete = true if cannot? :delete, @bike
end
def default_fields_for_forms
# :field_label MUST be defined in order for search to work
[
{ :name => :bike_brand__brand, :field_label => 'Brand' },
{ :name => :model, :field_label => 'Model'},
{ :name => :shop_id, :field_label => 'Shop ID'},
{ :name => :serial_number, :field_label => 'Serial Number'},
{ :name => "color", :xtype => "xcolorcombo"},
{ :name => :bike_style__style, :field_label => 'Style' },
{ :name => :seat_tube_height, :field_label => 'Seat Tube (in)'},
{ :name => :top_tube_length, :field_label => 'Top Tube (in)'},
{ :name => :wheel_size, :field_label => 'Wheel Size (in)'},
{ :name => :value, :field_label => 'Value'},
{ :name => :bike_condition__condition, :field_label => 'Condition'},
{ :name => :bike_purpose__purpose, :field_label => 'Purpose'}
]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
@@ -3,11 +3,25 @@
// calling superclass's initComponent
this.callParent();
// something...
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"bike_brand__brand",
"bike_style__style",
"bike_condition__condition",
"bike_purpose__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
// setting the 'rowclick' event
var view = this.getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')});
}, this);
},
colorBlock: function(value){
return Ext.String.format('<div style="display:inline-block">{1}</div><div style="background-color:#{0};width:50px;height:10px;display:inline-block;margin:0 5px 0 5px;border:solid;border-color:gray;border-width:1px;"></div>', value, value);
}
}
+31
View File
@@ -0,0 +1,31 @@
class CheckIns < Netzke::Basepack::Grid
def configure(c)
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
where(:loggable_type => "User").
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
where("start_date >= ?", Time.zone.now.beginning_of_day);
}
c.columns = [
{ :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.loggable_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
},
{ :name => "Status", :getter => lambda{ |rec| rec.start_date == rec.end_date ? "Checked In" : "Checked Out" } },
:start_date,
:end_date,
]
c.prohibit_update = true
c.prohibit_create = true
c.prohibit_delete = true
end
def default_bbar
[ :search ]
end
end
+21
View File
@@ -22,4 +22,25 @@ class Logs < Netzke::Basepack::Grid
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration}
]
end
def default_fields_for_forms
[
:loggable_id,
:loggable_type,
:logger_id,
:logger_type,
:context,
:start_date,
:end_date,
:description,
:log_action_id,
:log_action_type,
:created_at,
:updated_at
].map{ |x| { :name => x, :field_label => x.to_s.humanize} }
end
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
+9 -10
View File
@@ -29,22 +29,21 @@ class TransactionLogs < Netzke::Basepack::Grid
}
]
c.prohibit_update = true if cannot? :update, ::ActsAsLoggable::Log
c.prohibit_create = true if cannot? :create, ::ActsAsLoggable::Log
c.prohibit_delete = true if cannot? :delete, ::ActsAsLoggable::Log
@transaction_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Transaction").all
c.prohibit_update = true if cannot? :update, @transaction_logs
c.prohibit_create = true if cannot? :create, @transaction_logs
c.prohibit_delete = true if cannot? :delete, @transaction_logs
end
def default_fields_for_forms
customer = nil
item = nil
if session[:selected_transaction_id]
customer = "No Customer Selected"
item = "No Item Selected"
trans = Transaction.find_by_id(session[:selected_transaction_id])
if trans
customer = trans.customer
item = trans.item
end
customer = "No Customer Selected" if customer.nil?
item = "No Item Selected" if item.nil?
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment from:", :value => "#{customer.to_s}"},
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
@@ -58,8 +57,8 @@ class TransactionLogs < Netzke::Basepack::Grid
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
bbar.concat [:add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar.concat [ :apply ] if can? :update, @transaction_logs
bbar.concat [:add_in_form ] if can? :create, @transaction_logs
bbar
end
+20 -26
View File
@@ -1,5 +1,4 @@
class UserLogs < Netzke::Basepack::Grid
def configure(c)
super
@@ -14,10 +13,16 @@ class UserLogs < Netzke::Basepack::Grid
:copy_action_id => 4
}
if can? :manage, ::ActsAsLoggable::Log
#this seems inefficient
if can? :manage, ::ActsAsLoggable::Log.where(:loggable_type => "User").all
#admins and staff
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => session[:selected_user_id]);}
user_log_strong_default_attrs.merge!( { :loggable_id => session[:selected_user_id] } )
#if selected user nil, then the admin is viewing their own profile
selected_user = User.find_by_id(session[:selected_user_id])
selected_user = controller.current_user if selected_user.nil?
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => selected_user.id );}
user_log_strong_default_attrs.merge!( { :loggable_id => selected_user.id } )
user_log_data_store = {auto_load: true }
else
#just users
@@ -27,19 +32,17 @@ class UserLogs < Netzke::Basepack::Grid
end
c.model = "ActsAsLoggable::Log"
c.title = "User History"
c.title = "User Timesheet"
c.data_store = user_log_data_store
c.scope = user_log_scope
c.strong_default_attrs = user_log_strong_default_attrs
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :format => "g:ia - D, M j - Y", :default_value => Time.now.to_formatted_s(:db) },
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
:description,
{ :name => :user_action__action, :text => 'Action' },
:created_at,
:updated_at,
{ :id => :user_action__action, :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -48,23 +51,10 @@ class UserLogs < Netzke::Basepack::Grid
]
end
def default_fields_for_forms
#figure out a better way to do this
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
bike_id = current_user.bike.nil? ? nil : current_user.bike.id
action_id = current_user.user_role.id
[
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action', :value => action_id},
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
]
}
]
# Overriding the defautl add in form
def preconfigure_record_window(c)
super
c.form_config.klass = UserLogsAddItem
end
#override with nil to remove actions
@@ -72,4 +62,8 @@ class UserLogs < Netzke::Basepack::Grid
[ :apply, :add_in_form, :search ]
end
js_configure do |c|
c.mixin :init_component
end
end
@@ -0,0 +1,49 @@
{
initComponent: function(){
// calling superclass's initComponent
this.callParent();
// TESTTING
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"user_action__purpose"
];
// Ext.each(min_char_columns, function(column, index) {
// Ext.ComponentManager.get(column).editor.minChars = 1;
// });
var view = this.getView();
view.on('itemclick', function(view, record){
console.log('clicking ');
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
// console.log('Old record', record);
}, this);
view.on('itemupdate', function(record, index, node){
console.log('Updating item');
// if('end_date' in record.data.modified){
// console.log('start', record.data.start_date);
// console.log('end', record.data.end_date);
// }else if('start_date' in record.data.modified){
// }
}, this);
// var hoursInput = Ext.getCmp('user_logs_add_form_hours-inputEl');
// console.log(hoursInput);
// hoursInput.on('change', function(field, newValue, oldValue){
// console.log('Hours changing...', newValue);
// });
},
addNewItem: function(e){
e.fireEvent('click', {});
console.log(e);
console.log('Adding new item');
}
}
+31
View File
@@ -0,0 +1,31 @@
# The User Log Add Item Form
class UserLogsAddItem < Netzke::Basepack::Form
def configure(c)
super
c.model = 'ActsAsLoggable::Log'
#figure out a better way to do this
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
bike_id = current_user.bike.nil? ? nil : current_user.bike.id
c.items = [
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Log for:", :value => "#{current_user.to_s}"},
{ :id => :user_logs_add_form_start, :name => :start_date},
{ :id => :user_logs_add_form_time, :no_binding => true, :name => :time, :xtype => 'field', :fieldLabel => "Time:", :value => 0 },
{ :id => :user_logs_add_form_units, :xtype => 'combo', :no_binding => true, :name => :units, :fieldLabel => 'Units', :store => ['Mins', 'Hrs'], :value => 'Mins' },
{ :id => :user_logs_add_form_end, :name => :end_date, :hidden => true },
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action'},
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true,
:title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
]
}
]
end
js_configure do |c|
c.mixin :init_component
end
end
@@ -0,0 +1,76 @@
{
initComponent: function(){
// calling superclass's initComponent
this.callParent();
var panel = this;
var theForm = this.getForm();
var startInput = Ext.getCmp('user_logs_add_form_start');
var timeInput = Ext.getCmp('user_logs_add_form_time');
var unitsInput = Ext.getCmp('user_logs_add_form_units');
var endInput = Ext.getCmp('user_logs_add_form_end');
var startdate = startInput.items.items[0];
var starttime = startInput.items.items[1];
var enddate = endInput.items.items[0];
var endtime = endInput.items.items[1];
startdate.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
starttime.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
timeInput.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
unitsInput.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
}
}
+34
View File
@@ -0,0 +1,34 @@
class UserRoleJoins < Netzke::Basepack::Grid
def configure(c)
super
c.model = "UserRoleJoin"
c.header = false
c.title = "User Roles"
c.data_store.sorters = [{ :property => :user__username, :direction => :ASC}]
c.columns = [
{ :name => :user__username, :text => "Username", :read_only => true},
{ :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.user_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
},
{ :name => :role__role, :text => "Role"},
:created_at,
:updated_at,
:ends ]
end
def default_fields_for_forms
[
{ :name => :user__full_name, :field_label => 'Name'},
{ :name => :role__role, :field_label => 'Role' },
{ :name => :ends }
]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
-15
View File
@@ -1,15 +0,0 @@
class UserRoles < Netzke::Basepack::Grid
def configure(c)
super
c.model = "UserRole"
c.title = "User Roles"
c.columns = [ :role, :created_at, :updated_at, :ends ]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
+18 -3
View File
@@ -2,14 +2,29 @@ class UserStats < Netzke::Base
def body_content(user)
bike = user.bike
%Q(
<div id="user_stats_page">
completed = user.completed_build_bikes
#TODO I think it's time to switch to a template
body = ""
body += %Q(<div id="user_stats_page">)
body += %Q(
<p>Username: #{user.username}</p>
<p>Total Credits Earned: #{user.total_earned_credits}</p>
<p>Credits Available: #{user.total_credits}</p>
<p>Credits Spent: #{user.total_credits_spent}</p>
<p>Total Hours Worked: #{user.total_hours}</p>
<p>Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}</p>
<p>Current bike Shop ID: #{bike.shop_id if bike}</p>
<p>Current bike S/N: #{bike.serial_number if bike}</p>
</div>
)
unless completed.empty?
body += %Q(<p>Previously built bikes (#{completed.count}):</p>)
body += %Q(<ul>)
completed.each do |b|
body += %Q(<li>#{b.shop_id}</li>)
end
body += %Q(</ul>)
end
body += %Q(</div>)
end
js_configure do |c|
+13 -3
View File
@@ -1,17 +1,27 @@
class Users < Netzke::Basepack::Grid
include Netzke::Basepack::ActionColumn
column :reset do |c|
c.type = :action
c.actions = [{name: :reset_password, icon: :lock_break}]
c.header = ""
c.width = 20
end
def configure(c)
super
c.header = false
c.model = "User"
c.columns = [
{ :name => :username, :read_only => true },
:first_name,
:last_name,
:nickname,
:email,
:user_role__role,
:bike__shop_id
{ :id => :bike__shop_id, :name => :bike__shop_id}
]
c.columns << :reset if can? :manage, User
end
#override with nil to remove actions
@@ -2,10 +2,41 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = ["bike__shop_id"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
}, this);
},
onResetPassword: function(record){
user = record.data;
Ext.Msg.confirm(
"Reset Password",
"Are you sure you want to reset "+user.first_name+" "+user.last_name+"'s password?",
function(butt_id){
if( butt_id === "yes" ){
$.ajax({
type: 'POST',
url: '/api/v1/reset',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"user_id": user.id}),
complete: function() { },
success: function(data) {
Ext.Msg.alert("Success", "New Password: "+data.password);
},
error: function(data,textStatus) {
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]);
}
});
}
});
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ class UsersAndProfilesBorder < Netzke::Base
super
c.header = false
c.items = [
{ netzke_component: :users, header: "Users", region: :center, width: 300, split: true },
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
{ netzke_component: :user_logs, region: :east, split: true}
]
+22
View File
@@ -0,0 +1,22 @@
class Api::V1::BaseController < ActionController::Base
respond_to :json
before_filter :authenticate_user
private
def authenticate_user
if params[:username]
user = User.find_for_database_authentication( :email => params[:username] )
@current_user = user if user && user.valid_password?( params[:password] )
if @current_user.nil?
msg = "Username/Password/Token invalid"
render :json => {:error => msg }, :status => 403 and return
end
else
authenticate_user!
@current_user = current_user
end
end
end
+22
View File
@@ -0,0 +1,22 @@
class Api::V1::LogsController < Api::V1::BaseController
def checkin
#must use @current_user since user may not have signed in
if @current_user.checked_in?
render :json => { "error" => "You are already checked in."}, :status => 404 and return
else
@current_user.checkin
render :nothing => true, :status => 204 and return
end
end
def checkout
#must use @current_user since user may not have signed in
if !@current_user.checked_in?
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
else
@current_user.checkout
render :nothing => true, :status => 204 and return
end
end
end
@@ -0,0 +1,28 @@
require 'securerandom'
class Api::V1::UsersController < Api::V1::BaseController
def password_reset
if can? :manage, User
user = User.find_by_id(params[:user_id])
render :json => { "error" => "User not found"}, :status => 404 and return if user.nil?
render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id
new_pass = SecureRandom.hex[0,8]
user.password = new_pass
user.save
render :json => { "password" => new_pass}, :status => 200 and return
else
render :json => { "error" => "You do not have the permission"}, :status => 403 and return
end
end
def checkout
#must use @current_user since user may not have signed in
if !@current_user.checked_in?
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
else
@current_user.checkout
render :nothing => true, :status => 204 and return
end
end
end
+23
View File
@@ -0,0 +1,23 @@
module DeviseHelper
# A simple way to show error messages for the current devise resource. If you need
# to customize this method, you can either overwrite it in your application helpers or
# copy the views to your application.
#
# This method is intended to stay simple and it is unlikely that we are going to change
# it to add more behavior or options.
def devise_error_messages!
return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<p>#{sentence}</p>
#{messages}
HTML
html.html_safe
end
end
+6 -4
View File
@@ -3,7 +3,9 @@ class Ability
def initialize(current_user)
@current_user = current_user
self.send(current_user.role.to_sym)
current_user.roles.each do |role|
self.send(role.role.to_sym)
end
end
def admin
@@ -21,8 +23,8 @@ class Ability
def user
can :read, :all
can :update, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "Bike", :loggable_id => @current_user.bike_id }
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "User", :loggable_id => @current_user.id }
can :manage, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
end
end
+11 -14
View File
@@ -1,22 +1,23 @@
class Bike < ActiveRecord::Base
acts_as_loggable
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_status_id
attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_purpose_id
has_many :transactions
has_one :owner, :class_name => 'User'
has_one :task_list, :as => :item, :dependent => :destroy
belongs_to :bike_brand
belongs_to :bike_model
belongs_to :bike_style
belongs_to :bike_condition
belongs_to :bike_status
belongs_to :bike_purpose
default_scope order('shop_id ASC')
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
validates :serial_number, :uniqueness => true, :length => { :minimum => 3 }
validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true
validates :bike_model_id, :presence => true
validates :color, :presence => true
validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true
@@ -24,20 +25,16 @@ class Bike < ActiveRecord::Base
validates :wheel_size, :presence => true
#validates :value, :presence => true
validates :bike_condition_id, :presence => true
validates :bike_status_id, :presence => true
validates :bike_purpose_id, :presence => true
self.per_page = 15
after_save :create_task_list
after_create :create_task_list
def brand
self.bike_brand
end
def model
self.bike_model
end
def style
self.bike_style
end
@@ -46,8 +43,8 @@ class Bike < ActiveRecord::Base
self.bike_condition
end
def status
self.bike_status
def purpose
self.bike_purpose
end
def to_s
-1
View File
@@ -1,7 +1,6 @@
class BikeModel < ActiveRecord::Base
attr_accessible :model, :bike_brand_id
has_many :bikes
belongs_to :bike_brand
default_scope order('model ASC')
+9
View File
@@ -0,0 +1,9 @@
class BikePurpose < ActiveRecord::Base
attr_accessible :purpose
belongs_to :bike
def to_s
self.purpose
end
end
-9
View File
@@ -1,9 +0,0 @@
class BikeStatus < ActiveRecord::Base
attr_accessible :status
belongs_to :bike
def to_s
self.status
end
end
+3
View File
@@ -0,0 +1,3 @@
class CreditConversion < ActiveRecord::Base
attr_accessible :conversion
end
+16
View File
@@ -0,0 +1,16 @@
class Role < ActiveRecord::Base
attr_accessible :role
has_many :user_role_joins
has_many :users, through: :user_role_joins
validates_uniqueness_of :role
def to_s
self.role
end
def ==(other)
self.role == other.role
end
end
+101 -10
View File
@@ -7,16 +7,21 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :nickname, :user_role_id, :bike_id,
:user_profiles_attributes
:first_name, :last_name, :bike_id,
:user_profiles_attributes, :username
has_many :transactions
has_many :transactions, as: :customer
has_many :transaction_logs, through: :transactions, source: :logs
has_many :user_profiles
accepts_nested_attributes_for :user_profiles, allow_destroy: false
has_one :user_role
has_many :user_role_joins, :conditions => ["ends IS NULL OR ends > ?", Time.now]
has_many :roles, through: :user_role_joins
belongs_to :bike
default_scope order('username ASC')
validates :first_name, :presence => true
validates :last_name, :presence => true
@@ -24,29 +29,115 @@ class User < ActiveRecord::Base
"#{first_name} #{last_name}"
end
def email_required?
false
end
def full_name
to_s
end
def role
user_role.role
def role?(role)
if role.kind_of?(String) or role.kind_of?(Symbol)
role = Role.find_by_role(role.to_s)
end
roles.include?(role)
end
def role?(role)
user_role.to_s == role.to_s
### TODO methods below probably belong somewhere else
def completed_build_bikes
purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id
Bike.find_by_sql("
SELECT *
FROM bikes
INNER JOIN(
SELECT *
FROM transactions
WHERE customer_id = #{self.id}
) AS transactions ON bikes.id = transactions.bike_id
WHERE bike_purpose_id = #{purpose_id}")
end
def total_credits
total_earned_credits - total_credits_spent
end
def total_credits_spent
log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
transaction_logs.
where( "log_action_id = ? AND log_action_type = ?",
log_action.id, log_action.class.to_s).
sum{ |r| r.description.to_i }.round(2)
end
def total_earned_credits
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
# Find the first credit conversion which has a created_at date before the
# log's created_at date and join it to the log's row so we can calculate
# the credits earned from that log entry (each log could have a different
# conversion rate)
#
# The DISTINCT ON, and ORDER BY are important to getting the
# single conversion rate that applies to the respective log.
::ActsAsLoggable::Log.find_by_sql("
SELECT DISTINCT ON (logs.created_at) start_date, end_date,
conversion.conversion, conversion.created_at
FROM logs
INNER JOIN(
SELECT conversion, created_at
FROM credit_conversions
) AS conversion ON logs.created_at > conversion.created_at
WHERE logs.loggable_id = #{self.id}
AND logs.loggable_type = 'User'
AND (log_action_id != #{log_action.id} AND log_action_type = '#{log_action.class.to_s}')
ORDER BY logs.created_at, conversion.created_at DESC").
sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
end
def total_hours
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
end
def current_month_hours
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id)
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s)
.where( :start_date => current_month_range)
.where( :end_date => current_month_range)
.sum { |l| (l.end_date - l.start_date)/3600 }
.round(2)
end
def checked_in?
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
checked = logs.where( log_action_id: log_action.id).
where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date")
!checked.empty?
end
def checkin
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
time = Time.now
logs.create( logger_id: self.id,
logger_type: self.class.to_s,
start_date: time,
end_date: time,
log_action_id: log_action.id,
log_action_type: log_action.class.to_s)
save
end
def checkout
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
checked = logs.where( log_action_id: log_action.id).
where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date").first
checked.end_date = Time.now
checked.save
end
end
+1 -2
View File
@@ -1,5 +1,4 @@
class UserProfile < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity,
:addrState, :addrZip, :phone
@@ -10,7 +9,7 @@ class UserProfile < ActiveRecord::Base
#validates :addrCity , :presence => true
#validates :addrState , :presence => true
#validates :addrZip , :presence => true
validates :phone, :presence => true
#validates :phone, :presence => true
def to_s
[addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ")
-11
View File
@@ -1,11 +0,0 @@
class UserRole < ActiveRecord::Base
attr_accessible :role
belongs_to :user
self.per_page = 15
def to_s
self.role
end
end
+13
View File
@@ -0,0 +1,13 @@
class UserRoleJoin < ActiveRecord::Base
self.table_name = :user_role_joins
attr_accessible :role_id, :user_id, :ends
belongs_to :user
belongs_to :role
validate :role_id, presence: true, numericality: true
validate :user_id, presence: true, numericality: true
validates_uniqueness_of :user_id, :scope => :role_id
self.per_page = 15
end
@@ -1,24 +1,25 @@
%fieldset
= f.label :addrStreet1
%br/
= f.text_field :addrStreet1
%br/
= f.label :addrStreet2
%br/
= f.text_field :addrStreet2
%br/
= f.label :addrCity
%br/
= f.text_field :addrCity
%br/
= f.label :addrState
%br/
= f.text_field :addrState
%br/
= f.label :addrZip
%br/
= f.text_field :addrZip
%br/
= f.label :phone
%br/
= f.text_field :phone
.control-group
= f.label :addrStreet1, :class => "control-label"
.controls
= f.text_field :addrStreet1, :class => "control-label"
.control-group
= f.label :addrStreet2, :class => "control-label"
.controls
= f.text_field :addrStreet2, :class => "control-label"
.control-group
= f.label :addrCity, :class => "control-label"
.controls
= f.text_field :addrCity, :class => "control-label"
.control-group
= f.label :addrState, :class => "control-label"
.controls
= f.text_field :addrState, :class => "control-label"
.control-group
= f.label :addrZip, :class => "control-label"
.controls
= f.text_field :addrZip, :class => "control-label"
.control-group
= f.label :phone, :class => "control-label"
.controls
= f.text_field :phone, :class => "control-label"
@@ -1,3 +1,5 @@
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+28 -24
View File
@@ -1,32 +1,36 @@
%h2 Sign up
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= stylesheet_link_tag "bootstrap_and_overrides", :media => "all"
= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :class => "form-horizontal"}) do |f|
= devise_error_messages!
%div
= f.label :first_name
%br/
.controls
%h2 Sign up
.control-group
= f.label :username, :class => "control-label"
.controls
= f.text_field :username
.control-group
= f.label :first_name, :class => "control-label"
.controls
= f.text_field :first_name
%div
= f.label :last_name
%br/
.control-group
= f.label :last_name, :class => "control-label"
.controls
= f.text_field :last_name
%div
= f.label :nickname
%br/
= f.text_field :nickname
%div
= f.label :email
%br/
.control-group
= f.label :email, :class => "control-label"
.controls
= f.email_field :email
%div
= f.fields_for :user_profiles, UserProfile.new do |builder|
- profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles
= f.fields_for :user_profiles, profile_builder do |builder|
= render 'user_profile_fields', f: builder
%div
= f.label :password
%br/
.control-group
= f.label :password, :class => "control-label"
.controls
= f.password_field :password
%div
= f.label :password_confirmation
%br/
.control-group
= f.label :password_confirmation, :class => "control-label"
.controls
= f.password_field :password_confirmation
%div= f.submit "Sign up"
.control-group
.controls
= f.submit "Sign up"
= render "links"
+16 -6
View File
@@ -1,8 +1,12 @@
<h2>Sign in</h2>
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Velocipede</h2>
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
<br>
<br>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :username%><br />
<%= f.text_field :username%></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
@@ -12,17 +16,23 @@
<% end -%>
<div><%= f.submit "Sign in" %></div>
<div id="checkin_menu"style="display:none;">
<input id="checkin" name="checkin" type="button" value="CHECK IN">
<input id="checkout" name="checkout" type="button" value="CHECK OUT">
</div>
<% end %>
<%= render "links" %>
<% if Rails.env.development? %>
<% User.all.each do |user| %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.hidden_field :email, :value => user.email %></div>
<%= f.hidden_field :username, :value => user.username%></div>
<%= f.hidden_field :password, :value => 'password' %></div>
<div><%= f.submit "Sign in as #{user.email}" %></div>
<div><%= f.submit "Sign in as #{user.username}" %></div>
<% end %>
<% end %>
<% end %>
+1
View File
@@ -28,3 +28,4 @@
%p &copy; Velocipede 2013
= javascript_include_tag "application"
= javascript_include_tag params[:controller]
-25
View File
@@ -1,25 +0,0 @@
# SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000
+23
View File
@@ -0,0 +1,23 @@
development:
adapter: postgresql
database: velocipede
username: velocipede
password:
host: 127.0.0.1
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: postgresql
database: velocipede
username: velocipede
password:
host: 127.0.0.1
production:
adapter: postgresql
database: velocipede
username: velocipede
password:
host: 127.0.0.1
+4 -12
View File
@@ -5,20 +5,15 @@ Velocipede::Application.configure do
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.consider_all_requests_local = true
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
config.serve_static_assets = true
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
config.assets.compress = false
config.assets.debug = true
# Defaults to Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH
@@ -45,9 +40,6 @@ Velocipede::Application.configure do
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# TODO set to host
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
+12 -3
View File
@@ -26,7 +26,7 @@ Devise.setup do |config|
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
# config.authentication_keys = [ :email ]
config.authentication_keys = [ :username ]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
@@ -38,12 +38,12 @@ Devise.setup do |config|
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
config.case_insensitive_keys = [ :email ]
config.case_insensitive_keys = [ :username ]
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
# modifying a user and when used to authenticate or find a user. Default is :email.
config.strip_whitespace_keys = [ :email ]
config.strip_whitespace_keys = [ :username ]
# Tell if authentication through request.params is enabled. True by default.
# It can be set to an array that will enable params authentication only for the
@@ -221,3 +221,12 @@ Devise.setup do |config|
# manager.default_strategies(:scope => :user).unshift :some_external_strategy
# end
end
#Check in the user if they sign in. (Devise uses Warden)
Warden::Manager.after_set_user do |user,auth,opts|
# this essentially gets called after every netzke request, but alas,
# only using the after_authenticaion callback doesn't get fired after
# user creation.
user.checkin unless user.checked_in?
end
+1 -1
View File
@@ -14,7 +14,7 @@ en:
devise:
failure:
already_authenticated: 'You are already signed in.'
unauthenticated: 'You need to sign in or sign up before continuing.'
unauthenticated: 'You need to provide your username and password or sign in/sign up before continuing.'
unconfirmed: 'You have to confirm your account before continuing.'
locked: 'Your account is locked.'
invalid: 'Invalid email or password.'
+10
View File
@@ -4,4 +4,14 @@ Velocipede::Application.routes.draw do
netzke
root :to => 'site#index'
###########################
# API Routes
scope 'api', :module => :api do
scope 'v1', :module => :v1 do
post 'checkin' => "logs#checkin", :as => "api_checkin"
post 'checkout' => "logs#checkout", :as => "api_checkout"
post 'reset' => "users#password_reset", :as => "api_password_reset"
end
end
end
@@ -2,6 +2,5 @@ class AddEndsToUserRoles < ActiveRecord::Migration
def change
add_column(:user_roles, :ends, :timestamp)
add_column(:user_roles, :user_id, :integer)
remove_column(:users, :role_id)
end
end
@@ -0,0 +1,23 @@
class AlterUserRoles < ActiveRecord::Migration
def up
rename_table :user_roles, :user_role_joins
change_table :user_role_joins do |t|
t.rename :role, :role_id
#t.change :role_id, :integer
end
#for Postgres
connection.execute(%q{
alter table user_role_joins
alter column role_id
type integer using cast(role_id as integer)
})
end
def down
rename_table :user_role_joins, :user_roles
change_table :user_role_joins do |t|
t.rename :role_id, :role
t.change :role_id, :string
end
end
end
+8
View File
@@ -0,0 +1,8 @@
class AddRoles < ActiveRecord::Migration
def change
create_table(:roles) do |t|
t.string :role
t.timestamps
end
end
end
+5
View File
@@ -0,0 +1,5 @@
class AlterUser < ActiveRecord::Migration
def change
remove_column :users, :user_role_id
end
end
@@ -0,0 +1,6 @@
class AddUsernameToUser < ActiveRecord::Migration
def change
add_column :users, :username, :string
add_index :users, :username, :unique => true
end
end
@@ -0,0 +1,5 @@
class RemoveNickname < ActiveRecord::Migration
def change
remove_column :users, :nickname
end
end
@@ -0,0 +1,8 @@
class CreateCreditConversions < ActiveRecord::Migration
def change
create_table(:credit_conversions) do |t|
t.integer :conversion, :default => 1
t.timestamps
end
end
end
@@ -0,0 +1,15 @@
class BikeConditionIdToInt < ActiveRecord::Migration
def up
#for Postgres
connection.execute(%q{
alter table bikes
alter column bike_condition_id
type integer using cast(bike_condition_id as integer)
})
end
def down
#you really don't want to go back
change_column :bikes, :bike_condition_id, :string
end
end
@@ -0,0 +1,7 @@
class BikeStatusToPurpose < ActiveRecord::Migration
def change
rename_column :bikes, :bike_status_id, :bike_purpose_id
rename_column :bike_statuses, :status, :purpose
rename_table :bike_statuses, :bike_purposes
end
end
@@ -0,0 +1,10 @@
class AddModelToBike < ActiveRecord::Migration
def change
add_column :bikes, :model, :string
change_column :bikes, :bike_model_id, :integer, :null => true
Bike.all.each do |bike|
bike.model = BikeModel.find_by_id(bike.bike_model_id).model
bike.save
end
end
end
+23 -10
View File
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130419010051) do
ActiveRecord::Schema.define(:version => 20130902201853) do
create_table "bike_actions", :force => true do |t|
t.string "action", :limit => 128, :null => false
@@ -34,8 +34,8 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
t.integer "bike_brand_id", :null => false
end
create_table "bike_statuses", :force => true do |t|
t.string "status", :null => false
create_table "bike_purposes", :force => true do |t|
t.string "purpose", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
@@ -49,22 +49,29 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
create_table "bikes", :force => true do |t|
t.string "serial_number"
t.integer "bike_brand_id", :null => false
t.integer "bike_model_id", :null => false
t.integer "bike_model_id"
t.string "color"
t.integer "bike_style_id", :null => false
t.float "seat_tube_height"
t.float "top_tube_length"
t.integer "wheel_size"
t.float "value"
t.string "bike_condition_id", :null => false
t.integer "bike_status_id", :null => false
t.integer "bike_condition_id", :null => false
t.integer "bike_purpose_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "shop_id"
t.string "model"
end
add_index "bikes", ["shop_id"], :name => "index_bikes_on_shop_id", :unique => true
create_table "credit_conversions", :force => true do |t|
t.integer "conversion", :default => 1
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "customers", :force => true do |t|
t.string "first_name", :null => false
t.string "last_name", :null => false
@@ -97,6 +104,12 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
add_index "logs", ["loggable_id", "loggable_type", "context"], :name => "index_logs_on_loggable_id_and_loggable_type_and_context"
create_table "roles", :force => true do |t|
t.string "role"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "task_lists", :force => true do |t|
t.integer "item_id", :null => false
t.string "item_type", :null => false
@@ -148,8 +161,8 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
t.datetime "updated_at", :null => false
end
create_table "user_roles", :force => true do |t|
t.string "role"
create_table "user_role_joins", :force => true do |t|
t.integer "role_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "ends"
@@ -167,7 +180,6 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "user_role_id", :default => 1, :null => false
t.integer "bike_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -176,11 +188,12 @@ ActiveRecord::Schema.define(:version => 20130419010051) do
t.datetime "locked_at"
t.string "first_name", :default => "", :null => false
t.string "last_name", :default => "", :null => false
t.string "nickname"
t.string "username"
end
add_index "users", ["bike_id"], :name => "index_users_on_bike_id", :unique => true
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
end
@@ -13,3 +13,8 @@ staff:
action: STAFF
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
checkin:
id: 4
action: CHECKIN
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
+18
View File
@@ -0,0 +1,18 @@
undetermined:
id: 1
purpose: UNDETERMINED
scrap:
id: 2
purpose: SCRAP
parts:
id: 3
purpose: PARTS
storage:
id: 4
purpose: STORAGE
buildbike:
id: 5
purpose: BUILDBIKE
sale:
id: 6
purpose: SALE
-18
View File
@@ -1,18 +0,0 @@
undetermined:
id: 1
status: UNDETERMINED
scrap:
id: 2
status: SCRAP
parts:
id: 3
status: PARTS
storage:
id: 4
status: STORAGE
buildbike:
id: 5
status: BUILDBIKE
sale:
id: 6
status: SALE
+3
View File
@@ -0,0 +1,3 @@
conversion:
id: 1
conversion: 10
+12
View File
@@ -0,0 +1,12 @@
user:
id: 1
role: user
bike_admin:
id: 2
role: bike_admin
staff:
id: 3
role: staff
admin:
id: 4
role: admin
+17 -3
View File
@@ -15,17 +15,19 @@ end
#Load bike brands and models from sql
if BikeBrand.all.empty? and BikeModel.all.empty?
sql_path = File.join(Rails.root, 'db', 'seed', 'sql', 'bike_brands_and_models.sql')
system "sqlite3 db/development.sqlite3 < #{sql_path}" if Rails.env.development?
db_config = Rails.application.config.database_configuration[Rails.env]
cmd = "psql #{db_config["database"]} < #{sql_path}"
system cmd
end
if Rails.env.development?
#create default admin user
if UserRole.all.empty? and User.all.empty?
if User.all.empty?
FactoryGirl.create(:user)
FactoryGirl.create(:staff)
FactoryGirl.create(:admin)
FactoryGirl.create(:bike_admin)
FactoryGirl.create(:admin)
FactoryGirl.create(:user_profile)
end
@@ -35,4 +37,16 @@ if Rails.env.development?
FactoryGirl.create(:bike)
end
end
elsif Rails.env.production?
unless User.find_by_username('admin')
#create an admin
admin = User.create!( :username => 'admin',
:first_name => 'admin',
:last_name => 'admin',
:email=>'admin@example.com',
:password=>'password')
admin.roles << Role.find_by_role('admin')
end
end
+1 -1
View File
@@ -17,6 +17,6 @@ FactoryGirl.define do
wheel_size 27
value 100
bike_condition_id 1
bike_status_id 1
bike_purpose_id 1
end
end
@@ -1,5 +1,5 @@
FactoryGirl.define do
factory :user_role do
factory :role do
factory :role_staff do
role 'staff'
end
+16 -4
View File
@@ -1,26 +1,38 @@
FactoryGirl.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
sequence(:email) { |n| "user_#{n}@example.com" }
password 'password'
password_confirmation { password }
first_name 'Michael'
last_name 'Scott'
sequence(:bike_id) { |n| n }
association :user_role, factory: :role_user
after_build do |r|
r.roles << (Role.find_by_role("user") || FactoryGirl.create(:role_user))
end
factory :staff do
username "staff"
first_name 'Staff'
association :user_role, factory: :role_staff
after_build do |r|
r.roles << (Role.find_by_role("staff") || FactoryGirl.create(:role_staff))
end
end
factory :admin do
username "admin"
first_name 'Admin'
association :user_role, factory: :role_admin
after_build do |r|
r.roles << (Role.find_by_role("admin") || FactoryGirl.create(:role_admin))
end
end
factory :bike_admin do
username "bike_admin"
first_name 'BikeAdmin'
association :user_role, factory: :role_bike_admin
after_build do |r|
r.roles << (Role.find_by_role("bike_admin") || FactoryGirl.create(:role_bike_admin))
end
end
end