├── app ├── assets │ ├── stylesheets │ │ ├── .gitkeep │ │ └── search.scss │ ├── images │ │ └── favicon.png │ └── javascripts │ │ └── form_storage.coffee ├── views │ ├── replies │ │ ├── system_events │ │ │ ├── _close.html.erb │ │ │ ├── _reopen.html.erb │ │ │ ├── _excellent.html.erb │ │ │ ├── _unexcellent.html.erb │ │ │ ├── _ban.html.erb │ │ │ └── _mention.html.erb │ │ ├── index.js.erb │ │ ├── reply_to.js.erb │ │ ├── _system_event.html.erb │ │ ├── update.js.erb │ │ ├── create.js.erb │ │ ├── _create_callback.js.erb │ │ ├── _reply_to.html.erb │ │ └── edit.html.erb │ ├── admin │ │ ├── locations │ │ │ ├── edit.html.erb │ │ │ ├── _base.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── comments │ │ │ ├── destroy.js.erb │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── replies │ │ │ ├── destroy.js.erb │ │ │ ├── edit.html.erb │ │ │ └── _form.html.erb │ │ ├── nodes │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── topics │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── _form.html.erb │ │ ├── users │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── site_configs │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── sections │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ └── photos │ │ │ ├── show.html.erb │ │ │ └── index.html.erb │ ├── topics │ │ ├── preview.json.jbuilder │ │ ├── ban.js.erb │ │ ├── _sidebar_box_tips.html.erb │ │ ├── new.html.erb │ │ ├── _ban_reason.html.erb │ │ ├── edit.html.erb │ │ ├── translation │ │ │ ├── _need_login_to_reply.zh-TW.html.erb │ │ │ └── _need_login_to_reply.zh-CN.html.erb │ │ ├── create.js.erb │ │ ├── update.js.erb │ │ ├── _related_topics.html.erb │ │ ├── _node_selector.html.erb │ │ ├── feed.builder │ │ ├── _sidebar_box_node_recent_topics.html.erb │ │ ├── node_feed.builder │ │ ├── _reply_form.html.erb │ │ └── _sidebar_for_topic_index.html.erb │ ├── api │ │ └── v3 │ │ │ ├── photos │ │ │ └── create.json.jbuilder │ │ │ ├── nodes │ │ │ ├── index.json.jbuilder │ │ │ └── show.json.jbuilder │ │ │ ├── users │ │ │ ├── index.json.jbuilder │ │ │ ├── blocked.json.jbuilder │ │ │ ├── topics.json.jbuilder │ │ │ ├── followers.json.jbuilder │ │ │ ├── following.json.jbuilder │ │ │ ├── show.json.jbuilder │ │ │ └── replies.json.jbuilder │ │ │ ├── topics │ │ │ ├── index.json.jbuilder │ │ │ ├── replies.json.jbuilder │ │ │ └── show.json.jbuilder │ │ │ ├── notifications │ │ │ └── index.json.jbuilder │ │ │ ├── replies │ │ │ └── show.json.jbuilder │ │ │ └── application │ │ │ ├── _node.json.jbuilder │ │ │ └── _abilities.json.jbuilder │ ├── doorkeeper │ │ ├── applications │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── _menu.html.erb │ │ └── authorizations │ │ │ ├── error.html.erb │ │ │ └── show.html.erb │ ├── users │ │ ├── reward.js.erb │ │ ├── _bio.html.erb │ │ ├── replies.html.erb │ │ ├── topics.html.erb │ │ ├── followers.html.erb │ │ ├── index.html.erb │ │ ├── _user_list.html.erb │ │ ├── blocked.html.erb │ │ ├── _replies.html.erb │ │ ├── city.html.erb │ │ ├── _topics.html.erb │ │ ├── _recent_publish_topics.html.erb │ │ ├── _menu.html.erb │ │ ├── _reward.html.erb │ │ ├── favorites.html.erb │ │ └── _repos.html.erb │ ├── kaminari │ │ ├── _gap.html.erb │ │ ├── _next_page.html.erb │ │ ├── _prev_page.html.erb │ │ ├── _page.html.erb │ │ └── _paginator.html.erb │ ├── likes │ │ ├── index.html.erb │ │ ├── destory.js.erb │ │ └── create.js.erb │ ├── notifications │ │ ├── _mention.html.erb │ │ ├── _follow.html.erb │ │ ├── _topic.html.erb │ │ ├── _node_changed.html.erb │ │ ├── mentions │ │ │ ├── _topic.html.erb │ │ │ ├── _reply.html.erb │ │ │ └── _comment.html.erb │ │ ├── _team_invite.html.erb │ │ ├── _topic_reply.html.erb │ │ ├── notifications │ │ │ ├── _notification.html.erb │ │ │ └── index.html.erb │ │ └── _comment.html.erb │ ├── errors │ │ ├── 404.html.erb │ │ └── 403.html.erb │ ├── layouts │ │ └── window.html.erb │ ├── devise │ │ ├── menu │ │ │ ├── _login_items.html.erb │ │ │ └── _registration_items.html.erb │ │ ├── mailer │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── confirmation_instructions.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ └── unlocks │ │ │ └── new.html.erb │ ├── topic_mailer │ │ └── new_reply.html.erb │ ├── account │ │ └── create.js.erb │ ├── team_users │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ ├── index.html.erb │ │ ├── _team_user.html.erb │ │ └── show.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ ├── _hot_locations.html.erb │ │ ├── _comment.html.erb │ │ ├── _navbar.html.erb │ │ └── _index_sections.html.erb │ ├── user_mailer │ │ └── welcome.html.erb │ ├── comments │ │ ├── _comment.html.erb │ │ └── create.js.erb │ ├── search │ │ ├── _team.html.erb │ │ ├── _topic.html.erb │ │ ├── _page.html.erb │ │ ├── index.html.erb │ │ └── _user.html.erb │ ├── teams │ │ ├── index.html.erb │ │ ├── _team_list.html.erb │ │ ├── _sidebar.html.erb │ │ └── show.html.erb │ ├── home │ │ ├── markdown.html.erb │ │ └── index.html.erb │ └── settings │ │ └── _menu.html.erb ├── helpers │ ├── nodes_helper.rb │ ├── replies_helper.rb │ ├── locations_helper.rb │ └── teams_helper.rb ├── jobs │ ├── application_job.rb │ ├── notify_reply_job.rb │ ├── notify_topic_job.rb │ ├── github_repo_fetcher_job.rb │ ├── search_indexer.rb │ ├── mention_topic_job.rb │ └── push_job.rb ├── models │ ├── user_sso.rb │ ├── authorization.rb │ ├── concerns │ │ ├── user_avatar_delegate.rb │ │ ├── markdown_body.rb │ │ ├── soft_delete.rb │ │ ├── closeable.rb │ │ ├── searchable.rb │ │ └── mention_topic.rb │ ├── photo.rb │ ├── user │ │ ├── blockable.rb │ │ └── followable.rb │ ├── application_record.rb │ ├── device.rb │ ├── reply │ │ └── voteable.rb │ ├── section.rb │ ├── team.rb │ ├── topic │ │ └── auto_correct.rb │ ├── location.rb │ └── cache_version.rb ├── channels │ ├── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ ├── replies_channel.rb │ └── notifications_channel.rb ├── controllers │ ├── admin │ │ ├── home_controller.rb │ │ ├── application_controller.rb │ │ ├── photos_controller.rb │ │ ├── locations_controller.rb │ │ ├── stats_controller.rb │ │ ├── site_configs_controller.rb │ │ ├── comments_controller.rb │ │ └── replies_controller.rb │ ├── devices_controller.rb │ ├── comments_controller.rb │ ├── oauth │ │ ├── authorized_applications_controller.rb │ │ └── applications_controller.rb │ ├── nodes_controller.rb │ ├── photos_controller.rb │ ├── api │ │ └── v3 │ │ │ ├── nodes_controller.rb │ │ │ ├── photos_controller.rb │ │ │ └── root_controller.rb │ ├── users │ │ └── team_actions.rb │ ├── passwords_controller.rb │ ├── notifications │ │ └── notifications_controller.rb │ ├── sessions_controller.rb │ ├── account_controller.rb │ ├── likes_controller.rb │ └── home_controller.rb ├── uploaders │ ├── avatar_uploader.rb │ └── photo_uploader.rb └── mailers │ ├── user_mailer.rb │ └── base_mailer.rb ├── lib ├── templates │ └── erb │ │ └── scaffold │ │ ├── _base.html.erb │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── show.html.erb │ │ └── _form.html.erb ├── homeland │ ├── pipeline.rb │ ├── version.rb │ ├── username.rb │ ├── user_notification_helper.rb │ ├── pipeline │ │ ├── twemoji_filter.rb │ │ ├── floor_filter.rb │ │ └── normalize_mention_filter.rb │ ├── markdown.rb │ └── plugin.rb ├── assets │ ├── javascripts │ │ └── google_analytics.js │ └── stylesheets │ │ └── jquery.atwho.css ├── tasks │ └── bundler_audit.rake └── rails │ └── generators │ └── erb │ └── scaffold │ └── scaffold_generator.rb ├── public ├── robots.txt ├── api-doc │ ├── css │ │ └── common.css │ └── frames.html ├── avatar.png ├── favicon.ico └── 422.html ├── .rspec ├── config ├── locales │ ├── nodes.en.yml │ ├── nodes.zh-CN.yml │ ├── nodes.zh-TW.yml │ ├── devise.en.yml │ ├── carrierwave.zh-CN.yml │ ├── carrierwave.zh-TW.yml │ ├── carrierwave.en.yml │ ├── teams.zh-CN.yml │ ├── teams.zh-TW.yml │ ├── admin.zh-CN.yml │ ├── admin.zh-TW.yml │ ├── admin.en.yml │ ├── teams.en.yml │ ├── topics.zh-TW.yml │ └── topics.zh-CN.yml ├── spring.rb ├── elasticsearch.yml ├── elasticsearch.yml.default ├── environment.rb ├── initializers │ ├── social_share_button.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── rucaptcha.rb │ ├── per_form_csrf_tokens.rb │ ├── elasticsearch.rb │ ├── twemoji.rb │ ├── request_forgery_protection.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── status-page.rb │ ├── kaminari_config.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── exception-track.rb │ ├── redis.rb │ ├── cors.rb │ ├── assets.rb │ ├── inflections.rb │ ├── notifications.rb │ ├── rack-attack.rb │ └── content_security_policy.rb ├── redis.yml.default ├── memcached.yml ├── boot.rb ├── cable.yml ├── sidekiq.yml ├── database.yml.default ├── puma.example.rb └── secrets.yml.default ├── spec ├── fixtures │ └── test.png ├── helpers │ ├── nodes_helper_spec.rb │ └── locations_helper_spec.rb ├── factories │ ├── sections.rb │ ├── locations.rb │ ├── comments.rb │ ├── devices.rb │ ├── site_configs.rb │ ├── notifications.rb │ ├── replies.rb │ ├── notification_topics.rb │ ├── nodes.rb │ ├── teams.rb │ ├── topics.rb │ ├── notification_mentions.rb │ ├── notification_topic_replies.rb │ ├── notification_node_changeds.rb │ ├── access_tokens.rb │ ├── access_grants.rb │ ├── applications.rb │ └── team_users.rb ├── jobs │ ├── notify_reply_job_spec.rb │ ├── notify_topic_job_spec.rb │ └── github_repo_fetcher_job_spec.rb ├── requests │ └── users_spec.rb ├── models │ ├── notification_spec.rb │ ├── team_user_spec.rb │ ├── device_spec.rb │ ├── section_spec.rb │ └── user │ │ └── reward_fields_spec.rb ├── controllers │ ├── admin │ │ └── home_controller_spec.rb │ ├── nodes_controller_spec.rb │ ├── devices_controller_spec.rb │ └── search_controller_spec.rb └── api │ └── photos_spec.rb ├── bin ├── rake ├── gen-api-doc ├── bundle ├── rails ├── rspec ├── yarn ├── spring └── update ├── .codeclimate.yml ├── db ├── migrate │ ├── 20170302084611_remove_hr_fields.rb │ ├── 20160329091441_remove_old_notifications.rb │ ├── 20160518061135_add_closed_at_to_topics.rb │ ├── 20170222062457_add_reply_to_id_to_replies.rb │ ├── 20180316113342_add_last_reply_id_index_to_topics.rb │ ├── 20160119135131_update_user_name_allow_null.rb │ ├── 20160518083347_remove_private_token_from_users.rb │ ├── 20160710111853_add_team_id_to_topics.rb │ ├── 20170224074052_remove_bad_fields_from_users.rb │ ├── 20160826044113_add_level_to_oauth_applications.rb │ ├── 20160302043713_add_user_id_index_on_notifications.rb │ ├── 20161215014636_remove_body_html_field.rb │ ├── 20180316130855_rename_notifications_table_name.rb │ ├── 20160602020013_add_target_to_replies.rb │ ├── 20160119163218_update_oauth_token_fields_limit.rb │ ├── 20170217050010_create_exception_track_logs.rb │ ├── 20180614093642_change_topics_excellent_to_grade.rb │ ├── 20160819093756_add_lockable_to_devise.rb │ ├── 20160219075601_create_devices.rb │ ├── 20161230094832_add_text_pattern_opts_to_users.rb │ ├── 20160912124102_add_team_users_count_to_users.rb │ ├── 20161221022846_create_user_sso.rb │ ├── 20160707084438_create_team_users_join_table.rb │ ├── 20160119191013_add_indexes_to_table.rb │ ├── 20170207141208_remove_array_action_fields.rb │ ├── 20161228064225_remove_unneeded_indexes.rb │ ├── 20170204090209_create_actions.rb │ ├── 20160329032533_create_notifications.notifications.rb │ └── 20160126061903_create_settings.rb └── seeds.rb ├── config.ru ├── Vagrantfile ├── Dockerfile ├── Benchmarks.md ├── .gitignore └── Rakefile /app/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/_base.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_close.html.erb: -------------------------------------------------------------------------------- 1 | 关闭了讨论 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | user-agent: * 2 | Disallow: /cable 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -f progress 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_reopen.html.erb: -------------------------------------------------------------------------------- 1 | 重新开启了讨论 2 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_excellent.html.erb: -------------------------------------------------------------------------------- 1 | 将本帖设为了精华贴 2 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_unexcellent.html.erb: -------------------------------------------------------------------------------- 1 | 取消了精华贴 2 | -------------------------------------------------------------------------------- /public/api-doc/css/common.css: -------------------------------------------------------------------------------- 1 | /* Override this file with custom rules */ -------------------------------------------------------------------------------- /config/locales/nodes.en.yml: -------------------------------------------------------------------------------- 1 | "en": 2 | nodes: 3 | hot_node: "Popular Node" -------------------------------------------------------------------------------- /config/locales/nodes.zh-CN.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | nodes: 3 | hot_node: "热门节点" -------------------------------------------------------------------------------- /config/locales/nodes.zh-TW.yml: -------------------------------------------------------------------------------- 1 | "zh-TW": 2 | nodes: 3 | hot_node: "熱門節點" 4 | -------------------------------------------------------------------------------- /public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/homeland/master/public/avatar.png -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/new.html.erb: -------------------------------------------------------------------------------- 1 | <%%= render 'base' %> 2 | <%%= render 'form' %> 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/homeland/master/public/favicon.ico -------------------------------------------------------------------------------- /app/helpers/nodes_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NodesHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/views/admin/locations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'base' %> 2 | <%= render 'form' %> 3 | 4 | -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%%= render 'base' %> 2 | <%%= render 'form' %> 3 | 4 | -------------------------------------------------------------------------------- /spec/fixtures/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/homeland/master/spec/fixtures/test.png -------------------------------------------------------------------------------- /app/helpers/replies_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepliesHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/views/admin/comments/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $("#comment<%= params[:id] %>").remove(); 2 | App.notice("删除成功。") -------------------------------------------------------------------------------- /app/views/topics/preview.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.body markdown(@body) 4 | -------------------------------------------------------------------------------- /app/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/homeland/master/app/assets/images/favicon.png -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | end 5 | -------------------------------------------------------------------------------- /app/views/admin/replies/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $("#reply-<%= params[:id] %>").attr("class","deleted"); 2 | App.notice("删除成功。") -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - "lib/assets/**/*" 3 | - "app/controllers/admin/**/*" 4 | - "public/api-doc/**/*" 5 | -------------------------------------------------------------------------------- /lib/homeland/pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Pipeline 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/api/v3/photos/create.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.image_url @photo.image.url(:large) 4 | -------------------------------------------------------------------------------- /app/views/doorkeeper/applications/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "menu" %> 2 | 3 | <%= render 'form', application: @application %> 4 | -------------------------------------------------------------------------------- /app/models/user_sso.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserSSO < ActiveRecord::Base 4 | belongs_to :user 5 | end 6 | -------------------------------------------------------------------------------- /app/views/api/v3/nodes/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.nodes @nodes, partial: "node", as: :node 4 | -------------------------------------------------------------------------------- /app/views/api/v3/users/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.users @users, partial: "user", as: :user 4 | -------------------------------------------------------------------------------- /app/views/doorkeeper/applications/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "menu" %> 2 | 3 | <%= render 'form', application: @application %> 4 | -------------------------------------------------------------------------------- /app/views/replies/index.js.erb: -------------------------------------------------------------------------------- 1 | <% @replies.each do |reply| %> 2 | <%= render 'create_callback', reply: reply %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /bin/gen-api-doc: -------------------------------------------------------------------------------- 1 | yard --title "Ruby China API 文档" --out public/api-doc --no-private -r API.md app/controllers/api/v3/ app/views/api/v3 -------------------------------------------------------------------------------- /app/views/api/v3/topics/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.topics @topics, partial: "topic", as: :topic 4 | -------------------------------------------------------------------------------- /app/views/api/v3/users/blocked.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.blocked @users, partial: "user", as: :user 4 | -------------------------------------------------------------------------------- /app/views/api/v3/users/topics.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.topics @topics, partial: "topic", as: :topic 4 | -------------------------------------------------------------------------------- /app/views/api/v3/users/followers.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.followers @users, partial: "user", as: :user 4 | -------------------------------------------------------------------------------- /app/views/api/v3/users/following.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.following @users, partial: "user", as: :user 4 | -------------------------------------------------------------------------------- /app/views/doorkeeper/applications/_menu.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/users/reward.js.erb: -------------------------------------------------------------------------------- 1 | $('#reward-model').remove(); 2 | $('body').append("<%= j(render('reward'))%>"); 3 | $('#reward-modal').modal(); 4 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= content_tag :a, raw(t('pagination.gap')), class: "page-link" %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/topics/ban.js.erb: -------------------------------------------------------------------------------- 1 | $('#ban-modal').remove(); 2 | $('body').append("<%= j(render('ban', topic: @topic)) %>"); 3 | $('#ban-modal').modal(); 4 | -------------------------------------------------------------------------------- /app/views/users/_bio.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    <%= t("users.bio")%>
    3 |

    <%= auto_link(simple_format(bio)) %>

    4 |
    5 | -------------------------------------------------------------------------------- /app/views/admin/locations/_base.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t('admin.menu.locations') %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /spec/helpers/nodes_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe NodesHelper, type: :helper do 6 | end 7 | -------------------------------------------------------------------------------- /app/views/api/v3/topics/replies.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.replies @replies, partial: "reply", as: :reply 4 | json.meta @meta 5 | -------------------------------------------------------------------------------- /app/views/likes/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% @users.each do |user| %> 3 | <%= user_avatar_tag(user, :xs) %> 4 | <% end %> 5 |
    -------------------------------------------------------------------------------- /app/views/replies/reply_to.js.erb: -------------------------------------------------------------------------------- 1 | $(".reply[data-id=<%= @reply.id %>] .reply-to-block").replaceWith("<%= j(render('reply_to', reply: @reply, show_body: true)) %>") 2 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /app/views/api/v3/notifications/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.notifications @notifications, partial: "notification", as: :notification 4 | -------------------------------------------------------------------------------- /app/views/notifications/_mention.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "notifications/mentions/#{notification.target_type.underscore}", locals: { notification: notification } %> 2 | -------------------------------------------------------------------------------- /db/migrate/20170302084611_remove_hr_fields.rb: -------------------------------------------------------------------------------- 1 | class RemoveHrFields < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :hr 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/sections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :section do 5 | sequence(:name) { |n| "name#{n}" } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/topics/_sidebar_box_tips.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    小帖士
    3 |
    4 | <%= random_tips %> 5 |
    6 |
    -------------------------------------------------------------------------------- /lib/homeland/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class << self 5 | def version 6 | "3.1.0.rc" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/locations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :location do 5 | sequence(:name) { |n| "name#{n}" } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/api/v3/nodes/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.node do 4 | json.partial! partial: "node", locals: { node: @node } 5 | end 6 | json.meta @meta 7 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_ban.html.erb: -------------------------------------------------------------------------------- 1 | <% if reply.body.present? %> 2 | 屏蔽了此话题:<%= reply.body %> 3 | <% else %> 4 | 内容不符合版规屏蔽此话题 5 | <% end %> 6 | -------------------------------------------------------------------------------- /db/migrate/20160329091441_remove_old_notifications.rb: -------------------------------------------------------------------------------- 1 | class RemoveOldNotifications < ActiveRecord::Migration[5.0] 2 | def change 3 | drop_table :notifications 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | account: 3 | signed_up: 'Sign up successed.' 4 | updated: 'Profile update successed.' 5 | destroyed: 'Goodbye! You account was deleted.' -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | %w[ 4 | .ruby-version 5 | .rbenv-vars 6 | tmp/restart.txt 7 | tmp/caching-dev.txt 8 | ].each { |path| Spring.watch(path) } 9 | -------------------------------------------------------------------------------- /db/migrate/20160518061135_add_closed_at_to_topics.rb: -------------------------------------------------------------------------------- 1 | class AddClosedAtToTopics < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :topics, :closed_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/api/v3/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.user do 4 | json.partial! partial: "user", locals: { user: @user, detail: true } 5 | end 6 | json.meta @meta 7 | -------------------------------------------------------------------------------- /config/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: 127.0.0.1:9200 3 | 4 | development: 5 | <<: *defaults 6 | 7 | test: 8 | <<: *defaults 9 | 10 | production: 11 | <<: *defaults -------------------------------------------------------------------------------- /app/views/api/v3/replies/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.reply do 4 | json.partial! partial: "reply", locals: { reply: @reply, detail: true } 5 | end 6 | json.meta @meta 7 | -------------------------------------------------------------------------------- /app/views/api/v3/topics/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.topic do 4 | json.partial! partial: "topic", locals: { topic: @topic, detail: true } 5 | end 6 | json.meta @meta 7 | -------------------------------------------------------------------------------- /app/views/api/v3/users/replies.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | json.replies @replies do |reply| 4 | json.partial! partial: "reply", locals: { reply: reply, detail: true } 5 | end 6 | -------------------------------------------------------------------------------- /config/elasticsearch.yml.default: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: 127.0.0.1:9200 3 | 4 | development: 5 | <<: *defaults 6 | 7 | test: 8 | <<: *defaults 9 | 10 | production: 11 | <<: *defaults -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /db/migrate/20170222062457_add_reply_to_id_to_replies.rb: -------------------------------------------------------------------------------- 1 | class AddReplyToIdToReplies < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :replies, :reply_to_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180316113342_add_last_reply_id_index_to_topics.rb: -------------------------------------------------------------------------------- 1 | class AddLastReplyIdIndexToTopics < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :topics, :last_reply_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/likes/destory.js.erb: -------------------------------------------------------------------------------- 1 | <% if @success %> 2 | $("#<%= @element_id %>").attr("class","icon small_like").data("method","post").attr("title", "取消赞"); 3 | <% else %> 4 | App.alert("提交失败.") 5 | <% end %> 6 | -------------------------------------------------------------------------------- /config/initializers/social_share_button.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SocialShareButton.configure do |config| 4 | config.allow_sites = %w[twitter wechat facebook google_plus weibo douban] 5 | end 6 | -------------------------------------------------------------------------------- /config/redis.yml.default: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: 127.0.0.1 3 | port: 6379 4 | 5 | development: 6 | <<: *defaults 7 | 8 | test: 9 | <<: *defaults 10 | 11 | production: 12 | <<: *defaults -------------------------------------------------------------------------------- /db/migrate/20160119135131_update_user_name_allow_null.rb: -------------------------------------------------------------------------------- 1 | class UpdateUserNameAllowNull < ActiveRecord::Migration[4.2] 2 | def change 3 | change_column :users, :name, :string, null: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160518083347_remove_private_token_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemovePrivateTokenFromUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :private_token 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/errors/404.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Oops, Page not found

    4 |

    5 | 似乎没有这个页面哦!大哥,回去看看别的吧。 6 |

    7 |
    8 |
    9 | -------------------------------------------------------------------------------- /app/views/layouts/window.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= stylesheet_link_tag "window" %> 5 | <%= yield :scripts %> 6 | 7 | 8 | <%= yield %> 9 | 10 | -------------------------------------------------------------------------------- /app/views/likes/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if @success %> 2 | $("#<%= @element_id %>").attr("class","icon small_liked").data("method","delete").attr("title", "取消赞"); 3 | <% else %> 4 | App.alert("提交失败.") 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/channels/replies_channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepliesChannel < ApplicationCable::Channel 4 | def follow(data) 5 | stream_from "topics/#{data['topic_id']}/replies" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/homeland/username.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Username 5 | def self.sanitize(username) 6 | username.gsub(/[^\w.-]/, "_") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :comment do 5 | body "body" 6 | association :user 7 | association :commentable 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/devices.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :device do 5 | platform 1 6 | association :user 7 | token { SecureRandom.hex } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160710111853_add_team_id_to_topics.rb: -------------------------------------------------------------------------------- 1 | class AddTeamIdToTopics < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :topics, :team_id, :integer 4 | add_index :topics, :team_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20170224074052_remove_bad_fields_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveBadFieldsFromUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :co 4 | remove_column :users, :qq 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/devise/menu/_login_items.html.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? %> 2 | <%= link_to(t("common.logout"), destroy_user_session_path) %> 3 | <% else %> 4 | <%= link_to(t("common.login"), new_user_session_path) %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /spec/factories/site_configs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :site_config do 5 | sequence(:key) { |n| "key_#{n}" } 6 | sequence(:value) { |n| "value_#{n}" } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/notifications/_follow.html.erb: -------------------------------------------------------------------------------- 1 | <% user = notification.actor %> 2 | <% if user.blank? %> 3 | 相关信息已删除 4 | <% else %> 5 |
    6 | <%= user_name_tag(user) %> 开始关注你了。 7 |
    8 | <% end %> 9 | -------------------------------------------------------------------------------- /app/views/notifications/_topic.html.erb: -------------------------------------------------------------------------------- 1 | <% topic = notification.target %> 2 | <% if topic.blank? %> 3 | 相关信息已删除 4 | <% else %> 5 |
    6 | 创建了帖子 <%= topic_title_tag(topic) %> 7 |
    8 | <% end %> 9 | -------------------------------------------------------------------------------- /spec/factories/notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification, class: Notification do 5 | association :user 6 | association :actor, factory: :user 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/replies.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :reply do 5 | sequence(:body) { |n| "body#{n}" } 6 | association :user 7 | association :topic 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/memcached.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: 127.0.0.1:11211 3 | namespace: rb-1 4 | compress: true 5 | 6 | development: 7 | <<: *defaults 8 | 9 | test: 10 | <<: *defaults 11 | 12 | production: 13 | <<: *defaults -------------------------------------------------------------------------------- /app/jobs/notify_reply_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NotifyReplyJob < ApplicationJob 4 | queue_as :notifications 5 | 6 | def perform(reply_id) 7 | Reply.notify_reply_created(reply_id) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/notify_topic_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NotifyTopicJob < ApplicationJob 4 | queue_as :notifications 5 | 6 | def perform(topic_id) 7 | Topic.notify_topic_created(topic_id) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/authorization.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Authorization < ApplicationRecord 4 | belongs_to :user 5 | validates :uid, :provider, presence: true 6 | validates :uid, uniqueness: { scope: :provider } 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160826044113_add_level_to_oauth_applications.rb: -------------------------------------------------------------------------------- 1 | class AddLevelToOauthApplications < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :oauth_applications, :level, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/concerns/user_avatar_delegate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module UserAvatarDelegate 4 | extend ActiveSupport::Concern 5 | 6 | def user_avatar_raw 7 | self.user ? self.user[:avatar] : nil 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/photo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Photo < ApplicationRecord 4 | belongs_to :user, optional: true 5 | 6 | validates_presence_of :image 7 | 8 | # 封面图 9 | mount_uploader :image, PhotoUploader 10 | end 11 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | Rails.application.config.session_store :cookie_store, key: "_homeland_session", expire_after: 86_400 * 90 5 | -------------------------------------------------------------------------------- /spec/factories/notification_topics.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification_topic, parent: :notification do 5 | notify_type "topic" 6 | association :target, factory: :topic 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.erb: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.erb: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/views/topics/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t('topics.new_topic') %> 2 | 3 |
    4 |
    <%= t('topics.new_topic') %>
    5 |
    6 | <%= render 'form' %> 7 |
    8 |
    9 | -------------------------------------------------------------------------------- /config/initializers/rucaptcha.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | memcached_config = Rails.application.config_for(:memcached) 4 | RuCaptcha.configure do 5 | self.cache_store = [:mem_cache_store, memcached_config["host"], memcached_config] 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160302043713_add_user_id_index_on_notifications.rb: -------------------------------------------------------------------------------- 1 | class AddUserIdIndexOnNotifications < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_index :notifications, :read 4 | add_index :notifications, :user_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/nodes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :node do 5 | sequence(:name) { |n| "name#{n}" } 6 | section { |s| s.association(:section) } 7 | summary "summary" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/admin/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class HomeController < Admin::ApplicationController 5 | def index 6 | @recent_topics = Topic.recent.limit(5) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/github_repo_fetcher_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GitHubRepoFetcherJob < ApplicationJob 4 | queue_as :http_request 5 | 6 | def perform(user_id) 7 | User.fetch_github_repositories(user_id) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/topics/_ban_reason.html.erb: -------------------------------------------------------------------------------- 1 | <% if @topic.ban? %> 2 |
    3 |
    此贴已暂时被屏蔽!
    4 |
    5 | <%= raw Setting.ban_reason_html %> 6 |
    7 |
    8 | <% end %> 9 | -------------------------------------------------------------------------------- /config/initializers/per_form_csrf_tokens.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Enable per-form CSRF tokens. 6 | Rails.application.config.action_controller.per_form_csrf_tokens = true 7 | -------------------------------------------------------------------------------- /app/views/replies/system_events/_mention.html.erb: -------------------------------------------------------------------------------- 1 | 在 2 | 3 | <% if reply.target_type == "Topic" %> 4 | <%= topic_title_tag(reply&.target) %> 5 | <% else %> 6 | <%= topic_title_tag(reply&.target&.topic) %> 7 | <% end %> 8 | 中提及了此贴 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 4 | 5 | require "bundler/setup" # Set up gems listed in the Gemfile. 6 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | production: &defaults 2 | adapter: redis 3 | url: redis://localhost:6379/1 4 | timeout: 1 5 | 6 | development: 7 | <<: *defaults 8 | url: redis://localhost:6379/2 9 | 10 | test: 11 | <<: *defaults 12 | url: redis://localhost:6379/3 13 | -------------------------------------------------------------------------------- /config/initializers/elasticsearch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "elasticsearch/rails/instrumentation" 4 | 5 | config = Rails.application.config_for(:elasticsearch) 6 | 7 | Elasticsearch::Model.client = Elasticsearch::Client.new host: config["host"] 8 | -------------------------------------------------------------------------------- /db/migrate/20161215014636_remove_body_html_field.rb: -------------------------------------------------------------------------------- 1 | class RemoveBodyHtmlField < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :topics, :body_html 4 | remove_column :replies, :body_html 5 | remove_column :comments, :body_html 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/admin/comments/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.comments")%> <%= t("common.edit")%> 3 | <% end %> 4 | 5 | <%= render 'form' %> 6 | -------------------------------------------------------------------------------- /app/views/replies/_system_event.html.erb: -------------------------------------------------------------------------------- 1 | <%= user_avatar_tag(reply.user, :xs) %> <%= user_name_tag(reply.user) %> 2 | <%= render partial: "/replies/system_events/#{reply.action}", locals: { reply: reply } %> 3 | <%= l reply.created_at, format: :short %> 4 | -------------------------------------------------------------------------------- /config/initializers/twemoji.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "twemoji/svg" 4 | 5 | Twemoji.configure do |config| 6 | config.asset_root = "https://twemoji.b0.upaiyun.com/2" 7 | config.file_ext = "svg" 8 | config.class_name = "twemoji" 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20180316130855_rename_notifications_table_name.rb: -------------------------------------------------------------------------------- 1 | class RenameNotificationsTableName < ActiveRecord::Migration[5.2] 2 | def change 3 | # Revert Notification table name to "notifications" 4 | rename_table :new_notifications, :notifications 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/teams.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :team do 5 | sequence(:name) { |n| "Team #{n}" } 6 | sequence(:login) { |n| "team#{n}" } 7 | sequence(:email) { |n| "team#{n}@gethomeland.com" } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/admin/nodes/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.nodes")%> <%= t("common.create")%> 3 | <% end %> 4 |

    新建节点

    5 | 6 | <%= render 'form' %> 7 | -------------------------------------------------------------------------------- /config/initializers/request_forgery_protection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Enable origin-checking CSRF mitigation. 6 | Rails.application.config.action_controller.forgery_protection_origin_check = true 7 | -------------------------------------------------------------------------------- /app/views/admin/topics/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.topics")%> <%= t("common.create")%> 3 | <% end %> 4 |

    New topic

    5 | 6 | <%= render 'form' %> 7 | -------------------------------------------------------------------------------- /app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.users")%> <%= t("common.create")%> 3 | <% end %> 4 |

    New user

    5 | 6 | <%= render 'form' %> 7 | -------------------------------------------------------------------------------- /spec/factories/topics.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :topic do 5 | sequence(:title) { |n| "Topic Title #{n}" } 6 | sequence(:body) { |n| "Topic Body #{n}" } 7 | association :user 8 | association :node 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/admin/nodes/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.nodes")%> <%= t("common.edit")%> 3 | <% end %> 4 |

    修改节点

    5 | 6 | <%= render 'form' %> 7 | 8 | 9 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path("../spring", __FILE__) 6 | rescue LoadError => e 7 | raise unless e.message.include?("spring") 8 | end 9 | require "bundler/setup" 10 | load Gem.bin_path("rspec-core", "rspec") 11 | -------------------------------------------------------------------------------- /app/uploaders/avatar_uploader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AvatarUploader < BaseUploader 4 | def filename 5 | if super.present? 6 | @name ||= SecureRandom.hex(3) 7 | "avatar/#{model.id}/#{@name}.#{file.extension.downcase}" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/admin/site_configs/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.site_configs")%> 3 | <%= @site_config.var %> 4 | <% end %> 5 | 6 | <%= render 'form' %> 7 | 8 | -------------------------------------------------------------------------------- /app/views/doorkeeper/authorizations/error.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= t('doorkeeper.authorizations.error.title') %> 4 |
    5 |
    6 |
    <%= @pre_auth.error_response.body[:error_description] %>
    7 |
    8 |
    9 | -------------------------------------------------------------------------------- /app/views/admin/sections/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.sections") %> <%= t("common.create") %> 3 | <% end %> 4 | 5 |

    新建分类

    6 | 7 | <%= render 'form' %> 8 | 9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    你好 <%= @resource.fullname %>

    2 | 3 |

    由于多次错误密码尝试登录,你的帐号已经暂时被锁定了,锁定时间为 1 小时,锁定期间此帐号将无法登录,也无法尝试密码。

    4 | 5 |

    你也可以点击下面的链接立即解锁:

    6 | 7 |

    <%= link_to '解锁我的帐号', unlock_url(@resource, unlock_token: @token), class: 'btn' %>

    8 | -------------------------------------------------------------------------------- /app/views/admin/sections/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.sections") %> <%= t("common.edit") %> 3 | <% end %> 4 |

    Editing section

    5 | 6 | <%= render 'form' %> 7 | 8 | 9 | -------------------------------------------------------------------------------- /spec/factories/notification_mentions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification_mention, parent: :notification do 5 | notify_type "mention" 6 | association :target, factory: :reply 7 | association :second_target, factory: :topic 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160602020013_add_target_to_replies.rb: -------------------------------------------------------------------------------- 1 | class AddTargetToReplies < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :replies, :action, :string 4 | add_column :replies, :target_type, :string, after: :action 5 | add_column :replies, :target_id, :string, after: :target_type 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserMailer < BaseMailer 4 | def welcome(user_id) 5 | @user = User.find_by_id(user_id) 6 | return false if @user.blank? 7 | mail(to: @user.email, subject: t("mail.welcome_subject", app_name: Setting.app_name).to_s) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/topics/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t('topics.edit_topic') %> 2 | 3 |
    4 |
    <%= t('topics.edit_topic') %> <%= topic_title_tag(@topic) %>
    5 |
    6 | <%= render 'form' %> 7 |
    8 |
    9 | -------------------------------------------------------------------------------- /spec/factories/notification_topic_replies.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification_topic_reply, parent: :notification do 5 | notify_type "topic_reply" 6 | association :target, factory: :reply 7 | association :second_target, factory: :topic 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/locations_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LocationsHelper 4 | def location_name_tag(location, _options = {}) 5 | return "" if location.blank? 6 | name = location.is_a?(String) == true ? location : location.name 7 | link_to(name, location_users_path(name)) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/admin/replies/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t('admin.menu.replies') %> <%= t("common.edit") %> 3 | <% end %> 4 |

    <%= t("common.edit") %><%= t('admin.menu.replies') %>

    5 | <%= render 'form' %> -------------------------------------------------------------------------------- /spec/factories/notification_node_changeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :notification_node_changed, parent: :notification do 5 | notify_type "node_changed" 6 | association :target, factory: :topic 7 | association :second_target, factory: :node 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/concerns/markdown_body.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 转换 body -> html 4 | # [Plugin API] 5 | module MarkdownBody 6 | extend ActiveSupport::Concern 7 | include ActionView::Helpers::OutputSafetyHelper 8 | include ApplicationHelper 9 | 10 | def body_html 11 | markdown(body) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/topic_mailer/new_reply.html.erb: -------------------------------------------------------------------------------- 1 |

    你关注的帖子有了新回复

    2 |

    3 | <%= user_name_tag(@reply.user) %> 回复了 《<%= topic_title_tag(@topic)%>》:
    4 |

    5 |
    6 |

    <%= @reply.body_html %>

    7 |
    8 |

    9 | <%= topic_title_tag(@topic) %>. 10 |

    11 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += %i[password password_confirmation current_password token] 7 | -------------------------------------------------------------------------------- /db/migrate/20160119163218_update_oauth_token_fields_limit.rb: -------------------------------------------------------------------------------- 1 | class UpdateOauthTokenFieldsLimit < ActiveRecord::Migration[4.2] 2 | def change 3 | change_column :oauth_access_tokens, :expires_in, :integer, limit: 8, null: true 4 | change_column :oauth_access_grants, :expires_in, :integer, limit: 8, null: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/mailers/base_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BaseMailer < ActionMailer::Base 4 | default from: Setting.mailer_sender 5 | default charset: "utf-8" 6 | default content_type: "text/html" 7 | default_url_options[:host] = Setting.domain 8 | 9 | layout "mailer" 10 | helper :topics, :users 11 | end 12 | -------------------------------------------------------------------------------- /app/views/replies/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if @reply.errors.empty? %> 2 | <% flash[:notice] = '回帖更新成功。' %> 3 | Turbolinks.visit('<%= topic_path(@reply.topic_id) %>'); 4 | <% else %> 5 | $('form[tb="edit-reply"] .alert').remove(); 6 | $('form[tb="edit-reply"]').prepend('<%= j render("shared/error_messages", target: @reply) %>'); 7 | <% end %> 8 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/initializers/status-page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | redis_config = Rails.application.config_for(:redis) 4 | 5 | StatusPage.configure do 6 | use :cache 7 | use :redis, url: "redis://#{redis_config['host']}:#{redis_config['port']}/0" 8 | use :sidekiq 9 | use :database 10 | 11 | interval = 10 12 | end 13 | -------------------------------------------------------------------------------- /app/uploaders/photo_uploader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PhotoUploader < BaseUploader 4 | # Override the filename of the uploaded files: 5 | def filename 6 | if super.present? 7 | @name ||= SecureRandom.uuid 8 | "#{Time.now.year}/#{@name}.#{file.extension.downcase}" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/admin/topics/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.topics")%> <%= t("common.edit")%> 3 | <% end %> 4 |

    <%= t("common.edit")%><%= t("admin.menu.topics") %>

    5 | 6 | <%= render 'form' %> 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/views/topics/translation/_need_login_to_reply.zh-TW.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 需要 <%= link_to(t("common.login"), "/account/sign_in", class: "btn btn-primary") %> 後方可回應,如果你還沒有帳號按這裡 <%= link_to("#{t("common.register")}", "/account/sign_up", class: "btn btn-danger") %>。 4 |
    5 |
    -------------------------------------------------------------------------------- /app/views/topics/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if @topic.errors.empty? %> 2 | <% flash[:notice] = t('topics.create_topic_success') %> 3 | Turbolinks.visit('<%= topic_path(@topic.id) %>'); 4 | <% else %> 5 | $('form[tb="edit-topic"] .alert').remove(); 6 | $('form[tb="edit-topic"]').prepend('<%= j render("shared/error_messages", target: @topic) %>'); 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/topics/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if @topic.errors.empty? %> 2 | <% flash[:notice] = t('topics.update_topic_success') %> 3 | Turbolinks.visit('<%= topic_path(@topic.id) %>'); 4 | <% else %> 5 | $('form[tb="edit-topic"] .alert').remove(); 6 | $('form[tb="edit-topic"]').prepend('<%= j render("shared/error_messages", target: @topic) %>'); 7 | <% end %> 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require ::File.expand_path("../config/environment", __FILE__) 6 | 7 | # Action Cable uses EventMachine which requires that all classes are loaded in advance 8 | Rails.application.eager_load! 9 | 10 | run Rails.application 11 | -------------------------------------------------------------------------------- /app/controllers/devices_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DevicesController < ApplicationController 4 | before_action :authenticate_user! 5 | 6 | def destroy 7 | @device = current_user.devices.find(params[:id]) 8 | @device.delete 9 | redirect_to oauth_applications_path, notice: "设备信息已删除" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/helpers/teams_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TeamsHelper 4 | def team_member_counts_tag(team) 5 | count_text = I18n.t("teams.team_users_count", count: team.team_users_count) 6 | 7 | content_tag(:i, nil, class: "fa fa-users") + 8 | content_tag(:span, count_text, class: "team-users-count") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/jobs/notify_reply_job_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe NotifyReplyJob, type: :job do 6 | describe ".perform" do 7 | it "should work" do 8 | expect(Reply).to receive(:notify_reply_created).with(123).once 9 | NotifyReplyJob.perform_later(123) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/jobs/notify_topic_job_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe NotifyTopicJob, type: :job do 6 | describe ".perform" do 7 | it "should work" do 8 | expect(Topic).to receive(:notify_topic_created).with(321).once 9 | NotifyTopicJob.perform_later(321) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/errors/403.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %>404<% end %> 2 |
    3 |
    4 |

    >403 没有权限

    5 |

    6 | 抱歉,你没有做此操作的权限,如有问题,请到社区发帖询问。 7 |

    8 |

    9 | 返回首页 10 |

    11 |
    12 |
    13 | -------------------------------------------------------------------------------- /lib/assets/javascripts/google_analytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 5 | -------------------------------------------------------------------------------- /spec/factories/access_tokens.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :access_token, class: Doorkeeper::AccessToken do 5 | sequence(:resource_owner_id) { |n| n } 6 | application 7 | expires_in 2.hours 8 | 9 | factory :clientless_access_token do 10 | application nil 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/user/blockable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User 4 | module Blockable 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | action_store :block, :user 9 | action_store :block, :node 10 | end 11 | 12 | def block_users? 13 | block_user_actions.first.present? 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | # Options here can still be overridden by cmd line args. 2 | # sidekiq -C sidekiq.yml 3 | --- 4 | :concurrency: 20 5 | :pidfile: tmp/pids/sidekiq.pid 6 | :logfile: log/sidekiq.log 7 | :queues: 8 | - [notifications, 100] 9 | - [http_request, 50] 10 | - [search_indexer, 30] 11 | - [mailer, 5] 12 | - [mailers, 5] 13 | - [default, 3] 14 | -------------------------------------------------------------------------------- /app/channels/notifications_channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NotificationsChannel < ApplicationCable::Channel 4 | def subscribed 5 | logger.info "current connections: #{ActionCable.server.connections.count}" 6 | if self.current_user_id 7 | stream_from "notifications_count/#{self.current_user_id}" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/replies/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if !@reply.errors.blank? %> 2 | _topicView.replyCallback(0, '<%= j(@msg) %>'); 3 | <% else %> 4 | <% if @reply.upvote? %> 5 | Turbolinks.visit(location.href); 6 | <% else %> 7 | <%= render 'create_callback', reply: @reply %> 8 | 9 | _topicView.replyCallback(1, '<%= j(@msg) %>'); 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /spec/factories/access_grants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :access_grant, class: Doorkeeper::AccessGrant do 5 | sequence(:resource_owner_id) { |n| n } 6 | application 7 | expires_in 10.minutes 8 | redirect_uri "urn:ietf:wg:oauth:2.0:oob" 9 | sequence(:token) { |_n| SecureRandom.hex } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/jobs/github_repo_fetcher_job_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe GitHubRepoFetcherJob, type: :job do 6 | describe ".perform" do 7 | it "should work" do 8 | expect(User).to receive(:fetch_github_repositories).with(234).once 9 | GitHubRepoFetcherJob.perform_later(234) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/topics/translation/_need_login_to_reply.zh-CN.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 需要 <%= link_to(t("common.login"), "/account/sign_in", class: "btn btn-primary")%> 后方可回复, 如果你还没有账号请点击这里 <%= link_to("#{t("common.register")}", "/account/sign_up", class: "btn btn-danger")%>。 4 |
    5 |
    6 | -------------------------------------------------------------------------------- /app/views/notifications/_node_changed.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | topic = notification.target 3 | node = notification.second_target 4 | %> 5 | <% if topic.blank? or node.blank? %> 6 | 相关信息已删除 7 | <% else %> 8 |
    9 | 你发布的话题 <%= topic_title_tag(topic) %> 由于内容原因被管理员移动到了 <%= render_node_name(node.name, node.id) %> 节点,请注意查看节点说明。 10 |
    11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/account/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.empty? %> 2 | <% flash[:notice] = t('devise.sessions.signed_in') %> 3 | Turbolinks.visit('<%= topics_url %>'); 4 | <% else %> 5 | $('#new_user .alert').remove(); 6 | $('#new_user').prepend('<%= j render("shared/error_messages", target: resource) %>'); 7 | $('.rucaptcha-image-box').html('<%= j rucaptcha_image_tag %>'); 8 | <% end %> 9 | -------------------------------------------------------------------------------- /app/views/notifications/mentions/_topic.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | topic = notification.target 3 | %> 4 | 5 | <% if topic.blank? %> 6 |
    相关信息已删除
    7 | <% else %> 8 |
    9 | 在 <%= topic_title_tag(topic) %> 提及你: 10 |
    11 |
    12 | <%= topic.body_html %> 13 |
    14 | <% end %> 15 | -------------------------------------------------------------------------------- /spec/factories/applications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :application, class: Doorkeeper::Application do 5 | sequence(:name) { |n| "name#{n}" } 6 | sequence(:uid) { |n| "uid#{n}" } 7 | sequence(:secret) { |n| "secret#{n}" } 8 | redirect_uri "http://foobar.com" 9 | association :owner, factory: :user 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/notifications/mentions/_reply.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | reply = notification.target 3 | topic = notification.second_target || reply.topic 4 | %> 5 |
    6 | 在 <%= topic_title_tag(topic) %> 提及你: 7 |
    8 | <% if reply.present? %> 9 |
    10 | <%= reply.body_html %> 11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /lib/tasks/bundler_audit.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if Rails.env.development? || Rails.env.test? 4 | require "bundler/audit/cli" 5 | 6 | namespace :bundle do 7 | desc "Updates the ruby-advisory-db and runs audit" 8 | task :audit do 9 | %w[update check].each do |command| 10 | Bundler::Audit::CLI.start [command] 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20170217050010_create_exception_track_logs.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from exception_track (originally 20170217023900) 2 | class CreateExceptionTrackLogs < ActiveRecord::Migration[5.0] 3 | def change 4 | create_table :exception_tracks do |t| 5 | t.string :title 6 | t.text :body 7 | 8 | t.timestamps 9 | end 10 | 11 | drop_table :exception_logs 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Kaminari.configure do |config| 4 | config.default_per_page = 25 5 | # config.max_per_page = nil 6 | config.window = 4 7 | config.outer_window = 3 8 | # config.left = 0 9 | # config.right = 0 10 | # config.page_method_name = :page 11 | # config.param_name = :page 12 | # config.params_on_first_page = false 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20180614093642_change_topics_excellent_to_grade.rb: -------------------------------------------------------------------------------- 1 | class ChangeTopicsExcellentToGrade < ActiveRecord::Migration[5.2] 2 | def up 3 | rename_column :topics, :excellent, :grade 4 | 5 | # 61 is old nopoint node 6 | Topic.connection.execute("update topics set grade = -1 where node_id = 61") 7 | end 8 | 9 | def down 10 | rename_column :topics, :grade, :excellent 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/factories/team_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :team_user do 5 | association :team 6 | association :user 7 | role :member 8 | status :accepted 9 | end 10 | 11 | factory :team_owner, parent: :team_user do 12 | role :owner 13 | end 14 | 15 | factory :team_member, parent: :team_user do 16 | role :member 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/doorkeeper/authorizations/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    授权成功
    5 | 6 |
    7 |

    由于应用没有配置回调地址,请使用授权码:

    8 |

    <%= params[:code] %>

    9 |
    10 |
    11 | 12 |
    13 |
    14 | -------------------------------------------------------------------------------- /app/views/team_users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t("teams.edit_team_user") %> 2 | 3 | <%= render '/teams/header' %> 4 | 5 |
    6 |
    7 |
    8 |
    9 |

    <%= t("teams.edit_team_user") %>

    10 | <%= render '/team_users/form' %> 11 |
    12 |
    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /app/views/team_users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t("teams.new_team_user") %> 2 | 3 | <%= render '/teams/header' %> 4 | 5 |
    6 |
    7 |
    8 |
    9 |

    <%= t("teams.new_team_user") %>

    10 | <%= render '/team_users/form' %> 11 |
    12 |
    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /app/views/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if target.errors.any? %> 2 |
    3 | × 4 |
    有 <%= target.errors.count %> 处问题导致无法提交:
    5 | 10 |
    11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.erb: -------------------------------------------------------------------------------- 1 | <% if page.current? %> 2 |
  • 3 | <%= content_tag :a, page, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: "page-link" %> 4 |
  • 5 | <% else %> 6 |
  • 7 | <%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: "page-link" %> 8 |
  • 9 | <% end %> 10 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | identified_by :current_user_id 6 | 7 | def connect 8 | self.current_user_id = find_verified_user_id 9 | end 10 | 11 | protected 12 | 13 | def find_verified_user_id 14 | cookies.signed[:user_id] || nil 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    欢迎 <%= @resource.fullname %>

    2 |

    你已经成功注册 <%= link_to Setting.app_name, root_url %> 的账号, 3 | 接下来你需要点击下面的连接 激活 你的账号:

    4 |

    <%= link_to '激活账号', confirmation_url(@resource, confirmation_token: @token), class: 'btn' %>

    5 |

    如果看不到链接,请复制下面的连接然后在浏览器里面打开:

    6 |

    <%= confirmation_url(@resource, confirmation_token: @token) %>

    7 | -------------------------------------------------------------------------------- /app/views/devise/menu/_registration_items.html.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? %> 2 | 你好 <%= current_user.login %>, 3 | <% if not params[:controller].match(/admin/) %> 4 | <%= link_to('设置', setting_path) %> | 5 | <% if admin? current_user %>后台 | <% end %> 6 | <% end %> 7 | <% else %> 8 | <%= link_to('加入社区', new_user_registration_path) %> 9 | <% end %> 10 | -------------------------------------------------------------------------------- /lib/homeland/user_notification_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | module UserNotificationHelper 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | helper_method :unread_notify_count 9 | end 10 | 11 | def unread_notify_count 12 | return 0 if current_user.blank? 13 | @unread_notify_count ||= Notification.unread_count(current_user) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | 6 | include Redis::Objects 7 | 8 | scope :recent, -> { order(id: :desc) } 9 | scope :exclude_ids, ->(ids) { where.not(id: ids.map(&:to_i)) } 10 | scope :by_week, -> { where("created_at > ?", 7.days.ago.utc) } 11 | 12 | delegate :url_helpers, to: "Rails.application.routes" 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CommentsController < ApplicationController 4 | before_action :authenticate_user! 5 | 6 | def create 7 | @comment = Comment.new(comment_params) 8 | @comment.user = current_user 9 | @success = @comment.save 10 | end 11 | 12 | def comment_params 13 | params.require(:comment).permit(:commentable_type, :commentable_id, :body) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/user_mailer/welcome.html.erb: -------------------------------------------------------------------------------- 1 |

    你好 <%= @user.fullname %>

    2 |

    <%= t("mail.you_have_successfully") %> <%= link_to Setting.app_name, root_url %> <%= t("mail.registered_an_account") %>

    3 |

    <%= t("mail.welcome_title", app_name: Setting.app_name) %>

    4 |

    <%= link_to '立即登录', new_user_session_url, class: 'btn btn-primary' %>

    5 |

    <%= t("mail.login_from") %>:<%= link_to new_user_session_url, new_user_session_url %>

    6 | -------------------------------------------------------------------------------- /app/views/users/replies.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [@user.fullname, t('users.menu.replies')].join(' · ') %> 2 | 3 |
    4 | <%= render "sidebar", user: @user %> 5 |
    6 | <%= render "menu" %> 7 |
    8 | <%= render "replies", replies: @replies %> 9 | 12 |
    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /db/migrate/20160819093756_add_lockable_to_devise.rb: -------------------------------------------------------------------------------- 1 | class AddLockableToDevise < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :failed_attempts, :integer, default: 0, null: false # Only if lock strategy is :failed_attempts 4 | add_column :users, :unlock_token, :string # Only if unlock strategy is :email or :both 5 | add_column :users, :locked_at, :datetime 6 | add_index :users, :unlock_token, unique: true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/requests/users_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "GET /:login", type: :request do 6 | describe "login with complex cases" do 7 | it "should work" do 8 | %w[foo foo1 1234 foo-bar foo_bar foo_ foo.bar].each do |login| 9 | create(:user, login: login) 10 | get "/#{login}" 11 | assert_equal 200, response.status 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    <%= user_avatar_tag(item.user, :md) %>
    3 |
    4 |
    5 | <%= user_name_tag(item.user) %> <%= t("common.published_at") %> <%= timeago(item.created_at) %> 6 |
    7 |
    8 | <%= item.body_html %> 9 |
    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /app/views/search/_team.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    <%= user_avatar_tag(item, :md) %>
    5 |
    6 |
    7 |
    8 | <%= link_to item.name, item %> 9 |
    10 |
    11 | <%= markdown item.bio %> 12 |
    13 |
    14 |
    15 |
    -------------------------------------------------------------------------------- /db/migrate/20160219075601_create_devices.rb: -------------------------------------------------------------------------------- 1 | class CreateDevices < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :devices do |t| 4 | t.integer :platform, null: false 5 | t.integer :user_id, null: false 6 | t.string :token, null: false 7 | t.datetime :last_actived_at 8 | 9 | t.timestamps 10 | end 11 | 12 | add_index :devices, :user_id 13 | add_index :devices, [:user_id, :platform] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/shared/_hot_locations.html.erb: -------------------------------------------------------------------------------- 1 | <% cache(["index-locations",Time.now.strftime("%Y-%m-%d")]) do %> 2 |
    3 |
    <%=t("common.hot_locations")%>
    4 |
    5 | <% Location.hot.limit(12).each do |item| %> 6 | <%= location_name_tag(item) %> 7 | <% end %> 8 |
    9 |
    10 | <% end %> -------------------------------------------------------------------------------- /db/migrate/20161230094832_add_text_pattern_opts_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTextPatternOptsToUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | # UPDATE users SET name = substring(name, 99) WHERE length(name) >= 100 4 | change_column :users, :login, :string, limit: 100 5 | change_column :users, :name, :string, limit: 100 6 | add_index :users, 'lower(login) varchar_pattern_ops' 7 | add_index :users, 'lower(name) varchar_pattern_ops' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/shared/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= user_avatar_tag(comment.user, :md) %> 4 |
    5 |
    6 |
    7 | <%= user_name_tag(comment.user) %> 发表于 <%= timeago(comment.created_at) %> 8 |
    9 |
    10 | <%= comment.body_html %> 11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /app/views/search/_topic.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | title = hit.highlight.title.try(:first) || item.title 3 | %> 4 |
    5 |
    <%= link_to highlight(title), item %>
    6 |
    7 | <%= link_to polymorphic_url(item), item %> 8 | <%= item.created_at.to_date %> 9 |
    10 |
    <%= highlight(hit.highlight.body.try(:first)) %>
    11 |
    12 | -------------------------------------------------------------------------------- /app/views/topics/_related_topics.html.erb: -------------------------------------------------------------------------------- 1 | <% cache([@topic, 'related_topic', Date.current]) do %> 2 | <% if @topic.related_topics.present? %> 3 |
    4 |
    相关话题
    5 | 10 |
    11 | <% end %> 12 | <% end %> 13 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.erb: -------------------------------------------------------------------------------- 1 | <%= paginator.render do -%> 2 | 13 | <% end -%> 14 | -------------------------------------------------------------------------------- /app/views/shared/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /app/views/admin/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin,@comment] do |f| %> 2 | <%= render "/shared/error_messages", target: @comment %> 3 |
    4 | <%= f.label :body %> 5 | <%= f.text_area :body, class: "form-control" %> 6 |
    7 | 8 |
    9 | <%= f.submit "Save", class: "btn btn-primary", 'data-disable-with' => "Saving..." %> or 10 | <%= link_to 'Cancel', admin_comments_path, class: "btn" %> 11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/users/topics.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [@user.fullname, t('users.menu.topics')].join(' · ') %> 2 | 3 |
    4 | <%= render "sidebar", user: @user %> 5 |
    6 | <%= render "menu" %> 7 |
    8 |
    9 | <%= render "topics", topics: @topics %> 10 |
    11 | 14 |
    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /db/migrate/20160912124102_add_team_users_count_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTeamUsersCountToUsers < ActiveRecord::Migration[5.0] 2 | def up 3 | add_column :users, :team_users_count, :integer 4 | 5 | User.reset_column_information 6 | say_with_time "Reset all teams' team_users counter cache" do 7 | Team.select(:id).find_each { |team| Team.reset_counters(team.id, :team_users) } 8 | end 9 | end 10 | 11 | def down 12 | remove_column :users, :team_users_count 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/users/followers.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [@user.fullname, t('users.menu.followers')].join(' · ') %> 2 | 3 |
    4 | <%= render "sidebar", user: @user %> 5 |
    6 | <%= render "menu" %> 7 |
    8 |
    9 | <%= render "user_list", users: @users %> 10 |
    11 | 14 |
    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # -*- mode: ruby -*- 4 | # vi: set ft=ruby : 5 | 6 | Vagrant.configure(2) do |config| 7 | config.vm.box = "ubuntu/trusty64" 8 | config.vm.hostname = "homeland-dev" 9 | # For Android/iOS app dev 10 | config.vm.network "public_network" 11 | config.vm.provision "shell", path: "bin/provision.sh", privileged: false 12 | 13 | config.vm.provider "virtualbox" do |vb| 14 | vb.name = "homeland-dev" 15 | vb.memory = "2048" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/notifications/_team_invite.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | team = notification.second_target 3 | team_user = notification.target 4 | %> 5 | <% if team.blank? || team_user.blank? %> 6 |
    相关信息已删除
    7 | <% else %> 8 |
    9 | 邀请你加入 <%= user_name_tag(team) %> 10 |
    11 |
    12 | <%= link_to '查看邀请', main_app.user_team_user_path(team, team_user), class: 'btn btn-default btn-sm' %> 13 |
    14 | <% end %> 15 | -------------------------------------------------------------------------------- /db/migrate/20161221022846_create_user_sso.rb: -------------------------------------------------------------------------------- 1 | class CreateUserSSO < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :user_ssos do |t| 4 | t.integer :user_id, null: false 5 | t.string :uid, null: false, length: 255 6 | t.string :username 7 | t.string :email 8 | t.string :name 9 | t.string :avatar_url 10 | t.text :last_payload, null: false 11 | t.timestamps 12 | end 13 | 14 | add_index :user_ssos, :uid, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/oauth/authorized_applications_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Oauth 4 | class AuthorizedApplicationsController < Doorkeeper::ApplicationController 5 | before_action :authenticate_resource_owner! 6 | 7 | def destroy 8 | Doorkeeper::AccessToken.revoke_all_for params[:id].to_i, current_resource_owner 9 | redirect_to oauth_applications_url, notice: I18n.t(:notice, scope: %i[doorkeeper flash authorized_applications destroy]) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/locales/carrierwave.zh-CN.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | errors: 3 | messages: 4 | carrierwave_processing_error: 处理错误 5 | carrierwave_integrity_error: 格式不是可上载的类型 6 | extension_white_list_error: "不可上载 %{extension} 档案,可以上载的档案类型为:%{allowed_types}" 7 | rmagick_processing_error: "用 rmagick 处理影像档时发生错误,也许上载的不是影像档?原错误讯息:%{e}" 8 | mime_types_processing_error: "处理档案 MIME::Types 时发生错误,也许上载的档案不是正确的 content-type ?原错误:%{e}" 9 | mini_magick_processing_error: "使用 MiniMagick 处理影像时发生错误,也许上载的不是影像档?原错误:%{e}" 10 | -------------------------------------------------------------------------------- /config/locales/carrierwave.zh-TW.yml: -------------------------------------------------------------------------------- 1 | "zh-TW": 2 | errors: 3 | messages: 4 | carrierwave_processing_error: 處理錯誤 5 | carrierwave_integrity_error: 格式不是可上載的類型 6 | extension_white_list_error: "不可上載 %{extension} 檔案,可以上載的檔案類型為:%{allowed_types}" 7 | rmagick_processing_error: "用 rmagick 處理影像檔時發生錯誤,也許上載的不是影像檔?原錯誤訊息:%{e}" 8 | mime_types_processing_error: "處理檔案 MIME::Types 時發生錯誤,也許上載的檔案不是正確的 content-type ?原錯誤:%{e}" 9 | mini_magick_processing_error: "使用 MiniMagick 處理影像時發生錯誤,也許上載的不是影像檔?原錯誤:%{e}" 10 | -------------------------------------------------------------------------------- /app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class ApplicationController < ::ApplicationController 5 | layout "admin" 6 | 7 | before_action :authenticate_user! 8 | before_action :require_admin 9 | before_action :set_active_menu 10 | 11 | def require_admin 12 | render_404 unless current_user.admin? 13 | end 14 | 15 | def set_active_menu 16 | @current = ["/" + ["admin", controller_name].join("/")] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/admin/photos/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.photos")%> <%= t("admin.check_photo")%> 3 | <% end %> 4 |

    <%= notice %>

    5 | 6 |

    7 | User: 8 | <%= @photo.user.login %> 9 |

    10 |

    11 | <%= image_tag(@photo.image.url) %> 12 |

    13 | 14 | 15 | <%= link_to 'Edit', edit_admin_photo_path(@photo) %> | 16 | <%= link_to 'Back', admin_photos_path %> 17 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    你好 <%= @resource.fullname %>

    2 |

    有人请求找回在 <%= link_to Setting.app_name, root_url %> 上面,账号为 <%= @resource.login %> 的密码,如果你是,那么你可以通过下面的连接进入网站修改密码。

    3 |

    <%= link_to "修改我的密码", edit_password_url(@resource, reset_password_token: @token), class: 'btn' %>

    4 |

    如果看不到连接,请复制下面的连接然后在浏览器里面打开:

    5 |

    <%= edit_password_url(@resource, reset_password_token: @token) %>

    6 |

    如果你没有申请,可以忽略这封邮件,你的账号不会受到影响。

    7 | -------------------------------------------------------------------------------- /app/views/notifications/_topic_reply.html.erb: -------------------------------------------------------------------------------- 1 | <% reply = notification.target %> 2 | <% if reply.blank? %> 3 |
    相关信息已删除
    4 | <% else %> 5 | <% 6 | topic = notification.second_target || reply.topic 7 | %> 8 | 9 |
    10 | 在帖子 11 | <%= topic_title_tag(topic, reply: reply) %> 回复了: 12 |
    13 | <%- if reply.present? -%> 14 |
    15 | <%= reply.body_html %> 16 |
    17 | <%- end -%> 18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/teams/index.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t('menu.teams')%> 2 | 3 |
    4 |
    5 |
    6 | <%= t("users.hot_teams") %> 7 |
    <%= t("users.current_have") %> <%= @total_team_count %> <%= t("users.teams_joined") %> <%= Setting.app_name %>
    8 |
    9 |
    10 | <%= render "team_list", teams: @active_teams %> 11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /app/controllers/nodes_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NodesController < ApplicationController 4 | before_action :authenticate_user!, only: %i[block unblock] 5 | 6 | def index 7 | @nodes = Node.all 8 | render json: @nodes, only: [:name], methods: [:id] 9 | end 10 | 11 | def block 12 | current_user.block_node(params[:id]) 13 | render json: { code: 0 } 14 | end 15 | 16 | def unblock 17 | current_user.unblock_node(params[:id]) 18 | render json: { code: 0 } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/device.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Device < ApplicationRecord 4 | belongs_to :user 5 | 6 | enum platform: %i[ios android] 7 | 8 | validates :platform, :token, presence: true 9 | validates :token, uniqueness: { scope: %i[user_id platform] } 10 | 11 | def alive? 12 | return true if last_actived_at.blank? 13 | (Date.current - last_actived_at.to_date).to_i <= 14 14 | end 15 | 16 | def platform_name 17 | @platform_name ||= I18n.t "device_platform.#{self.platform}" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t('users.hot_users') %> 2 | 3 |
    4 |
    5 |
    6 | <%= t("users.hot_users") %> 7 |
    <%= t("users.current_have") %> <%= @total_user_count %> <%= t("users.users_joined") %> <%= Setting.app_name %>
    8 |
    9 |
    10 | <%= render "user_list", users: @active_users, columns: 4 %> 11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/show.html.erb: -------------------------------------------------------------------------------- 1 | <%%= render 'base' %> 2 | 3 |
    4 | <%%= link_to '修改', edit_<%= singular_table_name %>_path(@<%= file_name %>), class: "btn btn-sm btn-success" %> 5 | <%%= link_to '返回', <%= index_helper %>_path, class: "btn btn-sm btn-default" %> 6 |
    7 | 8 |
    9 | <% for attribute in attributes -%> 10 |

    11 | <%= attribute.human_name %>: 12 | <%%= @<%= file_name %>.<%= attribute.name %> %> 13 |

    14 | <% end -%> 15 |
    -------------------------------------------------------------------------------- /public/api-doc/frames.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ruby China API 文档 6 | 7 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # NAME: homeland/homeland 2 | FROM homeland/base:1.0.1 3 | MAINTAINER Jason Lee "https://github.com/huacnlee" 4 | 5 | ENV RAILS_ENV 'production' 6 | 7 | ENV HOMELAND_VERSION 'master' 8 | 9 | RUN useradd ruby -s /bin/bash -m -U &&\ 10 | mkdir -p /var/www &&\ 11 | cd /var/www 12 | ADD . /var/www/homeland 13 | RUN cd /var/www/homeland && bundle install --deployment &&\ 14 | find /var/www/homeland/vendor/bundle -name tmp -type d -exec rm -rf {} + &&\ 15 | chown -R ruby:ruby /var/www 16 | 17 | WORKDIR /var/www/homeland 18 | -------------------------------------------------------------------------------- /Benchmarks.md: -------------------------------------------------------------------------------- 1 | # 页面响应时间记录 2 | 3 | 要点,所有统计都得基于有 cache 的情况,生产环境。 4 | 5 | ## 2015-5-28 6 | 7 | - / = 36ms 8 | - /topics = 45ms 9 | - /topics/19436 = 67ms 10 | - /wiki = 26ms 11 | - /notifications = 39ms 12 | - /huacnlee = 100ms 13 | - /huacnlee/topics = 95ms 14 | - /huacnlee/favorites = 50ms 15 | - /huacnlee/followers = 85ms 16 | - /wiki/about = 37ms 17 | - /jobs = 68ms 18 | - /topics/new = 84ms 19 | - /sites = 25ms 20 | - /account/edit = 73ms -------------------------------------------------------------------------------- /app/controllers/admin/photos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class PhotosController < Admin::ApplicationController 5 | before_action :set_photo, only: %i[show destroy] 6 | 7 | def index 8 | @photos = Photo.recent.includes(:user).page(params[:page]) 9 | end 10 | 11 | def destroy 12 | @photo.destroy 13 | redirect_to(admin_photos_url) 14 | end 15 | 16 | private 17 | 18 | def set_photo 19 | @photo = Photo.find(params[:id]) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20160707084438_create_team_users_join_table.rb: -------------------------------------------------------------------------------- 1 | class CreateTeamUsersJoinTable < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :type, :string, limit: 20, after: :id 4 | 5 | create_table(:team_users) do |t| 6 | t.integer :team_id, index: true, null: false 7 | t.integer :user_id, index: true, null: false 8 | t.integer :role 9 | t.integer :status 10 | 11 | t.timestamps null: false 12 | end 13 | 14 | add_index :team_users, [:team_id, :user_id], unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/teams/_team_list.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% teams.each do |team| %> 3 |
    4 |
    5 |
    <%= user_avatar_tag(team, :md) %>
    6 |
    7 |
    <%= user_name_tag(team) %>
    8 |
    9 |
    <%= team_member_counts_tag(team) %>
    10 |
    11 |
    12 |
    13 |
    14 | <% end %> 15 |
    16 | -------------------------------------------------------------------------------- /spec/models/notification_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Notification, type: :model do 6 | let(:n) { create(:notification_topic_reply) } 7 | 8 | describe ".realtime_push_to_client" do 9 | it "should work" do 10 | hash = {} 11 | hash[:count] = n.user.notifications.unread.count 12 | args = ["notifications_count/#{n.user_id}", hash] 13 | expect(ActionCable.server).to receive(:broadcast).with(*args).once 14 | n.realtime_push_to_client 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/home/markdown.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag 'Markdown 教程' %> 2 | 3 | <% cache(['markdown-guide', Digest::MD5.hexdigest(Homeland::Markdown.example)]) do %> 4 | 5 |
    6 |
    7 |

    Markdown 教程

    8 |
    9 |
    10 |
    <%= Homeland::Markdown.example %>
    11 |
    12 |
    13 | <%= markdown(Homeland::Markdown.example)%> 14 |
    15 |
    16 |
    17 |
    18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/users/_user_list.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% users.each do |user| %> 3 |
    4 |
    5 |
    <%= user_avatar_tag(user, :md) %>
    6 |
    7 |
    <%= user_name_tag(user) %>
    8 |
    9 |
    <%= follow_user_tag(user, class: "") %>
    10 |
    11 |
    12 |
    13 |
    14 | <% end %> 15 |
    16 | -------------------------------------------------------------------------------- /app/models/user/followable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User 4 | module Followable 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | action_store :follow, :topic 9 | action_store :follow, :user, counter_cache: "followers_count", 10 | user_counter_cache: "following_count" 11 | end 12 | 13 | def follow_user(user) 14 | return unless user 15 | self.create_action(:follow, target: user) 16 | Notification.notify_follow(user.id, self.id) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/helpers/locations_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe LocationsHelper, type: :helper do 6 | it "should location_name_tag work with string" do 7 | expect(helper.location_name_tag("chengdu")).to eq(link_to("chengdu", location_users_path("chengdu"))) 8 | end 9 | 10 | it "should location_name_tag work with Location instance" do 11 | location = create(:location) 12 | expect(helper.location_name_tag(location)).to eq(link_to(location.name, location_users_path(location.name))) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/teams/_sidebar.html.erb: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /app/views/admin/sections/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin, @section] do |f| %> 2 | <%= render 'shared/error_messages', target: @section %> 3 |
    4 | <%= f.label :name %> 5 | <%= f.text_field :name, class: "form-control" %> 6 |
    7 | 8 |
    9 | <%= f.label :name %> 10 | <%= f.text_field :sort, class: "form-control" %> 11 |
    12 | 13 |
    14 | <%= f.submit t("common.save"), class: "btn btn-primary", 'data-disable-with' => t("common.saving") %> 15 |
    16 | <% end %> 17 | -------------------------------------------------------------------------------- /db/migrate/20160119191013_add_indexes_to_table.rb: -------------------------------------------------------------------------------- 1 | class AddIndexesToTable < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :topics, :deleted_at 4 | add_index :topics, [:node_id, :deleted_at] 5 | 6 | add_index :replies, :deleted_at 7 | add_index :replies, [:topic_id, :deleted_at] 8 | 9 | add_index :sites, :deleted_at 10 | add_index :sites, [:site_node_id, :deleted_at] 11 | 12 | remove_index :users, :location 13 | add_index :users, :location 14 | 15 | add_index :nodes, :sort 16 | 17 | add_index :photos, :user_id 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/homeland/pipeline/twemoji_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Pipeline 5 | class TwemojiFilter < HTML::Pipeline::Filter 6 | def call 7 | doc.xpath(".//text()").each do |node| 8 | content = node.to_html 9 | next unless content.include?(":") 10 | next if has_ancestor?(node, %w[pre code]) 11 | 12 | html = Twemoji.parse(content) 13 | 14 | next if html == content 15 | node.replace(html) 16 | end 17 | doc 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/photos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PhotosController < ApplicationController 4 | load_and_authorize_resource 5 | 6 | def create 7 | # 浮动窗口上传 8 | @photo = Photo.new 9 | @photo.image = params[:file] 10 | if @photo.image.blank? 11 | render json: { ok: false }, status: 400 12 | return 13 | end 14 | 15 | @photo.user_id = current_user.id 16 | if @photo.save 17 | render json: { ok: true, url: @photo.image.url(:large) } 18 | else 19 | render json: { ok: false } 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/concerns/soft_delete.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SoftDelete 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | default_scope -> { where(deleted_at: nil) } 8 | 9 | alias_method :destroy!, :destroy 10 | end 11 | 12 | def destroy 13 | run_callbacks(:destroy) do 14 | if persisted? 15 | t = Time.now.utc 16 | update_columns(deleted_at: t, updated_at: t) 17 | end 18 | 19 | @destroyed = true 20 | end 21 | freeze 22 | end 23 | 24 | def deleted? 25 | deleted_at.present? 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This file loads spring without using Bundler, in order to be fast. 5 | # It gets overwritten when you run the `spring binstub` command. 6 | 7 | unless defined?(Spring) 8 | require "rubygems" 9 | require "bundler" 10 | 11 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 12 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } 13 | gem "spring", match[1] 14 | require "spring/binstub" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/api/v3/application/_node.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @class NodeSerializer 4 | # 节点 5 | # 6 | # == attributes 7 | # - *id* [Integer] 编号 8 | # - *name* [String] 节点名称 9 | # - *summary* [String] 简介, Markdown 格式 10 | # - *section_id* [Integer] 大类别编号 11 | # - *section_name* [String] 大类别名称 12 | # - *topics_count* [Integer] 话题数量 13 | # - *sort* {Integer} 排序优先级 14 | # - *updated_at* [DateTime] 更新时间 15 | if node 16 | json.cache! ["v1", node] do 17 | json.(node, :id, :name, :topics_count, :summary, :section_id, :sort, :section_name, :updated_at) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/users/blocked.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [@user.fullname, t('users.menu.blocked')].join(' · ') %> 2 | 3 |
    4 | <%= render "sidebar", user: @user %> 5 |
    6 | <%= render "menu" %> 7 |
    8 |
    <%= t('users.menu.blocked') %>
    9 |
    10 | <%= render "user_list", users: @block_users %> 11 |
    12 | 15 |
    16 |
    17 |
    18 | -------------------------------------------------------------------------------- /spec/controllers/admin/home_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Admin::HomeController, type: :controller do 6 | let(:user) { create :user } 7 | let(:admin) { create :admin } 8 | describe "Admin requirement" do 9 | it "should open with admin user" do 10 | sign_in admin 11 | get :index 12 | expect(response.status).to eq 200 13 | end 14 | 15 | it "should 404 with non admin user" do 16 | sign_in user 17 | get :index 18 | expect(response.status).to eq 404 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/closeable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 开启关闭帖子功能 4 | module Closeable 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | end 9 | 10 | def closed? 11 | closed_at.present? 12 | end 13 | 14 | def close! 15 | transaction do 16 | Reply.create_system_event!(action: "close", topic_id: self.id) 17 | update!(closed_at: Time.now) 18 | end 19 | end 20 | 21 | def open! 22 | transaction do 23 | update!(closed_at: nil) 24 | Reply.create_system_event!(action: "reopen", topic_id: self.id) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/users/_replies.html.erb: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /app/views/team_users/index.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t('teams.users') %> 2 | 3 | <%= render '/teams/header' %> 4 | 5 |
    6 | <% if can? :update, @team %> 7 |
    <%= link_to t('teams.new_team_user'), new_user_team_user_path(@team), class: 'btn btn-default' %>
    8 | <% end %> 9 |
    10 | 11 | <%= render partial: '/team_users/team_user', collection: @team_users %> 12 |
    13 |
    14 | 17 |
    18 | -------------------------------------------------------------------------------- /app/views/topics/_node_selector.html.erb: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /config/database.yml.default: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | pool: 64 4 | timeout: 5000 5 | encoding: utf-8 6 | 7 | # 8 | # PRODUCTION 9 | # 10 | production: 11 | <<: *default 12 | database: homeland 13 | 14 | # 15 | # Development specific 16 | # 17 | development: 18 | <<: *default 19 | database: homeland-dev 20 | 21 | # Warning: The database defined as "test" will be erased and 22 | # re-generated from your development database when you run "rake". 23 | # Do not set this db to the same as development or production. 24 | test: 25 | <<: *default 26 | database: homeland-test 27 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # ============ init Section, Node ================ 2 | s1 = Section.create(name: 'Share') 3 | Node.create(name: 'Fun', summary: '...', section_id: s1.id) 4 | Node.create(name: 'Movie', summary: '...', section_id: s1.id) 5 | Node.create(name: 'Music', summary: '...', section_id: s1.id) 6 | s2 = Section.create(name: 'Geek') 7 | Node.create(name: 'Apple', summary: '...', section_id: s2.id) 8 | Node.create(name: 'Google', summary: '...', section_id: s2.id) 9 | Node.create(name: 'Coding', summary: '...', section_id: s2.id) 10 | Node.create(name: 'PlayStation / XBox', summary: '...', section_id: s2.id) 11 | -------------------------------------------------------------------------------- /spec/models/team_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe TeamUser, type: :model do 6 | let(:user) { create(:user) } 7 | 8 | describe "Create via login" do 9 | it "should work" do 10 | team_user = build(:team_user, login: user.login) 11 | expect(team_user.save).to eq true 12 | expect(team_user.user_id).to eq user.id 13 | end 14 | 15 | it "should add error when user not exists" do 16 | team_user = build(:team_user, login: "lasdjgalksdjgsad") 17 | expect(team_user.valid?).to eq false 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/reply/voteable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Reply 4 | module Voteable 5 | extend ActiveSupport::Concern 6 | 7 | UPVOTES = %w[+1 :+1: :thumbsup: :plus1: 👍 👍🏻 👍🏼 👍🏽 👍🏾 👍🏿] 8 | 9 | included do 10 | after_commit :check_vote_chars_for_like_topic, on: :create, unless: -> { system_event? } 11 | end 12 | 13 | def upvote? 14 | (body || "").strip.start_with?(*UPVOTES) 15 | end 16 | 17 | private 18 | def check_vote_chars_for_like_topic 19 | return unless upvote? 20 | user.like(topic) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /db/migrate/20170207141208_remove_array_action_fields.rb: -------------------------------------------------------------------------------- 1 | class RemoveArrayActionFields < ActiveRecord::Migration[5.0] 2 | def change 3 | # Remove old Array fields, they were instead by ActionStore 4 | # ref: https://github.com/ruby-china/homeland/pull/857 5 | remove_column :users, :following_ids 6 | remove_column :users, :blocked_user_ids 7 | remove_column :users, :blocked_node_ids 8 | remove_column :users, :favorite_topic_ids 9 | 10 | remove_column :topics, :liked_user_ids 11 | remove_column :topics, :follower_ids 12 | 13 | remove_column :replies, :liked_user_ids 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/search/_page.html.erb: -------------------------------------------------------------------------------- 1 | <% if Setting.has_module?(:wiki) %> 2 | <% 3 | title = hit.highlight.title.try(:first) || item.title 4 | %> 5 |
    6 |
    <%= link_to highlight(title), homeland_wiki.page_path(item) %> Wiki
    7 |
    8 | <%= link_to homeland_wiki.page_url(item), homeland_wiki.page_path(item) %> 9 | <%= item.updated_at.to_date %> 10 |
    11 |
    <%= highlight(hit.highlight.body.try(:first)) %>
    12 |
    13 | <% end %> 14 | -------------------------------------------------------------------------------- /app/views/users/city.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag params[:id] %> 2 | 3 |
    4 |
    5 |
    <%= params[:id] %>的会员
    6 |
    7 | <% @users.each do |item| %> 8 |
    9 |
    <%= user_avatar_tag(item, :md) %>
    10 |
    <%= user_name_tag(item) %>
    11 |
    12 | <% end %> 13 |
    14 | 17 |
    18 |
    19 | -------------------------------------------------------------------------------- /app/views/comments/create.js.erb: -------------------------------------------------------------------------------- 1 | App.loading(false); 2 | comments_box = $("#<%= @comment.commentable_type %>_<%= @comment.commentable_id %>_cell_comments .card-body"); 3 | comments_form = $("#<%= @comment.commentable_type %>_<%= @comment.commentable_id %>_cell_new_comment"); 4 | <% if @success %> 5 | comment_line = '<%= j(render("comment", item: @comment)) %>'; 6 | comments_box.append(comment_line); 7 | $(comment_line).fadeOut("fast"); 8 | comments_box.find('.no-result').remove(); 9 | $("abbr.timeago").timeago(); 10 | $("textarea",comments_form).val("").focus(); 11 | <% else %> 12 | $("textarea",comments_form).focus(); 13 | <% end %> 14 | -------------------------------------------------------------------------------- /config/puma.example.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | app_root = "/var/www/homeland" 4 | pidfile "#{app_root}/tmp/pids/puma.pid" 5 | state_path "#{app_root}/tmp/pids/puma.state" 6 | stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true 7 | bind "unix:/tmp/homeland.puma.sock" 8 | daemonize true 9 | port 7000 10 | workers 4 11 | threads 8, 16 12 | preload_app! 13 | 14 | on_worker_boot do 15 | ActiveSupport.on_load(:active_record) do 16 | ActiveRecord::Base.establish_connection 17 | end 18 | end 19 | 20 | before_fork do 21 | ActiveRecord::Base.connection_pool.disconnect! 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/api/v3/nodes_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | module V3 5 | class NodesController < Api::V3::ApplicationController 6 | # 获取 Nodes 列表 7 | # 8 | # GET /api/v3/nodes 9 | # @return [Array] 10 | def index 11 | @nodes = Node.includes(:section).all 12 | @meta = { total: Node.count } 13 | end 14 | 15 | ## 16 | # 获取单个 Node 详情 17 | # 18 | # GET /api/v3/nodes/:id 19 | # @return [NodeSerializer] 20 | def show 21 | @node = Node.find(params[:id]) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/admin/replies/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin,@reply] do |f| %> 2 | <%= render 'shared/error_messages', target: @reply %> 3 | 4 |
    5 | <%= f.label :body %> 6 | <%= f.text_area :body, class: "form-control" %> 7 |
    8 | 9 |
    10 | <%= f.label :user_id %> 11 | <%= f.text_field :user_id, class: "form-control" %> 12 |
    13 | 14 |
    15 | <%= f.submit t("common.save"), class: "btn btn-primary", 'data-disable-with' => t("common.saving") %> 16 | or 17 | <%= link_to 'Back', admin_replies_path %> 18 |
    19 | <% end %> 20 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag 'Resend confirmation instructions' %> 2 | 3 |

    Resend confirmation instructions

    4 | 5 | <%= form_for resource, as: resource_name, url: confirmation_path(resource_name), method: :post do |f| %> 6 | <%= f.error_notification %> 7 | <%= f.full_error :confirmation_token %> 8 | 9 |
    10 | <%= f.label :email %> 11 | <%= f.text_field :email, class: "form-control" %> 12 |
    13 | 14 |
    15 | <%= f.button :submit, "Resend confirmation instructions" %> 16 |
    17 | <% end %> 18 | 19 | <%= render "devise/shared/links" %> 20 | -------------------------------------------------------------------------------- /config/initializers/exception-track.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "exception_notification/sidekiq" 4 | 5 | ExceptionTrack.configure do 6 | # self.environments = %i(production) 7 | end 8 | 9 | ExceptionNotification.configure do |config| 10 | config.ignored_exceptions += %w[ 11 | ActionView::TemplateError 12 | ActionController::InvalidAuthenticityToken 13 | ActionController::BadRequest 14 | ActionView::MissingTemplate 15 | ActionController::UrlGenerationError 16 | ActionController::UnknownFormat 17 | ActionController::InvalidCrossOriginRequest 18 | ActionController::ParameterMissing 19 | ] 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/redis.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "redis" 4 | require "redis-namespace" 5 | require "redis/objects" 6 | 7 | redis_config = Rails.application.config_for(:redis) 8 | 9 | $redis = Redis.new(host: redis_config["host"], port: redis_config["port"]) 10 | $redis.select(0) 11 | Redis::Objects.redis = $redis 12 | 13 | sidekiq_url = "redis://#{redis_config['host']}:#{redis_config['port']}/0" 14 | Sidekiq.configure_server do |config| 15 | config.redis = { namespace: "sidekiq", url: sidekiq_url } 16 | end 17 | Sidekiq.configure_client do |config| 18 | config.redis = { namespace: "sidekiq", url: sidekiq_url } 19 | end 20 | -------------------------------------------------------------------------------- /app/views/team_users/_team_user.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= user_avatar_tag(team_user.user, :sm, link: false) %> 3 | 4 | <%= user_name_tag(team_user.user) %> 5 | <%= team_user&.user&.name %> 6 | 7 | 8 | <%= team_user.pendding? ? team_user.status_name : team_user.role_name %> 9 | 10 | 11 | <% if can?(:update, @team) && team_user.user_id != current_user&.id %> 12 | <%= link_to icon_tag('pencil'), edit_user_team_user_path(@team, team_user) %> 13 | <% end %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /config/locales/carrierwave.en.yml: -------------------------------------------------------------------------------- 1 | "en": 2 | errors: 3 | messages: 4 | carrierwave_processing_error: failed to be processed 5 | carrierwave_integrity_error: is not of an allowed file type 6 | extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" 7 | rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?" 8 | mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" 9 | mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" -------------------------------------------------------------------------------- /app/views/api/v3/application/_abilities.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @class BaseSerializer 4 | # @!method abilities 5 | # 当前 accessToken 对应的用户对此数据的权限 6 | # 7 | # @example 表示可修改,不可删除 8 | # 9 | # { update: true, destroy: false } 10 | # 11 | # @return update [Boolean] 当前 accessToken 是否有修改权限 12 | # @return destroy [Boolean] 当前 accessToken 是否有删除权限 13 | json.abilities do 14 | json.update can?(:update, object) 15 | json.destroy can?(:destroy, object) 16 | if object && object.is_a?(Topic) 17 | %i[ban normal excellent unexcellent close open].each do |action| 18 | json.set! action, can?(action, object) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/teams/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render '/teams/header' %> 2 | 3 |
    4 |
    5 |
    6 |
    7 | <% if @topics.blank? %> 8 |
    暂无任何话题
    9 | <% else %> 10 | <%= render partial: '/topics/topic', collection: @topics, locals: { suggest: false }, cached: -> (topic) { [topic, 'normal'] } %> 11 | <% end %> 12 |
    13 | 16 |
    17 |
    18 | 19 | <%= render '/teams/sidebar' %> 20 |
    21 | -------------------------------------------------------------------------------- /app/views/admin/locations/_form.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 3 | <%= form_for [:admin,@location] do |f| %> 4 | <%= render "/shared/error_messages", target: @location %> 5 |
    6 | <%= f.label :name %> 7 | <%= f.text_field :name, class: "form-control" %> 8 |
    9 | 10 |
    11 | <%= f.label :users_count %> 12 | <%= f.text_field :users_count, class: "form-control" %> 13 |
    14 | 15 |
    16 | <%= f.submit t("common.save"), class: "btn btn-primary" %> or <%= link_to t("common.cancel"), admin_locations_path %> 17 |
    18 | <% end %> 19 |
    20 | -------------------------------------------------------------------------------- /app/views/admin/site_configs/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @site_config, url: admin_site_config_path(@site_config.var), method: 'patch' do |f| %> 2 |
    3 | 4 | <%= f.text_area :value, value: Setting.send(@site_config.var), class: 'form-control', rows: 15 %> 5 |
    <%= t("setting.#{@site_config.var}") %>
    6 |
    7 | 8 |
    9 | <%= f.submit t("common.save"), class: "btn btn-primary", 'data-disable-with' => t("common.saving") %> 10 | <%= t("common.cancel") %> 11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/controllers/api/v3/photos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | module V3 5 | class PhotosController < Api::V3::ApplicationController 6 | before_action :doorkeeper_authorize! 7 | 8 | # 上传图片,请使用 Multipart 的方式提交图片文件 9 | # 10 | # POST /api/v3/photos 11 | # 12 | # @param file - 文件信息, [required] 13 | # 14 | # == returns 15 | # - image_url 图片 URL 16 | def create 17 | requires! :file 18 | 19 | @photo = Photo.new 20 | @photo.image = params[:file] 21 | @photo.user_id = current_user.id 22 | @photo.save! 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/users/team_actions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | module TeamActions 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | before_action :set_team, only: [:show] 9 | end 10 | 11 | private 12 | 13 | def team_show 14 | @topics = Topic.where(user_id: @team.user_ids, team_id: [nil, @team.id]) 15 | .fields_for_list 16 | .last_actived.includes(:user) 17 | @topics = @topics.page(params[:page]) 18 | end 19 | 20 | def only_team! 21 | render_404 if @user_type != :team 22 | end 23 | 24 | def set_team 25 | @team = @user 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /config/locales/teams.zh-CN.yml: -------------------------------------------------------------------------------- 1 | 'zh-CN': 2 | team_user_status: 3 | pendding: '等待接受' 4 | acceped: '已加入' 5 | team_user_role: 6 | owner: '组管理员' 7 | member: '成员' 8 | teams: 9 | teams: '组织' 10 | user_existed: '已经在组里面了' 11 | new_team: '创建公司/组织' 12 | edit_team: '设置公司/组织资料' 13 | new_team_user: '邀请成员' 14 | edit_team_user: '修改成员权限' 15 | delete_team_user: '移除成员' 16 | show_team_user: '查看邀请' 17 | accept_team_user: '接受邀请' 18 | reject_team_user: '拒绝' 19 | delete_team_user_confirm: '确定要将他从组里面移除掉么?' 20 | topics: '成员的话题列表' 21 | users: '成员列表' 22 | team_user_pendding: '等待接受' 23 | settings: '设置' 24 | team_users_count: "%{count} 个团队成员" 25 | -------------------------------------------------------------------------------- /config/locales/teams.zh-TW.yml: -------------------------------------------------------------------------------- 1 | 'zh-TW': 2 | team_user_status: 3 | pendding: '等待接受' 4 | acceped: '已加入' 5 | team_user_role: 6 | owner: '團體管理員' 7 | member: '成員' 8 | teams: 9 | teams: '團體' 10 | user_existed: '已經該團體成員' 11 | new_team: '創建公司/團體' 12 | edit_team: '設置公司/團體资料' 13 | new_team_user: '邀請成員' 14 | edit_team_user: '修改成員权限' 15 | delete_team_user: '移除成員' 16 | show_team_user: '查看邀請' 17 | accept_team_user: '接受邀請' 18 | reject_team_user: '拒絕' 19 | delete_team_user_confirm: '確定要將他從團體中移除嗎?' 20 | topics: '成員話題列表' 21 | users: '成員列表' 22 | team_user_pendding: '等待接受' 23 | settings: '設置' 24 | team_users_count: "%{count} 個團體成員" 25 | -------------------------------------------------------------------------------- /app/models/section.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Section < ApplicationRecord 4 | second_level_cache expires_in: 2.weeks 5 | 6 | has_many :nodes, dependent: :destroy 7 | 8 | validates :name, presence: true, uniqueness: true 9 | 10 | default_scope -> { order(sort: :desc) } 11 | 12 | after_save :update_cache_version 13 | after_destroy :update_cache_version 14 | 15 | def update_cache_version 16 | # 记录节点变更时间,用于清除缓存 17 | CacheVersion.section_node_updated_at = Time.now.to_i 18 | end 19 | 20 | def sorted_nodes 21 | nodes.where.sorted 22 | end 23 | 24 | def self.default 25 | @default ||= Section.first || Section.create(name: "分享") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /config/locales/admin.zh-CN.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | admin: 3 | menu: 4 | root_path: "概况" 5 | site_configs: "设置" 6 | users: "用户" 7 | sections: "分类" 8 | nodes: "节点" 9 | wiki: "Wiki" 10 | topics: "话题" 11 | replies: "回复" 12 | photos: "图片" 13 | comments: "评论" 14 | locations: "城市" 15 | exception_logs: "异常" 16 | overview: "概览" 17 | recent_topics: "最近帖子" 18 | statics: "统计信息" 19 | site_configs: 20 | settings: "全局设置" 21 | please_do_not_modify_key: "请勿修改 key" 22 | edit_setting: "修改设置" 23 | users: 24 | trust_user_can_modify_wiki: "信任用户将可以修改 Wiki" 25 | topic_list: "话题列表" 26 | check_photo: "瀏覽圖片" 27 | -------------------------------------------------------------------------------- /config/locales/admin.zh-TW.yml: -------------------------------------------------------------------------------- 1 | "zh-TW": 2 | admin: 3 | menu: 4 | root_path: "概況" 5 | site_configs: "設置" 6 | users: "用戶" 7 | sections: "分類" 8 | nodes: "節點" 9 | wiki: "Wiki" 10 | topics: "帖子" 11 | replies: "回覆" 12 | photos: "圖片" 13 | comments: "評論" 14 | locations: "城市" 15 | exception_logs: "異常" 16 | overview: "概覽" 17 | recent_topics: "最近帖子" 18 | statics: "統計信息" 19 | site_configs: 20 | settings: "設置項" 21 | please_do_not_modify_key: "請勿修改 key" 22 | edit_setting: "修改設置" 23 | users: 24 | trust_user_can_modify_wiki: "信任用戶將可以修改 Wiki" 25 | topic_list: "話題列表" 26 | check_photo: "瀏覽圖片" 27 | -------------------------------------------------------------------------------- /app/views/admin/site_configs/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.site_configs.settings") %> 3 | <% end %> 4 | 5 |

    <%= t("admin.site_configs.settings") %>

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% Setting::KEYS_IN_ADMIN.each do |key| %> 15 | 16 | 17 | 18 | 19 | 20 | <% end %> 21 |
    Key说明
    <%= key %><%= t("setting.#{key}") %><%= link_to icon_tag("pencil"), edit_admin_site_config_path(key) %>
    22 | -------------------------------------------------------------------------------- /db/migrate/20161228064225_remove_unneeded_indexes.rb: -------------------------------------------------------------------------------- 1 | class RemoveUnneededIndexes < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_index :devices, name: "index_devices_on_user_id_and_platform" 4 | remove_index :page_versions, name: "index_page_versions_on_page_id_and_version" 5 | remove_index :replies, name: "index_replies_on_topic_id_and_deleted_at" 6 | remove_index :sites, name: "index_sites_on_site_node_id_and_deleted_at" 7 | remove_index :team_users, name: "index_team_users_on_team_id_and_user_id" 8 | remove_index :topics, name: "index_topics_on_node_id" 9 | remove_index :new_notifications, name: "index_new_notifications_on_user_id_and_notify_type" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/team.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Team < User 4 | has_many :team_users 5 | has_many :users, through: :team_users 6 | 7 | has_many :topics 8 | 9 | attr_accessor :owner_id 10 | after_create do 11 | self.team_users.create(user_id: owner_id, role: :owner, status: :accepted) if self.owner_id.present? 12 | end 13 | 14 | def user_ids 15 | @user_ids ||= self.users.pluck(:id) 16 | end 17 | 18 | def password_required? 19 | false 20 | end 21 | 22 | def owner?(user) 23 | self.team_users.accepted.exists?(role: :owner, user_id: user.id) 24 | end 25 | 26 | def member?(user) 27 | self.team_users.accepted.exists?(user_id: user.id) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/replies/_create_callback.js.erb: -------------------------------------------------------------------------------- 1 | if($("#replies").length == 0){ 2 | Turbolinks.visit(location.href); 3 | } else { 4 | if ($(".reply[data-id=<%= reply.id %>]").length == 0) { 5 | var current_floor = parseInt($("#replies").data("last-floor")) + 1; 6 | var dom = $('<%= j(render("reply", reply: reply, reply_counter: reply.topic.replies_count - 1, display_edit: true)) %>'); 7 | $("#replies .items").append(dom); 8 | $("#replies .total b").text('<%= reply.topic.replies_count %>'); 9 | $('#topic-sidebar .total b').text('<%= reply.topic.replies_count %>'); 10 | dom.addClass('light').find("a.edit").css("display", "inline-block"); 11 | _topicView.itemsUpdated(); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /spec/controllers/nodes_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe NodesController, type: :controller do 6 | let(:node) { create(:node) } 7 | let(:user) { create(:user) } 8 | 9 | it "should have an index action" do 10 | get :index 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "should have an block action" do 15 | sign_in user 16 | post :block, params: { id: node } 17 | expect(response).to have_http_status(200) 18 | end 19 | 20 | it "should have an unblock action" do 21 | sign_in user 22 | post :unblock, params: { id: node } 23 | expect(response).to have_http_status(200) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/topic/auto_correct.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "auto-space" 4 | 5 | class Topic 6 | module AutoCorrect 7 | extend ActiveSupport::Concern 8 | 9 | CORRECT_CHARS = [ 10 | ["[", "["], 11 | ["]", "]"], 12 | ["【", "["], 13 | ["】", "]"], 14 | ["(", "("], 15 | [")", ")"] 16 | ] 17 | 18 | included do 19 | before_save :auto_correct_title 20 | end 21 | 22 | private 23 | def auto_correct_title 24 | return if title.blank? 25 | title.dup 26 | CORRECT_CHARS.each do |chars| 27 | title.gsub!(chars[0], chars[1]) 28 | end 29 | title.auto_space! 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/views/admin/locations/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'base' %> 2 | 3 |
    4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% @locations.each do |item| %> 12 | "> 13 | 14 | 15 | 16 | 19 | 20 | <% end %> 21 |
    #地名会员数
    <%= item.id %><%= item.name %><%= item.users_count %> 17 | <%= link_to "", edit_admin_location_path(item.id), class: "fa fa-pencil" %> 18 |
    22 | <%= paginate @locations %> 23 |
    24 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Avoid CORS issues when API is called from the frontend app. 6 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 7 | 8 | # Read more: https://github.com/cyu/rack-cors 9 | 10 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 11 | allow do 12 | origins "*" 13 | resource "/api/*", headers: :any, methods: %i[get post put delete destroy] 14 | resource "/oauth/*", headers: :any, methods: %i[get post put delete destroy] 15 | end 16 | end 17 | 18 | Rails.application.config.middleware.insert_before 0, Rack::UTF8Sanitizer 19 | -------------------------------------------------------------------------------- /app/controllers/admin/locations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class LocationsController < Admin::ApplicationController 5 | before_action :set_location, only: %i[show edit update destroy] 6 | 7 | def index 8 | @locations = Location.hot.page(params[:page]) 9 | end 10 | 11 | def edit 12 | end 13 | 14 | def update 15 | if @location.update(params[:location].permit!) 16 | redirect_to(admin_locations_path, notice: "Location 更新成功。") 17 | else 18 | render action: "edit" 19 | end 20 | end 21 | 22 | private 23 | 24 | def set_location 25 | @location = Location.find(params[:id]) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PasswordsController < Devise::PasswordsController 4 | before_action :require_no_sso! 5 | 6 | def create 7 | self.resource = resource_class.find_or_initialize_with_errors(Devise.reset_password_keys, resource_params, :not_found) 8 | if self.resource.persisted? && verify_rucaptcha?(resource) 9 | self.resource.send_reset_password_instructions 10 | end 11 | 12 | yield resource if block_given? 13 | 14 | if successfully_sent?(resource) 15 | respond_with({}, { location: after_sending_reset_password_instructions_path_for(resource_name) }) 16 | else 17 | respond_with(resource) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/admin/stats_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class StatsController < Admin::ApplicationController 5 | # GET /stats 6 | # params: 7 | # model - Model 名称 8 | # by - day, week, month 9 | def index 10 | result = { model: params[:model] } 11 | result[:count] = klass.unscoped.count 12 | result[:week_count] = klass.unscoped.where("created_at >= ?", Date.today.beginning_of_week).count 13 | result[:month_count] = klass.unscoped.where("created_at >= ?", Date.today.beginning_of_month).count 14 | render json: result.as_json 15 | end 16 | 17 | def klass 18 | params[:model].camelize.safe_constantize 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/jobs/search_indexer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SearchIndexer < ApplicationJob 4 | queue_as :search_indexer 5 | 6 | def perform(operation, type, id) 7 | obj = nil 8 | 9 | case type.downcase 10 | when "topic" 11 | obj = Topic.find_by_id(id) 12 | when "page" 13 | obj = Page.find_by_id(id) 14 | when "user" 15 | obj = User.find_by_id(id) 16 | end 17 | 18 | return false unless obj 19 | 20 | if operation == "update" 21 | obj.__elasticsearch__.update_document 22 | elsif operation == "delete" 23 | obj.__elasticsearch__.delete_document 24 | elsif operation == "index" 25 | obj.__elasticsearch__.index_document 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/models/concerns/searchable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Searchable 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | include Elasticsearch::Model 8 | 9 | after_commit on: :create do 10 | SearchIndexer.perform_later("index", self.class.name, self.id) 11 | end 12 | 13 | after_update do 14 | need_update = false 15 | if self.respond_to?(:indexed_changed?) 16 | need_update = indexed_changed? 17 | end 18 | 19 | SearchIndexer.perform_later("index", self.class.name, self.id) if need_update 20 | end 21 | 22 | after_commit on: :destroy do 23 | SearchIndexer.perform_later("delete", self.class.name, self.id) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/topics/feed.builder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | xml.instruct! :xml, version: "1.0" 4 | xml.rss(version: "2.0") do 5 | xml.channel do 6 | xml.title t("rss.recent_topics_title", name: Setting.app_name) 7 | xml.link root_url 8 | xml.description(t("rss.recent_topics_description", name: Setting.app_name)) 9 | xml.language("en-us") 10 | @topics.each do |topic| 11 | xml.item do 12 | xml.title topic.title 13 | xml.description markdown(topic.body) 14 | xml.author topic.user.login 15 | xml.pubDate(topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z")) 16 | xml.link topic_url topic 17 | xml.guid topic_url topic 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/topics/_sidebar_box_node_recent_topics.html.erb: -------------------------------------------------------------------------------- 1 | <% cache(["sidebar_for_node_recent_topics", topic.node_id, Time.now.strftime("%Y-%m-%d %H")]) do %> 2 | <% 3 | limit = [[topic.replies_count, 1].max, 10].min 4 | topics = Topic.where(:id.ne => topic.id, :node_id => topic.node_id).recent.limit(limit) 5 | %> 6 | 7 | <% if topics.present? %> 8 |
    9 |
    <%= t("topics.node_recent_topics") %>
    10 | 15 |
    16 | <% end %> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /app/jobs/mention_topic_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 在被提及的话题里面创建 mention 回复,连接到提及的话题 4 | class MentionTopicJob < ApplicationJob 5 | queue_as :notifications 6 | 7 | def perform(topic_ids, target_type:, target_id:, user_id:) 8 | return if topic_ids.blank? 9 | 10 | topics = Topic.where(id: topic_ids) 11 | 12 | topics.each do |topic| 13 | next if topic.replies.where(target_type: target_type, target_id: target_id).any? 14 | 15 | reply_param = { 16 | action: "mention", 17 | body: "", 18 | topic: topic, 19 | target_type: target_type, 20 | target_id: target_id, 21 | user_id: user_id 22 | } 23 | 24 | Reply.create!(reply_param) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20170204090209_create_actions.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from action_store (originally 20170204035500) 2 | class CreateActions < ActiveRecord::Migration[5.0] 3 | def change 4 | create_table :actions do |t| 5 | t.string :action_type, null: false 6 | t.string :action_option 7 | t.string :target_type 8 | t.integer :target_id 9 | t.string :user_type 10 | t.integer :user_id 11 | 12 | t.timestamps 13 | end 14 | 15 | add_index :actions, [:user_type, :user_id, :action_type] 16 | add_index :actions, [:target_type, :target_id, :action_type] 17 | 18 | add_column :users, :followers_count, :integer, default: 0 19 | add_column :users, :following_count, :integer, default: 0 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/api/v3/root_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | module V3 5 | class RootController < Api::V3::ApplicationController 6 | before_action :doorkeeper_authorize!, only: [:hello] 7 | 8 | def not_found 9 | raise ActiveRecord::RecordNotFound 10 | end 11 | 12 | # 简单的 API 测试接口,需要验证,便于快速测试 OAuth 以及其他 API 的基本格式是否正确 13 | # 14 | # GET /api/v3/hello 15 | # 16 | # @param limit - API token 17 | # @return [UserDetailSerializer] 18 | def hello 19 | optional! :limit, values: 0..100 20 | 21 | @meta = { time: Time.now } 22 | @user = current_user 23 | 24 | render "api/v3/users/show" 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/topics/node_feed.builder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | xml.instruct! :xml, version: "1.0" 4 | xml.rss version: "2.0" do 5 | xml.channel do 6 | xml.title t("rss.recent_node_topics_title", name: Setting.app_name, node_name: @node.name) 7 | xml.link root_url 8 | xml.description t("rss.recent_node_topics_description", name: Setting.app_name, node_name: @node.name) 9 | @topics.each do |topic| 10 | xml.item do 11 | xml.title topic.title 12 | xml.description markdown(topic.body) 13 | xml.author topic.user.login 14 | xml.pubDate topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z") 15 | xml.link topic_url(topic) 16 | xml.guid topic_url(topic) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/device_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe Device, type: :model do 6 | let(:device) { create :device } 7 | 8 | describe ".alive?" do 9 | context "more that 2 weeks" do 10 | let(:device) { create :device, last_actived_at: 3.weeks.ago } 11 | it { expect(device.alive?).to eq false } 12 | end 13 | 14 | context "last_actived_at nil" do 15 | let(:device) { create :device, last_actived_at: nil } 16 | it { expect(device.alive?).to eq true } 17 | end 18 | 19 | context "last_actived_at less than 14 days" do 20 | let(:device) { create :device, last_actived_at: 14.days.ago } 21 | it { expect(device.alive?).to eq true } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/location.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Location < ApplicationRecord 4 | second_level_cache expires_in: 2.weeks 5 | 6 | has_many :users 7 | 8 | scope :hot, -> { order(users_count: :desc) } 9 | 10 | validates :name, presence: true, uniqueness: { case_sensitive: false } 11 | 12 | before_save { |loc| loc.name = loc.name.downcase.strip } 13 | 14 | def self.location_find_by_name(name) 15 | return nil if name.blank? 16 | name = name.downcase.strip 17 | where("name ~* ?", name).first 18 | end 19 | 20 | def self.location_find_or_create_by_name(name) 21 | name = name.strip 22 | unless (location = location_find_by_name(name)) 23 | location = create(name: name) 24 | end 25 | location 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/homeland/pipeline/floor_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Pipeline 5 | class FloorFilter < HTML::Pipeline::Filter 6 | FLOOR_REGEXP = /#(\d+)([楼樓Ff])/ 7 | 8 | def call 9 | doc.search(".//text()").each do |node| 10 | content = node.to_html 11 | next unless content.include?("#") 12 | next if has_ancestor?(node, %w[pre code]) 13 | 14 | content.gsub!(FLOOR_REGEXP) do 15 | %(##{Regexp.last_match(1)}#{Regexp.last_match(2)}) 16 | end 17 | 18 | node.replace(content) 19 | end 20 | doc 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | node_modules/ 3 | tmp/ 4 | coverage/ 5 | db/*.sqlite3 6 | tmp/ 7 | log/*.log 8 | log/*.log* 9 | tmp/**/* 10 | .sass-cache/ 11 | .yardoc/ 12 | public/assets/ 13 | public/packs/ 14 | config/database.yml 15 | config/mongoid.yml 16 | config/config.yml 17 | config/thin.yml 18 | config/redis.yml 19 | config/mailer_daemon.yml 20 | config/secrets.yml 21 | public/photo/**/* 22 | public/user/**/* 23 | public/uploads/**/* 24 | public/system/**/* 25 | public/topics 26 | public/topics/**/* 27 | public/doc 28 | .redcar 29 | .DS_Store 30 | *.swp 31 | *.swo 32 | *.sublime-workspace 33 | .rvmrc 34 | vendor/ruby 35 | doc/wiki_repo 36 | solr/data/ 37 | solr/pids/ 38 | tags 39 | chromedriver.log 40 | .idea/ 41 | spec/examples.txt 42 | .vagrant 43 | .byebug_history 44 | .ruby-version 45 | /storage/* 46 | -------------------------------------------------------------------------------- /app/views/users/_topics.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <% topics.each do |topic| %> 8 | <%= 'deleted' if topic.deleted? %>"> 9 | 10 | 11 | 12 | 13 | <% end %> 14 |
    <%=t("common.node")%><%=t("common.title")%><%=t("common.replies_count")%>/<%= t("common.likes_count") %>
    <%= render_node_name(topic.node_name,topic.node_id) %><%= link_to(topic.title, topic_path(topic)) %> <%= topic_excellent_tag(topic) %> <%= timeago(topic.created_at) %><%= topic.replies_count %>/<%= topic.likes_count %>
    15 | -------------------------------------------------------------------------------- /db/migrate/20160329032533_create_notifications.notifications.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from notifications (originally 20160328045436) 2 | class CreateNotifications < ActiveRecord::Migration[4.2] 3 | def change 4 | create_table :new_notifications do |t| 5 | t.integer :user_id, null: false 6 | t.integer :actor_id 7 | t.string :notify_type, null: false 8 | t.string :target_type 9 | t.integer :target_id 10 | t.string :second_target_type 11 | t.integer :second_target_id 12 | t.string :third_target_type 13 | t.integer :third_target_id 14 | t.datetime :read_at 15 | 16 | t.timestamps null: false 17 | end 18 | 19 | add_index :new_notifications, [:user_id, :notify_type] 20 | add_index :new_notifications, [:user_id] 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/views/topics/_reply_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "/shared/editor_toolbar" %> 2 | <%= form_with(model: Reply.new, remote: true, url: topic_replies_path(@topic), id: "new_reply") do |f| %> 3 | 4 |
    5 | <%= f.text_area :body, class: "topic-editor form-control", rows: "4", tabindex: "1" %> 6 |
    7 | <%= f.hidden_field :reply_to_id %> 8 |
    9 | 10 | Command + Enter 11 | 12 | 13 |
    14 | <% end %> 15 | -------------------------------------------------------------------------------- /config/locales/admin.en.yml: -------------------------------------------------------------------------------- 1 | "en": 2 | admin: 3 | menu: 4 | root_path: "Dashboard" 5 | site_configs: "Settings" 6 | users: "Users" 7 | sections: "Sections" 8 | nodes: "Nodes" 9 | wiki: "Wiki" 10 | topics: "Topics" 11 | replies: "Replies" 12 | photos: "Photos" 13 | comments: "Comments" 14 | locations: "Locations" 15 | exception_logs: "Errors" 16 | overview: "Overview" 17 | recent_topics: "Recent Topics" 18 | statics: "Statistics" 19 | site_configs: 20 | settings: "Settings" 21 | please_do_not_modify_key: "Please do not modify key" 22 | edit_setting: "Edit Settings" 23 | users: 24 | trust_user_can_modify_wiki: "Allow user to modify Wiki" 25 | topic_list: "Topic List" 26 | check_photo: "View Photo" 27 | -------------------------------------------------------------------------------- /lib/homeland/pipeline/normalize_mention_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Pipeline 5 | class NormalizeMentionFilter < HTML::Pipeline::TextFilter 6 | PREFIX_REGEXP = %r{(^|[^#{User::LOGIN_FORMAT}!#/\$%&*@@])} 7 | USER_REGEXP = /#{PREFIX_REGEXP}@([#{User::LOGIN_FORMAT}]{1,20})/io 8 | 9 | def call 10 | users = [] 11 | # Makesure clone a new value, not change original value 12 | text = @text.clone.dup 13 | text.gsub!(USER_REGEXP) do 14 | prefix = Regexp.last_match(1) 15 | user = Regexp.last_match(2) 16 | users.push(user) 17 | "#{prefix}@user#{users.size}" 18 | end 19 | result[:normalize_mentions] = users 20 | text 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | form_model_name = "@#{file_name}" 3 | if controller_class_name.include?('::') 4 | namespace = controller_class_name.split('::').first.downcase 5 | form_model_name = "[:#{namespace},@#{file_name}]" 6 | end 7 | %> 8 | <%%= form_for(<%= form_model_name %>) do |f| %> 9 | <%%= render "/shared/error_messages", target: @<%= file_name %> %> 10 | <% for attribute in attributes -%> 11 |
    12 | <%%= f.label :<%= attribute.name %> %> 13 | <%%= f.text_field :<%= attribute.name %>, class: "form-control" %> 14 |
    15 | <% end -%> 16 | 17 |
    18 | <%%= f.submit t("common.save"), class: "btn btn-primary" %> or <%%= link_to t("common.cancel"), <%= index_helper %>_path %> 19 |
    20 | <%% end %> 21 | -------------------------------------------------------------------------------- /app/views/shared/_index_sections.html.erb: -------------------------------------------------------------------------------- 1 | <% cache(["index_locations", CacheVersion.section_node_updated_at]) do %> 2 |
    3 |
    <%= t("common.index_node_navigation")%>
    4 |
    5 |
    6 | <% Section.includes(:nodes).all.each do |section| %> 7 |
    8 | 9 | 10 | <% section.nodes.sorted.each do |node| %> 11 | <%= link_to(node.name, node_topics_path(node), title: node.name, data: { id: node.id })%> 12 | <% end %> 13 | 14 |
    15 | <% end %> 16 |
    17 |
    18 |
    19 | <% end %> 20 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

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

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /db/migrate/20160126061903_create_settings.rb: -------------------------------------------------------------------------------- 1 | class OldSetting < ApplicationRecord 2 | self.table_name = 'site_configs' 3 | end 4 | 5 | class CreateSettings < ActiveRecord::Migration[4.2] 6 | def self.up 7 | create_table :settings do |t| 8 | t.string :var, null: false 9 | t.text :value, null: true 10 | t.integer :thing_id, null: true 11 | t.string :thing_type, null: true, limit: 30 12 | t.timestamps 13 | end 14 | 15 | add_index :settings, %i(thing_type thing_id var), unique: true 16 | 17 | Setting.transaction do 18 | OldSetting.all.each do |item| 19 | Setting.create!(var: item.key, value: item.value) 20 | end 21 | end 22 | 23 | drop_table :site_configs 24 | end 25 | 26 | def self.down 27 | drop_table :settings 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/controllers/devices_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe DevicesController, type: :controller do 6 | let(:user) { create(:user) } 7 | let!(:device) { create(:device, user: user) } 8 | 9 | it "DELETE /devices/:id" do 10 | sign_in user 11 | expect(device.new_record?).to eq false 12 | expect do 13 | delete :destroy, params: { id: device.id } 14 | end.to change(user.devices, :count).by(-1) 15 | expect(response).to redirect_to(oauth_applications_path) 16 | expect(user.devices.where(id: device.id).count).to eq 0 17 | end 18 | 19 | it "require login" do 20 | expect do 21 | delete :destroy, params: { id: device.id } 22 | end.to change(user.devices, :count).by(0) 23 | expect(response.status).to eq(302) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag '重新发送解锁邮件' %> 2 | 3 |
    4 |
    5 |
    6 |
    7 |
    重新发送解锁邮件
    8 |
    9 | <%= form_for resource, as: resource_name, url: unlock_path(resource_name), method: :post do |f| %> 10 | <%= render "shared/error_messages", target: resource %> 11 | 12 |
    13 | <%= f.text_field :email, class: "form-control", placeholder: "Email" %> 14 |
    15 | 16 |
    17 | <%= f.submit "重新发送解锁邮件", class: 'btn btn-primary' %> 18 |
    19 | <% end %> 20 |
    21 |
    22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /app/views/users/_recent_publish_topics.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    <%= t("users.recent_publish_topic")%>
    3 | 4 | 5 | 6 | 7 | 8 | 9 | <% last_topics.each do |topic| %> 10 | 11 | 12 | 13 | 14 | 15 | <% end %> 16 |
    <%= t("common.reply_count")%><%= t("common.title")%><%= t("common.last_reply_time")%>
    <%= topic.replies_count %> <%= link_to(truncate(topic.title, length: 25), topic_path(topic))%> <%= l((topic.replied_at || topic.created_at), format: :short) %>
    17 |
    18 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = "1.0" 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | # Add Yarn node_modules folder to the asset load path. 11 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 12 | 13 | # Precompile additional assets. 14 | # application.js, application.css, and all non-JS/CSS in the app/assets 15 | # folder are already added. 16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 17 | Rails.application.config.assets.precompile += %w[app.js front.css turbolinks-app.css admin.css] 18 | -------------------------------------------------------------------------------- /spec/controllers/search_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe SearchController, type: :controller do 6 | describe "/search/users" do 7 | let(:user) { create(:user) } 8 | let(:users) { [create(:user), create(:user)] } 9 | 10 | it "should work" do 11 | sign_in user 12 | allow(User).to receive(:search).and_return(users) 13 | get :users 14 | res = JSON.parse(response.body) 15 | expect(response).to have_http_status(200) 16 | expect(res[0]).to include("login", "name", "avatar_url") 17 | expect(res.map { |j| j["login"] }).to match users.map(&:login) 18 | expect(res.map { |j| j["name"] }).to match users.map(&:name) 19 | expect(res.map { |j| j["avatar_url"] }).to match users.map(&:large_avatar_url) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/notifications/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Notifications 4 | class NotificationsController < Notifications::ApplicationController 5 | def index 6 | @notifications = notifications.includes(:actor).order("id desc").page(params[:page]) 7 | 8 | unread_ids = @notifications.reject(&:read?).select(&:id) 9 | Notification.read!(unread_ids) 10 | 11 | @notification_groups = @notifications.group_by { |note| note.created_at.to_date } 12 | 13 | Notification.realtime_push_to_client(current_user) 14 | end 15 | 16 | def clean 17 | notifications.delete_all 18 | redirect_to notifications_path 19 | end 20 | 21 | private 22 | 23 | def notifications 24 | Notification.where(user_id: current_user.id) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, '\1en' 10 | # inflect.singular /^(ox)en/i, '\1' 11 | # inflect.irregular 'person', 'people' 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # user_sso -> UserSSO 18 | inflect.acronym "SSO" 19 | # github -> GitHub 20 | inflect.acronym "GitHub" 21 | end 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/search.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | .search-results { 4 | .result { 5 | margin-bottom: 20px; 6 | 7 | em { color: $red; font-style: normal;} 8 | 9 | .title { 10 | font-size: 15px; 11 | line-height: 160%; 12 | 13 | .badge { background: $gray; color: $gray; font-weight: normal; font-size: 12px; margin-left: 4px; } 14 | } 15 | .info { 16 | margin-bottom: 6px; 17 | font-size: 14px; 18 | .url a { color: $green; } 19 | 20 | .date { color: #999; margin-left: 8px; } 21 | } 22 | 23 | .desc { 24 | color: #666; 25 | font-size: 13px; 26 | word-break: break-all; 27 | em { color: $red; } 28 | } 29 | } 30 | 31 | .user { 32 | .info { margin-top: 4px; font-size: 14px; } 33 | .info.number { color: #666; font-size: 13px; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.users")%> <%= t("common.edit")%> 3 | <% end %> 4 | 5 | <% if @user.user_type == :user %> 6 |
    7 | 8 | <%= link_to '删除他的最近 10 篇话题', clean_admin_user_path(@user, type: 'topics'), 9 | method: 'delete', class: 'btn btn-warning', 10 | data: { confirm: '注意!!!此动作是直接删除,无法恢复的,你确定要删除么?' } %> 11 | 12 | <%= link_to '删除他的最近 10 条回帖', clean_admin_user_path(@user, type: 'replies'), 13 | method: 'delete', class: 'btn btn-warning', 14 | data: { confirm: '注意!!!此动作是直接删除,无法恢复的,你确定要删除么?' } %> 15 | 16 |
    17 | <% end %> 18 | 19 | <%= render 'form' %> 20 | -------------------------------------------------------------------------------- /app/views/users/_menu.html.erb: -------------------------------------------------------------------------------- 1 | <%= render_list class: "users-menu nav nav-tabs" do |li| 2 | li << link_to(t("users.menu.profile"), user_path(@user), class: "nav-link") 3 | li << link_to(t("users.menu.topics"), topics_user_path(@user), class: "nav-link") 4 | li << link_to(t("users.menu.replies"), replies_user_path(@user), class: "nav-link") 5 | li << link_to(t("users.menu.favorites"), favorites_user_path(@user), class: "nav-link hide-ios hidden-mobile") 6 | li << link_to(t("users.menu.following"), following_user_path(@user), class: "nav-link hidden-mobile") 7 | li << link_to(t("users.menu.followers"), followers_user_path(@user), class: "nav-link hidden-mobile") 8 | if owner?(@user) && current_user.block_users? 9 | li << link_to(t("users.menu.blocked"), blocked_user_path(@user), class: "nav-link hide-ios hidden-mobile") 10 | end 11 | end %> 12 | -------------------------------------------------------------------------------- /config/locales/teams.en.yml: -------------------------------------------------------------------------------- 1 | 'en': 2 | team_user_status: 3 | pendding: 'Awaiting response' 4 | acceped: 'Accepted' 5 | team_user_role: 6 | owner: 'Owner' 7 | member: 'Member' 8 | teams: 9 | teams: 'Team' 10 | user_existed: 'Already in this team' 11 | new_team: 'New team' 12 | edit_team: 'Edit team profile' 13 | new_team_user: 'Add a member' 14 | edit_team_user: 'Change team roles' 15 | delete_team_user: 'Remove member' 16 | show_team_user: 'View invitations' 17 | accept_team_user: 'Accept invitation' 18 | reject_team_user: 'Decline' 19 | delete_team_user_confirm: 'Are you sure you want to remove this user from team?' 20 | topics: 'Topics of members' 21 | users: 'Members' 22 | team_user_pendding: 'Awaiting response' 23 | settings: 'Settings' 24 | team_users_count: "%{count} team members" 25 | -------------------------------------------------------------------------------- /spec/models/section_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Section, type: :model do 6 | describe ".default" do 7 | it "should work" do 8 | expect(Section.default).to be_a(Section) 9 | expect(Section.default.new_record?).to eq false 10 | end 11 | end 12 | 13 | describe "CacheVersion update" do 14 | let(:old) { 1.minutes.ago } 15 | it "should update on save" do 16 | CacheVersion.section_node_updated_at = old 17 | create(:section) 18 | expect(CacheVersion.section_node_updated_at).not_to eq(old) 19 | end 20 | 21 | it "should update on destroy" do 22 | section = create(:section) 23 | CacheVersion.section_node_updated_at = old 24 | section.destroy 25 | expect(CacheVersion.section_node_updated_at).not_to eq(old) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/notifications/notifications/_notification.html.erb: -------------------------------------------------------------------------------- 1 | <%= cache(['notifications', Notifications::VERSION, notification, notification.actor, notification.read?]) do %> 2 |
    5 |
    6 | <%= link_to image_tag(notification.actor_avatar_url), notification.actor_profile_url, title: notification.actor_name, class: 'user-avatar' %> 7 |
    8 |
    9 | <%= render partial: "/notifications/#{notification.notify_type.underscore}", locals: { notification: notification } %> 10 |
    11 |
    12 | <%= notification.created_at.strftime('%H:%M') %> 13 |
    14 |
    15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/replies/_reply_to.html.erb: -------------------------------------------------------------------------------- 1 | <% if reply.reply_to && reply.reply_to&.user %> 2 | <% 3 | reply_to = reply.reply_to 4 | user = reply_to.user 5 | %> 6 |
    7 |
    8 | 对 9 | <% if show_body %> 10 | <%= user_avatar_tag(user, :xs) %><%= user_name_tag(user) %> 11 | <% else %> 12 | <%= link_to reply_to_topic_reply_path(@topic, reply), remote: true do %> 13 | <%= user_avatar_tag(user, :xs, link: false) %><%= user.login %> 14 | 15 | <% end %> 16 | <% end %> 17 | 回复 18 |
    19 | <% if show_body %> 20 |
    21 | <%= markdown(reply_to.body) %> 22 |
    23 | <% end %> 24 |
    25 | <% end %> 26 | -------------------------------------------------------------------------------- /app/views/search/index.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [params[:q], t('common.search_result')].join(' · ') %> 2 | 3 |
    4 |
    5 |
    关于 “<%= params[:q] %>” 的搜索结果, 共 <%= @result.records.total %> 条
    6 |
    7 | 8 |
    9 | <% if @result.records.total == 0 %> 10 |
    没有搜索到任何有关 “<%= params[:q]%>” 的内容
    11 | <% else %> 12 | <% @result.records.each_with_hit do |item, hit| %> 13 | <% partial_view_name = item.class.name.downcase -%> 14 | <%= render partial: (partial_view_name == 'team' ? 'user' : partial_view_name), locals: { item: item, hit: hit } %> 15 | <% end %> 16 | <% end %> 17 |
    18 | 19 | 22 |
    23 | -------------------------------------------------------------------------------- /app/views/team_users/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag t("teams.show_team_user") %> 2 | <%= render '/teams/header' %> 3 | 4 |
    5 |
    6 |
    7 |
    8 |

    <%= t("teams.show_team_user") %>

    9 |

    10 | 你是否要接受邀请加入 <%= user_name_tag(@team) %>? 11 |

    12 |
    13 | <%= link_to t('teams.accept_team_user'), accept_user_team_user_path(@team, @team_user), data: { method: 'POST' }, class: 'btn btn-primary' %> 14 | <%= link_to t('teams.reject_team_user'), reject_user_team_user_path(@team, @team_user), data: { method: 'POST' }, class: 'btn btn-default' %> 15 |
    16 |
    17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /app/views/admin/photos/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t('admin.menu.photos') %> 3 | <% end %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @photos.each do |photo| %> 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% end %> 21 |
    #预览上传者
    <%= photo.id %><%= link_to image_tag(photo.image.url(:md), style: "width: 150px"), photo.image.url, target: "_blank" if !photo[:image].blank? %><%= photo.user.login if !photo.user.blank? %><%= link_to '', admin_photo_path(photo), 'data-confirm' => 'Are you sure?', method: :delete, class: "fa fa-trash" %>
    22 | <%= paginate @photos %> 23 |
    24 | 25 | <%= link_to 'New Photo', new_admin_photo_path %> 26 | -------------------------------------------------------------------------------- /app/views/settings/_menu.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= render_list class: "nav nav-pills flex-column nav-stacked" do |li| 4 | li << link_to(icon_tag('user-circle', label: '基本信息'), setting_path, class: "nav-link") 5 | li << link_to(icon_tag('address-card', label: '详细资料'), profile_setting_path, class: "nav-link hide-ios") 6 | li << link_to(icon_tag('qrcode', label: '打赏'), reward_setting_path, class: "nav-link hide-ios") 7 | li << link_to(icon_tag('gear', label: '账户设置'), account_setting_path, class: "nav-link hide-ios") 8 | li << link_to(icon_tag('keyboard-o', label: '登陆密码'), password_setting_path, class: "nav-link hide-ios") unless Setting.sso_enabled? 9 | li << link_to(icon_tag('cube', label: '管理我的应用'), oauth_applications_path, class: "nav-link hide-ios") 10 | end %> 11 |
    12 |
    13 | -------------------------------------------------------------------------------- /app/views/users/_reward.html.erb: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/homeland/markdown.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "html/pipeline" 4 | 5 | context = { 6 | gfm: true, 7 | video_width: 700, 8 | video_height: 387 9 | } 10 | 11 | filters = [ 12 | Homeland::Pipeline::NormalizeMentionFilter, 13 | Homeland::Pipeline::EmbedVideoFilter, 14 | Homeland::Pipeline::MarkdownFilter, 15 | Homeland::Pipeline::MentionFilter, 16 | Homeland::Pipeline::FloorFilter, 17 | Homeland::Pipeline::TwemojiFilter 18 | ] 19 | 20 | TopicPipeline = HTML::Pipeline.new(filters, context) 21 | 22 | module Homeland 23 | class Markdown 24 | class << self 25 | def call(body) 26 | result = TopicPipeline.call(body)[:output].inner_html 27 | result.strip! 28 | result 29 | end 30 | 31 | def example 32 | @example ||= open(Rails.root.join("lib/homeland/markdown_guides.md")).read 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/admin/site_configs_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class SiteConfigsController < Admin::ApplicationController 5 | before_action :set_setting, only: %i[edit update] 6 | 7 | def index 8 | end 9 | 10 | def edit 11 | end 12 | 13 | def update 14 | if @site_config.value != setting_param[:value] 15 | @site_config.value = setting_param[:value] 16 | @site_config.save 17 | @site_config.expire_cache 18 | redirect_to admin_site_configs_path, notice: "保存成功." 19 | else 20 | redirect_to admin_site_configs_path 21 | end 22 | end 23 | 24 | def set_setting 25 | @site_config = Setting.find_by(var: params[:id]) || Setting.new(var: params[:id]) 26 | end 27 | 28 | private 29 | 30 | def setting_param 31 | params[:setting].permit! 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/views/admin/sections/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.sections") %> 3 | <% end %> 4 | 5 |
    6 | <%= link_to '新建分类', new_admin_section_path, class: "btn btn-default" %> 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @sections.each do |section| %> 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | <% end %> 27 |
    #名称排序
    <%= section.id %><%= section.name %><%= section.sort %><%= link_to '', edit_admin_section_path(section), class: "fa fa-pencil" %> 24 | <%= link_to '', admin_section_path(section), 'data-confirm' => '确定要删除吗?', method: :delete, class: "fa fa-trash" %>
    28 | 29 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # frozen_string_literal: true 3 | 4 | # Add your own tasks in files placed in lib/tasks ending in .rake, 5 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 6 | 7 | require File.expand_path("../config/application", __FILE__) 8 | require "elasticsearch/rails/tasks/import" 9 | 10 | Rails.application.load_tasks 11 | 12 | task default: "bundle:audit" 13 | 14 | require "sdoc" 15 | require "rdoc/task" 16 | 17 | rdoc_files = %w[ 18 | README.md 19 | PLUGIN_DEV.md 20 | CONTRIBUTE.md 21 | API.md 22 | app/models 23 | app/controllers 24 | app/helpers 25 | lib/homeland.rb 26 | lib/single_sign_on.rb 27 | lib/homeland 28 | ] 29 | 30 | RDoc::Task.new do |rdoc| 31 | rdoc.rdoc_dir = "public/doc" 32 | rdoc.generator = "sdoc" 33 | rdoc.template = "rails" 34 | rdoc.main = "README.md" 35 | rdoc.rdoc_files.include(*rdoc_files) 36 | end 37 | -------------------------------------------------------------------------------- /lib/assets/stylesheets/jquery.atwho.css: -------------------------------------------------------------------------------- 1 | .atwho-view { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | display: none; 6 | margin-top: 18px; 7 | background: #fff; 8 | border: 1px solid #e0e0e0; 9 | border-radius: 3px; 10 | box-shadow: 0 0 5px rgba(0,0,0,.1); 11 | min-width: 120px; 12 | z-index: 10 13 | } 14 | .atwho-view .cur { 15 | background: #317DDA; 16 | color: #fff 17 | } 18 | .atwho-view .cur small { 19 | color: #fff 20 | } 21 | .atwho-view strong { font-weight: normal; } 22 | 23 | .atwho-view ul { 24 | list-style: none; 25 | padding: 0; 26 | margin: auto 27 | } 28 | .atwho-view ul li { 29 | display: block; 30 | padding: 5px 10px; 31 | border-bottom: 1px solid #eee; 32 | cursor: pointer 33 | } 34 | .atwho-view ul li img { width: 16px; height: 16px; border-radius: 180px; } 35 | .atwho-view small { 36 | font-size: smaller; 37 | color: #777; 38 | font-weight: 400 39 | } 40 | -------------------------------------------------------------------------------- /app/views/admin/topics/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin,@topic] do |f| %> 2 | <%= render 'shared/error_messages', target: @topic %> 3 | 4 |
    5 | <%= f.label :title %> 6 | <%= f.text_field :title, class: "form-control" %> 7 |
    8 | 9 |
    10 | <%= f.label :node_id %> 11 | <%= f.text_field :node_id, class: "form-control" %> 12 |
    13 | 14 |
    15 | <%= f.label :user_id %> 16 | <%= f.text_field :user_id, class: "form-control" %> 17 |
    18 | 19 |
    20 | <%= f.label :body %> 21 | <%= f.text_area :body, class: "form-control" %> 22 |
    23 | 24 |
    25 | <%= f.submit t("common.save"), class: "btn btn-primary", 'data-disable-with' => t("common.saving") %> 26 | or 27 | <%= link_to 'Back', admin_topics_path %> 28 |
    29 | <% end %> 30 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SessionsController < Devise::SessionsController 4 | skip_before_action :set_locale, only: [:create] 5 | before_action :require_no_sso!, only: %i[new create] 6 | 7 | def create 8 | resource = warden.authenticate!(scope: resource_name, recall: "#{controller_path}#new") 9 | set_flash_message(:notice, :signed_in) if is_navigational_format? 10 | sign_in(resource_name, resource) 11 | respond_to do |format| 12 | format.html { redirect_back_or_default(root_url) } 13 | format.json { render status: "201", json: resource.as_json(only: %i[login email]) } 14 | end 15 | end 16 | 17 | private 18 | 19 | # If not bind to a domain, request.domain is nil. 20 | def domain_or_host 21 | request.domain || request.host 22 | end 23 | 24 | def respond_to_on_destroy 25 | redirect_to topics_url 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rails/generators/erb/scaffold/scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators/erb" 4 | require "rails/generators/resource_helpers" 5 | 6 | module Erb 7 | module Generators 8 | class ScaffoldGenerator < Base 9 | include Rails::Generators::ResourceHelpers 10 | 11 | argument :attributes, type: :array, default: [], banner: "field:type field:type" 12 | 13 | def create_root_folder 14 | empty_directory File.join("app/views", controller_file_path) 15 | end 16 | 17 | def copy_view_files 18 | available_views.each do |view| 19 | filename = filename_with_extensions(view) 20 | template filename, File.join("app/views", controller_file_path, filename) 21 | end 22 | end 23 | 24 | protected 25 | 26 | def available_views 27 | %w[index edit show new _form _base] 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/api/photos_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | require "active_support/core_ext" 5 | 6 | describe "API", type: :request do 7 | let(:json) { JSON.parse(response.body) } 8 | 9 | describe "POST /api/v3/photos.json" do 10 | include ActionDispatch::TestProcess 11 | 12 | context "without login" do 13 | it "should response 401" do 14 | post "/api/v3/photos.json" 15 | expect(response.status).to eq 401 16 | end 17 | end 18 | 19 | context "with login" do 20 | it "should work" do 21 | login_user! 22 | post "/api/v3/photos.json", file: fixture_file_upload("test.png") 23 | @photo = Photo.last 24 | expect(response.status).to eq 200 25 | expect(@photo.user_id).to eq current_user.id 26 | expect(json["image_url"]).not_to eq nil 27 | expect(json["image_url"]).not_to eq "" 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/jobs/push_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PushJob < ApplicationJob 4 | queue_as :notifications 5 | 6 | # user_ids: 用户编号列表 7 | # note: { alert: 'Hello APNS World!', sound: 'true', badge: 1 } 8 | def perform(user_ids, note = {}) 9 | return false if Setting.apns_pem.blank? 10 | 11 | note[:sound] ||= "true" 12 | devices = Device.where(user_id: user_ids).all.to_a 13 | devices.select!(&:alive?) 14 | tokens = devices.collect(&:token) 15 | return false if tokens.blank? 16 | 17 | notification = RubyPushNotifications::APNS::APNSNotification.new(tokens, aps: note) 18 | pusher = RubyPushNotifications::APNS::APNSPusher.new(Setting.apns_pem, !Rails.env.production?) 19 | pusher.push [notification] 20 | Rails.logger.tagged("PushJob") do 21 | Rails.logger.info "send to #{tokens.size} devices #{note} status: #{notification.success}" 22 | end 23 | notification.success 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/admin/nodes/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin,@node] do |f| %> 2 | <%= render 'shared/error_messages', target: @node %> 3 | 4 |
    5 | <%= f.label :name %> 6 | <%= f.text_field :name, class: "form-control" %> 7 |
    8 | 9 |
    10 | <%= f.label :name %> 11 | <%= f.select :section_id, Section.all.collect { |s| [s.name,s.id] }, {}, { class: "form-control" } %> 12 |
    13 | 14 |
    15 | <%= f.label :sort %> 16 | <%= f.text_field :sort, class: "form-control" %> 17 |
    18 | 19 |
    20 | <%= f.label :summary %> 21 | <%= f.text_area :summary, class: "form-control" %> 22 |
    23 | 24 |
    25 | <%= f.submit t("common.save"), class: "btn btn-primary", 'data-disable-with' => t("common.saving") %> 26 | <%= link_to 'Back', admin_nodes_path %> 27 |
    28 | <% end %> 29 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /app/models/cache_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 用于记录特定的 cache version 3 | # 比如: 4 | # 记录最后更新 置顶话题的时间,以用于作为自动变换置顶那个 cache 的 key,以达到自动过期的目的 5 | # 用法例子: 6 | # 以上面个场景为例 7 | # Topic after_suggest -> 8 | # CacheVersion.topic_last_suggested_at = Time.now 9 | # View 里面 <% cache("topic/index/sidebar_suggest:#{CacheVersion.topic_last_suggested_at}") do %><% end %> 10 | class CacheVersion 11 | def self.method_missing(method, *args) 12 | method_name = method.to_s 13 | super(method, *args) 14 | rescue NoMethodError 15 | if method_name.match?(/=$/) 16 | var_name = method_name.sub("=", "") 17 | key = CacheVersion.mk_key(var_name) 18 | value = args.first.to_s 19 | # save 20 | Rails.cache.write(key, value) 21 | else 22 | key = CacheVersion.mk_key(method) 23 | Rails.cache.read(key) 24 | end 25 | end 26 | 27 | def self.mk_key(key) 28 | "cache_version:#{key}" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/account_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Devise User Controller 4 | class AccountController < Devise::RegistrationsController 5 | before_action :require_no_sso!, only: %i[new create] 6 | 7 | def new 8 | super 9 | end 10 | 11 | def edit 12 | redirect_to setting_path 13 | end 14 | 15 | # POST /resource 16 | def create 17 | build_resource(sign_up_params) 18 | resource.login = params[resource_name][:login] 19 | resource.email = params[resource_name][:email] 20 | if verify_rucaptcha?(resource) && resource.save 21 | sign_in(resource_name, resource) 22 | end 23 | end 24 | 25 | private 26 | 27 | # Overwrite the default url to be used after updating a resource. 28 | # It should be edit_user_registration_path 29 | # Note: resource param can't miss, because it's the super caller way. 30 | def after_update_path_for(_) 31 | edit_user_registration_path 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /config/initializers/notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # notifications Config 4 | Notifications.configure do 5 | # Class name of you User model, default: 'User' 6 | # self.user_class = 'User' 7 | 8 | # Method of user name in User model, default: 'name' 9 | self.user_name_method = "login" 10 | 11 | # Method of user avatar in User model, default: nil 12 | self.user_avatar_url_method = "large_avatar_url" 13 | 14 | # Method name of user profile page path, in User model, default: 'profile_url' 15 | self.user_profile_url_method = "profile_url" 16 | 17 | # authenticate_user method in your Controller, default: 'authenticate_user!' 18 | # If you use Devise, authenticate_user! is correct 19 | self.authenticate_user_method = "authenticate_user!" 20 | 21 | # current_user method name in your Controller, default: 'current_user' 22 | # If you use Devise, current_user is correct 23 | # self.current_user_method = 'current_user' 24 | end 25 | -------------------------------------------------------------------------------- /lib/homeland/plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Homeland 4 | class Plugin 5 | # name of plugin, use var name style. 6 | attr_accessor :name 7 | 8 | # Plugin version 9 | attr_accessor :version 10 | 11 | # Description of plugin 12 | attr_accessor :description 13 | 14 | # Display name of plugin 15 | attr_accessor :display_name 16 | 17 | # set true if plugin link wants list in top navbar 18 | attr_accessor :navbar_link 19 | 20 | # set true if plugin link wants list in user drodown menu 21 | attr_accessor :user_menu_link 22 | 23 | # set true if plugin link wants list in admin navbar 24 | attr_accessor :admin_navbar_link 25 | 26 | # path of plugin root, for example /blog 27 | attr_accessor :root_path 28 | 29 | # path of plugin admin page for example /admin/blog 30 | attr_accessor :admin_path 31 | 32 | # add RSpec test path 33 | attr_accessor :spec_path 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/users/favorites.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag [@user.fullname, t('users.menu.favorites')].join(' · ') %> 2 | 3 |
    4 | <%= render "sidebar", user: @user %> 5 |
    6 | <%= render "menu" %> 7 |
    8 |
    9 | 10 | 11 | 12 | 13 | 14 | <% @topics.each do |item| %> 15 | "> 16 | 17 | 18 | 19 | <% end %> 20 |
    <%= t("common.node") %><%= t("common.title") %>
    <%= render_node_name(item.node_name,item.node_id) %><%= topic_title_tag(item) %>
    21 |
    22 | 25 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /app/views/search/_user.html.erb: -------------------------------------------------------------------------------- 1 | <% if item.organization? %> 2 | <%= render 'team', item: item, hit: hit %> 3 | <% else %> 4 |
    5 |
    6 |
    7 |
    <%= user_avatar_tag(item, :md) %>
    8 |
    9 |
    10 |
    11 | <%= link_to item.fullname, item %> 12 | <%= render_user_level_tag(item) %> 13 |
    14 |
    15 | 第 <%= item.id %> 位<%= t("menu.users")%> 16 | • 17 | <%= item.created_at.to_date %> 18 | <% if item.location.present? %> • <%= location_name_tag(item.location) %><% end %> 19 | • 20 | <%= item.topics_count %> 篇帖子 • <%= item.replies_count %> 条回帖 21 |
    22 |
    23 |
    24 |
    25 | <% end %> 26 | -------------------------------------------------------------------------------- /app/assets/javascripts/form_storage.coffee: -------------------------------------------------------------------------------- 1 | @FormStorage = 2 | key: (element) -> 3 | "#{location.pathname} #{$(element).prop('id')}" 4 | 5 | init: -> 6 | if window.localStorage 7 | $(document).on 'input', 'textarea[name*=body]', -> 8 | textarea = $(this) 9 | localStorage.setItem(FormStorage.key(textarea), textarea.val()) 10 | 11 | $(document).on 'submit', 'form', -> 12 | form = $(this) 13 | form.find('textarea[name*=body]').each -> 14 | localStorage.removeItem(FormStorage.key(this)) 15 | 16 | $(document).on 'click', 'form a.reset', -> 17 | form = $(this).closest('form') 18 | form.find('textarea[name*=body]').each -> 19 | localStorage.removeItem(FormStorage.key(this)) 20 | 21 | restore: -> 22 | if window.localStorage 23 | $('textarea[name*=body]').each -> 24 | textarea = $(this) 25 | if value = localStorage.getItem(FormStorage.key(textarea)) 26 | textarea.val(value) 27 | -------------------------------------------------------------------------------- /app/models/concerns/mention_topic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MentionTopic 4 | extend ActiveSupport::Concern 5 | 6 | TOPIC_LINK_REGEXP = %r{://#{Setting.domain}/topics/([\d]+)}i 7 | 8 | included do 9 | attr_accessor :mentioned_topic_ids 10 | 11 | after_save :create_releated_for_mentioned_topics 12 | end 13 | 14 | def extract_mentioned_topic_ids 15 | matched_ids = body.scan(TOPIC_LINK_REGEXP).flatten 16 | current_topic_id = self.class.name == "Topic" ? self.id : self.topic_id 17 | return if matched_ids.blank? 18 | matched_ids = matched_ids.map(&:to_i).reject { |id| id == current_topic_id } 19 | Topic.where("id IN (?)", matched_ids).pluck(:id) 20 | end 21 | 22 | private 23 | def create_releated_for_mentioned_topics 24 | topic_ids = extract_mentioned_topic_ids 25 | return if topic_ids.blank? 26 | MentionTopicJob.perform_later(topic_ids, target_type: self.class.name, target_id: self.id, user_id: self.user_id) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/admin/comments/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t('admin.menu.comments') %> 3 | <% end %> 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | <% @comments.each do |item| %> 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | <% end %> 24 |
    被评论对象 8 | 评论人内容At
    <%= item.commentable_type %>#<%= item.commentable_id %><%= item.user.login if !item.user.blank? %><%= item.body %><%= l item.created_at, format: :short %><%= link_to '', edit_admin_comment_path(item), class: "fa fa-pencil" %> 21 | <%= link_to '', admin_comment_path(item), 'data-confirm' => 'Are you sure?', method: :delete, remote: true, class: "fa fa-trash" %>
    25 | <%= paginate @comments %> 26 | -------------------------------------------------------------------------------- /app/views/notifications/_comment.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | comment = notification.target 3 | second_target = notification.second_target 4 | %> 5 | 6 | <% if second_target.blank? %> 7 |
    相关信息已删除
    8 | <% else %> 9 | <% 10 | case notification.second_target_type 11 | when "Post" %> 12 | <% if Setting.has_module? :press %> 13 |
    14 | 在你发布的 <%= second_target.title %> 评论了: 15 |
    16 |
    17 | <%= comment.body_html %> 18 |
    19 | <% end %> 20 | <% when "Page" %> 21 | <% if Setting.has_module? :wiki %> 22 |
    23 | 在你发布的 <%= second_target.title %> 评论了: 24 |
    25 |
    26 | <%= comment.body_html %> 27 |
    28 | <% end %> 29 | <% end %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /config/locales/topics.zh-TW.yml: -------------------------------------------------------------------------------- 1 | "zh-TW": 2 | topics: 3 | new_topic: "新建帖子" 4 | edit_topic: "修改帖子" 5 | no_replies: "暫無回覆。" 6 | recent_topics: "最近發佈的帖子" 7 | recent_replies: "最近回覆的帖子" 8 | check_recent_topics: "查看更多最近發佈的帖子..." 9 | reply_this_fllor: "回覆此樓" 10 | tips_for_create_topic: "發帖說明" 11 | subscribe_with_rss: "訂閱 %{name} 社區新帖" 12 | submit_reply: "提交回復" 13 | topic_list: 14 | hot_topic: "活躍帖子" 15 | recent: "最新創建" 16 | last_reply: "最新回覆" 17 | popular: "優質帖子" 18 | no_reply: "無人問津" 19 | excellent: "精華帖" 20 | read_topic: "瀏覽帖子" 21 | create_topic_success: "帖子創建成功。" 22 | delete_topic_success: "帖子刪除成功。" 23 | update_topic_success: "帖子修改成功。" 24 | reply_success: "回覆成功。" 25 | reply_this_floor: "回覆此樓" 26 | topic_was_deleted: "[該主題已被刪除]" 27 | have_no_new_reply: "在你讀過以後還沒有新變化" 28 | has_new_replies: "有新內容" 29 | more_like_this: "相關帖子" 30 | node_recent_topics: "節點下其他話題" 31 | have_no_avatar: "你還未設置頭像,不能創建新帖子。" 32 | -------------------------------------------------------------------------------- /app/views/notifications/mentions/_comment.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | comment = notification.target 3 | second_target = notification.second_target 4 | %> 5 | 6 | <% if second_target.blank? %> 7 |
    相关信息已删除
    8 | <% else %> 9 | <% 10 | case notification.second_target_type 11 | when "Post" %> 12 | <% if Setting.has_module? :press %> 13 |
    14 | 在 <%= second_target.title %> 的评论中提及你: 15 |
    16 |
    17 | <%= comment.body_html %> 18 |
    19 | <% end %> 20 | <% when "Page" %> 21 | <% if Setting.has_module? :wiki %> 22 |
    23 | 在 <%= second_target.title %> 的评论中提及你: 24 |
    25 |
    26 | <%= comment.body_html %> 27 |
    28 | <% end %> 29 | <% end %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /config/locales/topics.zh-CN.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | topics: 3 | new_topic: "发布新话题" 4 | edit_topic: "修改话题" 5 | no_replies: "暂无回复。" 6 | recent_topics: "最近发布的话题" 7 | recent_replies: "最近回复的话题" 8 | check_recent_topics: "查看更多最近发布的话题..." 9 | reply_this_fllor: "回复此楼" 10 | tips_for_create_topic: "发帖说明" 11 | subscribe_with_rss: "订阅 %{name} 社区新话题" 12 | submit_reply: "提交回复" 13 | topic_list: 14 | hot_topic: "活跃话题" 15 | recent: "最新创建" 16 | last_reply: "最新回复" 17 | popular: "优质话题" 18 | no_reply: "无人问津" 19 | excellent: "精华帖" 20 | read_topic: "浏览话题" 21 | create_topic_success: "话题创建成功。" 22 | delete_topic_success: "话题删除成功。" 23 | update_topic_success: "话题修改成功。" 24 | reply_success: "回复成功。" 25 | reply_this_floor: "回复此楼" 26 | topic_was_deleted: "[该话题已被删除]" 27 | have_no_new_reply: "在你读过以后还没有新变化" 28 | has_new_replies: "有新内容" 29 | more_like_this: "相关话题" 30 | node_recent_topics: "节点下其他话题" 31 | have_no_avatar: "你还未设置头像,不能发布新话题。" 32 | -------------------------------------------------------------------------------- /app/controllers/admin/comments_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class CommentsController < Admin::ApplicationController 5 | before_action :set_comment, only: %i[show edit update destroy] 6 | respond_to :js, :html, only: [:destroy] 7 | 8 | def index 9 | @comments = Comment.recent.includes(:user).page(params[:page]) 10 | end 11 | 12 | def edit 13 | end 14 | 15 | def update 16 | if @comment.update(params[:comment].permit!) 17 | redirect_to admin_comments_path(@admin_comment), notice: "Comment was successfully updated." 18 | else 19 | render action: "edit" 20 | end 21 | end 22 | 23 | def destroy 24 | @comment.destroy 25 | respond_with do |format| 26 | format.html { redirect_to admin_comments_path } 27 | format.js { render layout: false } 28 | end 29 | end 30 | 31 | private 32 | 33 | def set_comment 34 | @comment = Comment.find(params[:id]) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/admin/nodes/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :sitemap do %> 2 | <%= t("admin.menu.nodes")%> 3 | <% end %> 4 | 5 |
    6 | <%= link_to '新建节点', new_admin_node_path, class: "btn btn-default" %> 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <% @nodes.each do |node| %> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | <% end %> 30 |
    #名称分类排序帖子数量
    <%= node.id %><%= link_to node.name, node_topics_path(node.id) %><%= node&.section&.name %><%= node.sort %><%= node.topics_count %><%= link_to '', edit_admin_node_path(node), class: "fa fa-pencil" %> 27 | <%= link_to '', admin_node_path(node), 'data-confirm' => 'Are you sure?' , method: :delete, class: "fa fa-trash" %>
    31 | -------------------------------------------------------------------------------- /spec/models/user/reward_fields_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe User, type: :model do 6 | describe "RewardFields" do 7 | let(:user) { create(:user) } 8 | 9 | it "should work" do 10 | params = { 11 | alipay: "alipay111", 12 | wechat: "wechat111" 13 | } 14 | expect(user.reward_enabled?).to eq false 15 | user.update_reward_fields(params) 16 | expect(user.reward_enabled?).to eq true 17 | expect(user.settings.reward_fields).to eq(params) 18 | expect(user.reward_fields).to eq(params) 19 | expect(user.reward_field(:wechat)).to eq "wechat111" 20 | expect(user.reward_field("wechat")).to eq "wechat111" 21 | expect(user.reward_field("alipay")).to eq "alipay111" 22 | expect(user.reward_field(:ddd)).to eq nil 23 | expect(user.reward_field(:facebook)).to eq nil 24 | expect(User.reward_field_label(:wechat)).to eq "微信" 25 | expect(User.reward_field_label(:alipay)).to eq "支付宝" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/topics/_sidebar_for_topic_index.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user && current_user.newbie? %> 2 |
    3 |
    新手必读
    4 |
    5 | <%= raw Setting.newbie_notices %> 6 |
    7 |
    8 | <% else %> 9 | 10 |
    11 |
    12 | <%= link_to t('topics.new_topic'), main_app.new_topic_path, class: 'btn btn-primary btn-block' %> 13 |
    14 |
    15 | <% end %> 16 | 17 | <%= render "topics/sidebar_box_tips" %> 18 | 19 | <%= raw Setting.topic_index_sidebar_html %> 20 | 21 | <% cache(["sidebar_statistics",Time.now.strftime("%Y-%m-%d %H")]) do %> 22 |
    23 |
    <%= t("common.statics")%>
    24 | 29 |
    30 | <% end %> 31 | -------------------------------------------------------------------------------- /app/views/users/_repos.html.erb: -------------------------------------------------------------------------------- 1 | <% if Setting.has_module? :github %> 2 |
    3 | <% if not user.github.blank? %> 4 |
    GitHub Public Repos
    5 | 17 | 20 | <% else %> 21 |
    22 | 未设置 GitHub 信息. 23 |
    24 | <% end %> 25 |
    26 | <% end %> 27 | -------------------------------------------------------------------------------- /config/initializers/rack-attack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if Setting.rack_attack.present? 4 | BLOCK_MESSAGE = ["你请求过快,超过了频率限制,暂时屏蔽一段时间。"] 5 | 6 | class Rack::Attack 7 | Rack::Attack.cache.store = Rails.cache 8 | 9 | ### Throttle Spammy Clients ### 10 | throttle("req/ip", limit: Setting.rack_attack["limit"] || 300, period: Setting.rack_attack["period"] || 3.minutes, &:ip) 11 | 12 | # 固定黑名单 13 | blocklist("blacklist/ip") do |req| 14 | Setting.blacklist_ips && !Setting.blacklist_ips.index(req.ip).nil? 15 | end 16 | 17 | # 允许 localhost 18 | safelist("allow from localhost") do |req| 19 | req.ip == "127.0.0.1" || req.ip == "::1" 20 | end 21 | 22 | ### Custom Throttle Response ### 23 | self.throttled_response = lambda do |_env| 24 | [503, {}, BLOCK_MESSAGE] 25 | end 26 | end 27 | 28 | ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, request_id, req| 29 | Rails.logger.info " RackAttack: #{req.ip} #{request_id} blocked." 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LikesController < ApplicationController 4 | before_action :authenticate_user!, only: %i[create destroy] 5 | before_action :set_likeable 6 | 7 | def index 8 | @users = @item.like_by_users.order("actions.id asc") 9 | render :index, layout: false 10 | end 11 | 12 | def create 13 | current_user.like(@item) 14 | render plain: @item.reload.likes_count 15 | end 16 | 17 | def destroy 18 | current_user.unlike(@item) 19 | render plain: @item.reload.likes_count 20 | end 21 | 22 | private 23 | 24 | def set_likeable 25 | @success = false 26 | @element_id = "likeable_#{params[:type]}_#{params[:id]}" 27 | 28 | defined_action = User.find_defined_action(:like, params[:type]) 29 | 30 | if defined_action.blank? 31 | render plain: "-1" 32 | return false 33 | end 34 | 35 | @item = defined_action[:target_klass].find_by(id: params[:id]) 36 | render plain: "-2" if @item.blank? 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /config/secrets.yml.default: -------------------------------------------------------------------------------- 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 `rails 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: 66ad08589cd561754c8cd659a5a50a3694904f9c455dd2177115697d12d2c27b873222da8646f34c8cf4598a0f108f12c05141d645a3534c9a9a9833f239f397 15 | 16 | test: 17 | secret_key_base: 66ad08589cd561754c8cd659a5a50a3694904f9c455dd2177115697d12d2c27b873222da8646f34c8cf4598a0f108f12c05141d645a3534c9a9a9833f239f397 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/replies_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class RepliesController < Admin::ApplicationController 5 | before_action :set_reply, only: %i[show edit update destroy] 6 | 7 | def index 8 | @replies = Reply.unscoped 9 | if params[:q].present? 10 | qstr = "%#{params[:q].downcase}%" 11 | @replies = @replies.where("body LIKE ?", qstr) 12 | end 13 | if params[:login].present? 14 | u = User.find_by_login(params[:login]) 15 | @replies = @replies.where("user_id = ?", u.try(:id)) 16 | end 17 | @replies = @replies.order(id: :desc).includes(:topic, :user) 18 | @replies = @replies.page(params[:page]) 19 | end 20 | 21 | def show 22 | if @reply.topic.blank? 23 | redirect_to admin_replies_path, alert: "帖子已经不存在" 24 | end 25 | end 26 | 27 | def destroy 28 | @reply.destroy 29 | end 30 | 31 | private 32 | 33 | def set_reply 34 | @reply = Reply.unscoped.find(params[:id]) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/oauth/applications_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Oauth 4 | class ApplicationsController < Doorkeeper::ApplicationsController 5 | before_action :authenticate_user! 6 | include Homeland::UserNotificationHelper 7 | 8 | def index 9 | @applications = current_user.oauth_applications 10 | @authorized_applications = Doorkeeper::Application.authorized_for(current_user) 11 | @devices = current_user.devices.all 12 | end 13 | 14 | # only needed if each application must have some owner 15 | def create 16 | @application = Doorkeeper::Application.new(application_params) 17 | @application.uid = SecureRandom.hex(4) 18 | @application.owner = current_user if Doorkeeper.configuration.confirm_application_owner? 19 | 20 | if @application.save 21 | flash[:notice] = I18n.t(:notice, scope: %i[doorkeeper flash applications create]) 22 | redirect_to oauth_application_url(@application) 23 | else 24 | render :new 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 | <% if !mobile? %> 2 |
    3 | <%= raw Setting.index_html %> 4 |
    5 | <% end %> 6 | 7 |
    8 |
    社区精华帖
    9 |
    10 | <% cache(["home_suggest_topics", @excellent_topics]) do %> 11 | <% 12 | odd_topics, even_topics = @excellent_topics.partition.each_with_index { |t, i| i.odd? } 13 | %> 14 |
    15 | <%= render partial: "topics/topic", collection: even_topics, locals: { suggest: false } %> 16 |
    17 | 18 |
    19 | <%= render partial: "topics/topic", collection: odd_topics, locals: { suggest: false } %> 20 |
    21 | <% end %> 22 |
    23 | 26 |
    27 | 28 | <% if !mobile? %> 29 | <%= render "/shared/index_sections" %> 30 | <%= render "/shared/hot_locations" %> 31 | <% end %> 32 | -------------------------------------------------------------------------------- /app/views/replies/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 修改回帖 <%= topic_title_tag(@topic) %> 4 |
    5 |
    6 | <%= form_for [@topic, @reply], remote: true, tb: 'edit-reply' do |f| %> 7 | <%= render "shared/error_messages", target: @reply %> 8 | <%= render "/shared/editor_toolbar" %> 9 |
    10 | <%= f.text_area :body, class: "topic-editor form-control", rows: 10 %> 11 |
    12 |
    13 | <%= f.submit t("common.save"), class: "btn btn-primary col-xs-2 hide-ios", 'data-disable-with' => t("common.saving") %> 14 | <% if can? :destroy, @reply %> 15 | <%= link_to "删除",[@topic,@reply], class: "btn btn-danger", method: :delete, data: { confirm: "确定要删除此回复么?" } %> 16 | <% end %> 17 |
    18 | <% end %> 19 |
    20 |
    21 |
    22 |
    23 | <%= render "/shared/editor_help_block" %> 24 |
    25 |
    26 | -------------------------------------------------------------------------------- /app/views/notifications/notifications/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= t('notifications.all_notifications') %> 4 | 5 | 6 | <%= link_to t('notifications.clean_all'), notifications.clean_notifications_path, class: 'btn btn-sm btn-danger', id: 'btn-remove-all', method: 'delete', remote: true %> 7 | 8 |
    9 |
    10 | <% if @notifications.blank? %> 11 |
    12 |
    <%= t('notifications.no_records') %>
    13 |
    14 | <% else %> 15 | <% @notification_groups.each do |group, notifications| %> 16 |
    17 |
    <%= group %>
    18 | <%= render notifications %> 19 |
    20 | <% end %> 21 | <% end %> 22 |
    23 | 26 |
    27 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Define an application-wide content security policy 5 | # For further information see the following documentation 6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 7 | 8 | # Rails.application.config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https, :unsafe_inline 15 | 16 | # # Specify URI for violation reports 17 | # # policy.report_uri "/csp-violation-report-endpoint" 18 | # end 19 | 20 | # Report CSP violations to a specified URI 21 | # For further information see the following documentation: 22 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 23 | # Rails.application.config.content_security_policy_report_only = true 24 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | def index 5 | @excellent_topics = Topic.excellent.recent.fields_for_list.limit(20).to_a 6 | end 7 | 8 | def uploads 9 | return render_404 if Rails.env.production? 10 | 11 | # This is a temporary solution for help generate image thumb 12 | # that when you use :file upload_provider and you have no Nginx image_filter configurations. 13 | # DO NOT use this in production environment. 14 | format, version = params[:format].split("!") 15 | filename = [params[:path], format].join(".") 16 | pragma = request.headers["Pragma"] == "no-cache" 17 | thumb = Homeland::ImageThumb.new(filename, version, pragma: pragma) 18 | if thumb.exists? 19 | send_file thumb.outpath, type: "image/jpeg", disposition: "inline" 20 | else 21 | render plain: "File not found", status: 404 22 | end 23 | end 24 | 25 | def api 26 | redirect_to "/api-doc/" 27 | end 28 | 29 | def error_404 30 | render_404 31 | end 32 | 33 | def markdown 34 | end 35 | end 36 | --------------------------------------------------------------------------------