diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 2c51e966..918178e2 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2017-04-25 04:36:49 +0530 using Haml-Lint version 0.24.0. +# on 2017-06-09 04:02:20 +0530 using Haml-Lint version 0.24.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -8,7 +8,7 @@ linters: - # Offense count: 952 + # Offense count: 945 LineLength: exclude: - "app/views/admin/campaigns/_form.html.haml" @@ -49,10 +49,11 @@ linters: - "app/views/admin/events/_voting_index.html.haml" - "app/views/admin/events/index.html.haml" - "app/views/admin/events/registrations.html.haml" - - "app/views/admin/events/reports.html.haml" - "app/views/admin/events/show.html.haml" - "app/views/admin/lodgings/_form.html.haml" - "app/views/admin/lodgings/index.html.haml" + - "app/views/admin/organizations/_form.html.haml" + - "app/views/admin/organizations/index.html.haml" - "app/views/admin/programs/_form.html.haml" - "app/views/admin/programs/show.html.haml" - "app/views/admin/questions/_form.html.haml" @@ -144,6 +145,7 @@ linters: - "app/views/layouts/_messages.html.haml" - "app/views/layouts/_navigation.html.haml" - "app/views/layouts/application.html.haml" + - "app/views/organizations/index.html.haml" - "app/views/payments/_payment.html.haml" - "app/views/proposals/_form.html.haml" - "app/views/proposals/_proposal_form.html.haml" @@ -170,7 +172,7 @@ linters: - "app/views/users/edit.html.haml" - "app/views/users/show.html.haml" - # Offense count: 222 + # Offense count: 223 InstanceVariables: exclude: - "app/views/admin/campaigns/_form.html.haml" @@ -184,6 +186,7 @@ linters: - "app/views/admin/events/_voting.html.haml" - "app/views/admin/events/_voting_index.html.haml" - "app/views/admin/lodgings/_form.html.haml" + - "app/views/admin/organizations/_form.html.haml" - "app/views/admin/questions/_questions.html.haml" - "app/views/admin/registration_periods/_form.html.haml" - "app/views/admin/reports/_all_events.html.haml" @@ -245,14 +248,11 @@ linters: - "app/views/admin/users/show.html.haml" - "app/views/users/edit.html.haml" - # Offense count: 8 + # Offense count: 4 UnnecessaryInterpolation: exclude: - "app/views/admin/conferences/_doughnut_chart.html.haml" - "app/views/admin/conferences/_recent_submissions.html.haml" - - "app/views/admin/events/reports.html.haml" - - "app/views/admin/reports/_events_with_requirements.html.haml" - - "app/views/admin/reports/_events_without_commercials.html.haml" - "app/views/proposals/_proposal_form.html.haml" - "app/views/proposals/new.html.haml" @@ -328,11 +328,10 @@ linters: - "app/views/tickets/_ticket.html.haml" - "app/views/tickets/index.html.haml" - # Offense count: 27 + # Offense count: 23 ClassesBeforeIds: exclude: - "app/views/admin/emails/index.html.haml" - - "app/views/admin/events/reports.html.haml" - "app/views/admin/events/show.html.haml" - "app/views/admin/reports/index.html.haml" - "app/views/admin/users/show.html.haml" @@ -386,6 +385,19 @@ linters: - "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/schedules/_carousel.html.haml" + # Offense count: 29 + TrailingWhitespace: + exclude: + - "app/views/admin/organizations/_form.html.haml" + - "app/views/admin/users/index.html.haml" + - "app/views/admin/users/show.html.haml" + - "app/views/admin/volunteers/index.html.haml" + - "app/views/admin/volunteers/show.html.haml" + - "app/views/conference_registrations/_volunteer.html.haml" + - "app/views/devise/passwords/new.html.haml" + - "app/views/payments/new.html.haml" + - "app/views/tickets/index.html.haml" + # Offense count: 2 FinalNewline: exclude: @@ -406,19 +418,6 @@ linters: - "app/views/schedules/_event.html.haml" - "app/views/schedules/_schedule_item.html.haml" - # Offense count: 38 - TrailingWhitespace: - exclude: - - "app/views/admin/users/_form.html.haml" - - "app/views/admin/users/index.html.haml" - - "app/views/admin/users/show.html.haml" - - "app/views/admin/volunteers/index.html.haml" - - "app/views/admin/volunteers/show.html.haml" - - "app/views/conference_registrations/_volunteer.html.haml" - - "app/views/devise/passwords/new.html.haml" - - "app/views/payments/new.html.haml" - - "app/views/tickets/index.html.haml" - # Offense count: 7 ClassAttributeWithStaticValue: exclude: diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index f9e1177d..f8c9ea19 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -76,7 +76,7 @@ module Admin def create @conference = Conference.new(conference_params) - + @conference.organization = Organization.find_or_create_by(name: 'organization') if @conference.save # user that creates the conference becomes organizer of that conference current_user.add_role :organizer, @conference @@ -211,7 +211,7 @@ module Admin :vpositions_attributes, :use_volunteers, :color, :sponsorship_levels_attributes, :sponsors_attributes, :targets, :targets_attributes, - :campaigns, :campaigns_attributes, :registration_limit) + :campaigns, :campaigns_attributes, :registration_limit, :organization_id) end end end diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb new file mode 100644 index 00000000..087bf7f8 --- /dev/null +++ b/app/controllers/admin/organizations_controller.rb @@ -0,0 +1,52 @@ +module Admin + class OrganizationsController < Admin::BaseController + load_and_authorize_resource :organization + + def index + @organizations = Organization.all + end + + def create + @organization = Organization.new(organization_params) + if @organization.save + redirect_to admin_organizations_path, + notice: 'Organization successfully created' + else + redirect_to new_admin_organization_path, + error: @organization.errors.full_messages.join(', ') + end + end + + def new + @organization = Organization.new + end + + def edit; end + + def update + if @organization.update_attributes(organization_params) + redirect_to admin_organizations_path, + notice: 'Organization successfully updated' + else + redirect_to edit_admin_organization_path(@organization), + error: @organization.errors.full_messages.join(', ') + end + end + + def destroy + if @organization.destroy + redirect_to admin_organizations_path, + notice: 'Organization successfully destroyed' + else + redirect_to admin_organizations_path, + error: 'Organization cannot be destroyed' + end + end + + private + + def organization_params + params.require(:organization).permit(:name, :description, :picture) + end + end +end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb new file mode 100644 index 00000000..3d549dfa --- /dev/null +++ b/app/controllers/organizations_controller.rb @@ -0,0 +1,7 @@ +class OrganizationsController < ApplicationController + load_and_authorize_resource :organization + + def index + @organizations = Organization.all + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index ff1628a1..dba29fa7 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -83,6 +83,7 @@ class Ability conference.registration_open? && !conference.registration_limit_exceeded? || conference.program.speakers.confirmed.include?(user) end + can :index, Organization can :index, Ticket can :manage, TicketPurchase, user_id: user.id can [:new, :create], Payment, user_id: user.id diff --git a/app/models/conference.rb b/app/models/conference.rb index 2ae6eabd..f58c50ed 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -7,6 +7,8 @@ class Conference < ActiveRecord::Base default_scope { order('start_date DESC') } + belongs_to :organization + has_paper_trail ignore: %i(updated_at guid revision events_per_week), meta: { conference_id: :id } has_and_belongs_to_many :questions @@ -53,7 +55,8 @@ class Conference < ActiveRecord::Base :start_date, :end_date, :start_hour, - :end_hour, presence: true + :end_hour, + :organization, presence: true validates :short_title, uniqueness: true validates :short_title, format: { with: /\A[a-zA-Z0-9_-]*\z/ } diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 00000000..60a83fef --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,7 @@ +class Organization < ActiveRecord::Base + has_many :conferences, dependent: :destroy + + validates :name, presence: true + + mount_uploader :picture, PictureUploader, mount_on: :picture +end diff --git a/app/views/admin/organizations/_form.html.haml b/app/views/admin/organizations/_form.html.haml new file mode 100644 index 00000000..c83facbb --- /dev/null +++ b/app/views/admin/organizations/_form.html.haml @@ -0,0 +1,13 @@ += semantic_form_for(@organization, url: (@organization.new_record? ? admin_organizations_path : admin_organization_path(@organization))) do |f| + = f.inputs name: 'Organization details' do + = f.input :name, as: :string, required: true + = f.input :description, as: :text, input_html: { rows: 10 }, placeholder: 'Decribe about your organization..' + = image_tag f.object.picture.thumb.url if f.object.picture? + - if @organization.picture + = image_tag(@organization.picture.thumb.url, width: '20%') + = f.input :picture + %p.text-right + - if @organization.new_record? + = f.submit 'Create Organization', class: 'btn btn-success' + - else + = f.submit 'Update Organization', class: 'btn btn-success' diff --git a/app/views/admin/organizations/index.html.haml b/app/views/admin/organizations/index.html.haml new file mode 100644 index 00000000..03bf11f9 --- /dev/null +++ b/app/views/admin/organizations/index.html.haml @@ -0,0 +1,31 @@ +.row + .col-md-12 + .page-header + %h1 Organizations + .btn-group.pull-right + = link_to 'Create Organization', new_admin_organization_path, class: 'btn btn-success pull-right' + %p.text-muted + Manage organizations in OSEM + .row + .col-md-12 + %table.table.table-hover.datatable + %thead + %th Name + %th Upcoming Conferences + %th Past Conferences + %th Actions + %tbody + - @organizations.each do |organization| + %tr + %td + = organization.name + %td + = organization.conferences.count + %td + = organization.conferences.count + %td + .btn-group + = link_to 'Edit', edit_admin_organization_path(organization), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_organization_path(organization), + method: :delete, class: 'btn btn-danger', data: { confirm: "Warning: This will delete #{organization.name} and all its data which includes data for all conferences within #{organization.name}. Do you really want to continue?" } diff --git a/app/views/layouts/_admin_sidebar_index.html.haml b/app/views/layouts/_admin_sidebar_index.html.haml index 4ae56dce..3844339b 100644 --- a/app/views/layouts/_admin_sidebar_index.html.haml +++ b/app/views/layouts/_admin_sidebar_index.html.haml @@ -32,3 +32,8 @@ = link_to(admin_revision_history_path) do %span.fa.fa-history Revision History + - if ENV['ORGANIZATIONS_ENABLED'] == 'true' + %li + = link_to(admin_organizations_path) do + %span.fa.fa-group + Organizations diff --git a/app/views/layouts/_user_menu.html.haml b/app/views/layouts/_user_menu.html.haml index 3fb32273..01432b3c 100644 --- a/app/views/layouts/_user_menu.html.haml +++ b/app/views/layouts/_user_menu.html.haml @@ -49,3 +49,8 @@ = link_to(admin_revision_history_path) do %span.fa.fa-history Revision History + - if ENV['ORGANIZATIONS_ENABLED'] == 'true' + %li + = link_to(admin_organizations_path) do + %span.fa.fa-group + Organizations diff --git a/app/views/organizations/index.html.haml b/app/views/organizations/index.html.haml new file mode 100644 index 00000000..b2794bd8 --- /dev/null +++ b/app/views/organizations/index.html.haml @@ -0,0 +1,16 @@ +.container + .row + .col-md-12.page-header + %h1 + Organizations + .btn-group.pull-right + / = link_to 'Add new', new_organization_path, class: 'btn btn-mini btn-success' + - @organizations.each do |organization| + .col-md-4 + .thumbnail + = image_tag(organization.picture.thumb.url, width: '20%') + .caption + %h4 + = organization.name + %button.btn.btn-success Conferences + / = link_to 'Edit', edit_organization_path(organization), class: 'btn btn-mini btn-default' diff --git a/config/routes.rb b/config/routes.rb index 9fa1c29a..b10d5d9c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,6 +18,7 @@ Osem::Application.routes.draw do resources :users, except: [:new, :index, :create, :destroy] namespace :admin do + resources :organizations resources :users do member do patch :toggle_confirmation @@ -102,7 +103,7 @@ Osem::Application.routes.draw do get '/revision_history/:id/revert_object' => 'versions#revert_object', as: 'revision_history_revert_object' get '/revision_history/:id/revert_attribute' => 'versions#revert_attribute', as: 'revision_history_revert_attribute' end - + resources :organizations, only: [:index] resources :conferences, only: [:index, :show] do resource :program, only: [] do resources :proposals, except: :destroy do diff --git a/db/migrate/20170529215453_create_organizations.rb b/db/migrate/20170529215453_create_organizations.rb new file mode 100644 index 00000000..0fa5450b --- /dev/null +++ b/db/migrate/20170529215453_create_organizations.rb @@ -0,0 +1,9 @@ +class CreateOrganizations < ActiveRecord::Migration + def change + create_table :organizations do |t| + t.string :name, null: false + t.text :description + t.string :picture + end + end +end diff --git a/db/migrate/20170531094819_move_conferences_to_organizations.rb b/db/migrate/20170531094819_move_conferences_to_organizations.rb new file mode 100644 index 00000000..e70acf16 --- /dev/null +++ b/db/migrate/20170531094819_move_conferences_to_organizations.rb @@ -0,0 +1,24 @@ +class MoveConferencesToOrganizations < ActiveRecord::Migration + class TempConference < ActiveRecord::Base + self.table_name = 'conferences' + end + + class TempOrganization < ActiveRecord::Base + self.table_name = 'organizations' + end + + def change + add_reference :conferences, :organization, index: true + + TempConference.reset_column_information + if TempConference.count != 0 + organization = TempOrganization.create(name: 'organization', description: 'Default organization') + TempConference.all.each do |conference| + conference.organization_id = organization.id + conference.save! + end + end + + add_foreign_key :conferences, :organizations, null: false, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 5d74950a..9772066a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170419132148) do +ActiveRecord::Schema.define(version: 20170531094819) do create_table "ahoy_events", force: :cascade do |t| t.uuid "visit_id", limit: 16 @@ -100,8 +100,11 @@ ActiveRecord::Schema.define(version: 20170419132148) do t.string "picture" t.integer "start_hour", default: 9 t.integer "end_hour", default: 20 + t.integer "organization_id" end + add_index "conferences", ["organization_id"], name: "index_conferences_on_organization_id" + create_table "conferences_questions", id: false, force: :cascade do |t| t.integer "conference_id" t.integer "question_id" @@ -266,6 +269,12 @@ ActiveRecord::Schema.define(version: 20170419132148) do t.datetime "updated_at" end + create_table "organizations", force: :cascade do |t| + t.string "name", null: false + t.text "description" + t.string "picture" + end + create_table "payments", force: :cascade do |t| t.string "last4" t.integer "amount" diff --git a/spec/controllers/admin/organizations_controller_spec.rb b/spec/controllers/admin/organizations_controller_spec.rb new file mode 100644 index 00000000..a4afc994 --- /dev/null +++ b/spec/controllers/admin/organizations_controller_spec.rb @@ -0,0 +1,171 @@ +require 'spec_helper' + +describe Admin::OrganizationsController do + let!(:admin) { create(:admin) } + let!(:organization) { create(:organization) } + let!(:user) { create(:user) } + + context 'logged in as user with no role' do + before :each do + sign_in user + end + + describe 'GET #new' do + before :each do + get :new + end + + it 'redirects to root' do + expect(flash[:alert]).to eq('You are not authorized to access this area!') + expect(response).to redirect_to(root_path) + end + end + + describe 'GET #index' do + before :each do + get :index + end + + it 'redirects to root' do + expect(flash[:alert]).to eq('You are not authorized to access this area!') + expect(response).to redirect_to(root_path) + end + end + + describe 'POST #create' do + it 'does not create new organization' do + expected = expect do + post :create, organization: attributes_for(:organization) + end + expected.to_not change(Organization, :count) + end + + it 'redirects to root' do + post :create, organization: attributes_for(:organization) + + expect(flash[:alert]).to eq('You are not authorized to access this area!') + expect(response).to redirect_to(root_path) + end + end + + describe 'PATCH #update' do + it 'does not update and redirects to root' do + old_name = organization.name + patch :update, id: organization.id, organization: attributes_for(:organization, name: 'new name') + + organization.reload + expect(organization.name).to eq(old_name) + expect(flash[:alert]).to eq('You are not authorized to access this area!') + expect(response).to redirect_to(root_path) + end + end + + describe 'DELETE #destroy' do + context 'for a valid organization' do + it 'does not destroy a resource' do + expected = expect do + delete :destroy, id: organization.id + end + expected.to_not change(Organization, :count) + end + + it 'redirects to root' do + delete :destroy, id: organization.id + + expect(flash[:alert]).to eq('You are not authorized to access this area!') + expect(response).to redirect_to(root_path) + end + end + end + end + + context 'logged in as admin' do + before :each do + sign_in admin + end + + describe 'GET #new' do + before do + get :new + end + it { expect(response).to render_template('new') } + end + + describe 'GET #index' do + before do + get :index + end + it { expect(response).to render_template('index') } + end + + describe 'POST #create' do + context 'with valid attributes' do + it 'creates new organization' do + expected = expect do + post :create, organization: attributes_for(:organization) + end + expected.to change { Organization.count }.by(1) + end + + it 'redirects to index' do + post :create, organization: attributes_for(:organization) + + expect(flash[:notice]).to eq('Organization successfully created') + expect(response).to redirect_to(admin_organizations_path) + end + end + + context 'with invalid attributes' do + it 'does not create new organization' do + expected = expect do + post :create, organization: attributes_for(:organization, name: '') + end + expected.to_not change(Organization, :count) + end + + it 'redirects to new' do + post :create, organization: attributes_for(:organization, name: '') + + expect(flash[:error]).to eq("Name can't be blank") + expect(response).to redirect_to(new_admin_organization_path) + end + end + end + + describe 'PATCH #update' do + it 'saves and redirects to index when the attributes are valid' do + patch :update, id: organization.id, organization: attributes_for(:organization, name: 'changed name') + + organization.reload + expect(organization.name).to eq('changed name') + expect(flash[:notice]).to eq('Organization successfully updated') + expect(response).to redirect_to(admin_organizations_path) + end + + it 'redirects to edit when attributes are invalid' do + patch :update, id: organization.id, organization: attributes_for(:organization, name: '') + + expect(flash[:error]).to eq("Name can't be blank") + expect(response).to redirect_to(edit_admin_organization_path(organization)) + end + end + + describe 'DELETE #destroy' do + context 'for a valid organization' do + it 'should successfully destroy a resource' do + expected = expect do + delete :destroy, id: organization.id + end + expected.to change { Organization.count }.by(-1) + end + + it 'redirects to index' do + delete :destroy, id: organization.id + + expect(flash[:notice]).to eq('Organization successfully destroyed') + expect(response).to redirect_to(admin_organizations_path) + end + end + end + end +end diff --git a/spec/controllers/organizations_controller_spec.rb b/spec/controllers/organizations_controller_spec.rb new file mode 100644 index 00000000..2916fdea --- /dev/null +++ b/spec/controllers/organizations_controller_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe OrganizationsController do + let!(:organization) { create(:organization) } + let!(:user) { create(:user) } + + describe 'GET #index' do + before :each do + sign_in user + get :index + end + + it { expect(response).to render_template('index') } + end +end diff --git a/spec/factories/conferences.rb b/spec/factories/conferences.rb index 8a9186a6..e1ef7d84 100644 --- a/spec/factories/conferences.rb +++ b/spec/factories/conferences.rb @@ -11,7 +11,7 @@ FactoryGirl.define do end_hour 20 registration_limit 0 description { Faker::Hipster.paragraph } - + organization after(:create) do |conference| Role.where(name: 'organizer', resource: conference).first_or_create(description: 'For the organizers of the conference (who shall have full access)') Role.where(name: 'cfp', resource: conference).first_or_create(description: 'For the members of the CfP team') diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb new file mode 100644 index 00000000..0ee76554 --- /dev/null +++ b/spec/factories/organizations.rb @@ -0,0 +1,13 @@ +FactoryGirl.define do + factory :organization do + name { Faker::Company.name } + description { Faker::Lorem.paragraph } + + # after(:create) do |organization| + # File.open("spec/support/logos/#{1 + rand(13)}.png") do |file| + # organization.picture = file + # end + # organization.save! + # end + end +end diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index b5a6d5f0..9bdbea83 100755 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -1630,7 +1630,7 @@ describe Conference do end describe 'after_create' do - let(:conference) { Conference.new(title: 'ABC', short_title: 'XYZ', start_date: Date.today, end_date: Date.today + 10, timezone: 'GMT') } + let(:conference) { create(:conference) } it 'calls back to create free ticket' do conference.save diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb new file mode 100644 index 00000000..ca84f532 --- /dev/null +++ b/spec/models/organization_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Organization do + let(:organization) { create(:organization) } + + describe 'validation' do + it 'is not valid without a name' do + should validate_presence_of(:name) + end + end + + describe 'associations' do + it { should have_many(:conferences).dependent(:destroy) } + end +end