diff --git a/.capistrano/metrics b/.capistrano/metrics new file mode 100644 index 0000000..94a4335 --- /dev/null +++ b/.capistrano/metrics @@ -0,0 +1 @@ +full \ No newline at end of file diff --git a/.gitignore b/.gitignore index 77f0fc8..11b75c0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ # Avatars of producst public/system/products public/system/users + +.project + +/coverage diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000..2dad006 --- /dev/null +++ b/.simplecov @@ -0,0 +1,7 @@ +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = Coveralls::SimpleCov::Formatter +SimpleCov.start do + add_filter 'config/' +end diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..145612c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: ruby +rvm: + - 2.1.0 + +before_script: + - "RAILS_ENV=test bundle exec rake db:create" + - "RAILS_ENV=test bundle exec rake db:schema:load" + +script: bundle exec rake + +notifications: + slack: zeuswpi:1pHNpPMD56jXSGG1w3Ysa9rd + + email: + recipients: + - tab@zeus.ugent.be + on_success: never + on_failure: change diff --git a/Gemfile b/Gemfile index 435d153..26435ea 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.1.7' +gem 'rails', '4.2' # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets @@ -29,14 +29,9 @@ gem 'spring', group: :development # add annotations of schema inside models gem 'annotate' -# Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' - # Use unicorn as the app server # gem 'unicorn' -# Use Capistrano for deployment -# gem 'capistrano-rails', group: :development # Deployment gem 'capistrano', '~> 3.1' gem 'capistrano-rails', '~> 1.1' @@ -46,9 +41,12 @@ group :production do gem 'mysql2' # Database end -# Use debugger -# gem 'debugger', group: [:development, :test] - +group :test do + gem 'capybara' + gem 'launchy' + gem "codeclimate-test-reporter", require: nil + gem 'faker', '1.4.2' +end #bootstrap gem 'bootstrap-sass', '3.2.0.0' @@ -56,9 +54,6 @@ gem 'bootstrap-sass', '3.2.0.0' #debug stuff gem 'byebug' -#passwords -gem 'bcrypt', '3.1.7' - #paginate stuff gem 'will_paginate', '3.0.7' gem 'bootstrap-will_paginate', '0.0.10' @@ -72,5 +67,12 @@ gem 'devise' # Use cancancan for authorization gem 'cancancan' -#ik wil test data maken dus dit -gem 'faker', '1.4.2' +# Safety first +gem 'paper_trail', '~> 4.0.0.beta' + +# Windoos sux +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] + +gem 'coveralls', require: false + +gem 'omniauth-oauth2' diff --git a/Gemfile.lock b/Gemfile.lock index ae209a0..fb566ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,37 +1,48 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (4.1.7) - actionpack (= 4.1.7) - actionview (= 4.1.7) + actionmailer (4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.7) - actionview (= 4.1.7) - activesupport (= 4.1.7) - rack (~> 1.5.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.0) + actionview (= 4.2.0) + activesupport (= 4.2.0) + rack (~> 1.6.0) rack-test (~> 0.6.2) - actionview (4.1.7) - activesupport (= 4.1.7) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + actionview (4.2.0) + activesupport (= 4.2.0) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.7) - activesupport (= 4.1.7) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + activejob (4.2.0) + activesupport (= 4.2.0) + globalid (>= 0.3.0) + activemodel (4.2.0) + activesupport (= 4.2.0) builder (~> 3.1) - activerecord (4.1.7) - activemodel (= 4.1.7) - activesupport (= 4.1.7) - arel (~> 5.0.0) - activesupport (4.1.7) - i18n (~> 0.6, >= 0.6.9) + activerecord (4.2.0) + activemodel (= 4.2.0) + activesupport (= 4.2.0) + arel (~> 6.0) + activesupport (4.2.0) + i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.3.7) annotate (2.6.5) activerecord (>= 2.3.0) rake (>= 0.8.7) - arel (5.0.1.20140414130214) - bcrypt (3.1.7) + arel (6.0.0) + bcrypt (3.1.10) + bcrypt (3.1.10-x64-mingw32) bootstrap-sass (3.2.0.0) sass (~> 3.2) bootstrap-will_paginate (0.0.10) @@ -41,12 +52,13 @@ GEM columnize (~> 0.8) debugger-linecache (~> 1.2) slop (~> 3.6) - cancancan (1.9.2) - capistrano (3.2.1) + cancancan (1.10.1) + capistrano (3.3.5) + capistrano-stats (~> 1.1.0) i18n rake (>= 10.0.0) sshkit (~> 1.3) - capistrano-bundler (1.1.3) + capistrano-bundler (1.1.4) capistrano (~> 3.1) sshkit (~> 1.2) capistrano-rails (1.1.2) @@ -55,19 +67,34 @@ GEM capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) + capistrano-stats (1.1.1) + capybara (2.4.4) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) climate_control (0.0.3) activesupport (>= 3.0) - cocaine (0.5.4) + cocaine (0.5.5) climate_control (>= 0.0.3, < 1.0) + codeclimate-test-reporter (0.4.7) + simplecov (>= 0.7.1, < 1.0.0) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) coffee-script (2.3.0) coffee-script-source execjs - coffee-script-source (1.8.0) - colorize (0.7.3) - columnize (0.8.9) + coffee-script-source (1.9.0) + colorize (0.7.5) + columnize (0.9.0) + coveralls (0.7.11) + multi_json (~> 1.10) + rest-client (>= 1.6.8, < 2) + simplecov (~> 0.9.1) + term-ansicolor (~> 1.3) + thor (~> 0.19.1) debugger-linecache (1.2.0) devise (3.4.1) bcrypt (~> 3.0) @@ -76,102 +103,169 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) + docile (1.1.5) erubis (2.7.0) - execjs (2.2.2) + execjs (2.3.0) faker (1.4.2) i18n (~> 0.5) + faraday (0.9.1) + multipart-post (>= 1.2, < 3) + ffi (1.9.6-x64-mingw32) + globalid (0.3.2) + activesupport (>= 4.1.0) + hashie (3.4.0) hike (1.2.3) - i18n (0.6.11) - jbuilder (2.2.5) + i18n (0.7.0) + jbuilder (2.2.6) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) - jquery-rails (3.1.2) - railties (>= 3.0, < 5.0) + jquery-rails (4.0.3) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (1.8.1) + json (1.8.2) + jwt (1.4.1) + launchy (2.4.3) + addressable (~> 2.3) + loofah (2.0.1) + nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) mime-types (2.4.3) - minitest (5.4.3) + mini_portile (0.6.2) + minitest (5.5.1) multi_json (1.10.1) + multi_xml (0.5.5) + multipart-post (2.0.0) mysql2 (0.3.17) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.9.1) + net-ssh (2.9.2) + netrc (0.10.3) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) + nokogiri (1.6.6.2-x64-mingw32) + mini_portile (~> 0.6.0) + oauth2 (1.0.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) + omniauth (1.2.2) + hashie (>= 1.2, < 4) + rack (~> 1.0) + omniauth-oauth2 (1.2.0) + faraday (>= 0.8, < 0.10) + multi_json (~> 1.3) + oauth2 (~> 1.0) + omniauth (~> 1.2) orm_adapter (0.5.0) - paperclip (4.2.0) + paper_trail (4.0.0.beta2) + activerecord (>= 3.0, < 6.0) + activesupport (>= 3.0, < 6.0) + paperclip (4.2.1) activemodel (>= 3.0.0) activesupport (>= 3.0.0) cocaine (~> 0.5.3) mime-types - rack (1.5.2) - rack-test (0.6.2) + rack (1.6.0) + rack-test (0.6.3) rack (>= 1.0) - rails (4.1.7) - actionmailer (= 4.1.7) - actionpack (= 4.1.7) - actionview (= 4.1.7) - activemodel (= 4.1.7) - activerecord (= 4.1.7) - activesupport (= 4.1.7) + rails (4.2.0) + actionmailer (= 4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + activemodel (= 4.2.0) + activerecord (= 4.2.0) + activesupport (= 4.2.0) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.7) - sprockets-rails (~> 2.0) - railties (4.1.7) - actionpack (= 4.1.7) - activesupport (= 4.1.7) + railties (= 4.2.0) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.5) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.1) + loofah (~> 2.0) + railties (4.2.0) + actionpack (= 4.2.0) + activesupport (= 4.2.0) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.3.2) - rdoc (4.1.2) + rake (10.4.2) + rdoc (4.2.0) json (~> 1.4) - responders (1.1.2) - railties (>= 3.2, < 4.2) + responders (2.1.0) + railties (>= 4.2.0, < 5) + rest-client (1.7.3) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + rest-client (1.7.3-x64-mingw32) + ffi (~> 1.9) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) sass (3.2.19) - sass-rails (4.0.4) + sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) sass (~> 3.2.2) - sprockets (~> 2.8, < 2.12) + sprockets (~> 2.8, < 3.0) sprockets-rails (~> 2.0) sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + simplecov (0.9.2) + docile (~> 1.1.0) + multi_json (~> 1.0) + simplecov-html (~> 0.9.0) + simplecov-html (0.9.0) slop (3.6.0) - spring (1.2.0) - sprockets (2.11.3) + spring (1.3.1) + sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.0) + sprockets-rails (2.2.4) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) sqlite3 (1.3.10) - sshkit (1.5.1) - colorize + sqlite3 (1.3.10-x64-mingw32) + sshkit (1.6.1) + colorize (>= 0.7.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) + term-ansicolor (1.3.0) + tins (~> 1.0) thor (0.19.1) thread_safe (0.3.4) tilt (1.4.1) - turbolinks (2.5.2) + tins (1.3.5) + turbolinks (2.5.3) coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.5.3) + tzinfo-data (1.2015.1) + tzinfo (>= 1.0.0) + uglifier (2.7.0) execjs (>= 0.3.0) json (>= 1.8.0) warden (1.2.3) rack (>= 1.0) will_paginate (3.0.7) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby + x64-mingw32 DEPENDENCIES annotate - bcrypt (= 3.1.7) bootstrap-sass (= 3.2.0.0) bootstrap-will_paginate (= 0.0.10) byebug @@ -179,18 +273,25 @@ DEPENDENCIES capistrano (~> 3.1) capistrano-rails (~> 1.1) capistrano-rvm + capybara + codeclimate-test-reporter coffee-rails (~> 4.0.0) + coveralls devise faker (= 1.4.2) jbuilder (~> 2.0) jquery-rails + launchy mysql2 + omniauth-oauth2 + paper_trail (~> 4.0.0.beta) paperclip - rails (= 4.1.7) + rails (= 4.2) sass-rails (~> 4.0.3) sdoc (~> 0.4.0) spring sqlite3 turbolinks + tzinfo-data uglifier (>= 1.3.0) will_paginate (= 3.0.7) diff --git a/README.md b/README.md index e69de29..d7370a0 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,7 @@ +Tab +=== +[![Code Climate](https://codeclimate.com/github/ZeusWPI/Tab/badges/gpa.svg)](https://codeclimate.com/github/ZeusWPI/Tab) +[![Travis CI](https://travis-ci.org/ZeusWPI/Tab.svg)](https://travis-ci.org/ZeusWPI/Tab) +[![Coverage Status](https://coveralls.io/repos/ZeusWPI/Tab/badge.svg?branch=master)](https://coveralls.io/r/ZeusWPI/Tab?branch=master) + +Yes. We have to drink. But we also have to pay. This combines both. diff --git a/app/assets/javascripts/callbacks.js.coffee b/app/assets/javascripts/callbacks.js.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/callbacks.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/orders.js.coffee b/app/assets/javascripts/orders.js.coffee index 9eab234..32d707f 100644 --- a/app/assets/javascripts/orders.js.coffee +++ b/app/assets/javascripts/orders.js.coffee @@ -8,19 +8,33 @@ ready = -> $('.btn-dec').on 'click', -> increment($(this), -1) - $('.btn-dec').prop("disabled", true) + $('.form_row').each((index, row) -> + disIfNec(row) + $(row).on('input', recalculate) + ) + + recalculate() + +disIfNec = (row) -> + counter = parseInt($(row).find('.row_counter').val()) + $(row).find('.btn-dec').prop('disabled', counter == 0); + $(row).find('.btn-inc').prop('disabled', counter == parseInt($(row).find('.stock').val())) + +recalculate = () -> + value = ($(row).find('.row_counter').val() * $(row).find('.price').val() for row in $('.form_row')).reduce (a, b) -> a+b + $('#order_price').val((value / 100.0).toFixed(2)) increment = (button, n) -> + row = $(button).closest('.form_row') + # Fix the counter - counter = $(button).closest('.form_row').find('.row_counter') + counter = $(row).find('.row_counter') counter.val(parseInt(counter.val()) + n) - # Enable or disable the dec button - counter.parent().find('.btn-dec').prop("disabled", counter.val() == '0'); + # Disable buttons if necessary + disIfNec(row) - # Update the price - oldVal = parseFloat($('#order_total_price').val()) - $('#order_total_price').val(parseFloat(oldVal + counter.parent().find('.price').val() * n).toFixed(2)) + recalculate() $(document).ready(ready) $(document).on('page:load', ready) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index f23eb96..d3eeca2 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -29,76 +29,58 @@ $gray-medium-light: #eaeaea; } /* miscellaneous */ + .nowrap { white-space: nowrap; } - -/* boostrap */ -body { - padding-top: 80px; -} - -/* footer */ -footer { - margin-top: 45px; - padding-top: 5px; - border-top: 1px solid #eaeaea; - color: #777; -} - -footer a { - color: #555; -} - -footer a:hover { - color: #222; -} - -footer small { - float: left; -} - -footer ul { - float: right; - list-style: none; -} - -footer ul li { - float: left; - margin-left: 15px; -} - -.overview{ - //margin-left: 2px; - width: 50%; - float: left; - border-radius: 0px; - margin-bottom: 0px; - border: 0px; - - .btn{ - margin-top: -55px; - width: 100%; - border-color: #444; - } - - .dagschotel{ - float:left; - position: absolute; - } - - .avatar{ - height: 190px; - width: 190px; - } -} - .center{ text-align: center; } +.form-field{ + margin-bottom: 15px; + text-align: bottom; +} + +/* bootstrap */ + +body { + padding-top: 80px; +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: #777; + a { + color: #555; + &:hover { + color: #222; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} + .debug_dump{ width: 100%; margin-top: 30px; } + +.nav-logo{ + margin:10px; + font-size:30px; +} diff --git a/app/assets/stylesheets/callbacks.css.scss b/app/assets/stylesheets/callbacks.css.scss new file mode 100644 index 0000000..e4c4d53 --- /dev/null +++ b/app/assets/stylesheets/callbacks.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the callbacks controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/orders.css.scss b/app/assets/stylesheets/orders.css.scss index 7415069..1f7d48c 100644 --- a/app/assets/stylesheets/orders.css.scss +++ b/app/assets/stylesheets/orders.css.scss @@ -1,3 +1,30 @@ // Place all the styles related to the Orders controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +.big-form-button { + height: 65px; + width: 65px; +} + +.form-control.big-form-field { + height: 65px; + text-align: center; +} + +.form_row_image { + height: 100px; + width: 100px; + margin-left: auto; + margin-right: auto; + position: relative; + img { + position: absolute; + top: 0; + bottom: 0; + left: 0; + top: 0; + margin: auto; + } +} + diff --git a/app/assets/stylesheets/overview.css.scss b/app/assets/stylesheets/overview.css.scss new file mode 100644 index 0000000..62e4d98 --- /dev/null +++ b/app/assets/stylesheets/overview.css.scss @@ -0,0 +1,21 @@ +.overviewthumbnail { + margin-bottom: 0px; + border: 0px; + + .btn{ + margin-top: -55px; + width: 80%; + margin-left:10%; + border: 0px; + } + + .dagschotel{ + float:left; + position: absolute; + } + + .avatar{ + height: 150px; + width: 150px; + } +} diff --git a/app/assets/stylesheets/products.css.scss b/app/assets/stylesheets/products.css.scss index e77849f..9ec81e9 100644 --- a/app/assets/stylesheets/products.css.scss +++ b/app/assets/stylesheets/products.css.scss @@ -5,3 +5,20 @@ .orders .thumbnail { min-height: 200px; } + +.pic img { + padding-top: 10px; +} + +.caption{ + text-align: center; +} + +.caption h3 { + margin: 8px 0; +} + +.caption h6 { + margin-bottom: 20px; +} + diff --git a/app/assets/stylesheets/profile.css.scss b/app/assets/stylesheets/profile.css.scss new file mode 100644 index 0000000..6eab87e --- /dev/null +++ b/app/assets/stylesheets/profile.css.scss @@ -0,0 +1,73 @@ +/* User profile */ + +.user_info { + text-align: center; +} + +.user_info h2 { + line-height: 45px; + text-align: center; +} + +.user_info h5 { + margin-right: 10px; + text-align: right; +} + +.user_info .button_to input[type="submit"] { + font-size: 16px; + color: #FFF; + padding: 12px 50px 12px 50px; + border: 1px solid #999; + width: 60%; + font-family: monospace; + + text-shadow: 0px 1px 1px #666; + text-decoration: none; + + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + + background: #64a724; + background: -moz-linear-gradient(top, #64a724 0%, #579727 50%, #58982a 51%, #498c25 100%); + background: -webkit-gradient(linear, left top, left bottom, from(#64a724), to(#498c25), color-stop(0.4, #579727), color-stop(0.5, #58982a), color-stop(.9, #498c25), color-stop(0.9, #498c25)); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#64a724', endColorstr='#498c25', GradientType=0 ); + + cursor: pointer; +} + +.debt { + padding: 12px 0; + text-align: center; + font-family: monospace; + font-size: 16px; + width: 60%; + margin: auto; + margin-top: 10px; + background-color: #FF7F00; + color: white; +} + +.stats{ + width: 60%; + margin: auto; +} + +.stats h4 { + text-transform: uppercase; + font-family: monospace; + background-color: #333; + color: white; + padding: 5px 10px; + margin-top: 15px; +} + +.stats ul { + list-style-type: none; + padding: 0 0 0 15px; + margin: 0px; +} + +.stats li { +} diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss index ddc2ba5..8e82fd4 100644 --- a/app/assets/stylesheets/users.css.scss +++ b/app/assets/stylesheets/users.css.scss @@ -3,6 +3,16 @@ // You can use Sass (SCSS) here: http://sass-lang.com/ +//signin +.sign-in{ + .checkbox label{ + padding-left: 0px; + } + #user_remember_me{ + margin-left: 10px; + } +} + .users .thumbnail { padding: 0px; min-height: 280px; @@ -37,11 +47,6 @@ background-color: #f5f5dc; } - - - - - /* sidebar */ aside { @@ -69,3 +74,36 @@ aside { } } } + +table { + width: 100%; +} + +.orders { + margin-top: -10px; + line-height: 140%; + & tr:nth-child(even){ + border-bottom: 1px solid #bbb; + td { + padding-bottom: 15px; + } + } + & tr:last-child { + border-bottom: none; + } + & tr:nth-child(odd) td { + padding-top: 10px; + } + & td.order_date { + font-size: 12px; + color: #999; + } +} + +.product_dagschotel { + height: 34px; +} + +#users-table td { + vertical-align: middle; +} diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index 7bfad91..96f8c66 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -1,7 +1,9 @@ require 'csv' class AdminsController < ApplicationController + def schulden - @users = User.all + authorize! :schulden, :admins + @users = User.members respond_to do |format| format.csv do headers['Content-Disposition'] = "attachment; filename=\"zeus-schulden\"" diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2e95090..8e4e7be 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,24 +4,28 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? + rescue_from CanCan::AccessDenied do |exception| + redirect_to root_path, flash: { error: exception.message } + end + def after_sign_in_path_for(resource) root_path end def after_sign_up_path_for(resource) - new_user_session_path + root_path end protected def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) { |u| u.permit( - :nickname, :name, :last_name, :password, :password_confirmation, - :current_password, :avatar + :nickname, :password, :password_confirmation, + :avatar ) } + devise_parameter_sanitizer.for(:account_update) { |u| u.permit( :password, :password_confirmation, :current_password, :avatar ) } end - end diff --git a/app/controllers/callbacks_controller.rb b/app/controllers/callbacks_controller.rb new file mode 100644 index 0000000..bbab5e8 --- /dev/null +++ b/app/controllers/callbacks_controller.rb @@ -0,0 +1,7 @@ +class CallbacksController < Devise::OmniauthCallbacksController + def zeuswpi + @user = User.from_omniauth(request.env["omniauth.auth"]) + @user.save + sign_in_and_redirect @user + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 84a2e57..ed72a23 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,49 +1,74 @@ class OrdersController < ApplicationController + include ActionView::Helpers::NumberHelper + + load_and_authorize_resource def new - @user = User.find(params[:user_id]) + init @order = @user.orders.build - @products = Product.all - @order_products = @order.order_products - @products.each do |p| - @order_products.build(product: p) - end + # products = @user.products.select("products.*", "sum(order_items.count) as count").group(:product_id).order("count desc") + # @order.g_order_items(products) + @order.g_order_items Product.all end def create - @user = User.find(params[:user_id]) - @order = @user.orders.build(order_params) - @products = Product.all - @order_products = @order.order_products + init + @order = @user.orders.build order_params + if @order.save - flash[:success] = "Ordered things! Get your stuff!" + flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!" redirect_to root_path else + @order.g_order_items Product.all render 'new' end end - def index - @users_by_name = User.all.order(:name) - @users_by_order = User.all.order(:orders_count).reverse_order + def destroy + order = Order.find(params[:id]) + if order.created_at > 5.minutes.ago + order.cancel + flash[:success] = "Order has been removed." + else + flash[:error] = "This order has been placed too long ago, it can't be removed. Please contact a sysadmin." + end + redirect_to root_path + end + + def overview + @users = User.members.order(:nickname) end def quickpay - @user = User.find(params[:user_id]) - order = @user.orders.build - order.products << @user.dagschotel + user = User.find(params[:user_id]) + order = user.orders.build + order.order_items << OrderItem.new(count: 1, product: user.dagschotel, order: order) if order.save - flash[:success] = "Quick pay succeeded" + flash[:success] = "Quick pay succeeded. #{view_context.link_to("Undo", [user, order], method: :delete)}." else - flash[:error] = "Quick pay went wrong ... Sorry!" + flash[:error] = order.errors.full_messages.first end redirect_to root_path end private + def init + @user = User.find(params[:user_id]) + + if @user.koelkast? + flash[:error] = "Koelkast can't order things." + redirect_to root_path + end + + unless current_user.koelkast? || current_user.admin? || current_user == @user + flash[:error] = "Please don't order stuff for other people" + redirect_to root_path + end + end + def order_params - params.require(:order).permit(order_products_attributes: [:product_id, :count]) + params.require(:order).permit(order_items_attributes: [:count, :price, product_attributes: [:id]]) end end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 65b79e7..9c30afe 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -1,4 +1,6 @@ class ProductsController < ApplicationController + load_and_authorize_resource + def new @product = Product.new end @@ -6,14 +8,15 @@ class ProductsController < ApplicationController def create @product = Product.new(product_params) if @product.save - redirect_to action: :index + redirect_to products_path else - render :new + render 'new' end end def index @products = Product.all + @categories = Product.categories end def edit @@ -24,7 +27,7 @@ class ProductsController < ApplicationController @product = Product.find(params[:id]) if @product.update_attributes(product_params) flash[:success] = "Succesfully updated product" - redirect_to action: :index + redirect_to products_path else render 'edit' end @@ -36,10 +39,22 @@ class ProductsController < ApplicationController redirect_to products_path end + def stock + @products = Product.all + end + + def update_stock + @products = Product.all + @products.each do |product| + stock_inc = params[:products][product.id.to_s][:stock_inc].to_i + product.increment!(:stock, stock_inc) if stock_inc > 0 + end + redirect_to products_path + end + private def product_params - params.require(:product).permit(:name, :price, :avatar) + params.require(:product).permit(:name, :price, :avatar, :category, :stock) end - end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2bceab6..786d3f3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,23 +1,66 @@ class UsersController < ApplicationController + load_and_authorize_resource + def show - @user = User.find(params[:id]) - @orders = @user.orders.paginate(page: params[:page]) + @user = User.find_by_id(params[:id]) || current_user + @orders = @user.orders + .active + .order(:created_at) + .reverse_order + .paginate(page: params[:page]) + @products = @user.products + .select("products.*", "sum(order_items.count) as count") + .where("orders.cancelled = ?", false) + .group(:product_id) + .order("count") + .reverse_order + @categories = @user.products + .select("products.category", "sum(order_items.count) as count") + .where("orders.cancelled = ?", false) + .group(:category) end def index - @users = User.all + @users = User.members end def destroy - User.find(params[:id]).destroy + @user = User.find(params[:id]) + @user.destroy flash[:success] = "Succesfully removed user" redirect_to users_path end - def dagschotel - user = User.find(params[:user_id]) - user.dagschotel = Product.find(params[:product_id]) - user.save - redirect_to edit_user_registration_path(user) + def edit_dagschotel + @user = User.find(params[:user_id]) + @dagschotel = @user.dagschotel + + @products = Product.all + @categories = Product.categories end + + def update_dagschotel + @user = User.find(params[:user_id]) + @user.dagschotel = Product.find(params[:product_id]) + + @products = Product.all + @categories = Product.categories + + if @user.save + flash[:success] = "Succesfully updated dagschotel" + redirect_to @user + else + flash[:error] = "Error updating dagschotel" + @dagschotel = @user.reload.dagschotel + render 'edit_dagschotel' + end + + end + + private + + def init + @user = User.find(params[:user_id]) + redirect_to root_path, error: "You are not authorized to access this page." unless @user == current_user || current_user.admin? + end end diff --git a/app/form_builders/formatted_form_builder.rb b/app/form_builders/formatted_form_builder.rb new file mode 100644 index 0000000..0c415e0 --- /dev/null +++ b/app/form_builders/formatted_form_builder.rb @@ -0,0 +1,185 @@ +class FormattedFormBuilder < ActionView::Helpers::FormBuilder + include ActionView::Helpers::TextHelper + include ActionView::Context + include ActionView::Helpers::NumberHelper + + FIELD_HELPERS = %w[text_field number_field file_field password_field] + + delegate :content_tag, to: :@template + + def initialize(object_name, object, template, options) + @inline_errors = true + + super + end + + FIELD_HELPERS.each do |method_name| + with_method_name = "#{method_name}_with_format" + without_method_name = "#{method_name}_without_format" + + define_method(with_method_name) do |name, options = {}| + form_group_builder(name, options) do + send(without_method_name, name, options) + end + end + + alias_method_chain method_name, :format + end + + def price_field(name, options = {}) + options[:min] ||= 0 + options[:step] ||= 0.01 + # if object.is_a?(ActiveRecord::Base) + # options[:value] ||= object[name] + if object.respond_to?(name) + options[:value] ||= object.send name + end + options[:value] = number_with_precision(options[:value], precision: 2) + + form_group_builder(name, options) do + number_field_without_format(name, options) + end + end + + def check_box_with_format(name, options = {}, checked_value = "1", unchecked_value = "0", &block) + options.symbolize_keys! + + checkbox = check_box_without_format(name, options.except(:label), checked_value, unchecked_value) + label_content = block_given? ? capture(&block) : options[:label] + + content_tag :div, class: control_wrapper_class do + checkbox + " " + label(name, label_content) + end + end + + def counter(name, options = {}) + form_group_builder(name, options) do + counter_button("btn-dec", "glyphicon-minus") + + text_field_without_format(name, options.merge({class: 'form-control row_counter big-form-field'})) + + counter_button("btn-inc", "glyphicon-plus") + end + end + + alias_method_chain :check_box, :format + + def collection_select_with_format(name, collection, value_method = :to_s, text_method = :titlecase, options = {}, html_options = {}) + form_group_builder(name, options, html_options) do + collection_select_without_format(name, collection, value_method, text_method, options, html_options) + end + end + + alias_method_chain :collection_select, :format + + def submit_with_format(name = nil, options = {}) + options[:class] = submit_class unless options[:class] + content_tag :div, class: submit_wrapper_class do + submit_without_format(name, options) + end + end + + alias_method_chain :submit, :format + + def error_messages + if object.errors.any? + content_tag :div, class: "panel panel-danger form-errors" do + content_tag(:div, class: "panel-body") do + error_header + error_body + end + end + end + end + + def error_header + content_tag(:div, class: "panel-heading") do + "#{pluralize(object.errors.count, "error")} prohibited this #{object.class.name.downcase} from being saved:" + end + end + + def error_body + content_tag :ul do + object.errors.full_messages.map do |msg| + content_tag :li, msg + end.join.html_safe + end + end + + private + def label_class + "control-label" + end + + def control_class + "form-control" + end + + def control_wrapper_class + "form-group" + end + + def submit_class + "btn btn-primary" + end + + def submit_wrapper_class + "actions" + end + + def form_group(*args, &block) + options = args.extract_options! + name = args.first + + options[:class] = [control_wrapper_class, options[:class]].compact.join(' ') + + content_tag(:div, options.except(:label)) do + label = generate_label(name, options[:label]) if options[:label] + control = capture(&block).to_s + + if label + label + control + else + control + end + end + end + + def form_group_builder(method, options, html_options = nil) + options.symbolize_keys! + + css_options = html_options || options + css_options[:class] = [control_class, css_options[:class]].compact.join(" ") + + wrapper_class = css_options.delete(:wrapper_class) + wrapper_options = css_options.delete(:wrapper) + + form_group_options = { + class: wrapper_class + } + + if wrapper_options.is_a?(Hash) + form_group_options.merge!(wrapper_options) + end + + unless options.delete(:skip_label) + form_group_options.reverse_merge!(label: { + text: options.delete(:label), + class: label_class + }) + end + + form_group(method, form_group_options) do + yield + end + end + + def generate_label(name, options = {}) + label("#{name}:", options[:text], options) + end + + def counter_button(button, glyphicon) + content_tag :span, class: "input-group-btn" do + content_tag :button, class: "btn btn-default btn-lg #{button}", type: "button" do + content_tag :span, "", class: "glyphicon btn-lg #{glyphicon}" + end + end + end +end diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb deleted file mode 100644 index d4f7b34..0000000 --- a/app/helpers/admins_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AdminsHelper -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 251c11f..c6f6172 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -10,57 +10,11 @@ module ApplicationHelper end def euro(f) - "€#{number_with_precision f, precision: 2}" + number_to_currency(f, unit: '€') end - #tijdelijk voor layout - def koelkast(status) - @koelkast ||= status + def f_form_for(record, options = {}, &block) + options[:builder] = FormattedFormBuilder + form_for(record, options, &block) end - - # Form helpers - def form_errors(object) - render partial: "form_errors", locals: {object: object} - end - - def form_text_field(f, tag) - render partial: "form_text_field", locals: {f: f, tag: tag} - end - - def form_password_field(f, tag) - render partial: "form_password_field", locals: {f: f, tag: tag} - end - - def form_text_area(f, tag) - render partial: "form_text_area", locals: {f: f, tag: tag} - end - - def form_fancy_text_area(f, tag) - render partial: "form_fancy_text_area", locals: {f: f, tag: tag} - end - - def form_email_field(f, tag) - render partial: "form_email_field", locals: {f: f, tag: tag} - end - - def form_date_field(f, tag, id, value) - render partial: "form_date_field", locals: {f: f, tag: tag, id: id, value: value} - end - - def form_number_field(f, tag) - render partial: "form_number_field", locals: {f: f, tag: tag} - end - - def form_collection_select(f, *args) - # This line enable passing optional arguments such as include_blank to the - # partial. If nothing is passed, an empty options hash is appended. - args << {} if args.length < 5 - - render partial: "form_collection_select", locals: {f: f, args: args} - end - - def form_check_box(f, tag) - render partial: "form_check_box", locals: {f: f, tag: tag} - end - end diff --git a/app/helpers/callbacks_helper.rb b/app/helpers/callbacks_helper.rb new file mode 100644 index 0000000..6c9550c --- /dev/null +++ b/app/helpers/callbacks_helper.rb @@ -0,0 +1,2 @@ +module CallbacksHelper +end diff --git a/app/helpers/devise_helper.rb b/app/helpers/devise_helper.rb deleted file mode 100644 index 377fdca..0000000 --- a/app/helpers/devise_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -module DeviseHelper - - def devise_error_messages! - return '' if resource.errors.empty? - - messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join - sentence = I18n.t('errors.messages.not_saved', - count: resource.errors.count, - resource: resource.class.model_name.human.downcase) - - html = <<-HTML -
Welcome <%= @email %>!
- -You can confirm your account email through the link below:
- -<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb deleted file mode 100644 index f667dc1..0000000 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -Hello <%= @resource.email %>!
- -Someone has requested a link to change your password. You can do this through the link below.
- -<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
- -If you didn't request this, please ignore this email.
-Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb deleted file mode 100644 index 41e148b..0000000 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -Hello <%= @resource.email %>!
- -Your account has been locked due to an excessive number of unsuccessful sign in attempts.
- -Click the link below to unlock your account:
- -<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb deleted file mode 100644 index 34a4960..0000000 --- a/app/views/devise/passwords/edit.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -