├── .editorconfig ├── .gitignore ├── .ruby-version ├── .travis.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Gemfile.lock.bundler-1-16-0-ruby-2-3-1 ├── Procfile ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ └── frontpage.png │ ├── javascripts │ │ ├── api_helpers.js.coffee │ │ ├── application.js │ │ ├── article.js.coffee │ │ ├── list_articles.js.coffee │ │ ├── search.js.coffee │ │ ├── static.js.coffee │ │ └── user.js.coffee │ └── stylesheets │ │ ├── api_helpers.css.scss │ │ ├── application.css.scss │ │ ├── article.css.scss │ │ ├── list_articles.css.scss │ │ ├── search.scss │ │ ├── static.css.scss │ │ └── user.css.scss ├── controllers │ ├── User │ │ ├── confirmations_controller.rb │ │ ├── omniauth_callbacks_controller.rb │ │ ├── passwords_controller.rb │ │ ├── registrations_controller.rb │ │ ├── sessions_controller.rb │ │ └── unlocks_controller.rb │ ├── api_helpers_controller.rb │ ├── application_controller.rb │ ├── article_controller.rb │ ├── concerns │ │ └── .keep │ ├── list_articles_controller.rb │ ├── search_controller.rb │ ├── static_controller.rb │ └── user_controller.rb ├── helpers │ ├── api_helpers_helper.rb │ ├── application_helper.rb │ ├── article_helper.rb │ ├── list_articles_helper.rb │ ├── search_helper.rb │ ├── static_helper.rb │ └── user_helper.rb ├── mailers │ ├── .keep │ └── article_sharer.rb ├── models │ ├── .keep │ ├── admin.rb │ ├── article.rb │ ├── concerns │ │ └── .keep │ ├── session.rb │ └── user.rb └── views │ ├── api_helpers │ └── password_compare.html.haml │ ├── article │ ├── _entry_form_basics.haml │ ├── _visibility_chooser.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── new.html.haml │ ├── share.html.haml │ └── show.html.haml │ ├── article_sharer │ └── share_article.html.haml │ ├── layouts │ ├── application.html.haml │ ├── mailer.html.haml │ ├── mailer.text.haml │ └── search.html.haml │ ├── list_articles │ ├── feed.html.haml │ └── index.html.haml │ ├── search │ └── index.html.haml │ ├── shared │ ├── _article_entry_form.haml │ ├── _article_single.haml │ ├── _article_single_master.haml │ ├── _article_single_show.haml │ ├── _article_single_show_feed.haml │ └── _list_several_articles.haml │ ├── static │ ├── about.html.haml │ └── index.html.haml │ ├── user │ ├── _export_articles.html.haml │ ├── index.html.haml │ └── public_page.html.haml │ └── users │ ├── confirmations │ └── new.html.haml │ ├── mailer │ ├── confirmation_instructions.html.haml │ ├── reset_password_instructions.html.haml │ └── unlock_instructions.html.haml │ ├── passwords │ ├── edit.html.haml │ └── new.html.haml │ ├── registrations │ ├── edit.html.haml │ └── new.html.haml │ ├── sessions │ └── new.html.haml │ ├── shared │ └── _links.html.haml │ └── unlocks │ └── new.html.haml ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── meta_tags.rb │ ├── mime_types.rb │ ├── rails_admin.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb └── secrets.yml ├── db ├── migrate │ ├── 20160228183336_create_users.rb │ ├── 20160228183446_create_articles.rb │ ├── 20160228183853_add_devise_to_users.rb │ ├── 20160228192041_add_user_to_articles.rb │ ├── 20160306110412_add_confirmable_to_devise.rb │ ├── 20160312135338_add_index_to_users.rb │ ├── 20160321183951_change_quote_column_users.rb │ ├── 20160321184517_change_size_of_quote_column_articles.rb │ ├── 20160807150421_create_sessions.rb │ ├── 20161014194032_add_tags_to_articles.rb │ ├── 20161018055739_change_tags_format.rb │ ├── 20170112075852_devise_create_admins.rb │ ├── 20171214043342_add_title_to_articles.rb │ └── 20180210205553_add_visibility_to_articles.rb ├── schema.rb └── seeds.rb ├── deploy └── nginx-configurations │ ├── README.md │ ├── certbot │ ├── cutouts-stg.siddharthkannan.in │ └── cutouts.siddharthkannan.in ├── docker-compose.yml ├── encrypt.go ├── img ├── v1_1.png ├── v1_2.png └── v1_3.png ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── license.md ├── log └── .keep ├── production.env.template ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt ├── readme.md ├── start-server.sh ├── test ├── controllers │ ├── .keep │ ├── api_helpers_controller_test.rb │ ├── article_controller_test.rb │ ├── list_articles_controller_test.rb │ ├── search_controller_test.rb │ ├── static_controller_test.rb │ └── user_controller_test.rb ├── fixtures │ ├── .keep │ ├── admins.yml │ ├── articles.yml │ ├── sessions.yml │ └── users.yml ├── helpers │ ├── .keep │ ├── api_helpers_helper_test.rb │ ├── article_helper_test.rb │ ├── list_articles_helper_test.rb │ ├── static_helper_test.rb │ └── user_helper_test.rb ├── integration │ └── .keep ├── mailers │ ├── .keep │ ├── article_sharer_test.rb │ └── previews │ │ └── article_sharer_preview.rb ├── models │ ├── .keep │ ├── admin_test.rb │ ├── article_test.rb │ ├── session_test.rb │ └── user_test.rb └── test_helper.rb └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style=space 3 | indent_size=2 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-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 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | 18 | .env 19 | coverage 20 | 21 | bin 22 | 23 | # Ignore the production.env file for safety 24 | production.env -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | before_install: gem install bundler -v '2.1.2' 3 | before_script: bundle exec rake db:migrate 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.0 2 | 3 | # Javascript run time required (ExecJS) 4 | RUN wget -q https://nodejs.org/dist/v14.15.0/node-v14.15.0-linux-x64.tar.xz 5 | RUN tar xf node-v14.15.0-linux-x64.tar.xz 6 | RUN rm -rf node-v14.15.0-linux-x64.tar.xz 7 | RUN mv node-v14.15.0-linux-x64 node 8 | RUN export PATH="$PATH:/node/bin" 9 | 10 | # Get source and install all dependencies 11 | COPY . /src 12 | WORKDIR /src 13 | RUN bundle install 14 | 15 | ENTRYPOINT [ "/src/start-server.sh" ] 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.7.0' 3 | 4 | gem 'dotenv-rails', :groups => [:development, :test] 5 | gem 'factory_bot', :groups => [:development, :test] 6 | gem 'faker', :git => 'https://github.com/stympy/faker.git', :branch => 'master', group: [:development, :test] 7 | gem 'simplecov', require: false, group: :test 8 | 9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 10 | gem 'rails', '~> 6.0', '>= 6.0.2.1' 11 | # Use sqlite3 as the database for Active Record 12 | gem 'sqlite3', group: [:development, :test] 13 | gem 'pg', '~> 0.11' 14 | # Use SCSS for stylesheets 15 | gem 'sass-rails', '> 4' 16 | # Use Uglifier as compressor for JavaScript assets 17 | gem 'uglifier', '>= 1.3.0' 18 | # Use CoffeeScript for .js.coffee assets and views 19 | gem 'coffee-rails' 20 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 21 | # gem 'therubyracer', platforms: :ruby 22 | 23 | # Use jquery as the JavaScript library 24 | gem 'jquery-rails' 25 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 26 | gem 'turbolinks' 27 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 28 | gem 'jbuilder', '~> 2.0' 29 | # bundle exec rake doc:rails generates the API under doc/api. 30 | gem 'sdoc', '~> 0.4.0', group: :doc 31 | 32 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 33 | gem 'spring', group: :development 34 | 35 | # Use ActiveModel has_secure_password 36 | # gem 'bcrypt', '~> 3.1.7' 37 | 38 | # Use unicorn as the app server 39 | # gem 'unicorn' 40 | 41 | # Use Capistrano for deployment 42 | # gem 'capistrano-rails', group: :development 43 | 44 | # Use debugger 45 | # gem 'debugger', group: [:development, :test] 46 | gem 'rails_12factor', group: :production 47 | 48 | gem 'sprockets-rails' 49 | gem 'bootstrap' 50 | gem 'devise' 51 | gem 'rails_admin' 52 | gem 'cancan' 53 | 54 | gem 'jquery-ui-rails' 55 | gem 'haml-rails' 56 | gem 'erb2haml', :group => :development 57 | gem 'redcarpet' 58 | gem 'font-awesome-rails' 59 | gem 'rabl' 60 | gem 'meta-tags' 61 | gem 'responders' 62 | gem 'puma' 63 | gem 'gon' 64 | 65 | # XSS vuln in actionview: `>= 6.0.0, <= 6.0.2.1` 66 | gem "actionview", ">= 6.0.2.2" 67 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/stympy/faker.git 3 | revision: 7c3956fc3e6441d3b043f4b4a06fb4f6ebcbfc5f 4 | branch: master 5 | specs: 6 | faker (2.11.0) 7 | i18n (>= 1.6, < 2) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | actioncable (6.0.2.2) 13 | actionpack (= 6.0.2.2) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | actionmailbox (6.0.2.2) 17 | actionpack (= 6.0.2.2) 18 | activejob (= 6.0.2.2) 19 | activerecord (= 6.0.2.2) 20 | activestorage (= 6.0.2.2) 21 | activesupport (= 6.0.2.2) 22 | mail (>= 2.7.1) 23 | actionmailer (6.0.2.2) 24 | actionpack (= 6.0.2.2) 25 | actionview (= 6.0.2.2) 26 | activejob (= 6.0.2.2) 27 | mail (~> 2.5, >= 2.5.4) 28 | rails-dom-testing (~> 2.0) 29 | actionpack (6.0.2.2) 30 | actionview (= 6.0.2.2) 31 | activesupport (= 6.0.2.2) 32 | rack (~> 2.0, >= 2.0.8) 33 | rack-test (>= 0.6.3) 34 | rails-dom-testing (~> 2.0) 35 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 36 | actiontext (6.0.2.2) 37 | actionpack (= 6.0.2.2) 38 | activerecord (= 6.0.2.2) 39 | activestorage (= 6.0.2.2) 40 | activesupport (= 6.0.2.2) 41 | nokogiri (>= 1.8.5) 42 | actionview (6.0.2.2) 43 | activesupport (= 6.0.2.2) 44 | builder (~> 3.1) 45 | erubi (~> 1.4) 46 | rails-dom-testing (~> 2.0) 47 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 48 | activejob (6.0.2.2) 49 | activesupport (= 6.0.2.2) 50 | globalid (>= 0.3.6) 51 | activemodel (6.0.2.2) 52 | activesupport (= 6.0.2.2) 53 | activemodel-serializers-xml (1.0.2) 54 | activemodel (> 5.x) 55 | activesupport (> 5.x) 56 | builder (~> 3.1) 57 | activerecord (6.0.2.2) 58 | activemodel (= 6.0.2.2) 59 | activesupport (= 6.0.2.2) 60 | activestorage (6.0.2.2) 61 | actionpack (= 6.0.2.2) 62 | activejob (= 6.0.2.2) 63 | activerecord (= 6.0.2.2) 64 | marcel (~> 0.3.1) 65 | activesupport (6.0.2.2) 66 | concurrent-ruby (~> 1.0, >= 1.0.2) 67 | i18n (>= 0.7, < 2) 68 | minitest (~> 5.1) 69 | tzinfo (~> 1.1) 70 | zeitwerk (~> 2.2) 71 | autoprefixer-rails (9.7.5) 72 | execjs 73 | bcrypt (3.1.13) 74 | bootstrap (4.4.1) 75 | autoprefixer-rails (>= 9.1.0) 76 | popper_js (>= 1.14.3, < 2) 77 | sassc-rails (>= 2.0.0) 78 | builder (3.2.4) 79 | cancan (1.6.10) 80 | coffee-rails (5.0.0) 81 | coffee-script (>= 2.2.0) 82 | railties (>= 5.2.0) 83 | coffee-script (2.4.1) 84 | coffee-script-source 85 | execjs 86 | coffee-script-source (1.12.2) 87 | concurrent-ruby (1.1.6) 88 | crass (1.0.6) 89 | devise (4.7.1) 90 | bcrypt (~> 3.0) 91 | orm_adapter (~> 0.1) 92 | railties (>= 4.1.0) 93 | responders 94 | warden (~> 1.2.3) 95 | docile (1.3.2) 96 | dotenv (2.7.5) 97 | dotenv-rails (2.7.5) 98 | dotenv (= 2.7.5) 99 | railties (>= 3.2, < 6.1) 100 | erb2haml (0.1.5) 101 | html2haml 102 | erubi (1.9.0) 103 | erubis (2.7.0) 104 | execjs (2.7.0) 105 | factory_bot (5.1.2) 106 | activesupport (>= 4.2.0) 107 | ffi (1.12.2) 108 | font-awesome-rails (4.7.0.5) 109 | railties (>= 3.2, < 6.1) 110 | globalid (0.4.2) 111 | activesupport (>= 4.2.0) 112 | gon (6.3.2) 113 | actionpack (>= 3.0.20) 114 | i18n (>= 0.7) 115 | multi_json 116 | request_store (>= 1.0) 117 | haml (5.1.2) 118 | temple (>= 0.8.0) 119 | tilt 120 | haml-rails (2.0.1) 121 | actionpack (>= 5.1) 122 | activesupport (>= 5.1) 123 | haml (>= 4.0.6, < 6.0) 124 | html2haml (>= 1.0.1) 125 | railties (>= 5.1) 126 | html2haml (2.2.0) 127 | erubis (~> 2.7.0) 128 | haml (>= 4.0, < 6) 129 | nokogiri (>= 1.6.0) 130 | ruby_parser (~> 3.5) 131 | i18n (1.8.2) 132 | concurrent-ruby (~> 1.0) 133 | jbuilder (2.10.0) 134 | activesupport (>= 5.0.0) 135 | jquery-rails (4.3.5) 136 | rails-dom-testing (>= 1, < 3) 137 | railties (>= 4.2.0) 138 | thor (>= 0.14, < 2.0) 139 | jquery-ui-rails (6.0.1) 140 | railties (>= 3.2.16) 141 | json (1.8.6) 142 | kaminari (1.2.0) 143 | activesupport (>= 4.1.0) 144 | kaminari-actionview (= 1.2.0) 145 | kaminari-activerecord (= 1.2.0) 146 | kaminari-core (= 1.2.0) 147 | kaminari-actionview (1.2.0) 148 | actionview 149 | kaminari-core (= 1.2.0) 150 | kaminari-activerecord (1.2.0) 151 | activerecord 152 | kaminari-core (= 1.2.0) 153 | kaminari-core (1.2.0) 154 | loofah (2.4.0) 155 | crass (~> 1.0.2) 156 | nokogiri (>= 1.5.9) 157 | mail (2.7.1) 158 | mini_mime (>= 0.1.1) 159 | marcel (0.3.3) 160 | mimemagic (~> 0.3.2) 161 | meta-tags (2.13.0) 162 | actionpack (>= 3.2.0, < 6.1) 163 | method_source (1.0.0) 164 | mimemagic (0.3.4) 165 | mini_mime (1.0.2) 166 | mini_portile2 (2.4.0) 167 | minitest (5.14.0) 168 | multi_json (1.14.1) 169 | nested_form (0.3.2) 170 | nio4r (2.5.2) 171 | nokogiri (1.10.9) 172 | mini_portile2 (~> 2.4.0) 173 | orm_adapter (0.5.0) 174 | pg (0.21.0) 175 | popper_js (1.16.0) 176 | puma (4.3.3) 177 | nio4r (~> 2.0) 178 | rabl (0.14.2) 179 | activesupport (>= 2.3.14) 180 | rack (2.2.2) 181 | rack-pjax (1.1.0) 182 | nokogiri (~> 1.5) 183 | rack (>= 1.1) 184 | rack-test (1.1.0) 185 | rack (>= 1.0, < 3) 186 | rails (6.0.2.2) 187 | actioncable (= 6.0.2.2) 188 | actionmailbox (= 6.0.2.2) 189 | actionmailer (= 6.0.2.2) 190 | actionpack (= 6.0.2.2) 191 | actiontext (= 6.0.2.2) 192 | actionview (= 6.0.2.2) 193 | activejob (= 6.0.2.2) 194 | activemodel (= 6.0.2.2) 195 | activerecord (= 6.0.2.2) 196 | activestorage (= 6.0.2.2) 197 | activesupport (= 6.0.2.2) 198 | bundler (>= 1.3.0) 199 | railties (= 6.0.2.2) 200 | sprockets-rails (>= 2.0.0) 201 | rails-dom-testing (2.0.3) 202 | activesupport (>= 4.2.0) 203 | nokogiri (>= 1.6) 204 | rails-html-sanitizer (1.3.0) 205 | loofah (~> 2.3) 206 | rails_12factor (0.0.3) 207 | rails_serve_static_assets 208 | rails_stdout_logging 209 | rails_admin (2.0.2) 210 | activemodel-serializers-xml (>= 1.0) 211 | builder (~> 3.1) 212 | haml (>= 4.0, < 6) 213 | jquery-rails (>= 3.0, < 5) 214 | jquery-ui-rails (>= 5.0, < 7) 215 | kaminari (>= 0.14, < 2.0) 216 | nested_form (~> 0.3) 217 | rack-pjax (>= 0.7) 218 | rails (>= 5.0, < 7) 219 | remotipart (~> 1.3) 220 | sassc-rails (>= 1.3, < 3) 221 | rails_serve_static_assets (0.0.5) 222 | rails_stdout_logging (0.0.5) 223 | railties (6.0.2.2) 224 | actionpack (= 6.0.2.2) 225 | activesupport (= 6.0.2.2) 226 | method_source 227 | rake (>= 0.8.7) 228 | thor (>= 0.20.3, < 2.0) 229 | rake (13.0.1) 230 | rdoc (4.3.0) 231 | redcarpet (3.5.0) 232 | remotipart (1.4.4) 233 | request_store (1.5.0) 234 | rack (>= 1.4) 235 | responders (3.0.0) 236 | actionpack (>= 5.0) 237 | railties (>= 5.0) 238 | ruby_parser (3.14.2) 239 | sexp_processor (~> 4.9) 240 | sass-rails (6.0.0) 241 | sassc-rails (~> 2.1, >= 2.1.1) 242 | sassc (2.2.1) 243 | ffi (~> 1.9) 244 | sassc-rails (2.1.2) 245 | railties (>= 4.0.0) 246 | sassc (>= 2.0) 247 | sprockets (> 3.0) 248 | sprockets-rails 249 | tilt 250 | sdoc (0.4.2) 251 | json (~> 1.7, >= 1.7.7) 252 | rdoc (~> 4.0) 253 | sexp_processor (4.14.1) 254 | simplecov (0.18.5) 255 | docile (~> 1.1) 256 | simplecov-html (~> 0.11) 257 | simplecov-html (0.12.2) 258 | spring (2.1.0) 259 | sprockets (4.0.0) 260 | concurrent-ruby (~> 1.0) 261 | rack (> 1, < 3) 262 | sprockets-rails (3.2.1) 263 | actionpack (>= 4.0) 264 | activesupport (>= 4.0) 265 | sprockets (>= 3.0.0) 266 | sqlite3 (1.4.2) 267 | temple (0.8.2) 268 | thor (1.0.1) 269 | thread_safe (0.3.6) 270 | tilt (2.0.10) 271 | turbolinks (5.2.1) 272 | turbolinks-source (~> 5.2) 273 | turbolinks-source (5.2.0) 274 | tzinfo (1.2.7) 275 | thread_safe (~> 0.1) 276 | uglifier (4.2.0) 277 | execjs (>= 0.3.0, < 3) 278 | warden (1.2.8) 279 | rack (>= 2.0.6) 280 | websocket-driver (0.7.1) 281 | websocket-extensions (>= 0.1.0) 282 | websocket-extensions (0.1.4) 283 | zeitwerk (2.3.0) 284 | 285 | PLATFORMS 286 | ruby 287 | 288 | DEPENDENCIES 289 | actionview (>= 6.0.2.2) 290 | bootstrap 291 | cancan 292 | coffee-rails 293 | devise 294 | dotenv-rails 295 | erb2haml 296 | factory_bot 297 | faker! 298 | font-awesome-rails 299 | gon 300 | haml-rails 301 | jbuilder (~> 2.0) 302 | jquery-rails 303 | jquery-ui-rails 304 | meta-tags 305 | pg (~> 0.11) 306 | puma 307 | rabl 308 | rails (~> 6.0, >= 6.0.2.1) 309 | rails_12factor 310 | rails_admin 311 | redcarpet 312 | responders 313 | sass-rails (> 4) 314 | sdoc (~> 0.4.0) 315 | simplecov 316 | spring 317 | sprockets-rails 318 | sqlite3 319 | turbolinks 320 | uglifier (>= 1.3.0) 321 | 322 | RUBY VERSION 323 | ruby 2.7.0p0 324 | 325 | BUNDLED WITH 326 | 2.1.2 327 | -------------------------------------------------------------------------------- /Gemfile.lock.bundler-1-16-0-ruby-2-3-1: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/stympy/faker.git 3 | revision: 679fd508c9f41f41b6df62024d67bc2281953395 4 | branch: master 5 | specs: 6 | faker (1.8.7) 7 | i18n (>= 0.7) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | actionmailer (4.2.11.1) 13 | actionpack (= 4.2.11.1) 14 | actionview (= 4.2.11.1) 15 | activejob (= 4.2.11.1) 16 | mail (~> 2.5, >= 2.5.4) 17 | rails-dom-testing (~> 1.0, >= 1.0.5) 18 | actionpack (4.2.11.1) 19 | actionview (= 4.2.11.1) 20 | activesupport (= 4.2.11.1) 21 | rack (~> 1.6) 22 | rack-test (~> 0.6.2) 23 | rails-dom-testing (~> 1.0, >= 1.0.5) 24 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 25 | actionview (4.2.11.1) 26 | activesupport (= 4.2.11.1) 27 | builder (~> 3.1) 28 | erubis (~> 2.7.0) 29 | rails-dom-testing (~> 1.0, >= 1.0.5) 30 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 31 | activejob (4.2.11.1) 32 | activesupport (= 4.2.11.1) 33 | globalid (>= 0.3.0) 34 | activemodel (4.2.11.1) 35 | activesupport (= 4.2.11.1) 36 | builder (~> 3.1) 37 | activerecord (4.2.11.1) 38 | activemodel (= 4.2.11.1) 39 | activesupport (= 4.2.11.1) 40 | arel (~> 6.0) 41 | activesupport (4.2.11.1) 42 | i18n (~> 0.7) 43 | minitest (~> 5.1) 44 | thread_safe (~> 0.3, >= 0.3.4) 45 | tzinfo (~> 1.1) 46 | arel (6.0.4) 47 | autoprefixer-rails (7.2.6) 48 | execjs 49 | bcrypt (3.1.13) 50 | bootstrap (4.0.0) 51 | autoprefixer-rails (>= 6.0.3) 52 | popper_js (>= 1.12.9, < 2) 53 | sass (>= 3.5.2) 54 | builder (3.2.4) 55 | cancan (1.6.10) 56 | coffee-rails (4.0.1) 57 | coffee-script (>= 2.2.0) 58 | railties (>= 4.0.0, < 5.0) 59 | coffee-script (2.4.1) 60 | coffee-script-source 61 | execjs 62 | coffee-script-source (1.12.2) 63 | concurrent-ruby (1.1.5) 64 | crass (1.0.5) 65 | devise (4.7.1) 66 | bcrypt (~> 3.0) 67 | orm_adapter (~> 0.1) 68 | railties (>= 4.1.0) 69 | responders 70 | warden (~> 1.2.3) 71 | docile (1.1.5) 72 | dotenv (2.2.1) 73 | dotenv-rails (2.2.1) 74 | dotenv (= 2.2.1) 75 | railties (>= 3.2, < 5.2) 76 | erb2haml (0.1.5) 77 | html2haml 78 | erubis (2.7.0) 79 | execjs (2.7.0) 80 | factory_bot (4.8.2) 81 | activesupport (>= 3.0.0) 82 | ffi (1.9.21) 83 | font-awesome-rails (4.7.0.3) 84 | railties (>= 3.2, < 5.2) 85 | globalid (0.4.2) 86 | activesupport (>= 4.2.0) 87 | gon (6.2.1) 88 | actionpack (>= 3.0) 89 | multi_json 90 | request_store (>= 1.0) 91 | haml (5.0.4) 92 | temple (>= 0.8.0) 93 | tilt 94 | haml-rails (1.0.0) 95 | actionpack (>= 4.0.1) 96 | activesupport (>= 4.0.1) 97 | haml (>= 4.0.6, < 6.0) 98 | html2haml (>= 1.0.1) 99 | railties (>= 4.0.1) 100 | html2haml (2.2.0) 101 | erubis (~> 2.7.0) 102 | haml (>= 4.0, < 6) 103 | nokogiri (>= 1.6.0) 104 | ruby_parser (~> 3.5) 105 | i18n (0.9.5) 106 | concurrent-ruby (~> 1.0) 107 | jbuilder (2.7.0) 108 | activesupport (>= 4.2.0) 109 | multi_json (>= 1.2) 110 | jquery-rails (4.3.1) 111 | rails-dom-testing (>= 1, < 3) 112 | railties (>= 4.2.0) 113 | thor (>= 0.14, < 2.0) 114 | jquery-ui-rails (5.0.5) 115 | railties (>= 3.2.16) 116 | json (1.8.6) 117 | kaminari (1.1.1) 118 | activesupport (>= 4.1.0) 119 | kaminari-actionview (= 1.1.1) 120 | kaminari-activerecord (= 1.1.1) 121 | kaminari-core (= 1.1.1) 122 | kaminari-actionview (1.1.1) 123 | actionview 124 | kaminari-core (= 1.1.1) 125 | kaminari-activerecord (1.1.1) 126 | activerecord 127 | kaminari-core (= 1.1.1) 128 | kaminari-core (1.1.1) 129 | loofah (2.4.0) 130 | crass (~> 1.0.2) 131 | nokogiri (>= 1.5.9) 132 | mail (2.7.1) 133 | mini_mime (>= 0.1.1) 134 | meta-tags (2.8.0) 135 | actionpack (>= 3.2.0, < 5.3) 136 | mini_mime (1.0.2) 137 | mini_portile2 (2.4.0) 138 | minitest (5.13.0) 139 | multi_json (1.13.1) 140 | nested_form (0.3.2) 141 | nokogiri (1.10.7) 142 | mini_portile2 (~> 2.4.0) 143 | orm_adapter (0.5.0) 144 | pg (0.21.0) 145 | popper_js (1.12.9) 146 | puma (3.11.4) 147 | rabl (0.13.1) 148 | activesupport (>= 2.3.14) 149 | rack (1.6.12) 150 | rack-pjax (1.0.0) 151 | nokogiri (~> 1.5) 152 | rack (>= 1.1) 153 | rack-test (0.6.3) 154 | rack (>= 1.0) 155 | rails (4.2.11.1) 156 | actionmailer (= 4.2.11.1) 157 | actionpack (= 4.2.11.1) 158 | actionview (= 4.2.11.1) 159 | activejob (= 4.2.11.1) 160 | activemodel (= 4.2.11.1) 161 | activerecord (= 4.2.11.1) 162 | activesupport (= 4.2.11.1) 163 | bundler (>= 1.3.0, < 2.0) 164 | railties (= 4.2.11.1) 165 | sprockets-rails 166 | rails-deprecated_sanitizer (1.0.3) 167 | activesupport (>= 4.2.0.alpha) 168 | rails-dom-testing (1.0.9) 169 | activesupport (>= 4.2.0, < 5.0) 170 | nokogiri (~> 1.6) 171 | rails-deprecated_sanitizer (>= 1.0.1) 172 | rails-html-sanitizer (1.3.0) 173 | loofah (~> 2.3) 174 | rails_12factor (0.0.3) 175 | rails_serve_static_assets 176 | rails_stdout_logging 177 | rails_admin (1.3.0) 178 | builder (~> 3.1) 179 | coffee-rails (~> 4.0) 180 | font-awesome-rails (>= 3.0, < 5) 181 | haml (>= 4.0, < 6) 182 | jquery-rails (>= 3.0, < 5) 183 | jquery-ui-rails (~> 5.0) 184 | kaminari (>= 0.14, < 2.0) 185 | nested_form (~> 0.3) 186 | rack-pjax (>= 0.7) 187 | rails (>= 4.0, < 6) 188 | remotipart (~> 1.3) 189 | sass-rails (>= 4.0, < 6) 190 | rails_serve_static_assets (0.0.5) 191 | rails_stdout_logging (0.0.5) 192 | railties (4.2.11.1) 193 | actionpack (= 4.2.11.1) 194 | activesupport (= 4.2.11.1) 195 | rake (>= 0.8.7) 196 | thor (>= 0.18.1, < 2.0) 197 | rake (13.0.1) 198 | rb-fsevent (0.10.2) 199 | rb-inotify (0.9.10) 200 | ffi (>= 0.5.0, < 2) 201 | rdoc (4.3.0) 202 | redcarpet (3.4.0) 203 | remotipart (1.4.2) 204 | request_store (1.4.1) 205 | rack (>= 1.4) 206 | responders (2.4.0) 207 | actionpack (>= 4.2.0, < 5.3) 208 | railties (>= 4.2.0, < 5.3) 209 | ruby_parser (3.10.1) 210 | sexp_processor (~> 4.9) 211 | sass (3.5.5) 212 | sass-listen (~> 4.0.0) 213 | sass-listen (4.0.0) 214 | rb-fsevent (~> 0.9, >= 0.9.4) 215 | rb-inotify (~> 0.9, >= 0.9.7) 216 | sass-rails (5.0.7) 217 | railties (>= 4.0.0, < 6) 218 | sass (~> 3.1) 219 | sprockets (>= 2.8, < 4.0) 220 | sprockets-rails (>= 2.0, < 4.0) 221 | tilt (>= 1.1, < 3) 222 | sdoc (0.4.2) 223 | json (~> 1.7, >= 1.7.7) 224 | rdoc (~> 4.0) 225 | sexp_processor (4.10.0) 226 | simplecov (0.15.1) 227 | docile (~> 1.1.0) 228 | json (>= 1.8, < 3) 229 | simplecov-html (~> 0.10.0) 230 | simplecov-html (0.10.2) 231 | spring (2.0.2) 232 | activesupport (>= 4.2) 233 | sprockets (3.7.2) 234 | concurrent-ruby (~> 1.0) 235 | rack (> 1, < 3) 236 | sprockets-rails (3.2.1) 237 | actionpack (>= 4.0) 238 | activesupport (>= 4.0) 239 | sprockets (>= 3.0.0) 240 | sqlite3 (1.3.13) 241 | temple (0.8.0) 242 | thor (1.0.1) 243 | thread_safe (0.3.6) 244 | tilt (2.0.8) 245 | turbolinks (5.1.0) 246 | turbolinks-source (~> 5.1) 247 | turbolinks-source (5.1.0) 248 | tzinfo (1.2.6) 249 | thread_safe (~> 0.1) 250 | uglifier (4.1.6) 251 | execjs (>= 0.3.0, < 3) 252 | warden (1.2.7) 253 | rack (>= 1.0) 254 | 255 | PLATFORMS 256 | ruby 257 | 258 | DEPENDENCIES 259 | actionview (>= 4.2.11.1) 260 | bootstrap (~> 4.0.0) 261 | cancan 262 | coffee-rails (~> 4.0.0) 263 | devise 264 | dotenv-rails 265 | erb2haml 266 | factory_bot 267 | faker! 268 | font-awesome-rails 269 | gon 270 | haml-rails 271 | jbuilder (~> 2.0) 272 | jquery-rails 273 | jquery-ui-rails 274 | meta-tags 275 | pg (~> 0.11) 276 | puma 277 | rabl 278 | rails (~> 4.0) 279 | rails_12factor 280 | rails_admin 281 | redcarpet 282 | responders 283 | sass-rails (> 4) 284 | sdoc (~> 0.4.0) 285 | simplecov 286 | spring 287 | sprockets-rails (~> 3) 288 | sqlite3 289 | turbolinks 290 | uglifier (>= 1.3.0) 291 | 292 | RUBY VERSION 293 | ruby 2.3.1p112 294 | 295 | BUNDLED WITH 296 | 1.16.0 297 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | -------------------------------------------------------------------------------- /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 File.expand_path('../config/application', __FILE__) 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/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/frontpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/assets/images/frontpage.png -------------------------------------------------------------------------------- /app/assets/javascripts/api_helpers.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/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 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require popper 15 | //= require bootstrap 16 | //= require jquery_ujs 17 | //= require jquery-ui 18 | //= require turbolinks 19 | //= require_tree . 20 | -------------------------------------------------------------------------------- /app/assets/javascripts/article.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/list_articles.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/search.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/static.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/user.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api_helpers.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api_helpers controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 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 bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require jquery-ui 14 | *= require_tree . 15 | *= require_self 16 | *= require font-awesome 17 | */ 18 | @import 'bootstrap'; 19 | html, 20 | body { 21 | margin-left: 20px; 22 | margin-right: 20px; 23 | margin-top: 20px; 24 | } 25 | form input, 26 | textarea { 27 | @extend .form-control; 28 | margin-top: 10px; 29 | margin-bottom: 10px; 30 | } 31 | 32 | blockquote.article-quote { 33 | @extend .blockquote; 34 | @extend .text-muted; 35 | padding: .5rem 1rem; 36 | border-left: .25rem solid #eceeef; 37 | // border-left: 10px solid #ccc; 38 | // border-color: grey; 39 | } 40 | -------------------------------------------------------------------------------- /app/assets/stylesheets/article.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the article controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/list_articles.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the list_articles controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/search.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the search controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the static controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/user.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the user controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/controllers/User/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | class User::ConfirmationsController < Devise::ConfirmationsController 2 | # GET /resource/confirmation/new 3 | # def new 4 | # super 5 | # end 6 | 7 | # POST /resource/confirmation 8 | # def create 9 | # super 10 | # end 11 | 12 | # GET /resource/confirmation?confirmation_token=abcdef 13 | # def show 14 | # super 15 | # end 16 | 17 | # protected 18 | 19 | # The path used after resending confirmation instructions. 20 | # def after_resending_confirmation_instructions_path_for(resource_name) 21 | # super(resource_name) 22 | # end 23 | 24 | # The path used after confirmation. 25 | # def after_confirmation_path_for(resource_name, resource) 26 | # super(resource_name, resource) 27 | # end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/User/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class User::OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 | # You should configure your model like this: 3 | # devise :omniauthable, omniauth_providers: [:twitter] 4 | 5 | # You should also create an action method in this controller like this: 6 | # def twitter 7 | # end 8 | 9 | # More info at: 10 | # https://github.com/plataformatec/devise#omniauth 11 | 12 | # GET|POST /resource/auth/twitter 13 | # def passthru 14 | # super 15 | # end 16 | 17 | # GET|POST /users/auth/twitter/callback 18 | # def failure 19 | # super 20 | # end 21 | 22 | # protected 23 | 24 | # The path used when omniauth fails 25 | # def after_omniauth_failure_path_for(scope) 26 | # super(scope) 27 | # end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/User/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class User::PasswordsController < Devise::PasswordsController 2 | # GET /resource/password/new 3 | # def new 4 | # super 5 | # end 6 | 7 | # POST /resource/password 8 | # def create 9 | # super 10 | # end 11 | 12 | # GET /resource/password/edit?reset_password_token=abcdef 13 | # def edit 14 | # super 15 | # end 16 | 17 | # PUT /resource/password 18 | # def update 19 | # super 20 | # end 21 | 22 | # protected 23 | 24 | # def after_resetting_password_path_for(resource) 25 | # super(resource) 26 | # end 27 | 28 | # The path used after sending reset password instructions 29 | # def after_sending_reset_password_instructions_path_for(resource_name) 30 | # super(resource_name) 31 | # end 32 | end 33 | -------------------------------------------------------------------------------- /app/controllers/User/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class User::RegistrationsController < Devise::RegistrationsController 2 | # before_action :configure_sign_up_params, only: [:create] 3 | # before_action :configure_account_update_params, only: [:update] 4 | 5 | # GET /resource/sign_up 6 | # def new 7 | # super 8 | # end 9 | 10 | # POST /resource 11 | # def create 12 | # super 13 | # end 14 | 15 | # GET /resource/edit 16 | # def edit 17 | # super 18 | # end 19 | 20 | # PUT /resource 21 | # def update 22 | # super 23 | # end 24 | 25 | # DELETE /resource 26 | # def destroy 27 | # super 28 | # end 29 | 30 | # GET /resource/cancel 31 | # Forces the session data which is usually expired after sign 32 | # in to be expired now. This is useful if the user wants to 33 | # cancel oauth signing in/up in the middle of the process, 34 | # removing all OAuth session data. 35 | # def cancel 36 | # super 37 | # end 38 | 39 | # protected 40 | 41 | # You can put the params you want to permit in the empty array. 42 | # def configure_sign_up_params 43 | # devise_parameter_sanitizer.for(:sign_up) << :attribute 44 | # end 45 | 46 | # You can put the params you want to permit in the empty array. 47 | # def configure_account_update_params 48 | # devise_parameter_sanitizer.for(:account_update) << :attribute 49 | # end 50 | 51 | # The path used after sign up. 52 | # def after_sign_up_path_for(resource) 53 | # super(resource) 54 | # end 55 | 56 | # The path used after sign up for inactive accounts. 57 | # def after_inactive_sign_up_path_for(resource) 58 | # super(resource) 59 | # end 60 | end 61 | -------------------------------------------------------------------------------- /app/controllers/User/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class User::SessionsController < Devise::SessionsController 2 | # before_action :configure_sign_in_params, only: [:create] 3 | 4 | # GET /resource/sign_in 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/sign_in 10 | # def create 11 | # super 12 | # end 13 | 14 | # DELETE /resource/sign_out 15 | # def destroy 16 | # super 17 | # end 18 | 19 | # protected 20 | 21 | # You can put the params you want to permit in the empty array. 22 | # def configure_sign_in_params 23 | # devise_parameter_sanitizer.for(:sign_in) << :attribute 24 | # end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/User/unlocks_controller.rb: -------------------------------------------------------------------------------- 1 | class User::UnlocksController < Devise::UnlocksController 2 | # GET /resource/unlock/new 3 | # def new 4 | # super 5 | # end 6 | 7 | # POST /resource/unlock 8 | # def create 9 | # super 10 | # end 11 | 12 | # GET /resource/unlock?unlock_token=abcdef 13 | # def show 14 | # super 15 | # end 16 | 17 | # protected 18 | 19 | # The path used after sending unlock password instructions 20 | # def after_sending_unlock_instructions_path_for(resource) 21 | # super(resource) 22 | # end 23 | 24 | # The path used after unlocking the resource 25 | # def after_unlock_path_for(resource) 26 | # super(resource) 27 | # end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/api_helpers_controller.rb: -------------------------------------------------------------------------------- 1 | class ApiHelpersController < ApplicationController 2 | include ApiHelpersHelper 3 | before_action :authenticate 4 | skip_before_action :authenticate, :only => [ :user_signin ] 5 | 6 | skip_before_action :verify_authenticity_token 7 | respond_to :json 8 | 9 | # Signing in a user 10 | # params must include 11 | # auth_data (== email || == username), auth_password ( == user.password) 12 | def user_signin 13 | if not params.keys.include? "auth_data" or not params.keys.include? "auth_password" 14 | respond_to do |format| 15 | format.json { render json: err_resp(msg: "Invalid parameters"), status: 400 } 16 | end 17 | return 18 | end 19 | 20 | # figure out if a user exists 21 | # Login with Username and Email both are supported 22 | if User.where(:email => params[:auth_data]).count > 0 23 | this_user = User.where(:email => params[:auth_data]).first 24 | elsif User.where(:username => params[:auth_data]).count > 0 25 | this_user = User.where(:username => params[:auth_data]).first 26 | else 27 | respond_to do |format| 28 | format.json { render json: err_resp(msg:"User not found"), status: 400 } 29 | end 30 | return 31 | end 32 | 33 | if this_user.valid_password?(params[:auth_password]) 34 | # create the session for the user 35 | this_session = Session.new 36 | this_session.user_id = this_user.id 37 | this_session.sid = OpenSSL::Digest::SHA256.new((Time.now.to_i + Random.new(Time.now.to_i).rand(1e3)).to_s).hexdigest 38 | if this_session.save! 39 | respond_to do |format| 40 | format.json { render json: ok_resp(payload: { "session" => this_session, "user" => this_user }), status: 200 } 41 | end 42 | return 43 | else 44 | respond_to do |format| 45 | format.json { render json: err_resp(msg:"Could not save session"), status: 500 } 46 | end 47 | return 48 | end 49 | else 50 | respond_to do |format| 51 | # bad password 52 | format.json { render json: err_resp(msg:"Bad password"), status: 401 } 53 | end 54 | return 55 | return 56 | end 57 | end 58 | 59 | # Creating an article from the parameters 60 | # params must include 61 | # sid, link (URL to the article), authors, quote 62 | def article_create 63 | puts "Paramas doesn't have SID: #{params_doesnt_have_sid params}" 64 | respond_to do |format| 65 | if params_doesnt_have_sid params 66 | format.json { render json: err_resp(msg: "You must provide a session ID!"), status: 401 } 67 | else 68 | user_id = get_user_id params[:sid] 69 | if user_id 70 | new_article = User.find(user_id).articles.new 71 | new_article.link = params[:link] 72 | new_article.quote = params[:quote] 73 | new_article.author = params[:authors] 74 | if new_article.save! 75 | format.json {render json: { "msg" => "Article created!", "res" => new_article }, status: 201 } 76 | else 77 | format.json { render json: { "error" => "Server error! Try again in some time." }, status: 500 } 78 | end 79 | else 80 | format.json { render json: { "error" => "Session timed out!" }, status: 401 } 81 | end 82 | end 83 | end 84 | end 85 | 86 | # List all the articles for the signed in user 87 | # Params must include 88 | # sid 89 | def articles_list 90 | render json: ok_resp(payload: { "articles": @user.articles }), status: 200 91 | end 92 | 93 | private 94 | attr_accessor :user 95 | 96 | def get_user_id(sid) 97 | this_session = Session.where("created_at > ?", 10.minutes.ago).where(:sid => sid) 98 | if this_session.count > 0 99 | this_session.first.user_id 100 | else 101 | nil 102 | end 103 | end 104 | 105 | def params_doesnt_have_sid(params) 106 | not params.keys.include? "sid" 107 | end 108 | 109 | def authenticate 110 | token = request.headers.fetch("Authorization", "") 111 | 112 | if token == "" 113 | render json: err_resp(msg: "Token required"), status: 401 114 | return 115 | end 116 | 117 | token_pattern = /^Bearer / 118 | 119 | if !token_pattern.match?(token) 120 | render json: err_resp(msg: "Malformed header"), status: 401 121 | return 122 | end 123 | 124 | token.gsub! token_pattern, "" 125 | token = token.split(" ").first 126 | session = Session.where(:sid => token).order(created_at: :desc).first 127 | if session == nil 128 | render json: err_resp(msg: "Invalid token"), status: 401 129 | return 130 | end 131 | 132 | user = User.find(session.user_id) 133 | if user == nil 134 | render json: err_resp(msg: "Token not connected to user"), status: 401 135 | return 136 | end 137 | 138 | @user = user 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :configure_permitted_parameters, if: :devise_controller? 3 | # Prevent CSRF attacks by raising an exception. 4 | # For APIs, you may want to use :null_session instead. 5 | protect_from_forgery with: :exception 6 | protected 7 | 8 | def configure_permitted_parameters 9 | devise_parameter_sanitizer.permit(:sign_up, keys: [ :username, :email, :password, :password_confirmation ]) 10 | devise_parameter_sanitizer.permit(:sign_in, keys: [ :login, :username, :email, :password ]) 11 | devise_parameter_sanitizer.permit(:account_update, keys: [ :username, :email, :password, :password_confirmation, :current_password ]) 12 | end 13 | 14 | def is_valid_email text 15 | return text =~ Devise.email_regexp 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/article_controller.rb: -------------------------------------------------------------------------------- 1 | class ArticleController < ApplicationController 2 | include ArticleHelper 3 | include ApplicationHelper 4 | before_action :authenticate_user!, :except => [:show] 5 | 6 | def index 7 | params[:input] = "" if params[:input] == nil 8 | tags, terms = understand_query(params[:input]) 9 | if tags.length > 0 and terms.length == 0 10 | @searchArticles = current_user.articles.searchForTags tags 11 | elsif tags.length > 0 and terms.length > 0 12 | @searchArticles = current_user.articles.searchForTagsAndTerms tags, terms 13 | else 14 | @searchArticles = current_user.articles.search params[:input] 15 | end 16 | end 17 | 18 | def create 19 | temp = current_user.articles.create 20 | article_params = get_article_params(params) 21 | article_params.map { |k, v| temp[k] = v } 22 | 23 | if !temp.valid? 24 | redirect_to new_article_path(article_params) 25 | return 26 | end 27 | 28 | if temp.save! 29 | redirect_to root_path 30 | else 31 | render plain: "Could not save the article!" 32 | end 33 | end 34 | 35 | def new 36 | @article = { } 37 | @populated = (params.keys & allowed_params).count > 0 38 | 39 | if @populated 40 | @article = current_user.articles.create 41 | get_article_params(params).map { |k, v| @article[k] = v } 42 | @article.valid? 43 | end 44 | end 45 | 46 | def destroy 47 | if Article.find(params[:id]).user_id != current_user.id 48 | redirect_to root_path, alert: "That's not your article to edit!" 49 | return 50 | end 51 | 52 | if Article.find(params[:id]).delete 53 | redirect_to root_path, notice: "That article was deleted!" 54 | else 55 | redirect_to root_path, alert: "That article could not be deleted! Try again later." 56 | end 57 | end 58 | 59 | def edit 60 | if Article.find(params[:id]).user_id != current_user.id 61 | redirect_to root_path, alert: "That's not your article to edit!" 62 | return 63 | end 64 | 65 | @article = Article.find(params[:id]) 66 | @article.valid? 67 | end 68 | 69 | def update 70 | if Article.find(params[:id]).user_id != current_user.id 71 | redirect_to root_path, alert: "That's not your article to edit!" 72 | return 73 | end 74 | 75 | temp = Article.find(params[:id]) 76 | get_article_params(params).map { |k, v| temp[k] = v } 77 | 78 | if !temp.valid? 79 | redirect_to edit_article_path(temp), alert: "OOPS! There were errors in that article: #{temp.errors.full_messages.join '; '}" 80 | return 81 | end 82 | 83 | if temp.save! 84 | redirect_to root_path, notice: "Article updated!" 85 | else 86 | redirect_to root_path, alert: "Couldn't update that article, try again later." 87 | end 88 | end 89 | 90 | def show 91 | temp = Article.where(:id => params[:id]) 92 | if temp.count < 1 || !show_allowed(temp[0]) 93 | redirect_to root_path, alert: "That article doesn't exist!" 94 | return 95 | end 96 | @article = temp[0] 97 | 98 | heading = article_title @article 99 | quote_slice_size = 100 100 | quote_slice = @article.quote.slice(0, quote_slice_size) + (@article.quote.length > quote_slice_size ? "..." : "") 101 | desc = "Cutout from #{link_host @article}" + 102 | " by #{@article.user.username} on #{@article.created_at.to_date.to_formatted_s :long}." + 103 | " \"#{quote_slice}\"" 104 | 105 | set_meta_tags og: { title: heading, 106 | description: desc, 107 | url: show_url(@article), 108 | type: "article", 109 | article: { 110 | author: @article.author 111 | }, 112 | image: cutouts_show_image_url }, 113 | twitter: { 114 | card: "summary", 115 | site: "@CutoutsApp", 116 | title: heading, 117 | description: desc, 118 | image: { 119 | _: cutouts_show_image_url, 120 | alt: "Scissors" 121 | } 122 | }, 123 | title: heading, 124 | description: desc, 125 | reverse: true 126 | end 127 | 128 | def share 129 | temp = Article.where(:id => params[:id]) 130 | if temp.count >= 1 131 | @thisOne = temp[0] 132 | else 133 | redirect_to root_path, alert: "That article doesn't exist!" 134 | end 135 | end 136 | 137 | def send_share 138 | temp = Article.where(:id => params[:id]) 139 | if temp.count < 1 140 | redirect_to root_path, alert: "That article doesn't exist!" 141 | return 142 | end 143 | article = temp[0] 144 | 145 | emails = params[:emails] 146 | emails = emails.split(",") 147 | emails = emails.map { |email| email.strip } 148 | valid_emails = emails.select { |email| is_valid_email(email) } 149 | 150 | # send emails to atmost 5 people at once 151 | valid_emails = valid_emails.slice(0, 5) 152 | 153 | # find out if this user should be cc'ed 154 | cc_author = params[:cc_myself] 155 | 156 | ArticleSharer.share_article(article, 157 | valid_emails, 158 | current_user, 159 | params[:share_as], 160 | cc_author, 161 | params[:comments]).deliver 162 | 163 | redirect_to root_path, notice: "Article shared with #{valid_emails.join ", "}" 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/list_articles_controller.rb: -------------------------------------------------------------------------------- 1 | class ListArticlesController < ApplicationController 2 | before_action :authenticate_user!, :except => [ :feed ] 3 | def index 4 | @allArticles = current_user.articles 5 | end 6 | 7 | def feed 8 | @page = params[:page] ? params[:page].to_i : 0 9 | @page = @page >= 0 ? @page : 0 10 | 11 | @articles = Article.where({ :visibility => 0 }).limit(20).offset(@page * 20) 12 | if current_user != nil 13 | @articles = Article.where({ :visibility => 0 }).where.not({ user_id: current_user.id }).limit(20).offset(@page * 20) 14 | end 15 | 16 | @page = @page + 1 17 | 18 | # decide whether to show the next page 19 | @show_next_page = true 20 | if @articles.count < 20 21 | @show_next_page = false 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/search_controller.rb: -------------------------------------------------------------------------------- 1 | class SearchController < ApplicationController 2 | before_action :authenticate_user! 3 | layout "search" 4 | def index 5 | @articles = current_user.articles 6 | @article_json = @articles.to_json 7 | gon.articles = @articles 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/static_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticController < ApplicationController 2 | include ApplicationHelper 3 | def index 4 | if user_signed_in? 5 | redirect_to "/users/" 6 | else 7 | heading = "Cutouts - File away articles you love, forever" 8 | desc = "Cutouts is an online bookmarking application which allows you to cut out parts of online articles and store them forever. It is hosted on Heroku. It is completely free and the code is open source on github" 9 | 10 | set_meta_tags og: { title: heading, 11 | description: desc, 12 | url: fully_qualified_root_url, 13 | type: "website", 14 | image: cutouts_show_image_url }, 15 | twitter: { 16 | card: "summary", 17 | site: "@CutoutsApp", 18 | title: heading, 19 | description: desc, 20 | image: { 21 | _: cutouts_show_image_url, 22 | alt: "Scissors" 23 | } 24 | }, 25 | title: heading, 26 | description: desc, 27 | reverse: true 28 | end 29 | end 30 | 31 | def about 32 | heading = "Cutouts - File away articles you love, forever" 33 | desc = "Cutouts is an online bookmarking application which allows you to cut out parts of online articles and store them forever. It is hosted on Heroku. It is completely free and the code is open source on github" 34 | 35 | set_meta_tags og: { title: heading, 36 | description: desc, 37 | url: about_page_url, 38 | type: "website", 39 | image: cutouts_show_image_url }, 40 | twitter: { 41 | card: "summary", 42 | site: "@CutoutsApp", 43 | title: heading, 44 | description: desc, 45 | image: { 46 | _: cutouts_show_image_url, 47 | alt: "Scissors" 48 | } 49 | }, 50 | title: heading, 51 | description: desc, 52 | reverse: true 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/controllers/user_controller.rb: -------------------------------------------------------------------------------- 1 | class UserController < ApplicationController 2 | include ApplicationHelper 3 | before_action :authenticate_user!, except: [ :public_page ] 4 | 5 | def index 6 | @allArticles = current_user.articles.all.limit(10) 7 | @prefill = params 8 | end 9 | 10 | def public_page 11 | user = User.where({ username: params[:username] }).first 12 | if user == nil 13 | redirect_to root_path, alert: "Username #{params[:username]} doesn't exist! You can create an account with that username if you would like to!" 14 | return 15 | end 16 | 17 | @allArticles = user.articles.where ({ :visibility => Article.visibilities["open"] }) 18 | 19 | heading = "@#{user.username}'s cutouts" 20 | desc = "@#{user.username} has #{@allArticles.length} public cutouts." 21 | 22 | set_meta_tags og: { title: heading, 23 | description: desc, 24 | url: user_public_page_url(user.username), 25 | type: "article", 26 | article: { 27 | author: user.username 28 | }, 29 | image: cutouts_show_image_url }, 30 | twitter: { 31 | card: "summary", 32 | site: "@CutoutsApp", 33 | title: heading, 34 | description: desc, 35 | image: { 36 | _: cutouts_show_image_url, 37 | alt: "Scissors" 38 | } 39 | }, 40 | title: heading, 41 | description: desc, 42 | reverse: true 43 | end 44 | 45 | def export_articles 46 | @articles = current_user.articles.all 47 | 48 | respond_to do |format| 49 | format.html do 50 | send_data(render_to_string(partial: "user/export_articles", layout: false, articles: @articles), type: 'text/html', filename: "cutout-articles-#{Date.today}.html", disposition: 'attachment') 51 | end 52 | 53 | format.json do 54 | articles = JSON.pretty_generate(@articles.as_json(only: [:link, :quote, :author, :tags, :title])) 55 | send_data(articles, type: 'application/json', filename: "cutout-articles-#{Date.today}.json", disposition: 'attachment') 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/helpers/api_helpers_helper.rb: -------------------------------------------------------------------------------- 1 | module ApiHelpersHelper 2 | def err_resp(msg:) 3 | { "error" => { "msg" => msg } } 4 | end 5 | 6 | def ok_resp(payload:) 7 | { "res" => payload } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def fully_qualified_root_url 3 | "https://cutouts.siddharthkannan.in" 4 | end 5 | 6 | def about_page_url 7 | "#{fully_qualified_root_url}/about" 8 | end 9 | 10 | def cutouts_show_image_url 11 | "http://cliparts.co/cliparts/dc4/okg/dc4okgKxi.jpg" 12 | end 13 | 14 | def user_public_page_url username 15 | "#{fully_qualified_root_url}/#{username}" 16 | end 17 | 18 | def understand_query query 19 | tags = [ ] 20 | terms = "" 21 | 22 | tag_re = /tag:([a-z0-9\-]+)/i 23 | t1 = query.scan(tag_re) 24 | 25 | for i in t1 26 | if i.length > 0 and i[0].is_a? String 27 | tags.push i[0] 28 | end 29 | end 30 | 31 | t2 = query.split(tags[-1]) 32 | if t2.length > 1 33 | terms = t2[-1] 34 | end 35 | 36 | if terms != nil and terms.is_a? String 37 | terms = terms.strip 38 | end 39 | 40 | return tags, terms 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/helpers/article_helper.rb: -------------------------------------------------------------------------------- 1 | module ArticleHelper 2 | include ApplicationHelper 3 | def allowed_params 4 | forbidden_params = [ "id", "created_at", "updated_at", "user_id" ] 5 | Article.column_names.reject { |k| forbidden_params.include? k } 6 | end 7 | 8 | def get_article_params params 9 | params.select! { |k| allowed_params.include? k.to_s } 10 | fix_types params.stringify_keys.to_unsafe_h 11 | end 12 | 13 | def show_allowed article 14 | !article.closed? 15 | end 16 | 17 | def viz_int_val viz 18 | Article.visibilities[viz] 19 | end 20 | 21 | def viz_icon viz 22 | ind = viz 23 | if viz.is_a? String 24 | ind = viz_int_val viz 25 | end 26 | 27 | icons = [ "globe", "unlock", "lock" ] 28 | tips = [ "publicly available", "available only if you share the link", "visible only to you" ] 29 | 30 | fa_icon(icons[ind], class: "fa-2x", title: tips[ind]) 31 | end 32 | 33 | def link_placeholder link 34 | link.slice(0, 10) + "..." 35 | end 36 | 37 | def link_host object 38 | link_valid = object.link =~ URI::regexp 39 | if link_valid 40 | URI(object.link).host 41 | else 42 | link_placeholder(object.link) 43 | end 44 | end 45 | 46 | def article_title object 47 | title_exists = object.title && object.title.length > 0 48 | if title_exists 49 | object.title 50 | else 51 | link_host object 52 | end 53 | end 54 | 55 | def show_url article 56 | fully_qualified_root_url + "/article/#{article.id}" 57 | end 58 | 59 | private 60 | 61 | def fix_types article 62 | article.merge "visibility" => article["visibility"].to_i 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /app/helpers/list_articles_helper.rb: -------------------------------------------------------------------------------- 1 | module ListArticlesHelper 2 | def username_for_user_id user_id 3 | t = User.find(user_id) 4 | if t 5 | t.username 6 | else 7 | "" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/search_helper.rb: -------------------------------------------------------------------------------- 1 | module SearchHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/static_helper.rb: -------------------------------------------------------------------------------- 1 | module StaticHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/user_helper.rb: -------------------------------------------------------------------------------- 1 | module UserHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/mailers/.keep -------------------------------------------------------------------------------- /app/mailers/article_sharer.rb: -------------------------------------------------------------------------------- 1 | class ArticleSharer < ActionMailer::Base 2 | default from: "Cutouts " 3 | layout "mailer" 4 | add_template_helper(ApplicationHelper) 5 | 6 | def share_article(article, emails, from_user, share_as, cc_author, comments) 7 | @article = article 8 | @from_username = (share_as && share_as.length > 0) ? share_as : from_user.username 9 | @comments = comments 10 | subject_component = (article.title && article.title.length > 0) ? article.title : "a cutout" 11 | 12 | cc_emails = [ ] 13 | if cc_author 14 | cc_emails.push(from_user.email) 15 | end 16 | 17 | mail(:to => emails, cc: cc_emails, :subject => "#{@from_username} has shared #{subject_component} with you!") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/models/.keep -------------------------------------------------------------------------------- /app/models/admin.rb: -------------------------------------------------------------------------------- 1 | class Admin < ActiveRecord::Base 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable and :omniauthable 4 | devise :database_authenticatable, :validatable 5 | end 6 | -------------------------------------------------------------------------------- /app/models/article.rb: -------------------------------------------------------------------------------- 1 | class Article < ActiveRecord::Base 2 | belongs_to :user 3 | validates :link, :quote, presence: true 4 | validates :link, format: { with: URI::regexp, message: "field must be a valid URL: Include http or https in the URL." } 5 | validates :link, length: { maximum: 255 } 6 | default_scope { order(created_at: :desc) } 7 | enum visibility: [ :open, :unlisted, :closed ] 8 | 9 | # This function has become long and complicated because of 10 | # a complex migration from the ActiveRecord serialized version of 11 | # arrays (stored as YAML) back to a normal comma-separated string 12 | def tags_array 13 | # http://stackoverflow.com/a/17641383/2080089 14 | return self.tags.split(',').uniq.map(&:strip) 15 | end 16 | 17 | def self.terms_query input 18 | "LOWER(quote) like LOWER('%#{input}%') 19 | or LOWER(author) like LOWER('%#{input}%') 20 | or LOWER(link) like LOWER('%#{input}%') 21 | or LOWER(tags) like LOWER('%#{input}')" 22 | end 23 | 24 | def self.search input 25 | return where(terms_query input) 26 | end 27 | 28 | def self.tags_query tags 29 | q1 = [ ] 30 | for i in tags 31 | q1.push "LOWER(tags) LIKE LOWER('%#{i}%')" 32 | end 33 | q1 = q1.join(" and ") 34 | return q1 35 | end 36 | 37 | def self.searchForTags tags 38 | return where(tags_query tags) 39 | end 40 | 41 | def self.searchForTagsAndTerms tags, terms 42 | q1 = tags_query tags 43 | q2 = terms_query terms 44 | 45 | return where("#{q1} and (#{q2})") 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/session.rb: -------------------------------------------------------------------------------- 1 | class Session < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable and :omniauthable 4 | devise :database_authenticatable, :registerable, :confirmable, 5 | :recoverable, :rememberable, :trackable, :validatable, 6 | authentication_keys: [ :login ] 7 | has_many :articles 8 | validates :username, :email, presence: true, uniqueness: { case_sensitive: false } 9 | validates_format_of :username, with: /^[a-zA-Z0-9_\.]*$/, :multiline => true 10 | 11 | attr_writer :login 12 | 13 | def login 14 | @login || self.username || self.email 15 | end 16 | 17 | def self.find_for_database_authentication(warden_conditions) 18 | conditions = warden_conditions.dup 19 | if login = conditions.delete(:login) 20 | where(conditions.to_hash).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first 21 | elsif conditions.has_key?(:username) || conditions.has_key?(:email) 22 | where(conditions.to_h).first 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/api_helpers/password_compare.html.haml: -------------------------------------------------------------------------------- 1 | %h1 ApiHelpers#password_compare 2 | %p Find me in app/views/api_helpers/password_compare.html.haml 3 | -------------------------------------------------------------------------------- /app/views/article/_entry_form_basics.haml: -------------------------------------------------------------------------------- 1 | = text_field_tag "link", prefill["link"], { :placeholder => "Link to the article", :required => "required" } 2 | = text_field_tag "author", prefill["author"], { :placeholder => "Name of the authors" } 3 | = text_field_tag "tags", prefill["tags"], { :placeholder => "Comma-separated tags" } 4 | = text_area_tag "quote", prefill["quote"], { :placeholder => "A selected paragraph from the article", :required => "required" } 5 | = text_field_tag "title", prefill["title"], { :placeholder => "Title of the article (optional)" } 6 | -------------------------------------------------------------------------------- /app/views/article/_visibility_chooser.haml: -------------------------------------------------------------------------------- 1 | - classes = [ "", "", "" ] 2 | - checked = [ false, false, false ] 3 | - selected = viz.to_i || 0 4 | - classes[selected] = "active" 5 | - checked[selected] = true 6 | .btn-group.btn-group-lg.btn-group-toggle{"data-toggle" => "buttons"} 7 | %label.btn.btn-secondary{ title: "Visible on your public Cutouts page", class: classes[0] } 8 | %input{ type: "radio", name: "visibility", value: "0", checked: checked[0] } 9 | %span Public 10 | %span.align-middle= fa_icon("globe", class: "fa-2x") 11 | %label.btn.btn-secondary{ title: "Invisible on your public Cutouts page, but you can still share the link to the cutout", class: classes[1] } 12 | %input{ type: "radio", name: "visibility", value: "1", checked: checked[1] } 13 | %span Unlisted 14 | %span= fa_icon("unlock", class: "fa-2x") 15 | %label.btn.btn-secondary{ title: "Visible only to you", class: classes[2] } 16 | %input{ type: "radio", name: "visibility", value: "2", checked: checked[2] } 17 | %span Private 18 | %span= fa_icon("lock", class: "fa-2x") 19 | -------------------------------------------------------------------------------- /app/views/article/edit.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6 4 | - if @article != { } and @article.errors.any? 5 | .errors 6 | %h3 Errors 7 | %p="#{pluralize(@article.errors.count, "error")} found in the article" 8 | %ol 9 | - for msg in @article.errors.full_messages 10 | %li=msg 11 | = form_tag(article_path(@article), method: :patch) do 12 | = render 'article/entry_form_basics', prefill: @article 13 | = render 'article/visibility_chooser', viz: viz_int_val(@article.visibility) 14 | = submit_tag "Update article" 15 | -------------------------------------------------------------------------------- /app/views/article/index.html.haml: -------------------------------------------------------------------------------- 1 | - # TODO: Figure out layout (boxes like Pocket?) ; Should look good in responsive as well 2 | = form_tag article_index_path, :method => :get do 3 | = text_field_tag :input, params[:input] 4 | = submit_tag "Search" 5 | - @searchArticles.each do |article| 6 | = render 'shared/article_single', :object => article 7 | -------------------------------------------------------------------------------- /app/views/article/new.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6 4 | - if @article != { } and @article.errors.any? and @populated 5 | .errors 6 | %h3 Errors 7 | %p="#{pluralize(@article.errors.count, "error")} found in the article" 8 | %ol 9 | - for msg in @article.errors.full_messages 10 | %li=msg 11 | = render :partial => "shared/article_entry_form", locals: { prefill: @article } 12 | -------------------------------------------------------------------------------- /app/views/article/share.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6 4 | = render 'shared/article_single_show', :object => @thisOne 5 | %h3 Share this Cutout! 6 | =form_tag("#{article_path(@thisOne)}/share", method: :post, class: "form") do 7 | = text_field_tag "emails", "", { :placeholder => "Please enter comma separated email IDs to share with here (maximum 5)", :required => true } 8 | = text_field_tag "share_as", "", { :placeholder => "The name that will be on the email. Default: #{current_user.username}" } 9 | = text_area_tag "comments", "", { :placeholder => "Add any comments you want to put in this email alongwith your cutout" } 10 | .form-group.row 11 | .form-check.form-check-inline.col-auto 12 | = check_box_tag "cc_myself", "", false, class: "form-check-input", id: "cc_myself" 13 | = label_tag "cc_myself", "cc myself", class: "form-check-label" 14 | = submit_tag "Share with these email IDs" 15 | -------------------------------------------------------------------------------- /app/views/article/show.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6{:style => "font-size: 25px" } 4 | = render "shared/article_single_show", :object => @article 5 | -------------------------------------------------------------------------------- /app/views/article_sharer/share_article.html.haml: -------------------------------------------------------------------------------- 1 | %p= "#{@from_username} has shared a Cutout from #{@article.link =~ URI::regexp ? URI(@article.link).host : "#{@article.link.slice(0, 10)}..."} with you." 2 | - if @comments != nil && @comments.length > 0 3 | %p= @comments 4 | %blockquote= @article.quote 5 | %p= "written by #{@article.author}" 6 | %a{:href => @article.link} See the complete article here! 7 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %meta{:name => "viewport", :content => "width=device-width, initial-scale=1"}/ 6 | = display_meta_tags site: 'Cutouts' 7 | %title Cutouts · File articles you love 8 | = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true 9 | = javascript_include_tag 'application', 'data-turbolinks-track' => true 10 | = csrf_meta_tags 11 | %script 12 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 13 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 14 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 15 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 16 | 17 | ga('create', 'UA-40518182-12', 'auto'); 18 | ga('send', 'pageview'); 19 | %body 20 | %h1.text-center Cutouts 21 | - if user_signed_in? 22 | %h1 23 | @#{current_user.username} 24 | #{link_to(fa_icon("home"), root_path)} 25 | #{link_to("Feed", feed_path)} 26 | %span.pull-right 27 | #{link_to(fa_icon("gear"), edit_user_registration_path )} 28 | #{link_to("Logout", destroy_user_session_path, method: :delete)} 29 | 30 | %hr{:style => "border-color: grey;"} 31 | 32 | - if notice 33 | %p.alert.alert-success= notice 34 | - if alert 35 | %p.alert.alert-danger= alert 36 | = yield 37 | 38 | %hr{:style => "border-color: grey;"} 39 | %p 40 | Cutouts is an 41 | %a{:href => "https://github.com/icyflame/cutouts", :target => "_blank"} 42 | open source 43 | application. Code licensed under the MIT license. 44 | 45 | %span.pull-right 46 | Copyright 2018 47 | %a{:href => "https://www.siddharthkannan.in"} 48 | Siddharth Kannan 49 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.haml: -------------------------------------------------------------------------------- 1 | %hmtl 2 | %head 3 | %meta{'http-equiv' => 'Content-Type', 'content' => "text/html; charset=utf8"}/ 4 | 5 | %body 6 | %a{:href => fully_qualified_root_url} 7 | %h1 Cutouts 8 | = yield 9 | %hr/ 10 | %p 11 | %a{:href => "#{fully_qualified_root_url}"} Cutouts 12 | is an online bookmarking application which allows you to cut out 13 | parts of online articles and store them forever. It is hosted on Heroku. 14 | It is completely free. The code is open source and can be found at 15 | %a{:href => "https://github.com/icyflame/cutouts"} GitHub 16 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.haml: -------------------------------------------------------------------------------- 1 | = yield -------------------------------------------------------------------------------- /app/views/layouts/search.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | = Gon::Base.render_data 5 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 6 | %meta{:name => "viewport", :content => "width=device-width, initial-scale=1"}/ 7 | = display_meta_tags site: 'Cutouts' 8 | %title Cutouts · File articles you love - Search 9 | = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true 10 | = javascript_include_tag 'application', 'data-turbolinks-track' => true 11 | = csrf_meta_tags 12 | %script 13 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 14 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 15 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 16 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 17 | 18 | ga('create', 'UA-40518182-12', 'auto'); 19 | ga('send', 'pageview'); 20 | %body 21 | %h1.text-center Cutouts 22 | - if user_signed_in? 23 | %h1 24 | @#{current_user.username} 25 | #{link_to(fa_icon("home"), root_path)} 26 | #{link_to("Feed", feed_path)} 27 | %span.pull-right 28 | #{link_to(fa_icon("gear"), edit_user_registration_path )} 29 | #{link_to("Logout", destroy_user_session_path, method: :delete)} 30 | 31 | %hr{:style => "border-color: grey;"} 32 | 33 | - if notice 34 | %p.alert.alert-success= notice 35 | - if alert 36 | %p.alert.alert-danger= alert 37 | = yield 38 | 39 | %hr{:style => "border-color: grey;"} 40 | %p 41 | Cutouts is an 42 | %a{:href => "https://github.com/icyflame/cutouts", :target => "_blank"} 43 | open source 44 | application. Code licensed under the MIT license. Search Page. 45 | 46 | %span.pull-right 47 | Copyright 2018 48 | %a{:href => "https://www.siddharthkannan.in"} 49 | Siddharth Kannan 50 | -------------------------------------------------------------------------------- /app/views/list_articles/feed.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6 4 | = render 'shared/list_several_articles', articles: @articles, partial: "shared/article_single_show_feed" 5 | %hr{:style => "border-color: grey;"} 6 | - if @page >= 2 7 | %a.btn.btn-primary{:href => "#{feed_path}?page=#{@page-2}"} 8 | Prev Page 9 | - if @show_next_page 10 | .pull-right 11 | %a.btn.btn-primary{:href => "#{feed_path}?page=#{@page}"} 12 | Next Page 13 | %br/ 14 | %br/ 15 | -------------------------------------------------------------------------------- /app/views/list_articles/index.html.haml: -------------------------------------------------------------------------------- 1 | - # TODO: Figure out layout (boxes like Pocket?) ; Should look good in responsive as well 2 | .row 3 | .col-md-3 4 | .center.col-md-6 5 | = render 'shared/list_several_articles', articles: @allArticles, partial: "shared/article_single" 6 | -------------------------------------------------------------------------------- /app/views/search/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 2 | You have a total of #{@articles.count} Articles 3 | 4 | %input.form-control{type: "text", value: "#{@search_string}", placeholder: "Start entering your search to show a list of results", id: "search_page_field", autofocus: true } 5 | %div.reference-html 6 | = render 'shared/list_several_articles', articles: @articles, partial: "shared/article_single" 7 | 8 | %div.result 9 | 10 | %script{src: "https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.0.4/fuse.min.js"} 11 | :javascript 12 | $(".reference-html").hide() 13 | var options = { 14 | keys: ["title", "author", "quote", "tags", "link"], 15 | shouldSort: true, 16 | minMatchCharLength: 3 17 | } 18 | 19 | var fuse = new Fuse(gon.articles, options) 20 | 21 | $("#search_page_field").on('input', function() { 22 | var search_query = $("#search_page_field").val(); 23 | var result = fuse.search(search_query); 24 | 25 | $(".result").empty() 26 | if (search_query.length > 0) { 27 | for (var i = 0; i < result.length; i++) { 28 | $("#article-id-" + result[i].id).clone().appendTo(".result"); 29 | } 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /app/views/shared/_article_entry_form.haml: -------------------------------------------------------------------------------- 1 | .center 2 | %h3 Add an article right away! 3 | - prefill = prefill || { } 4 | = form_tag article_index_path, method: :post do 5 | = render 'article/entry_form_basics', prefill: prefill 6 | = render 'article/visibility_chooser', viz: prefill["visibility"] || 0 7 | %input{ :type => "submit", :value => "Add article" } 8 | -------------------------------------------------------------------------------- /app/views/shared/_article_single.haml: -------------------------------------------------------------------------------- 1 | = render 'shared/article_single_master', object: object, user_page: true 2 | -------------------------------------------------------------------------------- /app/views/shared/_article_single_master.haml: -------------------------------------------------------------------------------- 1 | -# if feed_page is set, then the cutout's owner's username will be shown after the time of addition 2 | - feed_page = feed_page || false 3 | 4 | -# if user_page is set, then the edit cutout and delete cutout buttons will be shown 5 | - user_page = user_page || false 6 | 7 | %div.article_single{id: "article-id-#{object.id}"} 8 | 9 | %a{:href => object.link, :target => "_blank"} 10 | %span{:style => "font-size: 20px"} 11 | #{article_title object} #{fa_icon("external-link")} 12 | %span   #{viz_icon object.visibility} 13 | 14 | %p{:style => "font-size: 16px"} 15 | - if object.author.length > 0 16 | written by #{object.author}. 17 | %i{title: "#{object.created_at.to_s}"} 18 | added #{time_ago_in_words(object.created_at)} ago 19 | - if feed_page 20 | - username = username_for_user_id(object.user_id) 21 | - if username.length > 0 22 | by 23 | %a{:href => "/#{username}", :target => "_blank"} 24 | @#{username} 25 | %a.pull-right{:href => "https://archive.is/#{object.link}", :target => "_blank"} 26 | ARCHIVES 27 | 28 | %p{:style => "font-size: 15px"} 29 | - object.tags_array.each do |tag| 30 | %a.btn.btn-primary{:href => "#{article_index_path}?input=tag:#{tag}"}= tag 31 | 32 | %span.pull-right 33 | #{link_to(fa_icon("link", class: "fa-2x"), article_path(object))}   34 | #{link_to(fa_icon("share-alt", class: "fa-2x"), "#{article_path(object)}/share")}   35 | 36 | - if user_page 37 | #{link_to(fa_icon("pencil", class: "fa-2x"), edit_article_path(object))}   38 | #{link_to(fa_icon("trash", class: "fa-2x"), article_path(object), method: :delete, data: { confirm: "Are you sure you want to delete that article?" } )} 39 | 40 | %blockquote.article-quote #{object.quote} 41 | -------------------------------------------------------------------------------- /app/views/shared/_article_single_show.haml: -------------------------------------------------------------------------------- 1 | = render 'shared/article_single_master', object: object, feed_page: true 2 | -------------------------------------------------------------------------------- /app/views/shared/_article_single_show_feed.haml: -------------------------------------------------------------------------------- 1 | = render 'shared/article_single_master', object: object, feed_page: true 2 | -------------------------------------------------------------------------------- /app/views/shared/_list_several_articles.haml: -------------------------------------------------------------------------------- 1 | - if articles.count <= 0 2 | %h1 No cutouts found! 3 | - else 4 | - articles.each do |article| 5 | = render partial, :object => article 6 | -------------------------------------------------------------------------------- /app/views/static/about.html.haml: -------------------------------------------------------------------------------- 1 | %div.text-center 2 | %h2 About Cutouts 3 | %h3 The internet is the newspaper, the link to the article is the cutout 4 | %h3 A collection of all the articles that you have read, liked and would want to read again later. 5 | .about 6 | %h3 7 | Cutouts is a web application to bookmark the multitude of articles you read 8 | online, with the article's author, a quote from the article and some tags 9 | for classifying articles. Cutouts aims to be a basic web application that 10 | has none of the bells and whistles of a product like Pinterest which makes 11 | it's offerings clunky and slow. 12 | 13 | %h3 14 | Free and open source, Licensed under MIT: 15 | %a{:href => "https://github.com/icyflame/cutouts"} GitHub 16 | 17 | %h3 18 | %span Hosted on 19 | %a{:href => "https://heroku.com"} Heroku 20 | -------------------------------------------------------------------------------- /app/views/static/index.html.haml: -------------------------------------------------------------------------------- 1 | %div.text-center 2 | %h3 Cutout parts of articles you love and store them forever 3 | %h3 4 | Cutouts is free and will remain so forever! The code is open source on 5 | %a{:href => "https://github.com/icyflame" } GitHub 6 | %p 7 | Email cutouts to friends and family. Share the permalink on social media. 8 | Tag cutouts and search them using search strings and tags! 9 | %h3 Start adding Cutouts now! 10 | %h3 11 | #{link_to("Sign up", new_user_registration_path, { :class => "btn btn-lg btn-primary" })}   12 | #{link_to("Login", new_user_session_path, { :class => "btn btn-lg btn-primary" })}   13 | #{link_to("Check out the feed!", feed_path, { :class => "btn btn-lg btn-primary" })} 14 | = image_tag "frontpage.png", { :width => "1000px" } 15 | -------------------------------------------------------------------------------- /app/views/user/_export_articles.html.haml: -------------------------------------------------------------------------------- 1 | %html 2 | %head 3 | %meta{'http-equiv' => 'Content-Type', 'content' => "text/html; charset=utf8"}/ 4 | %title 5 | #{current_user.username}'s Cutouts - #{Date.today} 6 | :css 7 | body { 8 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | font-size: 14px; 10 | line-height: 1.428571429; 11 | color: #333333; 12 | margin: 40px; 13 | } 14 | p, h1, h2 { 15 | margin: 0; 16 | } 17 | hr { 18 | padding: 0 0 20px 0; 19 | border: none; 20 | border-top: medium double #333; 21 | color: #333; 22 | text-align: center; 23 | } 24 | a { 25 | font-size: 19px; 26 | color: #337ab7; 27 | text-decoration: none; 28 | } 29 | article { 30 | margin-bottom: 30px; 31 | } 32 | .article-quote { 33 | border-left: 10px solid gray; 34 | color: #777777; 35 | padding: 10px 20px; 36 | font-size: 17.5px; 37 | margin-left: 0; 38 | margin-top: 4px; 39 | } 40 | 41 | %body 42 | %h1 43 | #{current_user.username}'s Cutouts 44 | %p 45 | exported #{pluralize(@articles.count, "cutout") } on #{Date.today} 46 | %hr 47 | - @articles.each do |article| 48 | %article 49 | %h2 50 | %a{href: article.link, target: "_blank"} 51 | = article.title ? article.title : article.link 52 | %p 53 | written by #{article.author} 54 | %blockquote.article-quote 55 | = article.quote 56 | -------------------------------------------------------------------------------- /app/views/user/index.html.haml: -------------------------------------------------------------------------------- 1 | = form_tag article_index_path, :method => :get do 2 | = text_field_tag :input, params[:input] 3 | = submit_tag "Search" 4 | .row 5 | .col-md-6 6 | = render partial: "shared/article_entry_form", locals: { prefill: @prefill } 7 | 8 | .col-md-6 9 | %h3 10 | The last few Cutouts you added: 11 | %span.pull-right #{link_to(fa_icon("list"), list_articles_index_path, title: "See all your Cutouts")} 12 | 13 | = render "shared/list_several_articles", articles: @allArticles, partial: "shared/article_single" 14 | -------------------------------------------------------------------------------- /app/views/user/public_page.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .center.col-md-6 4 | = render 'shared/list_several_articles', articles: @allArticles, partial: "shared/article_single_show" 5 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Resend confirmation instructions 2 | = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| 3 | = devise_error_messages! 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true 8 | .actions 9 | = f.submit "Resend confirmation instructions" 10 | = render "users/shared/links" 11 | -------------------------------------------------------------------------------- /app/views/users/mailer/confirmation_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Welcome #{@email}! 3 | %p You can confirm your account email through the link below: 4 | %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) 5 | -------------------------------------------------------------------------------- /app/views/users/mailer/reset_password_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@resource.email}! 3 | %p Someone has requested a link to change your password. You can do this through the link below. 4 | %p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) 5 | %p If you didn't request this, please ignore this email. 6 | %p Your password won't change until you access the link above and create a new one. 7 | -------------------------------------------------------------------------------- /app/views/users/mailer/unlock_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@resource.email}! 3 | %p Your account has been locked due to an excessive number of unsuccessful sign in attempts. 4 | %p Click the link below to unlock your account: 5 | %p= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) 6 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Change your password 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| 3 | = devise_error_messages! 4 | = f.hidden_field :reset_password_token 5 | .field 6 | = f.label :password, "New password" 7 | %br/ 8 | = f.password_field :password, autofocus: true, autocomplete: "off" 9 | .field 10 | = f.label :password_confirmation, "Confirm new password" 11 | %br/ 12 | = f.password_field :password_confirmation, autocomplete: "off" 13 | .actions 14 | = f.submit "Change my password" 15 | = render "users/shared/links" 16 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Forgot your password? 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| 3 | = devise_error_messages! 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true 8 | .actions 9 | = f.submit "Send me reset password instructions" 10 | = render "users/shared/links" 11 | -------------------------------------------------------------------------------- /app/views/users/registrations/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h2 2 | Edit #{resource_name.to_s.humanize} 3 | 4 | %h3 Export your data 5 | 6 | %h4 Download human-readable HTML file #{link_to(fa_icon("cloud-download", text: ".html", class: "fa-2x"), user_export_articles_path(format: 'html', remote: 'true', method: 'get'))} 7 | %h4 Download machine-readable JSON file #{link_to(fa_icon("cloud-download", text: ".json", class: "fa-2x"), user_export_articles_path(format: 'json', remote: 'true', method: 'get'))} 8 | 9 | - # TODO: put in the right path here for all the export options 10 | 11 | %h3 Change your password 12 | = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| 13 | = devise_error_messages! 14 | .field 15 | = f.label :password, "New Password" 16 | %i (leave blank if you don't want to change it) 17 | %br/ 18 | = f.password_field :password, autocomplete: "off" 19 | .field 20 | = f.label :password_confirmation, "New Password confirmation" 21 | %br/ 22 | = f.password_field :password_confirmation, autocomplete: "off" 23 | .field 24 | = f.label :current_password 25 | %i (we need your current password to confirm your changes) 26 | %br/ 27 | = f.password_field :current_password, autocomplete: "off" 28 | .actions 29 | = f.submit "Update" 30 | %h3 Cancel my account 31 | %p 32 | Unhappy? #{button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete} 33 | = link_to "Back", :back 34 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .col-md-6 4 | %h2 Sign up 5 | = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| 6 | = devise_error_messages! 7 | .field 8 | = f.label :email 9 | %br/ 10 | = f.email_field :email, autofocus: true 11 | .field 12 | =f.label :username 13 | %br/ 14 | =f.text_field :username, required: :required 15 | .field 16 | = f.label :password 17 | - if @validatable 18 | %em 19 | (#{@minimum_password_length} characters minimum) 20 | %br/ 21 | = f.password_field :password, autocomplete: "off" 22 | .field 23 | = f.label :password_confirmation 24 | %br/ 25 | = f.password_field :password_confirmation, autocomplete: "off" 26 | .actions 27 | = f.submit "Sign up" 28 | = render "users/shared/links" 29 | -------------------------------------------------------------------------------- /app/views/users/sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | .col-md-3 3 | .col-md-6 4 | %h2 Log in 5 | = form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| 6 | .field 7 | = f.label :login 8 | %br/ 9 | = f.text_field :login, autofocus: true, required: true 10 | .field 11 | = f.label :password 12 | %br/ 13 | = f.password_field :password, autocomplete: "off", required: true 14 | - if devise_mapping.rememberable? 15 | .form-check 16 | = f.check_box :remember_me, { class: "form-check-input" } 17 | = f.label :remember_me, { class: "form-check-label" } 18 | .actions 19 | = f.submit "Log in" 20 | = render "users/shared/links" 21 | -------------------------------------------------------------------------------- /app/views/users/shared/_links.html.haml: -------------------------------------------------------------------------------- 1 | - if controller_name != 'sessions' 2 | = link_to "Log in", new_session_path(resource_name) 3 | %br/ 4 | - if devise_mapping.registerable? && controller_name != 'registrations' 5 | = link_to "Sign up", new_registration_path(resource_name) 6 | %br/ 7 | - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' 8 | = link_to "Forgot your password?", new_password_path(resource_name) 9 | %br/ 10 | - if devise_mapping.confirmable? && controller_name != 'confirmations' 11 | = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) 12 | %br/ 13 | - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' 14 | = link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) 15 | %br/ 16 | - if devise_mapping.omniauthable? 17 | - resource_class.omniauth_providers.each do |provider| 18 | = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) 19 | %br/ 20 | -------------------------------------------------------------------------------- /app/views/users/unlocks/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Resend unlock instructions 2 | = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| 3 | = devise_error_messages! 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true 8 | .actions 9 | = f.submit "Resend unlock instructions" 10 | = render "users/shared/links" 11 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Boilerplate 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | 7 | Rails.application.routes.default_url_options[:host] = 'cutouts.herokuapp.com' 8 | 9 | ActionMailer::Base.smtp_settings = { 10 | :user_name => ENV['SENDGRID_USERNAME'], 11 | :password => ENV['SENDGRID_PASSWORD'], 12 | :domain => ENV['SENDGRID_DOMAIN'], 13 | :address => 'smtp.sendgrid.net', 14 | :port => 587, 15 | :authentication => :plain, 16 | :enable_starttls_auto => true 17 | } 18 | 19 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_files = true 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = true 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | # Ignore bad email addresses and do not raise email delivery errors. 60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 61 | # config.action_mailer.raise_delivery_errors = false 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Disable automatic flushing of the log to improve performance. 71 | # config.autoflush_log = false 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Do not dump schema after migrations. 77 | config.active_record.dump_schema_after_migration = false 78 | end 79 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | config.active_support.test_order = :sorted 40 | 41 | # config.active_record.raise_in_transactional_callbacks = true 42 | end 43 | -------------------------------------------------------------------------------- /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.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. 2 | # Many of these configuration options can be set straight in your model. 3 | Devise.setup do |config| 4 | # The secret key used by Devise. Devise uses this key to generate 5 | # random tokens. Changing this key will render invalid all existing 6 | # confirmation, reset password and unlock tokens in the database. 7 | # config.secret_key = 'faaf033e593f8e5cd7281b98067a9c044b008bedae5bccdf237326b9b048eed936ee481c28749a68a9ec096047933fe457410ada6ba479cfb9a5c41f77814527' 8 | 9 | # ==> Mailer Configuration 10 | # Configure the e-mail address which will be shown in Devise::Mailer, 11 | # note that it will be overwritten if you use your own mailer class 12 | # with default "from" parameter. 13 | config.mailer_sender = 'cutouts@siddharthkannan.in' 14 | 15 | # Configure the class responsible to send e-mails. 16 | # config.mailer = 'Devise::Mailer' 17 | 18 | # ==> ORM configuration 19 | # Load and configure the ORM. Supports :active_record (default) and 20 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 21 | # available as additional gems. 22 | require 'devise/orm/active_record' 23 | 24 | # ==> Configuration for any authentication mechanism 25 | # Configure which keys are used when authenticating a user. The default is 26 | # just :email. You can configure it to use [:username, :subdomain], so for 27 | # authenticating a user, both parameters are required. Remember that those 28 | # parameters are used only when authenticating and not when retrieving from 29 | # session. If you need permissions, you should implement that in a before filter. 30 | # You can also supply a hash where the value is a boolean determining whether 31 | # or not authentication should be aborted when the value is not present. 32 | # config.authentication_keys = [ :email ] 33 | 34 | # Configure parameters from the request object used for authentication. Each entry 35 | # given should be a request method and it will automatically be passed to the 36 | # find_for_authentication method and considered in your model lookup. For instance, 37 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 38 | # The same considerations mentioned for authentication_keys also apply to request_keys. 39 | # config.request_keys = [] 40 | 41 | # Configure which authentication keys should be case-insensitive. 42 | # These keys will be downcased upon creating or modifying a user and when used 43 | # to authenticate or find a user. Default is :email. 44 | config.case_insensitive_keys = [ :email ] 45 | 46 | # Configure which authentication keys should have whitespace stripped. 47 | # These keys will have whitespace before and after removed upon creating or 48 | # modifying a user and when used to authenticate or find a user. Default is :email. 49 | config.strip_whitespace_keys = [ :email ] 50 | 51 | # Tell if authentication through request.params is enabled. True by default. 52 | # It can be set to an array that will enable params authentication only for the 53 | # given strategies, for example, `config.params_authenticatable = [:database]` will 54 | # enable it only for database (email + password) authentication. 55 | # config.params_authenticatable = true 56 | 57 | # Tell if authentication through HTTP Auth is enabled. False by default. 58 | # It can be set to an array that will enable http authentication only for the 59 | # given strategies, for example, `config.http_authenticatable = [:database]` will 60 | # enable it only for database authentication. The supported strategies are: 61 | # :database = Support basic authentication with authentication key + password 62 | # config.http_authenticatable = false 63 | 64 | # If 401 status code should be returned for AJAX requests. True by default. 65 | # config.http_authenticatable_on_xhr = true 66 | 67 | # The realm used in Http Basic Authentication. 'Application' by default. 68 | # config.http_authentication_realm = 'Application' 69 | 70 | # It will change confirmation, password recovery and other workflows 71 | # to behave the same regardless if the e-mail provided was right or wrong. 72 | # Does not affect registerable. 73 | # config.paranoid = true 74 | 75 | # By default Devise will store the user in session. You can skip storage for 76 | # particular strategies by setting this option. 77 | # Notice that if you are skipping storage for all authentication paths, you 78 | # may want to disable generating routes to Devise's sessions controller by 79 | # passing skip: :sessions to `devise_for` in your config/routes.rb 80 | config.skip_session_storage = [:http_auth] 81 | 82 | # By default, Devise cleans up the CSRF token on authentication to 83 | # avoid CSRF token fixation attacks. This means that, when using AJAX 84 | # requests for sign in and sign up, you need to get a new CSRF token 85 | # from the server. You can disable this option at your own risk. 86 | # config.clean_up_csrf_token_on_authentication = true 87 | 88 | # ==> Configuration for :database_authenticatable 89 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If 90 | # using other encryptors, it sets how many times you want the password re-encrypted. 91 | # 92 | # Limiting the stretches to just one in testing will increase the performance of 93 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 94 | # a value less than 10 in other environments. Note that, for bcrypt (the default 95 | # encryptor), the cost increases exponentially with the number of stretches (e.g. 96 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). 97 | config.stretches = Rails.env.test? ? 1 : 10 98 | 99 | # Setup a pepper to generate the encrypted password. 100 | # config.pepper = '15e88455b148ce328709d018930a38908a46efd762c7435fb64873e21797a8e4bbf8bbc60ee486c4ac8965518f05d6cb7521600e0c13516162573e7f0291708c' 101 | 102 | # ==> Configuration for :confirmable 103 | # A period that the user is allowed to access the website even without 104 | # confirming their account. For instance, if set to 2.days, the user will be 105 | # able to access the website for two days without confirming their account, 106 | # access will be blocked just in the third day. Default is 0.days, meaning 107 | # the user cannot access the website without confirming their account. 108 | # config.allow_unconfirmed_access_for = 2.days 109 | 110 | # A period that the user is allowed to confirm their account before their 111 | # token becomes invalid. For example, if set to 3.days, the user can confirm 112 | # their account within 3 days after the mail was sent, but on the fourth day 113 | # their account can't be confirmed with the token any more. 114 | # Default is nil, meaning there is no restriction on how long a user can take 115 | # before confirming their account. 116 | # config.confirm_within = 3.days 117 | 118 | # If true, requires any email changes to be confirmed (exactly the same way as 119 | # initial account confirmation) to be applied. Requires additional unconfirmed_email 120 | # db field (see migrations). Until confirmed, new email is stored in 121 | # unconfirmed_email column, and copied to email column on successful confirmation. 122 | config.reconfirmable = false 123 | 124 | # Defines which key will be used when confirming an account 125 | # config.confirmation_keys = [ :email ] 126 | 127 | # ==> Configuration for :rememberable 128 | # The time the user will be remembered without asking for credentials again. 129 | # config.remember_for = 2.weeks 130 | 131 | # Invalidates all the remember me tokens when the user signs out. 132 | config.expire_all_remember_me_on_sign_out = true 133 | 134 | # If true, extends the user's remember period when remembered via cookie. 135 | # config.extend_remember_period = false 136 | 137 | # Options to be passed to the created cookie. For instance, you can set 138 | # secure: true in order to force SSL only cookies. 139 | # config.rememberable_options = {} 140 | 141 | # ==> Configuration for :validatable 142 | # Range for password length. 143 | config.password_length = 8..128 144 | 145 | # Email regex used to validate email formats. It simply asserts that 146 | # one (and only one) @ exists in the given string. This is mainly 147 | # to give user feedback and not to assert the e-mail validity. 148 | # config.email_regexp = /\A[^@]+@[^@]+\z/ 149 | 150 | # ==> Configuration for :timeoutable 151 | # The time you want to timeout the user session without activity. After this 152 | # time the user will be asked for credentials again. Default is 30 minutes. 153 | # config.timeout_in = 30.minutes 154 | 155 | # If true, expires auth token on session timeout. 156 | # config.expire_auth_token_on_timeout = false 157 | 158 | # ==> Configuration for :lockable 159 | # Defines which strategy will be used to lock an account. 160 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 161 | # :none = No lock strategy. You should handle locking by yourself. 162 | # config.lock_strategy = :failed_attempts 163 | 164 | # Defines which key will be used when locking and unlocking an account 165 | # config.unlock_keys = [ :email ] 166 | 167 | # Defines which strategy will be used to unlock an account. 168 | # :email = Sends an unlock link to the user email 169 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 170 | # :both = Enables both strategies 171 | # :none = No unlock strategy. You should handle unlocking by yourself. 172 | # config.unlock_strategy = :both 173 | 174 | # Number of authentication tries before locking an account if lock_strategy 175 | # is failed attempts. 176 | # config.maximum_attempts = 20 177 | 178 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 179 | # config.unlock_in = 1.hour 180 | 181 | # Warn on the last attempt before the account is locked. 182 | # config.last_attempt_warning = true 183 | 184 | # ==> Configuration for :recoverable 185 | # 186 | # Defines which key will be used when recovering the password for an account 187 | # config.reset_password_keys = [ :email ] 188 | 189 | # Time interval you can reset your password with a reset password key. 190 | # Don't put a too small interval or your users won't have the time to 191 | # change their passwords. 192 | config.reset_password_within = 6.hours 193 | 194 | # ==> Configuration for :encryptable 195 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use 196 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, 197 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) 198 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy 199 | # REST_AUTH_SITE_KEY to pepper). 200 | # 201 | # Require the `devise-encryptable` gem when using anything other than bcrypt 202 | # config.encryptor = :sha512 203 | 204 | # ==> Scopes configuration 205 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 206 | # "users/sessions/new". It's turned off by default because it's slower if you 207 | # are using only default views. 208 | config.scoped_views = true 209 | 210 | # Configure the default scope given to Warden. By default it's the first 211 | # devise role declared in your routes (usually :user). 212 | # config.default_scope = :user 213 | 214 | # Set this configuration to false if you want /users/sign_out to sign out 215 | # only the current scope. By default, Devise signs out all scopes. 216 | # config.sign_out_all_scopes = true 217 | 218 | # ==> Navigation configuration 219 | # Lists the formats that should be treated as navigational. Formats like 220 | # :html, should redirect to the sign in page when the user does not have 221 | # access, but formats like :xml or :json, should return 401. 222 | # 223 | # If you have any extra navigational formats, like :iphone or :mobile, you 224 | # should add them to the navigational formats lists. 225 | # 226 | # The "*/*" below is required to match Internet Explorer requests. 227 | # config.navigational_formats = ['*/*', :html] 228 | 229 | # The default HTTP method used to sign out a resource. Default is :delete. 230 | config.sign_out_via = :delete 231 | 232 | # ==> OmniAuth 233 | # Add a new OmniAuth provider. Check the wiki for more information on setting 234 | # up on your models and hooks. 235 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' 236 | 237 | # ==> Warden configuration 238 | # If you want to use other strategies, that are not supported by Devise, or 239 | # change the failure app, you can configure them inside the config.warden block. 240 | # 241 | # config.warden do |manager| 242 | # manager.intercept_401 = false 243 | # manager.default_strategies(scope: :user).unshift :some_external_strategy 244 | # end 245 | 246 | # ==> Mountable engine configurations 247 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine 248 | # is mountable, there are some extra configurations to be taken into account. 249 | # The following options are available, assuming the engine is mounted as: 250 | # 251 | # mount MyEngine, at: '/my_engine' 252 | # 253 | # The router that invoked `devise_for`, in the example above, would be: 254 | # config.router_name = :my_engine 255 | # 256 | # When using omniauth, Devise cannot automatically set Omniauth path, 257 | # so you need to do it manually. For the users scope, it would be: 258 | # config.omniauth_path_prefix = '/my_engine/users/auth' 259 | end 260 | -------------------------------------------------------------------------------- /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 += [:password] 5 | -------------------------------------------------------------------------------- /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/meta_tags.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in MetaTags. 2 | MetaTags.configure do |config| 3 | # How many characters should the title meta tag have at most. Default is 70. 4 | # Set to nil or 0 to remove limits. 5 | # config.title_limit = 70 6 | 7 | # When true, site title will be truncated instead of title. Default is false. 8 | # config.truncate_site_title_first = false 9 | 10 | # Maximum length of the page description. Default is 300. 11 | # Set to nil or 0 to remove limits. 12 | # config.description_limit = 300 13 | 14 | # Maximum length of the keywords meta tag. Default is 255. 15 | # config.keywords_limit = 255 16 | 17 | # Default separator for keywords meta tag (used when an Array passed with 18 | # the list of keywords). Default is ", ". 19 | # config.keywords_separator = ', ' 20 | 21 | # When true, keywords will be converted to lowercase, otherwise they will 22 | # appear on the page as is. Default is true. 23 | # config.keywords_lowercase = true 24 | 25 | # When false, generated meta tags will be self-closing () instead 26 | # of open (``). Default is true. 27 | # config.open_meta_tags = true 28 | 29 | # List of additional meta tags that should use "property" attribute instead 30 | # of "name" attribute in tags. 31 | # config.property_tags.push( 32 | # 'x-hearthstone:deck', 33 | # ) 34 | end 35 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/rails_admin.rb: -------------------------------------------------------------------------------- 1 | RailsAdmin.config do |config| 2 | 3 | ### Popular gems integration 4 | 5 | ## == Devise == 6 | config.authenticate_with do 7 | warden.authenticate! scope: :admin 8 | end 9 | config.current_user_method(&:current_admin) 10 | 11 | ## == Cancan == 12 | # config.authorize_with :cancan 13 | 14 | ## == PaperTrail == 15 | # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0 16 | 17 | ### More at https://github.com/sferik/rails_admin/wiki/Base-configuration 18 | 19 | config.actions do 20 | dashboard # mandatory 21 | index # mandatory 22 | new 23 | export 24 | bulk_delete 25 | show 26 | edit 27 | delete 28 | show_in_app 29 | 30 | ## With an audit adapter, you can add: 31 | # history_index 32 | # history_show 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_boilerplate_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | omniauth_callbacks: 27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 28 | success: "Successfully authenticated from %{kind} account." 29 | passwords: 30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 31 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 33 | updated: "Your password has been changed successfully. You are now signed in." 34 | updated_not_active: "Your password has been changed successfully." 35 | registrations: 36 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 37 | signed_up: "Welcome! You have signed up successfully." 38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 42 | updated: "Your account has been updated successfully." 43 | sessions: 44 | signed_in: "Signed in successfully." 45 | signed_out: "Signed out successfully." 46 | already_signed_out: "Signed out successfully." 47 | unlocks: 48 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 49 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 50 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 51 | errors: 52 | messages: 53 | already_confirmed: "was already confirmed, please try signing in" 54 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 55 | expired: "has expired, please request a new one" 56 | not_found: "not found" 57 | not_locked: "was not locked" 58 | not_saved: 59 | one: "1 error prohibited this %{resource} from being saved:" 60 | other: "%{count} errors prohibited this %{resource} from being saved:" 61 | -------------------------------------------------------------------------------- /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 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | workers Integer(ENV['WEB_CONCURRENCY'] || 2) 2 | threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5) 3 | threads threads_count, threads_count 4 | 5 | preload_app! 6 | 7 | rackup DefaultRackup 8 | port ENV['PORT'] || 3000 9 | environment ENV['RACK_ENV'] || 'development' 10 | 11 | on_worker_boot do 12 | # Worker specific setup for Rails 4.1+ 13 | # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot 14 | ActiveRecord::Base.establish_connection 15 | end 16 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | devise_for :admins 4 | mount RailsAdmin::Engine => '/admin', as: 'rails_admin' 5 | get 'api_helpers/password_compare' 6 | 7 | scope '/api' do 8 | scope '/v1' do 9 | scope '/articles' do 10 | post '/' => 'api_helpers#article_create' 11 | get '/' => 'api_helpers#articles_list' 12 | end 13 | scope '/users' do 14 | scope '/auth' do 15 | post '/' => 'api_helpers#user_signin' 16 | end 17 | end 18 | end 19 | end 20 | 21 | get 'search' => 'search#index' 22 | 23 | scope '/about/' do 24 | get '/' => 'static#about' 25 | end 26 | 27 | get 'list_articles/index' 28 | 29 | get '/feed' => 'list_articles#feed' 30 | 31 | get 'user/export_articles' 32 | 33 | devise_for :users 34 | resources :article 35 | 36 | get 'users' => 'user#index' 37 | get ':username' => 'user#public_page' 38 | 39 | get 'article/:id/share' => 'article#share' 40 | post 'article/:id/share' => 'article#send_share' 41 | 42 | root 'static#index' 43 | end 44 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: fc86a57287f54571e340725d11407424aab8d37ca98236e8fbbadcb7866642769dcbaea680447a70ca1c0dc86ab1e01fd14bc5a66802a27adcc6a3a272ab8c75 15 | 16 | test: 17 | secret_key_base: 7d91a34537b30a2e449e74601497e8b879eb401812eea540ec89ffb5b2b03951bc7d7edea61715407b19b96d682d2a3c90942d9bc18ecd3a73c57d966b6f770b 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /db/migrate/20160228183336_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :users do |t| 4 | t.string :email, :unique => true 5 | t.string :username, :unique => true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160228183446_create_articles.rb: -------------------------------------------------------------------------------- 1 | class CreateArticles < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :articles do |t| 4 | t.string :link 5 | t.string :quote 6 | t.string :author 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160228183853_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 | 33 | # Uncomment below if timestamps were not included in your original model. 34 | # t.timestamps 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | 43 | def self.down 44 | # By default, we don't want to make any assumption about how to roll back a migration when your 45 | # model already existed. Please edit below which fields you would like to remove in this migration. 46 | raise ActiveRecord::IrreversibleMigration 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /db/migrate/20160228192041_add_user_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddUserToArticles < ActiveRecord::Migration[4.2] 2 | def change 3 | add_reference :articles, :user, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160306110412_add_confirmable_to_devise.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmableToDevise < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :users, :confirmation_token, :string 4 | add_column :users, :confirmed_at, :datetime 5 | add_column :users, :confirmation_sent_at, :datetime 6 | # add_column :users, :unconfirmed_email, :string # Only if using reconfirmable 7 | add_index :users, :confirmation_token, unique: true 8 | # User.reset_column_information # Need for some types of updates, but not for update_all. 9 | # To avoid a short time window between running the migration and updating all existing 10 | # users as confirmed, do the following 11 | # execute("UPDATE users SET confirmed_at = NOW()") 12 | # All existing user accounts should be able to log in after this. 13 | # Remind: Rails using SQLite as default. And SQLite has no such function :NOW. 14 | # Use :date('now') instead of :NOW when using SQLite. 15 | #execute("UPDATE users SET confirmed_at = date('now')") 16 | User.all.update_all confirmed_at: Time.now 17 | end 18 | 19 | def down 20 | remove_columns :users, :confirmation_token, :confirmed_at, :confirmation_sent_at 21 | # remove_columns :users, :unconfirmed_email # Only if using reconfirmable 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20160312135338_add_index_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :users, :username, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160321183951_change_quote_column_users.rb: -------------------------------------------------------------------------------- 1 | class ChangeQuoteColumnUsers < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_column :articles, :quote, :text 4 | end 5 | 6 | def self.down 7 | change_column :articles, :quote, :string 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160321184517_change_size_of_quote_column_articles.rb: -------------------------------------------------------------------------------- 1 | class ChangeSizeOfQuoteColumnArticles < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_column :articles, :quote, :text, :limit => nil 4 | end 5 | 6 | def self.down 7 | change_column :articles, :quote, :text, :limit => 255 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160807150421_create_sessions.rb: -------------------------------------------------------------------------------- 1 | class CreateSessions < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :sessions do |t| 4 | t.string :sid 5 | t.string :user_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20161014194032_add_tags_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddTagsToArticles < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :articles, :tags, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161018055739_change_tags_format.rb: -------------------------------------------------------------------------------- 1 | class ChangeTagsFormat < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_column :articles, :tags, :string, :default => "" 4 | end 5 | def self.down 6 | change_column :articles, :tags, :string, :default => "--- []\n" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20170112075852_devise_create_admins.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateAdmins < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table(:admins) do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.string :current_sign_in_ip 20 | t.string :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps 35 | end 36 | 37 | add_index :admins, :email, unique: true 38 | add_index :admins, :reset_password_token, unique: true 39 | # add_index :admins, :confirmation_token, unique: true 40 | # add_index :admins, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /db/migrate/20171214043342_add_title_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddTitleToArticles < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :articles, :title, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180210205553_add_visibility_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddVisibilityToArticles < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :articles, :visibility, :integer, :default => 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2018_02_10_205553) do 14 | 15 | create_table "admins", force: :cascade do |t| 16 | t.string "email", default: "", null: false 17 | t.string "encrypted_password", default: "", null: false 18 | t.string "reset_password_token" 19 | t.datetime "reset_password_sent_at" 20 | t.datetime "remember_created_at" 21 | t.integer "sign_in_count", default: 0, null: false 22 | t.datetime "current_sign_in_at" 23 | t.datetime "last_sign_in_at" 24 | t.string "current_sign_in_ip" 25 | t.string "last_sign_in_ip" 26 | t.datetime "created_at" 27 | t.datetime "updated_at" 28 | t.index ["email"], name: "index_admins_on_email", unique: true 29 | t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true 30 | end 31 | 32 | create_table "articles", force: :cascade do |t| 33 | t.string "link" 34 | t.text "quote" 35 | t.string "author" 36 | t.datetime "created_at" 37 | t.datetime "updated_at" 38 | t.integer "user_id" 39 | t.string "tags", default: "" 40 | t.string "title" 41 | t.integer "visibility", default: 0 42 | t.index ["user_id"], name: "index_articles_on_user_id" 43 | end 44 | 45 | create_table "sessions", force: :cascade do |t| 46 | t.string "sid" 47 | t.string "user_id" 48 | t.datetime "created_at" 49 | t.datetime "updated_at" 50 | end 51 | 52 | create_table "users", force: :cascade do |t| 53 | t.string "email" 54 | t.string "username" 55 | t.datetime "created_at" 56 | t.datetime "updated_at" 57 | t.string "encrypted_password", default: "", null: false 58 | t.string "reset_password_token" 59 | t.datetime "reset_password_sent_at" 60 | t.datetime "remember_created_at" 61 | t.integer "sign_in_count", default: 0, null: false 62 | t.datetime "current_sign_in_at" 63 | t.datetime "last_sign_in_at" 64 | t.string "current_sign_in_ip" 65 | t.string "last_sign_in_ip" 66 | t.string "confirmation_token" 67 | t.datetime "confirmed_at" 68 | t.datetime "confirmation_sent_at" 69 | t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true 70 | t.index ["email"], name: "index_users_on_email", unique: true 71 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 72 | t.index ["username"], name: "index_users_on_username", unique: true 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /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 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /deploy/nginx-configurations/README.md: -------------------------------------------------------------------------------- 1 | # Nginx configurations 2 | 3 | ``` shell 4 | $ cp * /etc/nginx/sites-available 5 | $ ls -1 | while read p; do ln -s /etc/nginx/sites-available/$p /etc/nginx/sites-enabled/$p; done 6 | $ systemctl reload nginx.service 7 | ``` 8 | -------------------------------------------------------------------------------- /deploy/nginx-configurations/certbot: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name cutouts.siddharthkannan.in cutouts-stg.siddharthkannan.in; 4 | 5 | location ~ /.well-known/acme-challenge { 6 | root /var/www/html; 7 | } 8 | 9 | # Forward all other paths to the HTTPS upstreams on this server 10 | location / { 11 | return 301 https://$host$request_uri; 12 | } 13 | } -------------------------------------------------------------------------------- /deploy/nginx-configurations/cutouts-stg.siddharthkannan.in: -------------------------------------------------------------------------------- 1 | upstream cutouts-staging { 2 | server localhost:8081; 3 | } 4 | 5 | server { 6 | listen 443 ssl http2; 7 | listen [::]:443 ssl http2; 8 | server_name cutouts-stg.siddharthkannan.in; 9 | 10 | # configuration from https://www.cyberciti.biz/faq/configure-nginx-to-use-only-tls-1-2-and-1-3/ 11 | ssl_protocols TLSv1.2 TLSv1.3; 12 | ssl_prefer_server_ciphers off; 13 | 14 | ssl_session_timeout 1d; 15 | ssl_session_cache shared:SharedNixCraftSSL:10m; 16 | ssl_session_tickets off; 17 | 18 | ssl_certificate /etc/letsencrypt/live/cutouts-stg.siddharthkannan.in/fullchain.pem; 19 | ssl_certificate_key /etc/letsencrypt/live/cutouts-stg.siddharthkannan.in/privkey.pem; 20 | 21 | # HSTS (ngx_http_headers_module is required) (63072000 seconds) 22 | add_header Strict-Transport-Security "max-age=63072000" always; 23 | 24 | # OCSP stapling 25 | ssl_stapling on; 26 | ssl_stapling_verify on; 27 | 28 | # verify chain of trust of OCSP response using Root CA and Intermediate certs 29 | ssl_trusted_certificate /etc/letsencrypt/live/cutouts-stg.siddharthkannan.in/fullchain.pem; 30 | 31 | location / { 32 | proxy_pass http://cutouts-staging; 33 | } 34 | } -------------------------------------------------------------------------------- /deploy/nginx-configurations/cutouts.siddharthkannan.in: -------------------------------------------------------------------------------- 1 | upstream cutouts-production { 2 | server localhost:8081; 3 | } 4 | 5 | server { 6 | listen 443 ssl http2; 7 | listen [::]:443 ssl http2; 8 | listen 80; 9 | server_name cutouts.siddharthkannan.in; 10 | 11 | # configuration from https://www.cyberciti.biz/faq/configure-nginx-to-use-only-tls-1-2-and-1-3/ 12 | ssl_protocols TLSv1.2 TLSv1.3; 13 | ssl_prefer_server_ciphers off; 14 | 15 | ssl_session_timeout 1d; 16 | ssl_session_cache shared:SharedNixCraftSSL:10m; 17 | ssl_session_tickets off; 18 | 19 | ssl_certificate /etc/letsencrypt/live/cutouts.siddharthkannan.in/fullchain.pem; 20 | ssl_certificate_key /etc/letsencrypt/live/cutouts.siddharthkannan.in/privkey.pem; 21 | 22 | # HSTS (ngx_http_headers_module is required) (63072000 seconds) 23 | add_header Strict-Transport-Security "max-age=63072000" always; 24 | 25 | # OCSP stapling 26 | ssl_stapling on; 27 | ssl_stapling_verify on; 28 | 29 | # verify chain of trust of OCSP response using Root CA and Intermediate certs 30 | ssl_trusted_certificate /etc/letsencrypt/live/cutouts.siddharthkannan.in/fullchain.pem; 31 | 32 | location / { 33 | proxy_pass http://cutouts-production; 34 | } 35 | } 36 | 37 | server { 38 | listen 8080; 39 | server_name _; 40 | 41 | location = /nginx_status { 42 | stub_status; 43 | } 44 | 45 | location / { 46 | return 404; 47 | } 48 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | server: 5 | build: '.' 6 | ports: 7 | - 8081:8081 8 | environment: 9 | RACK_ENV: "production" 10 | RAILS_ENV: "production" 11 | LANG: "en_US.UTF-8" 12 | env_file: 13 | - ./production.env 14 | networks: 15 | server_network: 16 | aliases: 17 | - server-container 18 | 19 | networks: 20 | server_network: 21 | -------------------------------------------------------------------------------- /encrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/sha256" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Hey, starting now!") 11 | 12 | } 13 | -------------------------------------------------------------------------------- /img/v1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/img/v1_1.png -------------------------------------------------------------------------------- /img/v1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/img/v1_2.png -------------------------------------------------------------------------------- /img/v1_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/img/v1_3.png -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/lib/tasks/.keep -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Siddharth Kannan 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/log/.keep -------------------------------------------------------------------------------- /production.env.template: -------------------------------------------------------------------------------- 1 | DATABASE_URL= 2 | SECRET_KEY_BASE= 3 | SENDGRID_DOMAIN= 4 | SENDGRID_PASSWORD= 5 | SENDGRID_USERNAME= -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # cutouts 2 | 3 | > Sign up, and start adding the articles that you have read and want to remember! Deployed [here][11]! 4 | 5 | [![Build Status][9]][10] 6 | 7 | You can find the Firefox companion add-on [here][8] 8 | 9 | 10 | ### TOC 11 | 12 | - [What?][1] 13 | - [Why?][2] 14 | - [Local Setup][14] 15 | - [Creating a local test user][18] 16 | - [Manual Test Plan][3] 17 | - [API Documentation][7] 18 | - [Release Notes][4] 19 | - [v1.1 - 2019-12-28][19] 20 | - [v1.0 - 2018-12-07][5] 21 | - [TODO][6] 22 | 23 | ## What? 24 | 25 | - Simple form with just two compulsary questions: Link and a Quote from the article that you want to remember 26 | - As simple as it can get. Nothing fancy. Vanilla bootstrap. Vanilla font awesome. 27 | - List public cutouts at profile page 28 | - Share a single cutout with friends and family by emailing it to them 29 | - Share a cutout on social media by copying it's permalink 30 | 31 | ## Why? 32 | 33 | The internet has a lot of good content, blog posts, articles, etc etc. I read a lot 34 | of stuff, and then could not find them again when I wanted to. Pocket is good for saving 35 | for later, Delicious and Pinterest are overkill for something as simple. Hence, this 36 | project! 37 | 38 | ## Local Setup 39 | 40 | **Note:** These steps were tested on a machine running Ubuntu 18.04 LTS. 41 | 42 | - Install `rbenv` 43 | - Install ruby version `2.7.0` 44 | - Install bundler version `2.12.0` 45 | - Run `bundle install` 46 | - [TEST] Run `./bin/rails --version` 47 | - This ensures that you have the rails gem installed locally 48 | 49 | ### Creating a local test user 50 | 51 | - Go to http://localhost:3000 52 | - Create a user using the `Sign Up` button 53 | - Now, you will be shown a prompt on the home page saying you must confirm your 54 | user by clicking the confirmation link in your email. 55 | - To do this process directly on the database, you can do: 56 | - Go into the rails console: `zeus console` 57 | - Get your user: `User.all` or `User.first` 58 | - Confirm your user: `user.confirm` 59 | - After confirming a user, you should be able to login to your account locally. 60 | 61 | A log of the commands to run and their output: 62 | 63 | ```irb 64 | irb(main):007:0> User.first 65 | User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 66 | => # 67 | irb(main):009:0> User.first.confirm 68 | User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 69 | (0.2ms) begin transaction 70 | SQL (0.7ms) UPDATE "users" SET "confirmed_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["confirmed_at", "2019-09-14 04:32:42.534172"], ["updated_at", "2019-09-14 04:32:42.542307"], ["id", 1]] 71 | (6.1ms) commit transaction 72 | => true 73 | ``` 74 | 75 | You can use this process to confirm all locally created users. 76 | 77 | ## Manual Test Plan 78 | 79 | - sign-up 80 | - confirm a new user using: `User.find(...).confirm` 81 | - login and logout 82 | - login, add a public cutout, check that it is accessible without login, logout 83 | - login, add an unlisted cutout, check that it is accessible without login, 84 | logout 85 | - login, add a private cutout, ensure that it isn't accessibly without login, 86 | logout 87 | - edit an cutout's visibility and check if the changes are reflected 88 | 89 | ## API Documentation 90 | 91 | > Check the [routes][13] file for the latest available routes 92 | 93 | All responses are JSON formatted. 94 | 95 | ### `GET /api/v1/feed` 96 | 97 | - Returns the list of top 20 articles from the public feed 98 | - Public 99 | - Auth not required 100 | - Rate limited 101 | 102 | ### `GET /api/v1/feed/:username` 103 | 104 | - Returns the top 20 public articles of the given user 105 | - Public 106 | - Auth not required 107 | - Rate limited 108 | 109 | ## Release Notes 110 | 111 | ### v1.1 - 2019-12-28 112 | 113 | - Switch to Ruby 2.7, Rails 6.0 and the latest version of several gems 114 | - Switch Heroku's build pack from cedar-14 to heroku-18 115 | 116 | ### v1.0 - 2018-02-07 117 | 118 | - Add, update, delete cutouts 119 | - Tag cutouts and then search the cutouts in a particular tag 120 | - List all the cutouts you have stored in the past 121 | - Export all your cutouts to UTF8 encoded HTML or JSON 122 | - Share the permalink for an article that has the title of the article, the 123 | quote, the author and it's tags 124 | 125 | ![img](./img/v1_1.png) 126 | 127 | - Email a Cutout to friends and family (maximum 5 at once) 128 | 129 | ![img](./img/v1_2.png) 130 | 131 | - Add an alias while emailing so that recipients know the cutout is from you 132 | - Display tags as Bootstrap buttons 133 | 134 | ![img](./img/v1_3.png) 135 | 136 | - A link to the archives of the page that has the Cutout 137 | - A companion 138 | [Firefox add-on][8] 139 | to make it easier to Cutout articles from Firefox 140 | 141 | ## TODO 142 | 143 | > In descending order of priority 144 | 145 | - [x] Create a good homepage that has a few words about why this project at all 146 | - [ ] User must be able to login with both username as well as email 147 | - Override devise? (Devise procedure [here][12] seems extremely long) 148 | - [ ] Fix the word limit on quote (50 words?) 149 | - [x] Fix the sign-in and sign-up form UI 150 | - [x] Fix the horrible UI to make it usable at the very least (copy medium, that interface is too good!) 151 | - [x] A system to tag articles (single words) 152 | - [x] A system to search for articles (single search bar, user search, tag search, author search, quote search) 153 | - [x] Ability for users to export their articles into a Markdown file for local storage (A JSON file for import too?) 154 | - [ ] A rating system for self, no sharing as yet 155 | - [ ] Probably friendship and the ability to follow other users 156 | 157 | [![forthebadge](http://forthebadge.com/images/badges/made-with-ruby.svg)](http://forthebadge.com) 158 | 159 | Code inside this repo is licensed under MIT. 160 | 161 | Copyright (c) 2015-2019 [Siddharth Kannan](http://icyflame.github.io) All Rights Reserved. 162 | 163 | [1]: #what 164 | [2]: #why 165 | [3]: #manual-test-plan 166 | [4]: #release-notes 167 | [5]: #v10---2018-02-07 168 | [6]: #todo 169 | [7]: #api-documentation 170 | [8]: https://addons.mozilla.org/en-US/firefox/addon/cutouts-firefox-extension/ 171 | [9]: https://travis-ci.org/icyflame/cutouts.svg?branch=master 172 | [10]: https://travis-ci.org/icyflame/cutouts 173 | [11]: https://cutouts.siddharthkannan.in 174 | [12]: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address 175 | [13]: https://github.com/icyflame/cutouts/blob/add-api-documentation/config/routes.rb 176 | [14]: #local-setup 177 | [15]: https://github.com/rbenv/ruby-build/issues/1215#issuecomment-399687588 178 | [16]: https://stackoverflow.com/a/43926527/2080089 179 | [17]: https://stackoverflow.com/a/46914751/2080089 180 | [18]: #creating-a-local-test-user 181 | [19]: #v11---2019-12-28 182 | -------------------------------------------------------------------------------- /start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d /node/bin ]; 4 | then 5 | echo "Node must be present at node/bin" 6 | exit 42 7 | fi 8 | 9 | export PATH="$PATH:/node/bin" 10 | cd /src 11 | bundle exec puma --preload -t 8:32 --port 8081 12 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/api_helpers_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApiHelpersControllerTest < ActionController::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/controllers/article_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArticleControllerTest < ActionController::TestCase 4 | include Devise::Test::ControllerHelpers 5 | include FactoryBot::Syntax::Methods 6 | 7 | def setup 8 | @request.env["devise.mapping"] = Devise.mappings[:user] 9 | user = FactoryBot.create(:user) 10 | sign_in user 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/controllers/list_articles_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ListArticlesControllerTest < ActionController::TestCase 4 | include Devise::Test::ControllerHelpers 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/search_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SearchControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/static_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StaticControllerTest < ActionController::TestCase 4 | include Devise::Test::ControllerHelpers 5 | 6 | test "should get about" do 7 | get :about 8 | assert_response 200 9 | end 10 | 11 | test "should get index and not redirect when not logged in" do 12 | get :index 13 | assert_response 200 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/controllers/user_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserControllerTest < ActionController::TestCase 4 | include Devise::Test::ControllerHelpers 5 | include FactoryBot::Syntax::Methods 6 | 7 | def setup 8 | @request.env["devise.mapping"] = Devise.mappings[:user] 9 | # user = FactoryBot.create(:user) 10 | # sign_in user 11 | end 12 | 13 | test "redirect from index to sign_in from homepage if not signed in" do 14 | get :index 15 | 16 | assert_response 302 17 | assert_redirected_to new_user_session_path 18 | end 19 | 20 | test "don't redirect anywhere from index if user is signed in" do 21 | sign_in FactoryBot.create(:user) 22 | 23 | get :index 24 | 25 | assert_response 200 26 | end 27 | 28 | test "public page should be available to the public" do 29 | get :public_page, params: { username: "carson" } 30 | assert_response 200 31 | end 32 | 33 | test "public page should be available to signed in users" do 34 | user = FactoryBot.create(:user) 35 | sign_in user 36 | get :public_page, params: { username: "carson" } 37 | assert_response 200 38 | end 39 | 40 | test "export_articles shouldn't work if user isn't logged in" do 41 | get :export_articles 42 | 43 | assert_response 302 44 | assert_redirected_to new_user_session_path 45 | end 46 | 47 | test "export_articles works if user is logged in" do 48 | sign_in FactoryBot.create(:user) 49 | 50 | get :export_articles 51 | 52 | assert_response 200 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/admins.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | -------------------------------------------------------------------------------- /test/fixtures/articles.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | link: "http://test.example.com" 5 | quote: "this could be a quote" 6 | author: "daisy" 7 | user: carson 8 | 9 | two: 10 | link: "http://example.com" 11 | quote: "a quote" 12 | author: "mrs hughes" 13 | user: carson 14 | -------------------------------------------------------------------------------- /test/fixtures/sessions.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | carson: 4 | email: "carson@downton.com" 5 | username: "carson" 6 | 7 | hughes: 8 | email: "hughes@downton.com" 9 | username: "hughes" 10 | 11 | patmore: 12 | email: "patmore@downton.com" 13 | username: "patmore" 14 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/helpers/.keep -------------------------------------------------------------------------------- /test/helpers/api_helpers_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApiHelpersHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/article_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArticleHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/list_articles_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ListArticlesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/static_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StaticHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/user_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/mailers/.keep -------------------------------------------------------------------------------- /test/mailers/article_sharer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArticleSharerTest < ActionMailer::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/mailers/previews/article_sharer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/article_sharer 2 | class ArticleSharerPreview < ActionMailer::Preview 3 | 4 | end 5 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/test/models/.keep -------------------------------------------------------------------------------- /test/models/admin_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AdminTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/article_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | include ArticleHelper 3 | 4 | class ArticleTest < ActiveSupport::TestCase 5 | test "should not save article without link and quote" do 6 | a = Article.new 7 | assert_not a.save 8 | end 9 | 10 | test "should not save article without quote" do 11 | a = Article.new 12 | a.link = "http://example.com" 13 | assert_not a.save 14 | end 15 | 16 | test "should not save article without link" do 17 | a = Article.new 18 | a.quote = "some quote" 19 | assert_not a.save 20 | end 21 | 22 | test "should save article with valid link and quote (bare minimum)" do 23 | a = Article.new 24 | a.link = "http://example.com" 25 | a.quote = "We are so cute, I wanna punch us in the face" 26 | assert a.save 27 | end 28 | 29 | test "should not save article with invalid link" do 30 | a = Article.new 31 | a.link = "notalink" 32 | a.author = "Amy Dunne" 33 | a.quote = "We are so cute, I wanna punch us in the face" 34 | assert_not a.save 35 | end 36 | 37 | test "should not save article with link of length 256 chars" do 38 | a = Article.new 39 | link = "https://" + (1...249).map { |i| "a" }.join 40 | a.link = link 41 | a.author = "Amy Dunne" 42 | a.quote = "We are so cute, I wanna punch us in the face" 43 | assert_not a.save 44 | end 45 | 46 | test "should save article with link of length 255 chars" do 47 | a = Article.new 48 | link = "https://" + (1...248).map { |i| "a" }.join 49 | a.link = link 50 | a.author = "Amy Dunne" 51 | a.quote = "We are so cute, I wanna punch us in the face" 52 | assert a.save 53 | end 54 | 55 | test "should save article with valid, link, quote, author, tags as a public article" do 56 | a = Article.new 57 | a.link = "http://example.com" 58 | a.quote = "testing" 59 | a.author = "jack" 60 | a.tags = "test1, test2" 61 | 62 | assert a.valid? 63 | assert a.save 64 | 65 | assert_equal a.tags_array.count, 2 66 | assert_equal viz_int_val(a.visibility), 0 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/models/session_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | test "user can't be created without email" do 5 | a = User.new 6 | a.username = "test" 7 | assert_not a.save 8 | end 9 | 10 | test "user can't be created without username" do 11 | a = User.new 12 | a.email = "test@example.com" 13 | assert_not a.save 14 | end 15 | 16 | test "user can be created with just username and email" do 17 | a = User.new 18 | a.username = "test" 19 | a.email = "test@example.com" 20 | end 21 | 22 | test "user can't be created with a duplicate email ID" do 23 | a = User.new 24 | a.username = "test" 25 | a.email = "carson@downton.com" 26 | assert_not a.save 27 | end 28 | 29 | test "user can't be created with a duplicate username" do 30 | a = User.new 31 | a.username = "carson" 32 | a.email = "carons1@downton.com" 33 | assert_not a.save 34 | end 35 | 36 | test "user is associated with some articles" do 37 | carson = User.where({ :username => "carson" }).first 38 | assert_equal carson.articles.count, 2 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start "rails" 3 | 4 | ENV['RAILS_ENV'] ||= 'test' 5 | require File.expand_path('../../config/environment', __FILE__) 6 | require 'rails/test_help' 7 | 8 | class ActiveSupport::TestCase 9 | include FactoryBot::Syntax::Methods 10 | 11 | FactoryBot.define do 12 | factory :user do 13 | email { Faker::Internet.email } 14 | username { Faker::Lorem.words(number: 3).join "_" } 15 | password { "password" } 16 | password_confirmation { "password" } 17 | confirmed_at { Date.today } 18 | end 19 | end 20 | 21 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 22 | fixtures :all 23 | 24 | # Add more helper methods to be used by all tests here... 25 | end 26 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icyflame/cutouts/8c641f2636412b9bbe02d6714fb10bd6ca82be0f/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------