Compare commits

..

15 Commits

28 changed files with 134 additions and 182 deletions

146
README.md
View File

@ -1,27 +1,17 @@
# GitLink - CCF开源创新服务平台
Trustie (确实)是一个以大众化协同开发、开放式资源共享、持续性可信评估为核心机理,面向高校创新实践的在线协作平台。
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
@ -33,19 +23,22 @@ GitLink确实开源是中国计算机学会CCF官方指定的开源
* imagemagick
### 步骤
### Steps
1克隆稳定版本
#### 1. 克隆稳定版本
```
git clone -b master https://gitlink.org.cn/Gitlink/forgeplus.git
git clone -b standalone https://git.trustie.net/jasder/forgeplus.git
```
2安装依赖包
#### 2. 安装依赖包
```bash
cd forgeplus && bundle install
```
3配置初始化文件进入项目根目录执行以下命令
#### 3. 配置初始化文件
进入项目根目录执行一下命令:
```bash
cp config/configuration.yml.example config/configuration.yml
cp config/database.yml.example config/database.yml
@ -53,7 +46,11 @@ touch config/redis.yml
touch config/elasticsearch.yml
```
4配置数据库数据库配置信息请查看/config/database.yml文件项目默认采用mysql数据库, 如需更改,请自行修改配置信息,默认配置如下
#### 4. 配置数据库
数据库配置信息请查看/config/database.yml文件
项目默认采用mysql数据库, 如需更改,请自行修改配置信息,
默认配置如下:
```bash
default: &default
adapter: mysql2
@ -63,13 +60,14 @@ default: &default
password: 123456
```
5配置gitea服务(可选)如需要部署自己的gitea平台请参考[gitea官方平台文档](https://docs.gitea.io/zh-cn/install-from-binary/)。因目前gitea平台api受限暂时推荐从forge平台获取[gitea部署文件](https://forgeplus.trustie.net/projects/Trustie/gitea-binary)进行部署
#### 5. 配置gitea服务(可选)
**如需要部署自己的gitea平台请参考gitea官方平台https://docs.gitea.io/zh-cn/install-from-binary/**
- 配置gitea服务步骤
-- 部署gitea服务并注册root账户
-- 修改forge平台的 config/configuration.yml中的gitea服务指向地址
**因目前gitea平台api受限暂时推荐从forge平台获取gitea部署文件进行部署https://forgeplus.trustie.net/projects/Trustie/gitea-binary**
**配置gitea服务步骤**
1. 部署gitea服务并注册root账户
2. 修改forge平台的 config/configuration.yml中的gitea服务指向地址
```ruby
gitea:
@ -79,9 +77,10 @@ gitea:
base_url: '/api/v1'
```
6安装redis环境请自行搜索各平台如何安装部署redis环境
#### 6. 安装redis环境
**请自行搜索各平台如何安装部署redis环境**
7安装imagemagick插件
#### 7. 安装imagemagick插件
- Mac OS X
```bash
brew install imagemagick ghostscript
@ -92,89 +91,88 @@ gitea:
sudo apt-get install -y imagemagick
```
8创建数据库开发环境为development 生成环境为production
#### 8. 创建数据库
**开发环境为development 生成环境为production**
```bash
rails db:create RAILS_ENV=development
```
9导入数据表结构
#### 9. 导入数据表结构
```bash
bundle exec rake sync_table_structure:import_csv
```
10执行migrate迁移文件开发环境为development 生成环境为production
#### 10. 执行migrate迁移文件
**开发环境为development 生成环境为production**
```bash
rails db:migrate RAILS_ENV=development
```
11clone前端代码将前端代码克隆到public/react目录下目录结构应该是: public/react/build
#### 11. clone前端代码
**将前端代码克隆到public/react目录下目录结构应该是: public/react/build**
```bash
git clone -b standalone https://git.trustie.net/jasder/build.git
```
12启动redis(此处以macOS系统为例)
#### 12. 启动redis(此处已mac系统为例)
```bash
redis-server&
```
13启动sidekiq开发环境为development 生成环境为production
#### 13. 启动sidekiq
**开发环境为development 生成环境为production**
```bash
bundle exec sidekiq -C config/sidekiq.yml -e production -d
```
14启动rails服务
#### 14. 启动rails服务
```bash
rails s
```
15浏览器访问在浏览器中输入如下地址访问
#### 15. 浏览器访问
在浏览器中输入如下地址访问:
```bash
http://localhost:3000/
```
16其他说明通过页面注册以第一个用户为平台管理员用户
#### 16. 其他说明
通过页面注册都第一个用户为平台管理员用户
## 页面展示
- 项目列表
- 代码库
![](docs/figs/code.png?raw=true)
<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>
![](docs/figs/issue_manage.png?raw=true)
- 任务查看
![](docs/figs/issue_view.png?raw=true)
- 任务指派
![](docs/figs/issue_assign2.png?raw=true)
- 里程碑
![](docs/figs/milestone.png?raw=true)
### API
- [API文档](https://forgeplus.trustie.net/docs/api)
- [API](showdoc.com.cn)
账号forgeplus@admin.com 密码forge123
## 贡献代码
我们期待您向GitLink提交贡献在您贡献时请遵循流程[【Wiki文档-GitLink协作开发流程】](https://www.gitlink.org.cn/Gitlink/forgeplus/wiki "【Wiki文档-GitLink协作开发流程】")
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
#### 指导文档
- [API文档](https://www.gitlink.org.cn/docs/api)
- [Git常用命令](https://git-scm.com/)
## 许可证协议
## License

View File

@ -22,9 +22,9 @@ class Users::TemplateMessageSettingsController < Users::BaseController
def get_current_setting
@current_setting = @_observed_user.user_template_message_setting
@current_setting = UserTemplateMessageSetting.build(@_observed_user.id) if @current_setting.nil?
@current_setting.notification_body.merge!(UserTemplateMessageSetting.init_notification_body.except(*@current_setting.notification_body.keys))
@current_setting.email_body.merge!(UserTemplateMessageSetting.init_email_body.except(*@current_setting.email_body.keys))
@current_setting = UserTemplateMessageSetting.build(@_observed_user.id) if @current_setting.nil?
end
def setting_params

View File

@ -7,6 +7,8 @@ class CacheAsyncClearJob < ApplicationJob
Cache::V2::ProjectCommonService.new(id).clear
when "owner_common_service"
Cache::V2::OwnnerCommonService.new(id).clear
when "project_rank_service"
Cache::V2::ProjectRankService.new(id).clear
end
end
end

View File

@ -52,6 +52,10 @@ class SendTemplateMessageJob < ApplicationJob
receivers = User.where(id: [issue&.assigned_to_id, issue&.author_id])
receivers_string, content, notification_url = MessageTemplate::IssueExpire.get_message_content(receivers, issue)
Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {issue_id: issue.id})
receivers.find_each do |receiver|
receivers_email_string, email_title, email_content = MessageTemplate::IssueExpire.get_email_message_content(receiver, issue)
Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content)
end
when 'IssueDeleted'
operator_id, issue_title, issue_assigned_to_id, issue_author_id = args[0], args[1], args[2], args[3]
operator = User.find_by_id(operator_id)

View File

@ -76,7 +76,7 @@ class Issue < ApplicationRecord
scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
scope :closed, ->{where(status_id: 5)}
after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
after_save :change_versions_count
after_save :change_versions_count, :send_update_message_to_notice_system
after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic
def incre_project_common
@ -200,4 +200,8 @@ class Issue < ApplicationRecord
self.project.decrement!(:closed_issues_count) if self.status_id == 5
end
def send_update_message_to_notice_system
SendTemplateMessageJob.perform_later('IssueExpire', self.id) if Site.has_notice_menu? && self.due_date == Date.today + 1.days
end
end

View File

@ -16,18 +16,21 @@
class MessageTemplate::IssueExpire < MessageTemplate
# MessageTemplate::IssueCreatorExpire.get_message_content(User.where(login: 'yystopf'), Issue.last)
def self.get_message_content(receiver, issue)
if receiver.user_template_message_setting.present?
return '', '', '' unless receiver.user_template_message_setting.email_body["Normal::IssueExpire"]
project = issue&.project
owner = project&.owner
content = sys_notice.gsub('{title}', issue&.subject)
url = notification_url.gsub('{owner}', owner&.login).gsub('{identifier}', project&.identifier).gsub('{id}', issue&.id.to_s)
return receivers_string(receivers), content, url
else
return '', '', ''
def self.get_message_content(receivers, issue)
receivers.each do |receiver|
if receiver.user_template_message_setting.present?
send_setting = receiver.user_template_message_setting.notification_body["CreateOrAssign::IssueExpire"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["CreateOrAssign::IssueExpire"] : send_setting
receivers = receivers.where.not(id: receiver.id) unless send_setting
end
end
return '', '', '' if receivers.blank?
project = issue&.project
owner = project&.owner
content = sys_notice.gsub('{title}', issue&.subject)
url = notification_url.gsub('{owner}', owner&.login).gsub('{identifier}', project&.identifier).gsub('{id}', issue&.id.to_s)
return receivers_string(receivers), content, url
rescue => e
Rails.logger.info("MessageTemplate::IssueExpire.get_message_content [ERROR] #{e}")
return '', '', ''
@ -35,9 +38,12 @@ class MessageTemplate::IssueExpire < MessageTemplate
def self.get_email_message_content(receiver, issue)
if receiver.user_template_message_setting.present?
return '', '', '' unless receiver.user_template_message_setting.email_body["Normal::IssueExpire"]
send_setting = receiver.user_template_message_setting.email_body["CreateOrAssign::IssueExpire"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_email_body["CreateOrAssign::IssueExpire"] : send_setting
return '', '', '' unless send_setting
project = issue&.project
owner = project&.owner
title = email_title
content = email
content.gsub!('{receiver}', receiver&.real_name)

View File

@ -19,7 +19,9 @@ class MessageTemplate::ProjectForked < MessageTemplate
def self.get_message_content(receivers, user, project)
receivers.each do |receiver|
if receiver.user_template_message_setting.present?
receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["ManageProject::Forked"]
send_setting = receiver.user_template_message_setting.notification_body["ManageProject::Forked"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::Forked"] : send_setting
receivers = receivers.where.not(id: receiver.id) unless send_setting
end
end
return '', '', '' if receivers.blank?

View File

@ -19,6 +19,8 @@ class MessageTemplate::ProjectMilestone < MessageTemplate
def self.get_message_content(receivers, operator, milestone)
receivers.each do |receiver|
if receiver.user_template_message_setting.present?
send_setting = receiver.user_template_message_setting.notification_body["ManageProject::Milestone"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::Milestone"] : send_setting
receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["ManageProject::Milestone"]
end
end
@ -36,7 +38,9 @@ class MessageTemplate::ProjectMilestone < MessageTemplate
def self.get_email_message_content(receiver, operator, milestone)
if receiver.user_template_message_setting.present?
return '', '', '' unless receiver.user_template_message_setting.email_body["ManageProject::Milestone"]
send_setting = receiver.user_template_message_setting.email_body["ManageProject::Milestone"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_email_body["ManageProject::Milestone"] : send_setting
return '', '', '' unless send_setting
project = milestone&.project
owner = project&.owner
title = email_title

View File

@ -19,7 +19,9 @@ class MessageTemplate::ProjectMilestoneCompleted < MessageTemplate
def self.get_message_content(receivers, milestone)
receivers.each do |receiver|
if receiver.user_template_message_setting.present?
receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["ManageProject::MilestoneCompleted"]
send_setting = receiver.user_template_message_setting.notification_body["ManageProject::MilestoneCompleted"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::MilestoneCompleted"] : send_setting
receivers = receivers.where.not(id: receiver.id) unless send_setting
end
end
return '', '', '' if receivers.blank?
@ -36,7 +38,9 @@ class MessageTemplate::ProjectMilestoneCompleted < MessageTemplate
def self.get_email_message_content(receiver, milestone)
if receiver.user_template_message_setting.present?
return '', '', '' unless receiver.user_template_message_setting.email_body["ManageProject::MilestoneCompleted"]
send_setting = receiver.user_template_message_setting.email_body["ManageProject::MilestoneCompleted"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_email_body["ManageProject::MilestoneCompleted"] : send_setting
return '', '', '' unless send_setting
project = milestone&.project
owner = project&.owner
title = email_title

View File

@ -19,7 +19,9 @@ class MessageTemplate::ProjectPraised < MessageTemplate
def self.get_message_content(receivers, user, project)
receivers.each do |receiver|
if receiver.user_template_message_setting.present?
receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["ManageProject::Praised"]
send_setting = receiver.user_template_message_setting.notification_body["ManageProject::Praised"]
send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::Praised"] : send_setting
receivers = receivers.where.not(id: receiver.id) unless send_setting
end
end
return '', '', '' if receivers.blank?

View File

@ -77,6 +77,9 @@
# index_projects_on_updated_on (updated_on)
#
class Project < ApplicationRecord
include Matchable
include Publicable
@ -125,8 +128,8 @@ class Project < ApplicationRecord
has_many :has_pinned_users, through: :pinned_projects, source: :user
has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
after_create :incre_user_statistic, :incre_platform_statistic
after_save :check_project_members, :reset_cache_data
before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned
after_save :check_project_members
before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data
before_destroy :decre_project_common, :decre_forked_from_project_count
after_destroy :decre_user_statistic, :decre_platform_statistic
scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)}
@ -150,7 +153,7 @@ class Project < ApplicationRecord
end
def reset_cache_data
CacheAsyncResetJob.perform_later("project_common_service", self.id)
CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id)
if changes[:user_id].present?
CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first)
CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last)
@ -163,6 +166,14 @@ class Project < ApplicationRecord
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1})
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1})
end
if changes[:is_public].present?
if changes[:is_public][0] && !changes[:is_public][1]
CacheAsyncClearJob.perform_later('project_rank_service', self.id)
end
if !changes[:is_public][0] && changes[:is_public][1]
$redis_cache.srem("v2-project-rank-deleted", self.id)
end
end
end
def decre_project_common

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/figs/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/figs/issue_assign.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
docs/figs/issue_assign2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/figs/issue_manage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

BIN
docs/figs/issue_view.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/figs/milestone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long