Compare commits

..

427 Commits

Author SHA1 Message Date
yystopf 2191102fa8 修复:移除review引用 2022-12-08 15:09:12 +08:00
yystopf 033d36406e 修复:get_user_info 字段缺失 2022-12-08 15:01:54 +08:00
yystopf 6e62aecbbc 修复:移除代码审查部分migrate 2022-12-08 14:56:02 +08:00
yystopf fb44955a5f 修复:移除代码审查部分model以及controller(表名冲突) 2022-12-08 14:54:41 +08:00
yystopf 6454c375ed 修复:移除oauth2表创建迁移 2022-12-08 14:48:57 +08:00
yystopf 534fdec9f2 merge from develop 2022-12-08 14:28:16 +08:00
yystopf dfa0f1161d Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-12-08 11:52:06 +08:00
yystopf bc6f5cec4d 新增:仓库贡献者行数查询接口 2022-12-08 11:51:59 +08:00
xxq250 ee27c90205 fixed qq命名调整,方便封装 2022-12-06 10:24:48 +08:00
xxq250 534ee80523 Merge remote-tracking branch 'origin/develop' into develop 2022-12-05 17:46:25 +08:00
xxq250 daf7514afe fixed 第三方授权登录配置 2022-12-05 17:46:21 +08:00
yystopf 4b1f1c697e 更改:下载excel文件不走跳转 2022-12-02 13:59:41 +08:00
yystopf 77c7acd582 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-12-02 12:11:30 +08:00
yystopf 025a6a53e9 修复:图片无法下载的问题 2022-12-02 12:11:24 +08:00
xxq250 6b846b10bb fixed 第三方授权登录(github,gitee,qq,wechat) 2022-12-01 17:51:54 +08:00
xxq250 763d7b499e fixed 第三方授权登录(github,gitee,qq,wechat) 2022-12-01 17:22:22 +08:00
yystopf 9b39446dfb 修复:提交文件内容格式由前端决定 2022-11-29 17:08:26 +08:00
yystopf c6e77ac7ee 修复:提交文件未创建新分支时为空判断 2022-11-29 17:02:10 +08:00
yystopf f5cdb6f02d 新增:旧统计接口移除处理 2022-11-29 14:31:14 +08:00
yystopf f7ca03db81 修复:通用分支移除projects_activity表修改 2022-11-28 11:41:36 +08:00
xxq250 8da8b021f6 Merge remote-tracking branch 'origin/develop' into develop 2022-11-28 11:20:01 +08:00
xxq250 70ba78a9b5 fixed 组织用户不存在 2022-11-28 11:19:56 +08:00
yystopf 6191ddd233 修复:通用分支移除声明表修改 2022-11-28 10:57:05 +08:00
xxq250 35cf298f5d fixed 增加emoji表情支持表字段,长度限制 2022-11-25 14:19:05 +08:00
xxq250 ee5d959404 fixed 增加emoji表情支持表字段,长度限制 2022-11-25 14:15:47 +08:00
xxq250 7f47cd8d46 fixed 增加emoji表情支持表字段 2022-11-25 14:09:26 +08:00
xxq250 06c0ac65bf Merge remote-tracking branch 'origin/develop' into develop 2022-11-23 17:58:56 +08:00
xxq250 8f8dab10c3 fixed 增加emoji表情支持 2022-11-23 17:58:51 +08:00
yystopf a6923bf7ed 新增:get_user_info电话号码展示 2022-11-23 17:44:37 +08:00
“xxq250” 667dca0553 Merge remote-tracking branch 'origin/develop' into develop 2022-11-23 17:07:59 +08:00
“xxq250” f23f02cb80 fixed 增加emoji表情支持 2022-11-23 17:07:54 +08:00
yystopf 38a8191b74 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-11-16 15:27:54 +08:00
yystopf e183f48c02 修复:合并请求查询关闭类型须根据合并请求类型查询 2022-11-16 15:27:49 +08:00
“xxq250” a1731f91a9 fixed 附件下载权限 2022-11-15 17:12:10 +08:00
yystopf ea7cf1cd1f 修复:排序字段 2022-11-15 15:17:06 +08:00
yystopf 255b615327 更改:项目首页分类后数量更改 2022-11-15 14:41:08 +08:00
yystopf 64c6f69f56 新增:统计项目分类下私有项目的数量 2022-11-15 14:25:35 +08:00
yystopf 49abadfa3f 新增:合并请求合并之后挂载的issue设置为关闭 2022-11-11 14:13:38 +08:00
yystopf 4cde7c374e 更改:反馈意见编辑器隐藏上传按钮 2022-11-11 14:04:36 +08:00
yystopf b56930b9ab 修复:反馈意见发送邮件失败错误返回 2022-11-11 12:25:25 +08:00
yystopf f98886b3a0 修复 2022-11-11 11:50:07 +08:00
yystopf 30572aa41c 修复:重复代码移除 2022-11-11 11:11:34 +08:00
yystopf ef83697cb8 修复:tooltip闪烁问题 2022-11-10 16:21:43 +08:00
yystopf 570f664c27 修复:标记issue数量问题 2022-11-10 11:02:50 +08:00
yystopf e24921bba3 修复 2022-11-09 14:56:27 +08:00
yystopf e4d62c5335 新增:字符串截取展示 2022-11-09 14:54:43 +08:00
yystopf 6d9cf8aad2 修复 2022-11-09 14:41:17 +08:00
yystopf e3179dfd6e 新增:新增反馈意见邮件回复功能 2022-11-09 14:35:11 +08:00
yystopf 7817487ab7 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-11-09 11:37:18 +08:00
yystopf 83b46fcb50 新增:用户反馈意见后台管理逻辑 2022-11-09 11:37:12 +08:00
“xxq250” 7cc9d207b6 fixed 项目列表查询count优化 2022-11-08 17:18:04 +08:00
“xxq250” abfb62f342 fixed 项目优化排序 2022-11-08 17:16:25 +08:00
“xxq250” ccbbab0741 fixed 项目优化排序 2022-11-08 15:33:12 +08:00
yystopf 3e5c1da2c5 新增:反馈意见模型及接口 2022-11-08 13:27:46 +08:00
yystopf 07aefa79eb 更改:项目后台管理移除精选设置 2022-11-02 14:21:49 +08:00
yystopf 423d5a9316 修复:同步项目权限到组织时需预先判断是否存在于团队中 2022-11-01 15:29:07 +08:00
yystopf 9664ca001e 新增: 邮箱验证码重新发送后之前的验证码需设置为无效 2022-10-27 17:57:55 +08:00
yystopf e15809c186 修复:无法正常转移的问题 2022-10-27 10:48:24 +08:00
yystopf 7d4c0df691 修复 2022-10-27 09:55:55 +08:00
yystopf e72dfa4973 修复:项目迁移设置权限在更改拥有者之后 2022-10-27 09:28:52 +08:00
yystopf 84a8339a04 修复 2022-10-26 15:16:06 +08:00
yystopf 3d5b2e76d7 修复:update_column无法触发count_cache 2022-10-26 14:58:04 +08:00
yystopf 8b14ee3c72 修复:更改组织下项目权限同时更改组织下权限同步至gitea 2022-10-26 14:49:40 +08:00
yystopf 3f7366fbcd 修复:更改用户通知邮箱只能自己改自己的 2022-10-21 15:28:48 +08:00
yystopf cfdbc5a682 修复 2022-10-21 15:07:31 +08:00
yystopf 9b880e3209 修复 2022-10-21 15:04:26 +08:00
yystopf c2ef23b746 修复 2022-10-21 14:42:51 +08:00
yystopf 8a33ea6707 修复 2022-10-21 14:40:43 +08:00
yystopf 9ee4c85b7c 更改:验证返回status以及万能验证码 2022-10-21 14:31:32 +08:00
yystopf e9b23d6577 更改:合并请求代码冲突提示语更改 2022-10-19 16:17:44 +08:00
yystopf 64d9c334ce 修复:项目语言验证信息统一 2022-10-19 15:18:15 +08:00
yystopf cad7e24299 修复:项目列表表头样式 2022-10-18 15:53:37 +08:00
yystopf 3b15dd84fc 新增:标签列表返回总数 2022-10-18 14:52:15 +08:00
yystopf 58a43a8b80 更改:合并请求和issue仅加载开启中的里程碑 2022-10-18 13:43:16 +08:00
yystopf e25231738a 新增:标签名字列表 2022-10-18 13:32:57 +08:00
yystopf 35965f542a 新增:标签名字列表 2022-10-18 13:31:12 +08:00
yystopf fa48e3f5ca 修复:许可证和忽略文件内容为空表单提示 2022-10-18 11:14:35 +08:00
yystopf 13e2738fb5 新增:列表页新增only_watched参数 2022-10-17 17:37:03 +08:00
yystopf 30437d828c 新增:项目git忽略文件新增验证 2022-10-17 13:16:22 +08:00
yystopf dcc7ab6738 更改:项目分类去掉图片上传 2022-10-17 13:01:08 +08:00
yystopf 57a7a17f73 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-10-17 12:57:32 +08:00
yystopf 5196f33f24 新增:项目许可证新增验证 2022-10-17 12:57:24 +08:00
“xxq250” b3cbed2963 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/controllers/members_controller.rb
2022-10-17 11:09:48 +08:00
“xxq250” dd1af6712c fixed 项目成员提示修改 2022-10-17 11:08:54 +08:00
yystopf 8807be6e4d 新增:项目语言新增唯一性验证 2022-10-17 10:52:57 +08:00
yystopf 8d41c27044 更改:项目成员添加提示 2022-10-17 10:42:40 +08:00
yystopf 53fe554d1e 更改:后台管理表头样式 2022-10-17 10:25:17 +08:00
“xxq250” 4e02dbe06b Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/controllers/attachments_controller.rb
2022-10-17 10:14:13 +08:00
“xxq250” 620ddff9bb fixed issue评论附件权限 2022-10-17 10:13:30 +08:00
yystopf 630a7aadeb 新增:组织数据修复任务 2022-10-15 11:29:23 +08:00
yystopf 6383384287 更改:后台管理用户真实姓名改为昵称 2022-10-14 15:31:20 +08:00
yystopf 83f1263f85 修复:更改查询项目判断条件 2022-10-14 14:52:25 +08:00
yystopf 9c11cd79e8 修复:消息模版无法查询子类的问题 2022-10-10 19:06:47 +08:00
yystopf a7384bc1c3 修复:代码详情页报错 2022-10-08 18:44:04 +08:00
yystopf 55170c7693 新增:更改邮箱相关接口文档 2022-09-29 13:58:14 +08:00
yystopf 2cde13eabb 新增:接口测试以及功能测试 2022-09-29 12:05:57 +08:00
yystopf d68e7f38a3 新增:更改邮箱service逻辑 2022-09-29 09:30:33 +08:00
yystopf 649d9c4ea6 新增:更改邮箱控制器方法 2022-09-28 16:09:10 +08:00
“xxq250” fce8166ba7 fixed 用户活跃数 2022-09-26 16:11:34 +08:00
“xxq250” ae0efc1115 Merge remote-tracking branch 'origin/develop' into develop 2022-09-26 10:38:32 +08:00
“xxq250” dfc2edd62d fixed注册gitea错误信息 2022-09-26 10:38:28 +08:00
“xxq250” fcdbf4ec7f Merge remote-tracking branch 'origin/dev_educoder_sync' into dev_educoder_sync
# Conflicts:
#	app/controllers/concerns/register_helper.rb
2022-09-26 10:32:01 +08:00
“xxq250” 1068168968 fixed注册gitea错误信息 2022-09-26 10:30:50 +08:00
yystopf d7f3a21add 修复:周统计数据需加入年份 2022-09-23 17:56:39 +08:00
yystopf ef9592ef30 更改:活跃度算法更新 2022-09-23 10:28:12 +08:00
yystopf cc42683316 新增:活跃度计算访问量限制 2022-09-23 10:07:52 +08:00
yystopf 70f1e6827f 更改:项目已设置语言,则不重新设置语言 2022-09-21 10:06:29 +08:00
yystopf b5188e3272 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-09-21 09:54:53 +08:00
yystopf ff881732d2 更改:项目已设置语言,则不重新设置语言 2022-09-21 09:54:49 +08:00
“xxq250” 2adf87a289 fixed nps数据统计 2022-09-20 09:52:40 +08:00
“xxq250” 6954a860af fixed nps数据统计 2022-09-20 09:47:29 +08:00
“xxq250” bb8e6aeaf8 nps数据统计 2022-09-20 09:41:27 +08:00
“xxq250” 6d7b7dd058 fixed 修改密码接口 2022-09-19 10:06:49 +08:00
yystopf 007b90c284 修复:删除旧的逻辑 2022-09-15 10:00:00 +08:00
yystopf 481d1b7db0 修复:commit log统计触发规则 2022-09-15 09:57:28 +08:00
yystopf 733f64eaec 新增:项目排行榜新增提交数算法 2022-09-15 09:44:12 +08:00
yystopf 383660c778 更改:组织缓存信息方式改在控制器里面 2022-09-15 09:38:28 +08:00
yystopf 4bd77a870b 更改:webhook创建数量限制为50个 2022-09-08 14:19:08 +08:00
yystopf b1f460d760 更改:webhook创建数量限制为50个 2022-09-08 10:40:52 +08:00
yystopf a438ab6f3e 新增:提示语locale配置 2022-09-06 14:57:54 +08:00
“xxq250” 3a34d8682c Merge remote-tracking branch 'origin/develop' into develop 2022-09-06 10:30:15 +08:00
“xxq250” 60894f00b1 nps已打分区分 2022-09-06 10:29:51 +08:00
“xxq250” 925b05e004 nps已打分区分 2022-09-06 10:29:49 +08:00
yystopf 4878d66620 修复 2022-09-06 09:04:46 +08:00
yystopf 632ade9d25 修复 2022-09-06 09:02:30 +08:00
yystopf 5acd45bf85 修复 2022-09-06 09:00:05 +08:00
yystopf d03b45eecf 新增:创建文件对分支长度进行验证 2022-09-06 08:59:24 +08:00
“xxq250” 32a9d762d8 调整nps用户调研接口,需要登录 2022-08-31 10:39:52 +08:00
“xxq250” 0a79950046 调整nps用户调研接口 2022-08-31 10:38:21 +08:00
“xxq250” 4b7c58b13f Merge remote-tracking branch 'origin/develop' into develop 2022-08-30 18:10:33 +08:00
“xxq250” 7148957368 nps用户调研 2022-08-30 18:10:27 +08:00
yystopf aa50c50254 修复 2022-08-30 10:40:27 +08:00
yystopf 60b3c7925d 新增:添加全部或者移除全部团队项目[文档] 2022-08-30 10:34:37 +08:00
yystopf 0f41cabe71 新增:添加全部或者移除全部团队项目 2022-08-30 10:23:35 +08:00
“xxq250” d04ed7e008 fixed 组织成员列表查询优化 2022-08-29 10:05:56 +08:00
“xxq250” 7993a89ce3 fixed 用户已被注册提示 2022-08-26 15:26:09 +08:00
yystopf 6168256897 修复: diff filepath为空报错 2022-08-22 10:48:05 +08:00
yystopf 4b167dacbb 修复: trace user 手机号为空 2022-08-17 18:39:01 +08:00
yystopf 911c47df8d 新增:文件blame 上个版本的行号 2022-08-17 18:05:15 +08:00
yystopf a37d7b9840 修复: 手机号默认不传 2022-08-17 15:28:58 +08:00
“xxq250” 217ab920a9 fixed commit日志同时更新项目时间 2022-08-16 15:33:15 +08:00
“xxq250” 98ae62d181 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-08-16 14:28:52 +08:00
“xxq250” 98f1723411 fixed 发行版git删除时,不同步处理 2022-08-16 14:28:40 +08:00
yystopf 68a38e75bc 修复: 消息返回接口时间 2022-08-16 14:07:37 +08:00
yystopf ee29c6be5c Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-08-15 15:55:37 +08:00
yystopf c0ee0a694c 更改:代码溯源status改为code 2022-08-15 15:55:33 +08:00
“xxq250” 25fa6f427b Merge remote-tracking branch 'origin/develop' into develop 2022-08-15 14:03:32 +08:00
“xxq250” 467d722d25 fixed reposyncer job任务增加分页 2022-08-15 14:03:27 +08:00
yystopf 3e352d3c7c 新增:获取代码溯源系统维护信息接口 2022-08-12 17:23:19 +08:00
yystopf 13472b2311 新增: 合并请求评论查询is_full 2022-08-11 15:21:11 +08:00
“xxq250” bdddd655bf Merge remote-tracking branch 'origin/develop' into develop 2022-08-11 09:31:49 +08:00
“xxq250” 6ec7a13293 fixed reposyncer 分支组合已配置,不能重复 2022-08-11 09:31:40 +08:00
“xxq250” 716b29c25f fixed 消息模板配置type 2022-08-11 09:29:37 +08:00
“xxq250” 5c4930a362 fixed 消息模板配置type 2022-08-11 09:29:36 +08:00
“xxq250” ad8fed9440 fixed 消息模板配置type 2022-08-11 09:29:35 +08:00
“xxq250” 964bef7896 fixed 消息模板配置type 2022-08-11 09:29:34 +08:00
“xxq250” 7e48e5e8b8 fixed 消息模板配置 2022-08-11 09:29:34 +08:00
“xxq250” 48846a7e31 fixed 已读未读标记优化 2022-08-11 09:29:33 +08:00
yystopf 32db15aad5 新增: commit找不到返回404 2022-08-11 09:29:12 +08:00
“xxq250” 8ec3e4bbaa fixed Gitea::PullRequest::FilesService增加参数 2022-08-11 09:28:12 +08:00
yystopf 4b05838498 新增:合并请求评论新增path参数筛选以及去掉分页最多返回200数据 2022-08-09 09:48:02 +08:00
yystopf b8be0c23cb 修复 2022-08-05 11:05:29 +08:00
yystopf d20a7b1318 修复:项目协作者添加组织成员问题 2022-08-05 10:50:41 +08:00
yystopf f8268accc0 新增: 组织下项目协作者添加当前用户 2022-08-04 17:28:14 +08:00
yystopf a9811768d4 修复: 合并请求评论类型允许为空 2022-08-04 15:46:19 +08:00
yystopf 024bc289de 修改: 合并请求评论必选参数以及审查列表去掉分页 2022-08-03 17:13:45 +08:00
yystopf 96d34d0237 修改:合并请求审查权限 2022-08-03 15:31:04 +08:00
yystopf fea11bdc2e 新增:webhook type类型 2022-08-03 14:20:25 +08:00
yystopf ab5d273f98 新增:版本diff新增diff信息 2022-08-02 19:03:54 +08:00
“xxq250” 8bf4e9b8d2 fixed Repository.url 2022-08-02 18:10:01 +08:00
yystopf 096b0b954d 新增: fork仓库协作者可以提交文件至仓库 2022-08-01 17:14:32 +08:00
yystopf df57312056 修复 2022-07-29 17:29:25 +08:00
“xxq250” f12cfabee3 fixed 精选项目不包含私有仓库 2022-07-28 17:30:09 +08:00
“xxq250” b22909a654 fixed 语言接口token参数错误 2022-07-28 17:20:22 +08:00
“xxq250” 579b45dfa4 fixed 精选项目不包含私有仓库 2022-07-28 17:19:39 +08:00
“xxq250” fbc1f95cd1 Merge remote-tracking branch 'origin/develop' into develop 2022-07-28 16:42:29 +08:00
“xxq250” 4ccf8ea5ff CloudIDEA文件标记已读未读 2022-07-28 16:42:21 +08:00
yystopf d5c141f892 修复:pulls文件夹load 2022-07-28 15:19:31 +08:00
yystopf 35c6cb98db 新增:批量提交文件支持参数选填 2022-07-28 14:25:33 +08:00
yystopf f417e42864 修复: 提示修改 2022-07-28 14:02:37 +08:00
yystopf 6e54203ebb 修复:批量提交文件参数缺失以及数据为空处理 2022-07-28 13:56:31 +08:00
“xxq250” 79a6841bbc Merge remote-tracking branch 'origin/develop' into develop 2022-07-28 09:31:19 +08:00
“xxq250” b3d78741a3 fixed reposyncer同步仓库接口增加gitlink 2022-07-28 09:31:14 +08:00
yystopf 935f3ddf7f Merge pull request '合并请求评论相关接口及文档' (#20) from yystopf/forgeplus:develop into develop 2022-07-27 16:11:17 +08:00
yystopf a2549f7ff1 新增: 评论及审查相关接口文档 2022-07-27 16:07:15 +08:00
yystopf d6b5dc326a 新增: 评论相关api根据需求重写 2022-07-27 14:52:49 +08:00
“xxq250” 003bfe7884 Merge remote-tracking branch 'origin/develop' into develop 2022-07-26 17:08:10 +08:00
“xxq250” df5efa0dc6 fixed 邮箱验证正则优化 2022-07-26 17:08:05 +08:00
yystopf a35db9fe80 新增:reviewers修改入口 2022-07-26 16:45:53 +08:00
yystopf 6183f1f76d 修复: 缺少title和body 2022-07-26 14:38:41 +08:00
yystopf e72273f3b2 修复: 合并请求详情中review需要最新的一个 2022-07-26 14:29:12 +08:00
yystopf cc09f724cc Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-07-26 14:14:33 +08:00
yystopf 7a54c5f536 新增: 接口文档更新 2022-07-26 14:14:25 +08:00
“xxq250” bb3f5cec76 fixed reposyncer同步仓库接口 2022-07-26 11:40:49 +08:00
“xxq250” 19ffd9fea1 fixed PullRequest 统一关联关系字段gitea_id 2022-07-26 10:55:42 +08:00
“xxq250” 5877474f59 Merge remote-tracking branch 'origin/develop' into develop 2022-07-26 10:44:21 +08:00
“xxq250” 1bbde165c2 reposyncer同步仓库接口 2022-07-26 10:44:12 +08:00
yystopf 3f5754a3a5 新增:合并请求详情接口 2022-07-25 18:23:06 +08:00
yystopf 5463c22518 新增: 合并请求创建审查接口 2022-07-25 17:28:38 +08:00
yystopf 5db43bf768 新增:新合并请求列表接口 2022-07-25 16:21:03 +08:00
yystopf 1feb166fd5 修复:合并请求评审模型修改 2022-07-25 16:20:42 +08:00
yystopf 0df1e5fdd0 新增:合并请求版本列表和diff接口文档 2022-07-22 18:08:16 +08:00
yystopf 8ba70a33be 新增: pr版本列表和diff接口 2022-07-22 17:33:08 +08:00
“xxq250” b21f44b59e fixed 代码提交日志根据提交commit记录多次 2022-07-22 10:11:21 +08:00
“xxq250” 1c56f21c96 fixed sonar未检测 2022-07-20 16:19:40 +08:00
yystopf 7793eacf75 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-07-19 16:47:06 +08:00
yystopf 25afedcdfd 新增: 批量更改文件 2022-07-19 16:47:01 +08:00
“xxq250” d95a689815 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-07-19 14:58:29 +08:00
xiaoxiaoqiong b386dc51e4 fixed 后台统计优化,7天时间精确 2022-07-19 14:56:02 +08:00
yystopf 1882120df3 新增: 获取单文件blame信息 2022-07-19 11:14:50 +08:00
yystopf 274cd81655 新增:获取commit列表 2022-07-19 10:41:25 +08:00
yystopf 6273349334 新增: 获取比较blame、token以及异常捕获处理 2022-07-18 15:39:51 +08:00
yystopf e8deb83004 fix: gemfile 2022-07-18 11:20:24 +08:00
yystopf 52701d1c2f Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-07-18 11:18:35 +08:00
yystopf b248f770f1 新增:获取单个提交的blame信息接口以及文档 2022-07-18 11:18:29 +08:00
xiaoxiaoqiong 04b9e43382 fixed 后台统计优化,新增项目数 2022-07-18 11:14:45 +08:00
xiaoxiaoqiong 3580a8653d fixed 后台统计优化,新增项目数 2022-07-18 11:04:57 +08:00
xiaoxiaoqiong d9653e461e fixed 后台统计优化,周存率小数计算 2022-07-18 11:00:47 +08:00
xiaoxiaoqiong d9a1d32ec8 fixed 后台统计优化,周存率 2022-07-18 10:55:41 +08:00
xiaoxiaoqiong b5dfd83527 fix 提交日志提交用户信息不匹配 2022-07-18 10:43:57 +08:00
xiaoxiaoqiong df7b91a8ba gemfile.lock reset 2022-07-18 10:38:45 +08:00
xiaoxiaoqiong 8dae15225e 后台统计优化 2022-07-18 10:37:53 +08:00
xiaoxiaoqiong 5fcf789e63 fixed 提交日志记录用户信息,仓库关联 2022-07-18 09:35:19 +08:00
xiaoxiaoqiong ed0655452c 增加从webhook接收仓库日志记录 2022-07-18 09:34:56 +08:00
yystopf 0dd062ce1c 新增: blobs和文件树接口及文档 2022-07-15 17:03:18 +08:00
yystopf bd3cbf33fd fix 2022-07-15 14:41:00 +08:00
yystopf b8d4b15b40 新增: 分支接口文档 2022-07-15 14:40:35 +08:00
yystopf 24d83133e7 新增: 创建分支接口 2022-07-15 14:02:48 +08:00
yystopf 4ed3a9d478 修复:cherry pick doorkeeper配置 2022-07-14 18:13:06 +08:00
yystopf fbbacfa983 修复: apk文件为下载文件 2022-07-14 09:36:52 +08:00
xiaoxiaoqiong d47708dc1c fixed 代码质量语言不明时-其他 2022-07-12 16:45:34 +08:00
xiaoxiaoqiong 625ed024e2 fixed 代质分析没完成状态时不查详情 2022-07-12 15:57:48 +08:00
xiaoxiaoqiong 60842e64e5 fixed 代质分析没完成状态时不查详情 2022-07-12 15:57:34 +08:00
xiaoxiaoqiong 993135602f fixed 代质分析没完成状态时不查详情 2022-07-12 15:56:44 +08:00
xiaoxiaoqiong 35e8f1d423 fixed 代质分析提示文案 2022-07-12 15:19:01 +08:00
xiaoxiaoqiong dbea2e1c3d fixed 代质分析未完成需要再检测完成,更新时间 2022-07-12 15:13:52 +08:00
xiaoxiaoqiong f1fdf0c37f fixed 代质分析未完成需要再检测完成 2022-07-12 15:09:24 +08:00
yystopf d0988e42b4 新增: 平台接口文档更新 2022-07-08 18:01:07 +08:00
xiaoxiaoqiong ef8f33e1cd 未配置语言根据项目检测更新 2022-07-08 16:33:27 +08:00
xiaoxiaoqiong b380b61873 fixed 代码质量分析 菜单 2022-07-08 14:42:02 +08:00
xiaoxiaoqiong 6730466230 fixed 代码质量分析api utf-8 2022-07-08 13:54:49 +08:00
xiaoxiaoqiong 94d6cac626 fixed 代码质量分析api utf-8 2022-07-08 13:42:36 +08:00
xiaoxiaoqiong 640d841242 fixed 代码质量分析api 2022-07-08 12:42:00 +08:00
xiaoxiaoqiong 6a0066b71c fixed 代码质量分析api 2022-07-08 12:35:08 +08:00
xiaoxiaoqiong aafbb8011b fixed 代码质量分析api 2022-07-08 11:49:07 +08:00
xiaoxiaoqiong 72a87ea909 fixed 代码质量分析api 2022-07-08 11:47:11 +08:00
xiaoxiaoqiong abf909d0e7 fixed 代码质量分析api 2022-07-08 11:46:07 +08:00
xiaoxiaoqiong a2321d5370 fixed 代码质量分析api 2022-07-08 11:43:23 +08:00
yystopf d154f0aecc 修复: readme匹配修复 2022-07-08 11:31:17 +08:00
xiaoxiaoqiong 68e4c8ad5a 代码质量分析api 2022-07-08 10:16:05 +08:00
xiaoxiaoqiong cf533f49cc fixed 接入sonar代码质量分析 2022-07-07 16:19:03 +08:00
yystopf 29d9410e42 修复: readme 匹配项目 2022-07-07 15:02:25 +08:00
xiaoxiaoqiong 2a9a835939 接入sonar代码质量分析 2022-07-07 13:50:21 +08:00
xiaoxiaoqiong d756646b1a 接入sonar代码质量分析 2022-07-07 13:41:14 +08:00
yystopf 3b62f09669 新增: 项目语言匹配并设置 2022-07-07 10:05:12 +08:00
yystopf 5864352049 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-07-07 09:24:18 +08:00
yystopf 1d6d48fc94 新增: pr 最后一次审核展示 2022-07-07 09:24:11 +08:00
xiaoxiaoqiong 484c91a581 fixed 同步gitea密码状态is_sync_pwd更新 2022-07-06 15:26:49 +08:00
yystopf 7b91ad37e5 修复 2022-06-30 16:38:46 +08:00
yystopf 9dbb9a42d6 新增: compare 支持sha对比 2022-06-30 16:33:09 +08:00
yystopf a608803aa8 新增: 合并请求审查需审查权限 2022-06-30 11:00:18 +08:00
yystopf 76cc185a1d 新增: 合并请求审核model 2022-06-29 17:16:29 +08:00
yystopf e76503ae26 fix: gitea client version 2022-06-26 09:06:11 +08:00
yystopf 50991f503f Merge pull request '新增oauth验证接口入口以及webhooks接口' (#14) from yystopf/forgeplus:hh_new_api into develop 2022-06-24 14:16:06 +08:00
yystopf acd031ca7d 修复: gemfile.lock 2022-06-24 14:14:57 +08:00
yystopf b59b76be4a 新增: 更新文档 2022-06-24 11:04:16 +08:00
yystopf 211581cef0 新增: webhooks文档使用新api地址 2022-06-24 09:26:01 +08:00
yystopf 7cad953413 新增: 用户项目列表支持多个参数 2022-06-23 18:16:57 +08:00
yystopf fe58175ee0 新增: webhooks使用token访问的接口 2022-06-23 16:14:00 +08:00
yystopf 5d0849a148 新增: 用户项目列表以及helper方法 2022-06-23 12:19:03 +08:00
yystopf d14bf35ca1 修复: gitea module命名重复问题 2022-06-23 11:33:11 +08:00
yystopf d7dd3901b8 新增: 项目详情 2022-06-22 19:08:14 +08:00
yystopf aefd6c08dc 新增: gitea-client引入以及路由定义 2022-06-21 17:39:11 +08:00
yystopf 3c2c742121 修复: 项目邀请链接生成多次返回提示 2022-06-21 11:37:41 +08:00
yystopf 9221e55752 修复: 邀请链接跳转用户已加入仓库的提示以及项目申请记录只创建一条 2022-06-21 11:18:14 +08:00
yystopf ba1fa8b685 修复: 项目邀请链接私有项目无法访问的情况 2022-06-20 16:37:59 +08:00
yystopf 29e3a55a0c 修复: 项目邀请链接私有项目无法访问的情况 2022-06-20 16:36:01 +08:00
xiaoxiaoqiong c426467a68 Merge remote-tracking branch 'origin/develop' into develop 2022-06-20 15:38:48 +08:00
xiaoxiaoqiong 9fe8df3c1b auth token过期时间7天 2022-06-20 15:38:42 +08:00
yystopf 531495429c 修复: 无法正常更改token长度的问题 2022-06-20 13:05:25 +08:00
yystopf bed2469014 fix: change error 2022-06-20 10:34:25 +08:00
yystopf 45b7f70d9f 新增:项目邀请链接增加时间限制,以及show_link接口新增 2022-06-20 10:16:39 +08:00
yystopf 1ebb6cb561 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-06-20 09:34:29 +08:00
yystopf 21ccedab9c 修复: doorkeeper 无法生成jwt token 2022-06-17 18:33:37 +08:00
xiaoxiaoqiong 8f45bcf959 Merge remote-tracking branch 'origin/develop' into develop 2022-06-17 17:02:31 +08:00
xiaoxiaoqiong 1f9caeece5 auth2接入 2022-06-17 17:02:24 +08:00
yystopf 5b4e6f4902 修复: 项目存在敏感词更新forked数量使用update_column 2022-06-17 14:19:41 +08:00
yystopf 3d0e323801 fix 2022-06-17 14:07:33 +08:00
yystopf f6fb50529c 新增: 邀请项目链接接口文档 2022-06-17 14:06:59 +08:00
yystopf c42f3d5928 新增:邀请用户链接生成 2022-06-17 14:06:59 +08:00
yystopf 42d2b51f7f 修复: 项目敏感词判定只在创建或者更新的时候 2022-06-16 13:42:17 +08:00
xiaoxiaoqiong 66c40d4791 fixed gitea接口返回判断 2022-06-15 11:16:08 +08:00
yystopf ce467736a7 修复: readme无法正常跳转目录的问题 2022-06-13 12:53:40 +08:00
yystopf 3e4dd90e17 修复: 用户名称展示时只去掉首末的空格 2022-06-08 10:46:55 +08:00
yystopf 8ae5587516 fix: search name use strip 2022-06-08 10:37:31 +08:00
xiaoxiaoqiong 0b543f7dda 通知邮件支持用户不存在时发送base_url 2022-06-01 16:08:20 +08:00
xiaoxiaoqiong a07204111a 通知邮件支持用户不存在时发送 2022-06-01 15:24:39 +08:00
yystopf 038df804c0 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-06-01 10:01:15 +08:00
yystopf 877948b0cd 新增: 通用消息模板后台创建 2022-06-01 10:00:57 +08:00
xiaoxiaoqiong 9a17c4865f 项目名称和描述增加敏感词检测 2022-06-01 09:27:18 +08:00
yystopf 9427db3e5a 新增: 通用消息模板 2022-05-31 16:34:08 +08:00
yystopf 81da71d478 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-05-30 14:50:23 +08:00
yystopf 169dc84e22 新增: 组织下项目需返回是否为公有项目字段 2022-05-30 14:50:12 +08:00
xiaoxiaoqiong d6fd85f536 Repository.url重写,导入项目时为空 2022-05-30 11:18:24 +08:00
xiaoxiaoqiong 6448a30e03 render_error替换成tip_exception 2022-05-30 10:25:43 +08:00
xiaoxiaoqiong 17fb43cea8 render_error替换成tip_exception 2022-05-30 10:24:39 +08:00
yystopf a6e50d2a25 修复 devops无法激活项目的问题 2022-05-27 10:48:20 +08:00
yystopf bd16ca476d 增加: 退出仓库api文档 2022-05-26 15:08:42 +08:00
yystopf 3dfe5d5d0f 修复 sync_gitea_params 2022-05-26 10:28:55 +08:00
yystopf eadc517a25 Merge pull request '同步develop最新代码' (#11) from yystopf/forgeplus:dev_educoder_sync into dev_educoder_sync 2022-05-25 17:27:42 +08:00
yystopf 1652e615be 同步develop最新代码 2022-05-25 17:25:21 +08:00
yystopf 2ea78b4f20 增加: 已登录用户对用户名、邮箱、电话进行校验 2022-05-25 11:47:35 +08:00
yystopf e36c7a4b6a 修复: 邮箱搜索不需要return 404 2022-05-24 18:09:36 +08:00
yystopf 8e8ec821f2 新增: 根据邮箱来获取用户 2022-05-24 17:26:10 +08:00
yystopf 5cf257c3cc Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-05-24 11:45:53 +08:00
yystopf aa18fe0ad4 修复: 转移项目时新接受者需要移除原来的权限角色 2022-05-24 11:44:05 +08:00
yystopf 1358c23ca6 fix: trasfer project member must remove old permission 2022-05-24 11:43:47 +08:00
yystopf c27cc063e6 add: new update user info simple and sync gitea 2022-05-24 10:59:52 +08:00
yystopf b0092487e6 fix: is readme use regex 2022-05-23 18:10:41 +08:00
yystopf fbebd0a623 fix 2022-05-23 09:27:09 +08:00
yystopf fe23c2afbf fix: replace content next if blank 2022-05-21 16:45:42 +08:00
yystopf 779c86108a fix: readme relative delay and replace_content 2022-05-20 17:00:02 +08:00
yystopf 83e43f0c97 add: new readme delay function 2022-05-20 11:43:22 +08:00
yystopf e545424b7d fix: project count use count 2022-05-19 15:54:38 +08:00
yystopf e237fd46f7 fix: not found user return 404 2022-05-18 11:37:23 +08:00
yystopf ca3c564529 fix: not found user return 404 2022-05-18 11:03:39 +08:00
yystopf 9cd18397f7 fix: import user login length required 2022-05-18 10:11:09 +08:00
xiaoxiaoqiong 7574071445 fix educoder to Gitlink::utils 2022-05-17 15:41:39 +08:00
yystopf 77620f7735 add: download and import user template xlsx 2022-05-16 16:59:25 +08:00
yystopf 50dc5cce5d fix 2022-05-07 14:06:48 +08:00
yystopf a6cee63d17 fix: change owner must fix foreign_key 2022-05-07 11:57:58 +08:00
yystopf 000197b0d1 fix: issues and pull_request length calculate 2022-05-06 17:25:34 +08:00
yystopf 6aa3321d17 fix: pr title description validate 2022-05-06 14:29:05 +08:00
yystopf d44308fc82 Merge branch 'develop' of https://gitlink.org.cn/Trustie/forgeplus into develop 2022-05-06 09:56:38 +08:00
yystopf ae7d0d1329 add: issues description and journals notes validate 2022-05-06 09:56:22 +08:00
xiaoxiaoqiong 55c74de8e4 fork_users加入组织 2022-05-05 10:41:02 +08:00
yystopf 7c88ea3d67 add: quit repo 2022-05-05 10:00:02 +08:00
yystopf f15cd44dff add: compare add paginate 2022-04-28 18:53:33 +08:00
yystopf 3abd6037d9 fix: show super description aways desplay 2022-04-28 11:12:16 +08:00
yystopf ec3a556a6f fix: default value for show super description 2022-04-28 11:03:42 +08:00
yystopf 793cbb372a Merge pull request '用户新增个人简介markdown编辑字段' (#8) from yystopf/forgeplus:hh_develop into develop 2022-04-28 10:53:06 +08:00
yystopf 396ac55429 add: user super description 2022-04-28 10:42:55 +08:00
yystopf 088c617cc1 add: name query for branches 2022-04-26 17:15:59 +08:00
xiaoxiaoqiong 17314b9164 take error utf8 2022-04-24 14:54:27 +08:00
xiaoxiaoqiong e6521ffdd2 utf8 2022-04-24 14:46:59 +08:00
yystopf fc3cf01d3f add: get user image action 2022-04-18 13:49:08 +08:00
yystopf 8f5bb0e2d9 fix: organization gitea_token read by associations 2022-04-18 10:52:48 +08:00
yystopf ef559b11a0 add: settings banner 2022-04-14 14:21:16 +08:00
yystopf 7f59f6739f fix 2022-04-12 11:20:19 +08:00
yystopf d6937f195c sync from develop 2022-04-12 11:01:09 +08:00
yystopf 2dfd69ae21 fix: response data result 2022-04-11 16:08:50 +08:00
yystopf 720c073f32 fix 2022-04-11 15:48:47 +08:00
yystopf 0076468a15 fix: trace client request data 2022-04-11 15:30:28 +08:00
yystopf 910ae48556 fix: exception to ex 2022-04-11 10:50:18 +08:00
yystopf eac7331070 fix: settings need prefix 2022-04-08 15:03:40 +08:00
yystopf 551f333261 fix 2022-04-08 14:52:56 +08:00
yystopf dec264d169 fix: sync user pwd 2022-04-07 16:28:40 +08:00
yystopf 7055974443 fix 2022-04-06 17:04:31 +08:00
yystopf 3536918938 fix 2022-03-31 16:18:16 +08:00
yystopf 56278b84f2 Merge pull request '导航增加动态用户数据获取' (#3) from hh_develop_navbar into develop 2022-03-31 16:08:36 +08:00
yystopf 882a50cf56 add: setting navbar varible 2022-03-31 16:04:48 +08:00
yystopf db4031b8bf fix 2022-03-31 14:24:47 +08:00
yystopf ecfc39565c fix 2022-03-31 13:18:38 +08:00
yystopf 9ced678f91 fix: user trace token 2022-03-31 13:03:54 +08:00
yystopf 7951ca28d5 Merge pull request '代码溯源service封装' (#2) from hh_develop into develop 2022-03-31 12:59:34 +08:00
yystopf 4bbb843edf add: trace users and get trace token 2022-03-31 12:54:39 +08:00
yystopf 747646cd9d add: trace client and api completed 2022-03-30 16:19:32 +08:00
yystopf d0d906d04c fix: readme image regex error rescued 2022-03-28 11:05:21 +08:00
yystopf bc639488aa fix: issues filter error 2022-03-22 10:45:14 +08:00
xiaoxiaoqiong 99fbecc834 gitea 请求延长到20分钟 2022-03-11 14:06:12 +08:00
yystopf 8d0a6d267a fix: download escape 2022-03-10 10:47:06 +08:00
yystopf b1c03ed5e8 fix: activity issues must only issue 2022-03-10 09:51:46 +08:00
yystopf ef12f85687 fix: create pull request valid merge_user_login 2022-03-07 11:17:10 +08:00
yystopf e120c4dac2 add: origin repository need open pr menu 2022-03-07 10:53:07 +08:00
yystopf e1cf2fcdeb fix: owner project not allow repeat name 2022-03-07 10:25:05 +08:00
yystopf c4f380873e fix: is public project attachment allow is not member read 2022-03-07 10:20:51 +08:00
yystopf 5cb2e55f74 add: organization users insist team nickname search 2022-03-07 09:58:05 +08:00
xiaoxiaoqiong 1dd3fbc69a fixed fork列表中存在组织时显示错误 2022-03-03 14:16:57 +08:00
yystopf b97c42d0ae fix: email logo 2022-03-02 10:33:35 +08:00
yystopf a2612d8009 fix 2022-02-24 18:09:11 +08:00
yystopf 2f2655525f fix: user rank user_id must exist 2022-02-24 18:07:18 +08:00
yystopf 1a93477635 fix: forked templete delay 2022-02-24 14:26:04 +08:00
yystopf 7c664addbd fix 2022-02-22 11:03:28 +08:00
yystopf c6b79f82bc fix: change password new version 2022-02-22 10:03:51 +08:00
xiaoxiaoqiong 4e3f9cc019 竞赛通知任务调度 2022-02-21 16:48:59 +08:00
xiaoxiaoqiong 5ed7b3e4ad 竞赛通知任务调度 2022-02-21 16:46:40 +08:00
xiaoxiaoqiong 4e2c46260c Merge remote-tracking branch 'origin/develop' into develop 2022-02-17 18:28:40 +08:00
xiaoxiaoqiong 9ba00b21c9 竞赛通知api 2022-02-17 18:28:32 +08:00
yystopf b11f5fd228 fix: cache rollback 2022-02-17 11:12:50 +08:00
yystopf 90dbe0730a fix: job not affect 2022-02-17 10:59:18 +08:00
yystopf d6633cb73a fix 2022-02-17 09:43:44 +08:00
yystopf a7a6abd22a fix: add rule for issue expire 2022-02-17 09:40:44 +08:00
yystopf c956667ce0 fix 2022-02-17 09:36:49 +08:00
yystopf 5701c818ed fix 2022-02-16 15:10:10 +08:00
yystopf 1ac2ddfaa5 fix: default setting for new template message 2022-02-16 11:46:34 +08:00
yystopf 4aac05d4e1 fix: build user template message data must fronted 2022-02-16 11:17:03 +08:00
yystopf 84b56bfcb8 fix: project milestone create notice template 2022-02-16 09:34:31 +08:00
yystopf 7485564bb9 add: template message platform varible 2022-02-15 18:30:13 +08:00
yystopf b5acf2308a Merge pull request '新增站内信以及里程碑完成度修复' (#299) from yystopf/forgeplus:develop into develop 2022-02-15 17:33:30 +08:00
yystopf e3f4313c80 fix 2022-02-15 17:29:13 +08:00
yystopf b223abe229 fix: init data 2022-02-15 17:26:46 +08:00
yystopf b3bede308c add: some new message rules 2022-02-15 17:13:06 +08:00
xiaoxiaoqiong 2f170a54c6 fixed setting编辑类型下拉选中 2022-02-15 17:10:57 +08:00
yystopf 22293572ac fix: issue changed about version callback 2022-02-14 17:02:59 +08:00
xiaoxiaoqiong 8720cab1e9 通过配置确定github克隆方式 2022-02-14 16:53:29 +08:00
yystopf 959c1e8d95 add: user profile setting message 2022-02-14 13:55:34 +08:00
yystopf 29dc3fff39 fix: migrate use old rule 2022-02-11 16:27:22 +08:00
yystopf 298abdd1b1 fix: devops organization bug 2022-02-10 14:08:14 +08:00
yystopf 26f87fba16 fix: edit transfer need change fork info 2022-01-25 14:26:11 +08:00
yystopf 1e9a32a93e fix: issues select count paginator 2022-01-25 11:39:06 +08:00
yystopf 2dc79052c2 fix: create project trend when status is closed 2022-01-25 10:34:20 +08:00
yystopf 380ca3a066 fix: projects query rule 2022-01-24 14:26:24 +08:00
yystopf 237b0afb08 fix: team user build use find or create by 2022-01-24 10:44:46 +08:00
yystopf e14936e820 fix: issues select count 2022-01-24 10:00:44 +08:00
yystopf 655c85851f Merge branch 'develop' of https://git.trustie.net/Gitlink/forgeplus into develop 2022-01-21 18:20:40 +08:00
yystopf 1c7e414aa4 fix: owner allow nil 2022-01-21 18:19:43 +08:00
yystopf 099fc52e00 fix: contributor error rescue 2022-01-21 18:19:34 +08:00
yystopf 490957ec3b fix: contributor error rescue 2022-01-21 18:19:27 +08:00
yystopf b1884d30d5 fix: detail result delay 2022-01-21 18:19:06 +08:00
xiaoxiaoqiong 98d8ee1fb6 Merge remote-tracking branch 'origin/develop' into develop 2022-01-21 15:50:41 +08:00
xiaoxiaoqiong 2ca77732c8 fixed 注册登录类型 2022-01-21 15:50:37 +08:00
yystopf 70d22f7c4f fix: create team overflow tip 2022-01-14 16:09:38 +08:00
yystopf 25d0e784bb fix: pr assignee info use array 2022-01-14 16:00:59 +08:00
yystopf 5d45b24299 fix: devops return result changed 2022-01-14 10:55:53 +08:00
yystopf b6b8befde7 fix: pr update must edit assignee login 2022-01-14 10:55:01 +08:00
yystopf f33b9559cd fix: download delay 2022-01-13 11:24:27 +08:00
yystopf f9bc2258ec fix: migrate repo github use another addr 2022-01-11 10:48:29 +08:00
1293 changed files with 38898 additions and 113239 deletions

2
.gitignore vendored
View File

@ -37,11 +37,13 @@ public/react/yarn.lock
# Ignore react node_modules # Ignore react node_modules
public/system/* public/system/*
public/react/*
/public/react/.cache /public/react/.cache
/public/react/node_modules/ /public/react/node_modules/
/public/react/config/stats.json /public/react/config/stats.json
/public/react/stats.json /public/react/stats.json
/public/react/.idea/* /public/react/.idea/*
/public/react/build/*
/public/h5build /public/h5build
/public/npm-debug.log /public/npm-debug.log

272
Gemfile
View File

@ -1,130 +1,142 @@
source 'https://gems.ruby-china.com' source 'https://gems.ruby-china.com'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 5.2.0' gem 'rails', '~> 5.2.0'
gem 'mysql2', '>= 0.4.4', '< 0.6.0' gem 'mysql2', '>= 0.4.4', '< 0.6.0'
gem 'puma', '~> 3.11' gem 'puma', '~> 3.11'
gem 'sass-rails', '~> 5.0' gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0' gem 'uglifier', '>= 1.3.0'
# gem 'coffee-rails', '~> 4.2' # gem 'coffee-rails', '~> 4.2'
gem 'turbolinks', '~> 5' gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5' gem 'jbuilder', '~> 2.5'
gem 'groupdate', '~> 4.1.0' gem 'groupdate', '~> 4.1.0'
gem 'chartkick' gem 'chartkick'
gem 'grape-entity', '~> 0.7.1' gem 'grape-entity', '~> 0.7.1'
gem 'kaminari', '~> 1.1', '>= 1.1.1' gem 'kaminari', '~> 1.1', '>= 1.1.1'
gem 'bootsnap', '>= 1.1.0', require: false gem 'bootsnap', '>= 1.1.0', require: false
gem 'chinese_pinyin' gem 'chinese_pinyin'
gem 'rack-cors' gem 'rack-cors'
gem 'redis-rails' gem 'redis-rails'
gem 'roo-xls' gem 'roo-xls'
gem 'simple_xlsx_reader' gem 'simple_xlsx_reader'
gem 'rubyzip' gem 'rubyzip'
gem 'spreadsheet' gem 'spreadsheet'
gem 'ruby-ole' gem 'ruby-ole'
# 导出为xlsx # 导出为xlsx
gem 'axlsx', '~> 3.0.0.pre' gem 'axlsx', '~> 3.0.0.pre'
gem 'axlsx_rails', '~> 0.5.2' gem 'axlsx_rails', '~> 0.5.2'
gem 'oauth2' gem 'oauth2'
#导出为pdf #导出为pdf
gem 'pdfkit' gem 'pdfkit'
gem 'wkhtmltopdf-binary' gem 'wkhtmltopdf-binary'
# gem 'request_store' # gem 'request_store'
#gem 'iconv' #gem 'iconv'
# markdown 转html # markdown 转html
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'rqrcode', '~> 0.10.1' gem 'rqrcode', '~> 0.10.1'
gem 'rqrcode_png' gem 'rqrcode_png'
gem 'acts-as-taggable-on', '~> 6.0' gem 'acts-as-taggable-on', '~> 6.0'
# a tree structure # a tree structure
gem 'ancestry' gem 'ancestry'
gem 'acts_as_list' gem 'acts_as_list'
gem 'omniauth-cas' gem 'omniauth-cas'
# profiler Middleware # profiler Middleware
gem 'rack-mini-profiler' gem 'rack-mini-profiler'
# object-based searching # object-based searching
gem 'ransack' gem 'ransack'
group :development, :test do group :development, :test do
gem 'rspec-rails', '~> 3.8' gem 'rspec-rails', '~> 3.8'
end end
group :development do group :development do
gem 'prettier' gem 'prettier'
gem 'rubocop', '~> 0.52.0' gem 'rubocop', '~> 0.52.0'
gem 'solargraph', '~> 0.38.0' gem 'solargraph', '~> 0.38.0'
gem 'awesome_print' gem 'awesome_print'
gem 'web-console', '>= 3.3.0' gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2' gem 'listen', '>= 3.0.5', '< 3.2'
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
gem "annotate", "~> 2.6.0" gem "annotate", "~> 2.6.0"
end end
group :test do group :test do
gem 'capybara', '>= 2.15', '< 4.0' gem 'capybara', '>= 2.15', '< 4.0'
gem 'selenium-webdriver' gem 'selenium-webdriver'
gem 'chromedriver-helper' gem 'chromedriver-helper'
end end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
#编码检测 #编码检测
gem 'rchardet', '~> 1.8' gem 'rchardet', '~> 1.8'
# http client # http client
gem 'faraday', '~> 0.15.4' gem 'faraday', '~> 0.15.4'
# view # view
gem 'active_decorator' gem 'active_decorator'
gem 'bootstrap', '~> 4.3.1' gem 'bootstrap', '~> 4.3.1'
gem 'jquery-rails' gem 'jquery-rails'
gem 'simple_form' gem 'simple_form'
gem 'font-awesome-sass', '4.7.0' gem 'font-awesome-sass', '4.7.0'
# i18n # i18n
gem 'rails-i18n', '~> 5.1' gem 'rails-i18n', '~> 5.1'
# job # job
gem 'sidekiq' gem 'sidekiq'
gem 'sinatra' gem 'sinatra'
gem "sidekiq-cron", "~> 1.1" gem "sidekiq-cron", "~> 1.1"
# batch insert # batch insert
gem 'bulk_insert' gem 'bulk_insert'
# elasticsearch # elasticsearch
gem 'searchkick' gem 'searchkick'
gem 'aasm' gem 'aasm'
gem 'enumerize' gem 'enumerize'
gem 'diffy' gem 'diffy'
gem 'deep_cloneable', '~> 3.0.0' gem 'deep_cloneable', '~> 3.0.0'
# oauth2 # oauth2
gem 'omniauth', '~> 1.9.0' gem 'omniauth', '~> 1.9.0'
gem 'omniauth-oauth2', '~> 1.6.0' gem 'omniauth-oauth2', '~> 1.6.0'
gem "omniauth-github"
# global var gem "omniauth-rails_csrf_protection"
gem 'request_store' gem 'omniauth-gitee', '~> 1.0.0'
gem "omniauth-wechat-oauth2"
# 敏感词汇
gem 'harmonious_dictionary', '~> 0.0.1' # global var
gem 'request_store'
gem 'parallel', '~> 1.19', '>= 1.19.1'
# 敏感词汇
gem 'letter_avatar' gem 'harmonious_dictionary', '~> 0.0.1'
gem 'parallel', '~> 1.19', '>= 1.19.1'
gem 'letter_avatar'
gem 'jwt'
gem 'doorkeeper'
gem 'doorkeeper-jwt'
gem 'gitea-client', '~> 0.10.6'

View File

@ -106,6 +106,10 @@ GEM
activerecord (>= 3.1.0, < 7) activerecord (>= 3.1.0, < 7)
diff-lcs (1.3) diff-lcs (1.3)
diffy (3.3.0) diffy (3.3.0)
doorkeeper (5.5.1)
railties (>= 5)
doorkeeper-jwt (0.4.1)
jwt (>= 2.1)
e2mmap (0.1.0) e2mmap (0.1.0)
elasticsearch (7.5.0) elasticsearch (7.5.0)
elasticsearch-api (= 7.5.0) elasticsearch-api (= 7.5.0)
@ -450,6 +454,8 @@ DEPENDENCIES
chromedriver-helper chromedriver-helper
deep_cloneable (~> 3.0.0) deep_cloneable (~> 3.0.0)
diffy diffy
doorkeeper
doorkeeper-jwt
enumerize enumerize
faraday (~> 0.15.4) faraday (~> 0.15.4)
font-awesome-sass (= 4.7.0) font-awesome-sass (= 4.7.0)
@ -458,6 +464,7 @@ DEPENDENCIES
harmonious_dictionary (~> 0.0.1) harmonious_dictionary (~> 0.0.1)
jbuilder (~> 2.5) jbuilder (~> 2.5)
jquery-rails jquery-rails
jwt
kaminari (~> 1.1, >= 1.1.1) kaminari (~> 1.1, >= 1.1.1)
letter_avatar letter_avatar
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)

143
README.md
View File

@ -1,30 +1,17 @@
# GitLink - CCF开源创新服务平台 Trustie (确实)是一个以大众化协同开发、开放式资源共享、持续性可信评估为核心机理,面向高校创新实践的在线协作平台。
GitLink确实开源是中国计算机学会CCF官方指定的开源创新服务平台旨在以“为开源创新服务”为使命以“成为开源创新的汇聚地”为愿景秉承“创新、开放、协作、共享”的价值观致力于为大规模开源开放协同创新助力赋能打造创新成果孵化和新工科人才培养的开源创新生态 ## 特性
<center> - 软件创作与生产深度融合的软件开发环境体系结构 软件自由创作和工程生产的高效衔接,适于软件开发中群体智慧的有效汇聚。
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/gitlink.png?raw=true" width=80% />
</center>
## 特色功能
- **分布式协作开发**基于Git打造分布式代码托管环境提供免费公、私有代码仓库支持在线文件编辑、代码分支管理、协作贡献统计、代码仓库复刻Fork、贡献合并请求PR、群智贡献审阅等功能让您的项目在这里健康、快速的成长 - 构件化协同开发环境的可扩展运行框架多样化工具的集成和联动,形成了强动态扩展能力的平台框架。
- **一站式过程管理**提供易修Issue、里程碑、通知提醒、标签归档等多样化任务管理工具支持各类开发任务的发布、指派与跟踪同时提供在线Wiki文档、组织多粒度管理等功能为您搭建一站式的项目过程管理环境让您的团队协作更高效、过程更透明 - “互联网即资源库”的全新软件复用模式 成长式软件资源管理系统,实现了分散资源的知识融合、资源的可持续增长和有效复用。
- **高效流水线运维**融合DevOps思想提供轻量级的工作流引擎Engine打通编码、测试、构建、部署等开发运维环节支持自定义配置、代码静态扫描、构建自动触发、容器镜像托管等功能同时支持接入第三方运维工具让您的代码更加快速、可靠地形成高质量的产品 ## 部署
- **多层次代码分析**提供软件软代码和芯片RTL代码的溯源分析、文件级和组件级许可证识别及风险分析、输入性开源漏洞检测和加固建议支持分析结果的多层次可视化展示帮助您实施有效开源治理厘清代码引用链发现并消除漏洞感染链为安全合规的开源引用保驾护航
- **多维度用户画像**:实时采集和分析平台中的各类开源资源数据,搭建多维度用户画像评估系统,提供开发活动统计、贡献度日历、用户能力建模、角色与专业定位分析等功能,让您在个人主页展示开发动态与创新能力!
## 第三方组件
## 部署流程 ### Depends Versions
### 依赖库
* Ruby 2.4.5 * Ruby 2.4.5
@ -36,19 +23,22 @@ GitLink确实开源是中国计算机学会CCF官方指定的开源
* imagemagick * imagemagick
### 步骤 ### Steps
1克隆稳定版本 #### 1. 克隆稳定版本
``` ```
git clone -b standalone https://git.trustie.net/jasder/forgeplus.git git clone -b standalone https://git.trustie.net/jasder/forgeplus.git
``` ```
2安装依赖包 #### 2. 安装依赖包
```bash ```bash
cd forgeplus && bundle install cd forgeplus && bundle install
``` ```
3配置初始化文件进入项目根目录执行以下命令 #### 3. 配置初始化文件
进入项目根目录执行一下命令:
```bash ```bash
cp config/configuration.yml.example config/configuration.yml cp config/configuration.yml.example config/configuration.yml
cp config/database.yml.example config/database.yml cp config/database.yml.example config/database.yml
@ -56,7 +46,11 @@ touch config/redis.yml
touch config/elasticsearch.yml touch config/elasticsearch.yml
``` ```
4配置数据库数据库配置信息请查看/config/database.yml文件项目默认采用mysql数据库, 如需更改,请自行修改配置信息,默认配置如下 #### 4. 配置数据库
数据库配置信息请查看/config/database.yml文件
项目默认采用mysql数据库, 如需更改,请自行修改配置信息,
默认配置如下:
```bash ```bash
default: &default default: &default
adapter: mysql2 adapter: mysql2
@ -66,13 +60,14 @@ default: &default
password: 123456 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平台api受限暂时推荐从forge平台获取gitea部署文件进行部署https://forgeplus.trustie.net/projects/Trustie/gitea-binary**
-- 部署gitea服务并注册root账户 **配置gitea服务步骤**
1. 部署gitea服务并注册root账户
-- 修改forge平台的 config/configuration.yml中的gitea服务指向地址 2. 修改forge平台的 config/configuration.yml中的gitea服务指向地址
```ruby ```ruby
gitea: gitea:
@ -82,9 +77,10 @@ gitea:
base_url: '/api/v1' base_url: '/api/v1'
``` ```
6安装redis环境请自行搜索各平台如何安装部署redis环境 #### 6. 安装redis环境
**请自行搜索各平台如何安装部署redis环境**
7安装imagemagick插件 #### 7. 安装imagemagick插件
- Mac OS X - Mac OS X
```bash ```bash
brew install imagemagick ghostscript brew install imagemagick ghostscript
@ -95,95 +91,88 @@ gitea:
sudo apt-get install -y imagemagick sudo apt-get install -y imagemagick
``` ```
8创建数据库开发环境为development 生成环境为production #### 8. 创建数据库
**开发环境为development 生成环境为production**
```bash ```bash
rails db:create RAILS_ENV=development rails db:create RAILS_ENV=development
``` ```
9导入数据表结构 #### 9. 导入数据表结构
```bash ```bash
bundle exec rake sync_table_structure:import_csv bundle exec rake sync_table_structure:import_csv
``` ```
10执行migrate迁移文件开发环境为development 生成环境为production #### 10. 执行migrate迁移文件
**开发环境为development 生成环境为production**
```bash ```bash
rails db:migrate RAILS_ENV=development rails db:migrate RAILS_ENV=development
``` ```
11clone前端代码将前端代码克隆到public/react目录下目录结构应该是: public/react/build #### 11. clone前端代码
**将前端代码克隆到public/react目录下目录结构应该是: public/react/build**
```bash ```bash
git clone -b standalone https://git.trustie.net/jasder/build.git git clone -b standalone https://git.trustie.net/jasder/build.git
``` ```
12启动redis(此处以macOS系统为例) #### 12. 启动redis(此处已mac系统为例)
```bash ```bash
redis-server& redis-server&
``` ```
13启动sidekiq开发环境为development 生成环境为production #### 13. 启动sidekiq
**开发环境为development 生成环境为production**
```bash ```bash
bundle exec sidekiq -C config/sidekiq.yml -e production -d bundle exec sidekiq -C config/sidekiq.yml -e production -d
``` ```
14启动rails服务 #### 14. 启动rails服务
```bash ```bash
rails s rails s
``` ```
15浏览器访问在浏览器中输入如下地址访问 #### 15. 浏览器访问
在浏览器中输入如下地址访问:
```bash ```bash
http://localhost:3000/ http://localhost:3000/
``` ```
16其他说明通过页面注册以第一个用户为平台管理员用户 #### 16. 其他说明
通过页面注册都第一个用户为平台管理员用户
## 页面展示 ## 页面展示
- 项目列表 - 代码库
![](docs/figs/code.png?raw=true)
<center>
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/project_list.png?raw=true" width=50% />
</center>
- 代码仓库
<center>
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/repo.png?raw=true" width=50% />
</center>
- 任务管理 - 任务管理
![](docs/figs/issue_manage.png?raw=true)
<center>
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/issues.png?raw=true" width=50% />
</center>
- 合并请求
<center>
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/PR.png?raw=true" width=50% />
</center>
- 引擎配置
<center>
<img src="https://code.gitlink.org.cn/young/forgeplus/raw/branch/standalone/docs/figs/engine.png?raw=true" width=50% />
</center>
- 任务查看
![](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提交贡献在您贡献时请遵循以下流程
1. Fork 项目 1. Fork 项目
2. 创建本地分支(git checkout -b my-new-feature) 2. 创建本地分支(git checkout -b my-new-feature)
3. 提交更改 (git commit -am 'Add some feature') 3. 提交更改 (git commit -am 'Add some feature')
4. 推送到分支 (git push origin my-new-feature) 4. 推送到分支 (git push origin my-new-feature)
5. 向源项目的 **develop** 分支发起 Pull Request 5. 向源项目的 **develop** 分支发起 Pull Request
#### 指导文档 ## License
- [API文档](https://www.gitlink.org.cn/docs/api)
- [Git常用命令](https://git-scm.com/)
## 许可证协议

View File

@ -1,136 +1,137 @@
//= require rails-ujs //= require rails-ujs
//= require activestorage //= require activestorage
//= require turbolinks //= require turbolinks
//= require jquery3 //= require jquery3
//= require popper //= require popper
//= require bootstrap-sprockets //= require bootstrap-sprockets
//= require jquery.validate.min //= require jquery.validate.min
//= require additional-methods.min //= require additional-methods.min
//= require bootstrap-notify //= require bootstrap-notify
//= require jquery.cookie.min //= require jquery.cookie.min
//= require select2 //= require select2
//= require moment.min //= require moment.min
//= require jquery.cxselect //= require jquery.cxselect
//= require bootstrap-datepicker //= require bootstrap-datepicker
//= require bootstrap-datetimepicker //= require bootstrap-datetimepicker
//= require bootstrap.viewer //= require bootstrap.viewer
//= require jquery.mloading //= require bootstrap/bootstrap-toggle
//= require jquery-confirm.min //= require jquery.mloading
//= require common //= require jquery-confirm.min
//= require common
//= require echarts
//= require codemirror/lib/codemirror //= require echarts
//= require codemirror/mode/shell/shell //= require codemirror/lib/codemirror
//= require editormd/editormd //= require codemirror/mode/shell/shell
//= require editormd/languages/zh-tw //= require editormd/editormd
//= require dragula/dragula //= require editormd/languages/zh-tw
//= require dragula/dragula
//= require_tree ./i18n
//= require_tree ./admins //= require_tree ./i18n
//= require_tree ./admins
$.ajaxSetup({
beforeSend: function(xhr) { $.ajaxSetup({
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')); beforeSend: function(xhr) {
} xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
}); }
});
// ******** select2 global config ********
$.fn.select2.defaults.set('theme', 'bootstrap4'); // ******** select2 global config ********
$.fn.select2.defaults.set('language', 'zh-CN'); $.fn.select2.defaults.set('theme', 'bootstrap4');
$.fn.select2.defaults.set('language', 'zh-CN');
Turbolinks.setProgressBarDelay(200);
Turbolinks.setProgressBarDelay(200);
$.notifyDefaults({
type: 'success', $.notifyDefaults({
z_index: 9999, type: 'success',
delay: 2000 z_index: 9999,
}); delay: 2000
});
function show_success_flash(){
$.notify({ function show_success_flash(){
message: '操作成功' $.notify({
},{ message: '操作成功'
type: 'success' },{
}); type: 'success'
} });
}
$(document).on('turbolinks:load', function(){
$('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' }); $(document).on('turbolinks:load', function(){
$('[data-toggle="popover"]').popover(); $('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' });
$('[data-toggle="popover"]').popover();
// 图片查看大图
$('img.preview-image').bootstrapViewer(); // 图片查看大图
$('img.preview-image').bootstrapViewer();
// flash alert提示框自动关闭
if($('.admin-alert-container .alert').length > 0){ // flash alert提示框自动关闭
setTimeout(function(){ if($('.admin-alert-container .alert').length > 0){
$('.admin-alert-container .alert:not(.alert-danger)').alert('close'); setTimeout(function(){
}, 2000); $('.admin-alert-container .alert:not(.alert-danger)').alert('close');
setTimeout(function(){ }, 2000);
$('.admin-alert-container .alert.alert-danger').alert('close'); setTimeout(function(){
}, 5000); $('.admin-alert-container .alert.alert-danger').alert('close');
} }, 5000);
}); }
});
$(document).on("turbolinks:before-cache", function () {
$('[data-toggle="tooltip"]').tooltip('hide'); $(document).on("turbolinks:before-cache", function () {
$('[data-toggle="popover"]').popover('hide'); $('[data-toggle="tooltip"]').tooltip('hide');
}); $('[data-toggle="popover"]').popover('hide');
// var progressBar = new Turbolinks.ProgressBar(); });
// var progressBar = new Turbolinks.ProgressBar();
// $(document).on('ajax:send', function(event){
// console.log('ajax send', event); // $(document).on('ajax:send', function(event){
// progressBar.setValue(0) // console.log('ajax send', event);
// progressBar.show() // progressBar.setValue(0)
// }); // progressBar.show()
// // });
// $(document).on('ajax:complete', function(event){ //
// console.log('ajax complete', event); // $(document).on('ajax:complete', function(event){
// progressBar.setValue(1) // console.log('ajax complete', event);
// progressBar.hide() // 分页时不触发,奇怪 // progressBar.setValue(1)
// }); // progressBar.hide() // 分页时不触发,奇怪
// $(document).on('ajax:success', function(event){ // });
// console.log('ajax success', event); // $(document).on('ajax:success', function(event){
// }); // console.log('ajax success', event);
// $(document).on('ajax:error', function(event){ // });
// console.log('ajax error', event); // $(document).on('ajax:error', function(event){
// }); // console.log('ajax error', event);
// });
$(function () {
}); $(function () {
});
$(document).on('turbolinks:load', function() {
$(document).on('turbolinks:load', function() {
$('.logo-item-left').on("change", 'input[type="file"]', function () {
var $fileInput = $(this); $('.logo-item-left').on("change", 'input[type="file"]', function () {
var file = this.files[0]; var $fileInput = $(this);
var imageType = /image.*/; var file = this.files[0];
if (file && file.type.match(imageType)) { var imageType = /image.*/;
var reader = new FileReader(); if (file && file.type.match(imageType)) {
reader.onload = function () { var reader = new FileReader();
var $box = $fileInput.parent(); reader.onload = function () {
$box.find('img').attr('src', reader.result).css('display', 'block'); var $box = $fileInput.parent();
$box.addClass('has-img'); $box.find('img').attr('src', reader.result).css('display', 'block');
}; $box.addClass('has-img');
reader.readAsDataURL(file); };
} else { reader.readAsDataURL(file);
} } else {
}); }
});
$('.attachment-item-left').on("change", 'input[type="file"]', function () {
var $fileInput = $(this); $('.attachment-item-left').on("change", 'input[type="file"]', function () {
var file = this.files[0]; var $fileInput = $(this);
var imageType = /image.*/; var file = this.files[0];
if (file && file.type.match(imageType)) { var imageType = /image.*/;
var reader = new FileReader(); if (file && file.type.match(imageType)) {
reader.onload = function () { var reader = new FileReader();
var $box = $fileInput.parent(); reader.onload = function () {
$box.find('img').attr('src', reader.result).css('display', 'block'); var $box = $fileInput.parent();
$box.addClass('has-img'); $box.find('img').attr('src', reader.result).css('display', 'block');
}; $box.addClass('has-img');
reader.readAsDataURL(file); };
} else { reader.readAsDataURL(file);
} } else {
}); }
});
}) })

View File

@ -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);

View File

@ -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

View File

@ -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"}

View File

@ -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);

View File

@ -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

View File

@ -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"}

View File

@ -8,6 +8,7 @@
@import "jquery.mloading"; @import "jquery.mloading";
@import "jquery-confirm.min"; @import "jquery-confirm.min";
@import "bootstrap-datetimepicker.min"; @import "bootstrap-datetimepicker.min";
@import "bootstrap/bootstrap-toggle.min";
@import "codemirror/lib/codemirror"; @import "codemirror/lib/codemirror";
@import "editormd/css/editormd.min"; @import "editormd/css/editormd.min";
@ -203,4 +204,14 @@ input.form-control {
color: #23272B; color: #23272B;
font-size: 1rem; 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;
} }

View File

@ -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; }

View File

@ -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}

View File

@ -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; }

View File

@ -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}

View File

@ -1,10 +1,42 @@
class AccountsController < ApplicationController class AccountsController < ApplicationController
before_action :require_login, only: [:login_check, :simple_update]
include ApplicationHelper 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 def index
render json: session render json: session
end end
# 为了同步平台上未注册gitea的用户
def gitea_register
user = User.find_by(login: sync_gitea_params[:login])
Users::SyncGiteaForm.new(sync_gitea_params.merge(user: user)).validate!
return render_error("该用户已同步协作平台") if user.gitea_token.present? && user.gitea_uid.present?
result = create_gitea_user!(user, sync_gitea_params[:login], sync_gitea_params[:email], sync_gitea_params[:password])
result[:user] ? render_ok : render_error(result[:message])
rescue Exception => e
uid_logger_error(e.message)
tip_exception(-1, e.message)
end
# 其他平台同步注册的用户 # 其他平台同步注册的用户
def remote_register def remote_register
Register::RemoteForm.new(remote_register_params).validate! Register::RemoteForm.new(remote_register_params).validate!
@ -92,7 +124,9 @@ class AccountsController < ApplicationController
sync_params = { sync_params = {
password: params[:password].to_s, password: params[:password].to_s,
email: @user.mail email: @user.mail,
login_name: @user.login,
source_id: 0
} }
interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params)
@ -138,17 +172,15 @@ class AccountsController < ApplicationController
tip_exception(-1, interactor.error) tip_exception(-1, interactor.error)
end end
rescue Register::BaseForm::EmailError => e rescue Register::BaseForm::EmailError => e
render_result(-2, e.message) render_error(-2, e.message)
rescue Register::BaseForm::LoginError => e rescue Register::BaseForm::LoginError => e
render_result(-3, e.message) render_error(-3, e.message)
rescue Register::BaseForm::PhoneError => e rescue Register::BaseForm::PhoneError => e
render_result(-4, e.message) render_error(-4, e.message)
rescue Register::BaseForm::PasswordFormatError => e rescue Register::BaseForm::PasswordFormatError => e
render_result(-5, e.message) render_error(-5, e.message)
rescue Register::BaseForm::PasswordConfirmationError => e
render_result(-7, e.message)
rescue Register::BaseForm::VerifiCodeError => e rescue Register::BaseForm::VerifiCodeError => e
render_result(-6, e.message) render_error(-6, e.message)
rescue Exception => e rescue Exception => e
Gitea::User::DeleteService.call(user.login) unless user.nil? Gitea::User::DeleteService.call(user.login) unless user.nil?
uid_logger_error(e.message) uid_logger_error(e.message)
@ -158,7 +190,7 @@ class AccountsController < ApplicationController
# 用户登录 # 用户登录
def login def login
Users::LoginForm.new(login_params).validate! Users::LoginForm.new(account_params).validate!
@user = User.try_to_login(params[:login], params[:password]) @user = User.try_to_login(params[:login], params[:password])
return normal_status(-2, "错误的账号或密码") if @user.blank? return normal_status(-2, "错误的账号或密码") if @user.blank?
@ -192,7 +224,9 @@ class AccountsController < ApplicationController
sync_params = { sync_params = {
password: params[:password].to_s, password: params[:password].to_s,
email: @user.mail email: @user.mail,
login_name: @user.login,
source_id: 0
} }
interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params)
@ -207,27 +241,28 @@ class AccountsController < ApplicationController
# 忘记密码 # 忘记密码
def reset_password def reset_password
begin begin
Accounts::ResetPasswordForm.new(reset_password_params).validate! 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 = find_user user.password, user.password_confirmation = params[:new_password], params[:new_password_confirmation]
return render_error('未找到相关账号') if user.blank? ActiveRecord::Base.transaction do
user.save!
user = Accounts::ResetPasswordService.call(user, reset_password_params) LimitForbidControl::UserLogin.new(user).clear
LimitForbidControl::UserLogin.new(user).clear if user.save! end
sucess_status
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 rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)
@ -251,7 +286,7 @@ class AccountsController < ApplicationController
def set_autologin_cookie(user) def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin") 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)
cookie_options = { cookie_options = {
:value => token.value, :value => token.value,
@ -313,6 +348,11 @@ class AccountsController < ApplicationController
Register::CheckColumnsForm.new(check_params).validate! Register::CheckColumnsForm.new(check_params).validate!
render_ok render_ok
end end
def login_check
Register::LoginCheckColumnsForm.new(check_params.merge(user: current_user)).validate!
render_ok
end
private private
@ -352,11 +392,15 @@ class AccountsController < ApplicationController
{ login: pre + code, email: email, phone: phone } { login: pre + code, email: email, phone: phone }
end end
def sync_gitea_params
params.permit(:login, :email, :password)
end
def user_params def user_params
params.require(:user).permit(:login, :email, :phone) params.require(:user).permit(:login, :email, :phone)
end end
def login_params def account_params
params.require(:account).permit(:login, :password) params.require(:account).permit(:login, :password)
end end
@ -365,20 +409,14 @@ class AccountsController < ApplicationController
end end
def register_params def register_params
params.permit(:login, :namespace, :password, :password_confirmation, :code) params.permit(:login, :namespace, :password, :code)
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 end
def remote_register_params def remote_register_params
params.permit(:username, :email, :password, :platform) params.permit(:username, :email, :password, :platform)
end end
def simple_update_params
params.permit(:username, :email, :password, :platform)
end
end end

View File

@ -1,10 +1,33 @@
class Admins::DashboardsController < Admins::BaseController class Admins::DashboardsController < Admins::BaseController
def index def index
@active_user_count = User.where(last_login_on: today).count # 用户活跃数
@weekly_active_user_count = User.where(last_login_on: current_week).count day_user_ids = CommitLog.where(created_at: today).pluck(:user_id).uniq
@month_active_user_count = User.where(last_login_on: current_month).count 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 end
def month_active_user def month_active_user
@ -42,10 +65,14 @@ class Admins::DashboardsController < Admins::BaseController
end end
def current_week 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 end
def current_month 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
end end

View File

@ -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

View File

@ -2,7 +2,7 @@ class Admins::ImportUsersController < Admins::BaseController
def create def create
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile) 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) render_ok(result)
rescue Admins::ImportUserService::Error => ex rescue Admins::ImportUserService::Error => ex
render_error(ex) render_error(ex)

View File

@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController
before_action :get_template, only: [:edit, :update, :destroy] before_action :get_template, only: [:edit, :update, :destroy]
def index def index
message_templates = MessageTemplate.group(:type).count.keys message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result
@message_templates = kaminari_array_paginate(message_templates) @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 end
def edit def edit
@ -31,7 +47,9 @@ class Admins::MessageTemplatesController < Admins::BaseController
private private
def message_template_params 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 end
def get_template def get_template

View File

@ -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

View File

@ -1,6 +1,6 @@
class Admins::ProjectIgnoresController < Admins::BaseController class Admins::ProjectIgnoresController < Admins::BaseController
before_action :set_ignore, only: [:edit,:update, :destroy,:show] 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 def index
sort_by = Ignore.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' 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) @project_ignore = Ignore.new(ignore_params)
if @project_ignore.save! if @project_ignore.save
redirect_to admins_project_ignores_path redirect_to admins_project_ignores_path
flash[:success] = "创建成功" flash[:success] = "创建成功"
else else
render :new redirect_to admins_project_ignores_path
flash[:danger] = "创建失败" flash[:danger] = @project_ignore.errors.full_messages.join(",")
end end
end end
@ -58,8 +58,8 @@ class Admins::ProjectIgnoresController < Admins::BaseController
redirect_to admins_project_ignores_path redirect_to admins_project_ignores_path
flash[:success] = "更新成功" flash[:success] = "更新成功"
else else
render :edit redirect_to admins_project_ignores_path
flash[:danger] = "更新失败" flash[:danger] = @project_ignore.errors.full_messages.join(",")
end end
end end
@ -98,23 +98,23 @@ class Admins::ProjectIgnoresController < Admins::BaseController
params.require(:ignore).permit(:name,:content) params.require(:ignore).permit(:name,:content)
end end
def validate_params # def validate_params
name = params[:ignore][:name] # name = params[:ignore][:name]
if name.blank? # if name.blank?
flash[:danger] = "名称不允许为空" # flash[:danger] = "名称不允许为空"
redirect_to admins_project_ignores_path # redirect_to admins_project_ignores_path
elsif check_ignore_present?(name) && @project_ignore.blank? # elsif check_ignore_present?(name) && @project_ignore.blank?
flash[:danger] = "创建失败:名称已存在" # flash[:danger] = "创建失败:名称已存在"
redirect_to admins_project_ignores_path # redirect_to admins_project_ignores_path
end # end
end # end
def check_ignore_present?(name) # def check_ignore_present?(name)
return true if name.blank? # return true if name.blank?
name_downcase = name.downcase # name_downcase = name.downcase
name_upcase = name.upcase # name_upcase = name.upcase
name_first_big = name.capitalize # name_first_big = name.capitalize
Ignore.exists?(name: name_downcase) || Ignore.exists?(name: name_upcase) || Ignore.exists?(name: name_first_big) # Ignore.exists?(name: name_downcase) || Ignore.exists?(name: name_upcase) || Ignore.exists?(name: name_first_big)
end # end
end end

View File

@ -27,17 +27,18 @@ class Admins::ProjectLanguagesController < Admins::BaseController
flash[:success] = '创建成功' flash[:success] = '创建成功'
else else
redirect_to admins_project_languages_path redirect_to admins_project_languages_path
flash[:danger] = '创建失败' flash[:danger] = @project_language.errors.full_messages.join(",")
end end
end end
def update def update
if @project_language.update_attribute(:name, @name) @project_language.attributes = {name: @name}
if @project_language.save
redirect_to admins_project_languages_path redirect_to admins_project_languages_path
flash[:success] = '更新成功' flash[:success] = '更新成功'
else else
redirect_to admins_project_languages_path redirect_to admins_project_languages_path
flash[:success] = '更新失败' flash[:danger] = @project_language.errors.full_messages.join(",")
end end
end end

View File

@ -1,6 +1,6 @@
class Admins::ProjectLicensesController < Admins::BaseController class Admins::ProjectLicensesController < Admins::BaseController
before_action :set_license, only: [:edit,:update, :destroy,:show] 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 def index
sort_by = License.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_at' 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 # position: max_position
# } # }
@project_license = License.new(license_params) @project_license = License.new(license_params)
if @project_license.save
if @project_license.save!
redirect_to admins_project_licenses_path redirect_to admins_project_licenses_path
flash[:success] = "创建成功" flash[:success] = "创建成功"
else else
render :new redirect_to admins_project_licenses_path
flash[:danger] = "创建失败" flash[:danger] = @project_license.errors.full_messages.join(",")
end end
end end
@ -54,12 +53,13 @@ class Admins::ProjectLicensesController < Admins::BaseController
# permissions: permissions.to_s, # permissions: permissions.to_s,
# limitations: limitations.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 redirect_to admins_project_licenses_path
flash[:success] = "更新成功" flash[:success] = "更新成功"
else else
render :edit render admins_project_licenses_path
flash[:danger] = "更新失败" flash[:danger] = @project_license.errors.full_messages.join(",")
end end
end end
@ -98,23 +98,23 @@ class Admins::ProjectLicensesController < Admins::BaseController
params.require(:license).permit(:name,:content) params.require(:license).permit(:name,:content)
end end
def validate_params # def validate_params
name = params[:license][:name] # name = params[:license][:name]
if name.blank? # if name.blank?
flash[:danger] = "名称不允许为空" # flash[:danger] = "名称不允许为空"
redirect_to admins_project_licenses_path # redirect_to admins_project_licenses_path
elsif check_license_present?(name) && @project_license.blank? # elsif check_license_present?(name) && @project_license.blank?
flash[:danger] = "创建失败:名称已存在" # flash[:danger] = "创建失败:名称已存在"
redirect_to admins_project_licenses_path # redirect_to admins_project_licenses_path
end # end
end # end
def check_license_present?(name) # def check_license_present?(name)
return true if name.blank? # return true if name.blank?
name_downcase = name.downcase # name_downcase = name.downcase
name_upcase = name.upcase # name_upcase = name.upcase
name_first_big = name.capitalize # name_first_big = name.capitalize
License.exists?(name: name_downcase) || License.exists?(name: name_upcase) || License.exists?(name: name_first_big) # License.exists?(name: name_downcase) || License.exists?(name: name_upcase) || License.exists?(name: name_first_big)
end # end
end end

View File

@ -1,57 +0,0 @@
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

View File

@ -1,57 +0,0 @@
class Admins::Topic::BannersController < Admins::Topic::BaseController
before_action :find_banner, only: [:edit, :update, :destroy]
def index
@banners = paginate(::Topic::Banner)
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)
end
end

View File

@ -1,11 +0,0 @@
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

View File

@ -1,57 +0,0 @@
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

View File

@ -1,57 +0,0 @@
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

View File

@ -1,57 +0,0 @@
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

View File

@ -1,57 +0,0 @@
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

View File

@ -1,57 +0,0 @@
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,81 @@
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 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
end

View File

@ -26,8 +26,7 @@ class ApplicationController < ActionController::Base
end end
DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z) DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
OPENKEY = Rails.application.config_for(:configuration)['sign_key'] || "79e33abd4b6588941ab7622aed1e67e8" OPENKEY = "79e33abd4b6588941ab7622aed1e67e8"
helper_method :current_user, :base_url helper_method :current_user, :base_url
@ -175,11 +174,11 @@ class ApplicationController < ActionController::Base
end end
def require_profile_completed def require_profile_completed
tip_exception(411, "请完善资料后再操作") unless User.current.profile_is_completed? # tip_exception(411, "请完善资料后再操作") unless User.current.profile_is_completed?
end end
def require_user_profile_completed(user) def require_user_profile_completed(user)
tip_exception(412, "请用户完善资料后再操作") unless user.profile_is_completed? # tip_exception(412, "请用户完善资料后再操作") unless user.profile_is_completed?
end end
# 异常提醒 # 异常提醒
@ -249,40 +248,56 @@ class ApplicationController < ActionController::Base
#return if params[:controller] == "main" #return if params[:controller] == "main"
# Find the current user # Find the current user
#Rails.logger.info("current_laboratory is #{current_laboratory} domain is #{request.subdomain}") #Rails.logger.info("current_laboratory is #{current_laboratory} domain is #{request.subdomain}")
User.current = find_current_user if request.headers["Authorization"].present? && request.headers["Authorization"].start_with?('Bearer')
uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous")) tip_exception(401, "请登录后再操作!") unless valid_doorkeeper_token?
if @doorkeeper_token.present?
# 开放课程通过链接访问的用户 # client方法对接需要一直带着用户标识uid
if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank? if @doorkeeper_token.resource_owner_id.blank?
content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}" tip_exception(-1, "缺少用户标识!") if params[:uid].nil?
User.current = User.find(params[:uid])
if Digest::MD5.hexdigest(content) == params[:chinaoocKey] else
user = open_class_user User.current = User.find_by(id: @doorkeeper_token.resource_owner_id)
if user
start_user_session(user)
set_autologin_cookie(user)
end end
User.current = user
end end
end else
# if !User.current.logged? && Rails.env.development? User.current = find_current_user
# User.current = User.find 1 uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
# end
# 开放课程通过链接访问的用户
if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
user = open_class_user
if user
start_user_session(user)
set_autologin_cookie(user)
end
User.current = user
end
end
if !User.current.logged? && Rails.env.development?
user = User.find 1
User.current = user
start_user_session(user)
end
# 测试版前端需求 # 测试版前端需求
logger.info("subdomain:#{request.subdomain}") # logger.info("subdomain:#{request.subdomain}")
if request.subdomain != "www" # if request.subdomain != "www"
if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除 # if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除
User.current = User.find 81403 # User.current = User.find 81403
elsif params[:debug] == 'student' # elsif params[:debug] == 'student'
User.current = User.find 8686 # User.current = User.find 8686
elsif params[:debug] == 'admin' # elsif params[:debug] == 'admin'
logger.info "@@@@@@@@@@@@@@@@@@@@@@ debug mode....." # logger.info "@@@@@@@@@@@@@@@@@@@@@@ debug mode....."
user = User.find 36480 # user = User.find 36480
User.current = user # User.current = user
cookies.signed[:user_id] = user.id # cookies.signed[:user_id] = user.id
end # end
# end
end end
# User.current = User.find 81403 # User.current = User.find 81403
end end
@ -336,6 +351,11 @@ class ApplicationController < ActionController::Base
@message = message @message = message
end end
# 实训等对应的仓库地址
def repo_ip_url(repo_path)
"#{edu_setting('git_address_ip')}/#{repo_path}"
end
def repo_url(repo_path) def repo_url(repo_path)
"#{edu_setting('git_address_domain')}/#{repo_path}" "#{edu_setting('git_address_domain')}/#{repo_path}"
end end
@ -604,7 +624,7 @@ class ApplicationController < ActionController::Base
def kaminari_paginate(relation) def kaminari_paginate(relation)
limit = params[:limit] || params[:per_page] limit = params[:limit] || params[:per_page]
limit = (limit.to_i.zero? || limit.to_i > 20) ? 20 : limit.to_i limit = (limit.to_i.zero? || limit.to_i > 15) ? 15 : limit.to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i page = params[:page].to_i.zero? ? 1 : params[:page].to_i
relation.page(page).per(limit) relation.page(page).per(limit)
@ -612,7 +632,7 @@ class ApplicationController < ActionController::Base
def kaminari_array_paginate(relation) def kaminari_array_paginate(relation)
limit = params[:limit] || params[:per_page] limit = params[:limit] || params[:per_page]
limit = (limit.to_i.zero? || limit.to_i > 20) ? 20 : limit.to_i limit = (limit.to_i.zero? || limit.to_i > 15) ? 15 : limit.to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i page = params[:page].to_i.zero? ? 1 : params[:page].to_i
Kaminari.paginate_array(relation).page(page).per(limit) Kaminari.paginate_array(relation).page(page).per(limit)
@ -681,7 +701,7 @@ class ApplicationController < ActionController::Base
@project, @owner = Project.find_with_namespace(namespace, id) @project, @owner = Project.find_with_namespace(namespace, id)
if @project and current_user.can_read_project?(@project) if @project and (current_user.can_read_project?(@project) || controller_path == "projects/project_invite_links")
logger.info "########### has project and can read project" logger.info "########### has project and can read project"
@project @project
# elsif @project && current_user.is_a?(AnonymousUser) # elsif @project && current_user.is_a?(AnonymousUser)
@ -689,9 +709,15 @@ class ApplicationController < ActionController::Base
# @project = nil if !@project.is_public? # @project = nil if !@project.is_public?
# render_forbidden and return # render_forbidden and return
else else
logger.info "###########project not found" if @project.present?
@project = nil logger.info "########### has project and but can't read project"
render_not_found and return @project = nil
render_forbidden and return
else
logger.info "###########project not found"
@project = nil
render_not_found and return
end
end end
@project @project
end end
@ -743,10 +769,37 @@ class ApplicationController < ActionController::Base
render json: exception.tip_json render json: exception.tip_json
end end
def render_parameter_missing
render json: { status: -1, message: '参数缺失' }
end
def set_export_cookies def set_export_cookies
cookies[:fileDownload] = true cookies[:fileDownload] = true
end end
# 149课程的评审用户数据创建包含创建课堂学生
def open_class_user
user = User.find_by(login: "OpenClassUser")
unless user
ActiveRecord::Base.transaction do
user_params = {status: 1, login: "OpenClassUser", lastname: "开放课程",
nickname: "开放课程", professional_certification: 1, certification: 1, grade: 0,
password: "12345678", phone: "11122223333", profile_completed: 1}
user = User.create!(user_params)
UserExtension.create!(user_id: user.id, gender: 0, school_id: 3396, :identity => 1, :student_id => "openclassuser") # 3396
subject = Subject.find_by(id: 149)
if subject
subject.courses.each do |course|
CourseMember.create!(course_id: course.id, role: 3, user_id: user.id) if !course.course_members.exists?(user_id: user.id)
end
end
end
end
user
end
# 记录热门搜索关键字 # 记录热门搜索关键字
def record_search_keyword def record_search_keyword
keyword = params[:keyword].to_s.strip keyword = params[:keyword].to_s.strip

View File

@ -1,261 +1,258 @@
#coding=utf-8 #coding=utf-8
# #
# 文件上传 # 文件上传
class AttachmentsController < ApplicationController class AttachmentsController < ApplicationController
before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file] before_action :require_login, :check_auth, except: [:show, :preview_attachment, :get_file]
before_action :find_file, only: %i[show destroy] before_action :find_file, only: %i[show destroy]
before_action :attachment_candown, only: [:show] before_action :attachment_candown, only: [:show]
skip_before_action :check_sign, only: [:show, :create] skip_before_action :check_sign, only: [:show, :create]
include ApplicationHelper include ApplicationHelper
def show def show
# 1. 优先跳到cdn # 1. 优先跳到cdn
# 2. 如果没有cdnsend_file # 2. 如果没有cdnsend_file
if @file.cloud_url.present? if @file.cloud_url.present?
update_downloads(@file) update_downloads(@file)
redirect_to @file.cloud_url and return redirect_to @file.cloud_url and return
end end
type_attachment = params[:disposition] || "attachment" type_attachment = params[:disposition] || "attachment"
if type_attachment == "inline" if type_attachment == "inline"
send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf'
elsif type_attachment == "MP4" elsif type_attachment == "MP4"
send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true
else else
send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream')
end end
update_downloads(@file) update_downloads(@file)
end end
def get_file def get_file
normal_status(-1, "参数缺失") if params[:download_url].blank? normal_status(-1, "参数缺失") if params[:download_url].blank?
url = URI.encode(params[:download_url].to_s.gsub("http:", "https:")) 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) if url.starts_with?(base_url) && !url.starts_with?("#{base_url}/repo")
domain = Gitea.gitea_config[:domain] domain = GiteaService.gitea_config[:domain]
api_url = Gitea.gitea_config[:base_url] api_url = GiteaService.gitea_config[:base_url]
url = url.split(base_url)[1].gsub("api", "repos").gsub('?filepath=', '/').gsub('&', '?') 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 request_url = [domain, api_url, url, "?ref=#{params[:ref]}&access_token=#{current_user&.gitea_token}"].join
response = Faraday.get(request_url) response = Faraday.get(request_url)
filename = url.to_s.split("/").pop() filename = url.to_s.split("/").pop()
else else
response = Faraday.get(url) response = Faraday.get(url)
filename = params[:download_url].to_s.split("/").pop() filename = params[:download_url].to_s.split("/").pop()
end end
send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment') send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment')
end end
def create def create
# 1. 本地存储 # 1. 本地存储
# 2. 上传到云 # 2. 上传到云
begin begin
upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称 upload_file = params["file"] || params["#{params[:file_param_name]}"]# 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}") uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}")
raise "未上传文件" unless upload_file raise "未上传文件" unless upload_file
folder = file_storage_directory folder = file_storage_directory
raise "存储目录未定义" unless folder.present? raise "存储目录未定义" unless folder.present?
month_folder = current_month_folder month_folder = current_month_folder
save_path = File.join(folder, month_folder) save_path = File.join(folder, month_folder)
ext = file_ext(upload_file.original_filename) ext = file_ext(upload_file.original_filename)
local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext) local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext)
content_type = upload_file.content_type.presence || 'application/octet-stream' 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 = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type)
remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端 remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端
logger.info "local_path: #{local_path}" logger.info "local_path: #{local_path}"
logger.info "remote_path: #{remote_path}" logger.info "remote_path: #{remote_path}"
disk_filename = local_path[save_path.size + 1, local_path.size] disk_filename = local_path[save_path.size + 1, local_path.size]
#存数据库 #存数据库
# #
@attachment = Attachment.where(disk_filename: disk_filename, @attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id, author_id: current_user.id,
cloud_url: remote_path).first cloud_url: remote_path).first
if @attachment.blank? if @attachment.blank?
@attachment = Attachment.new @attachment = Attachment.new
@attachment.filename = upload_file.original_filename @attachment.filename = upload_file.original_filename
@attachment.disk_filename = local_path[save_path.size + 1, local_path.size] @attachment.disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment.filesize = upload_file.tempfile.size @attachment.filesize = upload_file.tempfile.size
@attachment.content_type = content_type @attachment.content_type = content_type
@attachment.digest = digest @attachment.digest = digest
@attachment.author_id = current_user.id @attachment.author_id = current_user.id
@attachment.disk_directory = month_folder @attachment.disk_directory = month_folder
@attachment.cloud_url = remote_path @attachment.cloud_url = remote_path
@attachment.save! @attachment.save!
else else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}" logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
end end
render_json render_json
rescue => e rescue => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)
end end
end end
def destroy def destroy
begin begin
@file_path = absolute_path(local_path(@file)) @file_path = absolute_path(local_path(@file))
#return normal_status(403, "") unless @file.author == current_user #return normal_status(403, "") unless @file.author == current_user
@file.destroy! @file.destroy!
delete_file(@file_path) delete_file(@file_path)
normal_status("删除成功") normal_status("删除成功")
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
# 附件为视频时,点击播放 # 附件为视频时,点击播放
def preview_attachment def preview_attachment
attachment = Attachment.find_by(id: params[:id]) attachment = Attachment.find_by(id: params[:id])
dir_path = "#{Rails.root}/public/preview" dir_path = "#{Rails.root}/public/preview"
Dir.mkdir(dir_path) unless Dir.exist?(dir_path) Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
if params[:status] == "preview" if params[:status] == "preview"
if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/") if system("cp -r #{absolute_path(local_path(attachment))} #{dir_path}/")
render json: {status: 1, url: "/preview/#{attachment.disk_filename}"} render json: {status: 1, url: "/preview/#{attachment.disk_filename}"}
else else
normal_status(-1, "出现错误,请稍后重试") normal_status(-1, "出现错误,请稍后重试")
end end
else else
if system("rm -rf #{dir_path}/#{attachment.disk_filename}") if system("rm -rf #{dir_path}/#{attachment.disk_filename}")
normal_status(1, "操作成功") normal_status(1, "操作成功")
else else
normal_status(-1, "出现错误,请稍后重试") normal_status(-1, "出现错误,请稍后重试")
end end
end end
end end
private private
def find_file def find_file
@file = @file =
if params[:type] == 'history' if params[:type] == 'history'
AttachmentHistory.find params[:id] AttachmentHistory.find params[:id]
else else
Attachment.find params[:id] Attachment.find params[:id]
end end
end end
def delete_file(file_path) def delete_file(file_path)
File.delete(file_path) if File.exist?(file_path) File.delete(file_path) if File.exist?(file_path)
end end
def current_month_folder def current_month_folder
date = Time.now date = Time.now
"#{date.year}/#{date.month.to_s.rjust(2, '0')}" "#{date.year}/#{date.month.to_s.rjust(2, '0')}"
end end
def file_ext(file_name) def file_ext(file_name)
ext = '' ext = ''
exts = file_name.split(".") exts = file_name.split(".")
if exts.size > 1 if exts.size > 1
ext = ".#{exts.last}" ext = ".#{exts.last}"
end end
ext ext
end end
def file_save_to_local(save_path, temp_file, ext) def file_save_to_local(save_path, temp_file, ext)
unless Dir.exists?(save_path) unless Dir.exists?(save_path)
FileUtils.mkdir_p(save_path) ##不成功这里会抛异常 FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
end end
digest = md5_file(temp_file) digest = md5_file(temp_file)
digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}" digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}"
local_file_path = File.join(save_path, digest) + ext local_file_path = File.join(save_path, digest) + ext
save_temp_file(temp_file, local_file_path) save_temp_file(temp_file, local_file_path)
[local_file_path, digest] [local_file_path, digest]
end end
def save_temp_file(temp_file, save_file_path) def save_temp_file(temp_file, save_file_path)
File.open(save_file_path, 'wb') do |f| File.open(save_file_path, 'wb') do |f|
temp_file.rewind temp_file.rewind
while (buffer = temp_file.read(8192)) while (buffer = temp_file.read(8192))
f.write(buffer) f.write(buffer)
end end
end end
end end
def md5_file(temp_file) def md5_file(temp_file)
md5 = Digest::MD5.new md5 = Digest::MD5.new
temp_file.rewind temp_file.rewind
while (buffer = temp_file.read(8192)) while (buffer = temp_file.read(8192))
md5.update(buffer) md5.update(buffer)
end end
md5.hexdigest md5.hexdigest
end end
def file_save_to_ucloud(path, file, content_type) def file_save_to_ucloud(path, file, content_type)
ufile = Gitlink::Ufile.new( ufile = Gitlink::Ufile.new(
ucloud_public_key: edu_setting('public_key'), ucloud_public_key: edu_setting('public_key'),
ucloud_private_key: edu_setting('private_key'), ucloud_private_key: edu_setting('private_key'),
ucloud_public_read: true, ucloud_public_read: true,
ucloud_public_bucket: edu_setting('public_bucket'), ucloud_public_bucket: edu_setting('public_bucket'),
ucloud_public_bucket_host: edu_setting('public_bucket_host'), ucloud_public_bucket_host: edu_setting('public_bucket_host'),
ucloud_public_cdn_host: edu_setting('public_cdn_host'), ucloud_public_cdn_host: edu_setting('public_cdn_host'),
) )
File.open(file) do |f| File.open(file) do |f|
ufile.put(path, f, 'Content-Type' => content_type) ufile.put(path, f, 'Content-Type' => content_type)
end end
edu_setting('public_cdn_host') + "/" + path edu_setting('public_cdn_host') + "/" + path
end end
def attachment_candown def attachment_candown
unless current_user.admin? || current_user.business? unless current_user.admin? || current_user.business?
candown = true candown = true
unless params[:type] == 'history' if @file.container
if @file.container && current_user.logged? if @file.container.is_a?(Issue)
if @file.container.is_a?(Issue) project = @file.container.project
course = @file.container.project candown = project.is_public || (current_user.logged? && project.member?(current_user))
candown = course.member?(current_user) elsif @file.container.is_a?(Journal)
elsif @file.container.is_a?(Journal) project = @file.container.issue.project
course = @file.container.issue.project candown = project.is_public || (current_user.logged? && project.member?(current_user))
candown = course.member?(current_user) else
else project = nil
course = nil end
end tip_exception(403, "您没有权限进入") if project.present? && !candown
tip_exception(403, "您没有权限进入") if course.present? && !candown end
tip_exception(403, "您没有权限进入") if @file.container.is_a?(ApplyUserAuthentication) end
end end
end
end def send_file_with_range(path, options = {})
end logger.info("########request.headers: #{request.headers}")
logger.info("########request.headers: #{File.exist?(path)}")
def send_file_with_range(path, options = {})
logger.info("########request.headers: #{request.headers}") if File.exist?(path)
logger.info("########request.headers: #{File.exist?(path)}") size = File.size(path)
logger.info("########request.headers: #{request.headers}")
if File.exist?(path) if !request.headers["Range"]
size = File.size(path) status_code = 200 # 200 OK
logger.info("########request.headers: #{request.headers}") offset = 0
if !request.headers["Range"] length = File.size(path)
status_code = 200 # 200 OK else
offset = 0 status_code = 206 # 206 Partial Content
length = File.size(path) bytes = Rack::Utils.byte_ranges(request.headers, size)[0]
else offset = bytes.begin
status_code = 206 # 206 Partial Content length = bytes.end - bytes.begin
bytes = Rack::Utils.byte_ranges(request.headers, size)[0] end
offset = bytes.begin response.header["Accept-Ranges"] = "bytes"
length = bytes.end - bytes.begin response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes
end response.header["status"] = status_code
response.header["Accept-Ranges"] = "bytes"
response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes send_data IO.binread(path, length, offset), options
response.header["status"] = status_code else
raise ActionController::MissingFile, "Cannot read file #{path}."
send_data IO.binread(path, length, offset), options end
else end
raise ActionController::MissingFile, "Cannot read file #{path}."
end end
end
end

View File

@ -8,7 +8,7 @@ class Ci::BaseController < ApplicationController
namespace = params[:owner] namespace = params[:owner]
id = params[:repo] || params[:id] 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 end
def load_all_repo def load_all_repo

View File

@ -14,12 +14,12 @@ class Ci::CloudAccountsController < Ci::BaseController
def create def create
flag, msg = check_bind_cloud_account! flag, msg = check_bind_cloud_account!
return render_error(msg) if flag === true return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@cloud_account = bind_account! @cloud_account = bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
@ -27,17 +27,17 @@ class Ci::CloudAccountsController < Ci::BaseController
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def activate def activate
return render_error('请先认证') unless current_user.ci_certification? return tip_exception('请先认证') unless current_user.ci_certification?
begin begin
@cloud_account = Ci::CloudAccount.find params[:id] @cloud_account = Ci::CloudAccount.find params[:id]
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if @repo if @repo
return render_error('该项目已经激活') if @repo.repo_active? return tip_exception('该项目已经激活') if @repo.repo_active?
@repo.activate!(@project) @repo.activate!(@project)
else else
@repo = Ci::Repo.auto_create!(@ci_user, @project) @repo = Ci::Repo.auto_create!(@ci_user, @project)
@ -50,7 +50,7 @@ class Ci::CloudAccountsController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end
@ -59,39 +59,39 @@ class Ci::CloudAccountsController < Ci::BaseController
def bind def bind
flag, msg = check_bind_cloud_account! flag, msg = check_bind_cloud_account!
return render_error(msg) if flag === true return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@cloud_account = bind_account! @cloud_account = bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def trustie_bind def trustie_bind
account = params[:account].to_s 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! 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 ActiveRecord::Base.transaction do
@cloud_account = trustie_bind_account! @cloud_account = trustie_bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def unbind def unbind
@ -107,18 +107,18 @@ class Ci::CloudAccountsController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def oauth_grant def oauth_grant
password = params[:password].to_s 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 oauth = current_user.oauths.last
return render_error("服务器出小差了.") if oauth.blank? return tip_exception("服务器出小差了.") if oauth.blank?
result = gitea_oauth_grant!(password, oauth) 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) current_user.set_drone_step!(User::DEVOPS_CERTIFICATION)
end end

View File

@ -30,7 +30,7 @@ class Ci::PipelinesController < Ci::BaseController
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size
if size > 0 if size > 0
render_error("#{params[:branch]}分支已经存在流水线!") tip_exception("#{params[:branch]}分支已经存在流水线!")
return return
end end
pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name],owner: params[:owner], 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}) render_ok({id: pipeline.id})
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
# 在代码库创建文件 # 在代码库创建文件
@ -81,6 +81,7 @@ class Ci::PipelinesController < Ci::BaseController
repo_branch: pipeline.branch, repo_branch: pipeline.branch,
repo_config: pipeline.file_name repo_config: pipeline.file_name
} }
Rails.logger.info("########create_params===#{create_params.to_json}")
repo = Ci::Repo.create_repo(create_params) repo = Ci::Repo.create_repo(create_params)
repo repo
end end
@ -118,7 +119,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def destroy def destroy
@ -132,7 +133,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def content def content
@ -182,7 +183,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage def update_stage
@ -192,7 +193,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def delete_stage def delete_stage
@ -205,7 +206,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage_index(pipeline_id, show_index, diff) def update_stage_index(pipeline_id, show_index, diff)
@ -229,7 +230,7 @@ class Ci::PipelinesController < Ci::BaseController
unless steps.empty? unless steps.empty?
steps.each do |step| steps.each do |step|
unless step[:template_id] unless step[:template_id]
render_error('请选择模板!') tip_exception('请选择模板!')
return return
end end
if !step[:id] if !step[:id]
@ -246,7 +247,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def create_stage_step def create_stage_step
@ -262,7 +263,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage_step def update_stage_step
@ -279,7 +280,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def delete_stage_step def delete_stage_step
@ -289,6 +290,6 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end

View File

@ -30,19 +30,19 @@ class Ci::ProjectsController < Ci::BaseController
@file = interactor.result @file = interactor.result
render_result(1, "更新成功") render_result(1, "更新成功")
else else
render_error(interactor.error) tip_exception(interactor.error)
end end
end end
def activate def activate
return render_error('你还未认证') unless current_user.ci_certification? return tip_exception('你还未认证') unless current_user.ci_certification?
begin begin
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if @repo 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) @repo.activate!(@project)
return render_ok
else else
@repo = Ci::Repo.auto_create!(@ci_user, @project) @repo = Ci::Repo.auto_create!(@ci_user, @project)
@ci_user.update_column(:user_syncing, false) @ci_user.update_column(:user_syncing, false)
@ -55,12 +55,12 @@ class Ci::ProjectsController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end
def deactivate def deactivate
return render_error('该项目已经取消激活') if !@repo.repo_active? return tip_exception('该项目已经取消激活') if !@repo.repo_active?
@project.update_column(:open_devops, false) @project.update_column(:open_devops, false)
@repo.deactivate_repos! @repo.deactivate_repos!

View File

@ -20,14 +20,14 @@ class Ci::SecretsController < Ci::BaseController
if result["id"] if result["id"]
render_ok render_ok
else else
render_error(result["message"]) tip_exception(result["message"])
end end
else else
result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret
if result["id"] if result["id"]
render_ok render_ok
else else
render_error(result["message"]) tip_exception(result["message"])
end end
end 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 Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], {name: name}).delete_secret
render_ok render_ok
else else
render_error("参数名不能为空") tip_exception("参数名不能为空")
end end
rescue Exception => ex rescue Exception => ex
render_ok render_ok
end end
def ci_drone_url 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 user&.ci_cloud_account.drone_url
end end

View File

@ -50,7 +50,7 @@ class Ci::TemplatesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update def update
@ -63,7 +63,7 @@ class Ci::TemplatesController < Ci::BaseController
) )
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def destroy def destroy
@ -73,7 +73,7 @@ class Ci::TemplatesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
#======流水线模板查询=====# #======流水线模板查询=====#

View File

@ -1,138 +0,0 @@
#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

View File

@ -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

View File

@ -6,26 +6,37 @@ class CompareController < ApplicationController
end end
def show def show
load_compare_params if params[:type] == "sha"
compare load_compare_params
@merge_status, @merge_message = get_merge_message @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 end
private private
def get_merge_message
def get_merge_message
if @base.blank? || @head.blank? if @base.blank? || @head.blank?
return -2, "请选择分支" return -2, "请选择分支"
else else
return -2, "目标仓库未开启合并请求PR功能" unless @project.has_menu_permission("pulls")
if @head.include?(":") if @head.include?(":")
fork_project = @project.forked_projects.joins(:owner).where(users: {login: @head.to_s.split("/")[0]}).take fork_project = @project.forked_projects.joins(:owner).where(users: { login: @head.to_s.split("/")[0] }).take
return -2, "请选择正确的仓库" unless fork_project.present? return -2, "请选择正确的仓库" unless fork_project.present?
@exist_pullrequest = @project.pull_requests.where(is_original: true, head: @head.to_s.split(":")[1], base: @base, status: 0, fork_project_id: fork_project.id).take @exist_pullrequest = @project.pull_requests.where(is_original: true, head: @head.to_s.split(":")[1], base: @base, status: 0, fork_project_id: fork_project.id).take
else else
@exist_pullrequest = @project.pull_requests.where(is_original: false, head: @base, base: @head, status: 0).take @exist_pullrequest = @project.pull_requests.where(is_original: false, head: @base, base: @head, status: 0).last
end end
if @exist_pullrequest.present? if @exist_pullrequest.present?
return -2, "在这些分支之间的合并请求已存在:<a href='/#{@owner.login}/#{@project.identifier}/pulls/#{@exist_pullrequest.id}'>#{@exist_pullrequest.try(:title)}</a>" return -2, "在这些分支之间的合并请求已存在:<a href='/#{@owner.login}/#{@project.identifier}/pulls/#{@exist_pullrequest.id}'>#{@exist_pullrequest.try(:title)}</a>"
else else
if @compare_result["Commits"].blank? && @compare_result["Diff"].blank? if @compare_result["Commits"].blank? && @compare_result["Diff"].blank?
return -2, "分支内容相同,无需创建合并请求" return -2, "分支内容相同,无需创建合并请求"
end end
@ -50,6 +61,14 @@ class CompareController < ApplicationController
end end
def gitea_compare(base, head) def gitea_compare(base, head)
Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, CGI.escape(base), CGI.escape(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
end end

View File

@ -18,15 +18,15 @@ module Acceleratorable
end end
def accelerator_domain def accelerator_domain
Gitea.gitea_config[:accelerator]["domain"] GiteaService.gitea_config[:accelerator]["domain"]
end end
def accelerator_username def accelerator_username
Gitea.gitea_config[:accelerator]["access_key_id"] GiteaService.gitea_config[:accelerator]["access_key_id"]
end end
def config_accelerator? def config_accelerator?
Gitea.gitea_config[:accelerator].present? GiteaService.gitea_config[:accelerator].present?
end end
def is_foreign_url?(clone_addr) def is_foreign_url?(clone_addr)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -160,9 +160,9 @@ module Ci::CloudAccountManageable
state = SecureRandom.hex(8) state = SecureRandom.hex(8)
# redirect_uri eg: # 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 # 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") # redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
clientId = client_id(oauth) # 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}" grant_url = "#{@cloud_account.drone_url}/login"
logger.info "[gitea] grant_url: #{grant_url}" logger.info "[gitea] grant_url: #{grant_url}"
conn = Faraday.new(url: grant_url) do |req| conn = Faraday.new(url: grant_url) do |req|
@ -188,6 +188,7 @@ module Ci::CloudAccountManageable
response = conn.get response = conn.get
logger.info "[drone] response headers: #{response.headers}" logger.info "[drone] response headers: #{response.headers}"
# true
response.headers['location'].include?('error') ? false : true response.headers['location'].include?('error') ? false : true
end end

View File

@ -35,15 +35,15 @@ module LaboratoryHelper
# my_projects: "/users/#{current_user.try(:login)}/projects", # my_projects: "/users/#{current_user.try(:login)}/projects",
# my_projects: "https://www.trustie.net/users/#{current_user.try(:login)}/user_projectlist", # my_projects: "https://www.trustie.net/users/#{current_user.try(:login)}/user_projectlist",
{ {
new_syllabuses: "https://www.trustie.net/syllabuses/new", new_syllabuses: "https://forge.educoder.net/syllabuses/new",
new_course: "https://www.trustie.net/courses/new", new_course: "https://forge.educoder.net/courses/new",
edit_account: "https://www.trustie.net/my/account", edit_account: "https://forge.educoder.net/my/account",
my_courses: "https://www.trustie.net/users/#{current_user.try(:login)}/user_courselist", my_courses: "https://forge.educoder.net/users/#{current_user.try(:login)}/user_courselist",
my_projects: "/users/#{current_user.try(:login)}/projects", my_projects: "/users/#{current_user.try(:login)}/projects",
my_organ: "https://www.trustie.net/users/#{current_user.try(:login)}/user_organizations", my_organ: "https://forge.educoder.net/users/#{current_user.try(:login)}/user_organizations",
default_url: Rails.application.config_for(:configuration)['platform_url'], default_url: "https://www.educoder.net/",
tiding_url: "https://www.trustie.net/users/#{current_user.try(:login)}/user_messages", tiding_url: "https://www.educoder.net/users/#{current_user.try(:login)}/user_tidings",
register_url: "https://www.trustie.net/login?login=false" register_url: "https://www.educoder.net/user/register"
} }
end end
end end

View File

@ -11,7 +11,7 @@ module LoginHelper
def set_autologin_cookie(user) def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin") 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}" Rails.logger.info "###### def set_autologin_cookie and get_or_create_permanent_login_token result: #{token&.value}"
cookie_options = { cookie_options = {
@ -108,10 +108,15 @@ module LoginHelper
def sync_pwd_to_gitea!(user, hash={}) def sync_pwd_to_gitea!(user, hash={})
return true if user.is_sync_pwd? 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)) interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params.merge(hash))
if interactor.success? if interactor.success?
Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea success _########" Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea success _########"
user.update_column(:is_sync_pwd, true)
true true
else else
Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea fail!: #{interactor.error}" Rails.logger.info "########_ login is #{user.login} sync_pwd_to_gitea fail!: #{interactor.error}"

View File

@ -1,61 +1,82 @@
module RegisterHelper module RegisterHelper
extend ActiveSupport::Concern 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} result = {message: nil, user: nil}
email = email.blank? ? "#{username}@example.org" : email
user = User.new(admin: false, login: username, mail: email, type: "User") user = User.new(admin: false, login: username, mail: email, type: "User")
user.password = password user.password = password
user.platform = platform user.platform = platform
user.phone = phone if phone.present?
user.nickname = nickname if nickname.present?
if need_edit_info if need_edit_info
user.need_edit_info user.need_edit_info
else else
user.activate user.activate
end end
return unless user.valid? return unless user.valid?
create_gitea_user!(user, username, email, password)
end
def create_gitea_user!(forge_user, username, email, password=random_password)
interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password}) interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password})
if interactor.success? result = {}
gitea_user = interactor.result if interactor.success? || interactor.result[:message].to_s.include?("已被注册")
result = Gitea::User::GenerateTokenService.call(username, password) gitea_user = interactor.result[:message].to_s.include?("已被注册") ? Gitea::User::GetService.call(username, password) : interactor.result
user.gitea_token = result['sha1'] Rails.logger.info "##### [gitea] create_gitea_user result: #{gitea_user}"
user.gitea_uid = gitea_user[:body]['id'] if gitea_user[:body].present?
if user.save! result = Gitea::User::GenerateTokenService.call(username, password)
UserExtension.create!(user_id: user.id) forge_user.gitea_token = result['sha1']
result[:user] = {id: user.id, token: user.gitea_token} forge_user.gitea_uid = gitea_user[:body]['id'] if gitea_user[:body].present? && gitea_user[:body]['id'] !=0
forge_user.mail = email
if forge_user.save
UserExtension.create!(user_id: forge_user.id) if forge_user.user_extension.blank?
result[:user] = {id: forge_user.id, token: forge_user.gitea_token}
end
else
result[:message] = interactor.result[:message]
end end
else else
result[:message] = interactor.error Rails.logger.info "##### [gitea] create_gitea_user result: #{interactor.result[:message]}"
result[:message] = interactor.result[:message]
end end
result result
end end
def autosync_register_trustie(username, password, email, lastname="") def random_password
config = Rails.application.config_for(:configuration).symbolize_keys! "#{Random.rand(11111111)}"
api_host = config[:sync_url]
return if api_host.blank?
url = "#{api_host}/api/v1/users/common"
sync_json = {
"mail": email,
"password": password,
"login": username,
"lastname": lastname
}.compact
uri = URI.parse(url)
if api_host
http = Net::HTTP.new(uri.hostname, uri.port)
if api_host.include?("https://")
http.use_ssl = true
end
http.send_request('POST', uri.path, sync_json.to_json, {'Content-Type' => 'application/json'})
end
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 end

View File

@ -3,8 +3,8 @@ module RenderHelper
render json: { status: 0, message: 'success' }.merge(data) render json: { status: 0, message: 'success' }.merge(data)
end end
def render_error(message = '') def render_error(message = '', status=-1)
render json: { status: -1, message: message } render json: { status: status, message: message }
end end
def render_not_acceptable(message = '请求已拒绝') def render_not_acceptable(message = '请求已拒绝')
@ -28,8 +28,4 @@ module RenderHelper
def render_result(status=1, message='success') def render_result(status=1, message='success')
render json: { status: status, message: message } render json: { status: status, message: message }
end end
def render_parameter_missing
render json: { status: -1, message: '参数缺失' }
end
end end

View File

@ -5,7 +5,26 @@ module Repository::LanguagesPercentagable
result = Gitea::Repository::Languages::ListService.call(@owner.login, result = Gitea::Repository::Languages::ListService.call(@owner.login,
@repository.identifier, current_user&.gitea_token) @repository.identifier, current_user&.gitea_token)
result[:status] === :success ? hash_transform_precentagable(result[:body]) : nil @transform_language = @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
update_project_language(@transform_language) unless @transform_language.nil?
@transform_language
end
def update_project_language(language)
db_language = ProjectLanguage.find_or_create_by!(name: language.keys.first.downcase.upcase_first)
@project.update_column(:project_language_id, db_language.id) if @project.project_language_id.nil?
rescue
return
end end
# hash eq:{"JavaScript": 301681522,"Ruby": 1444004,"Roff": 578781} # hash eq:{"JavaScript": 301681522,"Ruby": 1444004,"Roff": 578781}

View File

@ -6,6 +6,13 @@ class ForksController < ApplicationController
def create def create
@new_project = Projects::ForkService.new(current_user, @project, params[:organization]).call @new_project = Projects::ForkService.new(current_user, @project, params[:organization]).call
# TODO: fix Educoder shixun
if @new_project.persisted?
ProjectScore.create(:project_id => @new_project.id, :score => 0) if @new_project.project_score.nil?
project_info = ProjectInfo.new(:user_id => current_user.id, :project_id => @new_project.id)
@project.project_infos << project_info
end
end end
private private

View File

@ -7,7 +7,7 @@ class IssueTagsController < ApplicationController
def index 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)) @user_admin_or_member = current_user.present? && (current_user.admin || @project.member?(current_user))
@page = params[:page] || 1 @page = params[:page] || 1
@limit = params[:limit] || 15 @limit = params[:limit] || 15

View File

@ -24,13 +24,13 @@ class IssuesController < ApplicationController
@filter_issues = @all_issues @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.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(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) @open_issues = @all_issues.where.not(status_id: IssueStatus::CLOSED)
@close_issues = @all_issues.where(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") scopes = Issues::ListQueryService.call(issues,params.delete_if{|k,v| v.blank?}, "Issue")
@issues_size = scopes.size @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) @issues = paginate(scopes)
respond_to do |format| respond_to do |format|
@ -109,7 +109,7 @@ class IssuesController < ApplicationController
def create def create
issue_params = issue_send_params(params) 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) @issue = Issue.new(issue_params)
if @issue.save! if @issue.save!
SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id) if Site.has_notice_menu?
@ -149,6 +149,8 @@ class IssuesController < ApplicationController
end end
@issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create")
@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)}" 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 AtmeService.call(current_user, @atme_receivers, @issue) if @atme_receivers.size > 0
@ -178,6 +180,7 @@ class IssuesController < ApplicationController
elsif params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size == 1 elsif params[:issue_tag_ids].is_a?(Array) && params[:issue_tag_ids].size == 1
@issue&.issue_tags_relates&.destroy_all @issue&.issue_tags_relates&.destroy_all
params[:issue_tag_ids].each do |tag| params[:issue_tag_ids].each do |tag|
next if tag == [""]
IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag)
end end
else else
@ -220,7 +223,7 @@ class IssuesController < ApplicationController
normal_status(-1, "不允许修改为关闭状态") normal_status(-1, "不允许修改为关闭状态")
else else
issue_params = issue_send_params(params).except(:issue_classify, :author_id, :project_id) 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) if @issue.update_attributes(issue_params)
if @issue&.pull_request.present? 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)) if Site.has_notice_menu? 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?
@ -244,7 +247,7 @@ class IssuesController < ApplicationController
end end
if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时 if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时
@issue.issue_times.update_all(end_time: Time.now) @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.issue_type.to_s == "2" && last_status_id != 5
if @issue.assigned_to_id.present? && last_status_id == 3 #只有当用户完成100%时才给token if @issue.assigned_to_id.present? && last_status_id == 3 #只有当用户完成100%时才给token
post_to_chain("add", @issue.token, @issue.get_assign_user.try(:login)) post_to_chain("add", @issue.token, @issue.get_assign_user.try(:login))

View File

@ -23,6 +23,7 @@ class JournalsController < ApplicationController
normal_status(-1, "评论内容不能为空") normal_status(-1, "评论内容不能为空")
else else
ActiveRecord::Base.transaction do 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 = { journal_params = {
journalized_id: @issue.id , journalized_id: @issue.id ,
journalized_type: "Issue", journalized_type: "Issue",
@ -53,6 +54,9 @@ class JournalsController < ApplicationController
end end
end end
end end
rescue Exception => exception
puts exception.message
normal_status(-1, exception.message)
end end
def destroy def destroy
@ -70,7 +74,8 @@ class JournalsController < ApplicationController
def update def update
content = params[:content] content = params[:content]
if content.present? if content.present?
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) if @journal.update_attribute(:notes, content)
normal_status(0, "更新成功") normal_status(0, "更新成功")
else else
@ -79,7 +84,9 @@ class JournalsController < ApplicationController
else else
normal_status(-1, "评论的内容不能为空") normal_status(-1, "评论的内容不能为空")
end end
rescue Exception => exception
puts exception.message
normal_status(-1, exception.message)
end end
def get_children_journals def get_children_journals

View File

@ -21,6 +21,14 @@ class MainController < ApplicationController
uid_logger("main start is #{cookies[:_educoder_session]}") uid_logger("main start is #{cookies[:_educoder_session]}")
end end
if params[:path] && params[:path].start_with?('projects/')
project_id = params[:path].split("/")[1]
project = Project.find_by_id(project_id)
if project.present?
return redirect_to("/projects/#{project.owner.login}/#{project.identifier}", status: 301)
end
end
# TODO: 这块之后需要整合者架构重新变化统一跳转到index后再路由分发 # TODO: 这块之后需要整合者架构重新变化统一跳转到index后再路由分发
if params[:path] && params[:path]&.include?("h5educoderbuild") && params[:path].split("/").first == "h5educoderbuild" if params[:path] && params[:path]&.include?("h5educoderbuild") && params[:path].split("/").first == "h5educoderbuild"
render file: 'public/h5educoderbuild/index.html', :layout => false, :content_type=> 'text/html' render file: 'public/h5educoderbuild/index.html', :layout => false, :content_type=> 'text/html'

View File

@ -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

View File

@ -8,6 +8,14 @@ class MembersController < ApplicationController
before_action :check_member_not_exists!, only: %i[remove change_role] before_action :check_member_not_exists!, only: %i[remove change_role]
def create def create
if @user.gitea_token.blank?
result = Gitea::User::GetService.call(@user.login)
if result[:status] == :error
# gitea不存在用户@user,直接注册
user_result = create_gitea_user!(@user, @user.login, @user.mail)
return render_error(user_result[:message]) if user_result[:message]
end
end
interactor = Projects::AddMemberInteractor.call(@project.owner, @project, @user) interactor = Projects::AddMemberInteractor.call(@project.owner, @project, @user)
SendTemplateMessageJob.perform_later('ProjectJoined', current_user.id, @user.id, @project.id) if Site.has_notice_menu? 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? SendTemplateMessageJob.perform_later('ProjectMemberJoined', current_user.id, @user.id, @project.id) if Site.has_notice_menu?
@ -26,6 +34,9 @@ class MembersController < ApplicationController
@total_count = scope.size @total_count = scope.size
@members = paginate(scope) @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 end
def remove def remove
@ -61,11 +72,14 @@ class MembersController < ApplicationController
end end
def check_member_exists! 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 end
def check_member_not_exists! 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 end
def check_user_profile_completed def check_user_profile_completed

View File

@ -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

View File

@ -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

View File

@ -2,7 +2,7 @@ class Oauth::BaseController < ActionController::Base
include RenderHelper include RenderHelper
include LoginHelper include LoginHelper
include ControllerRescueHandler include ControllerRescueHandler
include LoggerHelper include RegisterHelper
# include LaboratoryHelper # include LaboratoryHelper
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
@ -12,18 +12,6 @@ class Oauth::BaseController < ActionController::Base
end end
private private
def tip_exception(status = -1, message)
raise Educoder::TipException.new(status, message)
end
def tip_show_exception(status = -2, message)
raise Educoder::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
end
def session_user_id def session_user_id
# session[:user_id] # session[:user_id]
@ -35,7 +23,7 @@ class Oauth::BaseController < ActionController::Base
end end
def auth_hash 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'] request.env['omniauth.auth']
end end
@ -61,13 +49,4 @@ class Oauth::BaseController < ActionController::Base
Rails.logger.info("[wechat] set session unionid: #{unionid}") Rails.logger.info("[wechat] set session unionid: #{unionid}")
session[:unionid] = unionid session[:unionid] = unionid
end end
def session_edulogin
session[:edulogin]
end
def set_session_edulogin(login)
Rails.logger.info("[educoder] set sesstion edulogin: #{login}")
session[:edulogin] = login
end
end end

View File

@ -0,0 +1,68 @@
class Oauth::CallbacksController < Oauth::BaseController
def create
process_callback
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
# 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

View File

@ -1,6 +1,4 @@
class Oauth::EducoderController < Oauth::BaseController class Oauth::EducoderController < Oauth::BaseController
include RegisterHelper
def bind def bind
begin begin
login = params[:login] login = params[:login]
@ -34,41 +32,4 @@ class Oauth::EducoderController < Oauth::BaseController
render_error(ex.message) render_error(ex.message)
end end
end end
# 需要educoder那边设置回调地址
def create
begin
code = params['code'].to_s.strip
tip_exception("code不能为空") if code.blank?
new_user = false
result = EducoderOauth::Service.access_token(code)
result = EducoderOauth::Service.user_info(result[:access_token])
# 存在该用户
open_user = OpenUsers::Educoder.find_by(uid: result['login'])
if open_user.present? && open_user.user.present?
successful_authentication(open_user.user)
else
if current_user.blank? || !current_user.logged?
new_user = true
login = User.generate_login('E')
reg_result = autologin_register(login,"#{login}@forge.com", "Ec#{login}2021#", 'educoder', true)
if reg_result[:message].blank?
open_user = OpenUsers::Educoder.create!(user_id: reg_result[:user][:id], uid: result['login'], extra: result)
autosync_register_trustie(login, "Ec#{login}2021#", "#{login}@forge.com")
successful_authentication(open_user.user)
else
render_error(reg_result[:message])
end
else
OpenUsers::Educoder.create!(user: current_user, uid: result['login'], extra: result)
end
end
redirect_to root_path(new_user: new_user)
rescue Exception => ex
render_error(ex.message)
end
end
end end

View File

@ -61,7 +61,6 @@ class OauthController < ApplicationController
login = params[:login] login = params[:login]
email = params[:mail] email = params[:mail]
password = params[:password] password = params[:password]
lastname = params[:lastname]
callback_url = params[:callback_url] callback_url = params[:callback_url]
platform = params[:plathform] || 'educoder' platform = params[:plathform] || 'educoder'
@ -73,11 +72,8 @@ class OauthController < ApplicationController
if result[:message].blank? if result[:message].blank?
logger.info "[Oauth educoer] ====auto_register success" logger.info "[Oauth educoer] ====auto_register success"
user = User.find result[:user][:id] user = User.find result[:user][:id]
user.update_column(:lastname, params[:lastname])
autosync_register_trustie(login, password, email, lastname)
OpenUsers::Educoder.create!(user: user, uid: user.login)
successful_authentication(user) successful_authentication(user)
OpenUsers::Educoder.create!(user: user, uid: user.login)
render json: { callback_url: callback_url } render json: { callback_url: callback_url }
# redirect_to callback_url # redirect_to callback_url

View File

@ -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

View File

@ -4,8 +4,14 @@ class Organizations::OrganizationUsersController < Organizations::BaseController
def index def index
@organization_users = @organization.organization_users.includes(:user) @organization_users = @organization.organization_users.includes(:user)
search = params[:search].to_s.downcase if params[:search].present?
@organization_users = @organization_users.joins(:user).merge(User.like(search)) 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) @organization_users = kaminari_paginate(@organization_users)
end end

View File

@ -31,6 +31,7 @@ class Organizations::OrganizationsController < Organizations::BaseController
Organizations::CreateForm.new(organization_params.merge(original_name: "")).validate! Organizations::CreateForm.new(organization_params.merge(original_name: "")).validate!
@organization = Organizations::CreateService.call(current_user, organization_params) @organization = Organizations::CreateService.call(current_user, organization_params)
Util.write_file(@image, avatar_path(@organization)) if params[:image].present? Util.write_file(@image, avatar_path(@organization)) if params[:image].present?
Cache::V2::OwnerCommonService.new(@organization.id).reset
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
@ -48,6 +49,7 @@ class Organizations::OrganizationsController < Organizations::BaseController
Gitea::Organization::UpdateService.call(current_user.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? Util.write_file(@image, avatar_path(@organization)) if params[:image].present?
Cache::V2::OwnerCommonService.new(@organization.id).reset
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)

View File

@ -21,6 +21,17 @@ class Organizations::TeamProjectsController < Organizations::BaseController
tip_exception(e.message) tip_exception(e.message)
end end
def create_all
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do
@organization.projects.each do |project|
TeamProject.build(@organization.id, @team.id, project.id)
end
Gitea::Organization::TeamProject::CreateAllService.call(@organization.gitea_token, @team.gtid, @organization.login)
render_ok
end
end
def destroy def destroy
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@ -33,6 +44,17 @@ class Organizations::TeamProjectsController < Organizations::BaseController
tip_exception(e.message) tip_exception(e.message)
end end
def destroy_all
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do
@team.team_projects.each do |project|
project.destroy!
end
Gitea::Organization::TeamProject::DeleteAllService.call(@organization.gitea_token, @team.gtid, @organization.login)
render_ok
end
end
private private
def load_organization def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id]) @organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
@ -47,7 +69,7 @@ class Organizations::TeamProjectsController < Organizations::BaseController
end end
def load_operate_project def load_operate_project
@operate_project = Project.find_by(id: project_mark) || Project.find_by(identifier: project_mark) @operate_project = @organization.projects.where(id: project_mark).take || @organization.projects.where(identifier: project_mark).take
tip_exception("项目不存在") if @operate_project.nil? tip_exception("项目不存在") if @operate_project.nil?
end end

View File

@ -44,7 +44,7 @@ class Organizations::TeamsController < Organizations::BaseController
def create def create
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if @organization.teams.count >= 50 if @organization.teams.count >= 50
return render_forbidden("组织的团队数量已超过限制!") return tip_exception("组织的团队数量已超过限制!")
else else
Organizations::CreateTeamForm.new(team_params).validate! Organizations::CreateTeamForm.new(team_params).validate!
@team = Organizations::Teams::CreateService.call(current_user, @organization, team_params) @team = Organizations::Teams::CreateService.call(current_user, @organization, team_params)

View File

@ -12,7 +12,7 @@ class OwnersController < ApplicationController
def show def show
@owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id]) @owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id])
return render_ok(type: 'User') unless @owner.present? return render_not_found unless @owner.present?
# 组织 # 组织
if @owner.is_a?(Organization) if @owner.is_a?(Organization)
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition

View File

@ -12,6 +12,7 @@ class PraiseTreadController < ApplicationController
begin begin
return normal_status(2, "你已点过赞了") if current_user.liked?(@project) return normal_status(2, "你已点过赞了") if current_user.liked?(@project)
current_user.like!(@project) current_user.like!(@project)
SendTemplateMessageJob.perform_later('ProjectPraised', current_user.id, @project.id) if Site.has_notice_menu?
render_ok({praises_count: @project.praises_count, praised: current_user.liked?(@project)}) render_ok({praises_count: @project.praises_count, praised: current_user.liked?(@project)})
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)

View File

@ -10,7 +10,7 @@ class ProjectCategoriesController < ApplicationController
end end
def group_list def group_list
@project_categories = ProjectCategory.where('projects_count > 0').order(projects_count: :desc) @project_categories = ProjectCategory.select("id, name, projects_count, private_projects_count, (projects_count - private_projects_count) as public_projects_count").having('public_projects_count > 0').order(public_projects_count: :desc)
# projects = Project.no_anomory_projects.visible # projects = Project.no_anomory_projects.visible
# @category_group_list = projects.joins(:project_category).group("project_categories.id", "project_categories.name").size # @category_group_list = projects.joins(:project_category).group("project_categories.id", "project_categories.name").size
end end

View File

@ -0,0 +1,42 @@
class Projects::ProjectInviteLinksController < Projects::BaseController
before_action :require_manager!, except: [:show_link, :redirect_link]
before_action :require_login
def current_link
role = params[:role]
is_apply = params[:is_apply]
return render_error('请输入正确的参数!') unless role.present? && is_apply.present?
@project_invite_link = ProjectInviteLink.find_by(user_id: current_user.id, project_id: @project.id, role: role, is_apply: is_apply)
@project_invite_link = ProjectInviteLink.build!(@project, current_user, role, is_apply) unless @project_invite_link.present?
end
def generate_link
ActiveRecord::Base.transaction do
params_data = link_params.merge({user_id: current_user.id, project_id: @project.id})
Projects::ProjectInviteLinks::CreateForm.new(params_data).validate!
@project_invite_link = ProjectInviteLink.build!(project, user, params_data[:role], params_data[:is_apply])
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def show_link
@project_invite_link = ProjectInviteLink.find_by(sign: params[:invite_sign])
return render_not_found unless @project_invite_link.present?
end
def redirect_link
Projects::LinkJoinService.call(current_user, @project, params[:invite_sign])
render_ok
rescue Exception => e
uid_logger_error(e.message)
normal_status(-1, e.message)
end
private
def link_params
params.require(:project_invite_link).permit(:role, :is_apply)
end
end

View File

@ -9,7 +9,7 @@ class Projects::WebhooksController < Projects::BaseController
def create def create
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
return render_error("webhooks数量已到上限请删除暂不使用的webhooks以进行添加操作") if @project.webhooks.size > 19 return render_error("webhooks数量已到上限请删除暂不使用的webhooks以进行添加操作") if @project.webhooks.size > 49
return render_error("参数错误.") unless webhook_params.present? return render_error("参数错误.") unless webhook_params.present?
form = Projects::Webhooks::CreateForm.new(webhook_params) form = Projects::Webhooks::CreateForm.new(webhook_params)
return render json: {status: -1, message: form.errors} unless form.validate! return render json: {status: -1, message: form.errors} unless form.validate!

View File

@ -13,6 +13,8 @@ class ProjectsController < ApplicationController
def menu_list def menu_list
menu = [] menu = []
user_is_admin = current_user.admin? || @project.manager?(current_user)
menu.append(menu_hash_by_name("home")) menu.append(menu_hash_by_name("home"))
menu.append(menu_hash_by_name("code")) if @project.has_menu_permission("code") menu.append(menu_hash_by_name("code")) if @project.has_menu_permission("code")
menu.append(menu_hash_by_name("issues")) if @project.has_menu_permission("issues") menu.append(menu_hash_by_name("issues")) if @project.has_menu_permission("issues")
@ -22,7 +24,9 @@ class ProjectsController < ApplicationController
menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge? menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge?
menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge? menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge?
menu.append(menu_hash_by_name("activity")) menu.append(menu_hash_by_name("activity"))
menu.append(menu_hash_by_name("settings")) if (current_user.admin? || @project.manager?(current_user)) && @project.forge? menu.append(menu_hash_by_name("sonar"))
menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge?
menu.append(menu_hash_by_name("quit")) if !user_is_admin && @project.member(current_user.id) && @project.forge?
render json: menu render json: menu
end end
@ -36,8 +40,9 @@ class ProjectsController < ApplicationController
category_id = params[:category_id] category_id = params[:category_id]
@total_count = @total_count =
if category_id.blank? if category_id.blank?
ps = ProjectStatistic.first # ps = ProjectStatistic.first
ps.common_projects_count + ps.mirror_projects_count unless ps.blank? # ps.common_projects_count + ps.mirror_projects_count unless ps.blank?
@projects.total_count
else else
cate = ProjectCategory.find_by(id: category_id) cate = ProjectCategory.find_by(id: category_id)
cate&.projects_count || 0 cate&.projects_count || 0
@ -49,6 +54,13 @@ class ProjectsController < ApplicationController
Projects::CreateForm.new(project_params).validate! Projects::CreateForm.new(project_params).validate!
@project = Projects::CreateService.new(current_user, project_params).call @project = Projects::CreateService.new(current_user, project_params).call
# TODO: fix Educoder shixun
if @project.persisted?
ProjectScore.create(:project_id => @project.id, :score => 0) if @project.project_score.nil?
project_info = ProjectInfo.new(:user_id => current_user.id, :project_id => @project.id)
@project.project_infos << project_info
end
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
@ -59,7 +71,7 @@ class ProjectsController < ApplicationController
Projects::MigrateForm.new(mirror_params).validate! Projects::MigrateForm.new(mirror_params).validate!
@project = @project =
if enable_accelerator?(mirror_params[:clone_addr]) if EduSetting.get("mirror_address").to_s.include?("github") && enable_accelerator?(mirror_params[:clone_addr])
source_clone_url = mirror_params[:clone_addr] source_clone_url = mirror_params[:clone_addr]
uid_logger("########## 已动加速器 ##########") uid_logger("########## 已动加速器 ##########")
result = Gitea::Accelerator::MigrateService.call(mirror_params) result = Gitea::Accelerator::MigrateService.call(mirror_params)
@ -71,6 +83,11 @@ class ProjectsController < ApplicationController
else else
Projects::MigrateService.call(current_user, mirror_params) Projects::MigrateService.call(current_user, mirror_params)
end end
elsif EduSetting.get("mirror_address").to_s.include?("cnpmjs") && mirror_params[:clone_addr].include?("github.com")
source_clone_url = mirror_params[:clone_addr]
clone_url = source_clone_url.gsub('github.com', 'github.com.cnpmjs.org')
uid_logger("########## 更改clone_addr ##########")
Projects::MigrateService.call(current_user, mirror_params.merge(source_clone_url: source_clone_url, clone_addr: clone_url))
else else
Projects::MigrateService.call(current_user, mirror_params) Projects::MigrateService.call(current_user, mirror_params)
end end
@ -83,7 +100,7 @@ class ProjectsController < ApplicationController
return @branches = [] unless @project.forge? return @branches = [] unless @project.forge?
# result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier) # result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier)
result = Gitea::Repository::Branches::ListNameService.call(@owner, @project.identifier) result = Gitea::Repository::Branches::ListNameService.call(@owner, @project.identifier, params[:name])
@branches = result.is_a?(Hash) ? (result.key?(:status) ? [] : result["branch_name"]) : result @branches = result.is_a?(Hash) ? (result.key?(:status) ? [] : result["branch_name"]) : result
end end
@ -95,18 +112,18 @@ class ProjectsController < ApplicationController
end end
def group_type_list def group_type_list
project_statics = ProjectStatistic.first # project_statics = ProjectStatistic.first
@project_statics_list = [ @project_statics_list = [
{ {
project_type: 'common', project_type: 'common',
name: '开源托管项目', name: '开源托管项目',
projects_count: project_statics&.common_projects_count || 0 projects_count: Project.common.size || 0
}, },
{ {
project_type: 'mirror', project_type: 'mirror',
name: '开源镜像项目', name: '开源镜像项目',
projects_count: project_statics&.mirror_projects_count || 0 projects_count: Project.sync_mirror.size + Project.mirror.size || 0
} }
] ]
@ -130,7 +147,7 @@ class ProjectsController < ApplicationController
validate_params = project_params.slice(:name, :description, validate_params = project_params.slice(:name, :description,
:project_category_id, :project_language_id, :private, :identifier) :project_category_id, :project_language_id, :private, :identifier)
Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier)).validate! Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier, project_name: @project.name)).validate!
private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : params[:private] || false private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : params[:private] || false
@ -145,6 +162,15 @@ class ProjectsController < ApplicationController
} }
gitea_repo = Gitea::Repository::UpdateService.call(@owner, @project&.repository&.identifier, gitea_params) gitea_repo = Gitea::Repository::UpdateService.call(@owner, @project&.repository&.identifier, gitea_params)
@project.repository.update_attributes({hidden: gitea_repo["private"], identifier: gitea_repo["name"]}) @project.repository.update_attributes({hidden: gitea_repo["private"], identifier: gitea_repo["name"]})
# 更新对应所属分类下的项目数量(私有)
before_is_public = @project.previous_changes[:is_public].present? ? @project.previous_changes[:is_public][0] : @project.is_public
after_is_public = @project.previous_changes[:is_public].present? ? @project.previous_changes[:is_public][1] : @project.is_public
before_pc_id = @project.previous_changes[:project_category_id].present? ? @project.previous_changes[:project_category_id][0] : @project.project_category_id
after_pc_id = @project.previous_changes[:project_category_id].present? ? @project.previous_changes[:project_category_id][1] : @project.project_category_id
before_pc = ProjectCategory.find_by_id(before_pc_id)
after_pc = ProjectCategory.find_by_id(after_pc_id)
before_pc.decrement!(:private_projects_count, 1) if before_pc.present? && !before_is_public
after_pc.increment!(:private_projects_count, 1) if after_pc.present? && !after_is_public
end end
SendTemplateMessageJob.perform_later('ProjectSettingChanged', current_user.id, @project&.id, @project.previous_changes.slice(:name, :description, :project_category_id, :project_language_id, :is_public, :identifier)) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('ProjectSettingChanged', current_user.id, @project&.id, @project.previous_changes.slice(:name, :description, :project_category_id, :project_language_id, :is_public, :identifier)) if Site.has_notice_menu?
end end
@ -162,6 +188,8 @@ class ProjectsController < ApplicationController
Gitea::Repository::DeleteService.new(@project.owner, @project.identifier).call Gitea::Repository::DeleteService.new(@project.owner, @project.identifier).call
@project.destroy! @project.destroy!
@project.forked_projects.update_all(forked_from_project_id: nil) @project.forked_projects.update_all(forked_from_project_id: nil)
# 如果该项目有所属的项目分类以及为私有项目,需要更新对应数量
@project.project_category.decrement!(:private_projects_count, 1) if @project.project_category.present? && !@project.is_public
render_ok render_ok
end end
else else
@ -172,6 +200,22 @@ class ProjectsController < ApplicationController
tip_exception(e.message) tip_exception(e.message)
end end
def quit
user_is_admin = current_user.admin? || @project.manager?(current_user)
if !user_is_admin && @project.member(current_user.id) && @project.forge?
ActiveRecord::Base.transaction do
Projects::DeleteMemberInteractor.call(@project.owner, @project, current_user)
SendTemplateMessageJob.perform_later('ProjectMemberLeft', current_user.id, current_user.id, @project.id) if Site.has_notice_menu?
render_ok
end
else
render_forbidden('你不能退出该仓库')
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def watch_users def watch_users
watchers = @project.watchers.includes(:user).order("watchers.created_at desc").distinct watchers = @project.watchers.includes(:user).order("watchers.created_at desc").distinct
@watchers_count = watchers.size @watchers_count = watchers.size
@ -185,7 +229,7 @@ class ProjectsController < ApplicationController
end end
def fork_users def fork_users
fork_users = @project.fork_users.includes(:user, :project, :fork_project).order("fork_users.created_at desc").distinct fork_users = @project.fork_users.includes(:owner, :project, :fork_project).order("fork_users.created_at desc").distinct
@forks_count = fork_users.size @forks_count = fork_users.size
@fork_users = paginate(fork_users) @fork_users = paginate(fork_users)
end end

View File

@ -2,7 +2,7 @@ class PullRequestsController < ApplicationController
before_action :require_login, except: [:index, :show, :files, :commits] before_action :require_login, except: [:index, :show, :files, :commits]
before_action :require_profile_completed, only: [:create] before_action :require_profile_completed, only: [:create]
before_action :load_repository before_action :load_repository
before_action :check_menu_authorize before_action :check_menu_authorize, only: [:index, :show, :create, :update, :refuse_merge, :pr_merge]
before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos, :files, :commits] before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos, :files, :commits]
before_action :load_pull_request, only: [:files, :commits] before_action :load_pull_request, only: [:files, :commits]
before_action :find_atme_receivers, only: [:create, :update] before_action :find_atme_receivers, only: [:create, :update]
@ -16,7 +16,7 @@ class PullRequestsController < ApplicationController
issues = issues.where(is_private: false) unless current_user.present? && (current_user.admin? || @project.member?(current_user)) issues = issues.where(is_private: false) unless current_user.present? && (current_user.admin? || @project.member?(current_user))
@all_issues = issues.distinct @all_issues = issues.distinct
@filter_issues = @all_issues @filter_issues = @all_issues
@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 = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::OPEN}) @open_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::OPEN})
@close_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::CLOSED}) @close_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::CLOSED})
@merged_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::MERGED}) @merged_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::MERGED})
@ -29,7 +29,7 @@ class PullRequestsController < ApplicationController
end end
def new def new
@all_branches = Branches::ListService.call(@owner, @project) @all_branches = Branches::ListService.call(@owner, @project, params[:branch_name])
@is_fork = @project.forked_from_project_id.present? @is_fork = @project.forked_from_project_id.present?
@projects_names = [{ @projects_names = [{
project_user_login: @owner.try(:login), project_user_login: @owner.try(:login),
@ -50,7 +50,7 @@ class PullRequestsController < ApplicationController
end end
def get_branches def get_branches
branch_result = Branches::ListService.call(@owner, @project) branch_result = Branches::ListService.call(@owner, @project, params[:name])
render json: branch_result render json: branch_result
# return json: branch_result # return json: branch_result
end end
@ -58,9 +58,12 @@ class PullRequestsController < ApplicationController
def create def create
# return normal_status(-1, "您不是目标分支开发者,没有权限,请联系目标分支作者.") unless @project.operator?(current_user) # return normal_status(-1, "您不是目标分支开发者,没有权限,请联系目标分支作者.") unless @project.operator?(current_user)
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
Issues::CreateForm.new({subject: params[:title], description: params[:body].blank? ? params[:body] : params[:body].b}).validate!
@pull_request, @gitea_pull_request = PullRequests::CreateService.call(current_user, @owner, @project, params) @pull_request, @gitea_pull_request = PullRequests::CreateService.call(current_user, @owner, @project, params)
if @gitea_pull_request[:status] == :success if @gitea_pull_request[:status] == :success
@pull_request.bind_gitea_pull_request!(@gitea_pull_request[:body]["number"], @gitea_pull_request[:body]["id"]) @pull_request.bind_gitea_pull_request!(@gitea_pull_request[:body]["number"], @gitea_pull_request[:body]["id"])
reviewers = User.where(id: params[:reviewer_ids])
@pull_request.reviewers = reviewers
SendTemplateMessageJob.perform_later('PullRequestAssigned', current_user.id, @pull_request&.id) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('PullRequestAssigned', current_user.id, @pull_request&.id) if Site.has_notice_menu?
SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu?
Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}"
@ -89,22 +92,11 @@ class PullRequestsController < ApplicationController
else else
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
begin begin
return normal_status(-1, "title不能超过255个字符") if params[:title].length > 255 Issues::UpdateForm.new({subject: params[:title], description: params[:body].blank? ? params[:body] : params[:body].b}).validate!
merge_params merge_params
@issue&.issue_tags_relates&.destroy_all if params[:issue_tag_ids].blank? reviewers = User.where(id: params[:reviewer_ids])
if params[:issue_tag_ids].present? && !@issue&.issue_tags_relates.where(issue_tag_id: params[:issue_tag_ids]).exists? @pull_request.reviewers = reviewers
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|
IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag)
end
else
return normal_status(-1, "请输入正确的标记。")
end
end
if @issue.update_attributes(@issue_params) if @issue.update_attributes(@issue_params)
if @pull_request.update_attributes(@local_params.compact) if @pull_request.update_attributes(@local_params.compact)
@ -154,6 +146,8 @@ class PullRequestsController < ApplicationController
colsed = PullRequests::CloseService.call(@owner, @repository, @pull_request, current_user) colsed = PullRequests::CloseService.call(@owner, @repository, @pull_request, current_user)
if colsed === true if colsed === true
@pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::CLOSE) @pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::CLOSE)
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, @pull_request.id) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, @pull_request.id) if Site.has_notice_menu?
normal_status(1, "已拒绝") normal_status(1, "已拒绝")
else else
@ -175,6 +169,7 @@ class PullRequestsController < ApplicationController
@issue_assign_to = @issue.get_assign_user @issue_assign_to = @issue.get_assign_user
@gitea_pull = Gitea::PullRequest::GetService.call(@owner.login, @gitea_pull = Gitea::PullRequest::GetService.call(@owner.login,
@repository.identifier, @pull_request.gitea_number, current_user&.gitea_token) @repository.identifier, @pull_request.gitea_number, current_user&.gitea_token)
# @last_review = @pull_request.reviews.take
end end
def pr_merge def pr_merge
@ -198,6 +193,8 @@ class PullRequestsController < ApplicationController
# @pull_request.project_trend_status! # @pull_request.project_trend_status!
@pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::MERGE) @pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::MERGE)
@issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id) @issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id)
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
SendTemplateMessageJob.perform_later('PullRequestMerged', current_user.id, @pull_request.id) if Site.has_notice_menu? SendTemplateMessageJob.perform_later('PullRequestMerged', current_user.id, @pull_request.id) if Site.has_notice_menu?
normal_status(1, "合并成功") normal_status(1, "合并成功")
else else
@ -261,7 +258,7 @@ class PullRequestsController < ApplicationController
def get_relatived def get_relatived
@project_tags = @project.issue_tags&.select(:id,:name, :color).as_json @project_tags = @project.issue_tags&.select(:id,:name, :color).as_json
@project_versions = @project.versions&.select(:id,:name, :status).as_json @project_versions = @project.versions.opening&.select(:id,:name, :status).as_json
@project_members = @project.all_developers @project_members = @project.all_developers
@project_priories = IssuePriority&.select(:id,:name, :position).as_json @project_priories = IssuePriority&.select(:id,:name, :position).as_json
end end
@ -274,12 +271,12 @@ class PullRequestsController < ApplicationController
base: params[:base], #目标分支 base: params[:base], #目标分支
milestone: 0, #里程碑,未与本地的里程碑关联 milestone: 0, #里程碑,未与本地的里程碑关联
} }
assignee_login = User.find_by_id(params[:assigned_to_id])&.login
@requests_params = @local_params.merge({ @requests_params = @local_params.merge({
assignee: current_user.try(:login),
# assignees: ["#{params[:assigned_login].to_s}"], # assignees: ["#{params[:assigned_login].to_s}"],
assignees: ["#{current_user.try(:login).to_s}"], assignees: ["#{assignee_login.to_s}"],
labels: params[:issue_tag_ids], labels: params[:issue_tag_ids]
due_date: Time.now # due_date: Time.now
}) })
@issue_params = { @issue_params = {
author_id: current_user.id, author_id: current_user.id,

View File

@ -9,7 +9,7 @@ class RepositoriesController < ApplicationController
before_action :load_repository before_action :load_repository
before_action :authorizate!, except: [:sync_mirror, :tags, :commit, :archive] before_action :authorizate!, except: [:sync_mirror, :tags, :commit, :archive]
before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror]
before_action :get_ref, only: %i[entries sub_entries top_counts file archive] before_action :get_ref, only: %i[entries sub_entries top_counts files archive]
before_action :get_latest_commit, only: %i[entries sub_entries top_counts] before_action :get_latest_commit, only: %i[entries sub_entries top_counts]
before_action :get_statistics, only: %i[top_counts] before_action :get_statistics, only: %i[top_counts]
@ -47,14 +47,19 @@ class RepositoriesController < ApplicationController
end end
def entries def entries
@project.increment!(:visits) @week_project_visit_record, @month_project_visit_record = TimeableVisitRecord.build(@project.id)
CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id) if @week_project_visit_record.visits < 300 && @month_project_visit_record.visits < 1000
@week_project_visit_record.increment!(:visits)
@month_project_visit_record.increment!(:visits)
@project.increment!(:visits)
CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id)
end
if @project.educoder? if @project.educoder?
@entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name) @entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name)
else else
@entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call @entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call
@entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : [] @entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : []
@path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/"
end end
end end
@ -99,7 +104,7 @@ class RepositoriesController < ApplicationController
end end
end end
else else
@path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/"
interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref) interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref)
if interactor.success? if interactor.success?
result = interactor.result result = interactor.result
@ -137,21 +142,35 @@ class RepositoriesController < ApplicationController
else else
@commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token) @commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token)
@commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token, {diff: true}) @commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token, {diff: true})
render_error(@commit[:message], @commit[:status]) if @commit.has_key?(:status) || @commit_diff.has_key?(:status)
end end
end end
def tags def tags
result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]}) if params[:only_name].present?
result = Gitea::Repository::Tags::ListNameService.call(@owner, @project.identifier, params[:name])
@tags = result.is_a?(Hash) && result.key?(:status) ? [] : result @tags = result.is_a?(Hash) && result.key?(:status) ? [] : result
else
name_result = Gitea::Repository::Tags::ListNameService.call(@owner, @project.identifier, params[:name])
@tag_names = result.is_a?(Hash) && result.key?(:status) ? [] : name_result
result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]})
@tags = result.is_a?(Hash) && result.key?(:status) ? [] : result
end
end end
def contributors def contributors
if params[:filepath].present? || @project.educoder? if params[:filepath].present? || @project.educoder?
@contributors = [] @contributors = []
else else
@contributors = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) result = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier)
@contributors = result.is_a?(Hash) && result.key?(:status) ? [] : result
end end
rescue
@contributors = []
end end
def edit def edit
@ -219,10 +238,11 @@ class RepositoriesController < ApplicationController
else else
result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token) result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token)
end end
@path = Gitea.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/" @path = GiteaService.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/"
@readme = result[:status] === :success ? result[:body] : nil @readme = result[:status] === :success ? result[:body] : nil
@readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path) @readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path)
render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha") @readme['replace_content'] = readme_decode64_content(@readme, @owner, @repository, params[:ref], @path)
render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha", "replace_content")
rescue rescue
render json: nil render json: nil
end end
@ -236,9 +256,9 @@ class RepositoriesController < ApplicationController
end end
def archive def archive
domain = Gitea.gitea_config[:domain] domain = GiteaService.gitea_config[:domain]
api_url = Gitea.gitea_config[:base_url] api_url = GiteaService.gitea_config[:base_url]
archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{CGI.escape(params[:archive])}" archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{Addressable::URI.escape(params[:archive])}"
file_path = [domain, api_url, archive_url].join file_path = [domain, api_url, archive_url].join
file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden? file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden?
@ -249,10 +269,10 @@ class RepositoriesController < ApplicationController
end end
def raw def raw
domain = Gitea.gitea_config[:domain] domain = GiteaService.gitea_config[:domain]
api_url = Gitea.gitea_config[:base_url] api_url = GiteaService.gitea_config[:base_url]
url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{CGI.escape(params[:filepath])}?ref=#{CGI.escape(params[:ref])}" url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{Addressable::URI.escape(params[:filepath])}?ref=#{Addressable::URI.escape(params[:ref])}"
file_path = [domain, api_url, url].join file_path = [domain, api_url, url].join
file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&") file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&")
@ -376,7 +396,7 @@ class RepositoriesController < ApplicationController
local_requests = PullRequest.new(local_params.merge(user_id: current_user.try(:id), project_id: @project.id, issue_id: @pull_issue.id)) local_requests = PullRequest.new(local_params.merge(user_id: current_user.try(:id), project_id: @project.id, issue_id: @pull_issue.id))
if local_requests.save if local_requests.save
gitea_request = Gitea::PullRequest::CreateService.new(current_user.try(:gitea_token), @owner.login, @project.try(:identifier), requests_params).call gitea_request = Gitea::PullRequest::CreateService.new(current_user.try(:gitea_token), @owner.login, @project.try(:identifier), requests_params).call
if gitea_request[:status] == :success && local_requests.update_attributes(gpid: gitea_request["body"]["number"]) if gitea_request[:status] == :success && local_requests.update_attributes(gitea_number: gitea_request["body"]["number"])
local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create")
end end
end end

View File

@ -0,0 +1,20 @@
class ReviewsController < ApplicationController
before_action :require_login
before_action :load_project
before_action :load_pull_request
def create
return render_forbidden('您不是审查人员,无法进行审查!') if current_user&.id != @pull_request.issue.assigned_to_id
@review = Api::V1::Projects::Pulls::Reviews::CreateService.call(@project, @pull_request, review_params, current_user)
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

View File

@ -1,14 +1,23 @@
class SettingsController < ApplicationController class SettingsController < ApplicationController
def show def show
@old_projects_url = nil @old_projects_url = nil
get_navbar
get_add_menu get_add_menu
get_common_menu get_common_menu
get_personal_menu get_personal_menu
get_third_party
get_top_system_notification get_top_system_notification
get_banner
end end
private private
def get_navbar
@navbar = default_laboratory.navbar
if User.current.logged?
pernal_index = {"name"=>"个人主页", "link"=>get_site_url("url", "#{Rails.application.config_for(:configuration)['platform_url']}/current_user"), "hidden"=>false}
@navbar << pernal_index
end
end
def get_add_menu def get_add_menu
@add = [] @add = []
Site.add.select(:id, :name, :url, :key).to_a.map(&:serializable_hash).each do |site| Site.add.select(:id, :name, :url, :key).to_a.map(&:serializable_hash).each do |site|
@ -41,14 +50,6 @@ class SettingsController < ApplicationController
end end
end end
def get_third_party
@third_party = []
@third_party << {
name: 'educoder',
url: EducoderOauth.oauth_url
}
end
def get_top_system_notification def get_top_system_notification
@top_system_notification = SystemNotification.is_top.first @top_system_notification = SystemNotification.is_top.first
end end
@ -67,4 +68,14 @@ class SettingsController < ApplicationController
def append_http(url) def append_http(url)
url.to_s.start_with?("http") ? url : [request.protocol, request.host_with_port, url].join('') url.to_s.start_with?("http") ? url : [request.protocol, request.host_with_port, url].join('')
end end
def get_banner
@banner = {
title: EduSetting.get("banner_title") || '大学开源',
sub_title: EduSetting.get("banner_sub_title") || '让大学绽放开源之花',
content: EduSetting.get("banner_content") || "Powered by Trustie and GitLink"
}
@banner
end
end end

View File

@ -0,0 +1,37 @@
class SonarTasksController < ApplicationController
before_action :require_login
before_action :load_project
before_action :authenticate_user!
def index
@sonar_task = SonarTask.find_by(project_id: @project.id)
tip_exception(-1, "代码未检测,请点击开始检测按钮检测!") if @sonar_task.blank?
if @sonar_task.complete_status == 0
service = Sonar::ApiService.new(@project.id)
service.task_result(@sonar_task.task_id)
end
end
def create
Sonar::ApiService.call(@project.id)
render_ok
end
def type_detail
service = Sonar::ApiService.new(@project.id)
@data = service.type_detail(params[:task_id], params[:type])
end
def code_source
service = Sonar::ApiService.new(@project.id)
@data = service.source_code(params[:task_id], params[:component])
Rails.logger.info "data=======#{@data}"
end
private
def authenticate_user!
return if @project.member?(current_user) || current_user.admin?
render_forbidden('你没有权限操作')
end
end

View File

@ -2,24 +2,24 @@ class StatisticController < ApplicationController
# 平台概况 # 平台概况
def platform_profile def platform_profile
@platform_user_query = Statistic::PlatformUserQuery.new(params).call @platform_user_query = Statistic::PlatformUserQuery.new(params).call rescue [0, 0, 0]
@platform_project_query = Statistic::PlatformProjectQuery.new(params).call @platform_project_query = Statistic::PlatformProjectQuery.new(params).call rescue [0, 0, 0]
@platform_course_query = Statistic::PlatformCourseQuery.new(params).call @platform_course_query = Statistic::PlatformCourseQuery.new(params).call rescue [0, 0, 0]
end end
# 平台代码提交数据 # 平台代码提交数据
def platform_code def platform_code
@platform_pull_request_query = Statistic::PlatformPullRequestQuery.new(params).call @platform_pull_request_query = Statistic::PlatformPullRequestQuery.new(params).call rescue [0, 0]
@platform_commit_query = Statistic::PlatformCommitQuery.new(params,current_user).call @platform_commit_query = Statistic::PlatformCommitQuery.new(params,current_user).call rescue [0, 0]
end end
# 项目案例活跃度排行榜 # 项目案例活跃度排行榜
def active_project_rank def active_project_rank
@active_project_rank_query = Statistic::ActiveProjectRankQuery.new(params, current_user).call @active_project_rank_query = Statistic::ActiveProjectRankQuery.new(params, current_user).call rescue []
end end
# 开发者活跃度排行榜 # 开发者活跃度排行榜
def active_developer_rank def active_developer_rank
@active_developer_rank_query = Statistic::ActiveDeveloperRankQuery.new(params, current_user).call @active_developer_rank_query = Statistic::ActiveDeveloperRankQuery.new(params, current_user).call rescue []
end end
end end

Some files were not shown because too many files have changed in this diff Show More