├── .github └── workflows │ ├── rubocop.yml │ └── spec.yml ├── .gitignore ├── .irbrc ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .ruby-version ├── CREDITS.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── avatars │ │ │ ├── chisel.svg │ │ │ ├── chisel_original.png │ │ │ ├── chisel_thumb.png │ │ │ ├── drill.svg │ │ │ ├── drill_original.png │ │ │ ├── drill_thumb.png │ │ │ ├── hammer.svg │ │ │ ├── hammer_original.png │ │ │ ├── hammer_thumb.png │ │ │ ├── hand-file.svg │ │ │ ├── hand-file_original.png │ │ │ ├── hand-file_thumb.png │ │ │ ├── hand-plane.svg │ │ │ ├── hand-plane_original.png │ │ │ ├── hand-plane_thumb.png │ │ │ ├── pliers.svg │ │ │ ├── pliers_original.png │ │ │ ├── pliers_thumb.png │ │ │ ├── ruler.svg │ │ │ ├── ruler_original.png │ │ │ ├── ruler_thumb.png │ │ │ ├── saw.svg │ │ │ ├── saw_original.png │ │ │ ├── saw_thumb.png │ │ │ ├── screwdriver.svg │ │ │ ├── screwdriver_original.png │ │ │ ├── screwdriver_thumb.png │ │ │ ├── wrench.svg │ │ │ ├── wrench_original.png │ │ │ └── wrench_thumb.png │ │ ├── favicon.gif │ │ ├── hackweek-cameleon.svg │ │ ├── hackweek-label-small.png │ │ ├── hackweek-label-square.png │ │ ├── hackweek-label.png │ │ ├── hackweek-logo-dark.icon.png │ │ ├── hackweek-logo-dark.png │ │ ├── hackweek-logo-light.png │ │ ├── hackweek_monsterhacks.png │ │ ├── monsterhacks_monsters_only.png │ │ ├── suse-logo-black.png │ │ ├── suse-logo-color.png │ │ ├── suse-logo-sketch.png │ │ ├── suse-logo-white.png │ │ ├── suse_logo_w-tag_black.png │ │ └── suse_mark_reverse.png │ ├── javascripts │ │ ├── application.js │ │ ├── channels │ │ │ └── .keep │ │ ├── holder.js │ │ ├── jquery.table-filter.js │ │ └── zoomed.application.js │ ├── stylesheets │ │ ├── application.css │ │ ├── comments.scss │ │ ├── hackweek.scss │ │ ├── strap-on.scss │ │ ├── syntax.scss.erb │ │ └── webfonts.scss │ └── videos │ │ ├── Hackweek_Welcome_by_Ralf.mp4 │ │ └── Hackweek_Welcome_by_Ralf.webm ├── controllers │ ├── about_controller.rb │ ├── announcements_controller.rb │ ├── application_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ └── .keep │ ├── episodes_controller.rb │ ├── faqs_controller.rb │ ├── keywords_controller.rb │ ├── markdown_controller.rb │ ├── notifications_controller.rb │ ├── projects │ │ └── project_follows_controller.rb │ ├── projects_controller.rb │ ├── search_controller.rb │ ├── updates_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ └── markdown_helper.rb ├── indices │ ├── project_index.rb │ └── user_index.rb ├── jobs │ └── application_job.rb ├── models │ ├── ability.rb │ ├── announcement.rb │ ├── application_record.rb │ ├── comment.rb │ ├── enrollment.rb │ ├── episode.rb │ ├── faq.rb │ ├── keyword.rb │ ├── like.rb │ ├── membership.rb │ ├── notification.rb │ ├── project.rb │ ├── project_follow.rb │ ├── role.rb │ ├── update.rb │ └── user.rb └── views │ ├── about │ ├── index.html.haml │ └── show.html.haml │ ├── announcements │ ├── _announcement.html.haml │ ├── _announcement_toggle.js.erb │ ├── _file_buttons.html.haml │ ├── _form.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── new.html.haml │ └── show.html.haml │ ├── comments │ ├── _comment.html.haml │ ├── _form.html.haml │ ├── _help.html.haml │ └── index.html.haml │ ├── devise │ └── ichain_sessions │ │ ├── new.html.haml │ │ ├── new.js.erb │ │ ├── new_test.html.haml │ │ └── new_test.js.erb │ ├── episodes │ ├── _file_buttons.html.haml │ ├── _form.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── new.html.haml │ └── show.html.haml │ ├── faqs │ ├── _form.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ └── new.html.haml │ ├── kaminari │ ├── _first_page.html.haml │ ├── _gap.html.haml │ ├── _last_page.html.haml │ ├── _next_page.html.haml │ ├── _page.html.haml │ ├── _paginator.html.haml │ └── _prev_page.html.haml │ ├── keywords │ ├── _new.html.haml │ ├── _show.html.haml │ ├── edit.html.haml │ └── index.html.haml │ ├── layouts │ ├── _admin_menu.html.haml │ ├── _alert.html.haml │ ├── _footer.html.haml │ ├── _header.html.haml │ ├── _news.html.haml │ ├── _notification_tab.html.haml │ ├── _scripts.html.haml │ └── application.html.haml │ ├── markdown │ ├── _preview.html.haml │ └── preview.js.erb │ ├── notifications │ ├── index.html.haml │ └── mark_as_read.js.erb │ ├── projects │ ├── _episode_buttons.html.haml │ ├── _episode_list.html.haml │ ├── _file_buttons.html.haml │ ├── _form.html.haml │ ├── _hackers.html.haml │ ├── _index.html.haml │ ├── _info.html.haml │ ├── _like_button.html.haml │ ├── _like_toggle.js.erb │ ├── _list.html.haml │ ├── _list_item.html.haml │ ├── _membership_buttons.html.haml │ ├── _similar_projects.html.haml │ ├── _state_name.html.haml │ ├── _tabs.html.haml │ ├── _tile.html.haml │ ├── edit.html.haml │ ├── episode_list.js.erb │ ├── index.html.haml │ ├── index.js.erb │ ├── index.rss.haml │ ├── membership_list.js.erb │ ├── new.html.haml │ ├── project_follows │ │ ├── follow_toggle.js.erb │ │ └── index.html.haml │ └── show.html.haml │ ├── search │ └── result.html.haml │ ├── shared │ └── _editor_buttons.html.haml │ ├── updates │ ├── _activity.html.haml │ ├── _more.html.haml │ ├── _show.html.haml │ ├── _show_all_modal.html.haml │ └── index.js.erb │ └── users │ ├── _buttons.html.haml │ ├── _empty_projects.html.haml │ ├── _list.html.haml │ ├── _tabs.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── index.js.erb │ └── show.html.haml ├── bin ├── bundle ├── mina ├── rails ├── rake └── rspec ├── config.ru ├── config ├── application.rb ├── application.yml.example ├── boot.rb ├── database.yml.example ├── deploy.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── content_security_policy.rb │ ├── custom_failure.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── markdown.rb │ └── sentry.rb ├── locales │ └── en.yml ├── new_project_template.md ├── puma.rb ├── routes.rb ├── secrets.yml.example ├── storage.yml.example └── thinking_sphinx.yml.example ├── db ├── migrate │ ├── 20130408082936_create_projects.rb │ ├── 20130408132732_create_users.rb │ ├── 20130408142933_relate_project_to_user.rb │ ├── 20130409093521_create_updates.rb │ ├── 20130409110644_create_memberships.rb │ ├── 20130410175713_create_comments.rb │ ├── 20130411152323_create_likes.rb │ ├── 20130412090255_create_keywords.rb │ ├── 20130412090618_create_project_interests.rb │ ├── 20130412091534_create_user_interests.rb │ ├── 20130906125641_add_project_counter_cache.rb │ ├── 20130912160959_add_devise_to_users.rb │ ├── 20130919165239_add_aasm_state.rb │ ├── 20131010095502_create_announcements.rb │ ├── 20131010100037_create_enrollments.rb │ ├── 20131011111909_create_roles.rb │ ├── 20131011112148_user_belong_to_roles.rb │ ├── 20131011123003_create_events.rb │ ├── 20131011123206_project_belong_to_events.rb │ ├── 20140829132526_add_active_to_events.rb │ ├── 20140829151952_rename_events_to_episode.rb │ ├── 20140903092848_rename_episode_start_end.rb │ ├── 20140910105916_add_avatar_columns_to_projects.rb │ ├── 20150319172246_model_for_episodes_projects_association.rb │ ├── 20170221144704_add_url_to_projects.rb │ ├── 20180305183055_add_location_to_user.rb │ ├── 20180619192048_create_project_follows.rb │ ├── 20180702104632_create_notifications.rb │ ├── 20180712105306_add_description_to_episodes.rb │ ├── 20180730125036_create_impressions_table.rb │ ├── 20180730151836_add_projecthits_to_projects.rb │ ├── 20180820211721_create_faqs.rb │ ├── 20210303201638_add_hide_email_to_user.rb │ ├── 20210312230516_rename_project_interest.rb │ ├── 20210312232604_rename_user_interest.rb │ ├── 20210312233011_rename_memberships.rb │ ├── 20210314175543_rename_memberships_back.rb │ ├── 20210315195708_add_avatar_to_keywords.rb │ ├── 20210315195817_add_description_to_keywords.rb │ ├── 20220113162506_create_active_storage_tables.active_storage.rb │ └── 20220201134703_convert_to_active_storage.rb ├── schema.rb └── seeds.rb ├── docker-compose.override.yml.example ├── docker-compose.yml ├── lib └── tasks │ ├── .keep │ └── dev.rake ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── hackweek21poster.jpg └── robots.txt ├── spec ├── controllers │ ├── episodes_controller_spec.rb │ ├── markdown_controller_spec.rb │ ├── notifications_controller_spec.rb │ ├── projects │ │ └── project_follows_controller_spec.rb │ ├── projects_controller_spec.rb │ └── users_controller_spec.rb ├── factories │ ├── comments.rb │ ├── episodes.rb │ ├── notifications.rb │ ├── projects.rb │ ├── roles.rb │ └── users.rb ├── features │ ├── collaboration_spec.rb │ ├── comment_spec.rb │ ├── project_management_spec.rb │ └── search_spec.rb ├── helpers │ └── markdown_helper_spec.rb ├── models │ ├── ability_spec.rb │ ├── comment_spec.rb │ ├── episode_spec.rb │ └── project_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support │ ├── login_macros.rb │ └── sphinx_helpers.rb ├── storage └── .keep └── vendor └── .keep /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: Rubocop 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | rubocop: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | bundler-cache: true 17 | - run: bundle exec rubocop 18 | -------------------------------------------------------------------------------- /.github/workflows/spec.yml: -------------------------------------------------------------------------------- 1 | name: Specs 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | spec: 12 | runs-on: ubuntu-latest 13 | name: spec 14 | env: 15 | RAILS_ENV: test 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: ruby/setup-ruby@v1 19 | with: 20 | bundler-cache: true 21 | - name: Prepare spec 22 | run: | 23 | sudo systemctl start mysql.service 24 | wget -nv http://sphinxsearch.com/files/dicts/en.pak 25 | bundle exec rake dev:bootstrap --trace 26 | bundle exec bin/rake webdrivers:chromedriver:update 27 | - name: Run tests 28 | run: bundle exec rspec --color --format documentation 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | /vendor/bundle 10 | 11 | # Ignore the default SQLite database. 12 | /db/*.sqlite3 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp 17 | 18 | # Ignore the configuration files 19 | /config/application.yml 20 | /config/secrets.yml 21 | /config/master.key 22 | /config/*.enc 23 | /config/database.yml 24 | /config/storage.yml 25 | /config/thinking_sphinx.yml 26 | docker-compose.override.yml 27 | 28 | # Ignore sphinx search db 29 | /db/sphinx/ 30 | /sphinx/db 31 | /sphinx/binlog 32 | /sphinx/pids/*.pid 33 | 34 | # Ignore sphinx dictionary 35 | en.pak 36 | 37 | # Ignore sphinx autogenerated configs 38 | /config/*.sphinx.conf 39 | 40 | /public/system/* 41 | /storage/* 42 | 43 | # Ignore the coverage files 44 | /coverage 45 | 46 | # All the other crap that fits nowhere specifically 47 | *~ 48 | *.bak 49 | *.tmp 50 | .*.sw* 51 | .directory 52 | .o 53 | .project 54 | .so 55 | nbproject 56 | *.vim 57 | *.pid 58 | *.iml 59 | *.ipr 60 | *.iws 61 | .idea/ 62 | .vagrant/ 63 | -------------------------------------------------------------------------------- /.irbrc: -------------------------------------------------------------------------------- 1 | require 'irb/completion' 2 | require 'irb/ext/save-history' 3 | 4 | ARGV.concat ['--readline', '--prompt-mode', 'simple'] 5 | 6 | # 500 entries in the list 7 | IRB.conf[:SAVE_HISTORY] = 500 8 | 9 | # Store results in home directory with specified file name 10 | IRB.conf[:HISTORY_FILE] = "#{Dir.home}/.irb_history" 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | require: 3 | - rubocop-rails 4 | - rubocop-rspec 5 | - rubocop-capybara 6 | - rubocop-factory_bot 7 | 8 | AllCops: 9 | NewCops: enable 10 | Exclude: 11 | - 'archive/**/*' 12 | - 'vendor/**/*' 13 | - 'design/**/*' 14 | - 'tools/**/*' 15 | - 'db/schema.rb' 16 | 17 | Style/PercentLiteralDelimiters: 18 | Exclude: 19 | - 'config/deploy.rb' 20 | 21 | Style/HashSyntax: 22 | EnforcedShorthandSyntax: either 23 | 24 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.8 2 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | ### Icons 2 | * Hammer icon used is by Christopher T. Howlett from the [Noun Project](http://thenounproject.com/). 3 | * Hand-Plane, Chisel and Saw icons used are by Guvnor Co from the [Noun Project](http://thenounproject.com/). 4 | * Screwdriver icon used is by Tony Gines from the [Noun Project](http://thenounproject.com/). 5 | * Drill icon used is by James Keuning from the [Noun Project](http://thenounproject.com/). 6 | * Ruler icon used is by Chris Kerr from the [Noun Project](http://thenounproject.com/). 7 | * Wrench icon used is by John Caserta from the [Noun Project](http://thenounproject.com/). 8 | * Pliers icon used is by Andres Arenas from the [Noun Project](http://thenounproject.com/). 9 | * Hand-File icon used is by Luke Anthony Firth from the [Noun Project](http://thenounproject.com/). 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.opensuse.org/opensuse/infrastructure/dale/containers/hackweek/base:latest 2 | ARG CONTAINER_USERID=1000 3 | 4 | # Configure our user 5 | RUN usermod -u $CONTAINER_USERID hackweek 6 | 7 | # We copy the Gemfiles into this intermediate build stage so it's checksum 8 | # changes and all the subsequent stages (a.k.a. the bundle install call below) 9 | # have to be rebuild. Otherwise, after the first build of this image, 10 | # docker would use it's cache for this and the following stages. 11 | ADD Gemfile /hackweek/Gemfile 12 | ADD Gemfile.lock /hackweek/Gemfile.lock 13 | RUN chown -R hackweek /hackweek 14 | 15 | WORKDIR /hackweek 16 | USER hackweek 17 | 18 | # Install our bundle & process manager 19 | RUN bundle install --jobs=3 --retry=3; \ 20 | gem install foreman 21 | 22 | # Run our command 23 | CMD ["foreman", "start", "-f", "Procfile"] 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # as framework 5 | gem 'rails', '~> 7.0.1' 6 | # as asset pipeline 7 | gem 'sprockets-rails' 8 | # as the app server 9 | gem 'puma' 10 | 11 | group :development, :test do 12 | # as our rails console 13 | gem 'pry-byebug' 14 | gem 'pry-rails' 15 | # to improve inspect output 16 | gem 'hirb' 17 | end 18 | 19 | group :test do 20 | # for cleaning the test DB 21 | gem 'database_cleaner' 22 | # for measuring test coverage 23 | gem 'coveralls', require: false 24 | # as style hound 25 | gem 'rubocop' 26 | gem 'rubocop-capybara' 27 | gem 'rubocop-factory_bot' 28 | gem 'rubocop-rails' 29 | gem 'rubocop-rspec' 30 | # for time travel in tests 31 | gem 'timecop' 32 | # for feature tests 33 | gem 'webdrivers' 34 | end 35 | 36 | # as databases 37 | gem 'mysql2' 38 | # for stylesheets 39 | gem 'sass-rails' 40 | # as the front-end framework 41 | gem 'bootstrap-sass' 42 | # as vector icons 43 | gem 'font-awesome-sass' 44 | # as compressor for JavaScript assets 45 | gem 'uglifier', '>= 1.3.0' 46 | # as JavaScript library 47 | gem 'jquery-atwho-rails' 48 | gem 'jquery-hotkeys-rails' 49 | gem 'jquery-rails' 50 | gem 'js_cookie_rails' 51 | # as templating language 52 | gem 'haml-rails' 53 | # as authentification framework 54 | gem 'devise' 55 | gem 'devise_ichain_authenticatable' 56 | # as authorization framework 57 | gem 'cancancan' 58 | # for user avatars 59 | gem 'gravtastic' 60 | # for markdown rendering 61 | gem 'redcarpet' 62 | # for code block syntax highlighting 63 | gem 'rouge' 64 | # for token input 65 | gem 'selectize-rails' 66 | # as state machine 67 | gem 'aasm' 68 | # as exception notifier 69 | gem 'sentry-rails' 70 | gem 'sentry-ruby' 71 | # to set env variables 72 | gem 'figaro' 73 | # for keyboard shortcuts 74 | gem 'mousetrap-rails' 75 | # as search engine 76 | gem 'thinking-sphinx' 77 | # for pagination 78 | gem 'kaminari' 79 | # for slugs 80 | gem 'stringex' 81 | # for seeds 82 | gem 'factory_bot_rails', group: %i[development test] 83 | gem 'faker', group: %i[development test] 84 | # as test framework 85 | gem 'capybara', group: %i[development test] 86 | gem 'rails-controller-testing', group: %i[development test] 87 | gem 'rspec-rails', group: %i[development test] 88 | # as deployer 89 | gem 'mina' 90 | # as the log formater 91 | gem 'lograge' 92 | # for listening to file modifications 93 | gem 'listen' 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 SUSE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec bin/rails server -b 0.0.0.0 2 | search: bundle exec bin/rake ts:rebuild NODETACH=true 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/avatars/chisel_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/chisel_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/chisel_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/chisel_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/drill_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/drill_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/drill_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/drill_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hammer.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/avatars/hammer_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hammer_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hammer_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hammer_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hand-file.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/avatars/hand-file_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hand-file_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hand-file_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hand-file_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hand-plane_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hand-plane_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/hand-plane_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/hand-plane_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/pliers_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/pliers_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/pliers_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/pliers_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/ruler.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/avatars/ruler_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/ruler_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/ruler_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/ruler_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/saw_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/saw_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/saw_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/saw_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/screwdriver.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/avatars/screwdriver_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/screwdriver_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/screwdriver_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/screwdriver_thumb.png -------------------------------------------------------------------------------- /app/assets/images/avatars/wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/avatars/wrench_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/wrench_original.png -------------------------------------------------------------------------------- /app/assets/images/avatars/wrench_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/avatars/wrench_thumb.png -------------------------------------------------------------------------------- /app/assets/images/favicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/favicon.gif -------------------------------------------------------------------------------- /app/assets/images/hackweek-label-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-label-small.png -------------------------------------------------------------------------------- /app/assets/images/hackweek-label-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-label-square.png -------------------------------------------------------------------------------- /app/assets/images/hackweek-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-label.png -------------------------------------------------------------------------------- /app/assets/images/hackweek-logo-dark.icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-logo-dark.icon.png -------------------------------------------------------------------------------- /app/assets/images/hackweek-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-logo-dark.png -------------------------------------------------------------------------------- /app/assets/images/hackweek-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek-logo-light.png -------------------------------------------------------------------------------- /app/assets/images/hackweek_monsterhacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/hackweek_monsterhacks.png -------------------------------------------------------------------------------- /app/assets/images/monsterhacks_monsters_only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/monsterhacks_monsters_only.png -------------------------------------------------------------------------------- /app/assets/images/suse-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse-logo-black.png -------------------------------------------------------------------------------- /app/assets/images/suse-logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse-logo-color.png -------------------------------------------------------------------------------- /app/assets/images/suse-logo-sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse-logo-sketch.png -------------------------------------------------------------------------------- /app/assets/images/suse-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse-logo-white.png -------------------------------------------------------------------------------- /app/assets/images/suse_logo_w-tag_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse_logo_w-tag_black.png -------------------------------------------------------------------------------- /app/assets/images/suse_mark_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/images/suse_mark_reverse.png -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/jquery.table-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve jQuery Plugin: Table Filter v0.2.2 3 | * 4 | * LICENSE: http://hail2u.mit-license.org/2009 5 | */ 6 | 7 | /*jslint indent: 2, browser: true, regexp: true */ 8 | /*global jQuery, $ */ 9 | 10 | (function ($) { 11 | "use strict"; 12 | 13 | $.fn.addTableFilter = function (options) { 14 | var o = $.extend({}, $.fn.addTableFilter.defaults, options), 15 | tgt, 16 | id, 17 | label, 18 | input; 19 | 20 | if (this.is("table")) { 21 | // Generate ID 22 | if (!this.attr("id")) { 23 | this.attr({ 24 | id: "t-" + Math.floor(Math.random() * 99999999) 25 | }); 26 | } 27 | tgt = this.attr("id"); 28 | id = tgt + "-filtering"; 29 | 30 | // Build filtering form 31 | // label = $("").attr({ 32 | // "for": id 33 | // }).append(o.labelText); 34 | label = "" 35 | input = $("").attr({ 36 | id: id, 37 | size: o.size 38 | }); 39 | $("
").addClass("formTableFilter input-group").append(input).append(label).insertBefore("#table_search"); 40 | 41 | // Bind filtering function 42 | $("#" + id).delayBind("keyup", function (e) { 43 | var words = $(this).val().toLowerCase().split(" "); 44 | $("#" + tgt + " tbody tr").each(function () { 45 | var s = $(this).html().toLowerCase().replace(/<.+?>/g, "").replace(/\s+/g, " "), 46 | state = 0; 47 | $.each(words, function () { 48 | if (s.indexOf(this) < 0) { 49 | state = 1; 50 | return false; // break $.each() 51 | } 52 | }); 53 | 54 | if (state) { 55 | $(this).hide(); 56 | } else { 57 | $(this).show(); 58 | } 59 | }); 60 | }, 300); 61 | } 62 | 63 | return this; 64 | }; 65 | 66 | $.fn.addTableFilter.defaults = { 67 | labelText: "Keyword(s): ", 68 | size: 32 69 | }; 70 | 71 | $.fn.delayBind = function (type, data, func, timeout) { 72 | if ($.isFunction(data)) { 73 | timeout = func; 74 | func = data; 75 | data = undefined; 76 | } 77 | 78 | var self = this, 79 | wait = null, 80 | handler = function (e) { 81 | clearTimeout(wait); 82 | wait = setTimeout(function () { 83 | func.apply(self, [$.extend({}, e)]); 84 | }, timeout); 85 | }; 86 | 87 | return this.bind(type, data, handler); 88 | }; 89 | }(jQuery)); 90 | -------------------------------------------------------------------------------- /app/assets/javascripts/zoomed.application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require strap-on.css 13 | *= require webfonts.css 14 | *= require selectize 15 | *= require selectize.bootstrap3 16 | *= require hackweek.css 17 | *= require jquery.atwho 18 | *= require comments.css 19 | *= require syntax.css 20 | */ 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/comments.scss: -------------------------------------------------------------------------------- 1 | .preview-contents { 2 | background: #FFFFFF; 3 | margin-top: -1px; 4 | padding: 10px 16px; 5 | min-height: 81px; 6 | line-height: 24px; 7 | border: 1px solid #DDDDDD; 8 | border-top: 0; 9 | box-shadow: rgba(0, 0, 0, 0.075) 0 1px 1px 0 inset; 10 | border-radius: 0 0 6px 6px; 11 | 12 | p { 13 | font-size: 18px; 14 | color: #555555; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/assets/stylesheets/strap-on.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/syntax.scss.erb: -------------------------------------------------------------------------------- 1 | <%= Rouge::Themes::Github.render(scope: '.highlight') %> -------------------------------------------------------------------------------- /app/assets/stylesheets/webfonts.scss: -------------------------------------------------------------------------------- 1 | @import "font-awesome"; 2 | -------------------------------------------------------------------------------- /app/assets/videos/Hackweek_Welcome_by_Ralf.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/videos/Hackweek_Welcome_by_Ralf.mp4 -------------------------------------------------------------------------------- /app/assets/videos/Hackweek_Welcome_by_Ralf.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/assets/videos/Hackweek_Welcome_by_Ralf.webm -------------------------------------------------------------------------------- /app/controllers/about_controller.rb: -------------------------------------------------------------------------------- 1 | class AboutController < ApplicationController 2 | skip_before_action :authenticate_user!, only: %i[show index] 3 | 4 | def index 5 | return unless @episode 6 | 7 | @popular_keywords = Keyword.popular(@episode, 10).sample(3) 8 | @popular_projects = Project.current(@episode).liked.includes(:originator, :users, :kudos).order('likes_count DESC').first(6) 9 | end 10 | 11 | def show; end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/announcements_controller.rb: -------------------------------------------------------------------------------- 1 | class AnnouncementsController < ApplicationController 2 | load_and_authorize_resource params_method: :announcement_params 3 | skip_before_action :authenticate_user!, only: [:index] 4 | skip_before_action :store_location, only: [:enroll] 5 | 6 | # GET /announcements 7 | def index 8 | @announcements = Announcement.order('id DESC') 9 | end 10 | 11 | # GET /announcements/1 12 | def show; end 13 | 14 | # GET /announcements/new 15 | def new; end 16 | 17 | # GET /announcements/1/edit 18 | def edit; end 19 | 20 | # POST /announcements 21 | def create 22 | @announcement.originator = current_user 23 | 24 | if @announcement.save 25 | redirect_to announcements_path, notice: 'Announcement was successfully created.' 26 | else 27 | render action: 'new' 28 | end 29 | end 30 | 31 | # PUT /announcements/1 32 | def update 33 | if @announcement.update(announcement_params) 34 | redirect_to announcements_path, notice: 'Announcement was successfully updated.' 35 | else 36 | render action: 'edit' 37 | end 38 | end 39 | 40 | # DELETE /announcements/1 41 | def destroy 42 | @announcement.destroy 43 | 44 | respond_to do |format| 45 | format.html { redirect_to announcements_path, notice: 'Announcement was successfully deleted.' } 46 | format.js {} 47 | end 48 | end 49 | 50 | # GET /annoucements/1/enroll 51 | def enroll 52 | announcement = Announcement.find(params[:id]) 53 | announcement.enroll! current_user 54 | 55 | respond_to do |format| 56 | format.html { redirect_back_or_to root_path } 57 | format.js { render partial: 'announcement_toggle' } 58 | end 59 | end 60 | 61 | private 62 | 63 | def announcement_params 64 | params.require(:announcement).permit(:title, :text) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery prepend: true 3 | 4 | before_action :store_location 5 | before_action :authenticate_user! 6 | before_action :load_news 7 | before_action :set_episode 8 | before_action :load_notifications 9 | 10 | before_action do 11 | resource = controller_name.singularize.to_sym 12 | method = "#{resource}_params" 13 | params[resource] &&= send(method) if respond_to?(method, true) 14 | end 15 | 16 | rescue_from ActionController::ParameterMissing, with: :parameter_empty 17 | 18 | rescue_from CanCan::AccessDenied do |exception| 19 | respond_to do |format| 20 | format.json { head :forbidden } 21 | format.html { redirect_to main_app.root_url, alert: exception.message } 22 | end 23 | end 24 | 25 | protected 26 | 27 | # store last visited url unless it's the login/sign up path, 28 | # doesn't start with our base url or is an ajax call. 29 | def store_location 30 | return unless request.get? 31 | 32 | if request.path != new_user_ichain_session_path && 33 | request.path != new_user_ichain_registration_path && 34 | !request.path.starts_with?(Devise.ichain_base_url) && 35 | !request.xhr? 36 | session[:return_to] = request.fullpath 37 | end 38 | end 39 | 40 | # after sign in redirect to the stored location 41 | def after_sign_in_path_for(_resource) 42 | session[:return_to] || root_path 43 | end 44 | 45 | # after sign out redirect to projects 46 | def after_sign_out_path_for(_resource) 47 | projects_path 48 | end 49 | 50 | def load_news 51 | if user_signed_in? 52 | a = Announcement.last 53 | @news = (a if a && !a.users.include?(current_user)) 54 | end 55 | end 56 | 57 | def parameter_empty 58 | redirect_to(:back) 59 | flash['warn'] = 'Parameter missing...' 60 | end 61 | 62 | def set_episode 63 | @episode = if params[:episode].blank? 64 | Episode.active 65 | elsif params[:episode] == 'all' 66 | :all 67 | else 68 | Episode.find(params[:episode]) 69 | end 70 | logger.debug("\n\nEpisode: #{@episode.to_param}\n\n") 71 | end 72 | 73 | def load_notifications 74 | @notifications = Notification.where(recipient: current_user).unread if user_signed_in? 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | include MarkdownHelper 3 | before_action :get_parent, only: %i[create update] 4 | load_and_authorize_resource except: :index 5 | authorize_resource only: :index 6 | skip_before_action :verify_authenticity_token, only: [:reply_modal] 7 | 8 | def index 9 | @comments = Comment.accessible_by(current_ability).order('id DESC').page(params[:page]) 10 | end 11 | 12 | def create 13 | @comment = @parent.comments.build(comment_params) 14 | @comment.commenter = current_user 15 | 16 | if @comment.save 17 | @comment.send_notification(current_user, 18 | " commented on #{@comment.project.aasm_state}: #{@comment.project.title}") 19 | redirect_to project_path(@comment.project), notice: 'Thank you for your comment!' 20 | else 21 | logger.info "Blocked spam comment from #{current_user.name}" if @comment.errors.of_kind?(:base, 'is spam') 22 | redirect_to project_path(@comment.project), alert: "Could not comment: #{@comment.errors.full_messages.to_sentence}" 23 | end 24 | end 25 | 26 | def update 27 | respond_to do |format| 28 | if @comment.update(comment_params) 29 | format.html { redirect_to project_path(@comment.project), notice: 'Comment was successfully updated.' } 30 | else 31 | format.html { render :edit, status: :unprocessable_entity } 32 | end 33 | end 34 | end 35 | 36 | def destroy 37 | @comment.destroy 38 | 39 | respond_to do |format| 40 | format.html { redirect_to comments_path, notice: 'Comment was successfully deleted.' } 41 | end 42 | end 43 | 44 | def comment_params 45 | params.require(:comment).permit(:commentable_id, :commentable_type, :commenter_id, :text, :commenter) 46 | end 47 | 48 | protected 49 | 50 | def get_parent 51 | @parent = Project.find_by(url: params[:project_id]) if params[:project_id] 52 | @parent = Comment.find_by_id(params[:comment_id]) if params[:comment_id] 53 | 54 | redirect_to root_path unless defined?(@parent) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/episodes_controller.rb: -------------------------------------------------------------------------------- 1 | class EpisodesController < ApplicationController 2 | load_and_authorize_resource 3 | before_action :set_episode, only: %i[show edit update destroy] 4 | skip_before_action :authenticate_user!, only: %i[index show] 5 | 6 | # GET /episodes 7 | def index 8 | @episodes = Episode.order('start_date DESC') 9 | end 10 | 11 | # GET /episodes/1 12 | def show; end 13 | 14 | # GET /episodes/new 15 | def new 16 | @episode = Episode.new 17 | end 18 | 19 | # GET /episodes/1/edit 20 | def edit; end 21 | 22 | # POST /episodes 23 | def create 24 | @episode = Episode.new(episode_params) 25 | 26 | if @episode.save 27 | redirect_to episodes_path, notice: 'Episode was successfully created.' 28 | else 29 | render action: 'new' 30 | end 31 | end 32 | 33 | # PATCH/PUT /episodes/1 34 | def update 35 | if @episode.update(episode_params) 36 | redirect_to episodes_path, notice: 'Episode was successfully updated.' 37 | else 38 | render action: 'edit' 39 | end 40 | end 41 | 42 | # DELETE /episodes/1 43 | def destroy 44 | @episode.destroy 45 | redirect_to episodes_path, notice: 'Episode was successfully destroyed.' 46 | end 47 | 48 | # PUT /episodes/1/activate 49 | def activate 50 | @episode.active = true 51 | redirect_to episodes_path, notice: "#{@episode.name} is now active." 52 | end 53 | 54 | private 55 | 56 | # Use callbacks to share common setup or constraints between actions. 57 | def set_episode 58 | @episode = Episode.find(params[:id]) 59 | end 60 | 61 | # Only allow a trusted parameter "white list" through. 62 | def episode_params 63 | params.require(:episode).permit(:name, :start_date, :end_date, :description, :avatar) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /app/controllers/faqs_controller.rb: -------------------------------------------------------------------------------- 1 | class FaqsController < ApplicationController 2 | load_and_authorize_resource params_method: :faq_params 3 | skip_before_action :authenticate_user!, only: [:index] 4 | 5 | # GET /faqs | /faq 6 | def index 7 | @faqs = Faq.all 8 | end 9 | 10 | # GET /faqs/new 11 | def new 12 | @faq = Faq.new 13 | end 14 | 15 | # GET /faqs/1/edit 16 | def edit; end 17 | 18 | # POST /faqs 19 | def create 20 | @faq = Faq.new(faq_params) 21 | 22 | if @faq.save 23 | redirect_to faqs_url, notice: 'Faq was successfully created.' 24 | else 25 | render :new 26 | end 27 | end 28 | 29 | # PATCH/PUT /faqs/1 30 | def update 31 | if @faq.update(faq_params) 32 | redirect_to faqs_url, notice: 'Faq was successfully updated.' 33 | else 34 | render :edit 35 | end 36 | end 37 | 38 | # DELETE /faqs/1 39 | def destroy 40 | @faq.destroy 41 | redirect_to faqs_url, notice: 'Faq was successfully destroyed.' 42 | end 43 | 44 | private 45 | 46 | # Use callbacks to share common setup or constraints between actions. 47 | def set_faq 48 | @faq = Faq.find!(params[:id]) 49 | end 50 | 51 | # Only allow a trusted parameter "white list" through. 52 | def faq_params 53 | params.require(:faq).permit(:question, :answer) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/controllers/keywords_controller.rb: -------------------------------------------------------------------------------- 1 | class KeywordsController < ApplicationController 2 | skip_before_action :authenticate_user!, only: %i[index show] 3 | before_action :load_keyword, only: %i[show edit update] 4 | 5 | def index 6 | @keywords = Keyword.find_keyword("%#{params[:query]}%").by_episode(@episode).order(:name).distinct.page(params[:page]).per(params[:page_size]) 7 | @popular_keywords = Keyword.popular(@episode, 10) 8 | end 9 | 10 | def show 11 | @projects = Project.by_episode(@episode).by_keyword(@keyword).includes(:originator, :users, :kudos).page(params[:page]).per(params[:page_size]) 12 | render 'projects/index' 13 | end 14 | 15 | # GET /keywords/1/edit 16 | def edit; end 17 | 18 | # PATCH/PUT /keywords/javascript 19 | def update 20 | if @keyword.update(keyword_params) 21 | redirect_to(keyword_url(@episode, name: @keyword.name), notice: 'Keyword was successfully updated.') 22 | else 23 | render :edit 24 | end 25 | end 26 | 27 | private 28 | 29 | def load_keyword 30 | @keyword = Keyword.find_by!(name: params[:name]) 31 | end 32 | 33 | # Only allow a trusted parameter "white list" through. 34 | def keyword_params 35 | params.require(:keyword).permit(:description, :avatar) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/markdown_controller.rb: -------------------------------------------------------------------------------- 1 | class MarkdownController < ApplicationController 2 | def preview 3 | @markdown_source = helpers.enrich_markdown(markdown: params[:source]) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | class NotificationsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | @older_notifications = Notification.logged_in(current_user).order('created_at DESC').page(params[:page]) 6 | end 7 | 8 | def mark_as_read 9 | @notifications = Notification.logged_in(current_user).unread 10 | @notifications.update_all(read_at: Time.zone.now) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/projects/project_follows_controller.rb: -------------------------------------------------------------------------------- 1 | class Projects::ProjectFollowsController < ApplicationController 2 | load_and_authorize_resource :project, find_by: :url 3 | 4 | def index 5 | @users = @project.project_followers.page(params[:page]).per(params[:page_size]) 6 | end 7 | 8 | def create 9 | current_user.project_followings = current_user.project_followings | [@project] 10 | flash.now[:notice] = "You are now watching #{@project.title}" 11 | render 'follow_toggle' 12 | end 13 | 14 | def destroy 15 | current_user.project_followings.delete @project 16 | flash.now[:notice] = "You have stopped watching #{@project.title}" 17 | render 'follow_toggle' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/search_controller.rb: -------------------------------------------------------------------------------- 1 | class SearchController < ApplicationController 2 | skip_before_action :authenticate_user!, only: [:result] 3 | 4 | def result 5 | if params[:query] 6 | # First search in morphology mode, if fails — retry in wildcard mode 7 | search_query = ThinkingSphinx::Query.escape(params[:query]) 8 | if @episode.to_param == 'all' 9 | @projects = Project.search search_query 10 | @projects = Project.search search_query, star: true if @projects.empty? 11 | else 12 | @projects = Project.search search_query, with: { episode_ids: params[:episode].to_i } 13 | if @projects.empty? 14 | @projects = Project.search search_query, with: { episode_ids: params[:episode].to_i }, star: true 15 | end 16 | end 17 | 18 | @users = User.search search_query 19 | @users = User.search search_query, star: true if @users.empty? 20 | else 21 | @projects = [] 22 | @users = [] 23 | end 24 | end 25 | 26 | def keyword 27 | respond_to do |format| 28 | format.json do 29 | render json: { keywords: Keyword.find_keyword("#{params[:query]}%") } 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/controllers/updates_controller.rb: -------------------------------------------------------------------------------- 1 | class UpdatesController < ApplicationController 2 | skip_before_action :load_news 3 | skip_before_action :set_episode 4 | skip_before_action :load_notifications 5 | before_action :set_updates 6 | 7 | def index 8 | @next_page = params[:page].to_i + 1 9 | @last_page = @updates.page(params[:page]).last_page? 10 | end 11 | 12 | private 13 | 14 | def set_updates 15 | @updates = [] 16 | @updates = current_user.updates.page(params[:page]) unless params[:project_id] 17 | return unless params[:project_id] 18 | 19 | @project = Project.find(params[:project_id]) 20 | @updates = @project.updates.page(params[:page]) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :find_user_by_id 3 | before_action :redirect_to_slug, only: [:show] 4 | load_and_authorize_resource find_by: :name 5 | before_action :set_updates, except: :index 6 | 7 | skip_before_action :authenticate_user!, only: %i[show originated likes opportunities] 8 | skip_before_action :verify_authenticity_token, only: %i[add_keyword delete_keyword] 9 | 10 | def index 11 | @users = User.all.page(params[:page]).per(params[:page_size]) 12 | respond_to do |format| 13 | format.html { render } 14 | format.js 15 | end 16 | end 17 | 18 | def edit; end 19 | 20 | def update 21 | @user.update(params['user']) 22 | redirect_to user_path(@user) 23 | end 24 | 25 | def show 26 | @projects = @user.projects.by_episode(Episode.active) 27 | end 28 | 29 | def originated 30 | @projects = @user.originated_projects 31 | render :show 32 | end 33 | 34 | def likes 35 | @projects = @user.favourites 36 | render :show 37 | end 38 | 39 | def opportunities 40 | @projects = @user.recommended_projects(@episode) 41 | render :show 42 | end 43 | 44 | def add_keyword 45 | keywords = keyword_params.split(',') 46 | keywords.each do |word| 47 | logger.debug "Adding keyword \"#{word}\" from user #{@user.id}" 48 | current_user.add_keyword! word 49 | end 50 | redirect_to @user, notice: "Keyword '#{params[:keyword]}' added." 51 | end 52 | 53 | def delete_keyword 54 | keywords = keyword_params.split(',') 55 | keywords.each do |word| 56 | logger.debug "Deleting keyword \"#{word}\" to user #{@user.id}" 57 | current_user.remove_keyword! word 58 | end 59 | redirect_to @user, notice: "Keyword '#{params[:keyword]}' removed." 60 | end 61 | 62 | private 63 | 64 | def user_params 65 | params.require(:user).permit(:location, :hide_email) 66 | end 67 | 68 | def keyword_params 69 | params.require(:keyword) 70 | end 71 | 72 | def find_user_by_id 73 | @user = User.find_by(id: params[:id]) 74 | end 75 | 76 | def set_updates 77 | @updates = @user.updates.page(1) 78 | @last_page = @user.updates.page(1).last_page? || @user.updates.empty? 79 | end 80 | 81 | def redirect_to_slug 82 | redirect_to @user if @user 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def bootstrap_class_for(flash_type) 3 | case flash_type 4 | when 'success' 5 | 'alert-success' 6 | when 'error' 7 | 'alert-danger' 8 | when 'alert' 9 | 'alert-warning' 10 | when 'notice' 11 | 'alert-info' 12 | else 13 | 'alert-info' 14 | end 15 | end 16 | 17 | def active_page_size(page_size, param = nil) 18 | if param.blank? 19 | 'active' if page_size.to_i == 10 20 | elsif page_size.to_i == param.to_i 21 | 'active' 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/helpers/markdown_helper.rb: -------------------------------------------------------------------------------- 1 | module MarkdownHelper 2 | def enrich_markdown(markdown:, lines: nil) 3 | # build an excerpt 4 | markdown = markdown.lines[0..lines - 1].join if lines 5 | 6 | # replace :smiley: with a link to github.com emojis 7 | markdown.gsub!(/(?<=^|\s):([\w+-]+):(?=\s|$)/) do |match| 8 | %(}.png)) 9 | end 10 | # replace @hans with a link to the user with the login hans 11 | markdown.gsub!(/([^\w]|^)@([-\w]+)([^\w]|$)/) do 12 | "#{Regexp.last_match(1)}[@#{Regexp.last_match(2)}](#{::Rails.application.routes.url_helpers(only_path: true).user_path(Regexp.last_match(2))})#{Regexp.last_match(3)}" 13 | end 14 | # replace hw#my-project with a link to the project with the slug my-project 15 | markdown.gsub!(/([^\w]|^)hw#([-\w]+)([^\w]|$)/) do 16 | "#{Regexp.last_match(1)}[hw##{Regexp.last_match(2)}](#{::Rails.application.routes.url_helpers(only_path: true).project_path(Regexp.last_match(2))})#{Regexp.last_match(3)}" 17 | end 18 | 19 | sanitize(markdown) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/indices/project_index.rb: -------------------------------------------------------------------------------- 1 | ThinkingSphinx::Index.define :project, name: :project_real_time, with: :real_time do 2 | indexes title 3 | indexes description 4 | indexes comment_texts 5 | 6 | has episode_ids, as: :episode_ids, type: :integer, multi: true 7 | end 8 | -------------------------------------------------------------------------------- /app/indices/user_index.rb: -------------------------------------------------------------------------------- 1 | ThinkingSphinx::Index.define :user, name: :user_real_time, with: :real_time do 2 | indexes name 3 | end 4 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/models/ability.rb: -------------------------------------------------------------------------------- 1 | class Ability 2 | # The first argument to `can` is the action you are giving the user 3 | # permission to do. 4 | # If you pass :manage it will apply to every action. Other common actions 5 | # here are :read, :create, :update and :destroy. 6 | # 7 | # The second argument is the resource the user can perform the action on. 8 | # If you pass :all it will apply to every resource. Otherwise pass a Ruby 9 | # class of the resource. 10 | # 11 | # The third argument is an optional hash of conditions to further filter the 12 | # objects. 13 | # For example, here the user can only update published articles. 14 | # 15 | # can :update, Article, :published => true 16 | # 17 | # See the wiki for details: 18 | # https://github.com/ryanb/cancan/wiki/Defining-Abilities 19 | include CanCan::Ability 20 | 21 | def initialize(user) 22 | user ||= User.new # guest user (not logged in) 23 | if user 24 | # Everyone can: 25 | can :read, Project 26 | can :read, Keyword 27 | can :read, User 28 | can :read, Announcement 29 | can :read, Faq 30 | can %i[join leave like dislike create], Project 31 | can %i[originated likes opportunities], User 32 | can [:enroll], Announcement 33 | # ...change things they own 34 | can %i[update add_keyword delete_keyword], User, id: user.id 35 | can :read, Update, author_id: user.id 36 | can :manage, Project, originator_id: user.id 37 | can %i[create update], Comment, commenter_id: user.id 38 | can %i[update add_keyword delete_keyword advance recess add_episode delete_episode], 39 | Project do |project| 40 | project.users.include? user 41 | end 42 | 43 | # Organizers can: 44 | if user.role? :organizer 45 | can :manage, Announcement 46 | can :manage, Faq 47 | end 48 | 49 | # Admins can: 50 | can :manage, :all if user.role? :admin 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /app/models/announcement.rb: -------------------------------------------------------------------------------- 1 | class Announcement < ApplicationRecord 2 | validates :title, presence: true 3 | validates :text, presence: true 4 | validates :originator_id, presence: true 5 | 6 | belongs_to :originator, class_name: 'User' 7 | has_many :enrollments 8 | has_many :users, through: :enrollments 9 | 10 | def enroll!(user) 11 | users << user 12 | save! 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | include Rails.application.routes.url_helpers 3 | 4 | belongs_to :commentable, polymorphic: true 5 | has_many :comments, as: :commentable, dependent: :destroy 6 | 7 | belongs_to :commenter, class_name: 'User' 8 | alias_attribute :originator, :commenter 9 | 10 | validates_presence_of :commenter_id, :text, :commentable_id 11 | 12 | ThinkingSphinx::Callbacks.append(self, behaviours: [:real_time]) 13 | 14 | def project 15 | return @project if defined?(@project) 16 | 17 | @project = commentable.is_a?(Project) ? commentable : commentable.project 18 | end 19 | 20 | def send_notification(sender, message) 21 | recipients = project.project_followers - [sender] 22 | 23 | recipients.each do |recipient| 24 | Notification.create(recipient: recipient, actor: sender, action: message, notifiable: project) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/models/enrollment.rb: -------------------------------------------------------------------------------- 1 | class Enrollment < ApplicationRecord 2 | belongs_to :announcement 3 | belongs_to :user 4 | end 5 | -------------------------------------------------------------------------------- /app/models/episode.rb: -------------------------------------------------------------------------------- 1 | class Episode < ApplicationRecord 2 | has_and_belongs_to_many :projects 3 | has_one_attached :avatar 4 | 5 | validates :name, presence: true 6 | 7 | def self.active 8 | Episode.where(active: true).first 9 | end 10 | 11 | def active=(state) 12 | if state == true 13 | Episode.where('id != ? AND active = ?', id, true).each do |episode| 14 | episode.active = false 15 | episode.save 16 | end 17 | end 18 | write_attribute(:active, state) 19 | save 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/faq.rb: -------------------------------------------------------------------------------- 1 | class Faq < ApplicationRecord 2 | validates :question, presence: true 3 | validates :answer, presence: true 4 | end 5 | -------------------------------------------------------------------------------- /app/models/keyword.rb: -------------------------------------------------------------------------------- 1 | class Keyword < ApplicationRecord 2 | validates :name, presence: true 3 | validates :name, uniqueness: true 4 | validates :description, length: { maximum: 255 } 5 | 6 | scope :find_keyword, lambda { |str| 7 | where('lower(name) like ?', str.downcase) unless str.blank? 8 | } 9 | 10 | has_and_belongs_to_many :projects 11 | has_and_belongs_to_many :users 12 | 13 | has_one_attached :avatar 14 | 15 | scope :by_episode, lambda { |episode| 16 | joins(:keywords_projects).where(keywords_projects: { project: episode.projects.pluck(:id) }) if episode && episode.is_a?(Episode) 17 | } 18 | 19 | paginates_per 5 20 | 21 | def self.popular(episode, count) 22 | Keyword.by_episode(episode) 23 | .group(:name).count 24 | .sort_by { |_name, occurance| occurance }.reverse 25 | .first(count).map { |keyword| Keyword.find_by(name: keyword[0]) } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ApplicationRecord 2 | belongs_to :project, counter_cache: true 3 | belongs_to :user 4 | end 5 | -------------------------------------------------------------------------------- /app/models/membership.rb: -------------------------------------------------------------------------------- 1 | class Membership < ApplicationRecord 2 | belongs_to :project, counter_cache: true 3 | belongs_to :user 4 | end 5 | -------------------------------------------------------------------------------- /app/models/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification < ActiveRecord::Base 2 | belongs_to :recipient, class_name: 'User' 3 | belongs_to :actor, class_name: 'User' 4 | belongs_to :notifiable, polymorphic: true 5 | 6 | scope :unread, -> { where(read_at: nil) } 7 | scope :logged_in, ->(user) { where(recipient: user) } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/project_follow.rb: -------------------------------------------------------------------------------- 1 | class ProjectFollow < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :project 4 | end 5 | -------------------------------------------------------------------------------- /app/models/role.rb: -------------------------------------------------------------------------------- 1 | class Role < ApplicationRecord 2 | has_and_belongs_to_many :users 3 | end 4 | -------------------------------------------------------------------------------- /app/models/update.rb: -------------------------------------------------------------------------------- 1 | class Update < ApplicationRecord 2 | validates_presence_of :author_id, :project_id 3 | 4 | belongs_to :author, class_name: 'User' 5 | belongs_to :project 6 | end 7 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | devise :ichain_authenticatable, :ichain_registerable 3 | 4 | validates :name, presence: true 5 | validates :email, presence: true 6 | validates_uniqueness_of :name 7 | validates_uniqueness_of :email 8 | 9 | has_many :originated_projects, foreign_key: 'originator_id', class_name: 'Project' 10 | has_many :updates, -> { order 'created_at DESC' }, foreign_key: 'author_id', dependent: :destroy 11 | 12 | has_many :memberships 13 | has_many :projects, through: :memberships 14 | 15 | has_many :comments, foreign_key: 'commenter_id' 16 | has_many :likes 17 | has_many :enrollments 18 | 19 | has_many :announcements, through: :enrollments 20 | has_many :favourites, through: :likes, source: :project 21 | 22 | has_and_belongs_to_many :keywords 23 | 24 | has_many :project_follows 25 | has_many :project_followings, through: :project_follows, source: :project 26 | 27 | has_many :notifications, foreign_key: :recipient_id 28 | 29 | has_and_belongs_to_many :roles 30 | 31 | ThinkingSphinx::Callbacks.append(self, behaviours: [:real_time]) 32 | 33 | include Gravtastic 34 | has_gravatar default: 'retro' 35 | 36 | def role?(role) 37 | !!roles.find_by_name(role) 38 | end 39 | 40 | def to_param 41 | name 42 | end 43 | 44 | def add_keyword!(name) 45 | name.downcase! 46 | name.gsub!(/\s/, '') 47 | keyword = Keyword.find_by_name name 48 | keyword ||= Keyword.create! name: name 49 | unless keywords.include? keyword 50 | keywords << keyword 51 | save! 52 | end 53 | end 54 | 55 | def remove_keyword!(name) 56 | keyword = Keyword.find_by_name name 57 | if keywords.include? keyword 58 | keywords.delete(keyword) 59 | save! 60 | end 61 | end 62 | 63 | def recommended_projects(episode = nil) 64 | return [] if keywords.empty? 65 | 66 | recommended = [] 67 | keywords.each do |word| 68 | projects = if episode 69 | word.projects.select { |p| p.episodes.include?(episode) } 70 | else 71 | word.projects 72 | end 73 | projects.each do |p| 74 | next unless p.active? 75 | 76 | if episode 77 | recommended << p if p.episodes.include?(episode) 78 | else 79 | recommended << p 80 | end 81 | end 82 | end 83 | recommended.uniq 84 | end 85 | 86 | def self.for_ichain_username(username, attributes) 87 | attributes = attributes.with_indifferent_access 88 | user = find_by(name: username) 89 | if user 90 | user.update_attribute('email', attributes[:email]) if user.email != attributes[:email] 91 | else 92 | user = create(name: username, email: attributes[:email]) 93 | end 94 | user 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /app/views/announcements/_announcement.html.haml: -------------------------------------------------------------------------------- 1 | .media 2 | %i.fas.fa-bullhorn.media-object.pull-left 3 | = render partial: 'announcements/file_buttons', locals: { announcement: announcement } 4 | .media-body 5 | %h4.media-heading 6 | = announcement.title 7 | %br 8 | %small 9 | = announcement.updated_at 10 | by 11 | = announcement.originator.name 12 | - if can? :write, Announcement 13 | %span.label.label-info 14 | = pluralize(announcement.users.length, 'user') 15 | read this 16 | %p 17 | = simple_format(announcement.text) 18 | -------------------------------------------------------------------------------- /app/views/announcements/_announcement_toggle.js.erb: -------------------------------------------------------------------------------- 1 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 2 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 3 | $("#announcement-<%= @announcement.id %>").toggle(); 4 | -------------------------------------------------------------------------------- /app/views/announcements/_file_buttons.html.haml: -------------------------------------------------------------------------------- 1 | .btn-group.pull-right 2 | - if can? :edit, announcement 3 | = link_to(edit_announcement_path(announcement), :title=>'Edit this announcement?', :class=> "btn btn-default btn-xs") do 4 | %i.fas.fa-pencil-alt 5 | -if can? :destroy, announcement 6 | = link_to(announcement_path(announcement), :title=>'Delete this announcement?', :class=> "btn btn-default btn-xs", :method => :delete, data: { confirm: "Are you sure you want to delete this announcement for all users? This can't be undone!" }) do 7 | %i.fas.fa-times 8 | -------------------------------------------------------------------------------- /app/views/announcements/_form.html.haml: -------------------------------------------------------------------------------- 1 | = form_for @announcement, html: { role: "form"} do |f| 2 | - if @announcement.errors.any? 3 | #error_explanation 4 | %h2 5 | = pluralize(@announcement.errors.count, "error") 6 | prohibited this project from being saved: 7 | %ul 8 | - @announcement.errors.full_messages.each do |msg| 9 | %li 10 | = msg 11 | .field 12 | = f.text_field :title, :placeholder => "The announcements title", :class => 'form-control input-lg', :required => "required" 13 | .field 14 | = f.text_area :text, :rows => "20", :placeholder => "Add a announcement text. You can use HTML, please be careful!", :class => 'form-control input-lg' 15 | .actions 16 | %p 17 | = f.submit :class => "btn btn-success pull-right" 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/views/announcements/edit.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Edit 3 | = @announcement.title 4 | 5 | .row 6 | .col-sm-12 7 | = render 'form' 8 | -------------------------------------------------------------------------------- /app/views/announcements/index.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Announcements 3 | = "(#{@announcements.length})" 4 | 5 | .row 6 | .col-sm-12 7 | %h2 8 | Announcements 9 | - @announcements.each do |announcement| 10 | = render :partial => "announcements/announcement", :locals => {:announcement => announcement } 11 | %p 12 | - if can? :write, Announcement 13 | = link_to "New announcement", new_announcement_path, :class => "btn btn-success pull-right" -------------------------------------------------------------------------------- /app/views/announcements/new.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Create Announcement 3 | 4 | .row 5 | .col-sm-12 6 | %h2 Create an Announcement 7 | %p.help-block 8 | This will post a new announcement to the header and replace all previous unread announcements. 9 | = render 'form' 10 | -------------------------------------------------------------------------------- /app/views/announcements/show.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | = @announcement.title 3 | 4 | .row 5 | .col-sm-12 6 | %h2 7 | = @announcement.title 8 | = simple_format(@announcement.text) -------------------------------------------------------------------------------- /app/views/comments/_comment.html.haml: -------------------------------------------------------------------------------- 1 | %li{class: 'media', id: dom_id(comment)} 2 | = link_to user_path(comment.commenter), :class => 'pull-left' do 3 | = image_tag(comment.commenter.gravatar_url(:size => "40"), class: "media-object img-rounded", alt: comment.commenter.name, title: comment.commenter.name) 4 | .media-body 5 | %h6.media-heading 6 | = time_ago_in_words(comment.created_at) 7 | ago by 8 | = link_to user_path(comment.commenter) do 9 | = comment.commenter.name 10 | | 11 | %a{ 'href' => 'javascript:void(0)', 'data-target' => "#replyComment#{dom_id(comment)}", 'data-toggle' => 'modal' } 12 | Reply 13 | - if can? :update, comment 14 | | 15 | %a{ 'href' => 'javascript:void(0)', 'data-target' => "#editComment#{dom_id(comment)}", 'data-toggle' => 'modal', type: 'button' } 16 | Edit 17 | %p 18 | :markdown 19 | #{ enrich_markdown(markdown: comment.text) } 20 | - if !comment.comments.empty? 21 | %ul.media-list 22 | = render :partial => 'comments/comment', :collection => comment.comments, object: comment 23 | 24 | .modal.fade{ id: "editComment#{dom_id(comment)}", role: "dialog", tabindex: "-1"} 25 | .modal-dialog{role: "document"} 26 | .modal-content 27 | .modal-header 28 | %button.close{"data-dismiss" => "modal", type: "button"} 29 | %span{"aria-hidden" => "true"} × 30 | %h4.modal-title 31 | Edit Comment 32 | = comment.id 33 | .modal-body 34 | = render partial: 'comments/form', locals: { comment: comment, parent: comment.commentable, id: rand(36**10).to_s(36).upcase[0,5] } 35 | .modal-footer 36 | 37 | .modal.fade{ id: "replyComment#{dom_id(comment)}", role: "dialog", tabindex: "-1"} 38 | .modal-dialog{role: "document"} 39 | .modal-content 40 | .modal-header 41 | %button.close{'data-dismiss' => 'modal', type: 'button'} 42 | %span{'aria-hidden' => 'true'} × 43 | %h4.modal-title 44 | Reply to 45 | = comment.commenter.name 46 | .modal-body 47 | %p 48 | :markdown 49 | #{ enrich_markdown(markdown: comment.text) } 50 | %hr 51 | #replyform 52 | = render partial: 'comments/form', locals: { comment: @new_comment, parent: comment, id: rand(36**10).to_s(36).upcase[0,5] } 53 | .modal-footer 54 | -------------------------------------------------------------------------------- /app/views/comments/_form.html.haml: -------------------------------------------------------------------------------- 1 | = render :partial => 'comments/help' 2 | 3 | = form_for [parent, comment], html: { role: "form" } do |f| 4 | %p 5 | .comment-form-heading 6 | %ul.nav.nav-tabs 7 | %li.active.show-source{ role: :presentation } 8 | %a{ href: "#markdown-source#{id}", role: :tab, data: { toggle: :tab } } Edit 9 | %li.show-preview{ role: :presentation } 10 | %a{ href: "#markdown-preview#{id}", role: :tab, data: { toggle: :tab } } Preview 11 | .btnbar 12 | = render 'shared/editor_buttons' 13 | .comment-form-body 14 | .tab-content 15 | .tab-pane.active.fade.in{ role: 'tab-pane', id: "markdown-source#{id}" } 16 | = f.text_area :text, :placeholder => "Your comment. You can use markdown.", :class => 'form-control input-lg', :required => "required" 17 | .tab-pane.fade{ role: 'tab-pane', id: "markdown-preview#{id}" } 18 | .loading-spinner 19 | = icon('fas', 'spinner pulse 3x') 20 | .preview-contents.hidden 21 | %p 22 | = f.submit(class: "btn btn-success pull-right") 23 | 24 | -------------------------------------------------------------------------------- /app/views/comments/_help.html.haml: -------------------------------------------------------------------------------- 1 | #help{:class=>"collapse"} 2 | %pre 3 | :preserve 4 | # A First Level Header 5 | ## A Second Level Header 6 | 7 | Use one asterisk to *emphasize* 8 | 9 | Use two asterisks for **strong emphasis** 10 | 11 | - Use hyphens 12 | - for unordereed 13 | - lists 14 | 15 | This is an [link to example.com](http://example.com/) 16 | 17 | This is an image  18 | 19 | This is a user link @hans 20 | 21 | This is a project link hw#some-cool-title 22 | 23 | %p.text-right{:style=>"margin-bottom: 10px;"} 24 | = link_to "More Complex Markdown Help", "http://daringfireball.net/projects/markdown/syntax", target: "_blank" 25 | %p.text-right 26 | %a{:type=>"button", "data-toggle"=>"collapse", "data-target"=>"#help", :class=>"btn btn-default btn-xs"} 27 | %i.fas.fa-question 28 | Formatting Help 29 | -------------------------------------------------------------------------------- /app/views/comments/index.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Comments 3 | 4 | .row 5 | .col-sm-12 6 | - if @comments.any? 7 | .row 8 | .col-sm-12 9 | %table.table.table-hover 10 | %thead 11 | %th Date 12 | %th User 13 | %th Comment 14 | %th Project 15 | %th Actions 16 | - @comments.each do |comment| 17 | %tr 18 | %td 19 | = comment.updated_at 20 | %td 21 | - if comment.commenter 22 | = link_to user_path(comment.commenter) do 23 | = comment.commenter.name 24 | - else 25 | Deleted User 26 | %td 27 | = comment.text 28 | %td 29 | - if comment.commentable 30 | = link_to project_path(comment.commentable) do 31 | = comment.project.title 32 | - else 33 | Deleted Project/Comment 34 | %td 35 | .btn-group 36 | = link_to comment_path(comment), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, class: 'btn btn-danger' do 37 | Delete Comment 38 | .row 39 | .col-sm-12 40 | .text-center 41 | = paginate @comments 42 | - else 43 | No comments yet. 44 | -------------------------------------------------------------------------------- /app/views/devise/ichain_sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Sign In 3 | 4 | .row 5 | .col-md-6.col-md-offset-3 6 | %h3 Sign In 7 | %p.text-muted 8 | Using your SUSE/openSUSE credentials from the 9 | #{link_to('IDP Portal', 'https://idp-portal.suse.com')}. 10 | 11 | = form_tag(@login_url, :method => :post, :enctype => 'application/x-www-form-urlencoded',:role => 'form') do 12 | = hidden_field_tag :url, @back_url 13 | = hidden_field_tag :context, @context 14 | = hidden_field_tag :proxypath, @proxypath 15 | .form-group 16 | = label_tag :username, "Username" 17 | = text_field_tag :username, "", :autofocus => true, :placeholder => 'Username', :class => 'form-control', :required => "required" 18 | .form-group 19 | = label_tag :Password, "Password" 20 | = password_field_tag :password, "", :class => 'form-control', :required => "required" 21 | .form-group 22 | = button_tag(type: 'submit', class: "btn btn-success pull-right") do 23 | = content_tag(:strong, 'Sign In') 24 | 25 | - if devise_mapping.ichain_registerable? 26 | = link_to "Sign up", new_ichain_registration_path(resource_name) 27 | -------------------------------------------------------------------------------- /app/views/devise/ichain_sessions/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#flash').html('' + 23 | escape(item.name) + 24 | '
'; 25 | } 26 | }, 27 | load: function(query, callback) { 28 | console.log("load function"); 29 | if (!query.length) return callback(); 30 | $.ajax({ 31 | url: "#{search_keywords_path}.json", 32 | type: 'GET', 33 | dataType: 'json', 34 | data: { 35 | query: query, 36 | }, 37 | error: function(res) { 38 | console.log("error"); 39 | console.log(res); 40 | callback(); 41 | }, 42 | success: function(res) { 43 | console.log("success"); 44 | console.log(res); 45 | callback(res.keywords); 46 | } 47 | }); 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /app/views/keywords/_show.html.haml: -------------------------------------------------------------------------------- 1 | - keywords.each do |word| 2 | %span.badge.keyword 3 | = link_to word.name, keyword_path(@episode, word.name) 4 | - if current_user 5 | - if @project and can? :delete_keyword, @project 6 | = link_to(keyword_project_path(keyword: word.name), :method => :delete, :id => "remove-#{word.id}") do 7 | %i.fas.fa-times{:style=>"color: #fff"} 8 | - if @user and can? :remove_keyword, @user 9 | = link_to(keyword_user_path(keyword: word.name), :method => :delete, :id => "remove-#{word.id}") do 10 | %i.fas.fa-times{:style=>"color: #fff"} 11 | -------------------------------------------------------------------------------- /app/views/keywords/edit.html.haml: -------------------------------------------------------------------------------- 1 | #keyword_form 2 | %h1 3 | Edit Keyword 4 | = @keyword.name 5 | = form_for @keyword, url: keyword_path(name: @keyword.name) do |f| 6 | - if @keyword.errors.any? 7 | .alert.alert-warning 8 | %strong 9 | = pluralize(@keyword.errors.count, 'error') 10 | prohibited this keyword from being saved: 11 | %ul 12 | - @keyword.errors.full_messages.each do |msg| 13 | %li 14 | = msg 15 | 16 | .form-group 17 | = f.label('Description (maximum 255 characters)') 18 | = f.text_area :description, maxlength: "255", rows: 5, id: 'keyword_description', class: 'form-control input-lg' 19 | .form-group 20 | = f.label('Keyword Logo (Will be resized to 150x150 Pixels)') 21 | = f.file_field :avatar 22 | = f.submit class: 'btn btn-success pull-right' 23 | 24 | -------------------------------------------------------------------------------- /app/views/keywords/index.html.haml: -------------------------------------------------------------------------------- 1 | - if @episode 2 | - keyword_count = Keyword.by_episode(@episode).count 3 | .row 4 | .col-sm-9 5 | %h2 6 | All featured topics in this Hack Week 7 | %span.badge 8 | = keyword_count 9 | - @keywords.each do |keyword| 10 | .media 11 | .media-left 12 | - if keyword.avatar.attached? 13 | = image_tag(url_for(keyword.avatar), width: '150') 14 | - else 15 | %img{"data-src": "holder.js/150x150?text=#{keyword.name}"} 16 | .media-body 17 | %h4.media-heading 18 | = link_to keyword_path(episode: @episode, name: keyword.name) do 19 | = keyword.name 20 | %span.badge 21 | = keyword.projects.by_episode(@episode).count 22 | %p 23 | -if keyword.description 24 | = keyword.description 25 | - else 26 | Explore all the projects about 27 | = keyword.name 28 | .col-sm-3 29 | %h2 30 | Popular topics 31 | %p 32 | %ol 33 | - @popular_keywords.each do |keyword| 34 | %li 35 | = link_to(keyword_path(episode: @episode, name: keyword.name)) do 36 | = keyword.name 37 | %span.badge 38 | = keyword.projects.by_episode(@episode).count 39 | .row 40 | .col-sm-12.text-center 41 | = paginate @keywords 42 | %p 43 | =link_to(keywords_path(@episode, page_size: keyword_count), class: 'btn btn-default') do 44 | View All 45 | = keyword_count 46 | Keywords 47 | -------------------------------------------------------------------------------- /app/views/layouts/_admin_menu.html.haml: -------------------------------------------------------------------------------- 1 | %li{role: "separator", class: "divider"} 2 | %li{role: "presentation", class: "dropdown-header"} 3 | Admin 4 | %li 5 | = link_to(announcements_path, :id=>"announcements_path") do 6 | %i.fas.fa-bullhorn 7 | Announcements 8 | %li 9 | = link_to(episodes_path, :id => "episodes_path") do 10 | %i.far.fa-calendar-alt 11 | Hackweeks 12 | %li 13 | = link_to(users_path) do 14 | %i.far.fa-user 15 | Users 16 | -------------------------------------------------------------------------------- /app/views/layouts/_alert.html.haml: -------------------------------------------------------------------------------- 1 | - flash.each do |type, message| 2 | %div{:class=>"alert alert-dismissable #{bootstrap_class_for(type)}", :id=>"flash"} 3 | .button.close{"data-dismiss" => "alert", "aria-hidden"=>"true"} 4 | × 5 | = message 6 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.haml: -------------------------------------------------------------------------------- 1 | %footer 2 | .container 3 | .row 4 | .col-sm-12.text-center 5 | %div.col-sm-12.text-center 6 | %ul.nav.nav-pills.nav-justified 7 | %li 8 | = link_to(announcements_path) do 9 | %i.fas.fa-bullhorn 10 | News 11 | %li 12 | = link_to(about_path) do 13 | %i.fas.fa-info 14 | About 15 | %li 16 | = link_to('https://www.flickr.com/groups/hackweek/pool/') do 17 | %i.fas.fa-images 18 | Flickr Gallery 19 | %li 20 | = link_to(faqs_path) do 21 | %i.fas.fa-question 22 | FAQ 23 | %li 24 | = link_to('https://github.com/SUSE/hackweek/blob/master/CREDITS.md') do 25 | %i.fas.fa-heart 26 | Credits 27 | %p.small{style: 'padding-top: 25px'} 28 | © 29 | = Time.current.year 30 | SUSE. 31 | This tool is 32 | = link_to('free software,', 'https://www.gnu.org/philosophy/free-sw.html') 33 | you can run, copy, distribute, study, change and improve it. 34 | The source code and the developers are on 35 | = link_to('GitHub.', 'https://github.com/SUSE/hackweek') 36 | -------------------------------------------------------------------------------- /app/views/layouts/_news.html.haml: -------------------------------------------------------------------------------- 1 | %div{:class=>"alert alert-warning", :id=> "announcement-#{@news.id}"} 2 | .media.text-center 3 | = render :partial => "announcements/file_buttons", :locals => {:announcement => @news } 4 | .media-body 5 | %h3.media-heading 6 | = @news.title 7 | = simple_format(@news.text) 8 | = button_to 'Got it!', enroll_announcement_path(@news), :method => :get, :remote=>true, :class => "btn btn-success pull-right", :title=> "Dismiss this announcement" 9 | -------------------------------------------------------------------------------- /app/views/layouts/_notification_tab.html.haml: -------------------------------------------------------------------------------- 1 | %ul.navbar-notifications.nav.navbar-nav.navbar-right.hidden-xs 2 | %li.dropdown 3 | - if @notifications.any? 4 | = link_to mark_as_read_notifications_path(), id: 'notification-dropdown-activator', method: :post, data: { 'toggle': 'dropdown', 'behavior': 'notifications' }, remote: true do 5 | %i.fa.fa-bell 6 | %span#notification-count.new.badge 7 | = @notifications.length 8 | %b.caret 9 | - else 10 | %a.dropdown-toggle.nav-link{ 'data-toggle': 'dropdown', href: '#', id: 'notification-dropdown-activator', 'data-behavior': 'notifications' } 11 | %i.fa.fa-bell 12 | %span#notification-count.new.badge 13 | %b.caret 14 | .dropdown-menu 15 | %h5 Notifications 16 | %hr 17 | %ul#notification-dropdown 18 | -if @notifications.any? 19 | - @notifications.each do |notification| 20 | %li{ 'data-behavior':'notifications-link' } 21 | = link_to project_path(notification.notifiable) do 22 | - if notification.notifiable.avatar.attached? 23 | = image_tag url_for(notification.notifiable.avatar) 24 | %p 25 | #{notification.actor.name} #{notification.action} 26 | %hr 27 | - else 28 | %li.dropdown-item{ 'data-behavior': 'notifications-link' } No New Notifications 29 | = link_to notifications_path do 30 | %li{ 'data-behavior': 'notifications-link' } Older Notification 31 | -------------------------------------------------------------------------------- /app/views/layouts/_scripts.html.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | var pkBaseURL = (("https:" == document.location.protocol) ? "https://beans.opensuse.org/piwik/" : "http://beans.opensuse.org/piwik/"); 3 | document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E")); 4 | :javascript 5 | try { 6 | var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 23); 7 | piwikTracker.trackPageView(); 8 | piwikTracker.enableLinkTracking(); 9 | } catch( err ) {} 10 | %noscript 11 | %p 12 | %img{:src=>"http://beans.opensuse.org/piwik/piwik.php?idsite=23", :style=>"border:0", :alt=>''} -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | %html{:xmlns => "http://www.w3.org/1999/html"} 2 | %head 3 | %title 4 | - if @project 5 | SUSE Hack Week: 6 | = @project.title 7 | -else 8 | SUSE Hack Week 9 | %meta{:charset=> "utf-8"} 10 | %meta{:name => "viewport", :content =>"width=device-width, initial-scale=1.0"} 11 | = favicon_link_tag 'favicon.gif' 12 | = stylesheet_link_tag "application", :media => "all" 13 | %meta{:property => "og:site_name", :content => "SUSE Hack Week"} 14 | %meta{:property => "og:image", :content => "#{ image_url('hackweek-label-small.png', host: 'https://hackweek.opensuse.org') }" } 15 | - if @project 16 | %meta{ :property => "og:description", :content => "A SUSE #{@episode.try(:name)} Project" } 17 | %meta{:property => "og:title", :content => "#{ @project.title }"} 18 | -else 19 | %meta{:property => "og:description", :content => "Hack Week is the time SUSE employees and openSUSE community members experiment, innovate & learn interruption-free for a whole week."} 20 | = javascript_include_tag "application" 21 | = csrf_meta_tags 22 | %body 23 | = render :partial => "layouts/header" 24 | .container 25 | %div#loader 26 | #content 27 | - if flash 28 | #flash 29 | = render partial: "layouts/alert", flash: flash 30 | - if @news 31 | = render partial: "layouts/news" 32 | = yield 33 | = render :partial => "layouts/footer" 34 | 35 | = render :partial => "layouts/scripts" if Rails.env.production? 36 | = yield :script 37 | 38 | .modal.fade#modal{ role: :dialog, aria_hidden: true } 39 | .modal-dialog 40 | .modal-content 41 | .modal-header#modal-header 42 | .modal-body#modal-body 43 | .modal-footer 44 | %button.btn.btn-default{ type: :button, data:{ dismiss: :modal }} Close 45 | -------------------------------------------------------------------------------- /app/views/markdown/_preview.html.haml: -------------------------------------------------------------------------------- 1 | :markdown 2 | #{markdown_source} 3 | -------------------------------------------------------------------------------- /app/views/markdown/preview.js.erb: -------------------------------------------------------------------------------- 1 | $('#<%= params[:form_parent]%> .preview-contents').html("<%= escape_javascript(render partial: 'preview', locals: { markdown_source: @markdown_source }) %>"); 2 | $('#<%= params[:form_parent]%> .loading-spinner').addClass('hidden'); 3 | $('#<%= params[:form_parent]%> .preview-contents').removeClass('hidden'); 4 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 5 | $('meta[name="csrf-token"]').attr('content', '<%=form_authenticity_token%>'); 6 | -------------------------------------------------------------------------------- /app/views/notifications/index.html.haml: -------------------------------------------------------------------------------- 1 | %h3 Your Notifications 2 | %hr 3 | %br 4 | 5 | .older-notifications 6 | - @older_notifications.each do |notification| 7 | = link_to project_path(notification.notifiable) do 8 | %p 9 | - if notification.notifiable.avatar.attached? 10 | = image_tag(url_for(notification.notifiable.avatar), width: 40) 11 | #{notification.actor.name} #{notification.action} 12 | %span.pull-right (#{ time_ago_in_words(notification.created_at) } ago) 13 | %hr 14 | .notification_pagination 15 | = paginate @older_notifications 16 | -------------------------------------------------------------------------------- /app/views/notifications/mark_as_read.js.erb: -------------------------------------------------------------------------------- 1 | $('#notification-count').hide(); 2 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 3 | $('meta[name="csrf-token"]').attr('content', '<%=form_authenticity_token%>'); 4 | -------------------------------------------------------------------------------- /app/views/projects/_episode_buttons.html.haml: -------------------------------------------------------------------------------- 1 | - if can? :add_episode, @project 2 | .btn-group.pull-right 3 | %button.btn.btn-success.dropdown-toggle{"data-toggle" => "dropdown", :type => "button"} 4 | %i.fas.fa-plus 5 | Add Hackweek 6 | %span.caret 7 | %ul.dropdown-menu{:role => "menu"} 8 | - Episode.all.reverse.each do |episode| 9 | %li{:role => "presentation"} 10 | = link_to(truncate(episode.name, length: 50), episode_project_path(:episode_id => episode.id), method: :post, remote: true) -------------------------------------------------------------------------------- /app/views/projects/_episode_list.html.haml: -------------------------------------------------------------------------------- 1 | - unless @project.episodes.any? 2 | No hackweek. 3 | - if can? :delete_episode, @project 4 | How about you add one... 5 | - @project.episodes.each do |episode| 6 | %span.label.label-primary{style: 'display: inline-block'} 7 | = truncate(episode.name, length: 50) 8 | - if can? :delete_episode, @project 9 | = link_to(episode_project_path(:episode_id => episode.id), method: :delete, remote: true) do 10 | %i.fas.fa-times{:style=>"color: #fff"} -------------------------------------------------------------------------------- /app/views/projects/_file_buttons.html.haml: -------------------------------------------------------------------------------- 1 | .btn-group 2 | - if current_user 3 | - hidden = current_user.project_followings.include?(@project) ? '' : 'hidden' 4 | = link_to(project_followers_path(@project), method: :delete, class: "btn btn-default #{hidden}", title: 'Unfollow this project?', id: "unfollow-#{@project.id}", remote: true) do 5 | %i.fas.fa-eye-slash 6 | - hidden = current_user.project_followings.include?(@project) ? 'hidden' : '' 7 | = link_to(project_followers_path(@project), method: :post, class: "btn btn-default #{hidden}", title: 'Follow this project?', id: "follow-#{@project.id}", remote: true) do 8 | %i.far.fa-eye 9 | - if can? :edit, @project 10 | = link_to(edit_project_path(@episode, @project), title: 'Edit this project?', class: "btn btn-default", id: "project#{@project.to_param}-edit-link") do 11 | %i.fas.fa-pencil-alt 12 | - if @project.may_advance? 13 | - case @project.aasm_state 14 | - when "project" 15 | =link_to(advance_project_path(@episode, @project), method: :post, title: 'Finish this project', class: "btn btn-default", id: "project#{@project.to_param}-advance-link") do 16 | %i.fas.fa-check 17 | - when "record" 18 | =link_to(advance_project_path(@episode, @project), method: :post, title: 'Revive this project', class: "btn btn-default", id: "project#{@project.to_param}-advance-link") do 19 | %i.fas.fa-bolt 20 | - if @project.may_recess? 21 | - case @project.aasm_state 22 | - when "idea" 23 | =link_to(recess_project_path(@episode, @project), :method => :post,:title=>'Archive this project', :class=> "btn btn-default", id: "project#{@project.to_param}-recess-link") do 24 | %i.fas.fa-archive 25 | - when "invention" 26 | =link_to(recess_project_path(@episode, @project), :method => :post,:title=>"Restart this project", :class=> "btn btn-default", id: "project#{@project.to_param}-recess-link") do 27 | %i.fas.fa-lightbulb 28 | -if can? :destroy, @project 29 | = link_to(project_path(@episode, @project), :title=>'Delete this project?', :class=> "btn btn-default", id: "project#{@project.to_param}-delete-link", :method => :delete, data: { confirm: "Are you sure you want to delete this project? This can't be undone!" }) do 30 | %i.fas.fa-trash 31 | -------------------------------------------------------------------------------- /app/views/projects/_form.html.haml: -------------------------------------------------------------------------------- 1 | #project_form 2 | = form_for @project, url: (@project.new_record? ? projects_path : project_path(@episode, @project)), html: { role: :form } do |f| 3 | - if @project.errors.any? 4 | .alert.alert-warning 5 | %strong 6 | = pluralize(@project.errors.count, 'error') 7 | prohibited this project from being saved: 8 | %ul 9 | - @project.errors.full_messages.each do |msg| 10 | %li 11 | = msg 12 | 13 | .form-group 14 | = f.text_field :title, placeholder: 'Project title', class: 'form-control input-lg', required: 'required' 15 | 16 | .panel.panel-default 17 | .panel-heading 18 | %ul.nav.nav-pills 19 | %li.active.show-source{ role: :presentation } 20 | %a{ href: '#markdown-source', role: :tab, data: { toggle: :tab } } Edit 21 | %li.show-preview{ role: :presentation } 22 | %a{ href: '#markdown-preview', role: :tab, data: { toggle: :tab } } Preview 23 | .btnbar 24 | = render 'shared/editor_buttons' 25 | 26 | .panel-body 27 | .tab-content 28 | #markdown-source.tab-pane.active.fade.in{ role: 'tab-pane' } 29 | .form-group 30 | = f.text_area :description, rows: 20, id: 'project_description', 31 | class: 'form-control input-lg' 32 | #markdown-preview.tab-pane.fade{ role: 'tab-pane' } 33 | .loading-spinner 34 | = icon('fas', 'spinner pulse 3x') 35 | .preview-contents.hidden 36 | = render partial: 'comments/help' 37 | 38 | .form-group 39 | = f.label('Project Logo (will be shown next to the title)') 40 | = f.file_field :avatar 41 | = f.submit class: 'btn btn-success pull-right' 42 | 43 | :javascript 44 | $('textarea').atwho({at:"@", 'data':#{raw @username_array}}); 45 | -------------------------------------------------------------------------------- /app/views/projects/_hackers.html.haml: -------------------------------------------------------------------------------- 1 | - if @project.users.empty? 2 | %h4 No Hackers yet 3 | - else 4 | .well.well-sm 5 | - @project.users.each do |user| 6 | = link_to(user_path(user)) do 7 | = image_tag user.gravatar_url(:size => "64"), alt: user.name, title: user.name, class: "img-thumbnail", id: "user#{user.id}-gravatar" 8 | -------------------------------------------------------------------------------- /app/views/projects/_info.html.haml: -------------------------------------------------------------------------------- 1 | Updated 2 | = "#{time_ago_in_words project.updated_at}" 3 | ago. 4 | - if project.kudos.length > 0 5 | = pluralize(project.kudos.length, "hackers ♥️.", "hacker ♥️.") 6 | -else 7 | No love. 8 | 9 | - if project.project_followers.any? 10 | = link_to(project_followers_path(project)) do 11 | = pluralize(project.project_followers.count, 'follower.', 'followers.') 12 | 13 | - if project.users.empty? 14 | Has no hacker: 15 | = link_to 'grab it!', join_project_path(project.id), method: :post, remote: true 16 | -------------------------------------------------------------------------------- /app/views/projects/_like_button.html.haml: -------------------------------------------------------------------------------- 1 | - project = @project if @project 2 | - dislike = "display: none;" unless project.kudos.include? current_user 3 | = link_to(dislike_project_path(project.id), :title=>'Dislike this project?', :class=> "btn btn-default btn-xs", :id => "dislike-#{project.id}", :style => "#{dislike}", :remote=>true) do 4 | %i.fas.fa-heart{:style => "color: #73ba25;"} 5 | - like = "display: none;" if project.kudos.include? current_user 6 | =link_to(like_project_path(project.id), :title=>'Like this project', :class=> "btn btn-default btn-xs", :id => "like-#{project.id}", :style => "#{like}", :remote => true) do 7 | %i.far.fa-heart 8 | -------------------------------------------------------------------------------- /app/views/projects/_like_toggle.js.erb: -------------------------------------------------------------------------------- 1 | $("#like-<%= @project.id %>").toggle(); 2 | $("#dislike-<%= @project.id %>").toggle(); 3 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 4 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 5 | -------------------------------------------------------------------------------- /app/views/projects/_list.html.haml: -------------------------------------------------------------------------------- 1 | -if projects 2 | - projects.each_with_index do |project, index| 3 | %div{class: "#{project.aasm_state}", style: "border-bottom: 1px solid #ddd; padding: 10px 0 10px 0;"} 4 | = render :partial => "projects/list_item", :locals => {:project => project, :index => index } 5 | -------------------------------------------------------------------------------- /app/views/projects/_list_item.html.haml: -------------------------------------------------------------------------------- 1 | %h4.project-list-item{:id => "project-#{index}"} 2 | = link_to project_path(@episode, project) do 3 | - if project.avatar.attached? 4 | = image_tag(url_for(project.avatar), width: '20') 5 | = project.title 6 | 7 | %p{:style=>"margin-top: 0px"} 8 | %small 9 | = render :partial => "projects/state_name", :locals => {:project => project } 10 | by 11 | = link_to(user_path(project.originator)) do 12 | #{project.originator.name} 13 | = render :partial => "projects/like_button", :locals => {:project => project } 14 | .md-preview 15 | :markdown 16 | #{enrich_markdown(markdown: project.description, lines: 2)} 17 | - unless project.users.empty? 18 | .well.well-sm 19 | - project.users.each do |user| 20 | = link_to user_path(user) do 21 | = image_tag(user.gravatar_url(:size => "24"), :class => "img-thumbnail", :title => "#{user.name}", :alt => user.name) 22 | %span.help-block 23 | %small 24 | = render :partial => "projects/info", :locals => {:project => project } 25 | -------------------------------------------------------------------------------- /app/views/projects/_membership_buttons.html.haml: -------------------------------------------------------------------------------- 1 | - joined = @project.joined(current_user) ? 'hidden': '' 2 | = link_to(join_project_path(@episode, @project), :method => :post, :class => "btn btn-success #{joined}", remote: true) do 3 | %i.fas.fa-plus 4 | Join this project 5 | - joined = @project.joined(current_user) ? '': 'hidden' 6 | = link_to(leave_project_path(@episode, @project), :method => :post, :class => "btn btn-warning #{joined}", remote: true) do 7 | %i.fas.fa-minus 8 | Leave this project -------------------------------------------------------------------------------- /app/views/projects/_similar_projects.html.haml: -------------------------------------------------------------------------------- 1 | #accordion.panel-group{'aria-multiselectable': 'true', role: 'tablist'} 2 | - @similar_projects_keys.each_with_index do |word, i| 3 | .panel.panel-default 4 | .panel-heading{role: 'tab', id: "heading_#{i}"} 5 | %h4.panel-title 6 | %a{'aria-controls': "collapse_#{i}", 'aria-expanded': 'true', 'data-parent': '#accordion', 'data-toggle': 'collapse', href: "#collapse_#{i}", role: 'button'} 7 | = word.name 8 | %i.fas.fa-caret-right 9 | .panel-collapse.collapse{id: "collapse_#{i}", 'aria-labelledby': "heading_#{i}", role: 'tabpanel'} 10 | .panel-body 11 | - similar_projects = (word.projects.current(Episode.active)-[@project]).sample(5) 12 | - similar_projects.each do |project| 13 | %h5 14 | = link_to(project.title, project_path(project)) 15 | by 16 | = link_to(project.originator.name, user_path(project.originator)) 17 | %p 18 | :markdown 19 | #{enrich_markdown(markdown: project.description, lines: 50)} 20 | %hr 21 | -------------------------------------------------------------------------------- /app/views/projects/_state_name.html.haml: -------------------------------------------------------------------------------- 1 | - case project.aasm_state 2 | - when "idea" 3 | an 4 | - when "invention" 5 | an 6 | - else 7 | a 8 | = project.aasm_state -------------------------------------------------------------------------------- /app/views/projects/_tabs.html.haml: -------------------------------------------------------------------------------- 1 | %ul.nav.nav-tabs 2 | - all_state = "active" if action_name == "index" 3 | %li{:class => all_state} 4 | =link_to projects_path(@episode), :title => "All projects", :remote => true do 5 | %span 6 | All 7 | - popular_state = "active" if action_name == "popular" 8 | %li{:class => popular_state} 9 | =link_to popular_projects_path(@episode), :title => "Projects with many likes", :remote => true do 10 | %span.hidden-xs 11 | Popular 12 | %i.fas.fa-star.visible-xs 13 | - biggest_state = "active" if action_name == "biggest" 14 | %li{:class => biggest_state} 15 | =link_to biggest_projects_path(@episode), :title => "Projects with many users", :remote => true do 16 | %span.hidden-xs 17 | Crowded 18 | %i.fas.fa-user.visible-xs 19 | - finished_state = "active" if action_name == "finished" 20 | %li{:class => finished_state} 21 | =link_to finished_projects_path(@episode), :title => "Projects that are done", :remote => true do 22 | %span.hidden-xs 23 | Finished 24 | %i.fas.fa-check.visible-xs 25 | - archived_state = "active" if action_name == "archived" 26 | %li{:class => archived_state} 27 | =link_to archived_projects_path(@episode), :title => "Projects that are dismissed", :remote => true do 28 | %span.hidden-xs 29 | Archived 30 | %i.fas.fa-archive.visible-xs 31 | 32 | %li.pull-right 33 | =link_to projects_path(@episode, @project, format: :rss), title: 'New projects feed' do 34 | %i.fas.fa-rss 35 | %span 36 | RSS 37 | -------------------------------------------------------------------------------- /app/views/projects/_tile.html.haml: -------------------------------------------------------------------------------- 1 | .panel.panel-info 2 | .panel-heading 3 | %h3.panel-title 4 | = link_to project_path(project) do 5 | = project.title 6 | = render :partial => "projects/like_button", :locals => {:project => project } 7 | .panel-body 8 | :markdown 9 | #{enrich_markdown(markdown: project.description, lines: 140)} 10 | .user-list{:style=>"padding-top: 10px;"} 11 | - if project.users.empty? 12 | .alert.alert-warning 13 | Holy moly! This project has no developer yet and is up for 14 | = link_to 'grabs!', join_project_path(project.id), :class => "alert-link" 15 | - else 16 | .well.well-sm 17 | - project.users.each do |user| 18 | = link_to user_path(user) do 19 | = image_tag(user.gravatar_url(:size => "24"), :class => "img-thumbnail", :title => "#{user.name}", :alt => user.name) 20 | .panel-footer 21 | %small 22 | an idea by 23 | = link_to(user_path(project.originator)) do 24 | #{project.originator.name}, 25 | updated 26 | = "#{time_ago_in_words project.updated_at}" 27 | ago 28 | -------------------------------------------------------------------------------- /app/views/projects/edit.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Edit 3 | = @project.title 4 | 5 | .row 6 | .col-sm-12.project 7 | %h2 8 | Edit project 9 | = @project.title 10 | = render 'form' 11 | -------------------------------------------------------------------------------- /app/views/projects/episode_list.js.erb: -------------------------------------------------------------------------------- 1 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 2 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 3 | $('#flash').html("<%= escape_javascript render partial: "layouts/alert", flash: flash %>"); 4 | $('#episode_list').html("<%= escape_javascript render partial: 'episode_list' %>"); 5 | -------------------------------------------------------------------------------- /app/views/projects/index.html.haml: -------------------------------------------------------------------------------- 1 | = render :partial => "projects/index" 2 | - content_for :script do 3 | :javascript 4 | function projectPageCall(){ 5 | $( "#ideas" ).click(function() { 6 | console.log ("show only ideas"); 7 | $( ".invention" ).hide(); 8 | $( ".record" ).hide(); 9 | $( ".project" ).hide(); 10 | $( ".idea" ).show(); 11 | $( "#ideas-pill").addClass( "active" ); 12 | $( "#all-pill, #projects-pill").removeClass( "active" ); 13 | }); 14 | $( "#projects" ).click(function() { 15 | console.log ("show only projects"); 16 | $( ".invention" ).hide(); 17 | $( ".record" ).hide(); 18 | $( ".idea" ).hide(); 19 | $( ".project" ).show(); 20 | $( "#projects-pill").addClass( "active" ); 21 | $( "#all-pill, #ideas-pill").removeClass( "active" ); 22 | }); 23 | $( "#all" ).click(function() { 24 | console.log ("show everything"); 25 | $( ".invention" ).show(); 26 | $( ".record" ).show(); 27 | $( ".idea" ).show(); 28 | $( ".project" ).show(); 29 | $( "#all-pill").addClass( "active" ); 30 | $( "#projects-pill, #ideas-pill").removeClass( "active" ); 31 | }); 32 | $("table").addTableFilter({ 33 | }); 34 | 35 | Mousetrap.bind('j', function() { ProjectNext(); }); 36 | Mousetrap.bind('k', function() { ProjectPrevious(); }); 37 | 38 | function ProjectPrevious() { 39 | hash = window.location.hash 40 | all = $("h4[id^='project-']").length 41 | if (hash == "") 42 | window.location.hash = "#project-0"; 43 | else 44 | var blah = hash.split('-') 45 | next = parseInt(blah[1]) 46 | if(next > 0) 47 | next-- 48 | else 49 | next = all - 1 50 | window.location.hash = "#project-" + next; 51 | } 52 | 53 | function ProjectNext() { 54 | hash = window.location.hash 55 | all = $("h4[id^='project-']").length 56 | if (hash == "") 57 | window.location.hash = "#project-0"; 58 | else 59 | var blah = hash.split('-') 60 | next = parseInt(blah[1]) 61 | if(next == (all - 1)) 62 | next = 0 63 | else 64 | next++ 65 | window.location.hash = "#project-" + next; 66 | } 67 | } 68 | projectPageCall(); 69 | -------------------------------------------------------------------------------- /app/views/projects/index.js.erb: -------------------------------------------------------------------------------- 1 | $("#content").html($("<%= escape_javascript(render 'projects/index') %>")) 2 | projectPageCall(); 3 | history.pushState({isAjax: true}, null, clickedLink); 4 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 5 | $(window).scrollTop(0); 6 | -------------------------------------------------------------------------------- /app/views/projects/index.rss.haml: -------------------------------------------------------------------------------- 1 | !!! XML 2 | %rss(version="2.0" 3 | xmlns:content="http://purl.org/rss/1.0/modules/content/" 4 | xmlns:atom="http://www.w3.org/2005/Atom") 5 | %channel 6 | %link #{ newest_projects_url format: :rss } 7 | %atom:link(href="#{ newest_projects_url format: :rss }" 8 | rel="self" 9 | type="application/rss+xml") 10 | 11 | -if @episode.is_a? Episode 12 | %title Newest #{ @episode.name } projects 13 | %description These are the newest projects for #{ @episode.name } 14 | -else 15 | %title Newest projects (all Hackweeks) 16 | %description These are the newest projects for all Hackweeks 17 | 18 | -@newest.each do |project| 19 | %item 20 | %title #{ project.title } 21 | %link #{ project_url(project) } 22 | %description #{ project.description } 23 | %author #{ project.originator.email } (#{ project.originator.name }) 24 | %pubDate #{ project.created_at.rfc822 } 25 | %guid(isPermaLink='true') #{ project_url(project) } 26 | -------------------------------------------------------------------------------- /app/views/projects/membership_list.js.erb: -------------------------------------------------------------------------------- 1 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 2 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 3 | $('#flash').html("<%= escape_javascript render partial: "layouts/alert", flash: flash %>"); 4 | $('#hackers').html("<%= escape_javascript render partial: 'hackers' %>"); 5 | $('#membership_buttons').html("<%= escape_javascript render partial: 'membership_buttons' %>"); 6 | $('#file_buttons').html("<%= escape_javascript render partial: 'file_buttons' %>"); 7 | $('#episode_list').html("<%= escape_javascript render partial: 'episode_list' %>"); 8 | $('#episode_buttons').html("<%= escape_javascript render partial: 'episode_buttons' %>"); 9 | $('#activity').html("<%= escape_javascript render partial: 'updates/activity' %>"); 10 | $('#project_info').html("<%= escape_javascript render partial: 'info', locals: {:project => @project } %>"); 11 | -------------------------------------------------------------------------------- /app/views/projects/new.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Create Project 3 | 4 | .row 5 | .col-sm-12.project 6 | %h2 7 | Create a project 8 | %p 9 | Please use the description to give an overview about the state of your project. 10 | %ul 11 | %li 12 | What are your goals to be achieved at the end of this Hack Week? 13 | %li 14 | What kind collaboration are you looking for, which skills are needed? 15 | %li 16 | Link to sources, documentation or your detailed project plan. 17 | You can edit this at any time later and provide updates 18 | = render 'form' 19 | -------------------------------------------------------------------------------- /app/views/projects/project_follows/follow_toggle.js.erb: -------------------------------------------------------------------------------- 1 | $('input[name="authenticity_token"]').val('<%= form_authenticity_token %>'); 2 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 3 | $('#flash').html("<%= escape_javascript render partial: "layouts/alert", flash: flash %>"); 4 | $("#unfollow-<%= @project.id %>").toggleClass('hidden'); 5 | $("#follow-<%= @project.id %>").toggleClass('hidden'); 6 | $('#project_info').html("<%= escape_javascript render partial: 'projects/info', locals: {:project => @project } %>"); 7 | -------------------------------------------------------------------------------- /app/views/projects/project_follows/index.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | = @project.title 3 | Followers 4 | 5 | .row 6 | .col-sm-12 7 | = render partial: 'users/list' -------------------------------------------------------------------------------- /app/views/search/result.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Search result for: #{params[:q]} 3 | 4 | .row 5 | .col-sm-12 6 | %ul.nav.nav-tabs 7 | %li.active 8 | = link_to "#projects", "data-toggle"=>"tab" do 9 | Projects 10 | = "(#{@projects.length})" 11 | %li 12 | = link_to "#users", "data-toggle"=>"tab" do 13 | People 14 | = "(#{@users.length})" 15 | 16 | .tab-content 17 | .tab-pane.active#projects 18 | - unless @projects.empty? 19 | %table.table#project_table 20 | %thead 21 | - @projects.each_with_index do |project, index| 22 | %tr 23 | %td{:class => "#{project.aasm_state}"} 24 | = render :partial => "projects/list_item", :locals => {:project => project, :index => index } 25 | - else 26 | %h4 No projects found 27 | .tab-pane#users 28 | - unless @users.empty? 29 | %table.table#users_table 30 | - @users.each do |user| 31 | %tr 32 | - user_class = "busy" 33 | - if user.projects.empty? 34 | - user_class = "free" 35 | %td{:class => user_class } 36 | = link_to user_path(user) do 37 | = image_tag(user.gravatar_url(:size => "24"), :class => "img-thumbnail", :title => "#{user.name}", :alt => user.name) 38 | = user.name 39 | - else 40 | %h4 No users found 41 | -------------------------------------------------------------------------------- /app/views/shared/_editor_buttons.html.haml: -------------------------------------------------------------------------------- 1 | - @textarea = "$(this).closest('form').find('textarea')" 2 | %a.btn.btn-default{onclick: "#{@textarea}.val(#{@textarea}.val() + ' @').click();"} 3 | %i.fas.fa-at 4 | %a.btn.btn-default{onclick: "#{@textarea}.val(#{@textarea}.val() + ' :').click();"} 5 | %i.fas.fa-smile 6 | %a.btn.btn-default{onclick: "#{@textarea}.val(#{@textarea}.val() + ' [text](link-here)').click();"} 7 | %i.fas.fa-link 8 | %a.btn.btn-default{onclick: "textcover(#{@textarea}[0], '_')"} 9 | %i.fas.fa-italic 10 | %a.btn.btn-default{onclick: "textcover(#{@textarea}[0], '**')"} 11 | %i.fas.fa-bold 12 | -------------------------------------------------------------------------------- /app/views/updates/_activity.html.haml: -------------------------------------------------------------------------------- 1 | #activity 2 | %h4 3 | Activity 4 | - if @updates.blank? 5 | %i 6 | No activity yet. Do something! 7 | - else 8 | %p 9 | %ul 10 | - @updates.each do |update| 11 | %li 12 | = render :partial => "updates/show", :locals => { :update => update } 13 | %span.pull-right 14 | = render :partial => "updates/show_all_modal", :locals => { :updates => @updates } 15 | - unless @last_page 16 | %a{:href=>"#UpdatesModal", :role=>"button", :class=>"btn btn-default pull-right", "data-toggle"=>"modal"} 17 | %i.fas.fa-info 18 | All Activity 19 | -------------------------------------------------------------------------------- /app/views/updates/_more.html.haml: -------------------------------------------------------------------------------- 1 | - @updates.each do |update| 2 | %p 3 | = render :partial => "updates/show", :locals => { :update => update } 4 | -------------------------------------------------------------------------------- /app/views/updates/_show.html.haml: -------------------------------------------------------------------------------- 1 | %em 2 | %small= "#{time_ago_in_words update.created_at} ago:" 3 | = link_to "#{update.author.name}", user_path(update.author) 4 | %small 5 | = " #{update.text}" 6 | - if @project 7 | this project. 8 | - else 9 | = link_to "#{update.project.title}", project_path(update.project) 10 | -------------------------------------------------------------------------------- /app/views/updates/_show_all_modal.html.haml: -------------------------------------------------------------------------------- 1 | .modal.fade{:id=>"UpdatesModal", :tabindex=>"-1", :role=>"dialog", "aria-labelledby"=>"UpdatesModalLabel", "aria-hidden"=>"true"} 2 | .modal-dialog 3 | .modal-content 4 | .modal-header 5 | %button{:type=>"button", :class=>"close", "data-dismiss"=>"modal", "aria-hidden"=>"true"}× 6 | %h3{:id=>"UpdatesModal"} 7 | All Updates 8 | .modal-body 9 | #updates 10 | = render :partial => "updates/more" 11 | - unless @last_page 12 | .text-center 13 | = link_to updates_path(page: 2, project_id: @project.try(:id)), id: 'load-more-link', class: 'btn btn-default', remote: true do 14 | %i.fas.fa-chevron-circle-down 15 | Load More Updates 16 | .modal-footer#modal-bottom 17 | %button{:class=>"btn btn-primary", "data-dismiss"=>"modal", "aria-hidden"=>"true"}Close -------------------------------------------------------------------------------- /app/views/updates/index.js.erb: -------------------------------------------------------------------------------- 1 | $('#updates').append("<%= escape_javascript render partial: "updates/more", locals: { updates: @updates } %>"); 2 | $('#load-more-link').attr('href', '<%= raw updates_path(page: @next_page, project_id: @project.try(:id)) %>'); 3 | $('#UpdatesModal').animate({scrollTop: $("#modal-bottom").offset().top}, 1000); 4 | <% if @last_page %> 5 | $('#load-more-link').hide(); 6 | <% end %> 7 | 8 | -------------------------------------------------------------------------------- /app/views/users/_buttons.html.haml: -------------------------------------------------------------------------------- 1 | .btn-group 2 | .btn.btn-default{:title=>"#{user.name} had #{user.originated_projects.length} ideas"} 3 | = user.originated_projects.length 4 | %i.fas.lightbulb-o 5 | .btn.btn-default{:title=>"#{user.name} works on #{user.projects.length} projects"} 6 | = user.projects.length 7 | %i.fas.fa-puzzle-piece 8 | .btn.btn-default{:title=>"#{user.name} wrote #{user.comments.length} comments"} 9 | = user.comments.length 10 | %i.fas.fa-comments 11 | .btn.btn-default{:title=>"#{user.name} likes #{user.likes.length} projects"} 12 | = user.likes.length 13 | %i.fas.fa-heart 14 | -------------------------------------------------------------------------------- /app/views/users/_empty_projects.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | - if action_name == 'show' 3 | No project yet, join one! 4 | - if @user == current_user 5 | Check out your 6 | = link_to opportunities_user_path(@user) do 7 | opportunities. 8 | - if action_name == "opportunities" 9 | No project is matching your keywords yet, try adding some more on the right. 10 | - if action_name == "originated" 11 | No ideas yet. 12 | - if @user == current_user 13 | How about you 14 | = link_to new_project_path do 15 | create one? 16 | - if action_name == "likes" 17 | No likes yet, spread some 18 | %i.fas.fa-heart -------------------------------------------------------------------------------- /app/views/users/_list.html.haml: -------------------------------------------------------------------------------- 1 | - if @users.length > 0 2 | .row 3 | .col-sm-12 4 | %table.table#users_table 5 | - @users.each do |user| 6 | %tr 7 | %td 8 | = link_to user_path(user) do 9 | = image_tag(user.gravatar_url(:size => "24"), :class => "img-thumbnail", :title => "#{user.name}", :alt => user.name) 10 | = user.name 11 | - if current_user.role? :admin 12 | = link_to edit_user_path(user) do 13 | %i{class: 'fas fa-pencil-alt', title: 'Edit'} 14 | .row 15 | .col-md-12 16 | .text-center 17 | = paginate @users, :remote => true 18 | - else 19 | %h3 20 | No users yet. 21 | 22 | :javascript 23 | $( "#free" ).click(function() { 24 | $( ".free" ).show(); 25 | $( ".busy" ).hide(); 26 | $( "#free-pill").addClass( "active" ); 27 | $( "#all-pill, #busy-pill").removeClass( "active" ); 28 | }); 29 | 30 | $( "#busy" ).click(function() { 31 | $( ".free" ).hide(); 32 | $( ".busy" ).show(); 33 | $( "#busy-pill").addClass( "active" ); 34 | $( "#all-pill, #free-pill").removeClass( "active" ); 35 | }); 36 | 37 | $( "#all" ).click(function() { 38 | console.log ("show everything"); 39 | $( ".free" ).show(); 40 | $( ".busy" ).show(); 41 | $( "#all-pill").addClass( "active" ); 42 | $( "#busy-pill, #free-pill").removeClass( "active" ); 43 | }); 44 | 45 | $("table").addTableFilter({ 46 | }); 47 | -------------------------------------------------------------------------------- /app/views/users/_tabs.html.haml: -------------------------------------------------------------------------------- 1 | %ul.nav.nav-tabs 2 | - works_state = "active" if action_name == "show" 3 | %li{:class => works_state} 4 | =link_to user_path(@user), :title => "Ideas #{@user.name} came up with" do 5 | %span 6 | Works on 7 | - originated_state = "active" if action_name == "originated" 8 | %li{:class => originated_state} 9 | =link_to originated_user_path(@user), :title => "Ideas #{@user.name} came up with" do 10 | %span 11 | Originated 12 | - likes_state = "active" if action_name == "likes" 13 | %li{class: likes_state} 14 | =link_to likes_user_path(@user), title: "Things #{@user.name} likes" do 15 | %span.hidden-xs 16 | Likes 17 | %i.fas.fa-star.visible-xs 18 | - opportunities_state = "active" if action_name == "opportunities" 19 | %li{:class => opportunities_state} 20 | =link_to opportunities_user_path(@user), title: "Things #{@user.name} could hack on" do 21 | %span.hidden-xs 22 | Opportunities 23 | %i.fas.fa-user.visible-xs 24 | -------------------------------------------------------------------------------- /app/views/users/edit.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Edit 3 | = @user.name 4 | 5 | .row 6 | .col-md-12 7 | %h2 8 | Edit 9 | = @user.name 10 | = form_for(@user, html: { class: 'form-horizontal' }) do |f| 11 | - if @user.errors.any? 12 | #error_explanation 13 | %h2 14 | = pluralize(@user.errors.count, "error") 15 | prohibited this user from being saved: 16 | %ul 17 | - @user.errors.full_messages.each do |msg| 18 | %li 19 | = msg 20 | .form-group 21 | = f.label(:location, class: 'col-sm-2 control-label') 22 | .col-sm-10 23 | = f.text_field(:location, placeholder: "Location", class: 'form-control') 24 | .form-group 25 | .col-sm-offset-2.col-sm-10 26 | .checkbox 27 | %label 28 | =f.check_box(:hide_email) 29 | Hide Email? 30 | .form-group 31 | .col-sm-offset-2.col-sm-10 32 | = f.submit :class => "btn btn-default" 33 | -------------------------------------------------------------------------------- /app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Users 3 | 4 | .row 5 | .col-sm-12 6 | = render partial: 'list' -------------------------------------------------------------------------------- /app/views/users/index.js.erb: -------------------------------------------------------------------------------- 1 | $("#content").html($("<%= escape_javascript(render 'users/list') %>")) 2 | $('meta[name="csrf-token"]').attr('content','<%= form_authenticity_token %>'); 3 | -------------------------------------------------------------------------------- /app/views/users/show.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | User 3 | = @user.name 4 | 5 | .row 6 | .col-sm-1 7 | = image_tag(@user.gravatar_url(:size => "64"), :class => "img-thumbnail pull-left", :title => "#{@user.name}", :alt => @user.name) 8 | .col-sm-11 9 | %h1 10 | = @user.name 11 | .row 12 | .col-sm-12 13 | - if @user.location 14 | %p 15 | %i{class: 'fas fa-home', title: 'Location'} 16 | = @user.location 17 | - if current_user && !@user.hide_email? 18 | %p 19 | %i{class: 'fas fa-envelope', title: 'Email'} 20 | = mail_to @user.email 21 | .row 22 | .col-sm-8 23 | = render :partial => "users/tabs" 24 | .tab-content{:style=>"padding-top: 10px"} 25 | .tab-pane.active#projects 26 | - if @projects.empty? 27 | = render :partial => "empty_projects" 28 | - else 29 | = render :partial => "projects/list", locals: { projects: @projects } 30 | .col-sm-4 31 | .row 32 | .col-sm-12 33 | .text-center 34 | = render :partial => "users/buttons", :locals => {:user => @user } 35 | .row 36 | .col-sm-12#keywords 37 | %h4 Looking for projects around: 38 | - if @user.keywords.empty? 39 | %p Nothing at the moment 40 | - else 41 | = render :partial => "keywords/show", :locals => {:keywords => @user.keywords } 42 | -if can? :add_keyword, @user 43 | = render :partial => "keywords/new", :locals => {:what => @user } 44 | .row 45 | .col-sm-12 46 | = render partial: 'updates/activity' -------------------------------------------------------------------------------- /bin/mina: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'mina' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require 'pathname' 12 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path('bundle', __dir__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require 'rubygems' 27 | require 'bundler/setup' 28 | 29 | load Gem.bin_path('mina', 'mina') 30 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require 'pathname' 12 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path('bundle', __dir__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require 'rubygems' 27 | require 'bundler/setup' 28 | 29 | load Gem.bin_path('rspec-core', 'rspec') 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails' 4 | # Pick the frameworks you want: 5 | require 'active_model/railtie' 6 | # require "active_job/railtie" 7 | require 'active_record/railtie' 8 | require 'active_storage/engine' 9 | require 'action_controller/railtie' 10 | require 'action_mailer/railtie' 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require 'action_view/railtie' 14 | # require "action_cable/engine" 15 | require 'rails/test_unit/railtie' 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module Hackweek 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.0 25 | 26 | # Configuration for the application, engines, and railties goes here. 27 | # 28 | # These settings can be overridden in specific environments using the files 29 | # in config/environments, which are processed later. 30 | # 31 | # config.time_zone = "Central Time (US & Canada)" 32 | # config.eager_load_paths << Rails.root.join("extras") 33 | 34 | # Don't generate system test files. 35 | config.generators.system_tests = nil 36 | 37 | # Use the local disk to store files 38 | config.active_storage.service = :local 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /config/application.yml.example: -------------------------------------------------------------------------------- 1 | # Add application configuration variables here, as shown below. 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Make sure your secrets are at least 30 characters and all random, 5 | # no regular words or you'll be exposed to dictionary attacks. 6 | # You can use `rake secret` to generate a secure secret key. 7 | HACKWEEK_SECRET_KEY_BASE: '12345' 8 | HACKWEEK_DEVISE_SECRET_KEY: '12345' 9 | 10 | # Errbit (https://github.com/errbit/errbit) configuration 11 | # HACKWEEK_ERRBIT_API_KEY: '12345' 12 | # HACKWEEK_ERRBIT_HOST: errbit.example.com 13 | # HACKWEEK_ERRBIT_PROJECT_ID: '56kskscb02dc71fa0932000007' 14 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/database.yml.example: -------------------------------------------------------------------------------- 1 | <% 2 | if ENV['CONTAINER'] 3 | host = 'db' 4 | end 5 | %> 6 | 7 | default: &default 8 | adapter: mysql2 9 | encoding: utf8 10 | username: root 11 | password: root 12 | host: <%= host %> 13 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 14 | timeout: 5000 15 | 16 | development: 17 | <<: *default 18 | database: hackweek_development 19 | 20 | test: 21 | <<: *default 22 | database: hackweek_test 23 | 24 | production: 25 | <<: *default 26 | database: hackweek_production 27 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | require 'mina/rails' 2 | require 'mina/git' 3 | 4 | set :application_name, 'hackweek' 5 | set :domain, 'hackweek' 6 | set :port, '22' 7 | set :user, 'hackweek' 8 | set :deploy_to, '/home/hackweek/hackweek' 9 | set :repository, 'https://github.com/SUSE/hackweek.git' 10 | set :branch, 'master' 11 | 12 | # Shared dirs and files will be symlinked into the app-folder by the 'deploy:link_shared_paths' step. 13 | # Some plugins already add folders to shared_dirs like `mina/rails` add `public/assets`, `vendor/bundle` and many more 14 | # run `mina -d` to see all folders and files already included in `shared_dirs` and `shared_files` 15 | set :shared_dirs, fetch(:shared_dirs, []).push('public/system', 'public/img', 'sphinx', 'storage', '.bundle', 'tmp/pids') 16 | set :shared_files, fetch(:shared_files, []).push('config/database.yml', 17 | 'config/application.yml', 18 | 'config/secrets.yml', 19 | 'config/storage.yml', 20 | 'config/thinking_sphinx.yml', 21 | 'config/production.sphinx.conf', 22 | 'en.pak') 23 | 24 | desc 'Deploys the current version to the server.' 25 | task :deploy do 26 | deploy do 27 | # Put things that will set up an empty directory into a fully set-up 28 | # instance of your project. 29 | invoke :'git:clone' 30 | invoke :'deploy:link_shared_paths' 31 | invoke :'bundle:install' 32 | invoke :'rails:db_migrate' 33 | invoke :'rails:assets_precompile' 34 | invoke :'deploy:cleanup' 35 | 36 | on :launch do 37 | in_path(fetch(:current_path)) do 38 | command %{sudo systemctl restart hackweek} 39 | command %{sudo systemctl restart hackweek-sphinx} 40 | end 41 | end 42 | end 43 | end 44 | 45 | # For help in making your deploy script, see the Mina documentation: 46 | # 47 | # - https://github.com/mina-deploy/mina/tree/master/docs 48 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/integer/time' 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join('tmp/caching-dev.txt').exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raise an error on page load if there are pending migrations. 46 | config.active_record.migration_error = :page_load 47 | 48 | # Highlight code that triggered database queries in logs. 49 | config.active_record.verbose_query_logs = true 50 | 51 | # Suppress logger output for asset requests. 52 | config.assets.quiet = true 53 | 54 | # Raises error for missing translations. 55 | # config.i18n.raise_on_missing_translations = true 56 | 57 | # Annotate rendered view with file names. 58 | # config.action_view.annotate_rendered_view_with_filenames = true 59 | 60 | # Uncomment if you wish to allow Action Cable access from any origin. 61 | # config.action_cable.disable_request_forgery_protection = true 62 | 63 | # Enable authentification test mode 64 | config.devise.ichain_test_mode = true 65 | end 66 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/integer/time' 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV['CI'].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Print deprecation notices to the stderr. 37 | config.active_support.deprecation = :stderr 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raises error for missing translations. 46 | # config.i18n.raise_on_missing_translations = true 47 | 48 | # Annotate rendered view with file names. 49 | # config.action_view.annotate_rendered_view_with_filenames = true 50 | end 51 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.2' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report CSP violations to a specified URI. See: 24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /config/initializers/custom_failure.rb: -------------------------------------------------------------------------------- 1 | class CustomFailure < Devise::FailureApp 2 | def redirect_url 3 | new_user_ichain_session_path 4 | end 5 | 6 | def respond 7 | redirect 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += %i[ 5 | passw secret token _key crypt salt certificate otp ssn 6 | ] 7 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/markdown.rb: -------------------------------------------------------------------------------- 1 | require 'haml/filters/markdown' 2 | -------------------------------------------------------------------------------- /config/initializers/sentry.rb: -------------------------------------------------------------------------------- 1 | Sentry.init do |config| 2 | config.dsn = ENV.fetch('HACKWEEK_SENTRY_DSN', nil) 3 | config.breadcrumbs_logger = [:active_support_logger] 4 | 5 | # To activate performance monitoring, set one of these options. 6 | # We recommend adjusting the value in production: 7 | config.traces_sample_rate = 1.0 8 | # or 9 | # config.traces_sampler = lambda do |_context| 10 | # true 11 | # end 12 | end 13 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/new_project_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Goals 5 | 6 | 7 | ## Resources 8 | 9 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch('PORT') { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch('RAILS_ENV') { 'development' } 17 | 18 | # Specifies the `pidfile` that Puma will use. 19 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } 20 | 21 | # Specifies the number of `workers` to boot in clustered mode. 22 | # Workers are forked webserver processes. If using threads and workers together 23 | # the concurrency of the application would be max `threads` * `workers`. 24 | # Workers do not work on JRuby or Windows (both of which do not support 25 | # processes). 26 | # 27 | workers ENV.fetch('WEB_CONCURRENCY') { 2 } 28 | 29 | # Use the `preload_app!` method when specifying a `workers` number. 30 | # This directive tells Puma to first boot the application and load code 31 | # before forking the application. This takes advantage of Copy On Write 32 | # process behavior so workers use less memory. 33 | # 34 | preload_app! 35 | 36 | # Allow puma to be restarted by `rails restart` command. 37 | plugin 'tmp_restart' 38 | -------------------------------------------------------------------------------- /config/secrets.yml.example: -------------------------------------------------------------------------------- 1 | # We only use environment variables for configuration. You should too! 2 | # Why? Read http://12factor.net/config 3 | 4 | defaults: &defaults 5 | secret_key_base: '12345' 6 | devise_secret_key: '12345' 7 | 8 | development: 9 | <<: *defaults 10 | 11 | test: 12 | <<: *defaults 13 | 14 | production: 15 | secret_key_base: <%= ENV["HACKWEEK_SECRET_KEY_BASE"] %> 16 | devise_secret_key: <%= ENV["HACKWEEK_DEVISE_SECRET_KEY"] %> 17 | errbit_api_key: <%= ENV["HACKWEEK_ERRBIT_KEY"] %> 18 | -------------------------------------------------------------------------------- /config/storage.yml.example: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /config/thinking_sphinx.yml.example: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | min_infix_len: 3 3 | mysql41: 9312 4 | pid_file: sphinx/pids/searchd.pid 5 | indices_location: sphinx/db 6 | binlog_path: sphinx/binlog 7 | 8 | development: 9 | <<: *defaults 10 | test: 11 | <<: *defaults 12 | mysql41: 9313 13 | production: 14 | <<: *defaults 15 | morphology: 16 | - lemmatize_en_all 17 | -------------------------------------------------------------------------------- /db/migrate/20130408082936_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :projects do |t| 4 | t.string :name 5 | t.string :title 6 | t.text :description 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130408132732_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :users do |t| 4 | t.string :uid 5 | t.string :name 6 | t.string :email 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130408142933_relate_project_to_user.rb: -------------------------------------------------------------------------------- 1 | class RelateProjectToUser < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :projects, :originator_id, :integer 4 | remove_column :projects, :name 5 | end 6 | 7 | def down 8 | add_column :projects, :name, :string 9 | remove_column :projects, :orginator_id 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130409093521_create_updates.rb: -------------------------------------------------------------------------------- 1 | class CreateUpdates < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :updates do |t| 4 | t.text :text 5 | t.integer :author_id 6 | t.integer :project_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130409110644_create_memberships.rb: -------------------------------------------------------------------------------- 1 | class CreateMemberships < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :memberships do |t| 4 | t.integer :project_id 5 | t.integer :user_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130410175713_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :comments do |t| 4 | t.text :text 5 | t.integer :commentable_id 6 | t.string :commentable_type 7 | t.integer :commenter_id 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20130411152323_create_likes.rb: -------------------------------------------------------------------------------- 1 | class CreateLikes < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :likes do |t| 4 | t.integer :project_id 5 | t.integer :user_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130412090255_create_keywords.rb: -------------------------------------------------------------------------------- 1 | class CreateKeywords < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :keywords do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20130412090618_create_project_interests.rb: -------------------------------------------------------------------------------- 1 | class CreateProjectInterests < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :project_interests do |t| 4 | t.integer :project_id 5 | t.integer :keyword_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130412091534_create_user_interests.rb: -------------------------------------------------------------------------------- 1 | class CreateUserInterests < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :user_interests do |t| 4 | t.integer :user_id 5 | t.integer :keyword_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130906125641_add_project_counter_cache.rb: -------------------------------------------------------------------------------- 1 | class AddProjectCounterCache < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :projects, :likes_count, :integer 4 | add_column :projects, :memberships_count, :integer 5 | end 6 | 7 | def down 8 | remove_column :projects, :likes_count 9 | remove_column :projects, :memberships_count 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130912160959_add_devise_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDeviseToUsers < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_table(:users) do |t| 4 | ## Database authenticatable 5 | t.string :encrypted_password, null: false, default: '' 6 | 7 | ## Recoverable 8 | t.string :reset_password_token 9 | t.datetime :reset_password_sent_at 10 | 11 | ## Rememberable 12 | t.datetime :remember_created_at 13 | 14 | ## Trackable 15 | t.integer :sign_in_count, default: 0, null: false 16 | t.datetime :current_sign_in_at 17 | t.datetime :last_sign_in_at 18 | t.string :current_sign_in_ip 19 | t.string :last_sign_in_ip 20 | 21 | ## Confirmable 22 | # t.string :confirmation_token 23 | # t.datetime :confirmed_at 24 | # t.datetime :confirmation_sent_at 25 | # t.string :unconfirmed_email # Only if using reconfirmable 26 | 27 | ## Lockable 28 | # t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts 29 | # t.string :unlock_token # Only if unlock strategy is :email or :both 30 | # t.datetime :locked_at 31 | 32 | # Uncomment below if timestamps were not included in your original model. 33 | # t.timestamps 34 | end 35 | 36 | add_index :users, :email, unique: true 37 | add_index :users, :reset_password_token, unique: true 38 | # add_index :users, :confirmation_token, :unique => true 39 | # add_index :users, :unlock_token, :unique => true 40 | end 41 | 42 | def self.down 43 | # By default, we don't want to make any assumption about how to roll back a migration when your 44 | # model already existed. Please edit below which fields you would like to remove in this migration. 45 | raise ActiveRecord::IrreversibleMigration 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /db/migrate/20130919165239_add_aasm_state.rb: -------------------------------------------------------------------------------- 1 | class AddAasmState < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :projects, :aasm_state, :string 4 | end 5 | 6 | def down 7 | remove_column :projects, :aasm_state 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20131010095502_create_announcements.rb: -------------------------------------------------------------------------------- 1 | class CreateAnnouncements < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :announcements do |t| 4 | t.string :title 5 | t.text :text 6 | t.integer :originator_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20131010100037_create_enrollments.rb: -------------------------------------------------------------------------------- 1 | class CreateEnrollments < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :enrollments do |t| 4 | t.integer :announcement_id 5 | t.integer :user_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20131011111909_create_roles.rb: -------------------------------------------------------------------------------- 1 | class CreateRoles < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :roles do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20131011112148_user_belong_to_roles.rb: -------------------------------------------------------------------------------- 1 | class UserBelongToRoles < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :roles_users, id: false do |t| 4 | t.references :role, :user 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :roles_users 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20131011123003_create_events.rb: -------------------------------------------------------------------------------- 1 | class CreateEvents < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :events do |t| 4 | t.string :name 5 | t.date :start 6 | t.date :end 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20131011123206_project_belong_to_events.rb: -------------------------------------------------------------------------------- 1 | class ProjectBelongToEvents < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :events_projects, id: false do |t| 4 | t.references :event, :project 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :events_projects 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20140829132526_add_active_to_events.rb: -------------------------------------------------------------------------------- 1 | class AddActiveToEvents < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :events, :active, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140829151952_rename_events_to_episode.rb: -------------------------------------------------------------------------------- 1 | class RenameEventsToEpisode < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_table :events, :episodes 4 | rename_column :events_projects, :event_id, :episode_id 5 | rename_table :events_projects, :episodes_projects 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140903092848_rename_episode_start_end.rb: -------------------------------------------------------------------------------- 1 | class RenameEpisodeStartEnd < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_column :episodes, :start, :start_date 4 | rename_column :episodes, :end, :end_date 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20140910105916_add_avatar_columns_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddAvatarColumnsToProjects < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_attachment :projects, :avatar 4 | end 5 | 6 | def self.down 7 | remove_attachment :projects, :avatar 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20150319172246_model_for_episodes_projects_association.rb: -------------------------------------------------------------------------------- 1 | class ModelForEpisodesProjectsAssociation < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :episodes_projects, :id, :primary_key 4 | add_column :episodes_projects, :created_at, :datetime 5 | 6 | # We will be ordering this field to populate RSS feed 7 | add_index :episodes_projects, :created_at 8 | 9 | # To make sure every association is there only once 10 | add_index :episodes_projects, %i[episode_id project_id], unique: true 11 | 12 | # Composite index [A, B] can be used in searches for only A, but not for only B 13 | add_index :episodes_projects, :project_id 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20170221144704_add_url_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddUrlToProjects < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :projects, :url, :string 4 | Project.initialize_urls 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20180305183055_add_location_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddLocationToUser < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :location, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180619192048_create_project_follows.rb: -------------------------------------------------------------------------------- 1 | class CreateProjectFollows < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :project_follows do |t| 4 | t.integer :project_id 5 | t.integer :user_id 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20180702104632_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :notifications do |t| 4 | t.integer :recipient_id 5 | t.integer :actor_id 6 | t.datetime :read_at 7 | t.string :action 8 | t.integer :notifiable_id 9 | t.string :notifiable_type 10 | 11 | t.timestamps null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20180712105306_add_description_to_episodes.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToEpisodes < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :episodes, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180730125036_create_impressions_table.rb: -------------------------------------------------------------------------------- 1 | class CreateImpressionsTable < ActiveRecord::Migration[5.2] 2 | def self.up 3 | create_table :impressions, force: true do |t| 4 | t.string :impressionable_type 5 | t.integer :impressionable_id 6 | t.integer :user_id 7 | t.string :controller_name 8 | t.string :action_name 9 | t.string :view_name 10 | t.string :request_hash 11 | t.string :ip_address 12 | t.string :session_hash 13 | t.text :message 14 | t.text :referrer 15 | t.text :params 16 | t.timestamps 17 | end 18 | add_index :impressions, %i[impressionable_type message impressionable_id], 19 | name: 'impressionable_type_message_index', unique: false, length: { message: 255 } 20 | add_index :impressions, %i[impressionable_type impressionable_id request_hash], name: 'poly_request_index', 21 | unique: false 22 | add_index :impressions, %i[impressionable_type impressionable_id ip_address], name: 'poly_ip_index', unique: false 23 | add_index :impressions, %i[impressionable_type impressionable_id session_hash], name: 'poly_session_index', 24 | unique: false 25 | add_index :impressions, %i[controller_name action_name request_hash], name: 'controlleraction_request_index', 26 | unique: false 27 | add_index :impressions, %i[controller_name action_name ip_address], name: 'controlleraction_ip_index', unique: false 28 | add_index :impressions, %i[controller_name action_name session_hash], name: 'controlleraction_session_index', 29 | unique: false 30 | add_index :impressions, %i[impressionable_type impressionable_id params], name: 'poly_params_request_index', 31 | unique: false, length: { params: 255 } 32 | add_index :impressions, :user_id 33 | end 34 | 35 | def self.down 36 | drop_table :impressions 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /db/migrate/20180730151836_add_projecthits_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddProjecthitsToProjects < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :projects, :projecthits, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180820211721_create_faqs.rb: -------------------------------------------------------------------------------- 1 | class CreateFaqs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :faqs do |t| 4 | t.text :question 5 | t.text :answer 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20210303201638_add_hide_email_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddHideEmailToUser < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :hide_email, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210312230516_rename_project_interest.rb: -------------------------------------------------------------------------------- 1 | class RenameProjectInterest < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_table('project_interests', 'keywords_projects') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210312232604_rename_user_interest.rb: -------------------------------------------------------------------------------- 1 | class RenameUserInterest < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_table('user_interests', 'keywords_users') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210312233011_rename_memberships.rb: -------------------------------------------------------------------------------- 1 | class RenameMemberships < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_table('memberships', 'projects_users') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210314175543_rename_memberships_back.rb: -------------------------------------------------------------------------------- 1 | class RenameMembershipsBack < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_table(:projects_users, :memberships) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210315195708_add_avatar_to_keywords.rb: -------------------------------------------------------------------------------- 1 | class AddAvatarToKeywords < ActiveRecord::Migration[5.2] 2 | def up 3 | add_attachment :keywords, :avatar 4 | end 5 | 6 | def down 7 | remove_attachment :keywords, :avatar 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20210315195817_add_description_to_keywords.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToKeywords < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :keywords, :description, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220113162506_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :active_storage_blobs, id: primary_key_type do |t| 8 | t.string :key, null: false 9 | t.string :filename, null: false 10 | t.string :content_type 11 | t.text :metadata 12 | t.string :service_name, null: false 13 | t.bigint :byte_size, null: false 14 | t.string :checksum 15 | 16 | if connection.supports_datetime_with_precision? 17 | t.datetime :created_at, precision: 6, null: false 18 | else 19 | t.datetime :created_at, null: false 20 | end 21 | 22 | t.index [:key], unique: true 23 | end 24 | 25 | create_table :active_storage_attachments, id: primary_key_type do |t| 26 | t.string :name, null: false 27 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 28 | t.references :blob, null: false, type: foreign_key_type 29 | 30 | if connection.supports_datetime_with_precision? 31 | t.datetime :created_at, precision: 6, null: false 32 | else 33 | t.datetime :created_at, null: false 34 | end 35 | 36 | t.index %i[record_type record_id name blob_id], name: :index_active_storage_attachments_uniqueness, unique: true 37 | t.foreign_key :active_storage_blobs, column: :blob_id 38 | end 39 | 40 | create_table :active_storage_variant_records, id: primary_key_type do |t| 41 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 42 | t.string :variation_digest, null: false 43 | 44 | t.index %i[blob_id variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true 45 | t.foreign_key :active_storage_blobs, column: :blob_id 46 | end 47 | end 48 | 49 | private 50 | 51 | def primary_and_foreign_key_types 52 | config = Rails.configuration.generators 53 | setting = config.options[config.orm][:primary_key_type] 54 | primary_key_type = setting || :primary_key 55 | foreign_key_type = setting || :bigint 56 | [primary_key_type, foreign_key_type] 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /db/migrate/20220201134703_convert_to_active_storage.rb: -------------------------------------------------------------------------------- 1 | class ConvertToActiveStorage < ActiveRecord::Migration[7.0] 2 | require 'open-uri' 3 | 4 | def up 5 | models = [Project, Keyword] 6 | transaction do 7 | models.each do |model| 8 | model.find_each do |instance| 9 | next if instance.send('avatar_file_name').blank? 10 | 11 | blob = ActiveStorage::Blob.create(key: SecureRandom.uuid, 12 | filename: instance.send('avatar_file_name'), 13 | content_type: instance.send('avatar_content_type'), 14 | metadata: {}, 15 | service_name: 'local', 16 | byte_size: instance.send('avatar_file_size'), 17 | checksum: Digest::MD5.base64digest(File.read(instance.avatar.path))) 18 | attachment = ActiveStorage::Attachment.create(name: 'avatar', 19 | record_type: model.name, 20 | record_id: instance.id, 21 | blob_id: blob.id) 22 | source = attachment.record.send('avatar').path 23 | dest_dir = File.join( 24 | 'storage', 25 | attachment.blob.key.first(2), 26 | attachment.blob.key.first(4).last(2) 27 | ) 28 | dest = File.join(dest_dir, attachment.blob.key) 29 | 30 | FileUtils.mkdir_p(dest_dir) 31 | puts "Moving #{source} to #{dest}" 32 | FileUtils.cp(source, dest) 33 | end 34 | end 35 | end 36 | end 37 | 38 | def down 39 | raise ActiveRecord::IrreversibleMigration 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # Episodes 4 | 5 | # Roles 6 | Role.create(name: 'organizer') 7 | admin_role = Role.create(name: 'admin') 8 | # Uncomment these lines to create an initial admin user 9 | # admin_user = User.create(:name => "admin", :email => "admin@example.org") 10 | # admin_user.roles = [admin_role] 11 | -------------------------------------------------------------------------------- /docker-compose.override.yml.example: -------------------------------------------------------------------------------- 1 | # This file is generated by our Rakefile. Do not change it! 2 | version: '2.1' 3 | services: 4 | hackweek: 5 | build: 6 | args: 7 | CONTAINER_USERID: 13042 8 | 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: registry.opensuse.org/opensuse/mariadb:latest 4 | environment: 5 | - MYSQL_ROOT_PASSWORD=root 6 | hackweek: 7 | build: 8 | dockerfile: Dockerfile 9 | context: . 10 | command: foreman start -p 3000 11 | volumes: 12 | - .:/hackweek 13 | ports: 14 | - "3000:3000" 15 | environment: 16 | - CONTAINER=1 17 | depends_on: 18 | - db 19 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/tasks/dev.rake: -------------------------------------------------------------------------------- 1 | # Don't add any includes that aren't in the Ruby std-lib here, 2 | # If you do, dev:bootstrap will fail on bundle:install 3 | 4 | require 'find' 5 | 6 | class String 7 | def red 8 | "\e[0;31;49m#{self}\e[0m" 9 | end 10 | 11 | def green 12 | "\e[0;32;49m#{self}\e[0m" 13 | end 14 | end 15 | 16 | namespace :dev do 17 | file_task_names = [] 18 | 19 | %w[application database secrets storage].each do |example_base| 20 | config_file = File.join('config', "#{example_base}.yml") 21 | config_example = File.join('config', "#{example_base}.yml.example") 22 | t = Rake::FileTask.define_task(config_file => config_example) do |task| 23 | if File.exist?(task.name) 24 | puts ">>> Skipping #{task.prerequisites.first} -> #{task.name}, please examine and merge if needed." 25 | else 26 | cp task.prerequisites.first, task.name 27 | end 28 | end 29 | file_task_names << t.name 30 | end 31 | 32 | desc "copy example config files into place if they aren't already" 33 | task copy_sample_configs: file_task_names 34 | 35 | desc 'install gems using Bundler' 36 | task :bundle_install do 37 | sh 'bundle install' 38 | end 39 | 40 | desc 'check for searchd' 41 | task :require_searchd do 42 | found = false 43 | 44 | ENV['PATH'].split(':').each do |path| 45 | next unless File.exist?(path) 46 | 47 | found = Find.find(path).any? { |f| f.match(/\bsearchd$/) && File.executable?(f) } 48 | break if found 49 | end 50 | 51 | raise "Can't find searchd for Sphinx. Please install or add to PATH.".red unless found 52 | end 53 | 54 | desc 'bootstrap development environment' 55 | task bootstrap: %w[copy_sample_configs bundle_install db:create db:setup db:seed require_searchd ts:rebuild] do 56 | puts "\n\nCongrats! You should be all set.".green 57 | puts "\nHappy Hacking!".green 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |You may have mistyped the address or the page may have moved.
19 |Head back Home to try finding it again.
20 |Maybe you tried to change something you didn't have access to.
19 |Head back Home
20 |20 | How about you file an issue on github and let us know what you did? That would be AWESOME! 21 |
22 |Head back Home
23 |hans<\/strong><\/p>\n\n");') 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/controllers/notifications_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe NotificationsController do 4 | let(:notification) { create :notification } 5 | 6 | before :each do 7 | sign_in create :user 8 | end 9 | 10 | describe 'GET index' do 11 | it 'assigns all notification as @notifications' do 12 | get :index 13 | 14 | expect(assigns(:older_notifications)).to eq([notification]) 15 | end 16 | end 17 | 18 | describe 'POST mark_as_read' do 19 | it 'makes all the unread msges as read' do 20 | notification = create :notification 21 | expect do 22 | post :mark_as_read, format: :js 23 | end.to change(Notification.unread, :count).by(-1) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/controllers/projects/project_follows_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Projects::ProjectFollowsController, type: :controller do 4 | let(:user) { create :user } 5 | let(:episode) { create :episode } 6 | let(:project) { create :project } 7 | 8 | before :each do 9 | sign_in user 10 | end 11 | 12 | describe 'POST create' do 13 | it 'creates a unique project_follow' do 14 | expect do 15 | post :create, params: { project_id: project, format: :js } 16 | end.to change(ProjectFollow, :count).by(1) 17 | expect do 18 | post :create, params: { project_id: project, format: :js } 19 | end.to change(ProjectFollow, :count).by(0) 20 | end 21 | end 22 | 23 | describe 'DELETE destroy' do 24 | it 'deletes a project_follow entry' do 25 | post :create, params: { project_id: project, episode: episode, format: :js } 26 | expect do 27 | delete :destroy, params: { project_id: project, episode: episode, format: :js } 28 | end.to change(ProjectFollow, :count).by(-1) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UsersController do 4 | let(:user) { create(:user) } 5 | 6 | before :each do 7 | sign_in user 8 | end 9 | 10 | describe 'GET index' do 11 | it 'assigns all users as @users' do 12 | get :index 13 | expect(assigns(:users)).to eq([user]) 14 | end 15 | end 16 | 17 | describe 'GET show' do 18 | it 'assigns the requested user as @user' do 19 | get :show, params: { id: user.id } 20 | expect(assigns(:user)).to eq(user) 21 | end 22 | end 23 | 24 | describe 'POST update' do 25 | it 'updates the user' do 26 | patch :update, params: { id: user.id, user: { 27 | location: 'space' 28 | } } 29 | expect(response).to redirect_to(user_path(user.reload)) 30 | expect(user.location).to eq('space') 31 | end 32 | end 33 | 34 | describe 'POST add_keyword' do 35 | it 'ads a keyword to the user' do 36 | expect do 37 | post :add_keyword, params: { id: user.id, keyword: 'html' } 38 | end.to change(Keyword, :count).by(1) 39 | end 40 | end 41 | 42 | describe 'DELETE delete_keyword' do 43 | it 'deletes a keyword from the user' do 44 | post :add_keyword, params: { id: user.id, keyword: 'javascript' } 45 | 46 | expect do 47 | delete :delete_keyword, params: { id: user.id, keyword: 'javascript' } 48 | end.to change(user.keywords, :count).by(-1) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/factories/comments.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :comment do 3 | commenter factory: %i[user] 4 | commentable factory: %i[project] 5 | 6 | text { 'Sample' } 7 | 8 | trait :with_nested_comments do 9 | after :create do |comment| 10 | 3.times { comment.comments << create(:comment, commentable: comment) } 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/factories/episodes.rb: -------------------------------------------------------------------------------- 1 | # This will guess the User class 2 | FactoryBot.define do 3 | factory :episode do 4 | sequence(:name) { |n| "episode_#{n}" } 5 | start_date { 1.day.ago } 6 | end_date { 7.days.from_now } 7 | 8 | factory :active_episode do 9 | # ensure there is only one active episode 10 | after(:create) do |b| 11 | b.active = true 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/factories/notifications.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :notification, class: Notification do 3 | read_at { nil } 4 | recipient_id { 1 } 5 | action { 'string' } 6 | actor factory: %i[user] 7 | notifiable factory: %i[project] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/projects.rb: -------------------------------------------------------------------------------- 1 | # This will guess the User class 2 | FactoryBot.define do 3 | factory :all_states, class: Project do 4 | sequence(:title) { Faker::Lorem.sentence } 5 | sequence(:description) { Faker::Lorem.paragraph } 6 | 7 | likes_count { 0 } 8 | memberships_count { 0 } 9 | aasm_state { 'idea' } 10 | originator factory: %i[user] 11 | 12 | factory :idea, class: Project do 13 | aasm_state { 'idea' } 14 | end 15 | factory :project, class: Project do 16 | aasm_state { 'project' } 17 | after(:create) { |project| project.users << create(:user) } 18 | end 19 | factory :invention, class: Project do 20 | aasm_state { 'invention' } 21 | after(:create) { |project| project.users << create(:user) } 22 | end 23 | factory :record, class: Project do 24 | aasm_state { 'record' } 25 | end 26 | 27 | trait :with_comments do 28 | after :create do |project| 29 | 2.times { project.comments << create(:comment, commentable: project) } 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/factories/roles.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :role do 3 | name { 'rolename' } 4 | 5 | factory :admin_role do 6 | name { 'admin' } 7 | end 8 | factory :organizer_role do 9 | name { 'admin' } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # This will guess the User class 2 | FactoryBot.define do 3 | factory :user do 4 | name { Faker::Name.name } 5 | email { Faker::Internet.email } 6 | 7 | factory :admin, class: User do 8 | name { 'Admin' } 9 | after(:create) { |user| user.role_ids = create(:admin_role).id } 10 | end 11 | factory :organizer, class: User do 12 | name { 'Organizer' } 13 | after(:create) { |user| user.role_ids = create(:organizer_role).id } 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/features/collaboration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | feature 'Collaboration' do 4 | scenario 'User joins a project', :js do 5 | user = create(:user) 6 | project = create(:idea) 7 | sign_in user 8 | 9 | visit project_path(nil, project) 10 | 11 | click_on 'Join this project' 12 | expect(page).to have_css("#user#{user.id}-gravatar") 13 | expect(page).to have_text("Welcome to the project #{user.name}.") 14 | end 15 | 16 | scenario 'User leaves a project', :js do 17 | user = create(:user) 18 | project = create(:project, users: [user]) 19 | sign_in user 20 | 21 | visit project_path(nil, project) 22 | click_on 'Leave this project' 23 | 24 | expect(page).to have_no_css("#user#{user.id}-gravatar") 25 | expect(page).to have_text("Sorry to see you go #{user.name}.") 26 | end 27 | 28 | scenario 'User likes a project' do 29 | user = create(:user) 30 | project = create(:idea) 31 | sign_in user 32 | 33 | visit project_path(nil, project) 34 | 35 | expect do 36 | click_on "like-#{project.id}" 37 | end.to change(Project.liked, :count).by(1) 38 | expect(page).to have_no_css("like-#{project.id}") 39 | end 40 | 41 | scenario 'User dislikes a project' do 42 | user = create(:user) 43 | project = create(:idea) 44 | sign_in user 45 | 46 | visit project_path(nil, project) 47 | click_on "like-#{project.id}" 48 | 49 | expect do 50 | click_on "dislike-#{project.id}" 51 | end.to change(Project.liked, :count).by(-1) 52 | expect(page).to have_no_css("dislike-#{project.id}") 53 | end 54 | 55 | scenario 'User likes a project multiple times' do 56 | user = create(:user) 57 | project = create(:idea) 58 | sign_in user 59 | 60 | visit project_path(nil, project) 61 | click_on "like-#{project.id}" 62 | 63 | expect do 64 | find("#like-#{project.id}", visible: false).click 65 | end.to change(Project.liked, :count).by(0) 66 | expect(page).to have_no_css("like-#{project.id}") 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/features/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | feature 'Comment' do 4 | let(:user) { create :user } 5 | let(:project) { create(:idea, originator: user) } 6 | let(:project_with_comments) { create(:idea, :with_comments, originator: user) } 7 | let(:comment_text) { Faker::Lorem.sentence } 8 | 9 | before :each do 10 | sign_in user 11 | end 12 | 13 | scenario 'markdown preview', :js do 14 | visit project_path(:all, project) 15 | fill_in 'comment_text', with: '_italic_ **bold** :smile: @user' 16 | click_on 'Preview' 17 | 18 | expect(page).to have_css('em', text: 'italic') 19 | expect(page).to have_css('strong', text: 'bold') 20 | expect(page).to have_css('a[href$="/users/user"]') 21 | expect(page).to have_css('img[alt="add-emoji"]') 22 | end 23 | 24 | scenario 'create', :js do 25 | visit project_path(nil, project) 26 | 27 | within('#comments_form_section') do 28 | fill_in 'comment_text', with: comment_text 29 | end 30 | 31 | within('#comments_form_section') do 32 | click_on 'Create Comment' 33 | end 34 | 35 | within('#comments_section') do 36 | expect(page).to have_text comment_text 37 | end 38 | end 39 | 40 | scenario 'reply to a comment', :js do 41 | visit project_path(nil, project_with_comments) 42 | 43 | within("#comment_#{project_with_comments.comments.first.id}") do 44 | click_on 'Reply' 45 | end 46 | 47 | within("#replyCommentcomment_#{project_with_comments.comments.first.id}") do 48 | fill_in 'comment_text', with: 'You are wrong on the internet!' 49 | end 50 | 51 | within("#replyCommentcomment_#{project_with_comments.comments.first.id}") do 52 | click_on 'Create Comment' 53 | end 54 | 55 | within("#comment_#{project_with_comments.comments.first.id}") do 56 | expect(page).to have_text 'You are wrong on the internet!' 57 | end 58 | end 59 | 60 | scenario 'update', :js do 61 | comment = create(:comment, commenter: user, commentable: project) 62 | 63 | visit project_path(nil, project) 64 | 65 | within("li#comment_#{comment.id}") do 66 | click_on 'Edit' 67 | end 68 | 69 | within("#editCommentcomment_#{comment.id}") do 70 | fill_in 'comment_text', with: comment_text 71 | click_on 'Update Comment' 72 | end 73 | 74 | within("li#comment_#{comment.id}") do 75 | expect(page).to have_text comment_text 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/features/search_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Search result', :search do 4 | let!(:project) { create :project, title: 'SuperFragility' } 5 | let!(:other_project) { create :project } 6 | 7 | it 'can be found by the exact string' do 8 | results = Project.search project.title 9 | expect(results).to contain_exactly(project) 10 | end 11 | 12 | it 'can be found by string with a different case' do 13 | results = Project.search project.title.swapcase 14 | expect(results).to contain_exactly(project) 15 | end 16 | 17 | # OK, I give up. Wildcards seem not to work in test mode (but works perfectly in development). 18 | # Test this manually, please 19 | it 'can be found by substring in wildcard-enabled mode' do 20 | results = Project.search 'fragil', star: true 21 | expect(results).to contain_exactly(project) 22 | end 23 | 24 | # Stemming depends on the morphology engines bundled with sphinx. Current testing setup is 25 | # kinda weird here — skipping this test for now 26 | it 'can be found by stemming' do 27 | # stemming means that different forms of the verb are treated the as the same word. 28 | # For example, reading and read are considered the same word. 29 | project = create(:project, title: 'Read') 30 | results = Project.search 'reading' 31 | expect(results).to contain_exactly(project) 32 | end 33 | 34 | # TODO: Add tooltip with search operators to the corresponding view 35 | it 'can be found using operators' do 36 | linux = create(:project, title: 'Linux') 37 | opensuse = create(:project, title: 'openSUSE', description: 'A Linux distribution with cool kernel') 38 | hurd = create(:project, title: 'Hurd', description: 'A kernel replacing Linux') 39 | 40 | results = Project.search 'linux' 41 | expect(results).to contain_exactly(linux, opensuse, hurd) 42 | 43 | results = Project.search 'hurd|opensuse' 44 | expect(results).to contain_exactly(hurd, opensuse) 45 | 46 | results = Project.search 'linux -openSUSE' 47 | expect(results).to contain_exactly(linux, hurd) 48 | 49 | results = Project.search 'linux << kernel' 50 | expect(results).to contain_exactly(opensuse) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/helpers/markdown_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe MarkdownHelper, type: :helper do 4 | describe '.enrich_markdown' do 5 | it 'translates emoji' do 6 | text = 'I need :coffee: so badly, working openSUSE:Factory:Staging:F' 7 | expect(enrich_markdown(markdown: text)).to eq('I need  so badly, working openSUSE:Factory:Staging:F') 8 | end 9 | 10 | it 'translate @user links' do 11 | text = 'Hey @hans, how are you?' 12 | expect(enrich_markdown(markdown: text)).to eq('Hey [@hans](/users/hans), how are you?') 13 | end 14 | 15 | it 'translates hw#slug links' do 16 | text = 'Have you seen hw#super-cool? Its awesome' 17 | expect(enrich_markdown(markdown: text)).to eq('Have you seen [hw#super-cool](/projects/super-cool)? Its awesome') 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/ability_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'cancan/matchers' 3 | 4 | describe 'Ability' do 5 | subject(:ability) { Ability.new(user) } 6 | let(:user) { nil } 7 | 8 | context 'when user is a normal user' do 9 | let(:user) { create(:user) } 10 | let(:other_user) { create(:user) } 11 | let(:own_project) { create(:project, originator: user) } 12 | let(:foreign_project) { create(:project, originator: other_user) } 13 | let(:own_comment) { create(:comment, commenter: user) } 14 | let(:foreign_comment) { create(:comment, commenter: other_user) } 15 | let(:collaborated_project) { create(:project, originator: other_user, users: [user]) } 16 | 17 | # Myself 18 | it { is_expected.not_to be_able_to(:create, user) } 19 | it { is_expected.to be_able_to(:update, user) } 20 | it { is_expected.not_to be_able_to(:destroy, user) } 21 | it { is_expected.to be_able_to(:add_keyword, user) } 22 | it { is_expected.to be_able_to(:delete_keyword, user) } 23 | 24 | # Others 25 | it { is_expected.not_to be_able_to(:create, other_user) } 26 | it { is_expected.not_to be_able_to(:update, other_user) } 27 | it { is_expected.not_to be_able_to(:destroy, other_user) } 28 | it { is_expected.not_to be_able_to(:add_keyword, other_user) } 29 | it { is_expected.not_to be_able_to(:delete_keyword, other_user) } 30 | 31 | it { is_expected.to be_able_to(:manage, own_project) } 32 | it { is_expected.not_to be_able_to(:manage, foreign_project) } 33 | 34 | it { is_expected.to be_able_to(:create, own_comment) } 35 | it { is_expected.to be_able_to(:update, own_comment) } 36 | it { is_expected.not_to be_able_to(:destroy, own_comment) } 37 | 38 | it { is_expected.not_to be_able_to(:create, foreign_comment) } 39 | it { is_expected.not_to be_able_to(:update, foreign_comment) } 40 | it { is_expected.not_to be_able_to(:destroy, foreign_comment) } 41 | 42 | %i[edit update add_keyword delete_keyword advance recess add_episode delete_episode].each do |action| 43 | it { is_expected.to be_able_to(action, collaborated_project) } 44 | end 45 | 46 | it { is_expected.not_to be_able_to(:destroy, collaborated_project) } 47 | 48 | it { is_expected.not_to be_able_to(:manage, Announcement.new) } 49 | it { is_expected.not_to be_able_to(:manage, Faq.new) } 50 | end 51 | 52 | context 'when user is an organizer' do 53 | let(:user) { create(:organizer) } 54 | 55 | it { is_expected.to be_able_to(:manage, Announcement.new) } 56 | it { is_expected.to be_able_to(:manage, Faq.new) } 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/models/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Comment do 4 | it 'cleans up nested comments on deletion' do 5 | comment = create(:comment, :with_nested_comments) 6 | comment.destroy! 7 | expect(Comment.count).to eq 0 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/models/episode_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Episode do 4 | describe 'active' do 5 | it 'returns the active episode' do 6 | 3.times { FactoryBot.create(:episode) } 7 | active_episode = FactoryBot.create(:active_episode) 8 | expect(Episode.active).to eq(active_episode) 9 | active_episode.delete 10 | expect(Episode.active).to eq(nil) 11 | end 12 | end 13 | 14 | describe 'active=' do 15 | it 'activates the episode' do 16 | episode = FactoryBot.create(:episode) 17 | episode.active = true 18 | expect(Episode.active).to eq(episode) 19 | end 20 | 21 | it 'deactivates all other episodes' do 22 | activated_episodes = [ 23 | FactoryBot.create(:episode), 24 | FactoryBot.create(:episode) 25 | ] 26 | activated_episodes.each { |e| e.update!(active: true) } 27 | 28 | episode = FactoryBot.create(:episode) 29 | episode.active = true 30 | activated_episodes.each do |e| 31 | expect(e.reload.active).to eq(false) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 2 | RSpec.configure do |config| 3 | # The settings below are suggested to provide a good initial experience 4 | # with RSpec, but feel free to customize to your heart's content. 5 | # These two settings work together to allow you to limit a spec run 6 | # to individual examples or groups you care about by tagging them with 7 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 8 | # get run. 9 | config.filter_run :focus 10 | config.run_all_when_everything_filtered = true 11 | 12 | # Many RSpec users commonly either run the entire suite or an individual 13 | # file, and it's useful to allow more verbose output when running an 14 | # individual spec file. 15 | if config.files_to_run.one? 16 | # Use the documentation formatter for detailed output, 17 | # unless a formatter has already been configured 18 | # (e.g. via a command-line flag). 19 | config.default_formatter = 'doc' 20 | end 21 | 22 | # Print the 10 slowest examples and example groups at the 23 | # end of the spec run, to help surface which specs are running 24 | # particularly slow. 25 | config.profile_examples = 10 26 | 27 | # Run specs in random order to surface order dependencies. If you find an 28 | # order dependency and want to debug it, you can fix the order by providing 29 | # the seed, which is printed after each run. 30 | # --seed 1234 31 | config.order = :random 32 | 33 | # Seed global randomization in this process using the `--seed` CLI option. 34 | # Setting this allows you to use `--seed` to deterministically reproduce 35 | # test failures related to randomization by passing the same `--seed` value 36 | # as the one that triggered the failure. 37 | Kernel.srand config.seed 38 | 39 | # rspec-expectations config goes here. You can use an alternate 40 | # assertion/expectation library such as wrong or the stdlib/minitest 41 | # assertions if you prefer. 42 | config.expect_with :rspec do |expectations| 43 | # Enable only the newer, non-monkey-patching expect syntax. 44 | # For more details, see: 45 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 46 | expectations.syntax = :expect 47 | end 48 | 49 | # rspec-mocks config goes here. You can use an alternate test double 50 | # library (such as bogus or mocha) by changing the `mock_with` option here. 51 | config.mock_with :rspec do |mocks| 52 | # Enable only the newer, non-monkey-patching expect syntax. 53 | # For more details, see: 54 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 55 | mocks.syntax = :expect 56 | 57 | # Prevents you from mocking or stubbing a method that does not exist on 58 | # a real object. This is generally recommended. 59 | mocks.verify_partial_doubles = true 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/support/login_macros.rb: -------------------------------------------------------------------------------- 1 | module LoginMacros 2 | include Warden::Test::Helpers 3 | Warden.test_mode! 4 | 5 | def sign_in(user) 6 | login_as(user, scope: :user) 7 | end 8 | 9 | def sign_out 10 | logout(:user) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/sphinx_helpers.rb: -------------------------------------------------------------------------------- 1 | module SphinxHelpers 2 | def index 3 | ThinkingSphinx::Test.index 4 | # Wait for Sphinx to finish loading in the new index files. 5 | sleep 0.25 until index_finished? 6 | end 7 | 8 | def index_finished? 9 | Rails.root.glob("#{ThinkingSphinx::Test.config.indices_location}/*.{new,tmp}*").empty? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/storage/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/hackweek/c365d93b6bbf5dcc0add17f179330ceb9f06fa7a/vendor/.keep --------------------------------------------------------------------------------