# Conflicts: # .gitignore # Gemfile # Gemfile.lock # app/controllers/accounts_controller.rb # app/controllers/application_controller.rb # app/controllers/compare_controller.rb # app/controllers/journals_controller.rb # app/controllers/members_controller.rb # app/controllers/pull_requests_controller.rb # app/controllers/settings_controller.rb # app/controllers/users_controller.rb # app/controllers/version_releases_controller.rb # app/models/gitea/webhook_task.rb # app/models/message_template/pull_request_atme.rb # app/models/project.rb # app/models/project_category.rb # app/models/user_extension.rb # app/views/users/get_user_info.json.jbuilder # app/views/users/show.json.jbuilder # config/routes.rbpull/318/head
| @@ -37,13 +37,11 @@ public/react/yarn.lock | |||
| # Ignore react node_modules | |||
| public/system/* | |||
| public/react/* | |||
| /public/react/.cache | |||
| /public/react/node_modules/ | |||
| /public/react/config/stats.json | |||
| /public/react/stats.json | |||
| /public/react/.idea/* | |||
| /public/react/build/* | |||
| /public/h5build | |||
| /public/npm-debug.log | |||
| @@ -83,7 +81,7 @@ db/bak/ | |||
| docker/ | |||
| educoder.sql | |||
| redis_data/ | |||
| Dockerfile | |||
| dump.rdb | |||
| .tags* | |||
| ceshi_user.xlsx | |||
| public/trace_task_results | |||
| @@ -1,4 +1,35 @@ | |||
| # Changelog | |||
| ## [v3.2.0](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-06-09 | |||
| ### ENHANCEMENTS | |||
| * ADD 集成邮件和平台站内信等通知系统 | |||
| * Fix 代码库二级页面-优化文件子目录浏览功能(#50388) | |||
| * Fix 代码库二级页面-优化commit提交详情页页面排版及数据显示(#50372) | |||
| * Fix 代码库二级页面-优化commit提交信息列表页加载方式和数据排序功能(#50348) | |||
| * Fix 代码库二级页面-优化创建发行版功能(#50346) | |||
| * Fix 代码库二级页面-优化标签列表页功能(#50344) | |||
| * Fix 代码库二级页面-优化发行版本列表页功能(#50345) | |||
| * Fix 代码库二级页面-优化分支列表页功能(#50343) | |||
| * Fix 其他问题优化(#51581) (#51343) (#51108) | |||
| --- | |||
| ### BUGFIXES | |||
| * Fix 发行版—标签跳转链接错误(#51666) | |||
| * Fix 文件预览报错(#51660) | |||
| * Fix 标签创建时间显示错误(#51658) | |||
| * Fix 分支列表中头像显示问题(#51656) | |||
| * Fix 文本信息过长(#51630) (#51626) | |||
| * Fix 版本库中附件下载400(#51625) | |||
| * Fix loading页面优化(#51588) | |||
| * Fix 提交详情页面优化(#51577) | |||
| * Fix 修复疑修复制功能(#51569) | |||
| * Fix 修复新建发行版用户信息显示错误的问题(#51665) | |||
| * Fix 修复查看文件详细信息报错的问题(#51561) | |||
| * Fix 修复提交记录中时间显示格式问题(#51526) | |||
| * Fix 组织下项目更加更新时间倒序排序(#50833) | |||
| ## [v3.1.0](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-06-09 | |||
| * ENHANCEMENTS | |||
| @@ -31,7 +62,7 @@ | |||
| ## [v3.0.3](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-08 | |||
| * BUGFIXES | |||
| * Fix 解决易修标题过长导致的排版问题(45469) | |||
| * Fix 解决疑修标题过长导致的排版问题(45469) | |||
| * Fix 解决合并请求详情页面排版错误的问题(45457) | |||
| * FIX 解决转移仓库界面专有名词描述错误的问题(45455) | |||
| * Fix 解决markdown格式文件自动生成数字排序的问题(45454) | |||
| @@ -0,0 +1,33 @@ | |||
| FROM ubuntu:18.04 | |||
| RUN apt update | |||
| RUN apt install -y openssl libssl-dev imagemagick git ruby-dev nodejs libmariadb-dev libmysqlclient-dev shared-mime-info libpq-dev libxml2-dev libxslt-dev | |||
| RUN DEBIAN_FRONTEND="noninteractive" apt -y install tzdata | |||
| RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime | |||
| WORKDIR /home/app/gitlink | |||
| ADD ./ /home/app/gitlink | |||
| RUN gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ | |||
| RUN gem update --system | |||
| RUN gem install bundler | |||
| RUN gem install rake | |||
| RUN rm -rf Gemfile.lock | |||
| #RUN cp config/configuration.yml.example config/configuration.yml | |||
| #RUN cp config/database.yml.example config/database.yml | |||
| #RUN touch config/redis.yml | |||
| #RUN touch config/elasticsearch.yml | |||
| RUN bundle install | |||
| EXPOSE 4000 | |||
| RUN rails s -p 4000 -b '0.0.0.0' | |||
| @@ -1,136 +1,148 @@ | |||
| source 'https://gems.ruby-china.com' | |||
| git_source(:github) { |repo| "https://github.com/#{repo}.git" } | |||
| gem 'rails', '~> 5.2.0' | |||
| gem 'mysql2', '>= 0.4.4', '< 0.6.0' | |||
| gem 'puma', '~> 3.11' | |||
| gem 'sass-rails', '~> 5.0' | |||
| gem 'uglifier', '>= 1.3.0' | |||
| # gem 'coffee-rails', '~> 4.2' | |||
| gem 'turbolinks', '~> 5' | |||
| gem 'jbuilder', '~> 2.5' | |||
| gem 'groupdate', '~> 4.1.0' | |||
| gem 'chartkick' | |||
| gem 'grape-entity', '~> 0.7.1' | |||
| gem 'kaminari', '~> 1.1', '>= 1.1.1' | |||
| gem 'bootsnap', '>= 1.1.0', require: false | |||
| gem 'chinese_pinyin' | |||
| gem 'rack-cors' | |||
| gem 'redis-rails' | |||
| gem 'roo-xls' | |||
| gem 'simple_xlsx_reader' | |||
| gem 'rubyzip' | |||
| gem 'spreadsheet' | |||
| gem 'ruby-ole' | |||
| # 导出为xlsx | |||
| gem 'axlsx', '~> 3.0.0.pre' | |||
| gem 'axlsx_rails', '~> 0.5.2' | |||
| gem 'oauth2' | |||
| #导出为pdf | |||
| gem 'pdfkit' | |||
| gem 'wkhtmltopdf-binary' | |||
| # gem 'request_store' | |||
| #gem 'iconv' | |||
| # markdown 转html | |||
| gem 'redcarpet', '~> 3.4' | |||
| gem 'rqrcode', '~> 0.10.1' | |||
| gem 'rqrcode_png' | |||
| gem 'acts-as-taggable-on', '~> 6.0' | |||
| # a tree structure | |||
| gem 'ancestry' | |||
| gem 'acts_as_list' | |||
| gem 'omniauth-cas' | |||
| # profiler Middleware | |||
| gem 'rack-mini-profiler' | |||
| # object-based searching | |||
| gem 'ransack' | |||
| group :development, :test do | |||
| gem 'rspec-rails', '~> 3.8' | |||
| gem 'rails-controller-testing' | |||
| end | |||
| group :development do | |||
| gem 'prettier' | |||
| gem 'rubocop', '~> 0.52.0' | |||
| gem 'solargraph', '~> 0.38.0' | |||
| gem 'awesome_print' | |||
| gem 'web-console', '>= 3.3.0' | |||
| gem 'listen', '>= 3.0.5', '< 3.2' | |||
| gem 'spring' | |||
| gem 'spring-watcher-listen', '~> 2.0.0' | |||
| gem "annotate", "~> 2.6.0" | |||
| end | |||
| group :test do | |||
| gem 'capybara', '>= 2.15', '< 4.0' | |||
| gem 'selenium-webdriver' | |||
| gem 'chromedriver-helper' | |||
| gem 'simplecov', '~>0.12.0', require: false | |||
| end | |||
| gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] | |||
| #编码检测 | |||
| gem 'rchardet', '~> 1.8' | |||
| # http client | |||
| gem 'faraday', '~> 0.15.4' | |||
| # view | |||
| gem 'active_decorator' | |||
| gem 'bootstrap', '~> 4.3.1' | |||
| gem 'jquery-rails' | |||
| gem 'simple_form' | |||
| gem 'font-awesome-sass', '4.7.0' | |||
| # i18n | |||
| gem 'rails-i18n', '~> 5.1' | |||
| # job | |||
| gem 'sidekiq' | |||
| gem 'sinatra' | |||
| gem 'whenever' | |||
| gem "sidekiq-cron", "~> 1.1" | |||
| # batch insert | |||
| gem 'bulk_insert' | |||
| # elasticsearch | |||
| gem 'searchkick' | |||
| gem 'aasm' | |||
| gem 'enumerize' | |||
| gem 'diffy' | |||
| gem 'deep_cloneable', '~> 3.0.0' | |||
| # oauth2 | |||
| gem 'omniauth', '~> 1.9.0' | |||
| gem 'omniauth-oauth2', '~> 1.6.0' | |||
| # global var | |||
| gem 'request_store' | |||
| # 敏感词汇 | |||
| gem 'harmonious_dictionary', '~> 0.0.1' | |||
| gem 'parallel', '~> 1.19', '>= 1.19.1' | |||
| # log | |||
| gem 'multi_logger' | |||
| gem 'letter_avatar' | |||
| source 'https://gems.ruby-china.com' | |||
| git_source(:github) { |repo| "https://github.com/#{repo}.git" } | |||
| gem 'rails', '~> 5.2.0' | |||
| gem 'mysql2', '>= 0.4.4', '< 0.6.0' | |||
| gem 'puma', '~> 3.11' | |||
| gem 'sass-rails', '~> 5.0' | |||
| gem 'uglifier', '>= 1.3.0' | |||
| # gem 'coffee-rails', '~> 4.2' | |||
| gem 'turbolinks', '~> 5' | |||
| gem 'jbuilder', '~> 2.5' | |||
| gem 'groupdate', '~> 4.1.0' | |||
| gem 'chartkick' | |||
| gem 'grape-entity', '~> 0.7.1' | |||
| gem 'kaminari', '~> 1.1', '>= 1.1.1' | |||
| gem 'bootsnap', '>= 1.1.0', require: false | |||
| gem 'chinese_pinyin' | |||
| gem 'rack-cors' | |||
| gem 'redis-rails' | |||
| gem 'roo-xls' | |||
| gem 'simple_xlsx_reader' | |||
| gem 'rubyzip' | |||
| gem 'spreadsheet' | |||
| gem 'ruby-ole' | |||
| # 导出为xlsx | |||
| gem 'axlsx', '~> 3.0.0.pre' | |||
| gem 'axlsx_rails', '~> 0.5.2' | |||
| gem 'oauth2' | |||
| #导出为pdf | |||
| gem 'pdfkit' | |||
| gem 'wkhtmltopdf-binary' | |||
| # gem 'request_store' | |||
| #gem 'iconv' | |||
| # markdown 转html | |||
| gem 'redcarpet', '~> 3.4' | |||
| gem 'rqrcode', '~> 0.10.1' | |||
| gem 'rqrcode_png' | |||
| gem 'acts-as-taggable-on', '~> 6.0' | |||
| # a tree structure | |||
| gem 'ancestry' | |||
| gem 'acts_as_list' | |||
| gem 'omniauth-cas' | |||
| # profiler Middleware | |||
| gem 'rack-mini-profiler' | |||
| # object-based searching | |||
| gem 'ransack' | |||
| group :development, :test do | |||
| gem 'rspec-rails', '~> 3.8' | |||
| gem 'rails-controller-testing' | |||
| end | |||
| group :development do | |||
| gem 'prettier' | |||
| gem 'rubocop', '~> 0.52.0' | |||
| gem 'solargraph', '~> 0.38.0' | |||
| gem 'awesome_print' | |||
| gem 'web-console', '>= 3.3.0' | |||
| gem 'listen', '>= 3.0.5', '< 3.2' | |||
| gem 'spring' | |||
| gem 'spring-watcher-listen', '~> 2.0.0' | |||
| gem "annotate", "~> 2.6.0" | |||
| end | |||
| group :test do | |||
| gem 'capybara', '>= 2.15', '< 4.0' | |||
| gem 'selenium-webdriver' | |||
| gem 'chromedriver-helper' | |||
| gem 'simplecov', '~>0.12.0', require: false | |||
| end | |||
| gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] | |||
| #编码检测 | |||
| gem 'rchardet', '~> 1.8' | |||
| # http client | |||
| gem 'faraday', '~> 0.15.4' | |||
| # view | |||
| gem 'active_decorator' | |||
| gem 'bootstrap', '~> 4.3.1' | |||
| gem 'jquery-rails' | |||
| gem 'simple_form' | |||
| gem 'font-awesome-sass', '4.7.0' | |||
| # i18n | |||
| gem 'rails-i18n', '~> 5.1' | |||
| # job | |||
| gem 'sidekiq',"5.2.8" | |||
| gem 'sinatra' | |||
| gem "sidekiq-cron", "1.2.0" | |||
| gem 'whenever' | |||
| # batch insert | |||
| gem 'bulk_insert' | |||
| # elasticsearch | |||
| gem 'searchkick' | |||
| gem 'aasm' | |||
| gem 'enumerize' | |||
| gem 'diffy' | |||
| gem 'deep_cloneable', '~> 3.0.0' | |||
| # oauth2 | |||
| gem 'omniauth', '~> 1.9.0' | |||
| gem 'omniauth-oauth2', '~> 1.6.0' | |||
| gem "omniauth-github" | |||
| gem "omniauth-rails_csrf_protection" | |||
| gem 'omniauth-gitee', '~> 1.0.0' | |||
| gem "omniauth-wechat-oauth2" | |||
| # global var | |||
| gem 'request_store' | |||
| # 敏感词汇 | |||
| gem 'harmonious_dictionary', '~> 0.0.1' | |||
| gem 'parallel', '~> 1.19', '>= 1.19.1' | |||
| # log | |||
| gem 'multi_logger' | |||
| gem 'letter_avatar' | |||
| gem 'jwt' | |||
| gem 'doorkeeper' | |||
| gem 'doorkeeper-jwt' | |||
| gem 'gitea-client', '~> 0.11.1' | |||
| @@ -98,7 +98,6 @@ GEM | |||
| chromedriver-helper (2.1.1) | |||
| archive-zip (~> 0.10) | |||
| nokogiri (~> 1.8) | |||
| chronic (0.10.2) | |||
| chunky_png (1.3.11) | |||
| concurrent-ruby (1.1.6) | |||
| connection_pool (2.2.2) | |||
| @@ -107,7 +106,12 @@ GEM | |||
| activerecord (>= 3.1.0, < 7) | |||
| diff-lcs (1.3) | |||
| diffy (3.3.0) | |||
| docile (1.1.5) | |||
| domain_name (0.5.20190701) | |||
| unf (>= 0.0.5, < 1.0.0) | |||
| doorkeeper (5.5.1) | |||
| railties (>= 5) | |||
| doorkeeper-jwt (0.4.1) | |||
| jwt (>= 2.1) | |||
| e2mmap (0.1.0) | |||
| elasticsearch (7.5.0) | |||
| elasticsearch-api (= 7.5.0) | |||
| @@ -131,6 +135,8 @@ GEM | |||
| fugit (1.4.1) | |||
| et-orbi (~> 1.1, >= 1.1.8) | |||
| raabro (~> 1.4) | |||
| gitea-client (0.10.5) | |||
| rest-client (~> 2.1.0) | |||
| globalid (0.4.2) | |||
| activesupport (>= 4.2.0) | |||
| grape-entity (0.7.1) | |||
| @@ -141,6 +147,9 @@ GEM | |||
| harmonious_dictionary (0.0.1) | |||
| hashie (3.6.0) | |||
| htmlentities (4.3.4) | |||
| http-accept (1.7.0) | |||
| http-cookie (1.0.5) | |||
| domain_name (~> 0.5) | |||
| i18n (1.8.2) | |||
| concurrent-ruby (~> 1.0) | |||
| io-like (0.3.1) | |||
| @@ -151,7 +160,6 @@ GEM | |||
| rails-dom-testing (>= 1, < 3) | |||
| railties (>= 4.2.0) | |||
| thor (>= 0.14, < 2.0) | |||
| json (2.6.0) | |||
| jwt (2.2.1) | |||
| kaminari (1.2.0) | |||
| activesupport (>= 4.1.0) | |||
| @@ -179,6 +187,9 @@ GEM | |||
| mimemagic (~> 0.3.2) | |||
| maruku (0.7.3) | |||
| method_source (0.9.2) | |||
| mime-types (3.4.1) | |||
| mime-types-data (~> 3.2015) | |||
| mime-types-data (3.2022.0105) | |||
| mimemagic (0.3.10) | |||
| nokogiri (~> 1) | |||
| rake | |||
| @@ -187,13 +198,12 @@ GEM | |||
| minitest (5.14.0) | |||
| msgpack (1.3.3) | |||
| multi_json (1.14.1) | |||
| multi_logger (0.2.0) | |||
| railties | |||
| multi_xml (0.6.0) | |||
| multipart-post (2.1.1) | |||
| mustermann (1.1.1) | |||
| ruby2_keywords (~> 0.0.1) | |||
| mysql2 (0.5.3) | |||
| netrc (0.11.0) | |||
| nio4r (2.5.2) | |||
| nokogiri (1.10.8) | |||
| mini_portile2 (~> 2.4.0) | |||
| @@ -247,10 +257,6 @@ GEM | |||
| bundler (>= 1.3.0) | |||
| railties (= 5.2.4.1) | |||
| sprockets-rails (>= 2.0.0) | |||
| rails-controller-testing (1.0.5) | |||
| actionpack (>= 5.0.1.rc1) | |||
| actionview (>= 5.0.1.rc1) | |||
| activesupport (>= 5.0.1.rc1) | |||
| rails-dom-testing (2.0.3) | |||
| activesupport (>= 4.2.0) | |||
| nokogiri (>= 1.6) | |||
| @@ -297,6 +303,11 @@ GEM | |||
| regexp_parser (1.7.0) | |||
| request_store (1.5.0) | |||
| rack (>= 1.4) | |||
| rest-client (2.1.0) | |||
| http-accept (>= 1.7.0, < 2.0) | |||
| http-cookie (>= 1.0.2, < 2.0) | |||
| mime-types (>= 1.16, < 4.0) | |||
| netrc (~> 0.8) | |||
| reverse_markdown (1.4.0) | |||
| nokogiri | |||
| roo (2.8.3) | |||
| @@ -380,11 +391,6 @@ GEM | |||
| simple_xlsx_reader (1.0.4) | |||
| nokogiri | |||
| rubyzip | |||
| simplecov (0.12.0) | |||
| docile (~> 1.1.0) | |||
| json (>= 1.8, < 3) | |||
| simplecov-html (~> 0.10.0) | |||
| simplecov-html (0.10.2) | |||
| sinatra (2.0.8.1) | |||
| mustermann (~> 1.0) | |||
| rack (~> 2.0) | |||
| @@ -428,6 +434,9 @@ GEM | |||
| thread_safe (~> 0.1) | |||
| uglifier (4.2.0) | |||
| execjs (>= 0.3.0, < 3) | |||
| unf (0.1.4) | |||
| unf_ext | |||
| unf_ext (0.0.8.2) | |||
| unicode-display_width (1.6.1) | |||
| web-console (3.7.0) | |||
| actionview (>= 5.0) | |||
| @@ -437,8 +446,6 @@ GEM | |||
| websocket-driver (0.7.1) | |||
| websocket-extensions (>= 0.1.0) | |||
| websocket-extensions (0.1.4) | |||
| whenever (1.0.0) | |||
| chronic (>= 0.6.3) | |||
| wkhtmltopdf-binary (0.12.5.4) | |||
| xpath (3.2.0) | |||
| nokogiri (~> 1.8) | |||
| @@ -466,18 +473,21 @@ DEPENDENCIES | |||
| chromedriver-helper | |||
| deep_cloneable (~> 3.0.0) | |||
| diffy | |||
| doorkeeper | |||
| doorkeeper-jwt | |||
| enumerize | |||
| faraday (~> 0.15.4) | |||
| font-awesome-sass (= 4.7.0) | |||
| gitea-client (~> 0.10.2) | |||
| grape-entity (~> 0.7.1) | |||
| groupdate (~> 4.1.0) | |||
| harmonious_dictionary (~> 0.0.1) | |||
| jbuilder (~> 2.5) | |||
| jquery-rails | |||
| jwt | |||
| kaminari (~> 1.1, >= 1.1.1) | |||
| letter_avatar | |||
| listen (>= 3.0.5, < 3.2) | |||
| multi_logger | |||
| mysql2 (>= 0.4.4, < 0.6.0) | |||
| oauth2 | |||
| omniauth (~> 1.9.0) | |||
| @@ -490,7 +500,6 @@ DEPENDENCIES | |||
| rack-cors | |||
| rack-mini-profiler | |||
| rails (~> 5.2.0) | |||
| rails-controller-testing | |||
| rails-i18n (~> 5.1) | |||
| ransack | |||
| rchardet (~> 1.8) | |||
| @@ -507,11 +516,10 @@ DEPENDENCIES | |||
| sass-rails (~> 5.0) | |||
| searchkick | |||
| selenium-webdriver | |||
| sidekiq | |||
| sidekiq-cron (~> 1.1) | |||
| sidekiq (= 5.2.8) | |||
| sidekiq-cron (= 1.2.0) | |||
| simple_form | |||
| simple_xlsx_reader | |||
| simplecov (~> 0.12.0) | |||
| sinatra | |||
| solargraph (~> 0.38.0) | |||
| spreadsheet | |||
| @@ -521,7 +529,6 @@ DEPENDENCIES | |||
| tzinfo-data | |||
| uglifier (>= 1.3.0) | |||
| web-console (>= 3.3.0) | |||
| whenever | |||
| wkhtmltopdf-binary | |||
| BUNDLED WITH | |||
| @@ -1,17 +1,27 @@ | |||
| Trustie (确实)是一个以大众化协同开发、开放式资源共享、持续性可信评估为核心机理,面向高校创新实践的在线协作平台。 | |||
| # GitLink - CCF开源创新服务平台 | |||
| ## 特性 | |||
| GitLink(确实开源)是中国计算机学会(CCF)官方指定的开源创新服务平台,旨在以“为开源创新服务”为使命,以“成为开源创新的汇聚地”为愿景,秉承“创新、开放、协作、共享”的价值观,致力于为大规模开源开放协同创新助力赋能,打造创新成果孵化和新工科人才培养的开源创新生态! | |||
| - 软件创作与生产深度融合的软件开发环境体系结构 软件自由创作和工程生产的高效衔接,适于软件开发中群体智慧的有效汇聚。 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/gitlink.png?raw=true" width=80% /></center> | |||
| ## 特色功能 | |||
| - 构件化协同开发环境的可扩展运行框架多样化工具的集成和联动,形成了强动态扩展能力的平台框架。 | |||
| - **分布式协作开发**:基于Git打造分布式代码托管环境,提供免费公、私有代码仓库,支持在线文件编辑、代码分支管理、协作贡献统计、代码仓库复刻(Fork)、贡献合并请求(PR)、群智贡献审阅等功能,让您的项目在这里健康、快速的成长! | |||
| - “互联网即资源库”的全新软件复用模式 成长式软件资源管理系统,实现了分散资源的知识融合、资源的可持续增长和有效复用。 | |||
| - **一站式过程管理**:提供疑修(Issue)、里程碑、通知提醒、标签归档等多样化任务管理工具,支持各类开发任务的发布、指派与跟踪,同时提供在线Wiki文档、组织多粒度管理等功能,为您搭建一站式的项目过程管理环境,让您的团队协作更高效、过程更透明! | |||
| ## 部署 | |||
| - **高效流水线运维**:融合DevOps思想,提供轻量级的工作流引擎(Engine),打通编码、测试、构建、部署等开发运维环节;支持自定义配置、代码静态扫描、构建自动触发、容器镜像托管等功能,同时支持接入第三方运维工具,让您的代码更加快速、可靠地形成高质量的产品! | |||
| - **多层次代码分析**:提供软件软代码和芯片RTL代码的溯源分析、文件级和组件级许可证识别及风险分析、输入性开源漏洞检测和加固建议,支持分析结果的多层次可视化展示,帮助您实施有效开源治理,厘清代码引用链,发现并消除漏洞感染链,为安全合规的开源引用保驾护航! | |||
| ### Depends Versions | |||
| - **多维度用户画像**:实时采集和分析平台中的各类开源资源数据,搭建多维度用户画像评估系统,提供开发活动统计、贡献度日历、用户能力建模、角色与专业定位分析等功能,让您在个人主页展示开发动态与创新能力! | |||
| ## 部署流程 | |||
| ### 依赖库 | |||
| * Ruby 2.4.5 | |||
| @@ -23,22 +33,53 @@ Trustie (确实)是一个以大众化协同开发、开放式资源共享、 | |||
| * imagemagick | |||
| ### Steps | |||
| #### 1. 克隆稳定版本 | |||
| ### 步骤 | |||
| (1)安装 Rails 必要的一些三方库: | |||
| - Mac OS X | |||
| ```bash | |||
| brew install imagemagick ghostscript libxml2 libxslt libiconv | |||
| ``` | |||
| git clone -b standalone https://git.trustie.net/jasder/forgeplus.git | |||
| - Ubuntu | |||
| ```bash | |||
| sudo apt-get update | |||
| sudo apt-get install -y openssl libssl-dev imagemagick git ruby-dev nodejs libmariadb-dev libmysqlclient-dev shared-mime-info libpq-dev libxml2-dev libxslt-dev | |||
| sudo DEBIAN_FRONTEND="noninteractive" apt-get install -y tzdata | |||
| sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime | |||
| ``` | |||
| #### 2. 安装依赖包 | |||
| (2)安装 Ruby, Rails 运行环境:[如何快速正确的安装 Ruby, Rails 运行环境](https://ruby-china.org/wiki/install_ruby_guide) | |||
| ```bash | |||
| #检验环境是否正确 | |||
| ruby -v | |||
| #ruby 2.4.x ... | |||
| gem -v | |||
| #3.x.x | |||
| bundle -v | |||
| #Bundler version 2.x.x | |||
| rails -v | |||
| #Rails 5.2.x | |||
| ``` | |||
| (3)克隆稳定版本 | |||
| ```bash | |||
| cd forgeplus && bundle install | |||
| git clone -b master https://gitlink.org.cn/Gitlink/forgeplus.git | |||
| ``` | |||
| #### 3. 配置初始化文件 | |||
| 进入项目根目录执行一下命令: | |||
| (4)安装依赖包 | |||
| ```bash | |||
| #进入目录 | |||
| cd forgeplus | |||
| #删除Gemfile.lock | |||
| rm -rf Gemfile.lock | |||
| #安装依赖包 | |||
| bundle install | |||
| ``` | |||
| (5)配置初始化文件:进入项目根目录执行以下命令 | |||
| ```bash | |||
| cp config/configuration.yml.example config/configuration.yml | |||
| cp config/database.yml.example config/database.yml | |||
| @@ -46,12 +87,8 @@ touch config/redis.yml | |||
| touch config/elasticsearch.yml | |||
| ``` | |||
| #### 4. 配置数据库 | |||
| 数据库配置信息请查看/config/database.yml文件, | |||
| 项目默认采用mysql数据库, 如需更改,请自行修改配置信息, | |||
| 默认配置如下: | |||
| ```bash | |||
| (6)配置数据库:数据库配置信息请查看/config/database.yml文件,项目默认采用mysql数据库, 如需更改,请自行修改配置信息,默认配置如下 | |||
| ```yaml | |||
| default: &default | |||
| adapter: mysql2 | |||
| host: 127.0.0.1 | |||
| @@ -60,16 +97,15 @@ default: &default | |||
| password: 123456 | |||
| ``` | |||
| #### 5. 配置gitea服务(可选) | |||
| **如需要部署自己的gitea平台,请参考gitea官方平台:https://docs.gitea.io/zh-cn/install-from-binary/** | |||
| (7)配置gitea服务(可选):如需要部署自己的gitea平台,请参考[gitea官方平台文档](https://docs.gitea.io/zh-cn/install-from-binary/)。因目前gitea平台api受限,暂时推荐从forge平台获取[gitea部署文件](https://www.gitlink.org.cn/Gitlink/gitea-binary)进行部署 | |||
| **因目前gitea平台api受限,暂时推荐从forge平台获取gitea部署文件进行部署:https://forgeplus.trustie.net/projects/Trustie/gitea-binary** | |||
| - 配置gitea服务步骤: | |||
| -- 部署gitea服务,并注册root账户 | |||
| -- 修改forge平台的 config/configuration.yml中的gitea服务指向地址,如: | |||
| **配置gitea服务步骤** | |||
| 1. 部署gitea服务,并注册root账户 | |||
| 2. 修改forge平台的 config/configuration.yml中的gitea服务指向地址,如: | |||
| ```ruby | |||
| ```yaml | |||
| gitea: | |||
| access_key_id: 'root' | |||
| access_key_secret: 'password' | |||
| @@ -77,102 +113,100 @@ gitea: | |||
| base_url: '/api/v1' | |||
| ``` | |||
| #### 6. 安装redis环境 | |||
| **请自行搜索各平台如何安装部署redis环境** | |||
| #### 7. 安装imagemagick插件 | |||
| - Mac OS X | |||
| ```bash | |||
| brew install imagemagick ghostscript | |||
| ``` | |||
| (8)配置/config/database.yml文件(安装redis环境:请自行搜索各平台如何安装部署redis环境) | |||
| ```yaml | |||
| default: &default | |||
| url: redis://localhost:6379 | |||
| db: 1 | |||
| - Linux | |||
| ```bash | |||
| sudo apt-get install -y imagemagick | |||
| production: | |||
| <<: *default | |||
| url: redis://localhost:6379 | |||
| ``` | |||
| #### 8. 创建数据库 | |||
| **开发环境为development, 生成环境为production** | |||
| (9)创建数据库:开发环境为development, 生成环境为production | |||
| ```bash | |||
| rails db:create RAILS_ENV=development | |||
| ``` | |||
| #### 9. 导入数据表结构 | |||
| (10)导入数据表结构 | |||
| ```bash | |||
| bundle exec rake sync_table_structure:import_csv | |||
| ``` | |||
| #### 10. 执行migrate迁移文件 | |||
| **开发环境为development, 生成环境为production** | |||
| (11)执行migrate迁移文件:开发环境为development, 生成环境为production | |||
| ```bash | |||
| rails db:migrate RAILS_ENV=development | |||
| ``` | |||
| #### 11. clone前端代码 | |||
| **将前端代码克隆到public/react目录下,目录结构应该是: public/react/build** | |||
| (12)clone前端代码:将前端代码克隆到public/react目录下,目录结构应该是: public/react/build | |||
| ```bash | |||
| git clone -b standalone https://git.trustie.net/jasder/build.git | |||
| git clone -b master https://gitlink.org.cn/Gitlink/build.git | |||
| ``` | |||
| #### 12. 启动redis(此处已mac系统为例) | |||
| (13)启动redis(此处以macOS系统为例) | |||
| ```bash | |||
| redis-server& | |||
| ``` | |||
| #### 13. 启动sidekiq | |||
| **开发环境为development, 生成环境为production** | |||
| (14)启动sidekiq:开发环境为development, 生成环境为production | |||
| ```bash | |||
| bundle exec sidekiq -C config/sidekiq.yml -e production -d | |||
| ``` | |||
| #### 14. 启动rails服务 | |||
| (15)启动rails服务 | |||
| ```bash | |||
| rails s | |||
| ``` | |||
| #### 15. 浏览器访问 | |||
| 在浏览器中输入如下地址访问: | |||
| (16)浏览器访问:在浏览器中输入如下地址访问 | |||
| ```bash | |||
| http://localhost:3000/ | |||
| ``` | |||
| #### 16. 其他说明 | |||
| 通过页面注册都第一个用户为平台管理员用户 | |||
| ## 页面展示 | |||
| - 代码库 | |||
| (17)其他说明:通过页面注册以第一个用户为平台管理员用户 | |||
|  | |||
| ## 页面展示 | |||
| - 项目列表 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/project_list.png?raw=true" width=50% /> | |||
| </center> | |||
| - 代码仓库 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/repo.png?raw=true" width=50% /> | |||
| </center> | |||
| - 任务管理 | |||
|  | |||
| - 任务查看 | |||
|  | |||
| - 任务指派 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/issues.png?raw=true" width=50% /> | |||
| </center> | |||
| - 合并请求 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/PR.png?raw=true" width=50% /> | |||
| </center> | |||
| - 引擎配置 | |||
| <center> | |||
| <img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/master/docs/figs/engine.png?raw=true" width=50% /> | |||
| </center> | |||
|  | |||
| - 里程碑 | |||
|  | |||
| ### API | |||
| - [API文档](https://forgeplus.trustie.net/docs/api) | |||
| - [API](showdoc.com.cn) | |||
| 账号:forgeplus@admin.com 密码:forge123 | |||
| ## 贡献代码 | |||
| 1. Fork 项目 | |||
| 2. 创建本地分支(git checkout -b my-new-feature) | |||
| 3. 提交更改 (git commit -am 'Add some feature') | |||
| 4. 推送到分支 (git push origin my-new-feature) | |||
| 5. 向源项目的 **develop** 分支发起 Pull Request | |||
| 我们期待您向GitLink提交贡献!在您贡献时,请遵循流程:[【Wiki文档-GitLink协作开发流程】](https://www.gitlink.org.cn/Gitlink/forgeplus/wiki "【Wiki文档-GitLink协作开发流程】") | |||
| #### 指导文档 | |||
| - [API文档](https://www.gitlink.org.cn/docs/api) | |||
| - [Git常用命令](https://git-scm.com/) | |||
| ## License | |||
| ## 许可证协议 | |||
| @@ -51,6 +51,51 @@ http://localhost:3000/api/accounts/remote_register | jq | |||
| |-- token |string|用户token| | |||
| 返回值 | |||
| ```json | |||
| { | |||
| "status": 0, | |||
| "message": "success", | |||
| "user": { | |||
| "id": 36400, | |||
| "token": "8c87a80d9cfacc92fcb2451845104f35119eda96" | |||
| } | |||
| } | |||
| ``` | |||
| --- | |||
| #### 独立注册接口 | |||
| ``` | |||
| POST accounts/register | |||
| ``` | |||
| *示例* | |||
| ```bash | |||
| curl -X POST \ | |||
| -d "login=2456233122@qq.com" \ | |||
| -d "password=djs_D_00001" \ | |||
| -d "namespace=16895620" \ | |||
| -d "code=forge" \ | |||
| http://localhost:3000/api/accounts/remote_register | jq | |||
| ``` | |||
| *请求参数说明:* | |||
| |参数名|必选|类型|说明| | |||
| |-|-|-|-| | |||
| |login |是|string |邮箱或者手机号 | | |||
| |namespace |是|string |登录名 | | |||
| |password |是|string |密码 | | |||
| |code |是|string |验证码 | | |||
| *返回参数说明:* | |||
| |参数名|类型|说明| | |||
| |-|-|-| | |||
| |user|json object |返回数据| | |||
| |-- id |int |用户id | | |||
| |-- token |string|用户token| | |||
| 返回值 | |||
| ```json | |||
| { | |||
| @@ -1,101 +1,137 @@ | |||
| //= require rails-ujs | |||
| //= require activestorage | |||
| //= require turbolinks | |||
| //= require jquery3 | |||
| //= require popper | |||
| //= require bootstrap-sprockets | |||
| //= require jquery.validate.min | |||
| //= require additional-methods.min | |||
| //= require bootstrap-notify | |||
| //= require jquery.cookie.min | |||
| //= require select2 | |||
| //= require moment.min | |||
| //= require jquery.cxselect | |||
| //= require bootstrap-datepicker | |||
| //= require bootstrap-datetimepicker | |||
| //= require bootstrap.viewer | |||
| //= require jquery.mloading | |||
| //= require jquery-confirm.min | |||
| //= require common | |||
| //= require echarts | |||
| //= require codemirror/lib/codemirror | |||
| //= require codemirror/mode/shell/shell | |||
| //= require editormd/editormd | |||
| //= require editormd/languages/zh-tw | |||
| //= require dragula/dragula | |||
| //= require_tree ./i18n | |||
| //= require_tree ./admins | |||
| $.ajaxSetup({ | |||
| beforeSend: function(xhr) { | |||
| xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')); | |||
| } | |||
| }); | |||
| // ******** select2 global config ******** | |||
| $.fn.select2.defaults.set('theme', 'bootstrap4'); | |||
| $.fn.select2.defaults.set('language', 'zh-CN'); | |||
| Turbolinks.setProgressBarDelay(200); | |||
| $.notifyDefaults({ | |||
| type: 'success', | |||
| z_index: 9999, | |||
| delay: 2000 | |||
| }); | |||
| function show_success_flash(){ | |||
| $.notify({ | |||
| message: '操作成功' | |||
| },{ | |||
| type: 'success' | |||
| }); | |||
| } | |||
| $(document).on('turbolinks:load', function(){ | |||
| $('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' }); | |||
| $('[data-toggle="popover"]').popover(); | |||
| // 图片查看大图 | |||
| $('img.preview-image').bootstrapViewer(); | |||
| // flash alert提示框自动关闭 | |||
| if($('.admin-alert-container .alert').length > 0){ | |||
| setTimeout(function(){ | |||
| $('.admin-alert-container .alert:not(.alert-danger)').alert('close'); | |||
| }, 2000); | |||
| setTimeout(function(){ | |||
| $('.admin-alert-container .alert.alert-danger').alert('close'); | |||
| }, 5000); | |||
| } | |||
| }); | |||
| $(document).on("turbolinks:before-cache", function () { | |||
| $('[data-toggle="tooltip"]').tooltip('hide'); | |||
| $('[data-toggle="popover"]').popover('hide'); | |||
| }); | |||
| // var progressBar = new Turbolinks.ProgressBar(); | |||
| // $(document).on('ajax:send', function(event){ | |||
| // console.log('ajax send', event); | |||
| // progressBar.setValue(0) | |||
| // progressBar.show() | |||
| // }); | |||
| // | |||
| // $(document).on('ajax:complete', function(event){ | |||
| // console.log('ajax complete', event); | |||
| // progressBar.setValue(1) | |||
| // progressBar.hide() // 分页时不触发,奇怪 | |||
| // }); | |||
| // $(document).on('ajax:success', function(event){ | |||
| // console.log('ajax success', event); | |||
| // }); | |||
| // $(document).on('ajax:error', function(event){ | |||
| // console.log('ajax error', event); | |||
| // }); | |||
| $(function () { | |||
| }); | |||
| //= require rails-ujs | |||
| //= require activestorage | |||
| //= require turbolinks | |||
| //= require jquery3 | |||
| //= require popper | |||
| //= require bootstrap-sprockets | |||
| //= require jquery.validate.min | |||
| //= require additional-methods.min | |||
| //= require bootstrap-notify | |||
| //= require jquery.cookie.min | |||
| //= require select2 | |||
| //= require moment.min | |||
| //= require jquery.cxselect | |||
| //= require bootstrap-datepicker | |||
| //= require bootstrap-datetimepicker | |||
| //= require bootstrap.viewer | |||
| //= require bootstrap/bootstrap-toggle | |||
| //= require jquery.mloading | |||
| //= require jquery-confirm.min | |||
| //= require common | |||
| //= require echarts | |||
| //= require codemirror/lib/codemirror | |||
| //= require codemirror/mode/shell/shell | |||
| //= require editormd/editormd | |||
| //= require editormd/languages/zh-tw | |||
| //= require dragula/dragula | |||
| //= require_tree ./i18n | |||
| //= require_tree ./admins | |||
| $.ajaxSetup({ | |||
| beforeSend: function(xhr) { | |||
| xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')); | |||
| } | |||
| }); | |||
| // ******** select2 global config ******** | |||
| $.fn.select2.defaults.set('theme', 'bootstrap4'); | |||
| $.fn.select2.defaults.set('language', 'zh-CN'); | |||
| Turbolinks.setProgressBarDelay(200); | |||
| $.notifyDefaults({ | |||
| type: 'success', | |||
| z_index: 9999, | |||
| delay: 2000 | |||
| }); | |||
| function show_success_flash(){ | |||
| $.notify({ | |||
| message: '操作成功' | |||
| },{ | |||
| type: 'success' | |||
| }); | |||
| } | |||
| $(document).on('turbolinks:load', function(){ | |||
| $('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' }); | |||
| $('[data-toggle="popover"]').popover(); | |||
| // 图片查看大图 | |||
| $('img.preview-image').bootstrapViewer(); | |||
| // flash alert提示框自动关闭 | |||
| if($('.admin-alert-container .alert').length > 0){ | |||
| setTimeout(function(){ | |||
| $('.admin-alert-container .alert:not(.alert-danger)').alert('close'); | |||
| }, 2000); | |||
| setTimeout(function(){ | |||
| $('.admin-alert-container .alert.alert-danger').alert('close'); | |||
| }, 5000); | |||
| } | |||
| }); | |||
| $(document).on("turbolinks:before-cache", function () { | |||
| $('[data-toggle="tooltip"]').tooltip('hide'); | |||
| $('[data-toggle="popover"]').popover('hide'); | |||
| }); | |||
| // var progressBar = new Turbolinks.ProgressBar(); | |||
| // $(document).on('ajax:send', function(event){ | |||
| // console.log('ajax send', event); | |||
| // progressBar.setValue(0) | |||
| // progressBar.show() | |||
| // }); | |||
| // | |||
| // $(document).on('ajax:complete', function(event){ | |||
| // console.log('ajax complete', event); | |||
| // progressBar.setValue(1) | |||
| // progressBar.hide() // 分页时不触发,奇怪 | |||
| // }); | |||
| // $(document).on('ajax:success', function(event){ | |||
| // console.log('ajax success', event); | |||
| // }); | |||
| // $(document).on('ajax:error', function(event){ | |||
| // console.log('ajax error', event); | |||
| // }); | |||
| $(function () { | |||
| }); | |||
| $(document).on('turbolinks:load', function() { | |||
| $('.logo-item-left').on("change", 'input[type="file"]', function () { | |||
| var $fileInput = $(this); | |||
| var file = this.files[0]; | |||
| var imageType = /image.*/; | |||
| if (file && file.type.match(imageType)) { | |||
| var reader = new FileReader(); | |||
| reader.onload = function () { | |||
| var $box = $fileInput.parent(); | |||
| $box.find('img').attr('src', reader.result).css('display', 'block'); | |||
| $box.addClass('has-img'); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } else { | |||
| } | |||
| }); | |||
| $('.attachment-item-left').on("change", 'input[type="file"]', function () { | |||
| var $fileInput = $(this); | |||
| var file = this.files[0]; | |||
| var imageType = /image.*/; | |||
| if (file && file.type.match(imageType)) { | |||
| var reader = new FileReader(); | |||
| reader.onload = function () { | |||
| var $box = $fileInput.parent(); | |||
| $box.find('img').attr('src', reader.result).css('display', 'block'); | |||
| $box.addClass('has-img'); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } else { | |||
| } | |||
| }); | |||
| }) | |||
| @@ -0,0 +1,141 @@ | |||
| /* | |||
| * @Description: Do not edit | |||
| * @Date: 2021-08-31 11:16:45 | |||
| * @LastEditors: viletyy | |||
| * @Author: viletyy | |||
| * @LastEditTime: 2021-08-31 14:19:46 | |||
| * @FilePath: /forgeplus/app/assets/javascripts/admins/system_notifications/index.js | |||
| */ | |||
| $(document).on('turbolinks:load', function(){ | |||
| var showSuccessNotify = function() { | |||
| $.notify({ | |||
| message: '操作成功' | |||
| },{ | |||
| type: 'success' | |||
| }); | |||
| } | |||
| // close user | |||
| $('.project-list-container').on('click', '.recommend-action', function(){ | |||
| var $closeAction = $(this); | |||
| var $uncloseAction = $closeAction.siblings('.unrecommend-action'); | |||
| var $editAction = $closeAction.siblings('.edit-recommend-action'); | |||
| var keywordID = $closeAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认将该项目设置为推荐项目吗?', | |||
| ok: function(){ | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| recommend: true, | |||
| recommend_index: 1 | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.hide(); | |||
| $uncloseAction.show(); | |||
| $editAction.show(); | |||
| $(".project-item-"+keywordID).children('td').eq(5).text("√") | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| }); | |||
| // unclose user | |||
| $('.project-list-container').on('click', '.unrecommend-action', function(){ | |||
| var $uncloseAction = $(this); | |||
| var $closeAction = $uncloseAction.siblings('.recommend-action'); | |||
| var $editAction = $closeAction.siblings('.edit-recommend-action'); | |||
| var keywordID = $uncloseAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认取消该推荐项目吗?', | |||
| ok: function () { | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| recommend: false, | |||
| recommend_index: 0 | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.show(); | |||
| $uncloseAction.hide(); | |||
| $editAction.hide(); | |||
| $(".project-item-"+keywordID).children('td').eq(5).text("") | |||
| } | |||
| }); | |||
| } | |||
| }) | |||
| }); | |||
| // close user | |||
| $('.project-list-container').on('click', '.pinned-action', function(){ | |||
| var $closeAction = $(this); | |||
| var $uncloseAction = $closeAction.siblings('.unpinned-action'); | |||
| var keywordID = $closeAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认将该项目设置为精选项目吗?', | |||
| ok: function(){ | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| is_pinned: true, | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.hide(); | |||
| $uncloseAction.show(); | |||
| $(".project-item-"+keywordID).children('td').eq(4).text("√") | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| }); | |||
| // unclose user | |||
| $('.project-list-container').on('click', '.unpinned-action', function(){ | |||
| var $uncloseAction = $(this); | |||
| var $closeAction = $uncloseAction.siblings('.pinned-action'); | |||
| var keywordID = $uncloseAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认取消该精选项目吗?', | |||
| ok: function () { | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| is_pinned: false, | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.show(); | |||
| $uncloseAction.hide(); | |||
| $(".project-item-"+keywordID).children('td').eq(4).text("") | |||
| } | |||
| }); | |||
| } | |||
| }) | |||
| }); | |||
| }) | |||
| @@ -0,0 +1,76 @@ | |||
| /* | |||
| * @Description: Do not edit | |||
| * @Date: 2021-08-31 11:16:45 | |||
| * @LastEditors: viletyy | |||
| * @Author: viletyy | |||
| * @LastEditTime: 2021-08-31 14:19:46 | |||
| * @FilePath: /forgeplus/app/assets/javascripts/admins/system_notifications/index.js | |||
| */ | |||
| $(document).on('turbolinks:load', function(){ | |||
| var showSuccessNotify = function() { | |||
| $.notify({ | |||
| message: '操作成功' | |||
| },{ | |||
| type: 'success' | |||
| }); | |||
| } | |||
| // close user | |||
| $('.system-notification-list-container').on('click', '.close-action', function(){ | |||
| var $closeAction = $(this); | |||
| var $uncloseAction = $closeAction.siblings('.unclose-action'); | |||
| var keywordID = $closeAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认取消置顶吗?', | |||
| ok: function(){ | |||
| $.ajax({ | |||
| url: '/admins/system_notifications/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| system_notification: { | |||
| is_top: false | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.hide(); | |||
| $uncloseAction.show(); | |||
| $(".system-notification-item-"+keywordID).children('td').eq(3).text("") | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| }); | |||
| // unclose user | |||
| $('.system-notification-list-container').on('click', '.unclose-action', function(){ | |||
| var $uncloseAction = $(this); | |||
| var $closeAction = $uncloseAction.siblings('.close-action'); | |||
| var keywordID = $uncloseAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认置顶吗?', | |||
| ok: function () { | |||
| $.ajax({ | |||
| url: '/admins/system_notifications/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| system_notification: { | |||
| is_top: true | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.show(); | |||
| $uncloseAction.hide(); | |||
| $(".system-notification-item-"+keywordID).children('td').eq(3).text("√") | |||
| } | |||
| }); | |||
| } | |||
| }) | |||
| }); | |||
| }) | |||
| @@ -0,0 +1,180 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| +function ($) { | |||
| 'use strict'; | |||
| // TOGGLE PUBLIC CLASS DEFINITION | |||
| // ============================== | |||
| var Toggle = function (element, options) { | |||
| this.$element = $(element) | |||
| this.options = $.extend({}, this.defaults(), options) | |||
| this.render() | |||
| } | |||
| Toggle.VERSION = '2.2.0' | |||
| Toggle.DEFAULTS = { | |||
| on: 'On', | |||
| off: 'Off', | |||
| onstyle: 'primary', | |||
| offstyle: 'default', | |||
| size: 'normal', | |||
| style: '', | |||
| width: null, | |||
| height: null | |||
| } | |||
| Toggle.prototype.defaults = function() { | |||
| return { | |||
| on: this.$element.attr('data-on') || Toggle.DEFAULTS.on, | |||
| off: this.$element.attr('data-off') || Toggle.DEFAULTS.off, | |||
| onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle, | |||
| offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle, | |||
| size: this.$element.attr('data-size') || Toggle.DEFAULTS.size, | |||
| style: this.$element.attr('data-style') || Toggle.DEFAULTS.style, | |||
| width: this.$element.attr('data-width') || Toggle.DEFAULTS.width, | |||
| height: this.$element.attr('data-height') || Toggle.DEFAULTS.height | |||
| } | |||
| } | |||
| Toggle.prototype.render = function () { | |||
| this._onstyle = 'btn-' + this.options.onstyle | |||
| this._offstyle = 'btn-' + this.options.offstyle | |||
| var size = this.options.size === 'large' ? 'btn-lg' | |||
| : this.options.size === 'small' ? 'btn-sm' | |||
| : this.options.size === 'mini' ? 'btn-xs' | |||
| : '' | |||
| var $toggleOn = $('<label class="btn">').html(this.options.on) | |||
| .addClass(this._onstyle + ' ' + size) | |||
| var $toggleOff = $('<label class="btn">').html(this.options.off) | |||
| .addClass(this._offstyle + ' ' + size + ' active') | |||
| var $toggleHandle = $('<span class="toggle-handle btn btn-default">') | |||
| .addClass(size) | |||
| var $toggleGroup = $('<div class="toggle-group">') | |||
| .append($toggleOn, $toggleOff, $toggleHandle) | |||
| var $toggle = $('<div class="toggle btn" data-toggle="toggle">') | |||
| .addClass( this.$element.prop('checked') ? this._onstyle : this._offstyle+' off' ) | |||
| .addClass(size).addClass(this.options.style) | |||
| this.$element.wrap($toggle) | |||
| $.extend(this, { | |||
| $toggle: this.$element.parent(), | |||
| $toggleOn: $toggleOn, | |||
| $toggleOff: $toggleOff, | |||
| $toggleGroup: $toggleGroup | |||
| }) | |||
| this.$toggle.append($toggleGroup) | |||
| var width = this.options.width || Math.max($toggleOn.outerWidth(), $toggleOff.outerWidth())+($toggleHandle.outerWidth()/2) | |||
| var height = this.options.height || Math.max($toggleOn.outerHeight(), $toggleOff.outerHeight()) | |||
| $toggleOn.addClass('toggle-on') | |||
| $toggleOff.addClass('toggle-off') | |||
| this.$toggle.css({ width: width, height: height }) | |||
| if (this.options.height) { | |||
| $toggleOn.css('line-height', $toggleOn.height() + 'px') | |||
| $toggleOff.css('line-height', $toggleOff.height() + 'px') | |||
| } | |||
| this.update(true) | |||
| this.trigger(true) | |||
| } | |||
| Toggle.prototype.toggle = function () { | |||
| if (this.$element.prop('checked')) this.off() | |||
| else this.on() | |||
| } | |||
| Toggle.prototype.on = function (silent) { | |||
| if (this.$element.prop('disabled')) return false | |||
| this.$toggle.removeClass(this._offstyle + ' off').addClass(this._onstyle) | |||
| this.$element.prop('checked', true) | |||
| if (!silent) this.trigger() | |||
| } | |||
| Toggle.prototype.off = function (silent) { | |||
| if (this.$element.prop('disabled')) return false | |||
| this.$toggle.removeClass(this._onstyle).addClass(this._offstyle + ' off') | |||
| this.$element.prop('checked', false) | |||
| if (!silent) this.trigger() | |||
| } | |||
| Toggle.prototype.enable = function () { | |||
| this.$toggle.removeAttr('disabled') | |||
| this.$element.prop('disabled', false) | |||
| } | |||
| Toggle.prototype.disable = function () { | |||
| this.$toggle.attr('disabled', 'disabled') | |||
| this.$element.prop('disabled', true) | |||
| } | |||
| Toggle.prototype.update = function (silent) { | |||
| if (this.$element.prop('disabled')) this.disable() | |||
| else this.enable() | |||
| if (this.$element.prop('checked')) this.on(silent) | |||
| else this.off(silent) | |||
| } | |||
| Toggle.prototype.trigger = function (silent) { | |||
| this.$element.off('change.bs.toggle') | |||
| if (!silent) this.$element.change() | |||
| this.$element.on('change.bs.toggle', $.proxy(function() { | |||
| this.update() | |||
| }, this)) | |||
| } | |||
| Toggle.prototype.destroy = function() { | |||
| this.$element.off('change.bs.toggle') | |||
| this.$toggleGroup.remove() | |||
| this.$element.removeData('bs.toggle') | |||
| this.$element.unwrap() | |||
| } | |||
| // TOGGLE PLUGIN DEFINITION | |||
| // ======================== | |||
| function Plugin(option) { | |||
| return this.each(function () { | |||
| var $this = $(this) | |||
| var data = $this.data('bs.toggle') | |||
| var options = typeof option == 'object' && option | |||
| if (!data) $this.data('bs.toggle', (data = new Toggle(this, options))) | |||
| if (typeof option == 'string' && data[option]) data[option]() | |||
| }) | |||
| } | |||
| var old = $.fn.bootstrapToggle | |||
| $.fn.bootstrapToggle = Plugin | |||
| $.fn.bootstrapToggle.Constructor = Toggle | |||
| // TOGGLE NO CONFLICT | |||
| // ================== | |||
| $.fn.toggle.noConflict = function () { | |||
| $.fn.bootstrapToggle = old | |||
| return this | |||
| } | |||
| // TOGGLE DATA-API | |||
| // =============== | |||
| $(function() { | |||
| $('input[type=checkbox][data-toggle^=toggle]').bootstrapToggle() | |||
| }) | |||
| $(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function(e) { | |||
| var $checkbox = $(this).find('input[type=checkbox]') | |||
| $checkbox.bootstrapToggle('toggle') | |||
| e.preventDefault() | |||
| }) | |||
| }(jQuery); | |||
| @@ -0,0 +1,9 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('<label class="btn">').html(this.options.on).addClass(this._onstyle+" "+b),d=a('<label class="btn">').html(this.options.off).addClass(this._offstyle+" "+b+" active"),e=a('<span class="toggle-handle btn btn-default">').addClass(b),f=a('<div class="toggle-group">').append(c,d,e),g=a('<div class="toggle btn" data-toggle="toggle">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(b).addClass(this.options.style);this.$element.wrap(g),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:c,$toggleOff:d,$toggleGroup:f}),this.$toggle.append(f);var h=this.options.width||Math.max(c.outerWidth(),d.outerWidth())+e.outerWidth()/2,i=this.options.height||Math.max(c.outerHeight(),d.outerHeight());c.addClass("toggle-on"),d.addClass("toggle-off"),this.$toggle.css({width:h,height:i}),this.options.height&&(c.css("line-height",c.height()+"px"),d.css("line-height",d.height()+"px")),this.update(!0),this.trigger(!0)},c.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},c.prototype.on=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),void(a||this.trigger()))},c.prototype.off=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),void(a||this.trigger()))},c.prototype.enable=function(){this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},c.prototype.disable=function(){this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},c.prototype.update=function(a){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(a):this.off(a)},c.prototype.trigger=function(b){this.$element.off("change.bs.toggle"),b||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},c.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var d=a.fn.bootstrapToggle;a.fn.bootstrapToggle=b,a.fn.bootstrapToggle.Constructor=c,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=d,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(b){var c=a(this).find("input[type=checkbox]");c.bootstrapToggle("toggle"),b.preventDefault()})}(jQuery); | |||
| //# sourceMappingURL=bootstrap-toggle.min.js.map | |||
| @@ -0,0 +1 @@ | |||
| {"version":3,"file":"bootstrap-toggle.min.js","sources":["bootstrap-toggle.js"],"names":["$","Plugin","option","this","each","$this","data","options","Toggle","element","$element","extend","defaults","render","VERSION","DEFAULTS","on","off","onstyle","offstyle","size","style","width","height","prototype","attr","_onstyle","_offstyle","$toggleOn","html","addClass","$toggleOff","$toggleHandle","$toggleGroup","append","$toggle","prop","wrap","parent","Math","max","outerWidth","outerHeight","css","update","trigger","toggle","silent","removeClass","enable","removeAttr","disable","change","proxy","destroy","remove","removeData","unwrap","old","fn","bootstrapToggle","Constructor","noConflict","document","e","$checkbox","find","preventDefault","jQuery"],"mappings":";;;;;;;CASE,SAAUA,GACV,YAoID,SAASC,GAAOC,GACf,MAAOC,MAAKC,KAAK,WAChB,GAAIC,GAAUL,EAAEG,MACZG,EAAUD,EAAMC,KAAK,aACrBC,EAA2B,gBAAVL,IAAsBA,CAEtCI,IAAMD,EAAMC,KAAK,YAAcA,EAAO,GAAIE,GAAOL,KAAMI,IACvC,gBAAVL,IAAsBI,EAAKJ,IAASI,EAAKJ,OAtItD,GAAIM,GAAS,SAAUC,EAASF,GAC/BJ,KAAKO,SAAYV,EAAES,GACnBN,KAAKI,QAAYP,EAAEW,UAAWR,KAAKS,WAAYL,GAC/CJ,KAAKU,SAGNL,GAAOM,QAAW,QAElBN,EAAOO,UACNC,GAAI,KACJC,IAAK,MACLC,QAAS,UACTC,SAAU,UACVC,KAAM,SACNC,MAAO,GACPC,MAAO,KACPC,OAAQ,MAGTf,EAAOgB,UAAUZ,SAAW,WAC3B,OACCI,GAAIb,KAAKO,SAASe,KAAK,YAAcjB,EAAOO,SAASC,GACrDC,IAAKd,KAAKO,SAASe,KAAK,aAAejB,EAAOO,SAASE,IACvDC,QAASf,KAAKO,SAASe,KAAK,iBAAmBjB,EAAOO,SAASG,QAC/DC,SAAUhB,KAAKO,SAASe,KAAK,kBAAoBjB,EAAOO,SAASI,SACjEC,KAAMjB,KAAKO,SAASe,KAAK,cAAgBjB,EAAOO,SAASK,KACzDC,MAAOlB,KAAKO,SAASe,KAAK,eAAiBjB,EAAOO,SAASM,MAC3DC,MAAOnB,KAAKO,SAASe,KAAK,eAAiBjB,EAAOO,SAASO,MAC3DC,OAAQpB,KAAKO,SAASe,KAAK,gBAAkBjB,EAAOO,SAASQ,SAI/Df,EAAOgB,UAAUX,OAAS,WACzBV,KAAKuB,SAAW,OAASvB,KAAKI,QAAQW,QACtCf,KAAKwB,UAAY,OAASxB,KAAKI,QAAQY,QACvC,IAAIC,GAA6B,UAAtBjB,KAAKI,QAAQa,KAAmB,SAClB,UAAtBjB,KAAKI,QAAQa,KAAmB,SACV,SAAtBjB,KAAKI,QAAQa,KAAkB,SAC/B,GACCQ,EAAY5B,EAAE,uBAAuB6B,KAAK1B,KAAKI,QAAQS,IACzDc,SAAS3B,KAAKuB,SAAW,IAAMN,GAC7BW,EAAa/B,EAAE,uBAAuB6B,KAAK1B,KAAKI,QAAQU,KAC1Da,SAAS3B,KAAKwB,UAAY,IAAMP,EAAO,WACrCY,EAAgBhC,EAAE,gDACpB8B,SAASV,GACPa,EAAejC,EAAE,8BACnBkC,OAAON,EAAWG,EAAYC,GAC5BG,EAAUnC,EAAE,iDACd8B,SAAU3B,KAAKO,SAAS0B,KAAK,WAAajC,KAAKuB,SAAWvB,KAAKwB,UAAU,QACzEG,SAASV,GAAMU,SAAS3B,KAAKI,QAAQc,MAEvClB,MAAKO,SAAS2B,KAAKF,GACnBnC,EAAEW,OAAOR,MACRgC,QAAShC,KAAKO,SAAS4B,SACvBV,UAAWA,EACXG,WAAYA,EACZE,aAAcA,IAEf9B,KAAKgC,QAAQD,OAAOD,EAEpB,IAAIX,GAAQnB,KAAKI,QAAQe,OAASiB,KAAKC,IAAIZ,EAAUa,aAAcV,EAAWU,cAAeT,EAAcS,aAAa,EACpHlB,EAASpB,KAAKI,QAAQgB,QAAUgB,KAAKC,IAAIZ,EAAUc,cAAeX,EAAWW,cACjFd,GAAUE,SAAS,aACnBC,EAAWD,SAAS,cACpB3B,KAAKgC,QAAQQ,KAAMrB,MAAOA,EAAOC,OAAQA,IACrCpB,KAAKI,QAAQgB,SAChBK,EAAUe,IAAI,cAAef,EAAUL,SAAW,MAClDQ,EAAWY,IAAI,cAAeZ,EAAWR,SAAW,OAErDpB,KAAKyC,QAAO,GACZzC,KAAK0C,SAAQ,IAGdrC,EAAOgB,UAAUsB,OAAS,WACrB3C,KAAKO,SAAS0B,KAAK,WAAYjC,KAAKc,MACnCd,KAAKa,MAGXR,EAAOgB,UAAUR,GAAK,SAAU+B,GAC/B,MAAI5C,MAAKO,SAAS0B,KAAK,aAAoB,GAC3CjC,KAAKgC,QAAQa,YAAY7C,KAAKwB,UAAY,QAAQG,SAAS3B,KAAKuB,UAChEvB,KAAKO,SAAS0B,KAAK,WAAW,QACzBW,GAAQ5C,KAAK0C,aAGnBrC,EAAOgB,UAAUP,IAAM,SAAU8B,GAChC,MAAI5C,MAAKO,SAAS0B,KAAK,aAAoB,GAC3CjC,KAAKgC,QAAQa,YAAY7C,KAAKuB,UAAUI,SAAS3B,KAAKwB,UAAY,QAClExB,KAAKO,SAAS0B,KAAK,WAAW,QACzBW,GAAQ5C,KAAK0C,aAGnBrC,EAAOgB,UAAUyB,OAAS,WACzB9C,KAAKgC,QAAQe,WAAW,YACxB/C,KAAKO,SAAS0B,KAAK,YAAY,IAGhC5B,EAAOgB,UAAU2B,QAAU,WAC1BhD,KAAKgC,QAAQV,KAAK,WAAY,YAC9BtB,KAAKO,SAAS0B,KAAK,YAAY,IAGhC5B,EAAOgB,UAAUoB,OAAS,SAAUG,GAC/B5C,KAAKO,SAAS0B,KAAK,YAAajC,KAAKgD,UACpChD,KAAK8C,SACN9C,KAAKO,SAAS0B,KAAK,WAAYjC,KAAKa,GAAG+B,GACtC5C,KAAKc,IAAI8B,IAGfvC,EAAOgB,UAAUqB,QAAU,SAAUE,GACpC5C,KAAKO,SAASO,IAAI,oBACb8B,GAAQ5C,KAAKO,SAAS0C,SAC3BjD,KAAKO,SAASM,GAAG,mBAAoBhB,EAAEqD,MAAM,WAC5ClD,KAAKyC,UACHzC,QAGJK,EAAOgB,UAAU8B,QAAU,WAC1BnD,KAAKO,SAASO,IAAI,oBAClBd,KAAK8B,aAAasB,SAClBpD,KAAKO,SAAS8C,WAAW,aACzBrD,KAAKO,SAAS+C,SAiBf,IAAIC,GAAM1D,EAAE2D,GAAGC,eAEf5D,GAAE2D,GAAGC,gBAA8B3D,EACnCD,EAAE2D,GAAGC,gBAAgBC,YAAcrD,EAKnCR,EAAE2D,GAAGb,OAAOgB,WAAa,WAExB,MADA9D,GAAE2D,GAAGC,gBAAkBF,EAChBvD,MAMRH,EAAE,WACDA,EAAE,6CAA6C4D,oBAGhD5D,EAAE+D,UAAU/C,GAAG,kBAAmB,2BAA4B,SAASgD,GACtE,GAAIC,GAAYjE,EAAEG,MAAM+D,KAAK,uBAC7BD,GAAUL,gBAAgB,UAC1BI,EAAEG,oBAGFC"} | |||
| @@ -0,0 +1,180 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap2-toggle.js v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| +function ($) { | |||
| 'use strict'; | |||
| // TOGGLE PUBLIC CLASS DEFINITION | |||
| // ============================== | |||
| var Toggle = function (element, options) { | |||
| this.$element = $(element) | |||
| this.options = $.extend({}, this.defaults(), options) | |||
| this.render() | |||
| } | |||
| Toggle.VERSION = '2.2.0' | |||
| Toggle.DEFAULTS = { | |||
| on: 'On', | |||
| off: 'Off', | |||
| onstyle: 'primary', | |||
| offstyle: 'default', | |||
| size: 'normal', | |||
| style: '', | |||
| width: null, | |||
| height: null | |||
| } | |||
| Toggle.prototype.defaults = function() { | |||
| return { | |||
| on: this.$element.attr('data-on') || Toggle.DEFAULTS.on, | |||
| off: this.$element.attr('data-off') || Toggle.DEFAULTS.off, | |||
| onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle, | |||
| offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle, | |||
| size: this.$element.attr('data-size') || Toggle.DEFAULTS.size, | |||
| style: this.$element.attr('data-style') || Toggle.DEFAULTS.style, | |||
| width: this.$element.attr('data-width') || Toggle.DEFAULTS.width, | |||
| height: this.$element.attr('data-height') || Toggle.DEFAULTS.height | |||
| } | |||
| } | |||
| Toggle.prototype.render = function () { | |||
| this._onstyle = 'btn-' + this.options.onstyle | |||
| this._offstyle = 'btn-' + this.options.offstyle | |||
| var size = this.options.size === 'large' ? 'btn-large' | |||
| : this.options.size === 'small' ? 'btn-small' | |||
| : this.options.size === 'mini' ? 'btn-mini' | |||
| : '' | |||
| var $toggleOn = $('<label class="btn">').html(this.options.on) | |||
| .addClass(this._onstyle + ' ' + size) | |||
| var $toggleOff = $('<label class="btn">').html(this.options.off) | |||
| .addClass(this._offstyle + ' ' + size + ' active') | |||
| var $toggleHandle = $('<span class="toggle-handle btn btn-default">') | |||
| .addClass(size) | |||
| var $toggleGroup = $('<div class="toggle-group">') | |||
| .append($toggleOn, $toggleOff, $toggleHandle) | |||
| var $toggle = $('<div class="toggle btn" data-toggle="toggle">') | |||
| .addClass( this.$element.prop('checked') ? this._onstyle : this._offstyle+' off' ) | |||
| .addClass(size).addClass(this.options.style) | |||
| this.$element.wrap($toggle) | |||
| $.extend(this, { | |||
| $toggle: this.$element.parent(), | |||
| $toggleOn: $toggleOn, | |||
| $toggleOff: $toggleOff, | |||
| $toggleGroup: $toggleGroup | |||
| }) | |||
| this.$toggle.append($toggleGroup) | |||
| var width = this.options.width || Math.max($toggleOn.width(), $toggleOff.width())+($toggleHandle.outerWidth()/2) | |||
| var height = this.options.height || Math.max($toggleOn.height(), $toggleOff.height()) | |||
| $toggleOn.addClass('toggle-on') | |||
| $toggleOff.addClass('toggle-off') | |||
| this.$toggle.css({ width: width, height: height }) | |||
| if (this.options.height) { | |||
| $toggleOn.css('line-height', $toggleOn.height() + 'px') | |||
| $toggleOff.css('line-height', $toggleOff.height() + 'px') | |||
| } | |||
| this.update(true) | |||
| this.trigger(true) | |||
| } | |||
| Toggle.prototype.toggle = function () { | |||
| if (this.$element.prop('checked')) this.off() | |||
| else this.on() | |||
| } | |||
| Toggle.prototype.on = function (silent) { | |||
| if (this.$element.prop('disabled')) return false | |||
| this.$toggle.removeClass(this._offstyle + ' off').addClass(this._onstyle) | |||
| this.$element.prop('checked', true) | |||
| if (!silent) this.trigger() | |||
| } | |||
| Toggle.prototype.off = function (silent) { | |||
| if (this.$element.prop('disabled')) return false | |||
| this.$toggle.removeClass(this._onstyle).addClass(this._offstyle + ' off') | |||
| this.$element.prop('checked', false) | |||
| if (!silent) this.trigger() | |||
| } | |||
| Toggle.prototype.enable = function () { | |||
| this.$toggle.removeAttr('disabled') | |||
| this.$element.prop('disabled', false) | |||
| } | |||
| Toggle.prototype.disable = function () { | |||
| this.$toggle.attr('disabled', 'disabled') | |||
| this.$element.prop('disabled', true) | |||
| } | |||
| Toggle.prototype.update = function (silent) { | |||
| if (this.$element.prop('disabled')) this.disable() | |||
| else this.enable() | |||
| if (this.$element.prop('checked')) this.on(silent) | |||
| else this.off(silent) | |||
| } | |||
| Toggle.prototype.trigger = function (silent) { | |||
| this.$element.off('change.bs.toggle') | |||
| if (!silent) this.$element.change() | |||
| this.$element.on('change.bs.toggle', $.proxy(function() { | |||
| this.update() | |||
| }, this)) | |||
| } | |||
| Toggle.prototype.destroy = function() { | |||
| this.$element.off('change.bs.toggle') | |||
| this.$toggleGroup.remove() | |||
| this.$element.removeData('bs.toggle') | |||
| this.$element.unwrap() | |||
| } | |||
| // TOGGLE PLUGIN DEFINITION | |||
| // ======================== | |||
| function Plugin(option) { | |||
| return this.each(function () { | |||
| var $this = $(this) | |||
| var data = $this.data('bs.toggle') | |||
| var options = typeof option == 'object' && option | |||
| if (!data) $this.data('bs.toggle', (data = new Toggle(this, options))) | |||
| if (typeof option == 'string' && data[option]) data[option]() | |||
| }) | |||
| } | |||
| var old = $.fn.bootstrapToggle | |||
| $.fn.bootstrapToggle = Plugin | |||
| $.fn.bootstrapToggle.Constructor = Toggle | |||
| // TOGGLE NO CONFLICT | |||
| // ================== | |||
| $.fn.toggle.noConflict = function () { | |||
| $.fn.bootstrapToggle = old | |||
| return this | |||
| } | |||
| // TOGGLE DATA-API | |||
| // =============== | |||
| $(function() { | |||
| $('input[type=checkbox][data-toggle^=toggle]').bootstrapToggle() | |||
| }) | |||
| $(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function(e) { | |||
| var $checkbox = $(this).find('input[type=checkbox]') | |||
| $checkbox.bootstrapToggle('toggle') | |||
| e.preventDefault() | |||
| }) | |||
| }(jQuery); | |||
| @@ -0,0 +1,9 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap2-toggle.js v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-large":"small"===this.options.size?"btn-small":"mini"===this.options.size?"btn-mini":"",c=a('<label class="btn">').html(this.options.on).addClass(this._onstyle+" "+b),d=a('<label class="btn">').html(this.options.off).addClass(this._offstyle+" "+b+" active"),e=a('<span class="toggle-handle btn btn-default">').addClass(b),f=a('<div class="toggle-group">').append(c,d,e),g=a('<div class="toggle btn" data-toggle="toggle">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(b).addClass(this.options.style);this.$element.wrap(g),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:c,$toggleOff:d,$toggleGroup:f}),this.$toggle.append(f);var h=this.options.width||Math.max(c.width(),d.width())+e.outerWidth()/2,i=this.options.height||Math.max(c.height(),d.height());c.addClass("toggle-on"),d.addClass("toggle-off"),this.$toggle.css({width:h,height:i}),this.options.height&&(c.css("line-height",c.height()+"px"),d.css("line-height",d.height()+"px")),this.update(!0),this.trigger(!0)},c.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},c.prototype.on=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),void(a||this.trigger()))},c.prototype.off=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),void(a||this.trigger()))},c.prototype.enable=function(){this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},c.prototype.disable=function(){this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},c.prototype.update=function(a){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(a):this.off(a)},c.prototype.trigger=function(b){this.$element.off("change.bs.toggle"),b||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},c.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var d=a.fn.bootstrapToggle;a.fn.bootstrapToggle=b,a.fn.bootstrapToggle.Constructor=c,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=d,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(b){var c=a(this).find("input[type=checkbox]");c.bootstrapToggle("toggle"),b.preventDefault()})}(jQuery); | |||
| //# sourceMappingURL=bootstrap2-toggle.min.js.map | |||
| @@ -0,0 +1 @@ | |||
| {"version":3,"file":"bootstrap2-toggle.min.js","sources":["bootstrap2-toggle.js"],"names":["$","Plugin","option","this","each","$this","data","options","Toggle","element","$element","extend","defaults","render","VERSION","DEFAULTS","on","off","onstyle","offstyle","size","style","width","height","prototype","attr","_onstyle","_offstyle","$toggleOn","html","addClass","$toggleOff","$toggleHandle","$toggleGroup","append","$toggle","prop","wrap","parent","Math","max","outerWidth","css","update","trigger","toggle","silent","removeClass","enable","removeAttr","disable","change","proxy","destroy","remove","removeData","unwrap","old","fn","bootstrapToggle","Constructor","noConflict","document","e","$checkbox","find","preventDefault","jQuery"],"mappings":";;;;;;;CASE,SAAUA,GACV,YAoID,SAASC,GAAOC,GACf,MAAOC,MAAKC,KAAK,WAChB,GAAIC,GAAUL,EAAEG,MACZG,EAAUD,EAAMC,KAAK,aACrBC,EAA2B,gBAAVL,IAAsBA,CAEtCI,IAAMD,EAAMC,KAAK,YAAcA,EAAO,GAAIE,GAAOL,KAAMI,IACvC,gBAAVL,IAAsBI,EAAKJ,IAASI,EAAKJ,OAtItD,GAAIM,GAAS,SAAUC,EAASF,GAC/BJ,KAAKO,SAAYV,EAAES,GACnBN,KAAKI,QAAYP,EAAEW,UAAWR,KAAKS,WAAYL,GAC/CJ,KAAKU,SAGNL,GAAOM,QAAW,QAElBN,EAAOO,UACNC,GAAI,KACJC,IAAK,MACLC,QAAS,UACTC,SAAU,UACVC,KAAM,SACNC,MAAO,GACPC,MAAO,KACPC,OAAQ,MAGTf,EAAOgB,UAAUZ,SAAW,WAC3B,OACCI,GAAIb,KAAKO,SAASe,KAAK,YAAcjB,EAAOO,SAASC,GACrDC,IAAKd,KAAKO,SAASe,KAAK,aAAejB,EAAOO,SAASE,IACvDC,QAASf,KAAKO,SAASe,KAAK,iBAAmBjB,EAAOO,SAASG,QAC/DC,SAAUhB,KAAKO,SAASe,KAAK,kBAAoBjB,EAAOO,SAASI,SACjEC,KAAMjB,KAAKO,SAASe,KAAK,cAAgBjB,EAAOO,SAASK,KACzDC,MAAOlB,KAAKO,SAASe,KAAK,eAAiBjB,EAAOO,SAASM,MAC3DC,MAAOnB,KAAKO,SAASe,KAAK,eAAiBjB,EAAOO,SAASO,MAC3DC,OAAQpB,KAAKO,SAASe,KAAK,gBAAkBjB,EAAOO,SAASQ,SAI/Df,EAAOgB,UAAUX,OAAS,WACzBV,KAAKuB,SAAW,OAASvB,KAAKI,QAAQW,QACtCf,KAAKwB,UAAY,OAASxB,KAAKI,QAAQY,QACvC,IAAIC,GAA6B,UAAtBjB,KAAKI,QAAQa,KAAmB,YAClB,UAAtBjB,KAAKI,QAAQa,KAAmB,YACV,SAAtBjB,KAAKI,QAAQa,KAAkB,WAC/B,GACCQ,EAAY5B,EAAE,uBAAuB6B,KAAK1B,KAAKI,QAAQS,IACzDc,SAAS3B,KAAKuB,SAAW,IAAMN,GAC7BW,EAAa/B,EAAE,uBAAuB6B,KAAK1B,KAAKI,QAAQU,KAC1Da,SAAS3B,KAAKwB,UAAY,IAAMP,EAAO,WACrCY,EAAgBhC,EAAE,gDACpB8B,SAASV,GACPa,EAAejC,EAAE,8BACnBkC,OAAON,EAAWG,EAAYC,GAC5BG,EAAUnC,EAAE,iDACd8B,SAAU3B,KAAKO,SAAS0B,KAAK,WAAajC,KAAKuB,SAAWvB,KAAKwB,UAAU,QACzEG,SAASV,GAAMU,SAAS3B,KAAKI,QAAQc,MAEvClB,MAAKO,SAAS2B,KAAKF,GACnBnC,EAAEW,OAAOR,MACRgC,QAAShC,KAAKO,SAAS4B,SACvBV,UAAWA,EACXG,WAAYA,EACZE,aAAcA,IAEf9B,KAAKgC,QAAQD,OAAOD,EAEpB,IAAIX,GAAQnB,KAAKI,QAAQe,OAASiB,KAAKC,IAAIZ,EAAUN,QAASS,EAAWT,SAAUU,EAAcS,aAAa,EAC1GlB,EAASpB,KAAKI,QAAQgB,QAAUgB,KAAKC,IAAIZ,EAAUL,SAAUQ,EAAWR,SAC5EK,GAAUE,SAAS,aACnBC,EAAWD,SAAS,cACpB3B,KAAKgC,QAAQO,KAAMpB,MAAOA,EAAOC,OAAQA,IACrCpB,KAAKI,QAAQgB,SAChBK,EAAUc,IAAI,cAAed,EAAUL,SAAW,MAClDQ,EAAWW,IAAI,cAAeX,EAAWR,SAAW,OAErDpB,KAAKwC,QAAO,GACZxC,KAAKyC,SAAQ,IAGdpC,EAAOgB,UAAUqB,OAAS,WACrB1C,KAAKO,SAAS0B,KAAK,WAAYjC,KAAKc,MACnCd,KAAKa,MAGXR,EAAOgB,UAAUR,GAAK,SAAU8B,GAC/B,MAAI3C,MAAKO,SAAS0B,KAAK,aAAoB,GAC3CjC,KAAKgC,QAAQY,YAAY5C,KAAKwB,UAAY,QAAQG,SAAS3B,KAAKuB,UAChEvB,KAAKO,SAAS0B,KAAK,WAAW,QACzBU,GAAQ3C,KAAKyC,aAGnBpC,EAAOgB,UAAUP,IAAM,SAAU6B,GAChC,MAAI3C,MAAKO,SAAS0B,KAAK,aAAoB,GAC3CjC,KAAKgC,QAAQY,YAAY5C,KAAKuB,UAAUI,SAAS3B,KAAKwB,UAAY,QAClExB,KAAKO,SAAS0B,KAAK,WAAW,QACzBU,GAAQ3C,KAAKyC,aAGnBpC,EAAOgB,UAAUwB,OAAS,WACzB7C,KAAKgC,QAAQc,WAAW,YACxB9C,KAAKO,SAAS0B,KAAK,YAAY,IAGhC5B,EAAOgB,UAAU0B,QAAU,WAC1B/C,KAAKgC,QAAQV,KAAK,WAAY,YAC9BtB,KAAKO,SAAS0B,KAAK,YAAY,IAGhC5B,EAAOgB,UAAUmB,OAAS,SAAUG,GAC/B3C,KAAKO,SAAS0B,KAAK,YAAajC,KAAK+C,UACpC/C,KAAK6C,SACN7C,KAAKO,SAAS0B,KAAK,WAAYjC,KAAKa,GAAG8B,GACtC3C,KAAKc,IAAI6B,IAGftC,EAAOgB,UAAUoB,QAAU,SAAUE,GACpC3C,KAAKO,SAASO,IAAI,oBACb6B,GAAQ3C,KAAKO,SAASyC,SAC3BhD,KAAKO,SAASM,GAAG,mBAAoBhB,EAAEoD,MAAM,WAC5CjD,KAAKwC,UACHxC,QAGJK,EAAOgB,UAAU6B,QAAU,WAC1BlD,KAAKO,SAASO,IAAI,oBAClBd,KAAK8B,aAAaqB,SAClBnD,KAAKO,SAAS6C,WAAW,aACzBpD,KAAKO,SAAS8C,SAiBf,IAAIC,GAAMzD,EAAE0D,GAAGC,eAEf3D,GAAE0D,GAAGC,gBAA8B1D,EACnCD,EAAE0D,GAAGC,gBAAgBC,YAAcpD,EAKnCR,EAAE0D,GAAGb,OAAOgB,WAAa,WAExB,MADA7D,GAAE0D,GAAGC,gBAAkBF,EAChBtD,MAMRH,EAAE,WACDA,EAAE,6CAA6C2D,oBAGhD3D,EAAE8D,UAAU9C,GAAG,kBAAmB,2BAA4B,SAAS+C,GACtE,GAAIC,GAAYhE,EAAEG,MAAM8D,KAAK,uBAC7BD,GAAUL,gBAAgB,UAC1BI,EAAEG,oBAGFC"} | |||
| @@ -8,6 +8,7 @@ | |||
| @import "jquery.mloading"; | |||
| @import "jquery-confirm.min"; | |||
| @import "bootstrap-datetimepicker.min"; | |||
| @import "bootstrap/bootstrap-toggle.min"; | |||
| @import "codemirror/lib/codemirror"; | |||
| @import "editormd/css/editormd.min"; | |||
| @@ -58,3 +59,159 @@ input.form-control { | |||
| position: absolute; | |||
| } | |||
| .logo-item { | |||
| display: flex; | |||
| &-img { | |||
| display: block; | |||
| width: 80px; | |||
| height: 80px; | |||
| background: #e9ecef; | |||
| } | |||
| &-upload { | |||
| cursor: pointer; | |||
| position: absolute; | |||
| top: 0; | |||
| width: 80px; | |||
| height: 80px; | |||
| background: #e9ecef; | |||
| border: 1px solid #ced4da; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 27px; | |||
| left: 39px; | |||
| width: 2px; | |||
| height: 26px; | |||
| background: #495057; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 39px; | |||
| left: 27px; | |||
| width: 26px; | |||
| height: 2px; | |||
| background: #495057; | |||
| } | |||
| } | |||
| &-left { | |||
| position: relative; | |||
| width: 80px; | |||
| height: 80px; | |||
| &.has-img { | |||
| .logo-item-upload { | |||
| display: none; | |||
| } | |||
| &:hover { | |||
| .logo-item-upload { | |||
| display: block; | |||
| background: rgba(145, 145, 145, 0.8); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &-right { | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| color: #777777; | |||
| font-size: 0.8rem; | |||
| } | |||
| &-title { | |||
| color: #23272B; | |||
| font-size: 1rem; | |||
| } | |||
| } | |||
| .attachment-item { | |||
| display: flex; | |||
| &-img { | |||
| display: block; | |||
| width: 160px; | |||
| height: 160px; | |||
| background: #e9ecef; | |||
| } | |||
| &-upload { | |||
| cursor: pointer; | |||
| position: absolute; | |||
| top: 0; | |||
| width: 160px; | |||
| height: 160px; | |||
| background: #e9ecef; | |||
| border: 1px solid #ced4da; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 54px; | |||
| left: 78px; | |||
| width: 2px; | |||
| height: 52px; | |||
| background: #495057; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 78px; | |||
| left: 54px; | |||
| width: 52px; | |||
| height: 2px; | |||
| background: #495057; | |||
| } | |||
| } | |||
| &-left { | |||
| position: relative; | |||
| width: 160px; | |||
| height: 160px; | |||
| &.has-img { | |||
| .attachment-item-upload { | |||
| display: none; | |||
| } | |||
| &:hover { | |||
| .attachment-item-upload { | |||
| display: block; | |||
| background: rgba(145, 145, 145, 0.8); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &-right { | |||
| padding-top: 100px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| color: #777777; | |||
| font-size: 0.8rem; | |||
| } | |||
| &-title { | |||
| color: #23272B; | |||
| font-size: 1rem; | |||
| } | |||
| } | |||
| .table th, .table td { | |||
| padding: 0.75rem 0.1rem; | |||
| vertical-align: top; | |||
| border-top: 1px solid #dee2e6; | |||
| } | |||
| .table .thead-light th{ | |||
| white-space: nowrap; | |||
| } | |||
| @@ -0,0 +1,83 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| .checkbox label .toggle, | |||
| .checkbox-inline .toggle { | |||
| margin-left: -20px; | |||
| margin-right: 5px; | |||
| } | |||
| .toggle { | |||
| position: relative; | |||
| overflow: hidden; | |||
| } | |||
| .toggle input[type="checkbox"] { | |||
| display: none; | |||
| } | |||
| .toggle-group { | |||
| position: absolute; | |||
| width: 200%; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| transition: left 0.35s; | |||
| -webkit-transition: left 0.35s; | |||
| -moz-user-select: none; | |||
| -webkit-user-select: none; | |||
| } | |||
| .toggle.off .toggle-group { | |||
| left: -100%; | |||
| } | |||
| .toggle-on { | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| right: 50%; | |||
| margin: 0; | |||
| border: 0; | |||
| border-radius: 0; | |||
| } | |||
| .toggle-off { | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 50%; | |||
| right: 0; | |||
| margin: 0; | |||
| border: 0; | |||
| border-radius: 0; | |||
| } | |||
| .toggle-handle { | |||
| position: relative; | |||
| margin: 0 auto; | |||
| padding-top: 0px; | |||
| padding-bottom: 0px; | |||
| height: 100%; | |||
| width: 0px; | |||
| border-width: 0 1px; | |||
| } | |||
| .toggle.btn { min-width: 59px; min-height: 34px; } | |||
| .toggle-on.btn { padding-right: 24px; } | |||
| .toggle-off.btn { padding-left: 24px; } | |||
| .toggle.btn-lg { min-width: 79px; min-height: 45px; } | |||
| .toggle-on.btn-lg { padding-right: 31px; } | |||
| .toggle-off.btn-lg { padding-left: 31px; } | |||
| .toggle-handle.btn-lg { width: 40px; } | |||
| .toggle.btn-sm { min-width: 50px; min-height: 30px;} | |||
| .toggle-on.btn-sm { padding-right: 20px; } | |||
| .toggle-off.btn-sm { padding-left: 20px; } | |||
| .toggle.btn-xs { min-width: 35px; min-height: 22px;} | |||
| .toggle-on.btn-xs { padding-right: 12px; } | |||
| .toggle-off.btn-xs { padding-left: 12px; } | |||
| @@ -0,0 +1,28 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| .checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} | |||
| .toggle{position:relative;overflow:hidden} | |||
| .toggle input[type=checkbox]{display:none} | |||
| .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} | |||
| .toggle.off .toggle-group{left:-100%} | |||
| .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} | |||
| .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} | |||
| .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} | |||
| .toggle.btn{min-width:59px;min-height:34px} | |||
| .toggle-on.btn{padding-right:24px} | |||
| .toggle-off.btn{padding-left:24px} | |||
| .toggle.btn-lg{min-width:79px;min-height:45px} | |||
| .toggle-on.btn-lg{padding-right:31px} | |||
| .toggle-off.btn-lg{padding-left:31px} | |||
| .toggle-handle.btn-lg{width:40px} | |||
| .toggle.btn-sm{min-width:50px;min-height:30px} | |||
| .toggle-on.btn-sm{padding-right:20px} | |||
| .toggle-off.btn-sm{padding-left:20px} | |||
| .toggle.btn-xs{min-width:35px;min-height:22px} | |||
| .toggle-on.btn-xs{padding-right:12px} | |||
| .toggle-off.btn-xs{padding-left:12px} | |||
| @@ -0,0 +1,85 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap2-toggle.css v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| label.checkbox .toggle, | |||
| label.checkbox.inline .toggle { | |||
| margin-left: -20px; | |||
| margin-right: 5px; | |||
| } | |||
| .toggle { | |||
| min-width: 40px; | |||
| height: 20px; | |||
| position: relative; | |||
| overflow: hidden; | |||
| } | |||
| .toggle input[type="checkbox"] { | |||
| display: none; | |||
| } | |||
| .toggle-group { | |||
| position: absolute; | |||
| width: 200%; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| transition: left 0.35s; | |||
| -webkit-transition: left 0.35s; | |||
| -moz-user-select: none; | |||
| -webkit-user-select: none; | |||
| } | |||
| .toggle.off .toggle-group { | |||
| left: -100%; | |||
| } | |||
| .toggle-on { | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| right: 50%; | |||
| margin: 0; | |||
| border: 0; | |||
| border-radius: 0; | |||
| } | |||
| .toggle-off { | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 50%; | |||
| right: 0; | |||
| margin: 0; | |||
| border: 0; | |||
| border-radius: 0; | |||
| } | |||
| .toggle-handle { | |||
| position: relative; | |||
| margin: 0 auto; | |||
| padding-top: 0px; | |||
| padding-bottom: 0px; | |||
| height: 100%; | |||
| width: 0px; | |||
| border-width: 0 1px; | |||
| } | |||
| .toggle-handle.btn-mini { | |||
| top: -1px; | |||
| } | |||
| .toggle.btn { min-width: 30px; } | |||
| .toggle-on.btn { padding-right: 24px; } | |||
| .toggle-off.btn { padding-left: 24px; } | |||
| .toggle.btn-large { min-width: 40px; } | |||
| .toggle-on.btn-large { padding-right: 35px; } | |||
| .toggle-off.btn-large { padding-left: 35px; } | |||
| .toggle.btn-small { min-width: 25px; } | |||
| .toggle-on.btn-small { padding-right: 20px; } | |||
| .toggle-off.btn-small { padding-left: 20px; } | |||
| .toggle.btn-mini { min-width: 20px; } | |||
| .toggle-on.btn-mini { padding-right: 12px; } | |||
| .toggle-off.btn-mini { padding-left: 12px; } | |||
| @@ -0,0 +1,28 @@ | |||
| /*! ======================================================================== | |||
| * Bootstrap Toggle: bootstrap2-toggle.css v2.2.0 | |||
| * http://www.bootstraptoggle.com | |||
| * ======================================================================== | |||
| * Copyright 2014 Min Hur, The New York Times Company | |||
| * Licensed under MIT | |||
| * ======================================================================== */ | |||
| label.checkbox .toggle,label.checkbox.inline .toggle{margin-left:-20px;margin-right:5px} | |||
| .toggle{min-width:40px;height:20px;position:relative;overflow:hidden} | |||
| .toggle input[type=checkbox]{display:none} | |||
| .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} | |||
| .toggle.off .toggle-group{left:-100%} | |||
| .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} | |||
| .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} | |||
| .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} | |||
| .toggle-handle.btn-mini{top:-1px} | |||
| .toggle.btn{min-width:30px} | |||
| .toggle-on.btn{padding-right:24px} | |||
| .toggle-off.btn{padding-left:24px} | |||
| .toggle.btn-large{min-width:40px} | |||
| .toggle-on.btn-large{padding-right:35px} | |||
| .toggle-off.btn-large{padding-left:35px} | |||
| .toggle.btn-small{min-width:25px} | |||
| .toggle-on.btn-small{padding-right:20px} | |||
| .toggle-off.btn-small{padding-left:20px} | |||
| .toggle.btn-mini{min-width:20px} | |||
| .toggle-on.btn-mini{padding-right:12px} | |||
| .toggle-off.btn-mini{padding-left:12px} | |||
| @@ -1,374 +1,421 @@ | |||
| class AccountsController < ApplicationController | |||
| #skip_before_action :check_account, :only => [:logout] | |||
| def index | |||
| render json: session | |||
| end | |||
| # 其他平台同步注册的用户 | |||
| def remote_register | |||
| username = params[:username]&.gsub(/\s+/, "") | |||
| tip_exception("无法使用以下关键词:#{username},请重新命名") if ReversedKeyword.is_reversed(username).present? | |||
| email = params[:email]&.gsub(/\s+/, "") | |||
| password = params[:password] | |||
| platform = (params[:platform] || 'forge')&.gsub(/\s+/, "") | |||
| ActiveRecord::Base.transaction do | |||
| result = autologin_register(username, email, password, platform) | |||
| if result[:message].blank? | |||
| render_ok({user: result[:user]}) | |||
| else | |||
| render_error(result[:message]) | |||
| end | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| # 其他平台修改用户的信息,这边同步修改 | |||
| def remote_update | |||
| ActiveRecord::Base.transaction do | |||
| user_params = params[:user_params] | |||
| user_extension_params = params[:user_extension_params] | |||
| u = User.find_by(login: params[:old_user_login]) | |||
| user_mail = u.try(:mail) | |||
| if u.present? | |||
| ue = u.user_extension | |||
| u.login = user_params["login"] if user_params["login"] | |||
| u.mail = user_params["mail"] if user_params["mail"] | |||
| u.lastname = user_params["lastname"] if user_params["lastname"] | |||
| ue.gender = user_extension_params["gender"] | |||
| ue.school_id = user_extension_params["school_id"] | |||
| ue.location = user_extension_params["location"] | |||
| ue.location_city = user_extension_params["location_city"] | |||
| ue.identity = user_extension_params["identity"] | |||
| ue.technical_title = user_extension_params["technical_title"] | |||
| ue.student_id = user_extension_params["student_id"] | |||
| ue.description = user_extension_params["description"] | |||
| ue.save! | |||
| u.save! | |||
| sync_params = {} | |||
| if (user_params["mail"] && user_params["mail"] != user_mail) | |||
| sync_params = sync_params.merge(email: user_params["mail"]) | |||
| end | |||
| if sync_params.present? | |||
| interactor = Gitea::User::UpdateInteractor.call(u.login, sync_params) | |||
| if interactor.success? | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| end | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| # 其他平台同步登录 | |||
| def remote_login | |||
| @user = User.try_to_login(params[:login], params[:password]) | |||
| if @user | |||
| successful_authentication(@user) | |||
| render_ok({user: {id: @user.id, token: @user.gitea_token}}) | |||
| else | |||
| render_error("用户不存在") | |||
| end | |||
| end | |||
| #修改密码 | |||
| def remote_password | |||
| @user = User.find_by(login: params[:login]) | |||
| return render_error("未找到相关用户!") if @user.blank? | |||
| sync_params = { | |||
| password: params[:password].to_s, | |||
| email: @user.mail | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) | |||
| if interactor.success? | |||
| @user.update_attribute(:password, params[:password]) | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| # 用户注册 | |||
| # 注意:用户注册需要兼顾本地版,本地版是不需要验证码及激活码以及使用授权的,注册完成即可使用 | |||
| # params[:login] 邮箱或者手机号 | |||
| # params[:code] 验证码 | |||
| # code_type 1:注册手机验证码 8:邮箱注册验证码 | |||
| # 本地forge注册入口 | |||
| def register | |||
| begin | |||
| # 查询验证码是否正确;type只可能是1或者8 | |||
| type = phone_mail_type(params[:login].strip) | |||
| # code = params[:code].strip | |||
| if type == 1 | |||
| uid_logger("start register by phone: type is #{type}") | |||
| pre = 'p' | |||
| email = nil | |||
| phone = params[:login] | |||
| # verifi_code = VerificationCode.where(phone: phone, code: code, code_type: 1).last | |||
| # TODO: 暂时限定邮箱注册 | |||
| return normal_status(-1, '只支持邮箱注册') | |||
| else | |||
| uid_logger("start register by email: type is #{type}") | |||
| pre = 'm' | |||
| email = params[:login] | |||
| phone = nil | |||
| return normal_status(-1, "该邮箱已注册") if User.exists?(mail: params[:login]) | |||
| return normal_status(-1, "邮箱格式错误") unless params[:login] =~ CustomRegexp::EMAIL | |||
| # verifi_code = VerificationCode.where(email: email, code: code, code_type: 8).last | |||
| end | |||
| # uid_logger("start register: verifi_code is #{verifi_code}, code is #{code}, time is #{Time.now.to_i - verifi_code.try(:created_at).to_i}") | |||
| # check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60) | |||
| # todo 上线前请删除万能验证码"513231" | |||
| return normal_status(-1, "8~16位密码,支持字母数字和符号") unless params[:password] =~ CustomRegexp::PASSWORD | |||
| code = generate_identifier User, 8, pre | |||
| login = pre + code | |||
| @user = User.new(admin: false, login: login, mail: email, phone: phone, type: "User") | |||
| @user.password = params[:password] | |||
| # 现在因为是验证码,所以在注册的时候就可以激活 | |||
| @user.activate | |||
| @user.profile_completed = true | |||
| # 必须要用save操作,密码的保存是在users中 | |||
| interactor = Gitea::RegisterInteractor.call({username: login, email: email, password: params[:password]}) | |||
| if interactor.success? | |||
| gitea_user = interactor.result | |||
| result = Gitea::User::GenerateTokenService.new(login, params[:password]).call | |||
| @user.gitea_token = result['sha1'] | |||
| @user.gitea_uid = gitea_user[:body]['id'] | |||
| if @user.save! | |||
| UserExtension.create!(user_id: @user.id) | |||
| successful_authentication(@user) | |||
| normal_status("注册成功") | |||
| end | |||
| else | |||
| tip_exception(-1, interactor.error) | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| end | |||
| # 用户登录 | |||
| def login | |||
| Users::LoginForm.new(account_params).validate! | |||
| @user = User.try_to_login(params[:login], params[:password]) | |||
| return normal_status(-2, "错误的账号或密码") if @user.blank? | |||
| # user is already in local database | |||
| return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? | |||
| login_control = LimitForbidControl::UserLogin.new(@user) | |||
| return normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") if login_control.forbid? | |||
| password_ok = @user.check_password?(params[:password].to_s) | |||
| unless password_ok | |||
| if login_control.remain_times-1 == 0 | |||
| normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") | |||
| else | |||
| normal_status(-2, "你已经输错密码#{login_control.error_times+1}次,还剩余#{login_control.remain_times-1}次机会") | |||
| end | |||
| login_control.increment! | |||
| return | |||
| end | |||
| successful_authentication(@user) | |||
| sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) # TODO用户密码未同步 | |||
| # session[:user_id] = @user.id | |||
| end | |||
| def change_password | |||
| @user = User.find_by(login: params[:login]) | |||
| return render_error("未找到相关用户!") if @user.blank? | |||
| return render_error("旧密码不正确") unless @user.check_password?(params[:old_password]) | |||
| sync_params = { | |||
| password: params[:password].to_s, | |||
| email: @user.mail | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) | |||
| if interactor.success? | |||
| @user.update_attribute(:password, params[:password]) | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| # 忘记密码 | |||
| def reset_password | |||
| begin | |||
| code = params[:code] | |||
| login_type = phone_mail_type(params[:login].strip) | |||
| # 获取验证码 | |||
| if login_type == 1 | |||
| phone = params[:login] | |||
| verifi_code = VerificationCode.where(phone: phone, code: code, code_type: 2).last | |||
| user = User.find_by_phone(phone) | |||
| else | |||
| email = params[:login] | |||
| verifi_code = VerificationCode.where(email: email, code: code, code_type: 3).last | |||
| user = User.find_by_mail(email) #这里有问题,应该是为email,而不是mail 6.13-hs | |||
| end | |||
| return normal_status(-2, "验证码不正确") if verifi_code.try(:code) != code.strip | |||
| return normal_status(-2, "验证码已失效") if !verifi_code&.effective? | |||
| return normal_status(-1, "8~16位密码,支持字母数字和符号") unless params[:new_password] =~ CustomRegexp::PASSWORD | |||
| user.password, user.password_confirmation = params[:new_password], params[:new_password_confirmation] | |||
| ActiveRecord::Base.transaction do | |||
| user.save! | |||
| LimitForbidControl::UserLogin.new(user).clear | |||
| end | |||
| sucess_status | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| end | |||
| def successful_authentication(user) | |||
| uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}") | |||
| # Valid user | |||
| self.logged_user = user | |||
| # generate a key and set cookie if autologin | |||
| set_autologin_cookie(user) | |||
| UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip) | |||
| # user.daily_reward | |||
| user.update_column(:last_login_on, Time.now) | |||
| session[:"#{default_yun_session}"] = user.id | |||
| Rails.logger.info("#########_____session_default_yun_session__________###############{default_yun_session}") | |||
| # 注册完成后有一天的试用申请(先去掉) | |||
| # UserDayCertification.create(user_id: user.id, status: 1) | |||
| end | |||
| def set_autologin_cookie(user) | |||
| token = Token.get_or_create_permanent_login_token(user, "autologin") | |||
| sync_user_token_to_trustie(user.login, token.value) | |||
| cookie_options = { | |||
| :value => token.value, | |||
| :expires => 1.month.from_now, | |||
| :path => '/', | |||
| :secure => false, | |||
| :httponly => true | |||
| } | |||
| if edu_setting('cookie_domain').present? | |||
| cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain')) | |||
| end | |||
| cookies[autologin_cookie_name] = cookie_options | |||
| cookies.signed[:user_id] ||= user.id | |||
| logger.info("cookies is #{cookies} ======> #{cookies.signed[:user_id]} =====> #{cookies[autologin_cookie_name]}") | |||
| end | |||
| def logout | |||
| Rails.logger.info("########___logout_current_user____________########{current_user.try(:id)}") | |||
| UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id, :ip => request.remote_ip) | |||
| logout_user | |||
| render :json => {status: 1, message: "退出成功!"} | |||
| end | |||
| # 检验邮箱是否已被注册及邮箱或者手机号是否合法 | |||
| # 参数type为事件类型 1:注册;2:忘记密码;3:绑定 | |||
| def valid_email_and_phone | |||
| check_mail_and_phone_valid(params[:login], params[:type]) | |||
| end | |||
| # 发送验证码 | |||
| # params[:login] 手机号或者邮箱号 | |||
| # params[:type]为事件通知类型 1:用户注册注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验收手机号有效 # 如果有新的继续后面加 | |||
| # 发送验证码:send_type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 | |||
| # 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9: 验收手机号有效 | |||
| def get_verification_code | |||
| code = %W(0 1 2 3 4 5 6 7 8 9) | |||
| value = params[:login] | |||
| type = params[:type].strip.to_i | |||
| login_type = phone_mail_type(value) | |||
| send_type = verify_type(login_type, type) | |||
| verification_code = code.sample(6).join | |||
| sign = Digest::MD5.hexdigest("#{OPENKEY}#{value}") | |||
| tip_exception(501, "请求不合理") if sign != params[:smscode] | |||
| logger.info("########get_verification_code: login_type: #{login_type}, send_type:#{send_type}, ") | |||
| # 记录验证码 | |||
| check_verification_code(verification_code, send_type, value) | |||
| sucess_status | |||
| end | |||
| # 1 手机类型;0 邮箱类型 | |||
| # 注意新版的login是自动名生成的 | |||
| def phone_mail_type value | |||
| value =~ /^1\d{10}$/ ? 1 : 0 | |||
| end | |||
| private | |||
| # type 事件类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加 | |||
| # login_type 1:手机类型 2:邮箱类型 | |||
| def verify_type login_type, type | |||
| case type | |||
| when 1 | |||
| login_type == 1 ? 1 : 8 | |||
| when 2 | |||
| login_type == 1 ? 2 : 3 | |||
| when 3 | |||
| login_type == 1 ? 4 : tip_exception('请填写正确的手机号') | |||
| when 4 | |||
| login_type == 1 ? tip_exception('请填写正确的邮箱') : 5 | |||
| when 5 | |||
| login_type == 1 ? 9 : tip_exception('请填写正确的手机号') | |||
| end | |||
| end | |||
| def generate_login(login) | |||
| type = phone_mail_type(login.strip) | |||
| if type == 1 | |||
| uid_logger("start register by phone: type is #{type}") | |||
| pre = 'p' | |||
| email = nil | |||
| phone = login | |||
| else | |||
| uid_logger("start register by email: type is #{type}") | |||
| pre = 'm' | |||
| email = login | |||
| phone = nil | |||
| end | |||
| code = generate_identifier User, 8, pre | |||
| { login: pre + code, email: email, phone: phone } | |||
| end | |||
| def user_params | |||
| params.require(:user).permit(:login, :email, :phone) | |||
| end | |||
| def account_params | |||
| params.require(:account).permit(:login, :password) | |||
| end | |||
| end | |||
| class AccountsController < ApplicationController | |||
| before_action :require_login, only: [:login_check, :simple_update] | |||
| include ApplicationHelper | |||
| #skip_before_action :check_account, :only => [:logout] | |||
| def simple_update | |||
| simple_update_params.merge!(username: params[:username]&.gsub(/\s+/, "")) | |||
| simple_update_params.merge!(email: params[:email]&.gsub(/\s+/, "")) | |||
| simple_update_params.merge!(platform: (params[:platform] || 'forge')&.gsub(/\s+/, "")) | |||
| Register::RemoteForm.new(simple_update_params).validate! | |||
| ActiveRecord::Base.transaction do | |||
| result = auto_update(current_user, simple_update_params) | |||
| if result[:message].blank? | |||
| render_ok | |||
| else | |||
| render_error(result[:message]) | |||
| end | |||
| end | |||
| end | |||
| def index | |||
| render json: session | |||
| end | |||
| # 其他平台同步注册的用户 | |||
| def remote_register | |||
| Register::RemoteForm.new(remote_register_params).validate! | |||
| username = params[:username]&.gsub(/\s+/, "") | |||
| tip_exception("无法使用以下关键词:#{username},请重新命名") if ReversedKeyword.check_exists?(username) | |||
| email = params[:email]&.gsub(/\s+/, "") | |||
| password = params[:password] | |||
| platform = (params[:platform] || 'forge')&.gsub(/\s+/, "") | |||
| ActiveRecord::Base.transaction do | |||
| result = autologin_register(username, email, password, platform) | |||
| if result[:message].blank? | |||
| render_ok({user: result[:user]}) | |||
| else | |||
| render_error(result[:message]) | |||
| end | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| # 其他平台修改用户的信息,这边同步修改 | |||
| def remote_update | |||
| ActiveRecord::Base.transaction do | |||
| user_params = params[:user_params] | |||
| user_extension_params = params[:user_extension_params] | |||
| u = User.find_by(login: params[:old_user_login]) | |||
| user_mail = u.try(:mail) | |||
| if u.present? | |||
| ue = u.user_extension | |||
| u.login = user_params["login"] if user_params["login"] | |||
| u.mail = user_params["mail"] if user_params["mail"] | |||
| u.lastname = user_params["lastname"] if user_params["lastname"] | |||
| ue.gender = user_extension_params["gender"] | |||
| ue.school_id = user_extension_params["school_id"] | |||
| ue.location = user_extension_params["location"] | |||
| ue.location_city = user_extension_params["location_city"] | |||
| ue.identity = user_extension_params["identity"] | |||
| ue.technical_title = user_extension_params["technical_title"] | |||
| ue.student_id = user_extension_params["student_id"] | |||
| ue.description = user_extension_params["description"] | |||
| ue.save! | |||
| u.save! | |||
| sync_params = {} | |||
| if (user_params["mail"] && user_params["mail"] != user_mail) | |||
| sync_params = sync_params.merge(email: user_params["mail"]) | |||
| end | |||
| if sync_params.present? | |||
| interactor = Gitea::User::UpdateInteractor.call(u.login, sync_params) | |||
| if interactor.success? | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| end | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| # 其他平台同步登录 | |||
| def remote_login | |||
| @user = User.try_to_login(params[:login], params[:password]) | |||
| if @user | |||
| successful_authentication(@user) | |||
| render_ok({user: {id: @user.id, token: @user.gitea_token}}) | |||
| else | |||
| render_error("用户不存在") | |||
| end | |||
| end | |||
| #修改密码 | |||
| def remote_password | |||
| @user = User.find_by(login: params[:login]) | |||
| return render_error("未找到相关用户!") if @user.blank? | |||
| sync_params = { | |||
| password: params[:password].to_s, | |||
| email: @user.mail, | |||
| login_name: @user.login, | |||
| source_id: 0 | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) | |||
| if interactor.success? | |||
| @user.update_attribute(:password, params[:password]) | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| # 用户注册 | |||
| # 注意:用户注册需要兼顾本地版,本地版是不需要验证码及激活码以及使用授权的,注册完成即可使用 | |||
| # params[:login] 邮箱或者手机号 | |||
| # params[:namespace] 登录名 | |||
| # params[:code] 验证码 | |||
| # code_type 1:注册手机验证码 8:邮箱注册验证码 | |||
| # 本地forge注册入口需要重新更改逻辑 | |||
| def register | |||
| # type只可能是1或者8 | |||
| user = nil | |||
| begin | |||
| Register::Form.new(register_params).validate! | |||
| user = Users::RegisterService.call(register_params) | |||
| user.mail = "#{user.login}@example.org" if user.mail.blank? | |||
| password = register_params[:password].strip | |||
| # gitea用户注册, email, username, password | |||
| interactor = Gitea::RegisterInteractor.call({username: user.login, email: user.mail, password: password}) | |||
| if interactor.success? | |||
| gitea_user = interactor.result | |||
| result = Gitea::User::GenerateTokenService.call(user.login, password) | |||
| user.gitea_token = result['sha1'] | |||
| user.gitea_uid = gitea_user[:body]['id'] | |||
| if user.save! | |||
| UserExtension.create!(user_id: user.id) | |||
| # 绑定授权账号 | |||
| if ["qq", "wechat", "gitee", "github", "educoder"].include?(params[:type].to_s) && session[:unionid].present? | |||
| "OpenUsers::#{params[:type].to_s.capitalize}".constantize.create!(user: user, uid: session[:unionid]) | |||
| end | |||
| successful_authentication(user) | |||
| render_ok | |||
| end | |||
| else | |||
| tip_exception(-1, interactor.error) | |||
| end | |||
| rescue Register::BaseForm::EmailError => e | |||
| render_result(-2, e.message) | |||
| rescue Register::BaseForm::LoginError => e | |||
| render_result(-3, e.message) | |||
| rescue Register::BaseForm::PhoneError => e | |||
| render_result(-4, e.message) | |||
| rescue Register::BaseForm::PasswordFormatError => e | |||
| render_result(-5, e.message) | |||
| rescue Register::BaseForm::PasswordConfirmationError => e | |||
| render_result(-7, e.message) | |||
| rescue Register::BaseForm::VerifiCodeError => e | |||
| render_result(-6, e.message) | |||
| rescue Exception => e | |||
| Gitea::User::DeleteService.call(user.login) unless user.nil? | |||
| uid_logger_error(e.message) | |||
| tip_exception(-1, e.message) | |||
| end | |||
| end | |||
| # 用户登录 | |||
| def login | |||
| Users::LoginForm.new(login_params).validate! | |||
| @user = User.try_to_login(params[:login], params[:password]) | |||
| return normal_status(-2, "错误的账号或密码") if @user.blank? | |||
| # user is already in local database | |||
| return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? | |||
| login_control = LimitForbidControl::UserLogin.new(@user) | |||
| return normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") if login_control.forbid? | |||
| password_ok = @user.check_password?(params[:password].to_s) | |||
| unless password_ok | |||
| if login_control.remain_times-1 == 0 | |||
| normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") | |||
| else | |||
| normal_status(-2, "你已经输错密码#{login_control.error_times+1}次,还剩余#{login_control.remain_times-1}次机会") | |||
| end | |||
| login_control.increment! | |||
| return | |||
| end | |||
| successful_authentication(@user) | |||
| sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) # TODO用户密码未同步 | |||
| # session[:user_id] = @user.id | |||
| end | |||
| def change_password | |||
| @user = User.find_by(login: params[:login]) | |||
| return render_error("未找到相关用户!") if @user.blank? | |||
| return render_error("旧密码不正确") unless @user.check_password?(params[:old_password]) | |||
| sync_params = { | |||
| password: params[:password].to_s, | |||
| email: @user.mail, | |||
| login_name: @user.name, | |||
| source_id: 0 | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) | |||
| if interactor.success? | |||
| @user.update_attribute(:password, params[:password]) | |||
| render_ok | |||
| else | |||
| render_error(interactor.error) | |||
| end | |||
| end | |||
| # 忘记密码 | |||
| def reset_password | |||
| begin | |||
| Accounts::ResetPasswordForm.new(reset_password_params).validate! | |||
| user = find_user | |||
| return render_error('未找到相关账号') if user.blank? | |||
| user = Accounts::ResetPasswordService.call(user, reset_password_params) | |||
| LimitForbidControl::UserLogin.new(user).clear if user.save! | |||
| render_ok | |||
| rescue Register::BaseForm::EmailError => e | |||
| render_result(-2, e.message) | |||
| rescue Register::BaseForm::PhoneError => e | |||
| render_result(-4, e.message) | |||
| rescue Register::BaseForm::PasswordFormatError => e | |||
| render_result(-5, e.message) | |||
| rescue Register::BaseForm::PasswordConfirmationError => e | |||
| render_result(-7, e.message) | |||
| rescue Register::BaseForm::VerifiCodeError => e | |||
| render_result(-6, e.message) | |||
| rescue ActiveRecord::Rollback => e | |||
| render_result(-1, "服务器异常") | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| end | |||
| def successful_authentication(user) | |||
| uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}") | |||
| # Valid user | |||
| self.logged_user = user | |||
| # generate a key and set cookie if autologin | |||
| set_autologin_cookie(user) | |||
| UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip) | |||
| # user.daily_reward | |||
| user.update_column(:last_login_on, Time.now) | |||
| session[:"#{default_yun_session}"] = user.id | |||
| Rails.logger.info("#########_____session_default_yun_session__________###############{default_yun_session}") | |||
| # 注册完成后有一天的试用申请(先去掉) | |||
| # UserDayCertification.create(user_id: user.id, status: 1) | |||
| end | |||
| def set_autologin_cookie(user) | |||
| token = Token.get_or_create_permanent_login_token(user, "autologin") | |||
| # sync_user_token_to_trustie(user.login, token.value) | |||
| cookie_options = { | |||
| :value => token.value, | |||
| :expires => 1.month.from_now, | |||
| :path => '/', | |||
| :secure => false, | |||
| :httponly => true | |||
| } | |||
| if edu_setting('cookie_domain').present? | |||
| cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain')) | |||
| end | |||
| cookies[autologin_cookie_name] = cookie_options | |||
| cookies.signed[:user_id] ||= user.id | |||
| logger.info("cookies is #{cookies} ======> #{cookies.signed[:user_id]} =====> #{cookies[autologin_cookie_name]}") | |||
| end | |||
| def logout | |||
| Rails.logger.info("########___logout_current_user____________########{current_user.try(:id)}") | |||
| UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id, :ip => request.remote_ip) | |||
| logout_user | |||
| render :json => {status: 1, message: "退出成功!"} | |||
| end | |||
| # 检验邮箱是否已被注册及邮箱或者手机号是否合法 | |||
| # 参数type为事件类型 1:注册;2:忘记密码;3:绑定 | |||
| def valid_email_and_phone | |||
| check_mail_and_phone_valid(params[:login], params[:type]) | |||
| end | |||
| # 发送验证码 | |||
| # params[:login] 手机号或者邮箱号 | |||
| # params[:type]为事件通知类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验收手机号有效 # 如果有新的继续后面加 | |||
| # 发送验证码:send_type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 | |||
| # 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9: 验收手机号有效 | |||
| def get_verification_code | |||
| code = %W(0 1 2 3 4 5 6 7 8 9) | |||
| value = params[:login] | |||
| type = params[:type].strip.to_i | |||
| login_type = phone_mail_type(value) | |||
| send_type = verify_type(login_type, type) | |||
| verification_code = code.sample(6).join | |||
| sign = Digest::MD5.hexdigest("#{OPENKEY}#{value}") | |||
| tip_exception(501, "请求不合理") if sign != params[:smscode] | |||
| logger.info "########### 验证码:#{verification_code}" | |||
| logger.info("########get_verification_code: login_type: #{login_type}, send_type:#{send_type}, ") | |||
| # 记录验证码 | |||
| check_verification_code(verification_code, send_type, value) | |||
| render_ok | |||
| end | |||
| # check user's login or email or phone is used | |||
| # params[:value] 手机号或者邮箱号或者登录名 | |||
| # params[:type] 为事件类型 1:登录名(login) 2:email(邮箱) 3:phone(手机号) | |||
| def check | |||
| Register::CheckColumnsForm.new(check_params).validate! | |||
| render_ok | |||
| end | |||
| def login_check | |||
| Register::LoginCheckColumnsForm.new(check_params.merge(user: current_user)).validate! | |||
| render_ok | |||
| end | |||
| private | |||
| # type 事件类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加 | |||
| # login_type 1:手机类型 2:邮箱类型 | |||
| def verify_type login_type, type | |||
| case type | |||
| when 1 | |||
| login_type == 1 ? 1 : 8 | |||
| when 2 | |||
| login_type == 1 ? 2 : 3 | |||
| when 3 | |||
| login_type == 1 ? 4 : tip_exception('请填写正确的手机号') | |||
| when 4 | |||
| login_type == 1 ? tip_exception('请填写正确的邮箱') : 5 | |||
| when 5 | |||
| login_type == 1 ? 9 : tip_exception('请填写正确的手机号') | |||
| end | |||
| end | |||
| def generate_login(login) | |||
| type = phone_mail_type(login.strip) | |||
| if type == 1 | |||
| uid_logger("start register by phone: type is #{type}") | |||
| pre = 'p' | |||
| email = nil | |||
| phone = login | |||
| else | |||
| uid_logger("start register by email: type is #{type}") | |||
| pre = 'm' | |||
| email = login | |||
| phone = nil | |||
| end | |||
| code = generate_identifier User, 8, pre | |||
| { login: pre + code, email: email, phone: phone } | |||
| end | |||
| def user_params | |||
| params.require(:user).permit(:login, :email, :phone) | |||
| end | |||
| def login_params | |||
| params.require(:account).permit(:login, :password) | |||
| end | |||
| def check_params | |||
| params.permit(:type, :value) | |||
| end | |||
| def register_params | |||
| params.permit(:login, :namespace, :password, :password_confirmation, :code, :type) | |||
| end | |||
| def reset_password_params | |||
| params.permit(:login, :password, :password_confirmation, :code) | |||
| end | |||
| def find_user | |||
| phone_or_mail = strip(reset_password_params[:login]) | |||
| User.where("phone = :search OR mail = :search", search: phone_or_mail).last | |||
| end | |||
| def remote_register_params | |||
| params.permit(:username, :email, :password, :platform) | |||
| end | |||
| def simple_update_params | |||
| params.permit(:username, :email, :password, :platform) | |||
| end | |||
| end | |||
| @@ -1,10 +1,33 @@ | |||
| class Admins::DashboardsController < Admins::BaseController | |||
| def index | |||
| @active_user_count = User.where(last_login_on: today).count | |||
| @weekly_active_user_count = User.where(last_login_on: current_week).count | |||
| @month_active_user_count = User.where(last_login_on: current_month).count | |||
| # 用户活跃数 | |||
| day_user_ids = CommitLog.where(created_at: today).pluck(:user_id).uniq | |||
| weekly_user_ids = CommitLog.where(created_at: current_week).pluck(:user_id).uniq | |||
| month_user_ids = CommitLog.where(created_at: current_month).pluck(:user_id).uniq | |||
| @active_user_count = User.where(last_login_on: today).or(User.where(id: day_user_ids)).count | |||
| @weekly_active_user_count = User.where(last_login_on: current_week).or(User.where(id: weekly_user_ids)).count | |||
| @month_active_user_count = User.where(last_login_on: current_month).or(User.where(id: month_user_ids)).count | |||
| user_ids = User.where(created_on: pre_week).pluck(:id).uniq | |||
| weekly_keep_user_count = User.where(id: user_ids).where(last_login_on: current_week).count | |||
| @weekly_keep_rate = format("%.2f", user_ids.size > 0 ? weekly_keep_user_count.to_f / user_ids.size : 0) | |||
| @new_user_count = User.where(created_on: current_month).count | |||
| # 新用户注册数 | |||
| @day_new_user_count = User.where(created_on: today).count | |||
| @weekly_new_user_count = User.where(created_on: current_week).count | |||
| @month_new_user_count = User.where(created_on: current_month).count | |||
| # 活跃项目数 | |||
| day_project_ids = (CommitLog.where(created_at: today).pluck(:project_id).uniq + Issue.where(created_on: today).pluck(:project_id).uniq).uniq | |||
| weekly_project_ids = (CommitLog.where(created_at: current_week).pluck(:project_id).uniq + Issue.where(created_on: current_week).pluck(:project_id).uniq).uniq | |||
| month_project_ids = (CommitLog.where(created_at: current_month).pluck(:project_id).uniq + Issue.where(created_on: current_month).pluck(:project_id).uniq).uniq | |||
| @day_active_project_count = Project.where(updated_on: today).or(Project.where(id: day_project_ids)).count | |||
| @weekly_active_project_count = Project.where(updated_on: current_week).or(Project.where(id: weekly_project_ids)).count | |||
| @month_active_project_count = Project.where(updated_on: current_month).or(Project.where(id: month_project_ids)).count | |||
| # 新增项目数 | |||
| @day_new_project_count = Project.where(created_on: today).count | |||
| @weekly_new_project_count = Project.where(created_on: current_week).count | |||
| @month_new_project_count = Project.where(created_on: current_month).count | |||
| end | |||
| def month_active_user | |||
| @@ -16,7 +39,6 @@ class Admins::DashboardsController < Admins::BaseController | |||
| { value: count['professional'].to_i, name: '专业人士' }, | |||
| { value: count[nil].to_i, name: '未选职业' }, | |||
| ] | |||
| render_ok(data: data) | |||
| end | |||
| @@ -42,10 +64,14 @@ class Admins::DashboardsController < Admins::BaseController | |||
| end | |||
| def current_week | |||
| 7.days.ago.beginning_of_day..Time.now.end_of_day | |||
| 7.days.ago.end_of_day..Time.now.end_of_day | |||
| end | |||
| def current_month | |||
| 30.days.ago.beginning_of_day..Time.now.end_of_day | |||
| 30.days.ago.end_of_day..Time.now.end_of_day | |||
| end | |||
| def pre_week | |||
| 14.days.ago.end_of_day..7.days.ago.end_of_day | |||
| end | |||
| end | |||
| @@ -0,0 +1,49 @@ | |||
| class Admins::FeedbacksController < Admins::BaseController | |||
| before_action :get_feedback, only: [:new_history, :create_history, :destroy] | |||
| def index | |||
| sort_by = Feedback.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' | |||
| sort_direction = %w(desc asc).include?(params[:sort_direction]) ? params[:sort_direction] : 'desc' | |||
| feedbacks = Feedback.order("#{sort_by} #{sort_direction}") | |||
| @feedbacks = paginate(feedbacks) | |||
| end | |||
| def destroy | |||
| if @feedback.destroy | |||
| redirect_to admins_feedbacks_path | |||
| flash[:success] = "反馈意见删除成功" | |||
| else | |||
| redirect_to admins_feedbacks_path | |||
| flash[:danger] = "反馈意见删除失败" | |||
| end | |||
| end | |||
| def new_history | |||
| @feedback_message_history = FeedbackMessageHistory.new | |||
| end | |||
| def create_history | |||
| @feedback_message_history = @feedback.feedback_message_histories.new(feedback_message_history_params) | |||
| @feedback_message_history.user = current_user | |||
| if @feedback_message_history.save | |||
| redirect_to admins_feedbacks_path | |||
| flash[:success] = "发送通知成功" | |||
| else | |||
| redirect_to admins_feedbacks_path | |||
| flash[:danger] = @feedback_message_history.errors.full_messages.join(", ") | |||
| end | |||
| end | |||
| private | |||
| def feedback_params | |||
| params.require(:feedback).permit! | |||
| end | |||
| def feedback_message_history_params | |||
| params.require(:feedback_message_history).permit(:title, :content) | |||
| end | |||
| def get_feedback | |||
| @feedback = Feedback.find_by_id(params[:id]) | |||
| end | |||
| end | |||
| @@ -2,7 +2,7 @@ class Admins::ImportUsersController < Admins::BaseController | |||
| def create | |||
| return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile) | |||
| result = Admins::ImportUserService.call(params[:file].to_io) | |||
| result = Admins::ImportUserFromExcelService.call(params[:file].to_io) | |||
| render_ok(result) | |||
| rescue Admins::ImportUserService::Error => ex | |||
| render_error(ex) | |||
| @@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController | |||
| before_action :get_template, only: [:edit, :update, :destroy] | |||
| def index | |||
| message_templates = MessageTemplate.group(:type).count.keys | |||
| @message_templates = kaminari_array_paginate(message_templates) | |||
| message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result | |||
| @message_templates = kaminari_paginate(message_templates) | |||
| end | |||
| def new | |||
| @message_template = MessageTemplate.new | |||
| end | |||
| def create | |||
| @message_template = MessageTemplate::CustomTip.new(message_template_params) | |||
| @message_template.type = "MessageTemplate::CustomTip" | |||
| if @message_template.save! | |||
| redirect_to admins_message_templates_path | |||
| flash[:success] = "创建消息模板成功" | |||
| else | |||
| render :new | |||
| flash[:danger] = "创建消息模板失败" | |||
| end | |||
| end | |||
| def edit | |||
| @@ -31,7 +47,9 @@ class Admins::MessageTemplatesController < Admins::BaseController | |||
| private | |||
| def message_template_params | |||
| params.require(@message_template.type.split("::").join("_").underscore.to_sym).permit! | |||
| # type = @message_template.present? ? @message_template.type : "MessageTemplate::CustomTip" | |||
| # params.require(type.split("::").join("_").underscore.to_sym).permit! | |||
| params.require(:message_template).permit! | |||
| end | |||
| def get_template | |||
| @@ -0,0 +1,26 @@ | |||
| class Admins::NpsController < Admins::BaseController | |||
| def index | |||
| @on_off_switch = EduSetting.get("nps-on-off-switch").to_s == 'true' | |||
| @user_nps = UserNp.joins(:user).order(created_at: :desc) | |||
| keyword = params[:keyword].to_s.strip.presence | |||
| if keyword | |||
| sql = 'CONCAT(users.lastname, users.firstname) LIKE :keyword OR users.nickname LIKE :keyword OR users.login LIKE :keyword OR users.mail LIKE :keyword OR users.phone LIKE :keyword' | |||
| @user_nps = @user_nps.where(sql, keyword: "%#{keyword}%") | |||
| end | |||
| @user_nps = @user_nps.where("action_type != 'close'") if params[:done_score].present? | |||
| @min_score = @user_nps.where("action_type != 'close'").minimum("score") | |||
| @max_score = @user_nps.where("action_type != 'close'").maximum("score") | |||
| @score_total_count = UserNp.where("action_type !='close'").count | |||
| @user_nps = paginate @user_nps.includes(:user) | |||
| end | |||
| def switch_change | |||
| edu_setting = EduSetting.find_by(name: "nps-on-off-switch") | |||
| if edu_setting.blank? | |||
| edu_setting = EduSetting.new(name: "nps-on-off-switch") | |||
| end | |||
| edu_setting.value = params[:switch].to_s | |||
| edu_setting.save | |||
| render_ok | |||
| end | |||
| end | |||
| @@ -22,7 +22,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| max_position_items = ProjectCategory.select(:id, :position).pluck(:position).reject!(&:blank?) | |||
| max_position = max_position_items.present? ? max_position_items.max.to_i : 0 | |||
| @project_category = ProjectCategory.new(name: @name,position: max_position) | |||
| @project_category = ProjectCategory.new(name: @name,position: max_position, pinned_index: params[:project_category][:pinned_index].to_i) | |||
| if @project_category.save | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = '创建成功' | |||
| @@ -33,17 +33,18 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| end | |||
| def update | |||
| if @project_category.update_attribute(:name, @name) | |||
| if @project_category.update_attributes({name: @name, pinned_index: params[:project_category][:pinned_index].to_i}) | |||
| save_image_file(params[:logo], 'logo') | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = '更新成功' | |||
| else | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = '更新失败' | |||
| flash[:danger] = '更新失败' | |||
| end | |||
| end | |||
| def destroy | |||
| if @project_language.destroy | |||
| if @project_category.destroy | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = "删除成功" | |||
| else | |||
| @@ -80,4 +81,12 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| flash[:danger] = '分类已存在' | |||
| end | |||
| end | |||
| def save_image_file(file, type) | |||
| return unless file.present? && file.is_a?(ActionDispatch::Http::UploadedFile) | |||
| file_path = Util::FileManage.source_disk_filename(@project_category, type) | |||
| File.delete(file_path) if File.exist?(file_path) # 删除之前的文件 | |||
| Util.write_file(file, file_path) | |||
| end | |||
| end | |||
| @@ -1,6 +1,6 @@ | |||
| class Admins::ProjectIgnoresController < Admins::BaseController | |||
| before_action :set_ignore, only: [:edit,:update, :destroy,:show] | |||
| before_action :validate_params, only: [:create, :update] | |||
| # before_action :validate_params, only: [:create, :update] | |||
| def index | |||
| sort_by = Ignore.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' | |||
| @@ -31,12 +31,12 @@ class Admins::ProjectIgnoresController < Admins::BaseController | |||
| # } | |||
| @project_ignore = Ignore.new(ignore_params) | |||
| if @project_ignore.save! | |||
| if @project_ignore.save | |||
| redirect_to admins_project_ignores_path | |||
| flash[:success] = "创建成功" | |||
| else | |||
| render :new | |||
| flash[:danger] = "创建失败" | |||
| redirect_to admins_project_ignores_path | |||
| flash[:danger] = @project_ignore.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| @@ -58,8 +58,8 @@ class Admins::ProjectIgnoresController < Admins::BaseController | |||
| redirect_to admins_project_ignores_path | |||
| flash[:success] = "更新成功" | |||
| else | |||
| render :edit | |||
| flash[:danger] = "更新失败" | |||
| redirect_to admins_project_ignores_path | |||
| flash[:danger] = @project_ignore.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| @@ -98,23 +98,23 @@ class Admins::ProjectIgnoresController < Admins::BaseController | |||
| params.require(:ignore).permit(:name,:content) | |||
| end | |||
| def validate_params | |||
| name = params[:ignore][:name] | |||
| if name.blank? | |||
| flash[:danger] = "名称不允许为空" | |||
| redirect_to admins_project_ignores_path | |||
| elsif check_ignore_present?(name) && @project_ignore.blank? | |||
| flash[:danger] = "创建失败:名称已存在" | |||
| redirect_to admins_project_ignores_path | |||
| end | |||
| end | |||
| # def validate_params | |||
| # name = params[:ignore][:name] | |||
| # if name.blank? | |||
| # flash[:danger] = "名称不允许为空" | |||
| # redirect_to admins_project_ignores_path | |||
| # elsif check_ignore_present?(name) && @project_ignore.blank? | |||
| # flash[:danger] = "创建失败:名称已存在" | |||
| # redirect_to admins_project_ignores_path | |||
| # end | |||
| # end | |||
| def check_ignore_present?(name) | |||
| return true if name.blank? | |||
| name_downcase = name.downcase | |||
| name_upcase = name.upcase | |||
| name_first_big = name.capitalize | |||
| Ignore.exists?(name: name_downcase) || Ignore.exists?(name: name_upcase) || Ignore.exists?(name: name_first_big) | |||
| end | |||
| # def check_ignore_present?(name) | |||
| # return true if name.blank? | |||
| # name_downcase = name.downcase | |||
| # name_upcase = name.upcase | |||
| # name_first_big = name.capitalize | |||
| # Ignore.exists?(name: name_downcase) || Ignore.exists?(name: name_upcase) || Ignore.exists?(name: name_first_big) | |||
| # end | |||
| end | |||
| @@ -27,17 +27,18 @@ class Admins::ProjectLanguagesController < Admins::BaseController | |||
| flash[:success] = '创建成功' | |||
| else | |||
| redirect_to admins_project_languages_path | |||
| flash[:danger] = '创建失败' | |||
| flash[:danger] = @project_language.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| def update | |||
| if @project_language.update_attribute(:name, @name) | |||
| @project_language.attributes = {name: @name} | |||
| if @project_language.save | |||
| redirect_to admins_project_languages_path | |||
| flash[:success] = '更新成功' | |||
| else | |||
| redirect_to admins_project_languages_path | |||
| flash[:success] = '更新失败' | |||
| flash[:danger] = @project_language.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| @@ -1,6 +1,6 @@ | |||
| class Admins::ProjectLicensesController < Admins::BaseController | |||
| before_action :set_license, only: [:edit,:update, :destroy,:show] | |||
| before_action :validate_params, only: [:create, :update] | |||
| # before_action :validate_params, only: [:create, :update] | |||
| def index | |||
| sort_by = License.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' | |||
| @@ -30,13 +30,12 @@ class Admins::ProjectLicensesController < Admins::BaseController | |||
| # position: max_position | |||
| # } | |||
| @project_license = License.new(license_params) | |||
| if @project_license.save! | |||
| if @project_license.save | |||
| redirect_to admins_project_licenses_path | |||
| flash[:success] = "创建成功" | |||
| else | |||
| render :new | |||
| flash[:danger] = "创建失败" | |||
| redirect_to admins_project_licenses_path | |||
| flash[:danger] = @project_license.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| @@ -54,12 +53,13 @@ class Admins::ProjectLicensesController < Admins::BaseController | |||
| # permissions: permissions.to_s, | |||
| # limitations: limitations.to_s | |||
| # } | |||
| if @project_license.update_attributes(license_params) | |||
| @project_license.attributes = license_params | |||
| if @project_license.save | |||
| redirect_to admins_project_licenses_path | |||
| flash[:success] = "更新成功" | |||
| else | |||
| render :edit | |||
| flash[:danger] = "更新失败" | |||
| render admins_project_licenses_path | |||
| flash[:danger] = @project_license.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| @@ -98,23 +98,23 @@ class Admins::ProjectLicensesController < Admins::BaseController | |||
| params.require(:license).permit(:name,:content) | |||
| end | |||
| def validate_params | |||
| name = params[:license][:name] | |||
| if name.blank? | |||
| flash[:danger] = "名称不允许为空" | |||
| redirect_to admins_project_licenses_path | |||
| elsif check_license_present?(name) && @project_license.blank? | |||
| flash[:danger] = "创建失败:名称已存在" | |||
| redirect_to admins_project_licenses_path | |||
| end | |||
| end | |||
| # def validate_params | |||
| # name = params[:license][:name] | |||
| # if name.blank? | |||
| # flash[:danger] = "名称不允许为空" | |||
| # redirect_to admins_project_licenses_path | |||
| # elsif check_license_present?(name) && @project_license.blank? | |||
| # flash[:danger] = "创建失败:名称已存在" | |||
| # redirect_to admins_project_licenses_path | |||
| # end | |||
| # end | |||
| def check_license_present?(name) | |||
| return true if name.blank? | |||
| name_downcase = name.downcase | |||
| name_upcase = name.upcase | |||
| name_first_big = name.capitalize | |||
| License.exists?(name: name_downcase) || License.exists?(name: name_upcase) || License.exists?(name: name_first_big) | |||
| end | |||
| # def check_license_present?(name) | |||
| # return true if name.blank? | |||
| # name_downcase = name.downcase | |||
| # name_upcase = name.upcase | |||
| # name_first_big = name.capitalize | |||
| # License.exists?(name: name_downcase) || License.exists?(name: name_upcase) || License.exists?(name: name_first_big) | |||
| # end | |||
| end | |||
| @@ -1,4 +1,5 @@ | |||
| class Admins::ProjectsController < Admins::BaseController | |||
| before_action :find_project, only: [:edit, :update] | |||
| def index | |||
| sort_by = Project.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_on' | |||
| @@ -8,6 +9,26 @@ class Admins::ProjectsController < Admins::BaseController | |||
| @projects = paginate projects.includes(:owner, :members, :issues, :versions, :attachments, :project_score) | |||
| end | |||
| def edit ;end | |||
| def update | |||
| respond_to do |format| | |||
| if @project.update_attributes(project_update_params) | |||
| format.html do | |||
| redirect_to admins_projects_path | |||
| flash[:sucess] = "更新成功" | |||
| end | |||
| format.js {render_ok} | |||
| else | |||
| format.html do | |||
| redirect_to admins_projects_path | |||
| flash[:danger] = "更新失败" | |||
| end | |||
| format.js {render_js_error} | |||
| end | |||
| end | |||
| end | |||
| def destroy | |||
| project = Project.find_by!(id: params[:id]) | |||
| ActiveRecord::Base.transaction do | |||
| @@ -21,4 +42,13 @@ class Admins::ProjectsController < Admins::BaseController | |||
| redirect_to admins_projects_path | |||
| flash[:danger] = "删除失败" | |||
| end | |||
| private | |||
| def find_project | |||
| @project = Project.find_by_id(params[:id]) | |||
| end | |||
| def project_update_params | |||
| params.require(:project).permit(:is_pinned, :recommend, :recommend_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,75 @@ | |||
| class Admins::SystemNotificationsController < Admins::BaseController | |||
| before_action :get_notification, only: [:history, :edit,:update, :destroy] | |||
| # before_action :validate_identifer, only: [:create, :update] | |||
| def index | |||
| sort_by = SystemNotification.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' | |||
| sort_direction = %w(desc asc).include?(params[:sort_direction]) ? params[:sort_direction] : 'desc' | |||
| q = SystemNotification.ransack(subject_cont: params[:search]) | |||
| notifications = q.result(distinct: true).reorder("#{sort_by} #{sort_direction},created_at desc") | |||
| @notifications = paginate(notifications) | |||
| end | |||
| def history | |||
| @users = @notification.users | |||
| end | |||
| def new | |||
| @notification = SystemNotification.new | |||
| end | |||
| def edit | |||
| end | |||
| def create | |||
| @notification = SystemNotification.new(notification_params) | |||
| if @notification.save | |||
| redirect_to admins_system_notifications_path | |||
| flash[:success] = '系统消息创建成功' | |||
| else | |||
| redirect_to admins_system_notifications_path | |||
| flash[:danger] = @notification.errors.full_messages.join(",") | |||
| end | |||
| end | |||
| def update | |||
| respond_to do |format| | |||
| if @notification.update_attributes(notification_params) | |||
| format.html do | |||
| redirect_to admins_system_notifications_path | |||
| flash[:success] = '系统消息更新成功' | |||
| end | |||
| format.js {render_ok} | |||
| else | |||
| format.html do | |||
| redirect_to admins_system_notifications_path | |||
| flash[:danger] = @notification.errors.full_messages.join(",") | |||
| end | |||
| format.js {render_js_error} | |||
| end | |||
| end | |||
| end | |||
| def destroy | |||
| if @notification.destroy | |||
| redirect_to admins_system_notifications_path | |||
| flash[:success] = "系统消息删除成功" | |||
| else | |||
| redirect_to admins_system_notifications_path | |||
| flash[:danger] = "系统消息删除失败" | |||
| end | |||
| end | |||
| private | |||
| def notification_params | |||
| params.require(:system_notification).permit! | |||
| end | |||
| def get_notification | |||
| @notification = SystemNotification.find_by(id: params[:id]) | |||
| unless @notification.present? | |||
| redirect_to admins_system_notifications_path | |||
| flash[:danger] = "系统消息不存在" | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::ActivityForumsController < Admins::Topic::BaseController | |||
| before_action :find_activity_forum, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::ActivityForum.ransack(title_cont: params[:search]) | |||
| activity_forums = q.result(distinct: true) | |||
| @activity_forums = paginate(activity_forums) | |||
| end | |||
| def new | |||
| @activity_forum = ::Topic::ActivityForum.new | |||
| end | |||
| def create | |||
| @activity_forum = ::Topic::ActivityForum.new(activity_forum_params) | |||
| if @activity_forum.save | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:success] = "新增平台动态成功" | |||
| else | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:danger] = "新增平台动态失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @activity_forum.attributes = activity_forum_params | |||
| if @activity_forum.save | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:success] = "更新平台动态成功" | |||
| else | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:danger] = "更新平台动态失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @activity_forum.destroy | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:success] = "删除平台动态成功" | |||
| else | |||
| redirect_to admins_topic_activity_forums_path | |||
| flash[:danger] = "删除平台动态失败" | |||
| end | |||
| end | |||
| private | |||
| def find_activity_forum | |||
| @activity_forum = ::Topic::ActivityForum.find_by_id(params[:id]) | |||
| end | |||
| def activity_forum_params | |||
| params.require(:topic_activity_forum).permit(:title, :uuid, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,58 @@ | |||
| class Admins::Topic::BannersController < Admins::Topic::BaseController | |||
| before_action :find_banner, only: [:edit, :update, :destroy] | |||
| def index | |||
| @banners = paginate(::Topic::Banner) | |||
| @banners = paginate(::Topic::Banner.where("title like ?", "%#{params[:search]}%")) if params[:search].present? | |||
| end | |||
| def new | |||
| @banner = ::Topic::Banner.new | |||
| end | |||
| def create | |||
| @banner = ::Topic::Banner.new(banner_params) | |||
| if @banner.save | |||
| save_image_file(params[:image], @banner) | |||
| redirect_to admins_topic_banners_path | |||
| flash[:success] = "新增banner成功" | |||
| else | |||
| redirect_to admins_topic_banners_path | |||
| flash[:danger] = "新增banner失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @banner.attributes = banner_params | |||
| if @banner.save | |||
| save_image_file(params[:image], @banner) | |||
| redirect_to admins_topic_banners_path | |||
| flash[:success] = "更新banner成功" | |||
| else | |||
| redirect_to admins_topic_banners_path | |||
| flash[:danger] = "更新banner失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @banner.destroy | |||
| redirect_to admins_topic_banners_path | |||
| flash[:success] = "删除banner成功" | |||
| else | |||
| redirect_to admins_topic_banners_path | |||
| flash[:danger] = "删除banner失败" | |||
| end | |||
| end | |||
| private | |||
| def find_banner | |||
| @banner = ::Topic::Banner.find_by_id(params[:id]) | |||
| end | |||
| def banner_params | |||
| params.require(:topic_banner).permit(:title, :order_index, :url) | |||
| end | |||
| end | |||
| @@ -0,0 +1,11 @@ | |||
| class Admins::Topic::BaseController < Admins::BaseController | |||
| protected | |||
| def save_image_file(file, topic) | |||
| return unless file.present? && file.is_a?(ActionDispatch::Http::UploadedFile) | |||
| file_path = Util::FileManage.source_disk_filename(topic, 'image') | |||
| File.delete(file_path) if File.exist?(file_path) # 删除之前的文件 | |||
| Util.write_file(file, file_path) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::CardsController < Admins::Topic::BaseController | |||
| before_action :find_card, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::Card.ransack(title_cont: params[:search]) | |||
| cards = q.result(distinct: true) | |||
| @cards = paginate(cards) | |||
| end | |||
| def new | |||
| @card = ::Topic::Card.new | |||
| end | |||
| def create | |||
| @card = ::Topic::Card.new(card_params) | |||
| if @card.save | |||
| redirect_to admins_topic_cards_path | |||
| flash[:success] = "新增合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cards_path | |||
| flash[:danger] = "新增合作单位失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @card.attributes = card_params | |||
| if @card.save | |||
| redirect_to admins_topic_cards_path | |||
| flash[:success] = "更新合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cards_path | |||
| flash[:danger] = "更新合作单位失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @card.destroy | |||
| redirect_to admins_topic_cards_path | |||
| flash[:success] = "删除合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cards_path | |||
| flash[:danger] = "删除合作单位失败" | |||
| end | |||
| end | |||
| private | |||
| def find_card | |||
| @card = ::Topic::Card.find_by_id(params[:id]) | |||
| end | |||
| def card_params | |||
| params.require(:topic_card).permit(:title, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::CooperatorsController < Admins::Topic::BaseController | |||
| before_action :find_cooperator, only: [:edit, :update, :destroy] | |||
| def index | |||
| @cooperators = paginate(::Topic::Cooperator) | |||
| end | |||
| def new | |||
| @cooperator = ::Topic::Cooperator.new | |||
| end | |||
| def create | |||
| @cooperator = ::Topic::Cooperator.new(cooperator_params) | |||
| if @cooperator.save | |||
| save_image_file(params[:image], @cooperator) | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:success] = "新增合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:danger] = "新增合作单位失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @cooperator.attributes = cooperator_params | |||
| if @cooperator.save | |||
| save_image_file(params[:image], @cooperator) | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:success] = "更新合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:danger] = "更新合作单位失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @cooperator.destroy | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:success] = "删除合作单位成功" | |||
| else | |||
| redirect_to admins_topic_cooperators_path | |||
| flash[:danger] = "删除合作单位失败" | |||
| end | |||
| end | |||
| private | |||
| def find_cooperator | |||
| @cooperator = ::Topic::Cooperator.find_by_id(params[:id]) | |||
| end | |||
| def cooperator_params | |||
| params.require(:topic_cooperator).permit(:title, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::ExcellentProjectsController < Admins::Topic::BaseController | |||
| before_action :find_excellent_project, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::ExcellentProject.ransack(title_cont: params[:search]) | |||
| excellent_projects = q.result(distinct: true) | |||
| @excellent_projects = paginate(excellent_projects) | |||
| end | |||
| def new | |||
| @excellent_project = ::Topic::ExcellentProject.new | |||
| end | |||
| def create | |||
| @excellent_project = ::Topic::ExcellentProject.new(excellent_project_params) | |||
| if @excellent_project.save | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:success] = "新增优秀仓库成功" | |||
| else | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:danger] = "新增优秀仓库失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @excellent_project.attributes = excellent_project_params | |||
| if @excellent_project.save | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:success] = "更新优秀仓库成功" | |||
| else | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:danger] = "更新优秀仓库失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @excellent_project.destroy | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:success] = "删除优秀仓库成功" | |||
| else | |||
| redirect_to admins_topic_excellent_projects_path | |||
| flash[:danger] = "删除优秀仓库失败" | |||
| end | |||
| end | |||
| private | |||
| def find_excellent_project | |||
| @excellent_project = ::Topic::ExcellentProject.find_by_id(params[:id]) | |||
| end | |||
| def excellent_project_params | |||
| params.require(:topic_excellent_project).permit(:title, :uuid, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::ExperienceForumsController < Admins::Topic::BaseController | |||
| before_action :find_experience_forum, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::ExperienceForum.ransack(title_cont: params[:search]) | |||
| experience_forums = q.result(distinct: true) | |||
| @experience_forums = paginate(experience_forums) | |||
| end | |||
| def new | |||
| @experience_forum = ::Topic::ExperienceForum.new | |||
| end | |||
| def create | |||
| @experience_forum = ::Topic::ExperienceForum.new(experience_forum_params) | |||
| if @experience_forum.save | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:success] = "新增经验分享成功" | |||
| else | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:danger] = "新增经验分享失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @experience_forum.attributes = experience_forum_params | |||
| if @experience_forum.save | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:success] = "更新经验分享成功" | |||
| else | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:danger] = "更新经验分享失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @experience_forum.destroy | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:success] = "删除经验分享成功" | |||
| else | |||
| redirect_to admins_topic_experience_forums_path | |||
| flash[:danger] = "删除经验分享失败" | |||
| end | |||
| end | |||
| private | |||
| def find_experience_forum | |||
| @experience_forum = ::Topic::ExperienceForum.find_by_id(params[:id]) | |||
| end | |||
| def experience_forum_params | |||
| params.require(:topic_experience_forum).permit(:title, :uuid, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::GlccNewsController < Admins::Topic::BaseController | |||
| before_action :find_glcc, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::GlccNews.ransack(title_cont: params[:search]) | |||
| glcc_news = q.result(distinct: true) | |||
| @glcc_news = paginate(glcc_news) | |||
| end | |||
| def new | |||
| @glcc = ::Topic::GlccNews.new | |||
| end | |||
| def create | |||
| @glcc = ::Topic::GlccNews.new(glcc_params) | |||
| if @glcc.save | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:success] = "新增新闻稿成功" | |||
| else | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:danger] = "新增新闻稿失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @glcc.attributes = glcc_params | |||
| if @glcc.save | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:success] = "更新新闻稿成功" | |||
| else | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:danger] = "更新新闻稿失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @glcc.destroy | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:success] = "删除新闻稿成功" | |||
| else | |||
| redirect_to admins_topic_glcc_news_index_path | |||
| flash[:danger] = "删除新闻稿失败" | |||
| end | |||
| end | |||
| private | |||
| def find_glcc | |||
| @glcc = ::Topic::GlccNews.find_by_id(params[:id]) | |||
| end | |||
| def glcc_params | |||
| params.require(:topic_glcc_news).permit(:title, :uuid, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -0,0 +1,57 @@ | |||
| class Admins::Topic::PinnedForumsController < Admins::Topic::BaseController | |||
| before_action :find_pinned_forum, only: [:edit, :update, :destroy] | |||
| def index | |||
| q = ::Topic::PinnedForum.ransack(title_cont: params[:search]) | |||
| pinned_forums = q.result(distinct: true) | |||
| @pinned_forums = paginate(pinned_forums) | |||
| end | |||
| def new | |||
| @pinned_forum = ::Topic::PinnedForum.new | |||
| end | |||
| def create | |||
| @pinned_forum = ::Topic::PinnedForum.new(pinned_forum_params) | |||
| if @pinned_forum.save | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:success] = "新增精选文章成功" | |||
| else | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:danger] = "新增精选文章失败" | |||
| end | |||
| end | |||
| def edit | |||
| end | |||
| def update | |||
| @pinned_forum.attributes = pinned_forum_params | |||
| if @pinned_forum.save | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:success] = "更新精选文章成功" | |||
| else | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:danger] = "更新精选文章失败" | |||
| end | |||
| end | |||
| def destroy | |||
| if @pinned_forum.destroy | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:success] = "删除精选文章成功" | |||
| else | |||
| redirect_to admins_topic_pinned_forums_path | |||
| flash[:danger] = "删除精选文章失败" | |||
| end | |||
| end | |||
| private | |||
| def find_pinned_forum | |||
| @pinned_forum = ::Topic::PinnedForum.find_by_id(params[:id]) | |||
| end | |||
| def pinned_forum_params | |||
| params.require(:topic_pinned_forum).permit(:title, :uuid, :url, :order_index) | |||
| end | |||
| end | |||
| @@ -1,4 +1,6 @@ | |||
| class Admins::UsersController < Admins::BaseController | |||
| before_action :finder_user, except: [:index] | |||
| def index | |||
| params[:sort_by] = params[:sort_by].presence || 'created_on' | |||
| params[:sort_direction] = params[:sort_direction].presence || 'desc' | |||
| @@ -8,12 +10,9 @@ class Admins::UsersController < Admins::BaseController | |||
| end | |||
| def edit | |||
| @user = User.find(params[:id]) | |||
| end | |||
| def update | |||
| @user = User.find(params[:id]) | |||
| Admins::UpdateUserService.call(@user, update_params) | |||
| flash[:success] = '保存成功' | |||
| redirect_to edit_admins_user_path(@user) | |||
| @@ -26,43 +25,47 @@ class Admins::UsersController < Admins::BaseController | |||
| end | |||
| def destroy | |||
| User.find(params[:id]).destroy! | |||
| @user.destroy! | |||
| Gitea::User::DeleteService.call(@user.login) | |||
| render_delete_success | |||
| end | |||
| def lock | |||
| User.find(params[:id]).lock! | |||
| @user.lock! | |||
| render_ok | |||
| end | |||
| def unlock | |||
| User.find(params[:id]).activate! | |||
| @user.activate! | |||
| render_ok | |||
| end | |||
| def reward_grade | |||
| user = User.find(params[:user_id]) | |||
| return render_unprocessable_entity('金币数量必须大于0') if params[:grade].to_i <= 0 | |||
| RewardGradeService.call(user, container_id: user.id, container_type: 'Feedback', score: params[:grade].to_i, not_unique: true) | |||
| RewardGradeService.call(@user, container_id: @user.id, container_type: 'Feedback', score: params[:grade].to_i, not_unique: true) | |||
| render_ok(grade: user.grade) | |||
| render_ok(grade: @user.grade) | |||
| end | |||
| def reset_login_times | |||
| User.find(params[:id]).reset_login_times! | |||
| @user.reset_login_times! | |||
| render_ok | |||
| end | |||
| private | |||
| def finder_user | |||
| @user = User.find(params[:id]) | |||
| end | |||
| def update_params | |||
| params.require(:user).permit(%i[lastname nickname gender identity technical_title student_id is_shixun_marker | |||
| mail phone location location_city school_id department_id admin business is_test | |||
| password professional_certification authentication]) | |||
| password professional_certification authentication login]) | |||
| end | |||
| end | |||
| @@ -0,0 +1,55 @@ | |||
| class Api::V1::BaseController < ApplicationController | |||
| include Api::ProjectHelper | |||
| include Api::UserHelper | |||
| include Api::PullHelper | |||
| # before_action :doorkeeper_authorize! | |||
| # skip_before_action :user_setup | |||
| protected | |||
| # def current_user | |||
| # #client方法对接,需要一直带着用户标识uid | |||
| # Rails.logger.info doorkeeper_token | |||
| # if doorkeeper_token && doorkeeper_token.resource_owner_id.blank? | |||
| # # return User.anonymous if params[:uid].nil? | |||
| # # tip_exception("2222") | |||
| # # return render_error('缺少用户标识!') if params[:uid].nil? | |||
| # User.current = User.find(params[:uid]) | |||
| # else | |||
| # User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token | |||
| # end | |||
| # end | |||
| def limit | |||
| params.fetch(:limit, 15) | |||
| end | |||
| def page | |||
| params.fetch(:page, 1) | |||
| end | |||
| # 具有对仓库的管理权限 | |||
| def require_manager_above | |||
| @project = load_project | |||
| return render_forbidden if !current_user.admin? && !@project.manager?(current_user) | |||
| end | |||
| # 具有对仓库的操作权限 | |||
| def require_operate_above | |||
| @project = load_project | |||
| return render_forbidden if !current_user.admin? && !@project.operator?(current_user) | |||
| end | |||
| # 具有仓库的操作权限或者fork仓库的操作权限 | |||
| def require_operate_above_or_fork_project | |||
| @project = load_project | |||
| puts !current_user.admin? && !@project.operator?(current_user) && !(@project.fork_project.present? && @project.fork_project.operator?(current_user)) | |||
| return render_forbidden if !current_user.admin? && !@project.operator?(current_user) && !(@project.fork_project.present? && @project.fork_project.operator?(current_user)) | |||
| end | |||
| # 具有对仓库的访问权限 | |||
| def require_public_and_member_above | |||
| @project = load_project | |||
| return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user) | |||
| end | |||
| end | |||
| @@ -0,0 +1,18 @@ | |||
| class Api::V1::Projects::BranchesController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above, only: [:all] | |||
| def all | |||
| @result_object = Api::V1::Projects::Branches::AllListService.call(@project, current_user&.gitea_token) | |||
| end | |||
| before_action :require_operate_above, only: [:create] | |||
| def create | |||
| @result_object = Api::V1::Projects::Branches::CreateService.call(@project, branch_params, current_user&.gitea_token) | |||
| end | |||
| private | |||
| def branch_params | |||
| params.require(:branch).permit(:new_branch_name, :old_branch_name) | |||
| end | |||
| end | |||
| @@ -0,0 +1,8 @@ | |||
| class Api::V1::Projects::CodeStatsController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above, only: [:index] | |||
| def index | |||
| @result_object = Api::V1::Projects::CodeStats::ListService.call(@project, {ref: params[:ref]}, current_user&.gitea_token) | |||
| puts @result_object | |||
| end | |||
| end | |||
| @@ -0,0 +1,12 @@ | |||
| class Api::V1::Projects::CommitsController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above, only: [:index, :diff] | |||
| def index | |||
| @result_object = Api::V1::Projects::Commits::ListService.call(@project, {page: page, limit: limit, sha: params[:sha]}, current_user&.gitea_token) | |||
| puts @result_object | |||
| end | |||
| def diff | |||
| @result_object = Api::V1::Projects::Commits::DiffService.call(@project, params[:sha], current_user&.gitea_token) | |||
| end | |||
| end | |||
| @@ -0,0 +1,17 @@ | |||
| class Api::V1::Projects::ContentsController < Api::V1::BaseController | |||
| before_action :require_operate_above_or_fork_project, only: [:batch] | |||
| def batch | |||
| @batch_content_params = batch_content_params | |||
| # 处理下author和committer信息,如果没传则默认为当前用户信息 | |||
| @batch_content_params.merge!(author_email: current_user.mail, author_name: current_user.login) if batch_content_params[:author_email].blank? && batch_content_params[:author_name].blank? | |||
| @batch_content_params.merge!(committer_email: current_user.mail, committer_name: current_user.login) if batch_content_params[:committer_email].blank? && batch_content_params[:committer_name].blank? | |||
| @result_object = Api::V1::Projects::Contents::BatchCreateService.call(@project, @batch_content_params, @project.owner.gitea_token) | |||
| end | |||
| private | |||
| def batch_content_params | |||
| params.require(:content).permit(:author_email, :author_name, :author_timeunix, :branch, :committer_email, :committer_name, :committer_timeunix, :message, :new_branch, files: [ :action_type, :content, :encoding, :file_path]) | |||
| end | |||
| end | |||
| @@ -0,0 +1,12 @@ | |||
| class Api::V1::Projects::GitController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above, only: [:trees, :blobs] | |||
| def trees | |||
| @result_object = Api::V1::Projects::Git::TreesService.call(@project, params[:sha], {recursive: params[:recursive], page: page, limit: limit}, current_user&.gitea_token) | |||
| end | |||
| def blobs | |||
| @result_object = Api::V1::Projects::Git::BlobsService.call(@project, params[:sha], current_user&.gitea_token) | |||
| end | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| class Api::V1::Projects::Pulls::BaseController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above | |||
| before_action :load_pull_request | |||
| end | |||
| @@ -0,0 +1,40 @@ | |||
| class Api::V1::Projects::Pulls::JournalsController < Api::V1::Projects::Pulls::BaseController | |||
| def index | |||
| @journals = Api::V1::Projects::Pulls::Journals::ListService.call(@project, @pull_request, params, current_user) | |||
| @journals = @journals.limit(200) | |||
| end | |||
| def create | |||
| @journal = Api::V1::Projects::Pulls::Journals::CreateService.call(@project, @pull_request, create_params, current_user) | |||
| end | |||
| before_action :find_journal, only: [:update, :destroy] | |||
| def update | |||
| @journal = Api::V1::Projects::Pulls::Journals::UpdateService.call(@project, @pull_request, @journal, update_params, current_user) | |||
| end | |||
| def destroy | |||
| if @journal.destroy | |||
| render_ok | |||
| else | |||
| render_error("删除评论失败!") | |||
| end | |||
| end | |||
| private | |||
| def create_params | |||
| params.permit(:parent_id, :line_code, :note, :commit_id, :path, :type, :review_id, :diff => {}) | |||
| end | |||
| def update_params | |||
| params.permit(:note, :commit_id, :state) | |||
| end | |||
| def find_journal | |||
| @journal = @pull_request.journals.find_by_id(params[:id]) | |||
| return render_not_found unless @journal.present? | |||
| end | |||
| end | |||
| @@ -0,0 +1,20 @@ | |||
| class Api::V1::Projects::Pulls::PullsController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above | |||
| def index | |||
| @pulls = Api::V1::Projects::Pulls::ListService.call(@project, query_params) | |||
| @pulls = kaminari_paginate(@pulls) | |||
| end | |||
| before_action :load_pull_request, only: [:show] | |||
| def show | |||
| @result_object = Api::V1::Projects::Pulls::GetService.call(@project, @pull_request, current_user&.gitea_token) | |||
| @last_review = @pull_request.reviews.order(created_at: :desc).take | |||
| end | |||
| private | |||
| def query_params | |||
| params.permit(:status, :keyword, :priority_id, :issue_tag_id, :version_id, :reviewer_id, :sort_by, :sort_direction) | |||
| end | |||
| end | |||
| @@ -0,0 +1,23 @@ | |||
| class Api::V1::Projects::Pulls::ReviewsController < Api::V1::Projects::Pulls::BaseController | |||
| def index | |||
| @reviews = @pull_request.reviews | |||
| @reviews = @reviews.where(status: params[:status]) if params[:status].present? | |||
| # @reviews = kaminari_paginate(@reviews) | |||
| end | |||
| before_action :require_reviewer, only: [:create] | |||
| def create | |||
| @review = Api::V1::Projects::Pulls::Reviews::CreateService.call(@project, @pull_request, review_params, current_user) | |||
| end | |||
| private | |||
| def require_reviewer | |||
| return render_forbidden('您没有审查权限,请联系项目管理员') if !current_user.admin? && !@pull_request.reviewers.exists?(current_user.id) && !@project.manager?(current_user) | |||
| end | |||
| def review_params | |||
| params.require(:review).permit(:content, :commit_id, :status) | |||
| end | |||
| end | |||
| @@ -0,0 +1,10 @@ | |||
| class Api::V1::Projects::Pulls::VersionsController < Api::V1::Projects::Pulls::BaseController | |||
| def index | |||
| @result_object = Api::V1::Projects::Pulls::Versions::ListService.call(@project, @pull_request, {page: page, limit: limit}, current_user&.gitea_token) | |||
| end | |||
| def diff | |||
| @result_object = Api::V1::Projects::Pulls::Versions::GetDiffService.call(@project, @pull_request, params[:id], {filepath: params[:filepath]}, current_user&.gitea_token) | |||
| end | |||
| end | |||
| @@ -0,0 +1,61 @@ | |||
| class Api::V1::Projects::WebhooksController < Api::V1::BaseController | |||
| before_action :require_manager_above | |||
| before_action :find_webhook, only: [:show, :update, :destroy, :tests, :hooktasks] | |||
| def index | |||
| # @result_object = Api::V1::Projects::Webhooks::ListService.call(@project, current_user&.gitea_token) | |||
| @webhooks = @project.webhooks | |||
| @webhooks = @webhooks.where(type: params[:type]) if params[:type].present? | |||
| @webhooks = kaminari_paginate(@webhooks) | |||
| end | |||
| def create | |||
| return render_error("webhooks数量已到上限!请删除暂不使用的webhooks以进行添加操作") if @project.webhooks.size > 49 | |||
| @result_object = Api::V1::Projects::Webhooks::CreateService.call(@project, create_webhook_params, current_user&.gitea_token) | |||
| end | |||
| def show | |||
| @result_object = Api::V1::Projects::Webhooks::GetService.call(@project, params[:id], current_user&.gitea_token) | |||
| end | |||
| def update | |||
| @result_object = Api::V1::Projects::Webhooks::UpdateService.call(@project, params[:id], webhook_params, current_user&.gitea_token) | |||
| end | |||
| def destroy | |||
| @result_object = Api::V1::Projects::Webhooks::DeleteService.call(@project, params[:id], current_user&.gitea_token) | |||
| if @result_object | |||
| return render_ok | |||
| else | |||
| return render_error('删除失败!') | |||
| end | |||
| end | |||
| def tests | |||
| @result_object = Api::V1::Projects::Webhooks::TestsService.call(@project, params[:id], current_user&.gitea_token) | |||
| if @result_object | |||
| return render_ok | |||
| else | |||
| return render_error('推送失败!') | |||
| end | |||
| end | |||
| def hooktasks | |||
| @hooktasks = @webhook.tasks.where(is_delivered: true).order("delivered desc") | |||
| @hooktasks = kaminari_paginate(@hooktasks) | |||
| end | |||
| private | |||
| def create_webhook_params | |||
| params.require(:webhook).permit(:active, :branch_filter, :http_method, :url, :content_type, :secret, :type, events: []) | |||
| end | |||
| def webhook_params | |||
| params.require(:webhook).permit(:active, :branch_filter, :http_method, :url, :content_type, :secret, events: []) | |||
| end | |||
| def find_webhook | |||
| @webhook = Gitea::Webhook.find_by_id(params[:id]) | |||
| return render_not_found unless @webhook.present? | |||
| end | |||
| end | |||
| @@ -0,0 +1,19 @@ | |||
| class Api::V1::ProjectsController < Api::V1::BaseController | |||
| before_action :require_public_and_member_above, only: [:show, :compare, :blame] | |||
| def index | |||
| render_ok | |||
| end | |||
| def show | |||
| @result_object = Api::V1::Projects::GetService.call(@project, current_user.gitea_token) | |||
| end | |||
| def compare | |||
| @result_object = Api::V1::Projects::CompareService.call(@project, params[:from], params[:to], current_user&.gitea_token) | |||
| end | |||
| def blame | |||
| @result_object = Api::V1::Projects::BlameService.call(@project, params[:sha], params[:filepath], current_user&.gitea_token) | |||
| end | |||
| end | |||
| @@ -0,0 +1,16 @@ | |||
| class Api::V1::Users::FeedbacksController < Api::V1::BaseController | |||
| before_action :load_observe_user | |||
| before_action :check_auth_for_observe_user | |||
| def create | |||
| @result = Api::V1::Users::Feedbacks::CreateService.call(@observe_user, feedback_params) | |||
| return render_error("反馈意见创建失败.") if @result.nil? | |||
| return render_ok | |||
| end | |||
| private | |||
| def feedback_params | |||
| params.permit(:content) | |||
| end | |||
| end | |||
| @@ -0,0 +1,13 @@ | |||
| class Api::V1::Users::ProjectsController < Api::V1::BaseController | |||
| before_action :load_observe_user | |||
| def index | |||
| @object_results = Api::V1::Users::Projects::ListService.call(@observe_user, query_params, current_user) | |||
| @projects = kaminari_paginate(@object_results) | |||
| end | |||
| private | |||
| def query_params | |||
| params.permit(:category, :is_public, :project_type, :sort_by, :sort_direction, :search) | |||
| end | |||
| end | |||
| @@ -0,0 +1,105 @@ | |||
| class Api::V1::UsersController < Api::V1::BaseController | |||
| before_action :load_observe_user | |||
| before_action :check_auth_for_observe_user | |||
| def send_email_vefify_code | |||
| code = %W(0 1 2 3 4 5 6 7 8 9) | |||
| verification_code = code.sample(6).join | |||
| mail = params[:email] | |||
| code_type = params[:code_type] | |||
| sign = Digest::MD5.hexdigest("#{OPENKEY}#{mail}") | |||
| Rails.logger.info sign | |||
| tip_exception(501, "请求不合理") if sign != params[:smscode] | |||
| # 60s内不能重复发送 | |||
| send_email_limit_cache_key = "send_email_60_second_limit:#{mail}" | |||
| tip_exception(-2, '请勿频繁操作') if Rails.cache.exist?(send_email_limit_cache_key) | |||
| send_email_control = LimitForbidControl::SendEmailCode.new(mail) | |||
| tip_exception(-2, '邮件发送太频繁,请稍后再试') if send_email_control.forbid? | |||
| begin | |||
| UserMailer.update_email(mail, verification_code).deliver_now | |||
| Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute) | |||
| send_email_control.increment! | |||
| rescue Exception => e | |||
| logger_error(e) | |||
| tip_exception(-2,"邮件发送失败,请稍后重试") | |||
| end | |||
| ver_params = {code_type: code_type, code: verification_code, email: mail} | |||
| last_code = VerificationCode.where(code_type: code_type, email: mail).last | |||
| last_code.update_attributes!({created_at: Time.current - 10.minute}) if last_code.present? | |||
| data = VerificationCode.new(ver_params) | |||
| if data.save! | |||
| render_ok | |||
| else | |||
| tip_exception(-1, "创建数据失败") | |||
| end | |||
| end | |||
| def check_password | |||
| password = params[:password] | |||
| return tip_exception(-5, "8~16位密码,支持字母数字和符号") unless password =~ CustomRegexp::PASSWORD | |||
| return tip_exception(-5, "密码错误") unless @observe_user.check_password?(password) | |||
| render_ok | |||
| end | |||
| def check_email | |||
| mail = strip(params[:email]) | |||
| return tip_exception(-2, "邮件格式有误") unless mail =~ CustomRegexp::EMAIL | |||
| exist_owner = Owner.find_by(mail: mail) | |||
| return tip_exception(-2, '邮箱已被使用') if exist_owner | |||
| render_ok | |||
| end | |||
| def check_email_verify_code | |||
| code = strip(params[:code]) | |||
| mail = strip(params[:email]) | |||
| code_type = params[:code_type] | |||
| return tip_exception(-2, "邮件格式有误") unless mail =~ CustomRegexp::EMAIL | |||
| verifi_code = VerificationCode.where(email: mail, code: code, code_type: code_type).last | |||
| return render_ok if code == "123123" && EduSetting.get("code_debug") # 万能验证码,用于测试 # TODO 万能验证码,用于测试 | |||
| return tip_exception(-6, "验证码不正确") if verifi_code&.code != code | |||
| return tip_exception(-6, "验证码已失效") if !verifi_code&.effective? | |||
| render_ok | |||
| end | |||
| def check_phone_verify_code | |||
| code = strip(params[:code]) | |||
| phone = strip(params[:phone]) | |||
| code_type = params[:code_type] | |||
| return tip_exception(-2, "手机号格式有误") unless phone =~ CustomRegexp::PHONE | |||
| verifi_code = VerificationCode.where(phone: phone, code: code, code_type: code_type).last | |||
| return render_ok if code == "123123" && EduSetting.get("code_debug") # 万能验证码,用于测试 # TODO 万能验证码,用于测试 | |||
| return tip_exception(-6, "验证码不正确") if verifi_code&.code != code | |||
| return tip_exception(-6, "验证码已失效") if !verifi_code&.effective? | |||
| render_ok | |||
| end | |||
| def update_email | |||
| @result_object = Api::V1::Users::UpdateEmailService.call(@observe_user, params, current_user.gitea_token) | |||
| if @result_object | |||
| return render_ok | |||
| else | |||
| return render_error('更改邮箱失败!') | |||
| end | |||
| end | |||
| def update_phone | |||
| @result_object = Api::V1::Users::UpdatePhoneService.call(@observe_user, params) | |||
| if @result_object | |||
| return render_ok | |||
| else | |||
| return render_error('更改手机号失败!') | |||
| end | |||
| end | |||
| end | |||
| @@ -1,261 +1,258 @@ | |||
| #coding=utf-8 | |||
| # | |||
| # 文件上传 | |||
| class AttachmentsController < ApplicationController | |||
| before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file] | |||
| before_action :find_file, only: %i[show destroy] | |||
| before_action :attachment_candown, only: [:show] | |||
| skip_before_action :check_sign, only: [:show, :create] | |||
| include ApplicationHelper | |||
| def show | |||
| # 1. 优先跳到cdn | |||
| # 2. 如果没有cdn,send_file | |||
| if @file.cloud_url.present? | |||
| update_downloads(@file) | |||
| redirect_to @file.cloud_url and return | |||
| end | |||
| type_attachment = params[:disposition] || "attachment" | |||
| if type_attachment == "inline" | |||
| send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' | |||
| elsif type_attachment == "MP4" | |||
| send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true | |||
| else | |||
| send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') | |||
| end | |||
| update_downloads(@file) | |||
| end | |||
| def get_file | |||
| normal_status(-1, "参数缺失") if params[:download_url].blank? | |||
| url = URI.encode(params[:download_url].to_s.gsub("http:", "https:")) | |||
| if url.starts_with?(base_url) | |||
| domain = Gitea.gitea_config[:domain] | |||
| api_url = Gitea.gitea_config[:base_url] | |||
| url = url.split(base_url)[1].gsub("api", "repos").gsub('?filepath=', '/').gsub('&', '?') | |||
| request_url = [domain, api_url, url, "?ref=#{params[:ref]}&access_token=#{current_user&.gitea_token}"].join | |||
| response = Faraday.get(request_url) | |||
| filename = url.to_s.split("/").pop() | |||
| else | |||
| response = Faraday.get(url) | |||
| filename = params[:download_url].to_s.split("/").pop() | |||
| end | |||
| send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment') | |||
| end | |||
| def create | |||
| # 1. 本地存储 | |||
| # 2. 上传到云 | |||
| begin | |||
| upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称 | |||
| uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}") | |||
| raise "未上传文件" unless upload_file | |||
| folder = file_storage_directory | |||
| raise "存储目录未定义" unless folder.present? | |||
| month_folder = current_month_folder | |||
| save_path = File.join(folder, month_folder) | |||
| ext = file_ext(upload_file.original_filename) | |||
| local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext) | |||
| content_type = upload_file.content_type.presence || 'application/octet-stream' | |||
| # remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type) | |||
| remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端 | |||
| logger.info "local_path: #{local_path}" | |||
| logger.info "remote_path: #{remote_path}" | |||
| disk_filename = local_path[save_path.size + 1, local_path.size] | |||
| #存数据库 | |||
| # | |||
| @attachment = Attachment.where(disk_filename: disk_filename, | |||
| author_id: current_user.id, | |||
| cloud_url: remote_path).first | |||
| if @attachment.blank? | |||
| @attachment = Attachment.new | |||
| @attachment.filename = upload_file.original_filename | |||
| @attachment.disk_filename = local_path[save_path.size + 1, local_path.size] | |||
| @attachment.filesize = upload_file.tempfile.size | |||
| @attachment.content_type = content_type | |||
| @attachment.digest = digest | |||
| @attachment.author_id = current_user.id | |||
| @attachment.disk_directory = month_folder | |||
| @attachment.cloud_url = remote_path | |||
| @attachment.save! | |||
| else | |||
| logger.info "文件已存在,id = #{@attachment.id}, filename = #{@attachment.filename}" | |||
| end | |||
| render_json | |||
| rescue => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| end | |||
| def destroy | |||
| begin | |||
| @file_path = absolute_path(local_path(@file)) | |||
| #return normal_status(403, "") unless @file.author == current_user | |||
| @file.destroy! | |||
| delete_file(@file_path) | |||
| normal_status("删除成功") | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| raise ActiveRecord::Rollback | |||
| end | |||
| end | |||
| # 附件为视频时,点击播放 | |||
| def preview_attachment | |||
| attachment = Attachment.find_by(id: params[:id]) | |||
| dir_path = "#{Rails.root}/public/preview" | |||
| Dir.mkdir(dir_path) unless Dir.exist?(dir_path) | |||
| if params[:status] == "preview" | |||
| if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/") | |||
| render json: {status: 1, url: "/preview/#{attachment.disk_filename}"} | |||
| else | |||
| normal_status(-1, "出现错误,请稍后重试") | |||
| end | |||
| else | |||
| if system("rm -rf #{dir_path}/#{attachment.disk_filename}") | |||
| normal_status(1, "操作成功") | |||
| else | |||
| normal_status(-1, "出现错误,请稍后重试") | |||
| end | |||
| end | |||
| end | |||
| private | |||
| def find_file | |||
| @file = | |||
| if params[:type] == 'history' | |||
| AttachmentHistory.find params[:id] | |||
| else | |||
| Attachment.find params[:id] | |||
| end | |||
| end | |||
| def delete_file(file_path) | |||
| File.delete(file_path) if File.exist?(file_path) | |||
| end | |||
| def current_month_folder | |||
| date = Time.now | |||
| "#{date.year}/#{date.month.to_s.rjust(2, '0')}" | |||
| end | |||
| def file_ext(file_name) | |||
| ext = '' | |||
| exts = file_name.split(".") | |||
| if exts.size > 1 | |||
| ext = ".#{exts.last}" | |||
| end | |||
| ext | |||
| end | |||
| def file_save_to_local(save_path, temp_file, ext) | |||
| unless Dir.exists?(save_path) | |||
| FileUtils.mkdir_p(save_path) ##不成功这里会抛异常 | |||
| end | |||
| digest = md5_file(temp_file) | |||
| digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}" | |||
| local_file_path = File.join(save_path, digest) + ext | |||
| save_temp_file(temp_file, local_file_path) | |||
| [local_file_path, digest] | |||
| end | |||
| def save_temp_file(temp_file, save_file_path) | |||
| File.open(save_file_path, 'wb') do |f| | |||
| temp_file.rewind | |||
| while (buffer = temp_file.read(8192)) | |||
| f.write(buffer) | |||
| end | |||
| end | |||
| end | |||
| def md5_file(temp_file) | |||
| md5 = Digest::MD5.new | |||
| temp_file.rewind | |||
| while (buffer = temp_file.read(8192)) | |||
| md5.update(buffer) | |||
| end | |||
| md5.hexdigest | |||
| end | |||
| def file_save_to_ucloud(path, file, content_type) | |||
| ufile = Educoder::Ufile.new( | |||
| ucloud_public_key: edu_setting('public_key'), | |||
| ucloud_private_key: edu_setting('private_key'), | |||
| ucloud_public_read: true, | |||
| ucloud_public_bucket: edu_setting('public_bucket'), | |||
| ucloud_public_bucket_host: edu_setting('public_bucket_host'), | |||
| ucloud_public_cdn_host: edu_setting('public_cdn_host'), | |||
| ) | |||
| File.open(file) do |f| | |||
| ufile.put(path, f, 'Content-Type' => content_type) | |||
| end | |||
| edu_setting('public_cdn_host') + "/" + path | |||
| end | |||
| def attachment_candown | |||
| unless current_user.admin? || current_user.business? | |||
| candown = true | |||
| unless params[:type] == 'history' | |||
| if @file.container && current_user.logged? | |||
| if @file.container.is_a?(Issue) | |||
| course = @file.container.project | |||
| candown = course.member?(current_user) | |||
| elsif @file.container.is_a?(Journal) | |||
| course = @file.container.issue.project | |||
| candown = course.member?(current_user) | |||
| else | |||
| course = nil | |||
| end | |||
| tip_exception(403, "您没有权限进入") if course.present? && !candown | |||
| tip_exception(403, "您没有权限进入") if @file.container.is_a?(ApplyUserAuthentication) | |||
| end | |||
| end | |||
| end | |||
| end | |||
| def send_file_with_range(path, options = {}) | |||
| logger.info("########request.headers: #{request.headers}") | |||
| logger.info("########request.headers: #{File.exist?(path)}") | |||
| if File.exist?(path) | |||
| size = File.size(path) | |||
| logger.info("########request.headers: #{request.headers}") | |||
| if !request.headers["Range"] | |||
| status_code = 200 # 200 OK | |||
| offset = 0 | |||
| length = File.size(path) | |||
| else | |||
| status_code = 206 # 206 Partial Content | |||
| bytes = Rack::Utils.byte_ranges(request.headers, size)[0] | |||
| offset = bytes.begin | |||
| length = bytes.end - bytes.begin | |||
| end | |||
| response.header["Accept-Ranges"] = "bytes" | |||
| response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes | |||
| response.header["status"] = status_code | |||
| send_data IO.binread(path, length, offset), options | |||
| else | |||
| raise ActionController::MissingFile, "Cannot read file #{path}." | |||
| end | |||
| end | |||
| end | |||
| #coding=utf-8 | |||
| # | |||
| # 文件上传 | |||
| class AttachmentsController < ApplicationController | |||
| before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file] | |||
| before_action :find_file, only: %i[show destroy] | |||
| before_action :attachment_candown, only: [:show] | |||
| skip_before_action :check_sign, only: [:show, :create] | |||
| include ApplicationHelper | |||
| def show | |||
| # 1. 优先跳到cdn | |||
| # 2. 如果没有cdn,send_file | |||
| if @file.cloud_url.present? | |||
| update_downloads(@file) | |||
| redirect_to @file.cloud_url and return | |||
| end | |||
| type_attachment = params[:disposition] || "attachment" | |||
| if type_attachment == "inline" | |||
| send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' | |||
| elsif type_attachment == "MP4" | |||
| send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true | |||
| else | |||
| send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') | |||
| end | |||
| update_downloads(@file) | |||
| end | |||
| def get_file | |||
| normal_status(-1, "参数缺失") if params[:download_url].blank? | |||
| url = base_url.starts_with?("https:") ? URI.encode(params[:download_url].to_s.gsub("http:", "https:")) : URI.encode(params[:download_url].to_s) | |||
| if url.starts_with?(base_url) && !url.starts_with?("#{base_url}/repo") | |||
| domain = GiteaService.gitea_config[:domain] | |||
| api_url = GiteaService.gitea_config[:base_url] | |||
| url = ("/repos"+url.split(base_url + "/api")[1]).gsub('?filepath=', '/').gsub('&', '?') | |||
| request_url = [domain, api_url, url, "?ref=#{params[:ref]}&access_token=#{current_user&.gitea_token}"].join | |||
| response = Faraday.get(request_url) | |||
| filename = url.to_s.split("/").pop() | |||
| else | |||
| response = Faraday.get(url) | |||
| filename = params[:download_url].to_s.split("/").pop() | |||
| end | |||
| send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment') | |||
| end | |||
| def create | |||
| # 1. 本地存储 | |||
| # 2. 上传到云 | |||
| begin | |||
| upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称 | |||
| uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}") | |||
| raise "未上传文件" unless upload_file | |||
| folder = file_storage_directory | |||
| raise "存储目录未定义" unless folder.present? | |||
| month_folder = current_month_folder | |||
| save_path = File.join(folder, month_folder) | |||
| ext = file_ext(upload_file.original_filename) | |||
| local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext) | |||
| content_type = upload_file.content_type.presence || 'application/octet-stream' | |||
| # remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type) | |||
| remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端 | |||
| logger.info "local_path: #{local_path}" | |||
| logger.info "remote_path: #{remote_path}" | |||
| disk_filename = local_path[save_path.size + 1, local_path.size] | |||
| #存数据库 | |||
| # | |||
| @attachment = Attachment.where(disk_filename: disk_filename, | |||
| author_id: current_user.id, | |||
| cloud_url: remote_path).first | |||
| if @attachment.blank? | |||
| @attachment = Attachment.new | |||
| @attachment.filename = upload_file.original_filename | |||
| @attachment.disk_filename = local_path[save_path.size + 1, local_path.size] | |||
| @attachment.filesize = upload_file.tempfile.size | |||
| @attachment.content_type = content_type | |||
| @attachment.digest = digest | |||
| @attachment.author_id = current_user.id | |||
| @attachment.disk_directory = month_folder | |||
| @attachment.cloud_url = remote_path | |||
| @attachment.save! | |||
| else | |||
| logger.info "文件已存在,id = #{@attachment.id}, filename = #{@attachment.filename}" | |||
| end | |||
| render_json | |||
| rescue => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| end | |||
| def destroy | |||
| begin | |||
| @file_path = absolute_path(local_path(@file)) | |||
| #return normal_status(403, "") unless @file.author == current_user | |||
| @file.destroy! | |||
| delete_file(@file_path) | |||
| normal_status("删除成功") | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| raise ActiveRecord::Rollback | |||
| end | |||
| end | |||
| # 附件为视频时,点击播放 | |||
| def preview_attachment | |||
| attachment = Attachment.find_by(id: params[:id]) | |||
| dir_path = "#{Rails.root}/public/preview" | |||
| Dir.mkdir(dir_path) unless Dir.exist?(dir_path) | |||
| if params[:status] == "preview" | |||
| if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/") | |||
| render json: {status: 1, url: "/preview/#{attachment.disk_filename}"} | |||
| else | |||
| normal_status(-1, "出现错误,请稍后重试") | |||
| end | |||
| else | |||
| if system("rm -rf #{dir_path}/#{attachment.disk_filename}") | |||
| normal_status(1, "操作成功") | |||
| else | |||
| normal_status(-1, "出现错误,请稍后重试") | |||
| end | |||
| end | |||
| end | |||
| private | |||
| def find_file | |||
| @file = | |||
| if params[:type] == 'history' | |||
| AttachmentHistory.find params[:id] | |||
| else | |||
| Attachment.find params[:id] | |||
| end | |||
| end | |||
| def delete_file(file_path) | |||
| File.delete(file_path) if File.exist?(file_path) | |||
| end | |||
| def current_month_folder | |||
| date = Time.now | |||
| "#{date.year}/#{date.month.to_s.rjust(2, '0')}" | |||
| end | |||
| def file_ext(file_name) | |||
| ext = '' | |||
| exts = file_name.split(".") | |||
| if exts.size > 1 | |||
| ext = ".#{exts.last}" | |||
| end | |||
| ext | |||
| end | |||
| def file_save_to_local(save_path, temp_file, ext) | |||
| unless Dir.exists?(save_path) | |||
| FileUtils.mkdir_p(save_path) ##不成功这里会抛异常 | |||
| end | |||
| digest = md5_file(temp_file) | |||
| digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}" | |||
| local_file_path = File.join(save_path, digest) + ext | |||
| save_temp_file(temp_file, local_file_path) | |||
| [local_file_path, digest] | |||
| end | |||
| def save_temp_file(temp_file, save_file_path) | |||
| File.open(save_file_path, 'wb') do |f| | |||
| temp_file.rewind | |||
| while (buffer = temp_file.read(8192)) | |||
| f.write(buffer) | |||
| end | |||
| end | |||
| end | |||
| def md5_file(temp_file) | |||
| md5 = Digest::MD5.new | |||
| temp_file.rewind | |||
| while (buffer = temp_file.read(8192)) | |||
| md5.update(buffer) | |||
| end | |||
| md5.hexdigest | |||
| end | |||
| def file_save_to_ucloud(path, file, content_type) | |||
| ufile = Gitlink::Ufile.new( | |||
| ucloud_public_key: edu_setting('public_key'), | |||
| ucloud_private_key: edu_setting('private_key'), | |||
| ucloud_public_read: true, | |||
| ucloud_public_bucket: edu_setting('public_bucket'), | |||
| ucloud_public_bucket_host: edu_setting('public_bucket_host'), | |||
| ucloud_public_cdn_host: edu_setting('public_cdn_host'), | |||
| ) | |||
| File.open(file) do |f| | |||
| ufile.put(path, f, 'Content-Type' => content_type) | |||
| end | |||
| edu_setting('public_cdn_host') + "/" + path | |||
| end | |||
| def attachment_candown | |||
| unless current_user.admin? || current_user.business? | |||
| candown = true | |||
| if @file.container | |||
| if @file.container.is_a?(Issue) | |||
| project = @file.container.project | |||
| candown = project.is_public || (current_user.logged? && project.member?(current_user)) | |||
| elsif @file.container.is_a?(Journal) | |||
| project = @file.container.issue.project | |||
| candown = project.is_public || (current_user.logged? && project.member?(current_user)) | |||
| else | |||
| project = nil | |||
| end | |||
| tip_exception(403, "您没有权限进入") if project.present? && !candown | |||
| end | |||
| end | |||
| end | |||
| def send_file_with_range(path, options = {}) | |||
| logger.info("########request.headers: #{request.headers}") | |||
| logger.info("########request.headers: #{File.exist?(path)}") | |||
| if File.exist?(path) | |||
| size = File.size(path) | |||
| logger.info("########request.headers: #{request.headers}") | |||
| if !request.headers["Range"] | |||
| status_code = 200 # 200 OK | |||
| offset = 0 | |||
| length = File.size(path) | |||
| else | |||
| status_code = 206 # 206 Partial Content | |||
| bytes = Rack::Utils.byte_ranges(request.headers, size)[0] | |||
| offset = bytes.begin | |||
| length = bytes.end - bytes.begin | |||
| end | |||
| response.header["Accept-Ranges"] = "bytes" | |||
| response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes | |||
| response.header["status"] = status_code | |||
| send_data IO.binread(path, length, offset), options | |||
| else | |||
| raise ActionController::MissingFile, "Cannot read file #{path}." | |||
| end | |||
| end | |||
| end | |||
| @@ -1,35 +1,19 @@ | |||
| class BindUsersController < ApplicationController | |||
| # before_action :require_login | |||
| def create | |||
| # user = CreateBindUserService.call(create_params) | |||
| # | |||
| if params[:type] == "qq" | |||
| begin | |||
| user = CreateBindUserService.call(current_user, create_params) | |||
| successful_authentication(user) if user.id != current_user.id | |||
| render_ok | |||
| rescue ApplicationService::Error => ex | |||
| render_error(ex.message) | |||
| end | |||
| else | |||
| begin | |||
| tip_exception '系统错误' if session[:unionid].blank? | |||
| bind_user = User.try_to_login(params[:username], params[:password]) | |||
| tip_exception '用户名或者密码错误' if bind_user.blank? | |||
| tip_exception '用户名或者密码错误' unless bind_user.check_password?(params[:password].to_s) | |||
| tip_exception '该账号已被绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s) | |||
| OpenUsers::Wechat.create!(user: bind_user, uid: session[:unionid]) | |||
| successful_authentication(bind_user) | |||
| render_ok | |||
| rescue Exception => e | |||
| render_error(e.message) | |||
| end | |||
| end | |||
| Rails.logger.debug "--------------开始绑定用户------------" | |||
| Rails.logger.debug "--------------params: #{params.to_unsafe_h}" | |||
| tip_exception '系统错误' if session[:unionid].blank? | |||
| bind_user = User.try_to_login(params[:username], params[:password]) | |||
| tip_exception '用户名或者密码错误' if bind_user.blank? | |||
| tip_exception '用户名或者密码错误' unless bind_user.check_password?(params[:password].to_s) | |||
| tip_exception '参数错误' unless ["qq", "wechat", "gitee", "github", "educoder"].include?(params[:type].to_s) | |||
| tip_exception '该账号已被绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s) | |||
| "OpenUsers::#{params[:type].to_s.capitalize}".constantize.create!(user: bind_user, uid: session[:unionid]) | |||
| successful_authentication(bind_user) | |||
| @user = bind_user | |||
| end | |||
| def new_user | |||
| @@ -8,7 +8,7 @@ class Ci::BaseController < ApplicationController | |||
| namespace = params[:owner] | |||
| id = params[:repo] || params[:id] | |||
| @ci_user, @repo = Ci::Repo.find_with_namespace(namespace, id) | |||
| @ci_user, @repo = Ci::Repo.find_with_namespace(namespace, id, current_user.login) | |||
| end | |||
| def load_all_repo | |||
| @@ -14,12 +14,12 @@ class Ci::CloudAccountsController < Ci::BaseController | |||
| def create | |||
| flag, msg = check_bind_cloud_account! | |||
| return render_error(msg) if flag === true | |||
| return tip_exception(msg) if flag === true | |||
| ActiveRecord::Base.transaction do | |||
| @cloud_account = bind_account! | |||
| if @cloud_account.blank? | |||
| render_error('激活失败, 请检查你的云服务器信息是否正确.') | |||
| tip_exception('激活失败, 请检查你的云服务器信息是否正确.') | |||
| raise ActiveRecord::Rollback | |||
| else | |||
| current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) | |||
| @@ -27,17 +27,17 @@ class Ci::CloudAccountsController < Ci::BaseController | |||
| end | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def activate | |||
| return render_error('请先认证') unless current_user.ci_certification? | |||
| return tip_exception('请先认证') unless current_user.ci_certification? | |||
| begin | |||
| @cloud_account = Ci::CloudAccount.find params[:id] | |||
| ActiveRecord::Base.transaction do | |||
| if @repo | |||
| return render_error('该项目已经激活') if @repo.repo_active? | |||
| return tip_exception('该项目已经激活') if @repo.repo_active? | |||
| @repo.activate!(@project) | |||
| else | |||
| @repo = Ci::Repo.auto_create!(@ci_user, @project) | |||
| @@ -50,7 +50,7 @@ class Ci::CloudAccountsController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| end | |||
| @@ -59,39 +59,39 @@ class Ci::CloudAccountsController < Ci::BaseController | |||
| def bind | |||
| flag, msg = check_bind_cloud_account! | |||
| return render_error(msg) if flag === true | |||
| return tip_exception(msg) if flag === true | |||
| ActiveRecord::Base.transaction do | |||
| @cloud_account = bind_account! | |||
| if @cloud_account.blank? | |||
| render_error('激活失败, 请检查你的云服务器信息是否正确.') | |||
| tip_exception('激活失败, 请检查你的云服务器信息是否正确.') | |||
| raise ActiveRecord::Rollback | |||
| else | |||
| current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) | |||
| end | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def trustie_bind | |||
| account = params[:account].to_s | |||
| return render_error("account不能为空.") if account.blank? | |||
| return tip_exception("account不能为空.") if account.blank? | |||
| flag, msg = check_trustie_bind_cloud_account! | |||
| return render_error(msg) if flag === true | |||
| return tip_exception(msg) if flag === true | |||
| ActiveRecord::Base.transaction do | |||
| @cloud_account = trustie_bind_account! | |||
| if @cloud_account.blank? | |||
| render_error('激活失败, 请检查你的云服务器信息是否正确.') | |||
| tip_exception('激活失败, 请检查你的云服务器信息是否正确.') | |||
| raise ActiveRecord::Rollback | |||
| else | |||
| current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) | |||
| end | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def unbind | |||
| @@ -107,18 +107,18 @@ class Ci::CloudAccountsController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def oauth_grant | |||
| password = params[:password].to_s | |||
| return render_error('你输入的密码不正确.') unless current_user.check_password?(password) | |||
| return tip_exception('你输入的密码不正确.') unless current_user.check_password?(password) | |||
| oauth = current_user.oauths.last | |||
| return render_error("服务器出小差了.") if oauth.blank? | |||
| return tip_exception("服务器出小差了.") if oauth.blank? | |||
| result = gitea_oauth_grant!(password, oauth) | |||
| return render_error('授权失败.') unless result === true | |||
| return tip_exception('授权失败.') unless result === true | |||
| current_user.set_drone_step!(User::DEVOPS_CERTIFICATION) | |||
| end | |||
| @@ -30,7 +30,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| ActiveRecord::Base.transaction do | |||
| size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size | |||
| if size > 0 | |||
| render_error("#{params[:branch]}分支已经存在流水线!") | |||
| tip_exception("#{params[:branch]}分支已经存在流水线!") | |||
| return | |||
| end | |||
| pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name],owner: params[:owner], | |||
| @@ -53,7 +53,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok({id: pipeline.id}) | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| # 在代码库创建文件 | |||
| @@ -81,6 +81,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| repo_branch: pipeline.branch, | |||
| repo_config: pipeline.file_name | |||
| } | |||
| Rails.logger.info("########create_params===#{create_params.to_json}") | |||
| repo = Ci::Repo.create_repo(create_params) | |||
| repo | |||
| end | |||
| @@ -118,7 +119,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def destroy | |||
| @@ -132,7 +133,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def content | |||
| @@ -182,7 +183,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def update_stage | |||
| @@ -192,7 +193,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def delete_stage | |||
| @@ -205,7 +206,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def update_stage_index(pipeline_id, show_index, diff) | |||
| @@ -229,7 +230,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| unless steps.empty? | |||
| steps.each do |step| | |||
| unless step[:template_id] | |||
| render_error('请选择模板!') | |||
| tip_exception('请选择模板!') | |||
| return | |||
| end | |||
| if !step[:id] | |||
| @@ -246,7 +247,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def create_stage_step | |||
| @@ -262,7 +263,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def update_stage_step | |||
| @@ -279,7 +280,7 @@ class Ci::PipelinesController < Ci::BaseController | |||
| render_ok | |||
| end | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def delete_stage_step | |||
| @@ -289,6 +290,6 @@ class Ci::PipelinesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| end | |||
| @@ -30,19 +30,19 @@ class Ci::ProjectsController < Ci::BaseController | |||
| @file = interactor.result | |||
| render_result(1, "更新成功") | |||
| else | |||
| render_error(interactor.error) | |||
| tip_exception(interactor.error) | |||
| end | |||
| end | |||
| def activate | |||
| return render_error('你还未认证') unless current_user.ci_certification? | |||
| return tip_exception('你还未认证') unless current_user.ci_certification? | |||
| begin | |||
| ActiveRecord::Base.transaction do | |||
| if @repo | |||
| return render_error('该项目已经激活') if @repo.repo_active? | |||
| @repo.destroy! if @repo&.repo_user_id == 0 | |||
| return tip_exception('该项目已经激活') if @repo.repo_active? | |||
| @repo.activate!(@project) | |||
| return render_ok | |||
| else | |||
| @repo = Ci::Repo.auto_create!(@ci_user, @project) | |||
| @ci_user.update_column(:user_syncing, false) | |||
| @@ -55,12 +55,12 @@ class Ci::ProjectsController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| end | |||
| def deactivate | |||
| return render_error('该项目已经取消激活') if !@repo.repo_active? | |||
| return tip_exception('该项目已经取消激活') if !@repo.repo_active? | |||
| @project.update_column(:open_devops, false) | |||
| @repo.deactivate_repos! | |||
| @@ -20,14 +20,14 @@ class Ci::SecretsController < Ci::BaseController | |||
| if result["id"] | |||
| render_ok | |||
| else | |||
| render_error(result["message"]) | |||
| tip_exception(result["message"]) | |||
| end | |||
| else | |||
| result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret | |||
| if result["id"] | |||
| render_ok | |||
| else | |||
| render_error(result["message"]) | |||
| tip_exception(result["message"]) | |||
| end | |||
| end | |||
| end | |||
| @@ -39,14 +39,14 @@ class Ci::SecretsController < Ci::BaseController | |||
| Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], {name: name}).delete_secret | |||
| render_ok | |||
| else | |||
| render_error("参数名不能为空") | |||
| tip_exception("参数名不能为空") | |||
| end | |||
| rescue Exception => ex | |||
| render_ok | |||
| end | |||
| def ci_drone_url | |||
| user = User.find_by(login: params[:owner]) | |||
| user = User.find_by(login: params[:owner]) || User.find_by(login: current_user.login) | |||
| user&.ci_cloud_account.drone_url | |||
| end | |||
| @@ -50,7 +50,7 @@ class Ci::TemplatesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def update | |||
| @@ -63,7 +63,7 @@ class Ci::TemplatesController < Ci::BaseController | |||
| ) | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| def destroy | |||
| @@ -73,7 +73,7 @@ class Ci::TemplatesController < Ci::BaseController | |||
| end | |||
| render_ok | |||
| rescue Exception => ex | |||
| render_error(ex.message) | |||
| tip_exception(ex.message) | |||
| end | |||
| #======流水线模板查询=====# | |||
| @@ -0,0 +1,138 @@ | |||
| #coding=utf-8 | |||
| class ClaimsController < ApplicationController | |||
| # skip_before_action :verify_authenticity_token | |||
| protect_from_forgery with: :null_session | |||
| before_action :require_login, except: [:index] | |||
| before_action :set_issue | |||
| def index | |||
| @user_claimed = 0 | |||
| @claims = @issue.claims.claim_includes.order("created_at desc") | |||
| @claims.each do |claim| | |||
| if claim.user_id == current_user.id | |||
| @user_claimed = 1 | |||
| break | |||
| end | |||
| end | |||
| render file: 'app/views/claims/list.json.jbuilder' | |||
| end | |||
| def create | |||
| @claim = Claim.find_by_sql(["select id from claims where issue_id=? and user_id=?",params[:issue_id], current_user.id]) | |||
| if @claim.present? | |||
| return normal_status(-1,"您已经声明过该易修") | |||
| end | |||
| ActiveRecord::Base.transaction do | |||
| @claim = Claim.new(parse_issue_params(params)) | |||
| if @claim.save | |||
| @claims = @issue.claims.claim_includes.order("created_at desc") | |||
| @user_claimed = 1 | |||
| journal_params = { | |||
| journalized_id: params[:issue_id], | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: "新建声明: #{params[:claim_note]}", | |||
| } | |||
| journal = Journal.new(journal_params) | |||
| if journal.save | |||
| render file: 'app/views/claims/list.json.jbuilder' | |||
| else | |||
| normal_status(-1,"新建声明关联评论操作失败") | |||
| end | |||
| else | |||
| normal_status(-1,"新建声明操作失败") | |||
| end | |||
| end | |||
| end | |||
| def update | |||
| @claim = Claim.find_by_id(params[:claim_id]) | |||
| if @claim.blank? | |||
| return normal_status(-1,"易修不存在") | |||
| end | |||
| if @claim.user_id != current_user.id | |||
| return normal_status(-1,"你不能更新别人的声明") | |||
| end | |||
| ActiveRecord::Base.transaction do | |||
| if @claim.update_attribute(:note,params[:claim_note]) | |||
| @claims = @issue.claims.claim_includes.order("created_at desc") | |||
| @user_claimed = 1 | |||
| journal_params = { | |||
| journalized_id: params[:issue_id], | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: "更新声明: #{params[:claim_note]}", | |||
| } | |||
| journal = Journal.new(journal_params) | |||
| if journal.save | |||
| render file: 'app/views/claims/list.json.jbuilder' | |||
| else | |||
| normal_status(-1,"新建声明关联评论操作失败") | |||
| end | |||
| else | |||
| normal_status(-1,"声明更新操作失败") | |||
| end | |||
| end | |||
| end | |||
| def destroy | |||
| @claim = Claim.find_by_sql(["select id from claims where issue_id=? and user_id=?",params[:issue_id], current_user.id]) | |||
| if @claim.blank? | |||
| normal_status(-1,"您未曾声明过该易修") | |||
| else | |||
| @claim = @claim[0] | |||
| # 判断current user是否是claimer | |||
| ActiveRecord::Base.transaction do | |||
| if @claim.destroy | |||
| @claims = @issue.claims.claim_includes.order("created_at desc") | |||
| @user_claimed = 0 | |||
| journal_params = { | |||
| journalized_id: params[:issue_id], | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: "取消声明", | |||
| } | |||
| journal = Journal.new(journal_params) | |||
| if journal.save | |||
| render file: 'app/views/claims/list.json.jbuilder' | |||
| else | |||
| normal_status(-1,"新建声明关联评论操作失败") | |||
| end | |||
| else | |||
| normal_status(-1,"取消声明操作失败") | |||
| end | |||
| end | |||
| end | |||
| end | |||
| private | |||
| def parse_issue_params(params) | |||
| { | |||
| issue_id: params[:issue_id], | |||
| user_id: current_user.id, | |||
| note: params[:claim_note], | |||
| } | |||
| end | |||
| def set_issue | |||
| @issue = Issue.find_by_id(params[:issue_id]) | |||
| unless @issue.present? | |||
| normal_status(-1, "易修不存在") | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,30 @@ | |||
| class CommitLogsController < ApplicationController | |||
| def create | |||
| tip_exception "未认证" unless params[:token].to_s == "7917908927b6f1b792f2027a08a8b24a2de42c1692c2fd45da0dee5cf90a5af5" | |||
| ref = params[:ref] | |||
| user_name = params[:pusher][:login] | |||
| user_mail = params[:pusher][:email] | |||
| user = User.find_by(mail: user_mail) | |||
| user = User.find_by(login: user_name) if user.blank? | |||
| repository_id = params[:repository][:id] | |||
| repository_name = params[:repository][:name] | |||
| repository_full_name = params[:repository][:full_name] | |||
| owner_name = repository_full_name.split("/")[0] | |||
| owner = User.find_by(login: owner_name) | |||
| project = Project.where(identifier: repository_name).where(user_id: owner&.id)&.first | |||
| project = Project.where(identifier: repository_name).where(gpid: repository_id)&.first if project.blank? | |||
| project.update_column(:updated_on, Time.now) if project.present? | |||
| params[:commits].each do |commit| | |||
| commit_id = commit[:id] | |||
| message = commit[:message] | |||
| CommitLog.create(user: user, project: project, repository_id: repository_id, | |||
| name: repository_name, full_name: repository_full_name, | |||
| ref: ref, commit_id: commit_id, message: message) | |||
| # 统计数据新增 | |||
| CacheAsyncSetJob.perform_later("project_common_service", {commits: 1}, project.id) | |||
| end | |||
| end | |||
| end | |||
| @@ -6,9 +6,18 @@ class CompareController < ApplicationController | |||
| end | |||
| def show | |||
| load_compare_params | |||
| compare | |||
| @merge_status, @merge_message = get_merge_message | |||
| if params[:type] == "sha" | |||
| load_compare_params | |||
| @compare_result ||= gitea_compare(@base, @head) | |||
| else | |||
| load_compare_params | |||
| compare | |||
| @merge_status, @merge_message = get_merge_message | |||
| end | |||
| @page_size = page_size <= 0 ? 1 : page_size | |||
| @page_limit = page_limit <=0 ? 15 : page_limit | |||
| @page_offset = (@page_size -1) * @page_limit | |||
| Rails.logger.info("+========#{@page_size}-#{@page_limit}-#{@page_offset}") | |||
| end | |||
| private | |||
| @@ -16,6 +25,7 @@ class CompareController < ApplicationController | |||
| if @base.blank? || @head.blank? | |||
| return -2, "请选择分支" | |||
| else | |||
| return -2, "目标仓库未开启合并请求(PR)功能" unless @project.has_menu_permission("pulls") | |||
| if @head.include?(":") | |||
| fork_project = @project.forked_projects.joins(:owner).where(users: {login: @head.to_s.split("/")[0]}).take | |||
| return -2, "请选择正确的仓库" unless fork_project.present? | |||
| @@ -24,14 +34,10 @@ class CompareController < ApplicationController | |||
| @exist_pullrequest = @project.pull_requests.where(is_original: false, head: @base, base: @head, status: 0).take | |||
| end | |||
| if @exist_pullrequest.present? | |||
| return -2, "在这些分支之间的合并请求已存在:<a href='/projects/#{@owner.login}/#{@project.identifier}/pulls/#{@exist_pullrequest.id}/Messagecount'>#{@exist_pullrequest.try(:title)}</a>" | |||
| else | |||
| # if @compare_result["Commits"].blank? && @compare_result["Diff"].blank? | |||
| # return -2, "分支内容相同,无需创建合并请求" | |||
| # end | |||
| puts "``````````", @base, @head | |||
| if @base == @head | |||
| return -2, "请选择不同分支" | |||
| return -2, "在这些分支之间的合并请求已存在:<a href='/#{@owner.login}/#{@project.identifier}/pulls/#{@exist_pullrequest.id}'>#{@exist_pullrequest.try(:title)}</a>" | |||
| else | |||
| if @compare_result["Commits"].blank? && @compare_result["Diff"].blank? | |||
| return -2, "分支内容相同,无需创建合并请求" | |||
| end | |||
| end | |||
| end | |||
| @@ -46,12 +52,22 @@ class CompareController < ApplicationController | |||
| end | |||
| def load_compare_params | |||
| @base = Addressable::URI.unescape(params[:base]) | |||
| @head = params[:head].include?('json') ? params[:head]&.split('.json')[0] : params[:head] | |||
| # @base = Addressable::URI.unescape(params[:base]) | |||
| @base = params[:base].include?(":") ? Addressable::URI.unescape(params[:base].split(":")[0]) + ':' + Base64.decode64(params[:base].split(":")[1]) : Base64.decode64(params[:base]) | |||
| @head = params[:head].include?('.json') ? params[:head][0..-6] : params[:head] | |||
| # @head = Addressable::URI.unescape(@head) | |||
| @head = @head.include?(":") ? Addressable::URI.unescape(@head.split(":")[0]) + ':' + Base64.decode64(@head.split(":")[1]) : Base64.decode64(@head) | |||
| end | |||
| def gitea_compare(base, head) | |||
| Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, base, head, current_user.gitea_token) | |||
| Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, Addressable::URI.escape(base), Addressable::URI.escape(head), current_user.gitea_token) | |||
| end | |||
| def page_size | |||
| params.fetch(:page, 1).to_i | |||
| end | |||
| def page_limit | |||
| params.fetch(:limit, 15).to_i | |||
| end | |||
| end | |||
| @@ -18,15 +18,15 @@ module Acceleratorable | |||
| end | |||
| def accelerator_domain | |||
| Gitea.gitea_config[:accelerator]["domain"] | |||
| GiteaService.gitea_config[:accelerator]["domain"] | |||
| end | |||
| def accelerator_username | |||
| Gitea.gitea_config[:accelerator]["access_key_id"] | |||
| GiteaService.gitea_config[:accelerator]["access_key_id"] | |||
| end | |||
| def config_accelerator? | |||
| Gitea.gitea_config[:accelerator].present? | |||
| GiteaService.gitea_config[:accelerator].present? | |||
| end | |||
| def is_foreign_url?(clone_addr) | |||
| @@ -0,0 +1,20 @@ | |||
| module Api::ProjectHelper | |||
| extend ActiveSupport::Concern | |||
| def load_project | |||
| namespace = params[:owner] | |||
| repo = params[:repo] | |||
| @project, @owner = Project.find_with_namespace(namespace, repo) | |||
| if @project | |||
| logger.info "###########:project founded" | |||
| @project | |||
| else | |||
| logger.info "###########:project not found" | |||
| @project = nil | |||
| render_not_found and return | |||
| end | |||
| @project | |||
| end | |||
| end | |||
| @@ -0,0 +1,19 @@ | |||
| module Api::PullHelper | |||
| extend ActiveSupport::Concern | |||
| def load_pull_request | |||
| pull_request_id = params[:pull_id] || params[:id] | |||
| @pull_request = @project.pull_requests.where(gitea_number: pull_request_id).where.not(id: pull_request_id).take || PullRequest.find_by_id(pull_request_id) | |||
| @issue = @pull_request&.issue | |||
| if @pull_request | |||
| logger.info "###########pull_request founded" | |||
| @pull_request | |||
| else | |||
| logger.info "###########pull_request not found" | |||
| @pull_request = nil | |||
| render_not_found and return | |||
| end | |||
| @pull_request | |||
| end | |||
| end | |||
| @@ -0,0 +1,28 @@ | |||
| module Api::UserHelper | |||
| extend ActiveSupport::Concern | |||
| def load_observe_user | |||
| username = params[:owner] | |||
| @observe_user = User.find_by(login: username) | |||
| if @observe_user | |||
| logger.info "###########observe_user not founded" | |||
| @observe_user | |||
| else | |||
| logger.info "###########observe_user not found" | |||
| @observe_user = nil | |||
| render_not_found and return | |||
| end | |||
| @observe_user | |||
| end | |||
| # 是否具有查看用户或编辑用户的权限 | |||
| def check_auth_for_observe_user | |||
| return render_forbidden unless current_user.admin? || @observe_user.id == current_user.id | |||
| end | |||
| def strip(str) | |||
| str.to_s.strip.presence | |||
| end | |||
| end | |||
| @@ -160,9 +160,9 @@ module Ci::CloudAccountManageable | |||
| state = SecureRandom.hex(8) | |||
| # redirect_uri eg: | |||
| # https://localhost:3000/login/oauth/authorize?client_id=94976481-ad0e-4ed4-9247-7eef106007a2&redirect_uri=http%3A%2F%2F121.69.81.11%3A80%2Flogin&response_type=code&state=9cab990b9cfb1805 | |||
| redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login") | |||
| clientId = client_id(oauth) | |||
| grant_url = "#{Gitea.gitea_config[:domain]}/login/oauth/authorize?client_id=#{clientId}&redirect_uri=#{redirect_uri}&response_type=code&state=#{state}" | |||
| # redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login") | |||
| # clientId = client_id(oauth) | |||
| grant_url = "#{@cloud_account.drone_url}/login" | |||
| logger.info "[gitea] grant_url: #{grant_url}" | |||
| conn = Faraday.new(url: grant_url) do |req| | |||
| @@ -188,6 +188,7 @@ module Ci::CloudAccountManageable | |||
| response = conn.get | |||
| logger.info "[drone] response headers: #{response.headers}" | |||
| # true | |||
| response.headers['location'].include?('error') ? false : true | |||
| end | |||
| @@ -20,7 +20,7 @@ module ControllerRescueHandler | |||
| end | |||
| # rescue_from ActionView::MissingTemplate, with: :object_not_found | |||
| # rescue_from ActiveRecord::RecordNotFound, with: :object_not_found | |||
| rescue_from Educoder::TipException, with: :tip_show | |||
| rescue_from Gitlink::TipException, with: :tip_show | |||
| rescue_from ::ActionView::MissingTemplate, with: :missing_template | |||
| rescue_from ActiveRecord::RecordNotFound, with: :object_not_found | |||
| rescue_from ActionController::ParameterMissing, with: :render_parameter_missing | |||
| @@ -36,10 +36,10 @@ module GitCommon | |||
| begin | |||
| @commits = GitService.commits(repo_path: @repo_path) | |||
| logger.info("git first commit is #{@commits.try(:first)}") | |||
| raise Educoder::TipException.new("请先创建版本库") if @commits.nil? | |||
| raise Gitlink::TipException.new("请先创建版本库") if @commits.nil? | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| raise Educoder::TipException.new("提交记录异常") | |||
| raise Gitlink::TipException.new("提交记录异常") | |||
| end | |||
| end | |||
| @@ -34,7 +34,7 @@ module GitHelper | |||
| rescue Exception => e | |||
| Rails.logger.error(e.message) | |||
| raise Educoder::TipException.new("文档内容获取异常") | |||
| raise Gitlink::TipException.new("文档内容获取异常") | |||
| end | |||
| end | |||
| @@ -64,7 +64,7 @@ module GitHelper | |||
| # 版本库Fork功能 | |||
| def project_fork(container, original_rep_path, username) | |||
| raise Educoder::TipException.new("fork源路径为空,fork失败!") if original_rep_path.blank? | |||
| raise Gitlink::TipException.new("fork源路径为空,fork失败!") if original_rep_path.blank? | |||
| # 将要生成的仓库名字 | |||
| new_repo_name = "#{username.try(:strip)}/#{container.try(:identifier)}#{ Time.now.strftime("%Y%m%d%H%M%S")}" | |||
| # uid_logger("start fork container: repo_name is #{new_repo_name}") | |||
| @@ -11,7 +11,7 @@ module LoginHelper | |||
| def set_autologin_cookie(user) | |||
| token = Token.get_or_create_permanent_login_token(user, "autologin") | |||
| sync_user_token_to_trustie(user.login, token.value) | |||
| # sync_user_token_to_trustie(user.login, token.value) | |||
| Rails.logger.info "###### def set_autologin_cookie and get_or_create_permanent_login_token result: #{token&.value}" | |||
| cookie_options = { | |||
| @@ -109,10 +109,15 @@ module LoginHelper | |||
| def sync_pwd_to_gitea!(user, hash={}) | |||
| return true if user.is_sync_pwd? | |||
| sync_params = { email: user.mail } | |||
| sync_params = { | |||
| login_name: user.name, | |||
| source_id: 0, | |||
| email: user.mail | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params.merge(hash)) | |||
| if interactor.success? | |||
| Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea success _########" | |||
| user.update_column(:is_sync_pwd, true) | |||
| true | |||
| else | |||
| Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea fail!: #{interactor.error}" | |||
| @@ -1,21 +1,25 @@ | |||
| module RegisterHelper | |||
| extend ActiveSupport::Concern | |||
| def autologin_register(username, email, password, platform= 'forge', need_edit_info = false) | |||
| def autologin_register(username, email, password, platform = 'forge', phone = nil, nickname =nil, need_edit_info = false) | |||
| result = {message: nil, user: nil} | |||
| email = email.blank? ? "#{username}@example.org" : email | |||
| user = User.new(admin: false, login: username, mail: email, type: "User") | |||
| user.password = password | |||
| user.platform = platform | |||
| user.phone = phone if phone.present? | |||
| user.nickname = nickname if nickname.present? | |||
| if need_edit_info | |||
| user.need_edit_info | |||
| else | |||
| else | |||
| user.activate | |||
| end | |||
| return unless user.valid? | |||
| interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password}) | |||
| result ={} | |||
| if interactor.success? | |||
| gitea_user = interactor.result | |||
| result = Gitea::User::GenerateTokenService.call(username, password) | |||
| @@ -26,12 +30,12 @@ module RegisterHelper | |||
| result[:user] = {id: user.id, token: user.gitea_token} | |||
| end | |||
| else | |||
| result[:message] = interactor.error | |||
| result[:message] = interactor.result[:message] | |||
| end | |||
| result | |||
| end | |||
| def autosync_register_trustie(username, password, email) | |||
| def autosync_register_trustie(username, password, email, lastname="") | |||
| config = Rails.application.config_for(:configuration).symbolize_keys! | |||
| api_host = config[:sync_url] | |||
| @@ -42,8 +46,9 @@ module RegisterHelper | |||
| sync_json = { | |||
| "mail": email, | |||
| "password": password, | |||
| "login": username | |||
| } | |||
| "login": username, | |||
| "lastname": lastname | |||
| }.compact | |||
| uri = URI.parse(url) | |||
| if api_host | |||
| @@ -57,4 +62,32 @@ module RegisterHelper | |||
| end | |||
| end | |||
| def auto_update(user, params={}) | |||
| return if params.blank? | |||
| result = {message: nil, user: nil} | |||
| before_login = user.login | |||
| user.login = params[:username] | |||
| user.password = params[:password] | |||
| user.mail = params[:email] | |||
| if user.save! | |||
| sync_params = { | |||
| password: params[:password].to_s, | |||
| email: params[:email], | |||
| login_name: params[:username], | |||
| new_name: params[:username], | |||
| source_id: 0 | |||
| } | |||
| interactor = Gitea::User::UpdateInteractor.call(before_login, sync_params) | |||
| if interactor.success? | |||
| result[:user] = user | |||
| else | |||
| result[:message] = '用户同步Gitea失败!' | |||
| end | |||
| else | |||
| result[:message] = user.errors.full_messages.join(",") | |||
| return | |||
| end | |||
| end | |||
| end | |||
| @@ -3,8 +3,8 @@ module RenderHelper | |||
| render json: { status: 0, message: 'success' }.merge(data) | |||
| end | |||
| def render_error(message = '') | |||
| render json: { status: -1, message: message } | |||
| def render_error(message = '', status = -1) | |||
| render json: { status: status, message: message } | |||
| end | |||
| def render_not_acceptable(message = '请求已拒绝') | |||
| @@ -28,4 +28,8 @@ module RenderHelper | |||
| def render_result(status=1, message='success') | |||
| render json: { status: status, message: message } | |||
| end | |||
| def render_parameter_missing | |||
| render json: { status: -1, message: '参数缺失' } | |||
| end | |||
| end | |||
| @@ -5,7 +5,17 @@ module Repository::LanguagesPercentagable | |||
| result = Gitea::Repository::Languages::ListService.call(@owner.login, | |||
| @repository.identifier, current_user&.gitea_token) | |||
| result[:status] === :success ? hash_transform_precentagable(result[:body]) : nil | |||
| @transform_language = result[:status] === :success ? hash_transform_precentagable(result[:body]) : nil | |||
| update_project_language(@transform_language) unless @transform_language.nil? | |||
| @transform_language | |||
| end | |||
| def update_project_language(language) | |||
| return if @project.project_language.present? | |||
| db_language = ProjectLanguage.find_or_create_by!(name: language.keys.first.downcase.upcase_first) | |||
| @project.update_column(:project_language_id, db_language.id) | |||
| rescue | |||
| return | |||
| end | |||
| # hash eq:{"JavaScript": 301681522,"Ruby": 1444004,"Roff": 578781} | |||
| @@ -16,7 +16,7 @@ class ForksController < ApplicationController | |||
| if current_user&.id == @project.user_id | |||
| render_result(-1, "自己不能fork自己的项目") | |||
| elsif Project.exists?(user_id: current_user.id, identifier: @project.identifier) | |||
| render_result(-1, "fork失败,你已拥有了这个项目") | |||
| render_result(0, "fork失败,你已拥有了这个项目") | |||
| end | |||
| # return if current_user != @project.owner | |||
| # render_result(-1, "自己不能fork自己的项目") | |||
| @@ -2,12 +2,12 @@ class IssueTagsController < ApplicationController | |||
| before_action :require_login, except: [:index] | |||
| before_action :load_repository | |||
| before_action :set_user | |||
| before_action :check_issue_permission, except: :index | |||
| before_action :check_issue_tags_permission | |||
| before_action :set_issue_tag, only: [:edit, :update, :destroy] | |||
| def index | |||
| issue_tags = @project.issue_tags.reorder("#{order_name} #{order_type}") | |||
| issue_tags = @project.issue_tags.includes(:issues).reorder("issue_tags.#{order_name} #{order_type}") | |||
| @user_admin_or_member = current_user.present? && (current_user.admin || @project.member?(current_user)) | |||
| @page = params[:page] || 1 | |||
| @limit = params[:limit] || 15 | |||
| @@ -17,7 +17,7 @@ class IssueTagsController < ApplicationController | |||
| def create | |||
| title = params[:name].to_s.strip.first(10) | |||
| title = params[:name].to_s.strip.first(15) | |||
| desc = params[:description].to_s.first(30) | |||
| color = params[:color] || "#ccc" | |||
| @@ -29,7 +29,7 @@ class IssueTagsController < ApplicationController | |||
| if title.present? | |||
| if IssueTag.exists?(name: title, project_id: @project.id) | |||
| normal_status(-1, "标签已存在") | |||
| normal_status(-1, "项目标记已存在") | |||
| else | |||
| ActiveRecord::Base.transaction do | |||
| begin | |||
| @@ -37,12 +37,12 @@ class IssueTagsController < ApplicationController | |||
| if issue_tag.save | |||
| # gitea_tag = Gitea::Labels::CreateService.new(current_user, @repository.try(:identifier), tag_params).call | |||
| # if gitea_tag && issue_tag.update_attributes(gid: gitea_tag["id"], gitea_url: gitea_tag["url"]) | |||
| # normal_status(0, "标签创建成功") | |||
| normal_status(0, "项目标记创建成功!") | |||
| # else | |||
| # normal_status(-1, "标签创建失败") | |||
| # normal_status(-1, "项目标记创建失败") | |||
| # end | |||
| else | |||
| normal_status(-1, "标签创建失败") | |||
| normal_status(-1, "项目标记创建失败") | |||
| end | |||
| rescue => e | |||
| puts "create version release error: #{e.message}" | |||
| @@ -51,7 +51,7 @@ class IssueTagsController < ApplicationController | |||
| end | |||
| end | |||
| else | |||
| normal_status(-1, "标签名称不能为空") | |||
| normal_status(-1, "项目标记名称不能为空") | |||
| end | |||
| end | |||
| @@ -60,8 +60,8 @@ class IssueTagsController < ApplicationController | |||
| end | |||
| def update | |||
| title = params[:name] | |||
| desc = params[:description] | |||
| title = params[:name].to_s.strip.first(15) | |||
| desc = params[:description].to_s.first(30) | |||
| color = params[:color] || "#ccc" | |||
| tag_params = { | |||
| @@ -71,19 +71,19 @@ class IssueTagsController < ApplicationController | |||
| } | |||
| if title.present? | |||
| if IssueTag.exists?(name: title, project_id: @project.id) && (@issue_tag.name != title) | |||
| normal_status(-1, "标签已存在") | |||
| normal_status(-1, "项目标记已存在") | |||
| else | |||
| ActiveRecord::Base.transaction do | |||
| begin | |||
| if @issue_tag.update_attributes(tag_params) | |||
| # gitea_tag = Gitea::Labels::UpdateService.new(current_user, @repository.try(:identifier),@issue_tag.try(:gid), tag_params).call | |||
| # if gitea_tag | |||
| # normal_status(0, "标签更新成功") | |||
| # normal_status(0, "项目标记更新成功") | |||
| # else | |||
| # normal_status(-1, "标签更新失败") | |||
| # normal_status(-1, "项目标记更新失败") | |||
| # end | |||
| else | |||
| normal_status(-1, "标签更新失败") | |||
| normal_status(-1, "项目标记更新失败") | |||
| end | |||
| rescue => e | |||
| puts "create version release error: #{e.message}" | |||
| @@ -92,7 +92,7 @@ class IssueTagsController < ApplicationController | |||
| end | |||
| end | |||
| else | |||
| normal_status(-1, "标签名称不能为空") | |||
| normal_status(-1, "项目标记名称不能为空") | |||
| end | |||
| end | |||
| @@ -102,12 +102,12 @@ class IssueTagsController < ApplicationController | |||
| if @issue_tag.destroy | |||
| # issue_tag = Gitea::Labels::DeleteService.new(@user, @repository.try(:identifier), @issue_tag.try(:gid)).call | |||
| # if issue_tag | |||
| # normal_status(0, "标签删除成功") | |||
| # normal_status(0, "项目标记删除成功") | |||
| # else | |||
| # normal_status(-1, "标签删除失败") | |||
| # normal_status(-1, "项目标记删除失败") | |||
| # end | |||
| else | |||
| normal_status(-1, "标签删除失败") | |||
| normal_status(-1, "项目标记删除失败") | |||
| end | |||
| rescue => e | |||
| puts "create version release error: #{e.message}" | |||
| @@ -122,16 +122,16 @@ class IssueTagsController < ApplicationController | |||
| @user = @project.owner | |||
| end | |||
| def check_issue_permission | |||
| unless @project.member?(current_user) || current_user.admin? | |||
| normal_status(-1, "您没有权限") | |||
| def check_issue_tags_permission | |||
| unless @project.manager?(current_user) || current_user.admin? | |||
| return render_forbidden('你不是管理员,没有权限操作') | |||
| end | |||
| end | |||
| def set_issue_tag | |||
| @issue_tag = IssueTag.find_by_id(params[:id]) | |||
| unless @issue_tag.present? | |||
| normal_status(-1, "标签不存在") | |||
| normal_status(-1, "项目标记不存在") | |||
| end | |||
| end | |||
| @@ -9,7 +9,7 @@ class IssuesController < ApplicationController | |||
| before_action :check_project_public, only: [:index ,:show, :copy, :index_chosen, :close_issue] | |||
| before_action :set_issue, only: [:edit, :update, :destroy, :show, :copy, :close_issue, :lock_issue] | |||
| before_action :check_token_enough, only: [:create, :update] | |||
| before_action :check_token_enough, :find_atme_receivers, only: [:create, :update] | |||
| skip_after_action :user_trace_log, only: [:update] | |||
| @@ -17,7 +17,8 @@ class IssuesController < ApplicationController | |||
| include TagChosenHelper | |||
| def index | |||
| @user_admin_or_member = current_user.present? && current_user.logged? && (current_user.admin || @project.member?(current_user)) | |||
| @user_operate_issue = current_user.present? && current_user.logged? && (current_user.admin || @project.member?(current_user)) | |||
| @user_admin_or_member = current_user.present? && current_user.logged? && (current_user.admin || @project.member?(current_user) || @project.is_public?) | |||
| issues = @project.issues.issue_issue.issue_index_includes | |||
| issues = issues.where(is_private: false) unless @user_admin_or_member | |||
| @@ -25,13 +26,13 @@ class IssuesController < ApplicationController | |||
| @filter_issues = @all_issues | |||
| @filter_issues = @filter_issues.where.not(status_id: IssueStatus::CLOSED) if params[:status_type].to_i == IssueStatus::ADD | |||
| @filter_issues = @filter_issues.where(status_id: IssueStatus::CLOSED) if params[:status_type].to_i == IssueStatus::SOLVING | |||
| @filter_issues = @filter_issues.where("subject LIKE ? OR description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? | |||
| @filter_issues = @filter_issues.where("issues.subject LIKE ? OR issues.description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? | |||
| @open_issues = @all_issues.where.not(status_id: IssueStatus::CLOSED) | |||
| @close_issues = @all_issues.where(status_id: IssueStatus::CLOSED) | |||
| @assign_to_me = @filter_issues.where(assigned_to_id: current_user&.id) | |||
| @my_published = @filter_issues.where(author_id: current_user&.id) | |||
| scopes = Issues::ListQueryService.call(issues,params.delete_if{|k,v| v.blank?}, "Issue") | |||
| @issues_size = scopes.size | |||
| @assign_to_me = scopes.where(assigned_to_id: current_user&.id) | |||
| @my_published = scopes.where(author_id: current_user&.id) | |||
| @issues = paginate(scopes) | |||
| respond_to do |format| | |||
| @@ -110,11 +111,11 @@ class IssuesController < ApplicationController | |||
| def create | |||
| issue_params = issue_send_params(params) | |||
| Issues::CreateForm.new({subject:issue_params[:subject]}).validate! | |||
| Issues::CreateForm.new({subject: issue_params[:subject], description: issue_params[:description].blank? ? issue_params[:description] : issue_params[:description].b}).validate! | |||
| @issue = Issue.new(issue_params) | |||
| if @issue.save! | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) | |||
| SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @issue&.id) | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @issue&.id) if Site.has_notice_menu? | |||
| if params[:attachment_ids].present? | |||
| params[:attachment_ids].each do |id| | |||
| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) | |||
| @@ -127,9 +128,15 @@ class IssuesController < ApplicationController | |||
| end | |||
| end | |||
| if params[:issue_tag_ids].present? | |||
| params[:issue_tag_ids].each do |tag| | |||
| IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) | |||
| end | |||
| if params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size > 1 | |||
| return normal_status(-1, "最多只能创建一个标记。") | |||
| elsif params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size == 1 | |||
| params[:issue_tag_ids].each do |tag| | |||
| IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) | |||
| end | |||
| else | |||
| return normal_status(-1, "请输入正确的标记。") | |||
| end | |||
| end | |||
| if params[:assigned_to_id].present? | |||
| Tiding.create!(user_id: params[:assigned_to_id], trigger_user_id: current_user.id, | |||
| @@ -144,7 +151,13 @@ class IssuesController < ApplicationController | |||
| end | |||
| @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") | |||
| render json: {status: 0, message: "创建成", id: @issue.id} | |||
| @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE) if params[:status_id].to_i == 5 | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, @issue) if @atme_receivers.size > 0 | |||
| render json: {status: 0, message: "创建成功", id: @issue.id} | |||
| else | |||
| normal_status(-1, "创建失败") | |||
| end | |||
| @@ -162,11 +175,19 @@ class IssuesController < ApplicationController | |||
| def update | |||
| last_token = @issue.token | |||
| last_status_id = @issue.status_id | |||
| if params[:issue_tag_ids].present? && !@issue&.issue_tags_relates.where(issue_tag_id: params[:issue_tag_ids]).exists? | |||
| @issue&.issue_tags_relates&.destroy_all | |||
| params[:issue_tag_ids].each do |tag| | |||
| IssueTagsRelate.create(issue_id: @issue.id, issue_tag_id: tag) | |||
| end | |||
| @issue&.issue_tags_relates&.destroy_all if params[:issue_tag_ids].blank? | |||
| if params[:issue_tag_ids].present? | |||
| if params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size > 1 | |||
| return normal_status(-1, "最多只能创建一个标记。") | |||
| elsif params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size == 1 | |||
| @issue&.issue_tags_relates&.destroy_all | |||
| params[:issue_tag_ids].each do |tag| | |||
| next if tag == [""] | |||
| IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) | |||
| end | |||
| else | |||
| return normal_status(-1, "请输入正确的标记。") | |||
| end | |||
| end | |||
| issue_files = params[:attachment_ids] | |||
| @@ -205,14 +226,14 @@ class IssuesController < ApplicationController | |||
| normal_status(-1, "不允许修改为关闭状态") | |||
| else | |||
| issue_params = issue_send_params(params).except(:issue_classify, :author_id, :project_id) | |||
| Issues::UpdateForm.new({subject:issue_params[:subject]}).validate! | |||
| Issues::UpdateForm.new({subject: issue_params[:subject], description: issue_params[:description].blank? ? issue_params[:description] : issue_params[:description].b}).validate! | |||
| if @issue.update_attributes(issue_params) | |||
| user_trace_update_log(issue_hash) | |||
| if @issue&.pull_request.present? | |||
| SendTemplateMessageJob.perform_later('PullRequestChanged', current_user.id, @issue&.pull_request&.id, @issue.previous_changes.slice(:assigned_to_id, :priority_id, :fixed_version_id, :issue_tags_value)) | |||
| SendTemplateMessageJob.perform_later('PullRequestAssigned', current_user.id, @issue&.pull_request&.id ) if @issue.previous_changes[:assigned_to_id].present? | |||
| SendTemplateMessageJob.perform_later('PullRequestChanged', current_user.id, @issue&.pull_request&.id, @issue.previous_changes.slice(:assigned_to_id, :priority_id, :fixed_version_id, :issue_tags_value)) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('PullRequestAssigned', current_user.id, @issue&.pull_request&.id ) if @issue.previous_changes[:assigned_to_id].present? && Site.has_notice_menu? | |||
| else | |||
| previous_changes = @issue.previous_changes.slice(:status_id, :assigned_to_id, :tracker_id, :priority_id, :fixed_version_id, :done_ratio, :issue_tags_value, :branch_name) | |||
| if @issue.previous_changes[:start_date].present? | |||
| @@ -221,12 +242,18 @@ class IssuesController < ApplicationController | |||
| if @issue.previous_changes[:due_date].present? | |||
| previous_changes.merge!(due_date: [@issue.previous_changes[:due_date][0].to_s, @issue.previous_changes[:due_date][1].to_s]) | |||
| end | |||
| SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_changes) | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) if @issue.previous_changes[:assigned_to_id].present? | |||
| if @issue.previous_changes[:status_id].present? && @issue.previous_changes[:status_id][1] == 5 | |||
| @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE) | |||
| end | |||
| if @issue.previous_changes[:status_id].present? && @issue.previous_changes[:status_id][0] == 5 | |||
| @issue.project_trends.where(action_type: ProjectTrend::CLOSE).destroy_all | |||
| end | |||
| SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_changes) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) if @issue.previous_changes[:assigned_to_id].present? && Site.has_notice_menu? | |||
| end | |||
| if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时 | |||
| @issue.issue_times.update_all(end_time: Time.now) | |||
| @issue.update_closed_issues_count_in_project! | |||
| # @issue.update_closed_issues_count_in_project! | |||
| if @issue.issue_type.to_s == "2" && last_status_id != 5 | |||
| if @issue.assigned_to_id.present? && last_status_id == 3 #只有当用户完成100%时,才给token | |||
| post_to_chain("add", @issue.token, @issue.get_assign_user.try(:login)) | |||
| @@ -243,6 +270,10 @@ class IssuesController < ApplicationController | |||
| post_to_chain(change_type, change_token.abs, current_user.try(:login)) | |||
| end | |||
| @issue.create_journal_detail(change_files, issue_files, issue_file_ids, current_user&.id) if @issue.previous_changes.present? | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, @issue) if @atme_receivers.size > 0 | |||
| normal_status(0, "更新成功") | |||
| else | |||
| normal_status(-1, "更新失败") | |||
| @@ -275,7 +306,7 @@ class IssuesController < ApplicationController | |||
| status_id = @issue.status_id | |||
| token = @issue.token | |||
| login = @issue.user.try(:login) | |||
| SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigned_to_id, @issue.author_id) | |||
| SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigned_to_id, @issue.author_id) if Site.has_notice_menu? | |||
| if @issue.destroy | |||
| if issue_type == "2" && status_id != 5 | |||
| post_to_chain("add", token, login) | |||
| @@ -298,7 +329,7 @@ class IssuesController < ApplicationController | |||
| issues = Issue.where(id: issue_ids, issue_type: "1") | |||
| if issues.present? | |||
| issues.find_each do |i| | |||
| SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, i&.subject, i.assigned_to_id, i.author_id) | |||
| SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, i&.subject, i.assigned_to_id, i.author_id) if Site.has_notice_menu? | |||
| end | |||
| if issues.destroy_all | |||
| normal_status(0, "删除成功") | |||
| @@ -343,8 +374,14 @@ class IssuesController < ApplicationController | |||
| if i.previous_changes[:due_date].present? | |||
| previous_changes.merge!(due_date: [i.previous_changes[:due_date][0].to_s, i.previous_changes[:due_date][1].to_s]) | |||
| end | |||
| SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, i&.id, previous_changes) | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, i&.id) if i.previous_changes[:assigned_to_id].present? | |||
| if i.previous_changes[:status_id].present? && i.previous_changes[:status_id][1] == 5 | |||
| i.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE) | |||
| end | |||
| if i.previous_changes[:status_id].present? && i.previous_changes[:status_id][0] == 5 | |||
| i.project_trends.where(action_type: ProjectTrend::CLOSE).destroy_all | |||
| end | |||
| SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, i&.id, previous_changes) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, i&.id) if i.previous_changes[:assigned_to_id].present? && Site.has_notice_menu? | |||
| end | |||
| normal_status(0, "批量更新成功") | |||
| else | |||
| @@ -359,8 +396,8 @@ class IssuesController < ApplicationController | |||
| @new_issue = @issue.dup | |||
| @new_issue.author_id = current_user.id | |||
| if @new_issue.save | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @new_issue&.id) | |||
| SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @new_issue&.id) | |||
| SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @new_issue&.id) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @new_issue&.id) if Site.has_notice_menu? | |||
| issue_tags = @issue.issue_tags.pluck(:id) | |||
| if issue_tags.present? | |||
| issue_tags.each do |tag| | |||
| @@ -438,27 +475,28 @@ class IssuesController < ApplicationController | |||
| def check_project_public | |||
| unless @project.is_public || @project.member?(current_user) || current_user.admin? || (@project.user_id == current_user.id) | |||
| normal_status(-1, "您没有权限") | |||
| return render_forbidden | |||
| end | |||
| end | |||
| def set_issue | |||
| @issue = Issue.find_by_id(params[:id]) | |||
| if @issue.blank? | |||
| normal_status(-1, "标签不存在") | |||
| elsif @issue.is_lock &&!(@project.member?(current_user) || current_user.admin?) | |||
| normal_status(-1, "您没有权限") | |||
| return render_not_found | |||
| elsif !(@project.is_public || (current_user.present? && (@project.member?(current_user) || current_user&.admin? || (@project.user_id == current_user&.id)))) | |||
| return render_forbidden | |||
| end | |||
| end | |||
| def check_issue_permission | |||
| unless @project.is_public || (current_user.present? && (@project.member?(current_user) || current_user&.admin? || (@project.user_id == current_user&.id))) | |||
| normal_status(-1, "您没有权限") | |||
| return render_forbidden | |||
| end | |||
| end | |||
| def operate_issue_permission | |||
| return render_forbidden("您没有权限进行此操作.") unless current_user.admin? || @project.member?(current_user) | |||
| @issue = Issue.find_by_id(params[:id]) unless @issue.present? | |||
| return render_forbidden("您没有权限进行此操作.") unless current_user.present? && current_user.logged? && (current_user.admin? || @project.member?(current_user) || (@project.is_public && @issue.nil?) || (@project.is_public && @issue.present? && @issue.author_id == current_user.id)) | |||
| end | |||
| def export_issues(issues) | |||
| @@ -1,6 +1,6 @@ | |||
| class JournalsController < ApplicationController | |||
| before_action :require_login, except: [:index, :get_children_journals] | |||
| before_action :require_profile_completed, only: [:create] | |||
| before_action :require_profile_completed, :find_atme_receivers, only: [:create] | |||
| before_action :set_issue | |||
| before_action :check_issue_permission | |||
| before_action :set_journal, only: [:destroy, :edit, :update] | |||
| @@ -23,34 +23,41 @@ class JournalsController < ApplicationController | |||
| if notes.blank? | |||
| normal_status(-1, "评论内容不能为空") | |||
| else | |||
| journal_params = { | |||
| journalized_id: @issue.id , | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: notes.to_s.strip, | |||
| parent_id: params[:parent_id] | |||
| } | |||
| journal = Journal.new journal_params | |||
| if journal.save | |||
| if params[:attachment_ids].present? | |||
| params[:attachment_ids].each do |id| | |||
| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) | |||
| unless attachment.blank? | |||
| attachment.container = journal | |||
| attachment.author_id = current_user.id | |||
| attachment.description = "" | |||
| attachment.save | |||
| ActiveRecord::Base.transaction do | |||
| Journals::CreateForm.new({notes: notes.to_s.strip.blank? ? notes.to_s.strip : notes.to_s.strip.b}).validate! | |||
| journal_params = { | |||
| journalized_id: @issue.id , | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: notes.to_s.strip, | |||
| parent_id: params[:parent_id] | |||
| } | |||
| journal = Journal.new journal_params | |||
| if journal.save | |||
| if params[:attachment_ids].present? | |||
| params[:attachment_ids].each do |id| | |||
| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) | |||
| unless attachment.blank? | |||
| attachment.container = journal | |||
| attachment.author_id = current_user.id | |||
| attachment.description = "" | |||
| attachment.save | |||
| end | |||
| end | |||
| end | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, journal) if @atme_receivers.size > 0 | |||
| # @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal") | |||
| render :json => { status: 0, message: "评论成功", id: journal.id} | |||
| # normal_status(0, "评论成功") | |||
| else | |||
| normal_status(-1, "评论失败") | |||
| end | |||
| # @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal") | |||
| render :json => { status: 0, message: "评论成功", id: journal.id} | |||
| # normal_status(0, "评论成功") | |||
| else | |||
| normal_status(-1, "评论失败") | |||
| end | |||
| end | |||
| rescue Exception => exception | |||
| puts exception.message | |||
| normal_status(-1, exception.message) | |||
| end | |||
| def destroy | |||
| @@ -70,6 +77,7 @@ class JournalsController < ApplicationController | |||
| content = params[:content] | |||
| if content.present? | |||
| old_value = old_value_to_hash(@journal, params) | |||
| Journals::UpdateForm.new({notes: notes.to_s.strip.blank? ? notes.to_s.strip : notes.to_s.strip.b}).validate! | |||
| if @journal.update_attribute(:notes, content) | |||
| user_trace_update_log(old_value) | |||
| normal_status(0, "更新成功") | |||
| @@ -79,7 +87,9 @@ class JournalsController < ApplicationController | |||
| else | |||
| normal_status(-1, "评论的内容不能为空") | |||
| end | |||
| rescue Exception => exception | |||
| puts exception.message | |||
| normal_status(-1, exception.message) | |||
| end | |||
| def get_children_journals | |||
| @@ -23,9 +23,9 @@ class MainController < ApplicationController | |||
| # TODO: 这块之后需要整合,者架构重新变化,统一跳转到index后再路由分发 | |||
| if params[:path] && params[:path]&.include?("h5educoderbuild") && params[:path].split("/").first == "h5educoderbuild" | |||
| render file: 'public/h5educoderbuild/index.html', :layout => false | |||
| render file: 'public/h5educoderbuild/index.html', :layout => false, :content_type=> 'text/html' | |||
| else | |||
| render file: 'public/react/build/index.html', :layout => false | |||
| render file: 'public/react/build/index.html', :layout => false, :content_type=> 'text/html' | |||
| end | |||
| end | |||
| @@ -0,0 +1,56 @@ | |||
| class MarkFilesController < ApplicationController | |||
| before_action :require_login | |||
| before_action :load_project | |||
| before_action :load_pull_request | |||
| def index | |||
| @files_result = Gitea::PullRequest::FilesService.call(@owner.login, @project.identifier, @pull_request.gitea_number, current_user&.gitea_token, { "only-file-name": true }) | |||
| @mark_files = MarkFile.where(pull_request_id: @pull_request.id) | |||
| end | |||
| def create | |||
| # unless @pull_request.mark_files.present? | |||
| # MarkFile.bulk_insert(*%i[pull_request_id, file_path_sha file_path created_at updated_at]) do |worker| | |||
| # @files_result['Files'].each do |file| | |||
| # worker.add(pull_request_id: @pull_request.id, file_path_sha: SecureRandom.uuid.gsub("-", ""), file_path: file['Name']) | |||
| # end | |||
| # end | |||
| # end | |||
| end | |||
| def mark_file_as_unread | |||
| tip_exception "参数错误" if params[:file_path_sha].blank? | |||
| file_path = Base64.strict_decode64(params[:file_path_sha].to_s) | |||
| mark_file = @pull_request.mark_files.find_or_initialize_by(file_path_sha: params[:file_path_sha]) | |||
| mark_file.file_path = file_path | |||
| mark_file.user_id = current_user.id | |||
| mark_file.mark_as_read = false | |||
| mark_file.save | |||
| render_ok | |||
| rescue Exception => e | |||
| tip_exception "参数解析错误" | |||
| end | |||
| def mark_file_as_read | |||
| tip_exception "参数错误" if params[:file_path_sha].blank? | |||
| file_path = Base64.strict_decode64(params[:file_path_sha].to_s) | |||
| mark_file = @pull_request.mark_files.find_or_initialize_by(file_path_sha: params[:file_path_sha]) | |||
| mark_file.file_path = file_path | |||
| mark_file.user_id = current_user.id | |||
| mark_file.mark_as_read = true | |||
| mark_file.save | |||
| render_ok | |||
| rescue Exception => e | |||
| tip_exception "参数解析错误" | |||
| end | |||
| private | |||
| def review_params | |||
| params.require(:review).permit(:content, :commit_id, :status) | |||
| end | |||
| def load_pull_request | |||
| @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || PullRequest.find_by_id(params[:id]) | |||
| end | |||
| end | |||
| @@ -3,15 +3,15 @@ class MembersController < ApplicationController | |||
| before_action :load_project | |||
| before_action :find_user_with_id, only: %i[create remove change_role] | |||
| before_action :check_user_profile_completed, only: [:create] | |||
| before_action :operate!, except: %i[index] | |||
| before_action :operate! | |||
| before_action :check_member_exists!, only: %i[create] | |||
| before_action :check_member_not_exists!, only: %i[remove change_role] | |||
| skip_after_action :user_trace_log, only: [:change_role] | |||
| def create | |||
| interactor = Projects::AddMemberInteractor.call(@project.owner, @project, @user) | |||
| SendTemplateMessageJob.perform_later('ProjectJoined', current_user.id, @user.id, @project.id) | |||
| SendTemplateMessageJob.perform_later('ProjectMemberJoined', current_user.id, @user.id, @project.id) | |||
| SendTemplateMessageJob.perform_later('ProjectJoined', current_user.id, @user.id, @project.id) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('ProjectMemberJoined', current_user.id, @user.id, @project.id) if Site.has_notice_menu? | |||
| render_response(interactor) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| @@ -27,12 +27,15 @@ class MembersController < ApplicationController | |||
| @total_count = scope.size | |||
| @members = paginate(scope) | |||
| if @project.owner.is_a?(Organization) && (params[:page].to_i == 1 || params[:page].blank?) && !@project.members.exists?(user_id: current_user.id) | |||
| @current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take | |||
| end | |||
| end | |||
| def remove | |||
| interactor = Projects::DeleteMemberInteractor.call(@project.owner, @project, @user) | |||
| SendTemplateMessageJob.perform_later('ProjectLeft', current_user.id, @user.id, @project.id) | |||
| SendTemplateMessageJob.perform_later('ProjectMemberLeft', current_user.id, @user.id, @project.id) | |||
| SendTemplateMessageJob.perform_later('ProjectLeft', current_user.id, @user.id, @project.id) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('ProjectMemberLeft', current_user.id, @user.id, @project.id) if Site.has_notice_menu? | |||
| render_response(interactor) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| @@ -42,11 +45,8 @@ class MembersController < ApplicationController | |||
| def change_role | |||
| old_value = @project.members.where(user_id: params[:user_id])[0].roles.last.name | |||
| interactor = Projects::ChangeMemberRoleInteractor.call(@project.owner, @project, @user, params[:role]) | |||
| user_trace_update_log(old_value) | |||
| SendTemplateMessageJob.perform_later('ProjectRole', current_user.id, @user.id, @project.id, message_role_name) | |||
| SendTemplateMessageJob.perform_later('ProjectRole', current_user.id, @user.id, @project.id, message_role_name) if Site.has_notice_menu? | |||
| render_response(interactor) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| @@ -67,11 +67,14 @@ class MembersController < ApplicationController | |||
| end | |||
| def check_member_exists! | |||
| return render_error("user_id为#{params[:user_id]}的用户已经是项目成员") if member_exists? | |||
| @current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take | |||
| return render_error("#{@user&.nickname}已经是项目成员") if member_exists? || (params[:user_id].to_i == current_user.id && @current_user_header_team.present?) | |||
| end | |||
| def check_member_not_exists! | |||
| return render_error("user_id为#{params[:user_id]}的用户还不是项目成员") unless member_exists? | |||
| @current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take | |||
| return render_error("用户为组织成员,请到组织下操作!") if (params[:user_id].to_i == current_user.id && @current_user_header_team.present?) && !member_exists? | |||
| return render_error("#{@user&.nickname}还不是项目成员") unless member_exists? | |||
| end | |||
| def check_user_profile_completed | |||
| @@ -0,0 +1,33 @@ | |||
| class NoticesController < ApplicationController | |||
| def create | |||
| return tip_exception("参数有误") if params["source"].blank? | |||
| user_id = params[:user_id] | |||
| if params["source"] == "CompetitionBegin" | |||
| competition_id = params[:competition_id] | |||
| SendTemplateMessageJob.perform_later('CompetitionBegin', user_id, competition_id) | |||
| elsif params["source"] == "CompetitionResult" | |||
| competition_id = params[:competition_id] | |||
| SendTemplateMessageJob.perform_later('CompetitionResult', user_id, competition_id) | |||
| elsif params["source"] == "CompetitionReview" | |||
| competition_id = params[:competition_id] | |||
| SendTemplateMessageJob.perform_later('CompetitionReview', user_id, competition_id) | |||
| elsif params["source"] == "CustomTip" | |||
| users_id = params[:users_id] | |||
| props = params[:props].to_unsafe_hash | |||
| return tip_exception("参数有误") unless props.is_a?(Hash) && users_id.is_a?(Array) | |||
| template_id = params[:template_id] | |||
| SendTemplateMessageJob.perform_later('CustomTip', users_id, template_id, props) | |||
| else | |||
| tip_exception("#{params["source"]}未配置") | |||
| end | |||
| render_ok | |||
| end | |||
| private | |||
| def params_props | |||
| params.require(:notice).permit(:props) | |||
| end | |||
| end | |||
| @@ -0,0 +1,17 @@ | |||
| class NpsController < ApplicationController | |||
| before_action :require_login | |||
| # close,关闭 | |||
| # createIssue,创建issue | |||
| # createPullRequest,创建PR | |||
| # auditPullRequest,审核PR | |||
| # indexProject,项目主页 | |||
| # createProject,创建项目 | |||
| # createOrganization,创建组织 | |||
| def create | |||
| tip_exception "缺少参数" if params[:action_id].blank? || params[:action_type].blank? | |||
| UserNp.create(:action_id => params[:action_id].to_i, :action_type => params[:action_type], :user_id => User.current.id, :score => params[:score].to_f, memo: params[:memo]) | |||
| render_ok | |||
| end | |||
| end | |||
| @@ -3,6 +3,7 @@ class Oauth::BaseController < ActionController::Base | |||
| include LoginHelper | |||
| include ControllerRescueHandler | |||
| include LoggerHelper | |||
| include RegisterHelper | |||
| # include LaboratoryHelper | |||
| skip_before_action :verify_authenticity_token | |||
| @@ -13,13 +14,13 @@ class Oauth::BaseController < ActionController::Base | |||
| private | |||
| def tip_exception(status = -1, message) | |||
| raise Educoder::TipException.new(status, message) | |||
| raise Gitlink::TipException.new(status, message) | |||
| end | |||
| def tip_show_exception(status = -2, message) | |||
| raise Educoder::TipException.new(status, message) | |||
| raise Gitlink::TipException.new(status, message) | |||
| end | |||
| def tip_show(exception) | |||
| uid_logger("Tip show status is #{exception.status}, message is #{exception.message}") | |||
| render json: exception.tip_json | |||
| @@ -35,7 +36,7 @@ class Oauth::BaseController < ActionController::Base | |||
| end | |||
| def auth_hash | |||
| Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}") | |||
| # Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}") | |||
| request.env['omniauth.auth'] | |||
| end | |||
| @@ -0,0 +1,93 @@ | |||
| class Oauth::CallbacksController < Oauth::BaseController | |||
| def create | |||
| process_callback_new | |||
| rescue Exception => e | |||
| Rails.logger.info "授权失败:#{e}" | |||
| tip_exception("授权失败") | |||
| end | |||
| private | |||
| def config_providers | |||
| config = Rails.application.config_for(:configuration) | |||
| config.dig("oauth").keys | |||
| end | |||
| # QQ: {"ret":0,"msg":"","is_lost":0,"nickname":"颜值不算太高","gender":"男","gender_type":1,"province":"","city":"","year":"2013","constellation":"","figureurl":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/30","figureurl_1":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/50","figureurl_2":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/100","figureurl_qq_1":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=40\u0026t=1568887757","figureurl_qq_2":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=100\u0026t=1568887757","figureurl_qq":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=140\u0026t=1568887757","figureurl_type":"1","is_yellow_vip":"0","vip":"0","yellow_vip_level":"0","level":"0","is_yellow_year_vip":"0"} | |||
| def process_callback | |||
| Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}") | |||
| if auth_hash.blank? | |||
| redirect_to("/login") && return | |||
| end | |||
| new_user = false | |||
| platform = auth_hash[:provider] | |||
| uid = auth_hash[:uid] | |||
| mail = auth_hash.info.email || nil | |||
| nickname = ["gitee", "github"].include?(platform) ? auth_hash.info.name : auth_hash.info.nickname | |||
| open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.find_by(uid: uid) | |||
| if open_user.present? && open_user.user.present? | |||
| successful_authentication(open_user.user) | |||
| else | |||
| if current_user.blank? || !current_user.logged? | |||
| has_user = User.find_by(mail: mail) | |||
| if has_user.present? | |||
| "OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user_id: has_user.id, uid: uid, extra: auth_hash.extra) | |||
| successful_authentication(has_user) | |||
| else | |||
| new_user = true | |||
| login = build_login_name(platform, auth_hash.info.nickname) | |||
| mail = "#{login}@example.org" if mail.blank? | |||
| code = %W(0 1 2 3 4 5 6 7 8 9) | |||
| rand_password = code.sample(10).join | |||
| reg_result = autologin_register(login, mail, rand_password, platform, nil, nickname) | |||
| Rails.logger.info("[OAuth2] omniauth.auth [reg_result] #{reg_result} ") | |||
| if reg_result[:message].blank? | |||
| open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user_id: reg_result[:user][:id], uid: uid, extra: auth_hash.extra) | |||
| successful_authentication(open_user.user) | |||
| else | |||
| tip_exception(reg_result.present? ? reg_result[:message] : "授权失败") | |||
| end | |||
| end | |||
| else | |||
| "OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user: current_user, uid: login, extra: auth_hash.extra) | |||
| end | |||
| end | |||
| redirect_to root_path(new_user: new_user) | |||
| end | |||
| def process_callback_new | |||
| Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}") | |||
| if auth_hash.blank? | |||
| redirect_to("/login") && return | |||
| end | |||
| platform = auth_hash[:provider] | |||
| uid = auth_hash[:uid] | |||
| uid = auth_hash.info.unionid if platform == "wechat" | |||
| open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.find_by(uid: uid) | |||
| if open_user.present? && open_user.user.present? | |||
| successful_authentication(open_user.user) | |||
| redirect_to root_path(new_user: false) | |||
| return | |||
| else | |||
| if current_user.blank? || !current_user.logged? | |||
| session[:unionid] = uid | |||
| else | |||
| "OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user: current_user, uid: uid) | |||
| end | |||
| end | |||
| Rails.logger.info("[OAuth2] session[:unionid] -> #{session[:unionid]}") | |||
| redirect_to "/bindlogin/#{platform}" | |||
| end | |||
| # gitee,github nickname=login,如果系统未占用保留原用户名 | |||
| def build_login_name(provider, nickname) | |||
| if ["gitee", "github"].include?(provider) && User.find_by(login: nickname).blank? | |||
| nickname | |||
| else | |||
| User.generate_user_login('p') | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,39 @@ | |||
| class Oauth2Controller < ActionController::Base | |||
| layout 'doorkeeper/application' | |||
| include LoginHelper | |||
| def show | |||
| client_id = params[:call_url].split("client_id=")[1].split("&redirect_uri")[0] | |||
| @call_url = request.fullpath.split('call_url=').last | |||
| @app = Doorkeeper::Application.find_by(uid: client_id) | |||
| end | |||
| def create | |||
| if params[:login].blank? | |||
| @error = {msg: '邮箱地址或用户名不能为空', id: 'login'} | |||
| elsif params[:password].blank? | |||
| @error = {msg: '请输入密码', id: 'password'} | |||
| else | |||
| @user = User.try_to_login(params[:login], params[:password]) | |||
| return @error = {msg: '账号或密码错误', id: 'login'} if @user.blank? | |||
| return @error = {msg: '违反平台使用规范,账号已被锁定', id: 'login'} if @user.locked? | |||
| login_control = LimitForbidControl::UserLogin.new(@user) | |||
| return @error = {msg: "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码", id: 'account'} if login_control.forbid? | |||
| password_ok = @user.check_password?(params[:password].to_s) | |||
| unless password_ok | |||
| if login_control.remain_times-1 == 0 | |||
| @error = {msg: "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码", id: 'account'} | |||
| else | |||
| @error = {msg: "你已经输错密码#{login_control.error_times+1}次,还剩余#{login_control.remain_times-1}次机会", id: 'account'} | |||
| end | |||
| login_control.increment! | |||
| return | |||
| end | |||
| login_control.clear | |||
| redirect_to params[:call_url] + "&auth=" + @user.login | |||
| end | |||
| end | |||
| end | |||
| @@ -61,6 +61,7 @@ class OauthController < ApplicationController | |||
| login = params[:login] | |||
| email = params[:mail] | |||
| password = params[:password] | |||
| lastname = params[:lastname] | |||
| callback_url = params[:callback_url] | |||
| platform = params[:plathform] || 'educoder' | |||
| @@ -72,8 +73,11 @@ class OauthController < ApplicationController | |||
| if result[:message].blank? | |||
| logger.info "[Oauth educoer] ====auto_register success" | |||
| user = User.find result[:user][:id] | |||
| successful_authentication(user) | |||
| user.update_column(:lastname, params[:lastname]) | |||
| autosync_register_trustie(login, password, email, lastname) | |||
| OpenUsers::Educoder.create!(user: user, uid: user.login) | |||
| successful_authentication(user) | |||
| render json: { callback_url: callback_url } | |||
| # redirect_to callback_url | |||
| @@ -0,0 +1,130 @@ | |||
| class ObRepositorySyncsController < ApplicationController | |||
| before_action :require_login | |||
| before_action :load_project | |||
| before_action :load_ob_repository_sync, except: [:create] | |||
| before_action :authenticate_user! | |||
| def index | |||
| render_ok(data: @ob_repository_sync) | |||
| end | |||
| def create | |||
| tip_exception "参数错误" if params[:github_address].blank? && params[:gitee_address].blank? | |||
| project_name ="#{@project.owner.name}:#{@project.identifier}" | |||
| service = ObRepositorySync::ApiService.new(project_name) | |||
| domain = GiteaService.gitea_config[:domain] | |||
| project_params = params.merge({ "gitlink_address": "#{domain}/#{@project.owner&.login}/#{@project.identifier}.git" }) | |||
| res = service.create_projects(project_params) | |||
| tip_exception "保存失败: #{res["msg"]}" if res["code"].to_s != "200" | |||
| sync_id = res["data"]["id"] | |||
| ob_repository_sync = ObRepositorySync.find_or_initialize_by(project_id: @project.id) | |||
| ob_repository_sync.project_id = @project.id | |||
| ob_repository_sync.user_id = current_user.id | |||
| ob_repository_sync.name = project_name | |||
| ob_repository_sync.github_address = "#{params[:github_address]}" | |||
| ob_repository_sync.gitee_address = "#{params[:gitee_address]}" | |||
| ob_repository_sync.github_token = "#{params[:github_token]}" | |||
| ob_repository_sync.gitee_token = "#{params[:gitee_token]}" | |||
| ob_repository_sync.sync_id = sync_id | |||
| ob_repository_sync.save! | |||
| render_ok | |||
| end | |||
| def delete | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.delete_project @ob_repository_sync.sync_id | |||
| tip_exception "删除失败: #{res["msg"]}" if res["code"].to_s != "200" | |||
| if res["code"].to_s == "200" | |||
| @ob_repository_sync.destroy! | |||
| end | |||
| render_ok | |||
| end | |||
| def jobs | |||
| tip_exception "该项目未创建同步任务" if @ob_repository_sync.blank? | |||
| page = params[:page] || 1 | |||
| limit = params[:limit] || 10 | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| source = "" | |||
| if params[:type] && params[:type].to_s.downcase == "github" | |||
| source = "github_branch" | |||
| elsif params[:type] && params[:type].to_s.downcase == "gitee" | |||
| source = "gitee_branch" | |||
| end | |||
| res = service.get_projects_jobs(source, page, limit) | |||
| data = res["data"]["list"] | |||
| render_ok(count: res["data"]["total"], data: data) | |||
| end | |||
| def create_jobs | |||
| tip_exception "必须配置一个分支" if params[:github_branch].blank? && params[:gitee_branch].blank? && params[:gitlink_branch].blank? | |||
| ob_jobs = ObRepositorySyncJob.where(ob_repository_sync_id: @ob_repository_sync.id) | |||
| ob_jobs = ob_jobs.where(job_type: params[:job_type]) if params[:job_type].present? | |||
| ob_jobs = ob_jobs.where(github_branch: params[:github_branch]) if params[:github_branch].present? | |||
| ob_jobs = ob_jobs.where(gitee_branch: params[:gitee_branch]) if params[:gitee_branch].present? | |||
| ob_jobs = ob_jobs.where(gitlink_branch: params[:gitlink_branch]) if params[:gitlink_branch].present? | |||
| tip_exception "该分支组合已配置,不能重复!" if ob_jobs.count > 0 | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.create_projects_jobs(params) | |||
| tip_exception "保存失败: #{res["msg"]}" if res["code"].to_s != "200" | |||
| job_id = res["data"]["id"] | |||
| job = ObRepositorySyncJob.new | |||
| job.ob_repository_sync_id = @ob_repository_sync.id | |||
| job.github_branch = "#{params[:github_branch]}" | |||
| job.gitee_branch = "#{params[:gitee_branch]}" | |||
| job.gitlink_branch = "#{params[:gitlink_branch]}" | |||
| job.job_type = "#{params[:job_type]}" | |||
| job.base = "#{params[:base]}" | |||
| job.job_id = job_id | |||
| job.save | |||
| render_ok | |||
| end | |||
| def delete_job | |||
| tip_exception "缺少参数job_id" if params[:job_id].blank? | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.delete_job params[:job_id] | |||
| tip_exception "删除失败: #{res["msg"]}" if res["code"].to_s != "200" | |||
| job = ObRepositorySyncJob.find_by(ob_repository_sync_id: @ob_repository_sync.id, job_id: params[:job_id]) | |||
| job.destroy! if job.present? | |||
| render_ok | |||
| end | |||
| def start_job | |||
| tip_exception "缺少参数job_id" if params[:job_id].blank? | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.start_job params[:job_id] | |||
| tip_exception "启动错误: #{res["msg"]}" if res["code"].to_s != "200" | |||
| render_ok | |||
| end | |||
| def stop_job | |||
| tip_exception "缺少参数job_id" if params[:job_id].blank? | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.stop_job params[:job_id] | |||
| tip_exception "停止错误: #{res["msg"]}" if res["code"].to_s != "200" | |||
| render_ok | |||
| end | |||
| def job_logs | |||
| tip_exception "该项目未创建同步任务" if @ob_repository_sync.blank? | |||
| tip_exception "缺少参数job_id" if params[:job_id].blank? | |||
| service = ObRepositorySync::ApiService.new(@ob_repository_sync.name) | |||
| res = service.job_logs params[:job_id] | |||
| tip_exception "请求错误: #{res["msg"]}" if res["code"].to_s != "200" | |||
| render_ok(count: res["data"]["total"], data: res["data"]["list"]) | |||
| end | |||
| private | |||
| def load_ob_repository_sync | |||
| @ob_repository_sync = ObRepositorySync.find_by(project_id: @project.id) | |||
| end | |||
| def authenticate_user! | |||
| return if @project.member?(current_user) || current_user.admin? | |||
| render_forbidden('你没有权限操作') | |||
| end | |||
| end | |||
| @@ -4,8 +4,14 @@ class Organizations::OrganizationUsersController < Organizations::BaseController | |||
| def index | |||
| @organization_users = @organization.organization_users.includes(:user) | |||
| search = params[:search].to_s.downcase | |||
| @organization_users = @organization_users.joins(:user).merge(User.like(search)) | |||
| if params[:search].present? | |||
| search = params[:search].to_s.downcase | |||
| user_condition_users = User.like(search).to_sql | |||
| team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql | |||
| users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users") | |||
| @organization_users = @organization_users.where(user_id: users).distinct | |||
| end | |||
| @organization_users = kaminari_paginate(@organization_users) | |||
| end | |||
| @@ -22,14 +22,16 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| @can_create_project = @organization.can_create_project?(current_user.id) | |||
| @is_admin = can_edit_org? | |||
| @is_member = @organization.is_member?(current_user.id) | |||
| Cache::V2::OwnerCommonService.new(@organization.id).read | |||
| end | |||
| def create | |||
| ActiveRecord::Base.transaction do | |||
| tip_exception("无法使用以下关键词:#{organization_params[:name]},请重新命名") if ReversedKeyword.is_reversed(organization_params[:name]).present? | |||
| Organizations::CreateForm.new(organization_params).validate! | |||
| tip_exception("无法使用以下关键词:#{organization_params[:name]},请重新命名") if ReversedKeyword.check_exists?(organization_params[:name]) | |||
| Organizations::CreateForm.new(organization_params.merge(original_name: "")).validate! | |||
| @organization = Organizations::CreateService.call(current_user, organization_params) | |||
| Util.write_file(@image, avatar_path(@organization)) if params[:image].present? | |||
| Cache::V2::OwnerCommonService.new(@organization.id).reset | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| @@ -38,15 +40,22 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| def update | |||
| ActiveRecord::Base.transaction do | |||
| Organizations::CreateForm.new(organization_params).validate! | |||
| Organizations::CreateForm.new(organization_params.merge(original_name: @organization.login)).validate! | |||
| login = @organization.login | |||
| @organization.login = organization_params[:name] if organization_params[:name].present? | |||
| @organization.nickname = organization_params[:nickname] if organization_params[:nickname].present? | |||
| @organization.save! | |||
| sync_organization_extension! | |||
| # 更改组织可见性为私有,则需将该组织下的所有仓库同步更改为私有仓库 | |||
| if organization_extension_params[:visibility] == "privacy" | |||
| Project.where(user_id: @organization.id).where(is_public: true).each do |project| | |||
| update_project_private(project) | |||
| end | |||
| end | |||
| Gitea::Organization::UpdateService.call(@organization.gitea_token, login, @organization.reload) | |||
| Gitea::Organization::UpdateService.call(current_user.gitea_token, login, @organization.reload) | |||
| Util.write_file(@image, avatar_path(@organization)) if params[:image].present? | |||
| Cache::V2::OwnerCommonService.new(@organization.id).reset | |||
| end | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| @@ -56,10 +65,16 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| def destroy | |||
| tip_exception("密码不正确") unless current_user.check_password?(password) | |||
| ActiveRecord::Base.transaction do | |||
| Gitea::Organization::DeleteService.call(@organization.gitea_token, @organization.login) | |||
| @organization.destroy! | |||
| gitea_destroy = Gitea::Organization::DeleteService.call(current_user.gitea_token, @organization.login) | |||
| if gitea_destroy[:status] == 204 | |||
| @organization.destroy! | |||
| render_ok | |||
| elsif gitea_destroy[:status] == 500 | |||
| tip_exception("当组织内含有仓库时,无法删除此组织") | |||
| else | |||
| tip_exception("") | |||
| end | |||
| end | |||
| render_ok | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| @@ -68,8 +83,7 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| def recommend | |||
| recommend = %W(xuos Huawei_Technology openatom_foundation pkecosystem TensorLayer) | |||
| @organizations = Organization.with_visibility(%w(common)) | |||
| .where(login: recommend).select(:id, :login, :firstname, :lastname, :nickname) | |||
| @organizations = Organization.includes(:organization_extension).where(organization_extensions: {recommend: true}).to_a.each_slice(group_size).to_a | |||
| end | |||
| private | |||
| @@ -80,6 +94,10 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| :max_repo_creation, :nickname) | |||
| end | |||
| def group_size | |||
| params.fetch(:group_size, 4).to_i | |||
| end | |||
| def password | |||
| params.fetch(:password, "") | |||
| end | |||
| @@ -111,5 +129,20 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| def sync_organization_extension! | |||
| @organization.organization_extension.update_attributes!(organization_extension_params) | |||
| end | |||
| def update_project_private(project) | |||
| project.update_attributes!(is_public: false) | |||
| project.forked_projects.update_all(is_public: project.is_public) | |||
| gitea_params = { | |||
| private: true, | |||
| default_branch: project.default_branch, | |||
| website: project.website, | |||
| name: project.identifier | |||
| } | |||
| gitea_repo = Gitea::Repository::UpdateService.call(@organization, project&.repository&.identifier, gitea_params) | |||
| project.repository.update_attributes({hidden: gitea_repo["private"], identifier: gitea_repo["name"]}) | |||
| # 更新对应所属分类下的项目数量(私有) | |||
| project.project_category.decrement!(:private_projects_count, 1) if project.project_category.present? | |||
| end | |||
| end | |||