├── log └── .keep ├── app ├── mailers │ ├── .keep │ ├── application_mailer.rb │ └── user_mailer.rb ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── post_vote.rb │ ├── post.rb │ ├── comment.rb │ ├── user.rb │ └── post_version.rb ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── comment_list.js.coffee │ │ │ ├── comment_notification_list.js.coffee │ │ │ ├── markdown_editor.js.coffee │ │ │ ├── comment.js.coffee │ │ │ ├── comment_form.js.coffee │ │ │ ├── post_favorite_button.js.coffee │ │ │ ├── comment_box.js.coffee │ │ │ └── post_rating_buttons.js.coffee │ │ ├── routes.js.erb │ │ └── application.js.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── posts.scss │ │ ├── backend │ │ └── main.css.sass │ │ ├── backend.css │ │ └── frontend │ │ └── main.css.sass ├── controllers │ ├── concerns │ │ └── .keep │ ├── admin │ │ ├── main_controller.rb │ │ ├── application_controller.rb │ │ └── post_versions_controller.rb │ ├── sessions_controller.rb │ ├── application_controller.rb │ ├── users_controller.rb │ ├── comments_controller.rb │ ├── password_resets_controller.rb │ ├── post_versions_controller.rb │ └── posts_controller.rb ├── views │ ├── users │ │ ├── edit.html.haml~ │ │ ├── edit.html.haml │ │ ├── new.html.haml │ │ └── _form.html.haml │ ├── admin │ │ ├── main │ │ │ └── index.html.haml │ │ └── post_versions │ │ │ ├── edit_decline.html.haml │ │ │ ├── index.html.haml │ │ │ └── show.html.haml │ ├── layouts │ │ ├── mailer.html.haml │ │ ├── admin.html.haml │ │ └── application.html.haml │ ├── posts │ │ ├── edit.html.haml │ │ ├── new.html.haml │ │ ├── posts_list.html.haml │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── post_versions │ │ ├── edit.html.haml │ │ └── show.html.haml │ ├── sessions │ │ ├── new.html.haml │ │ └── _form.html.haml │ ├── user_mailer │ │ ├── activation_success_email.html.haml │ │ ├── activation_needed_email.html.haml │ │ └── reset_password_email.html.haml │ ├── shared │ │ ├── _flash_messages.html.haml │ │ ├── _post_markdown_editor.html.haml │ │ └── _menu.html.haml │ ├── vue_templates │ │ ├── comments │ │ │ ├── _comment_notification_list_tpl.html.haml │ │ │ ├── _comment_box_tpl.html.haml │ │ │ ├── _comment_list_tpl.html.haml │ │ │ ├── _comment_form_tpl.html.haml │ │ │ └── _comment_tpl.html.haml │ │ └── shared │ │ │ ├── _post_favorite_button_tpl.html.haml │ │ │ └── _post_rating_buttons_tpl.html.haml │ ├── password_resets │ │ ├── new.html.haml │ │ └── edit.html.haml │ └── _vue_templates.html.haml ├── helpers │ ├── posts_helper.rb │ ├── application_helper.rb │ └── admin │ │ └── application_helper.rb ├── serializers │ ├── user_front_data_serializer.rb │ ├── comment_serializer.rb │ └── post_serializer.rb └── services │ └── markdown_parser.rb ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep └── templates │ └── haml │ └── scaffold │ └── _form.html.haml ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── vendor └── assets │ ├── javascripts │ ├── .keep │ └── notify.min.js │ └── stylesheets │ └── .keep ├── README.rdoc ├── bin ├── rake ├── bundle ├── rails └── setup ├── config ├── boot.rb ├── initializers │ ├── cookies_serializer.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── serializer_config.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ ├── simple_form.rb │ └── sorcery.rb ├── environment.rb ├── locales │ ├── en.yml │ └── simple_form.en.yml ├── secrets.yml.example ├── application.rb ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── routes.rb └── database.yml.example ├── config.ru ├── db ├── migrate │ ├── 20151203181509_add_tree_to_comments.rb │ ├── 20160121170807_remove_source_from_posts.rb │ ├── 20151112123948_add_is_admin_to_users.rb │ ├── 20160126092912_add_user_ref_to_posts.rb │ ├── 20160125104823_add_favorite_posts_to_users.rb │ ├── 20160204092842_add_post_version_ref_to_posts.rb │ ├── 20160204125131_add_inherit_version_id_to_post_versions.rb │ ├── 20151111154656_sorcery_remember_me.rb │ ├── 20151114124649_change_users_fields.rb │ ├── 20160204171926_remove_fields_from_posts.rb │ ├── 20151112094518_sorcery_user_activation.rb │ ├── 20160128113543_create_post_votes.rb │ ├── 20151112210217_sorcery_reset_password.rb │ ├── 20160204091437_create_post_versions.rb │ ├── 20160120135250_change_posts_fields.rb │ ├── 20151031194302_create_posts.rb │ ├── 20151103095347_create_comments.rb │ └── 20151111154655_sorcery_core.rb ├── seeds.rb └── schema.rb ├── Rakefile ├── spec ├── services │ └── markdown_parser_spec.rb ├── models │ ├── post_spec.rb │ ├── user_spec.rb │ └── post_version_spec.rb ├── factories │ └── common.rb ├── rails_helper.rb └── spec_helper.rb ├── .gitignore ├── Gemfile └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/users/edit.html.haml~: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/admin/main/index.html.haml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/users/edit.html.haml: -------------------------------------------------------------------------------- 1 | = render 'form' 2 | -------------------------------------------------------------------------------- /app/views/users/new.html.haml: -------------------------------------------------------------------------------- 1 | = render 'form' 2 | -------------------------------------------------------------------------------- /app/helpers/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.haml: -------------------------------------------------------------------------------- 1 | %hmtl 2 | %body 3 | = yield -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | Simple Vue.js and Rails Application 4 | -------------------------------------------------------------------------------- /app/views/posts/edit.html.haml: -------------------------------------------------------------------------------- 1 | = render 'shared/post_markdown_editor' 2 | -------------------------------------------------------------------------------- /app/views/post_versions/edit.html.haml: -------------------------------------------------------------------------------- 1 | = render 'shared/post_markdown_editor' 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes.js.erb: -------------------------------------------------------------------------------- 1 | <%= JsRoutes.generate(namespace: "Zvample") %> 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require ./frontend/main 3 | */ 4 | -------------------------------------------------------------------------------- /app/views/posts/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Write Post 2 | = render 'shared/post_markdown_editor' 3 | -------------------------------------------------------------------------------- /app/models/post_vote.rb: -------------------------------------------------------------------------------- 1 | class PostVote < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :post 4 | end 5 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | = render 'form' 2 | 3 | = link_to 'Forgot Password?', new_password_reset_path 4 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/views/posts/posts_list.html.haml: -------------------------------------------------------------------------------- 1 | %ul 2 | - @posts.each do |post| 3 | %li 4 | = link_to post.title, post_version_path(post) 5 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/views/user_mailer/activation_success_email.html.haml: -------------------------------------------------------------------------------- 1 | %p Congratulations, #{ @user.email }! 2 | %p Now you can #{ link_to 'login', login_url } 3 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /app/controllers/admin/main_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class MainController < Admin::ApplicationController 3 | def index 4 | 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /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/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 4 | -------------------------------------------------------------------------------- /db/migrate/20151203181509_add_tree_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddTreeToComments < ActiveRecord::Migration 2 | def change 3 | add_column :comments, :tree, :ltree 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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: '_zvample_session' 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20160121170807_remove_source_from_posts.rb: -------------------------------------------------------------------------------- 1 | class RemoveSourceFromPosts < ActiveRecord::Migration 2 | def change 3 | remove_column :posts, :source, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/shared/_flash_messages.html.haml: -------------------------------------------------------------------------------- 1 | -# TODO: use vue-strap 2 | - ApplicationHelper::FLASH_MESSAGE_TYPES.each do |message_type| 3 | %p(id = message_type) 4 | = flash[message_type] 5 | -------------------------------------------------------------------------------- /db/migrate/20151112123948_add_is_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddIsAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :is_admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/user_mailer/activation_needed_email.html.haml: -------------------------------------------------------------------------------- 1 | %p Welcome, #{ @user.email }! 2 | %p Now you need to activate you profile, just visit #{ link_to 'link', activate_user_url(@user.activation_token) } 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20160126092912_add_user_ref_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddUserRefToPosts < ActiveRecord::Migration 2 | def change 3 | add_reference :posts, :user, index: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/posts.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Posts controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/views/user_mailer/reset_password_email.html.haml: -------------------------------------------------------------------------------- 1 | %p Hello, #{ @user.email } 2 | 3 | %p You have requested to reset your password. 4 | 5 | %p To choose a new password, just follow this #{ link_to 'link', @url } 6 | -------------------------------------------------------------------------------- /db/migrate/20160125104823_add_favorite_posts_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddFavoritePostsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :favorite_posts, :integer, default: [], array: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160204092842_add_post_version_ref_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddPostVersionRefToPosts < ActiveRecord::Migration 2 | def change 3 | add_reference :posts, :post_version, index: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/posts/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Posts 2 | 3 | .row 4 | .col-md-9 5 | - @posts.each do |post| 6 | %h3 7 | = link_to post.post_version.title, post_path(post) 8 | .col-md-3 9 | = render 'shared/menu' 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/vue_templates/comments/_comment_notification_list_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='comment-notification-list-tpl' ) 2 | %li.comment-notifications.error(v-for='notice in notifications') 3 | %span {{ notice }} 4 | -------------------------------------------------------------------------------- /app/serializers/user_front_data_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserFrontDataSerializer < ActiveModel::Serializer 2 | attributes :id, :email, :full_name, :deleted_at 3 | 4 | def full_name 5 | "#{object.first_name} #{object.last_name}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20160204125131_add_inherit_version_id_to_post_versions.rb: -------------------------------------------------------------------------------- 1 | class AddInheritVersionIdToPostVersions < ActiveRecord::Migration 2 | def change 3 | add_column :post_versions, :inherit_version_id, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/password_resets/new.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for password_resets_path, defaults: { input_html: { class: 'form-control' } } do |f| 2 | .form-group 3 | = f.input :email 4 | .form-group 5 | = f.button :submit, "Reset my password!", class: 'btn btn-default' 6 | -------------------------------------------------------------------------------- /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/javascripts/components/comment_list.js.coffee: -------------------------------------------------------------------------------- 1 | window.CommentList = Vue.extend( 2 | template: '#comment-list-tpl' 3 | 4 | data: -> 5 | maxCommentDepth: 5 6 | 7 | props: ['comments'] 8 | ) 9 | 10 | Vue.component('comment-list', window.CommentList) 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/comment_notification_list.js.coffee: -------------------------------------------------------------------------------- 1 | window.CommentNotificationList = Vue.extend( 2 | template: '#comment-notification-list-tpl' 3 | 4 | props: ['notifications'] 5 | ) 6 | 7 | Vue.component('comment-notification-list', window.CommentNotificationList) 8 | -------------------------------------------------------------------------------- /app/views/vue_templates/comments/_comment_box_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='comment-box-tpl' ) 2 | .well 3 | %comment-form 4 | 5 | %hr 6 | 7 | %h3 8 | Comments 9 | %span.badge {{ commentsQuantity }} 10 | 11 | %comment-list( :comments = 'comments' ) 12 | %br 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/backend/main.css.sass: -------------------------------------------------------------------------------- 1 | @import "bootstrap-sprockets" 2 | @import "bootstrap" 3 | 4 | .post-panel 5 | @extend .panel-default 6 | 7 | &.panel-body 8 | @extend .panel-body 9 | padding: 3em 10 | 11 | .decline-hint 12 | small 13 | background-color: rgba(178, 227, 220, 1) 14 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | FLASH_MESSAGE_TYPES = [:success, :notice, :alert] 3 | 4 | def is_current_user_admin? 5 | return current_user.is_admin? if logged_in? 6 | nil 7 | end 8 | 9 | def markdown(text) 10 | MarkdownParser.new(text).to_html 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/serializer_config.rb: -------------------------------------------------------------------------------- 1 | # Disable for all serializers (except ArraySerializer) 2 | ActiveModel::Serializer.root = false 3 | 4 | ActiveModel::Serializer.setup do |config| 5 | config.key_format = :lower_camel 6 | end 7 | 8 | # Disable for ArraySerializer 9 | ActiveModel::ArraySerializer.root = false 10 | -------------------------------------------------------------------------------- /db/migrate/20151111154656_sorcery_remember_me.rb: -------------------------------------------------------------------------------- 1 | class SorceryRememberMe < ActiveRecord::Migration 2 | def change 3 | add_column :users, :remember_me_token, :string, :default => nil 4 | add_column :users, :remember_me_token_expires_at, :datetime, :default => nil 5 | 6 | add_index :users, :remember_me_token 7 | end 8 | end -------------------------------------------------------------------------------- /app/views/vue_templates/shared/_post_favorite_button_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='post-favorite-button-tpl' ) 2 | .col-md-1 3 | %button.format-button{'@click.prevent':'add_to_favorites'} 4 | %span.glyphicon(:class = "[isEmpty ? 'glyphicon-star-empty' : 'glyphicon-star']") 5 | %span.bages {{ quantity }} 6 | -------------------------------------------------------------------------------- /db/migrate/20151114124649_change_users_fields.rb: -------------------------------------------------------------------------------- 1 | class ChangeUsersFields < ActiveRecord::Migration 2 | def up 3 | remove_column :users, :is_active 4 | add_column :users, :deleted_at, :datetime 5 | end 6 | 7 | def down 8 | add_column :users, :is_active, :boolean 9 | remove_column :users, :deleted_at 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160204171926_remove_fields_from_posts.rb: -------------------------------------------------------------------------------- 1 | class RemoveFieldsFromPosts < ActiveRecord::Migration 2 | def up 3 | remove_columns :posts, :title, :body, :version 4 | end 5 | 6 | def down 7 | add_column :posts, :title, :string 8 | add_column :posts, :body, :text 9 | add_column :posts, :version, :integer 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/templates/haml/scaffold/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for(@<%= singular_table_name %>) do |f| 2 | = f.error_notification 3 | 4 | .form-inputs 5 | <%- attributes.each do |attribute| -%> 6 | = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> 7 | <%- end -%> 8 | 9 | .form-actions 10 | = f.button :submit 11 | -------------------------------------------------------------------------------- /app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ApplicationController < ApplicationController 2 | skip_before_filter :require_login 3 | before_filter :admin_only! 4 | 5 | layout 'admin' 6 | 7 | private 8 | 9 | def admin_only! 10 | return if logged_in? && current_user.is_admin? 11 | redirect_to root_url 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/posts/show.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | %article.col-md-12 3 | %header 4 | %h1 5 | = @post.post_version.title 6 | %section 7 | = markdown @post.post_version.body 8 | 9 | %hr 10 | .row 11 | %post-rating-buttons( post = "#{@post_json_data}" ) 12 | %post-favorite-button( post = "#{@post_json_data}" ) 13 | 14 | %hr 15 | %comment-box( post-id = "#{@post.id}" ) 16 | -------------------------------------------------------------------------------- /db/migrate/20151112094518_sorcery_user_activation.rb: -------------------------------------------------------------------------------- 1 | class SorceryUserActivation < ActiveRecord::Migration 2 | def change 3 | add_column :users, :activation_state, :string, :default => nil 4 | add_column :users, :activation_token, :string, :default => nil 5 | add_column :users, :activation_token_expires_at, :datetime, :default => nil 6 | 7 | add_index :users, :activation_token 8 | end 9 | end -------------------------------------------------------------------------------- /app/views/password_resets/edit.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @user, url: password_reset_path(@token), defaults: { { input_html: 'form-control' } } do |f| 2 | .form-group 3 | = f.input :email, as: :email 4 | .form-group 5 | = f.input :password, as: :password 6 | .form-group 7 | = f.input :password_confirmation, as: :password 8 | .form-group 9 | = f.button :submit, class: 'btn btn-default' 10 | -------------------------------------------------------------------------------- /app/views/vue_templates/comments/_comment_list_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='comment-list-tpl' ) 2 | .comment-item( v-for='comment in comments' track-by='id' ) 3 | %comment( :comment = 'comment' ) 4 | 5 | %div(:class = "{nestedComment: comment.treeDepth < maxCommentDepth}") 6 | %comment-list( :comments = 'comment.children' ) 7 | 8 | -# %pre 9 | {{ $data | json }} 10 | -------------------------------------------------------------------------------- /db/migrate/20160128113543_create_post_votes.rb: -------------------------------------------------------------------------------- 1 | class CreatePostVotes < ActiveRecord::Migration 2 | def change 3 | create_table :post_votes do |t| 4 | t.references :user, index: true, foreign_key: true, null: false 5 | t.references :post, index: true, foreign_key: true, null: false 6 | t.integer :rate, default: 0, null: false 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/sessions/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for :session, url: sessions_path, defaults: { input_html: { class: 'form-control' } } do |f| 2 | .form-group 3 | = f.input :email, as: :email 4 | .form-group 5 | = f.input :password, as: :password 6 | .checkbox 7 | = f.input :remember_me, as: :boolean, input_html: { class: '' } 8 | .form-group 9 | = f.button :submit, class: 'btn btn-default' 10 | -------------------------------------------------------------------------------- /db/migrate/20151112210217_sorcery_reset_password.rb: -------------------------------------------------------------------------------- 1 | class SorceryResetPassword < ActiveRecord::Migration 2 | def change 3 | add_column :users, :reset_password_token, :string, :default => nil 4 | add_column :users, :reset_password_token_expires_at, :datetime, :default => nil 5 | add_column :users, :reset_password_email_sent_at, :datetime, :default => nil 6 | 7 | add_index :users, :reset_password_token 8 | end 9 | end -------------------------------------------------------------------------------- /app/views/post_versions/show.html.haml: -------------------------------------------------------------------------------- 1 | %h2 2 | = @post_version.title 3 | 4 | %p 5 | = markdown @post_version.body 6 | 7 | .row 8 | .col-md-1 9 | = link_to 'Edit', edit_post_version_path(@post_version), class: 'btn btn-default' 10 | 11 | .col-md-2 12 | - if @post_version.draft? 13 | = button_to 'Send to moderation', send_to_moderation_post_version_path(@post_version), method: :put, class: 'btn btn-primary' 14 | 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/markdown_editor.js.coffee: -------------------------------------------------------------------------------- 1 | window.MarkdownEditor = Vue.extend( 2 | template: '#markdown-editor-tpl' 3 | 4 | data: -> 5 | body: '' 6 | html_data: undefined 7 | 8 | methods: 9 | preview: -> 10 | @$http.post(Zvample.preview_posts_path(), { body: @body }).then (response) => 11 | @html_data = response.data 12 | ) 13 | 14 | Vue.component('markdown-editor', window.MarkdownEditor) 15 | -------------------------------------------------------------------------------- /db/migrate/20160204091437_create_post_versions.rb: -------------------------------------------------------------------------------- 1 | class CreatePostVersions < ActiveRecord::Migration 2 | def change 3 | create_table :post_versions do |t| 4 | t.references :user, index: true, foreign_key: true, null: false 5 | t.string :title, null: false 6 | t.text :body, null: false 7 | t.string :aasm_state 8 | t.string :decline_reason 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/comment.js.coffee: -------------------------------------------------------------------------------- 1 | window.Comment = Vue.extend( 2 | template: '#comment-tpl' 3 | 4 | props: 5 | comment: 6 | type: Object 7 | required: true 8 | 9 | data: -> 10 | replied: false 11 | 12 | methods: 13 | showForm: -> 14 | @replied = !@replied 15 | 16 | events: 17 | 'signal:hideForm': -> 18 | @replied = false 19 | ) 20 | 21 | Vue.component('comment', window.Comment) 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :post_version 4 | 5 | has_many :comments 6 | has_many :post_votes 7 | has_many :user_votes, through: :post_votes, source: :user 8 | 9 | def favorites_quantity 10 | User.favorites_quantity(self.id) 11 | end 12 | 13 | def rating_quantity 14 | self.post_votes.map(&:rate).sum 15 | end 16 | 17 | def owned_by?(user) 18 | self.user == user 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/_vue_templates.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'vue_templates/comments/comment_box_tpl' 2 | = render partial: 'vue_templates/comments/comment_list_tpl' 3 | = render partial: 'vue_templates/comments/comment_notification_list_tpl' 4 | = render partial: 'vue_templates/comments/comment_form_tpl' 5 | = render partial: 'vue_templates/comments/comment_tpl' 6 | = render partial: 'vue_templates/shared/post_favorite_button_tpl' 7 | = render partial: 'vue_templates/shared/post_rating_buttons_tpl' 8 | -------------------------------------------------------------------------------- /app/views/admin/post_versions/edit_decline.html.haml: -------------------------------------------------------------------------------- 1 | %h2= @post_version.title 2 | 3 | = simple_form_for @post_version, url: decline_admin_post_version_path(@post_version), method: :put do |f| 4 | .form-group 5 | = f.input :decline_reason, as: :text, input_html: { class: 'form-control' } 6 | .form-group 7 | = link_to :back, class: 'btn btn-default' do 8 | %span.glyphicon.glyphicon-chevron-left 9 | Back 10 | = f.button :submit, "Decline", class: 'btn btn-danger' 11 | -------------------------------------------------------------------------------- /db/migrate/20160120135250_change_posts_fields.rb: -------------------------------------------------------------------------------- 1 | class ChangePostsFields < ActiveRecord::Migration 2 | def up 3 | remove_columns :posts, :title_ru, :body_ru 4 | 5 | rename_column :posts, :title_en, :title 6 | rename_column :posts, :body_en, :body 7 | end 8 | 9 | def down 10 | add_column :posts, :title_ru, :string 11 | add_column :posts, :body_ru, :text 12 | 13 | rename_column :posts, :title, :title_en 14 | rename_column :posts, :body, :body_en 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/shared/_post_markdown_editor.html.haml: -------------------------------------------------------------------------------- 1 | %markdown-editor(inline-template) 2 | = simple_form_for @post_version, defaults: { input_html: { class: 'form-control' } } do |f| 3 | .form-group 4 | = f.input :title 5 | .form-group 6 | = f.input :body, as: :text, input_html: { 'v-model':'body', rows: 5 } 7 | .form-group 8 | = f.button :submit, class: 'btn btn-success' 9 | %a.btn.btn-primary{href:'#', '@click.prevent':'preview'} Preview 10 | 11 | %span{'v-html':'html_data'} 12 | -------------------------------------------------------------------------------- /db/migrate/20151031194302_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration 2 | def change 3 | create_table :posts do |t| 4 | t.string :title_ru, null: false 5 | t.string :title_en, null: false 6 | t.text :body_ru, null: false 7 | t.text :body_en, null: false 8 | t.string :source, null: false 9 | t.boolean :is_published, null: false, default: false 10 | t.integer :version, null: false, default: 0 11 | 12 | t.timestamps 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20151103095347_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.integer :parent_id 5 | t.integer :post_id, null: false, foreign_key: true 6 | t.integer :user_id, null: false, foreign_key: true 7 | t.text :body 8 | t.string :ip 9 | t.boolean :is_hidden 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :comments, :post_id 15 | add_index :comments, :user_id 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/shared/_menu.html.haml: -------------------------------------------------------------------------------- 1 | %nav.panel.panel-default 2 | %header.panel-heading.text-center 3 | %h2 Menu 4 | 5 | %ul.panel-body.list-group.text-center 6 | %li.list-group-item 7 | = link_to 'posts/draft' do 8 | Drafts 9 | %span.badge 10 | 11 | %li.list-group-item 12 | = link_to 'posts/on_moderation' do 13 | On moderation 14 | %span.badge 15 | 16 | %li.list-group-item 17 | = link_to 'posts/declined' do 18 | Declined 19 | %span.badge 20 | -------------------------------------------------------------------------------- /app/services/markdown_parser.rb: -------------------------------------------------------------------------------- 1 | class MarkdownParser 2 | attr_reader :markdown_data 3 | 4 | def initialize(markdown_data) 5 | @markdown_data = markdown_data 6 | end 7 | 8 | def renderer 9 | Redcarpet::Render::HTML.new(filter_html: true, hard_wrap: true) 10 | end 11 | 12 | def to_html 13 | white_list_sanitizer = Rails::Html::WhiteListSanitizer.new 14 | markdown = Redcarpet::Markdown.new(renderer).render(markdown_data) 15 | 16 | white_list_sanitizer.sanitize(markdown).html_safe 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/serializers/comment_serializer.rb: -------------------------------------------------------------------------------- 1 | class CommentSerializer < ActiveModel::Serializer 2 | attributes :id, :body, :children, :parent_id, :tree_depth, :username, :commented_at 3 | 4 | def children 5 | result = [] 6 | 7 | object.children.each do |children| 8 | result << CommentSerializer.new(children) 9 | end 10 | 11 | result 12 | end 13 | 14 | def username 15 | object.user.email.split(/@/).first 16 | end 17 | 18 | def commented_at 19 | object.created_at.strftime("%d %B %Y %H:%M") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | Rails.application.config.assets.precompile += %w( backend.css ) 12 | -------------------------------------------------------------------------------- /db/migrate/20151111154655_sorcery_core.rb: -------------------------------------------------------------------------------- 1 | class SorceryCore < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :first_name 5 | t.string :last_name 6 | t.date :birth_date 7 | t.string :email, null: false 8 | t.string :crypted_password 9 | t.string :salt 10 | t.integer :login_count, default: 0, null: false 11 | t.boolean :is_active, default: false, null: false 12 | 13 | t.timestamps 14 | end 15 | 16 | add_index :users, :email, unique: true 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/helpers/admin/application_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::ApplicationHelper 2 | 3 | LABELS_BY_STATES_MAP = { 4 | draft: 'label-default', 5 | declined: 'label-danger', 6 | approved: 'label-success', 7 | on_moderation: 'label-warning' 8 | } 9 | 10 | def label_state_class(obj) 11 | LABELS_BY_STATES_MAP[obj.aasm_state.to_sym] 12 | end 13 | 14 | def current_post_link(post_version) 15 | if post_version.post.present? 16 | link_to "##{post_version.post.id}", post_path(post_version.post) 17 | else 18 | '-' 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/comment_form.js.coffee: -------------------------------------------------------------------------------- 1 | window.CommentForm = Vue.extend( 2 | template: '#comment-form-tpl' 3 | 4 | props: 5 | parentId: 6 | type: Number 7 | default: undefined 8 | 9 | data: -> 10 | body: "" # TODO: do NOT assign text field to undefined!! 11 | notifications: [] 12 | loggedIn: gon.is_logged_in 13 | isUserBanned: gon.is_user_banned 14 | 15 | methods: 16 | addComment: (e) -> 17 | if this.loggedIn and !this.isUserBanned 18 | @$dispatch('signal:addComment', @) 19 | ) 20 | 21 | Vue.component('comment-form', window.CommentForm) 22 | -------------------------------------------------------------------------------- /app/views/users/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for @user, defaults: { input_html: { class: 'form-control' } } do |f| 2 | - if @user.errors.any? 3 | #error_explanation 4 | %h2 5 | #{ pluralize(@user.errors.count, "error") } prohibited 6 | %ul 7 | - @user.errors.full_messages.each do |msg| 8 | %li 9 | = msg 10 | 11 | .form-group 12 | = f.input :email, as: :email 13 | .form-group 14 | = f.input :password, as: :password 15 | .form-group 16 | = f.input :password_confirmation, as: :password 17 | .form-group 18 | = f.button :submit, class: 'btn btn-default' 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/backend.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree ./backend 13 | */ 14 | 15 | -------------------------------------------------------------------------------- /spec/services/markdown_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe MarkdownParser do 4 | it 'convers markdown tags to html' do 5 | md_title = '# title' 6 | expect(MarkdownParser.new(md_title).to_html.strip).to eq('
hello
') 12 | end 13 | 14 | it 'filters ' 16 | expect(MarkdownParser.new(script_tag).to_html.strip).to eq('alert(1)
') 17 | end 18 | end 19 | 20 | -------------------------------------------------------------------------------- /.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 all logfiles and tempfiles. 11 | /log/* 12 | !/log/.keep 13 | /tmp 14 | 15 | /.idea 16 | /public/system 17 | /public/assets 18 | 19 | database.yml 20 | secrets.yml 21 | 22 | # Ignore dotfiles 23 | .ruby-version 24 | .ruby-gemset 25 | .projectile 26 | .vagrant 27 | .rspec -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | skip_before_filter :require_login, except: :destroy 3 | 4 | def new; end 5 | 6 | def create 7 | if login(session_params[:email], session_params[:password], session_params[:remember_me]) 8 | redirect_back_or_to root_url, success: 'Welcome!' 9 | else 10 | render :new 11 | end 12 | end 13 | 14 | def destroy 15 | logout 16 | redirect_back_or_to root_url, success: 'Success' 17 | end 18 | 19 | private 20 | 21 | def session_params 22 | params.require(:session).permit(:email, :password, :remember_me) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/vue_templates/comments/_comment_form_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='comment-form-tpl' ) 2 | %ul 3 | %comment-notification-list(:notifications = 'notifications') 4 | 5 | %div 6 | %pre(v-show = 'body') 7 | {{ body }} 8 | 9 | %div(v-if = '!loggedIn') 10 | Please login bla bla bla template 11 | 12 | = simple_form_for :comment, html: { 'v-on:submit.prevent': 'addComment($event)' } do |f| 13 | .form-group 14 | = f.input :body, as: :text, label: false, input_html: { 'v-model': 'body', ':disabled': "!loggedIn", class: 'form-control', rows: 5 } 15 | .form-group.text-right 16 | = f.button :submit, class: 'btn btn-success' 17 | -------------------------------------------------------------------------------- /app/views/vue_templates/comments/_comment_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='comment-tpl' ) 2 | .comment-header 3 | .row.top-line 4 | .col-md-2 5 | %span.label.label-green 6 | {{ comment.username }} 7 | .col-md-3 8 | %time.small 9 | {{ comment.commentedAt }} 10 | .col-md-1.small 11 | = link_to '#{{ comment.id }}', '#' 12 | .col-md-1.col-md-offset-5 13 | %span 14 | 15 | .comment-body 16 | .row 17 | %p 18 | {{ comment.body }} 19 | 20 | .comment-actions 21 | .row.reply 22 | %a{href:'#', '@click.prevent':"showForm"} Reply 23 | 24 | %comment-form(v-if="replied" :parent-id="comment.id") 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/models/post_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Post do 4 | USERS_QUANTITY = 3 5 | 6 | let(:post) { create(:post) } 7 | let(:rand_array) { [-1, 0, 1, 1, 0, 1, -1] } 8 | 9 | it "selects post's favorites quantity" do 10 | USERS_QUANTITY.times do 11 | user = create(:user) 12 | user.add_or_drop_favorites!(post) 13 | end 14 | 15 | expect(post.favorites_quantity).to eq(3) 16 | end 17 | 18 | it 'counts post rating quantity' do 19 | rand_array.size.times do |num| 20 | user = create(:user) 21 | user.rate_post(post, rand_array[num]) 22 | end 23 | 24 | array_quantity = rand_array.sum 25 | 26 | expect(post.rating_quantity).to eq(array_quantity) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_filter :init_js_data 3 | before_filter :require_login 4 | 5 | # Prevent CSRF attacks by raising an exception. 6 | # For APIs, you may want to use :null_session instead. 7 | protect_from_forgery with: :exception 8 | 9 | private 10 | 11 | def init_js_data 12 | gon.is_logged_in = logged_in? 13 | 14 | if logged_in? 15 | gon.current_user = UserFrontDataSerializer.new(current_user) 16 | gon.is_user_banned = !!current_user.deleted_at? 17 | end 18 | end 19 | 20 | def not_authenticated 21 | render json: { alert: t('ui.small_notifications.basic.authentication_error') } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/serializers/post_serializer.rb: -------------------------------------------------------------------------------- 1 | class PostSerializer < ActiveModel::Serializer 2 | include ActionView::Helpers::AssetTagHelper 3 | 4 | attributes :id, :favorites_quantity, :is_empty, :votes_quantity, :is_voted, :default_rate, :user_post 5 | 6 | def favorites_quantity 7 | object.favorites_quantity 8 | end 9 | 10 | def is_empty 11 | !scope.included_in_favorites?(object) if scope 12 | end 13 | 14 | def votes_quantity 15 | object.rating_quantity 16 | end 17 | 18 | def is_voted 19 | object.user_votes.include?(scope) if scope 20 | end 21 | 22 | def default_rate 23 | object.post_votes.find_by(user: scope).try(:rate) if scope 24 | end 25 | 26 | def user_post 27 | object.owned_by?(scope) if scope 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | after_create :update_comment_hierarchy 3 | 4 | belongs_to :user 5 | belongs_to :post 6 | 7 | validates :body, :post_id, presence: true 8 | validates :body, length: { in: 5..140 } 9 | 10 | scope :parent_comments, -> { where(parent_id: nil) } 11 | 12 | def children 13 | Comment.where(parent_id: id) 14 | end 15 | 16 | def tree_depth 17 | result = ActiveRecord::Base.connection.execute("SELECT NLEVEL('#{tree}')") 18 | result.getvalue(0, 0) 19 | end 20 | 21 | def update_comment_hierarchy 22 | parent_tree = Comment.find_by(id: parent_id).try(:tree) 23 | 24 | ltree = parent_tree ? "#{parent_tree}.#{id}" : id.to_s 25 | 26 | update_column(:tree, ltree) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/post_favorite_button.js.coffee: -------------------------------------------------------------------------------- 1 | window.PostFavoriteButton = Vue.extend( 2 | template: '#post-favorite-button-tpl' 3 | 4 | props: 5 | post: 6 | type: Object 7 | coerce: (val) -> 8 | JSON.parse(val) 9 | 10 | data: -> 11 | quantity: 12 | @post.favoritesQuantity 13 | isEmpty: 14 | @post.isEmpty 15 | 16 | methods: 17 | add_to_favorites: -> 18 | @$http.put(Zvample.add_to_favorites_posts_path(), {id: @post.id}).then (response) -> 19 | if gon.is_logged_in 20 | @$nextTick -> 21 | @isEmpty = response.data.status 22 | @quantity = response.data.quantity 23 | 24 | root_vue.notify(response.data) 25 | ) 26 | 27 | Vue.component('post-favorite-button', window.PostFavoriteButton) 28 | -------------------------------------------------------------------------------- /app/views/layouts/admin.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %title Admin Panel 6 | = include_gon 7 | = stylesheet_link_tag 'backend', media: 'all' 8 | = javascript_include_tag 'backend' 9 | = csrf_meta_tags 10 | %body 11 | %header 12 | %nav.navbar.navbar-default 13 | .navbar-header 14 | = link_to 'Admin Panel', admin_root_url, class: 'navbar-brand' 15 | .collapse.navbar-collapse 16 | .nav.navbar-nav 17 | %li 18 | = link_to 'Post Versions', admin_post_versions_url 19 | 20 | .nav.navbar-nav.navbar-right 21 | = link_to 'Front page', root_url, class: 'navbar-brand' 22 | 23 | .container 24 | = yield 25 | -------------------------------------------------------------------------------- /spec/factories/common.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | sequence(:email) { |n| "test_email_#{n}@zvlex.me" } 3 | 4 | factory :user do 5 | first_name 'Alexander' 6 | last_name 'Zutikov' 7 | email { generate(:email) } 8 | password 'secret' 9 | password_confirmation 'secret' 10 | end 11 | 12 | factory :post_version do 13 | user 14 | title 'Lorem Ipsum' 15 | body 'The standard Lorem Ipsum' 16 | 17 | factory :on_moderation_post_version do 18 | aasm_state 'on_moderation' 19 | end 20 | 21 | factory :approved_post_version do 22 | aasm_state 'approved' 23 | end 24 | 25 | factory :declined_post_version do 26 | aasm_state 'declined' 27 | end 28 | end 29 | 30 | factory :post do 31 | user 32 | post_version 33 | end 34 | 35 | factory :post_vote do 36 | user 37 | post 38 | rate 1 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/views/admin/post_versions/index.html.haml: -------------------------------------------------------------------------------- 1 | %table.table 2 | %thead 3 | %tr 4 | %th 5 | = PostVersion.human_attribute_name(:id) 6 | %th 7 | = PostVersion.human_attribute_name(:title) 8 | %th 9 | = PostVersion.human_attribute_name(:aasm_state) 10 | %th 11 | = PostVersion.human_attribute_name(:post) 12 | 13 | %tbody 14 | - @post_versions.each do |post_version| 15 | %tr 16 | %td 17 | = link_to post_version.id, admin_post_version_path(post_version) 18 | %td #{post_version.title} 19 | %td 20 | %span{class: "label #{label_state_class(post_version)}"} 21 | = post_version.aasm_state 22 | 23 | - if post_version.decline_reason? 24 | .decline-hint 25 | %small #{post_version.decline_reason} 26 | %td 27 | = current_post_link(post_version) 28 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /app/views/vue_templates/shared/_post_rating_buttons_tpl.html.haml: -------------------------------------------------------------------------------- 1 | %script( type='text/x-template' id='post-rating-buttons-tpl') 2 | .col-md-1.text-center( v-show = 'isVoted' ) 3 | %span 4 | %button.format-button{'@click.prevent':'vote(1)'} 5 | %span.rating-icons{':class':'[ ratesMap[defaultRate] ]'} 6 | %span.favorites-quantity {{ quantity }} 7 | .col-md-2.text-center( v-else ) 8 | %span( v-show = 'userPost' ) 9 | %button.format-button{'@click.prevent':'vote(0)'} 10 | %span.rating-icons{':class':'[ ratesMap[0] ]'} 11 | %span.favorites-quantity {{ quantity }} 12 | %span( v-else ) 13 | %button.format-button{'@click.prevent':'vote(-1)'} 14 | %span.glyphicon.glyphicon-minus 15 | %button.format-button{'@click.prevent':'vote(0)'} 16 | %span.glyphicon.glyphicon-eye-open 17 | %button.format-button{'@click.prevent':'vote(1)'} 18 | %span.glyphicon.glyphicon-plus 19 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | skip_before_filter :require_login, except: [:edit, :update] 3 | 4 | def new 5 | @user = User.new 6 | end 7 | 8 | def edit 9 | @user = User.find(params[:id]) 10 | end 11 | 12 | def create 13 | @user = User.new(user_params) 14 | 15 | if @user.save 16 | login(params[:user][:email], params[:user][:password]) 17 | 18 | redirect_to root_url, success: 'success' 19 | else 20 | render :new 21 | end 22 | end 23 | 24 | def activate 25 | @user = User.load_from_activation_token(params[:id]) 26 | 27 | if @user && @user.activate! 28 | redirect_to login_path, success: 'Was activated' 29 | else 30 | redirect_to root_path, alert: 'Cannot activate user' 31 | end 32 | end 33 | 34 | private 35 | 36 | def user_params 37 | params.require(:user).permit(:email, :password, :password_confirmation) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ApplicationMailer 2 | 3 | # Subject can be set in your I18n file at config/locales/en.yml 4 | # with the following lookup: 5 | # 6 | # en.user_mailer.activation_needed_email.subject 7 | # 8 | def activation_needed_email(user) 9 | @user = user 10 | # TODO: add I18n 11 | mail(to: @user.email, subject: 'Account Activation') 12 | end 13 | 14 | # Subject can be set in your I18n file at config/locales/en.yml 15 | # with the following lookup: 16 | # 17 | # en.user_mailer.activation_success_email.subject 18 | # 19 | def activation_success_email(user) 20 | @user = user 21 | 22 | mail(to: @user.email, subject: 'You account successful activated') 23 | end 24 | 25 | def reset_password_email(user) 26 | @user = User.find user.id 27 | @url = edit_password_reset_url(@user.reset_password_token) 28 | 29 | mail(to: @user.email, subject: "Your password has been reset") 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /config/secrets.yml.example: -------------------------------------------------------------------------------- 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: 367044cde5c27da34905be8cc3c410949928858f0f6f14abad23e85fbfce4013ae20e5f1566b6ed49266412f941b5fc4ab6b950a74f5ae76127bac034e5a5c90 15 | 16 | test: 17 | secret_key_base: 409632f45bc4f5180754f8a09ff803d94fd834cfecacbc7cef8c6209ddfd9ab7e1120cb5bb6ecc8638722f036a1b35d65a74972de534c5a49b5da49a821ddfbe 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 | -------------------------------------------------------------------------------- /app/controllers/admin/post_versions_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class PostVersionsController < Admin::ApplicationController 3 | before_filter :find_post_version!, only: [:show, :approve, :decline, :edit_decline] 4 | 5 | def index 6 | @post_versions = PostVersion.where.not(aasm_state: :draft).order(created_at: :desc) 7 | end 8 | 9 | def show; end 10 | 11 | def approve 12 | if @post_version.approve! 13 | redirect_to admin_post_versions_path 14 | end 15 | end 16 | 17 | def decline 18 | @post_version.assign_attributes(post_version_params) 19 | 20 | if @post_version.decline! 21 | redirect_to admin_post_versions_path 22 | else 23 | render :edit_decline 24 | end 25 | end 26 | 27 | def edit_decline; end 28 | 29 | private 30 | 31 | def find_post_version! 32 | @post_version = PostVersion.find(params[:id]) 33 | end 34 | 35 | def post_version_params 36 | params.require(:post_version).permit(:decline_reason) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/comment_box.js.coffee: -------------------------------------------------------------------------------- 1 | window.CommentBox = Vue.extend( 2 | template: '#comment-box-tpl' 3 | 4 | props: 5 | postId: 6 | type: String 7 | required: true 8 | 9 | data: -> 10 | comments: [] 11 | 12 | computed: 13 | postCommentsUrl: -> 14 | Zvample.post_comments_path(@postId) 15 | 16 | commentsQuantity: -> 17 | @comments.length 18 | 19 | ready: -> 20 | @$http.get(@postCommentsUrl).then (response) -> 21 | @comments = response.data 22 | 23 | events: 24 | 'signal:addComment': (child) -> 25 | formData = { comment: { body: child.body, parent_id: child.parentId } } 26 | 27 | @$http.post(@postCommentsUrl, formData).then (response) -> 28 | @$nextTick -> 29 | switch response.data.status 30 | when 422 31 | child.$set('notifications', response.data.errors) 32 | else 33 | @comments = response.data 34 | child.$set('body', undefined) 35 | @$broadcast('signal:hideForm') 36 | ) 37 | 38 | Vue.component('comment-box', window.CommentBox) 39 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/post_rating_buttons.js.coffee: -------------------------------------------------------------------------------- 1 | window.PostRatingButtons = Vue.extend( 2 | template: '#post-rating-buttons-tpl' 3 | 4 | props: 5 | post: 6 | type: Object 7 | coerce: (val) -> 8 | JSON.parse(val) 9 | 10 | data: -> 11 | quantity: 12 | @post.votesQuantity 13 | 14 | isVoted: 15 | @post.isVoted 16 | 17 | defaultRate: 18 | @post.defaultRate 19 | 20 | userPost: 21 | @post.userPost 22 | 23 | ratesMap: 24 | { 25 | '-1': 'minus', 26 | '0': 'eye-open', 27 | '1': 'plus' 28 | } 29 | 30 | methods: 31 | vote: (rate) -> 32 | return if @post.userPost 33 | 34 | params = { id: @post.id, rate: rate } 35 | 36 | @$http.put(Zvample.vote_posts_path(), params).then (response) -> 37 | if gon.is_logged_in 38 | @$nextTick -> 39 | @quantity = response.data.quantity 40 | @isVoted = true 41 | @defaultRate = rate 42 | 43 | root_vue.notify(response.data) 44 | ) 45 | 46 | Vue.component('post-rating-buttons', window.PostRatingButtons) 47 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | skip_before_filter :require_login, only: [:index] 3 | 4 | def index 5 | @post = Post.find(params[:post_id]) 6 | 7 | respond_to do |format| 8 | format.html { redirect_to @post } 9 | format.json do 10 | render json: @post.comments.parent_comments, each_serialize: CommentSerializer 11 | end 12 | end 13 | end 14 | 15 | def create 16 | @post = Post.find(params[:post_id]) 17 | 18 | @comments = @post.comments 19 | 20 | if current_user.deleted_at? 21 | render json: { warning: t('ui.small_notifications.basic.user_banned') } 22 | else 23 | @comment = current_user.comments.build(comment_params) 24 | @comment.post = @post 25 | 26 | if @comment.save 27 | render json: @comments.parent_comments, each_serialize: CommentSerializer 28 | else 29 | render json: { errors: @comment.errors.full_messages, status: 422 } 30 | end 31 | end 32 | end 33 | 34 | private 35 | 36 | def comment_params 37 | params.require(:comment).permit(:body, :parent_id) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/controllers/password_resets_controller.rb: -------------------------------------------------------------------------------- 1 | class PasswordResetsController < ApplicationController 2 | skip_before_filter :require_login 3 | 4 | def create 5 | @user = User.where(email: params[:email]).first 6 | 7 | @user.deliver_reset_password_instructions! if @user 8 | 9 | redirect_to root_path, notice: 'Instructions have been sent to your email.' 10 | end 11 | 12 | def edit 13 | @token = params[:id] 14 | @user = User.load_from_reset_password_token(params[:id]) 15 | 16 | if @user.blank? 17 | not_authenticated 18 | return 19 | end 20 | end 21 | 22 | def update 23 | @token = params[:id] 24 | @user = User.load_from_reset_password_token(params[:id]) 25 | 26 | if @user.blank? 27 | not_authenticated 28 | return 29 | end 30 | 31 | # the next line makes the password confirmation validation work 32 | @user.password_confirmation = params[:user][:password_confirmation] 33 | # the next line clears the temporary token and updates the password 34 | if @user.change_password!(params[:user][:password]) 35 | redirect_to(root_path, :notice => 'Password was successfully updated.') 36 | else 37 | render :action => "edit" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/assets/stylesheets/frontend/main.css.sass: -------------------------------------------------------------------------------- 1 | @import "bootstrap-sprockets" 2 | @import "bootstrap" 3 | 4 | @media (min-width: 1200px) 5 | .container 6 | width: 970px 7 | 8 | .postForm 9 | padding: 10px 10 | 11 | .login-buttons 12 | margin-right: 0.3em 13 | 14 | .nestedComment 15 | padding-left: 50px 16 | 17 | .comment-header 18 | margin: 1.5em 0 0.5em 2em 19 | 20 | .top-line 21 | padding: 5px 0 5px 0 22 | 23 | .comment-body 24 | margin-left: 3em 25 | 26 | .label-green 27 | @extend .label 28 | background: rgba(73, 138, 7, 0.75) 29 | 30 | .comment-actions 31 | margin-left: 3em 32 | 33 | .reply 34 | a 35 | font-size: 0.9em 36 | 37 | .favorite-button 38 | font-size: 1.3em 39 | 40 | .format-button 41 | border: 1px solid #ccc 42 | background-color: transparent 43 | 44 | .favorites-quantity 45 | font-weight: bold 46 | 47 | .rating-icons 48 | @extend .glyphicon 49 | 50 | &.plus 51 | @extend .glyphicon-plus 52 | color: #5cb85c 53 | 54 | &.minus 55 | @extend .glyphicon-minus 56 | color: #d9534f 57 | 58 | &.eye-open 59 | @extend .glyphicon-eye-open 60 | color: #777 61 | 62 | .comment-notifications 63 | @extend .alert 64 | 65 | &.error 66 | @extend .alert-danger 67 | padding: 1em 68 | -------------------------------------------------------------------------------- /app/controllers/post_versions_controller.rb: -------------------------------------------------------------------------------- 1 | class PostVersionsController < ApplicationController 2 | before_filter :require_login 3 | before_filter :find_post_version!, except: :create 4 | 5 | def show; end 6 | 7 | def edit; end 8 | 9 | def create 10 | @post_version = current_user.post_versions.build(post_version_params) 11 | 12 | if @post_version.save 13 | redirect_to post_version_path(@post_version) 14 | else 15 | render 'posts/new' 16 | end 17 | end 18 | 19 | def update 20 | # TODO: show notification 21 | if @post_version.on_moderation? 22 | redirect_to post_version_path(@post_version), notice: 'Can not modify post already was sent on moderation' 23 | return 24 | end 25 | 26 | if @post_version.update_or_create_post_version!(post_version_params) 27 | redirect_to post_version_path(@post_version) 28 | end 29 | end 30 | 31 | def send_to_moderation 32 | if @post_version.send_to_moderation! 33 | redirect_to '/posts/on_moderation' 34 | end 35 | end 36 | 37 | private 38 | 39 | def post_version_params 40 | params.require(:post_version).permit(:title, :body, :context) 41 | end 42 | 43 | def find_post_version! 44 | @post_version = current_user.post_versions.find(params[:id]) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | authenticates_with_sorcery! 3 | 4 | has_many :posts 5 | has_many :comments 6 | has_many :post_votes 7 | has_many :post_versions 8 | 9 | validates :email, :password, :password_confirmation, presence: true, if: :new_record? 10 | 11 | validates :email, uniqueness: true 12 | 13 | validates :password, length: { minimum: 6 }, if: :new_record? 14 | validates :password, confirmation: true, if: :new_record? 15 | 16 | scope :admins, -> { where(is_admin: true) } 17 | scope :active, -> { where(deleted_at: nil) } 18 | scope :favorites_quantity, -> (post_id) { active.where("favorite_posts @> '{?}'", post_id).count.to_i } 19 | 20 | def add_or_drop_favorites!(post) 21 | if included_in_favorites?(post) 22 | drop_favorites(post) 23 | else 24 | add_favorites(post) 25 | end 26 | 27 | self.save! 28 | end 29 | 30 | def included_in_favorites?(post) 31 | self.favorite_posts.include?(post.id) 32 | end 33 | 34 | def drop_favorites(post) 35 | self.favorite_posts.delete(post.id) 36 | end 37 | 38 | def add_favorites(post) 39 | self.favorite_posts << post.id 40 | end 41 | 42 | def rate_post(post, rate) 43 | return if post.owned_by?(self) 44 | 45 | unless self.post_votes.where(post: post).exists? 46 | self.post_votes.create(post: post, rate: rate) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %title Zvample 6 | = include_gon 7 | = stylesheet_link_tag 'application', media: 'all' 8 | = javascript_include_tag 'application' 9 | = csrf_meta_tags 10 | %body 11 | %header 12 | %nav.navbar.navbar-default 13 | .navbar-header 14 | = link_to 'Zvample', root_url, class: 'navbar-brand' 15 | 16 | .collapse.navbar-collapse 17 | .nav.navbar-nav 18 | - if is_current_user_admin? 19 | %li 20 | = link_to 'Admin', admin_root_url 21 | %li 22 | = link_to 'Posts', posts_url 23 | 24 | .nav.navbar-nav.navbar-right.login-buttons 25 | - if current_user 26 | .navbar-text 27 | = current_user.email 28 | 29 | = link_to 'Write Post', new_post_path, class: 'btn btn-success navbar-btn' 30 | = link_to 'Edit Profile', edit_user_path(current_user), class: 'btn btn-default navbar-btn' 31 | = link_to 'Logout', :logout, method: :post, class: 'btn btn-danger navbar-btn' 32 | - else 33 | = link_to 'Login', :login, class: 'btn btn-success navbar-btn' 34 | = link_to 'Register', new_user_path, class: 'btn btn-default navbar-btn' 35 | 36 | .container 37 | = yield 38 | 39 | = render partial: '/vue_templates' 40 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js.coffee: -------------------------------------------------------------------------------- 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 any plugin's vendor/assets/javascripts directory 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/rails/sprockets#sprockets-directives) for details 11 | # about supported directives. 12 | # 13 | #= require jquery 14 | #= require jquery_ujs 15 | #= require vue 16 | #= require vue-resource 17 | #= require underscore 18 | #= require js-routes 19 | #= require autosize 20 | #= require notify.min 21 | #= require_tree . 22 | 23 | $(document).ready -> 24 | Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') 25 | 26 | window.root_vue = new Vue( 27 | el: 'body' 28 | 29 | data: -> 30 | notifyStylesMap: { 31 | success: 'success' 32 | notice: 'info' 33 | warning: 'warn' 34 | alert: 'error' 35 | } 36 | 37 | methods: 38 | notify: (response_data) -> 39 | noty_style = _.first(_.keys(response_data)) 40 | 41 | $.notify response_data[noty_style], @notifyStylesMap[noty_style] 42 | ) 43 | 44 | # Auto resize textarea 45 | autosize document.querySelectorAll('textarea') 46 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "action_controller/railtie" 9 | require "action_mailer/railtie" 10 | require "action_view/railtie" 11 | require "sprockets/railtie" 12 | # require "rails/test_unit/railtie" 13 | 14 | # Require the gems listed in Gemfile, including any gems 15 | # you've limited to :test, :development, or :production. 16 | Bundler.require(*Rails.groups) 17 | 18 | module Zvample 19 | class Application < Rails::Application 20 | # Settings in config/environments/* take precedence over those specified here. 21 | # Application configuration should go into files in config/initializers 22 | # -- all .rb files in that directory are automatically loaded. 23 | 24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 26 | # config.time_zone = 'Central Time (US & Canada)' 27 | 28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 30 | # config.i18n.default_locale = :de 31 | 32 | # Do not swallow errors in after_commit/after_rollback callbacks. 33 | config.active_record.raise_in_transactional_callbacks = true 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/admin/post_versions/show.html.haml: -------------------------------------------------------------------------------- 1 | .post-version 2 | .panel.panel-default 3 | .panel-heading 4 | .row 5 | .col-md-1 6 | = link_to admin_post_versions_path, class: 'btn btn-default btn-xs' do 7 | %span.glyphicon.glyphicon-chevron-left 8 | 9 | - unless @post_version.declined? 10 | .col-md-4.col-md-offset-5 11 | .col-md-1 12 | = button_to 'Approve', approve_admin_post_version_path(@post_version), method: :put, class: 'btn btn-success btn-xs' 13 | .col-md-1 14 | = link_to 'Decline', edit_decline_admin_post_version_path(@post_version), class: 'btn btn-danger btn-xs' 15 | 16 | .row 17 | .col-md-12 18 | %h3= @post_version.title 19 | 20 | %hr 21 | .row 22 | .col-md-3 23 | %small 24 | %strong 25 | = PostVersion.human_attribute_name(:user) 26 | %mark #{@post_version.user.email} 27 | 28 | .row 29 | .col-md-3 30 | %small 31 | %strong 32 | = PostVersion.human_attribute_name(:created_at) 33 | %mark #{@post_version.created_at} 34 | 35 | .row 36 | .col-md-3 37 | %span{ class: "label #{label_state_class(@post_version)}"} 38 | = @post_version.aasm_state 39 | 40 | - if @post_version.decline_reason? 41 | .decline-hint 42 | %small #{@post_version.decline_reason} 43 | 44 | .post-panel.panel-body 45 | .row 46 | %p= markdown @post_version.body 47 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe User do 4 | let(:user) { create(:user) } 5 | let(:post) { create(:post) } 6 | let(:rate) { rand(-1..1) } 7 | 8 | context 'when pressed favorite button' do 9 | it 'drops favorite posts' do 10 | user.favorite_posts = [post.id] 11 | user.drop_favorites(post) 12 | 13 | expect(user.favorite_posts).to eq([]) 14 | end 15 | 16 | it 'adds favorite posts' do 17 | user.favorite_posts = [] 18 | user.add_favorites(post) 19 | 20 | expect(user.favorite_posts).not_to be_empty 21 | end 22 | 23 | it 'checks if post are included' do 24 | user.favorite_posts = [post.id] 25 | included = user.included_in_favorites?(post) 26 | 27 | expect(included).to be(true) 28 | end 29 | end 30 | 31 | context 'when user votes post' do 32 | specify 'skip or vote up/down' do 33 | user.rate_post(post, rate) 34 | 35 | expect(user.post_votes.map(&:rate)).to eq([rate]) 36 | end 37 | 38 | it "does not vote if user already voted" do 39 | create(:post_vote, user: user, post: post, rate: rate) 40 | user.rate_post(post, rate) 41 | 42 | expect(user.post_votes.size).to be(1) 43 | end 44 | 45 | specify 'several users voted' do 46 | 3.times do 47 | create(:post_vote, post: post, rate: rate) 48 | end 49 | 50 | expect(post.user_votes.size).to eq(3) 51 | end 52 | 53 | specify 'user can not vote his post' do 54 | post = create(:post, user: user) 55 | user.rate_post(post, rate) 56 | 57 | expect(user.post_votes).to be_empty 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |If you are the application owner check the logs for more information.
64 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |