Merge pull request '20230112版本' (#314) from Trustie/forgeplus:trustie_server into develop

This commit is contained in:
xxq250 2023-01-12 16:56:37 +08:00
commit 7a9a107423
322 changed files with 50844 additions and 1249221 deletions

View File

@ -118,6 +118,10 @@ 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"
gem "omniauth-rails_csrf_protection"
gem 'omniauth-gitee', '~> 1.0.0'
gem "omniauth-wechat-oauth2"
# global var # global var
gem 'request_store' gem 'request_store'
@ -135,4 +139,4 @@ gem 'doorkeeper'
gem 'doorkeeper-jwt' gem 'doorkeeper-jwt'
gem 'gitea-client', '~> 0.10.2' gem 'gitea-client', '~> 0.11.1'

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

@ -111,7 +111,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)
@ -139,6 +141,7 @@ class AccountsController < ApplicationController
Register::Form.new(register_params).validate! Register::Form.new(register_params).validate!
user = Users::RegisterService.call(register_params) user = Users::RegisterService.call(register_params)
user.mail = "#{user.login}@example.org" if user.mail.blank?
password = register_params[:password].strip password = register_params[:password].strip
# gitea用户注册, email, username, password # gitea用户注册, email, username, password
@ -150,6 +153,10 @@ class AccountsController < ApplicationController
user.gitea_uid = gitea_user[:body]['id'] user.gitea_uid = gitea_user[:body]['id']
if user.save! if user.save!
UserExtension.create!(user_id: user.id) UserExtension.create!(user_id: user.id)
# 绑定授权账号
if ["qq", "wechat", "gitee", "github", "educoder"].include?(params[:type].to_s) && session[:unionid].present?
"OpenUsers::#{params[:type].to_s.capitalize}".constantize.create!(user: user, uid: session[:unionid])
end
successful_authentication(user) successful_authentication(user)
render_ok render_ok
end end
@ -391,7 +398,7 @@ class AccountsController < ApplicationController
end end
def register_params def register_params
params.permit(:login, :namespace, :password, :password_confirmation, :code) params.permit(:login, :namespace, :password, :password_confirmation, :code, :type)
end end
def reset_password_params def reset_password_params

View File

@ -1,9 +1,9 @@
class Admins::DashboardsController < Admins::BaseController class Admins::DashboardsController < Admins::BaseController
def index def index
# 用户活跃数 # 用户活跃数
day_user_ids = CommitLog.where(created_at: today).pluck(:project_id).uniq day_user_ids = CommitLog.where(created_at: today).pluck(:user_id).uniq
weekly_user_ids = CommitLog.where(created_at: current_week).pluck(:project_id).uniq weekly_user_ids = CommitLog.where(created_at: current_week).pluck(:user_id).uniq
month_user_ids = CommitLog.where(created_at: current_month).pluck(:project_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 @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 @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 @month_active_user_count = User.where(last_login_on: current_month).or(User.where(id: month_user_ids)).count

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

@ -7,12 +7,12 @@ class Admins::MessageTemplatesController < Admins::BaseController
end end
def new def new
@message_template = MessageTemplate::CustomTip.new @message_template = MessageTemplate.new
end end
def create def create
@message_template = MessageTemplate::CustomTip.new(ignore_params) @message_template = MessageTemplate::CustomTip.new(message_template_params)
@message_template.type = "MessageTemplate::CustomTip"
if @message_template.save! if @message_template.save!
redirect_to admins_message_templates_path redirect_to admins_message_templates_path
flash[:success] = "创建消息模板成功" flash[:success] = "创建消息模板成功"
@ -47,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,8 +1,9 @@
class Admins::Topic::BannersController < Admins::Topic::BaseController class Admins::Topic::BannersController < Admins::Topic::BaseController
before_action :find_banner, only: [:edit, :update, :destroy] before_action :find_banner, only: [:edit, :update, :destroy]
def index def index
@banners = paginate(::Topic::Banner) @banners = paginate(::Topic::Banner)
@banners = paginate(::Topic::Banner.where("title like ?", "%#{params[:search]}%")) if params[:search].present?
end end
def new def new
@ -52,6 +53,6 @@ class Admins::Topic::BannersController < Admins::Topic::BaseController
end end
def banner_params def banner_params
params.require(:topic_banner).permit(:title, :order_index) params.require(:topic_banner).permit(:title, :order_index, :url)
end end
end end

View File

@ -2,6 +2,7 @@ class Api::V1::BaseController < ApplicationController
include Api::ProjectHelper include Api::ProjectHelper
include Api::UserHelper include Api::UserHelper
include Api::PullHelper
# before_action :doorkeeper_authorize! # before_action :doorkeeper_authorize!
# skip_before_action :user_setup # skip_before_action :user_setup
@ -30,18 +31,25 @@ class Api::V1::BaseController < ApplicationController
# 具有对仓库的管理权限 # 具有对仓库的管理权限
def require_manager_above def require_manager_above
@project = load_project @project = load_project
return render_forbidden unless current_user.admin? && @project.manager?(current_user) return render_forbidden if !current_user.admin? && !@project.manager?(current_user)
end end
# 具有对仓库的操作权限 # 具有对仓库的操作权限
def require_operate_above def require_operate_above
@project = load_project @project = load_project
return render_forbidden unless current_user.admin? && @project.operator?(current_user) 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 end
# 具有对仓库的访问权限 # 具有对仓库的访问权限
def require_public_and_member_above def require_public_and_member_above
@project = load_project @project = load_project
return render_forbidden unless @project.is_public || (current_user.admin? && @project.member?(current_user)) return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
end end
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

@ -1,9 +1,13 @@
class Api::V1::Projects::ContentsController < Api::V1::BaseController class Api::V1::Projects::ContentsController < Api::V1::BaseController
before_action :require_operate_above, only: [:batch] before_action :require_operate_above_or_fork_project, only: [:batch]
def batch def batch
@result_object = Api::V1::Projects::Contents::BatchCreateService.call(@project, batch_content_params, current_user&.gitea_token) @batch_content_params = batch_content_params
puts @result_object # 处理下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 end
private private

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

@ -5,11 +5,13 @@ class Api::V1::Projects::WebhooksController < Api::V1::BaseController
def index def index
# @result_object = Api::V1::Projects::Webhooks::ListService.call(@project, current_user&.gitea_token) # @result_object = Api::V1::Projects::Webhooks::ListService.call(@project, current_user&.gitea_token)
@webhooks = @project.webhooks @webhooks = @project.webhooks
@webhooks = @webhooks.where(type: params[:type]) if params[:type].present?
@webhooks = kaminari_paginate(@webhooks) @webhooks = kaminari_paginate(@webhooks)
end end
def create def create
@result_object = Api::V1::Projects::Webhooks::CreateService.call(@project, webhook_params, current_user&.gitea_token) 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 end
def show def show
@ -44,6 +46,10 @@ class Api::V1::Projects::WebhooksController < Api::V1::BaseController
end end
private private
def create_webhook_params
params.require(:webhook).permit(:active, :branch_filter, :http_method, :url, :content_type, :secret, :type, events: [])
end
def webhook_params def webhook_params
params.require(:webhook).permit(:active, :branch_filter, :http_method, :url, :content_type, :secret, events: []) params.require(:webhook).permit(:active, :branch_filter, :http_method, :url, :content_type, :secret, events: [])
end end

View File

@ -15,6 +15,5 @@ class Api::V1::ProjectsController < Api::V1::BaseController
def blame def blame
@result_object = Api::V1::Projects::BlameService.call(@project, params[:sha], params[:filepath], current_user&.gitea_token) @result_object = Api::V1::Projects::BlameService.call(@project, params[:sha], params[:filepath], current_user&.gitea_token)
puts @result_object
end end
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

@ -1,6 +1,105 @@
class Api::V1::UsersController < Api::V1::BaseController class Api::V1::UsersController < Api::V1::BaseController
def index before_action :load_observe_user
render_ok before_action :check_auth_for_observe_user
def send_email_vefify_code
code = %W(0 1 2 3 4 5 6 7 8 9)
verification_code = code.sample(6).join
mail = params[:email]
code_type = params[:code_type]
sign = Digest::MD5.hexdigest("#{OPENKEY}#{mail}")
Rails.logger.info sign
tip_exception(501, "请求不合理") if sign != params[:smscode]
# 60s内不能重复发送
send_email_limit_cache_key = "send_email_60_second_limit:#{mail}"
tip_exception(-2, '请勿频繁操作') if Rails.cache.exist?(send_email_limit_cache_key)
send_email_control = LimitForbidControl::SendEmailCode.new(mail)
tip_exception(-2, '邮件发送太频繁,请稍后再试') if send_email_control.forbid?
begin
UserMailer.update_email(mail, verification_code).deliver_now
Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute)
send_email_control.increment!
rescue Exception => e
logger_error(e)
tip_exception(-2,"邮件发送失败,请稍后重试")
end
ver_params = {code_type: code_type, code: verification_code, email: mail}
last_code = VerificationCode.where(code_type: code_type, email: mail).last
last_code.update_attributes!({created_at: Time.current - 10.minute}) if last_code.present?
data = VerificationCode.new(ver_params)
if data.save!
render_ok
else
tip_exception(-1, "创建数据失败")
end
end
def check_password
password = params[:password]
return tip_exception(-5, "8~16位密码支持字母数字和符号") unless password =~ CustomRegexp::PASSWORD
return tip_exception(-5, "密码错误") unless @observe_user.check_password?(password)
render_ok
end
def check_email
mail = strip(params[:email])
return tip_exception(-2, "邮件格式有误") unless mail =~ CustomRegexp::EMAIL
exist_owner = Owner.find_by(mail: mail)
return tip_exception(-2, '邮箱已被使用') if exist_owner
render_ok
end
def check_email_verify_code
code = strip(params[:code])
mail = strip(params[:email])
code_type = params[:code_type]
return tip_exception(-2, "邮件格式有误") unless mail =~ CustomRegexp::EMAIL
verifi_code = VerificationCode.where(email: mail, code: code, code_type: code_type).last
return render_ok if code == "123123" && EduSetting.get("code_debug") # 万能验证码,用于测试 # TODO 万能验证码,用于测试
return tip_exception(-6, "验证码不正确") if verifi_code&.code != code
return tip_exception(-6, "验证码已失效") if !verifi_code&.effective?
render_ok
end
def check_phone_verify_code
code = strip(params[:code])
phone = strip(params[:phone])
code_type = params[:code_type]
return tip_exception(-2, "手机号格式有误") unless phone =~ CustomRegexp::PHONE
verifi_code = VerificationCode.where(phone: phone, code: code, code_type: code_type).last
return render_ok if code == "123123" && EduSetting.get("code_debug") # 万能验证码,用于测试 # TODO 万能验证码,用于测试
return tip_exception(-6, "验证码不正确") if verifi_code&.code != code
return tip_exception(-6, "验证码已失效") if !verifi_code&.effective?
render_ok
end
def update_email
@result_object = Api::V1::Users::UpdateEmailService.call(@observe_user, params, current_user.gitea_token)
if @result_object
return render_ok
else
return render_error('更改邮箱失败!')
end
end
def update_phone
@result_object = Api::V1::Users::UpdatePhoneService.call(@observe_user, params)
if @result_object
return render_ok
else
return render_error('更改手机号失败!')
end
end end
end end

View File

@ -20,9 +20,9 @@ class ApplicationController < ActionController::Base
# TODO # TODO
# check sql query time # check sql query time
before_action do before_action do
if request.subdomain === 'testforgeplus' || request.subdomain === "profiler" # if request.subdomain === 'testforgeplus' || request.subdomain === "profiler"
Rack::MiniProfiler.authorize_request # Rack::MiniProfiler.authorize_request
end # end
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)
@ -79,8 +79,7 @@ class ApplicationController < ActionController::Base
# 判断用户的邮箱或者手机是否可用 # 判断用户的邮箱或者手机是否可用
# params[:type] 1: 注册2忘记密码3绑定 # params[:type] 1: 注册2忘记密码3绑定
def check_mail_and_phone_valid login, type def check_mail_and_phone_valid login, type
unless login =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/ || login =~ /^1\d{10}$/ || unless login =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/ || login =~ /^1\d{10}$/
login =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])$/
tip_exception(-2, "请输入正确的手机号或邮箱") tip_exception(-2, "请输入正确的手机号或邮箱")
end end
@ -103,8 +102,10 @@ class ApplicationController < ActionController::Base
when 1, 2, 4, 9 when 1, 2, 4, 9
# 手机类型的发送 # 手机类型的发送
sigle_para = {phone: value} sigle_para = {phone: value}
status = Gitlink::Sms.send(mobile: value, code: code) # status = Gitlink::Sms.send(mobile: value, code: code)
tip_exception(-2, code_msg(status)) if status != 0 # tip_exception(-2, code_msg(status)) if status != 0
status = Sms::UcloudService.call(value, code, send_type)
tip_exception(-2, ucloud_code_msg(status)) if status != 0
when 8, 3, 5 when 8, 3, 5
# 邮箱类型的发送 # 邮箱类型的发送
sigle_para = {email: value} sigle_para = {email: value}
@ -116,8 +117,13 @@ class ApplicationController < ActionController::Base
send_email_control = LimitForbidControl::SendEmailCode.new(value) send_email_control = LimitForbidControl::SendEmailCode.new(value)
tip_exception(-1, '邮件发送太频繁,请稍后再试') if send_email_control.forbid? tip_exception(-1, '邮件发送太频繁,请稍后再试') if send_email_control.forbid?
begin begin
UserMailer.register_email(value, code).deliver_now if send_type == 3
UserMailer.find_password(value, code).deliver_now
elsif send_type == 5
UserMailer.bind_email(value, code).deliver_now
else
UserMailer.register_email(value, code).deliver_now
end
Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute) Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute)
send_email_control.increment! send_email_control.increment!
# Mailer.run.email_register(code, value) # Mailer.run.email_register(code, value)
@ -149,6 +155,27 @@ class ApplicationController < ActionController::Base
end end
end end
def ucloud_code_msg status
case status
when 0
"验证码已经发送到您的手机,请注意查收"
when 171
"API签名错误"
when 18014
"无效手机号码"
when 18017
"无效模板"
when 18018
"短信模板参数与短信模板不匹配"
when 18023
"短信内容中含有运营商拦截的关键词"
when 18033
"变量内容不符合规范"
else
"错误码#{status}"
end
end
def validate_type(object_type) def validate_type(object_type)
normal_status(2, "参数") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip) normal_status(2, "参数") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip)
end end
@ -173,6 +200,25 @@ class ApplicationController < ActionController::Base
tip_exception(401, "请登录后再操作") unless User.current.logged? tip_exception(401, "请登录后再操作") unless User.current.logged?
end end
def require_login_or_token
if params[:token].present?
user = User.try_to_autologin(params[:token])
User.current = user
end
tip_exception(401, "请登录后再操作") unless User.current.logged?
end
def require_login_cloud_ide_saas
if params[:sign].present? && params[:email].present?
sign = Digest::MD5.hexdigest("#{OPENKEY}#{params[:email]}")
if params[:sign].to_s == sign
user = User.find_by(mail: params[:email])
User.current = user
end
end
tip_exception(401, "请登录后再操作") unless User.current.logged?
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
@ -277,11 +323,11 @@ class ApplicationController < ActionController::Base
end end
end end
# if !User.current.logged? && Rails.env.development? if !User.current.logged? && Rails.env.development?
# user = User.find 1 user = User.find 1
# User.current = user User.current = user
# start_user_session(user) start_user_session(user)
# end end
# 测试版前端需求 # 测试版前端需求
@ -619,7 +665,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 > 50) ? 50 : 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)
@ -704,9 +750,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

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 = GiteaService.gitea_config[:domain] domain = GiteaService.gitea_config[:domain]
api_url = GiteaService.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) || course.is_public 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

@ -1,35 +1,19 @@
class BindUsersController < ApplicationController class BindUsersController < ApplicationController
# before_action :require_login
def create def create
# user = CreateBindUserService.call(create_params) Rails.logger.debug "--------------开始绑定用户------------"
# Rails.logger.debug "--------------params: #{params.to_unsafe_h}"
if params[:type] == "qq" tip_exception '系统错误' if session[:unionid].blank?
begin
user = CreateBindUserService.call(current_user, create_params)
successful_authentication(user) if user.id != current_user.id
render_ok bind_user = User.try_to_login(params[:username], params[:password])
rescue ApplicationService::Error => ex tip_exception '用户名或者密码错误' if bind_user.blank?
render_error(ex.message) tip_exception '用户名或者密码错误' unless bind_user.check_password?(params[:password].to_s)
end tip_exception '参数错误' unless ["qq", "wechat", "gitee", "github", "educoder"].include?(params[:type].to_s)
else tip_exception '该账号已被绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s)
begin
tip_exception '系统错误' if session[:unionid].blank?
bind_user = User.try_to_login(params[:username], params[:password]) "OpenUsers::#{params[:type].to_s.capitalize}".constantize.create!(user: bind_user, uid: session[:unionid])
tip_exception '用户名或者密码错误' if bind_user.blank? successful_authentication(bind_user)
tip_exception '用户名或者密码错误' unless bind_user.check_password?(params[:password].to_s) @user = bind_user
tip_exception '该账号已被绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s)
OpenUsers::Wechat.create!(user: bind_user, uid: session[:unionid])
successful_authentication(bind_user)
render_ok
rescue Exception => e
render_error(e.message)
end
end
end end
def new_user def new_user

View File

@ -15,12 +15,15 @@ class CommitLogsController < ApplicationController
owner = User.find_by(login: owner_name) 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(user_id: owner&.id)&.first
project = Project.where(identifier: repository_name).where(gpid: repository_id)&.first if project.blank? 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| params[:commits].each do |commit|
commit_id = commit[:id] commit_id = commit[:id]
message = commit[:message] message = commit[:message]
CommitLog.create(user: user, project: project, repository_id: repository_id, CommitLog.create(user: user, project: project, repository_id: repository_id,
name: repository_name, full_name: repository_full_name, name: repository_name, full_name: repository_full_name,
ref: ref, commit_id: commit_id, message: message) ref: ref, commit_id: commit_id, message: message)
# 统计数据新增
CacheAsyncSetJob.perform_later("project_common_service", {commits: 1}, project.id)
end end
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

@ -16,4 +16,13 @@ module Api::UserHelper
end end
@observe_user @observe_user
end 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 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 = {

View File

@ -1,21 +1,25 @@
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?
interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password}) interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password})
result ={}
if interactor.success? if interactor.success?
gitea_user = interactor.result gitea_user = interactor.result
result = Gitea::User::GenerateTokenService.call(username, password) result = Gitea::User::GenerateTokenService.call(username, password)
@ -26,7 +30,7 @@ module RegisterHelper
result[:user] = {id: user.id, token: user.gitea_token} result[:user] = {id: user.id, token: user.gitea_token}
end end
else else
result[:message] = interactor.error result[:message] = interactor.result[:message]
end end
result result
end end
@ -66,7 +70,7 @@ module RegisterHelper
user.password = params[:password] user.password = params[:password]
user.mail = params[:email] user.mail = params[:email]
if user.save! if user.save!
sync_params = { sync_params = {
password: params[:password].to_s, password: params[:password].to_s,
email: params[:email], email: params[:email],
@ -74,9 +78,9 @@ module RegisterHelper
new_name: params[:username], new_name: params[:username],
source_id: 0 source_id: 0
} }
interactor = Gitea::User::UpdateInteractor.call(before_login, sync_params) interactor = Gitea::User::UpdateInteractor.call(before_login, sync_params)
if interactor.success? if interactor.success?
result[:user] = user result[:user] = user
else else
result[:message] = '用户同步Gitea失败!' result[:message] = '用户同步Gitea失败!'

View File

@ -3,7 +3,7 @@ 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: status, message: message } render json: { status: status, message: message }
end end

View File

@ -11,6 +11,7 @@ module Repository::LanguagesPercentagable
end end
def update_project_language(language) 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) db_language = ProjectLanguage.find_or_create_by!(name: language.keys.first.downcase.upcase_first)
@project.update_column(:project_language_id, db_language.id) @project.update_column(:project_language_id, db_language.id)
rescue rescue

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

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

@ -26,6 +26,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 +64,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,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

@ -3,6 +3,7 @@ class Oauth::BaseController < ActionController::Base
include LoginHelper include LoginHelper
include ControllerRescueHandler include ControllerRescueHandler
include LoggerHelper include LoggerHelper
include RegisterHelper
# include LaboratoryHelper # include LaboratoryHelper
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
@ -13,13 +14,13 @@ class Oauth::BaseController < ActionController::Base
private private
def tip_exception(status = -1, message) def tip_exception(status = -1, message)
raise Educoder::TipException.new(status, message) raise Gitlink::TipException.new(status, message)
end end
def tip_show_exception(status = -2, message) def tip_show_exception(status = -2, message)
raise Educoder::TipException.new(status, message) raise Gitlink::TipException.new(status, message)
end end
def tip_show(exception) def tip_show(exception)
uid_logger("Tip show status is #{exception.status}, message is #{exception.message}") uid_logger("Tip show status is #{exception.status}, message is #{exception.message}")
render json: exception.tip_json render json: exception.tip_json
@ -35,7 +36,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

View File

@ -0,0 +1,93 @@
class Oauth::CallbacksController < Oauth::BaseController
def create
process_callback_new
rescue Exception => e
Rails.logger.info "授权失败:#{e}"
tip_exception("授权失败")
end
private
def config_providers
config = Rails.application.config_for(:configuration)
config.dig("oauth").keys
end
# QQ: {"ret":0,"msg":"","is_lost":0,"nickname":"颜值不算太高","gender":"男","gender_type":1,"province":"","city":"","year":"2013","constellation":"","figureurl":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/30","figureurl_1":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/50","figureurl_2":"http://qzapp.qlogo.cn/qzapp/101508858/0F860F4B329768F47B22341C5FD9089C/100","figureurl_qq_1":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=40\u0026t=1568887757","figureurl_qq_2":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=100\u0026t=1568887757","figureurl_qq":"http://thirdqq.qlogo.cn/g?b=oidb\u0026k=My3segFVHFqVmauibJQUltw\u0026s=140\u0026t=1568887757","figureurl_type":"1","is_yellow_vip":"0","vip":"0","yellow_vip_level":"0","level":"0","is_yellow_year_vip":"0"}
def process_callback
Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}")
if auth_hash.blank?
redirect_to("/login") && return
end
new_user = false
platform = auth_hash[:provider]
uid = auth_hash[:uid]
mail = auth_hash.info.email || nil
nickname = ["gitee", "github"].include?(platform) ? auth_hash.info.name : auth_hash.info.nickname
open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.find_by(uid: uid)
if open_user.present? && open_user.user.present?
successful_authentication(open_user.user)
else
if current_user.blank? || !current_user.logged?
has_user = User.find_by(mail: mail)
if has_user.present?
"OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user_id: has_user.id, uid: uid, extra: auth_hash.extra)
successful_authentication(has_user)
else
new_user = true
login = build_login_name(platform, auth_hash.info.nickname)
mail = "#{login}@example.org" if mail.blank?
code = %W(0 1 2 3 4 5 6 7 8 9)
rand_password = code.sample(10).join
reg_result = autologin_register(login, mail, rand_password, platform, nil, nickname)
Rails.logger.info("[OAuth2] omniauth.auth [reg_result] #{reg_result} ")
if reg_result[:message].blank?
open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user_id: reg_result[:user][:id], uid: uid, extra: auth_hash.extra)
successful_authentication(open_user.user)
else
tip_exception(reg_result.present? ? reg_result[:message] : "授权失败")
end
end
else
"OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user: current_user, uid: login, extra: auth_hash.extra)
end
end
redirect_to root_path(new_user: new_user)
end
def process_callback_new
Rails.logger.info("[OAuth2] omniauth.auth -> #{request.env['omniauth.auth'].inspect}")
if auth_hash.blank?
redirect_to("/login") && return
end
platform = auth_hash[:provider]
uid = auth_hash[:uid]
uid = auth_hash.info.unionid if platform == "wechat"
open_user = "OpenUsers::#{platform.to_s.capitalize}".constantize.find_by(uid: uid)
if open_user.present? && open_user.user.present?
successful_authentication(open_user.user)
redirect_to root_path(new_user: false)
return
else
if current_user.blank? || !current_user.logged?
session[:unionid] = uid
else
"OpenUsers::#{platform.to_s.capitalize}".constantize.create!(user: current_user, uid: uid)
end
end
Rails.logger.info("[OAuth2] session[:unionid] -> #{session[:unionid]}")
redirect_to "/bindlogin/#{platform}"
end
# gitee,github nickname=login,如果系统未占用保留原用户名
def build_login_name(provider, nickname)
if ["gitee", "github"].include?(provider) && User.find_by(login: nickname).blank?
nickname
else
User.generate_user_login('p')
end
end
end

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,12 +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?
user_condition_users = User.like(search).to_sql search = params[:search].to_s.downcase
team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql user_condition_users = User.like(search).to_sql
users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users") 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
@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)
@ -45,9 +46,16 @@ class Organizations::OrganizationsController < Organizations::BaseController
@organization.nickname = organization_params[:nickname] if organization_params[:nickname].present? @organization.nickname = organization_params[:nickname] if organization_params[:nickname].present?
@organization.save! @organization.save!
sync_organization_extension! sync_organization_extension!
# 更改组织可见性为私有,则需将该组织下的所有仓库同步更改为私有仓库
if organization_extension_params[:visibility] == "privacy"
Project.where(user_id: @organization.id).where(is_public: true).each do |project|
update_project_private(project)
end
end
Gitea::Organization::UpdateService.call(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)
@ -121,5 +129,20 @@ class Organizations::OrganizationsController < Organizations::BaseController
def sync_organization_extension! def sync_organization_extension!
@organization.organization_extension.update_attributes!(organization_extension_params) @organization.organization_extension.update_attributes!(organization_extension_params)
end end
def update_project_private(project)
project.update_attributes!(is_public: false)
project.forked_projects.update_all(is_public: project.is_public)
gitea_params = {
private: true,
default_branch: project.default_branch,
website: project.website,
name: project.identifier
}
gitea_repo = Gitea::Repository::UpdateService.call(@organization, project&.repository&.identifier, gitea_params)
project.repository.update_attributes({hidden: gitea_repo["private"], identifier: gitea_repo["name"]})
# 更新对应所属分类下的项目数量(私有)
project.project_category.decrement!(:private_projects_count, 1) if project.project_category.present?
end
end end

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

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

@ -1,10 +1,10 @@
class ProjectRankController < ApplicationController class ProjectRankController < ApplicationController
# 根据时间获取热门项目 # 根据时间获取热门项目
def index def index
$redis_cache.zunionstore("recent-days-project-rank", get_timeable_key_names) $redis_cache.zunionstore("recent-days-project-rank-#{time}", get_timeable_key_names)
deleted_data = $redis_cache.smembers("v2-project-rank-deleted") deleted_data = $redis_cache.smembers("v2-project-rank-deleted")
$redis_cache.zrem("recent-days-project-rank", deleted_data) unless deleted_data.blank? $redis_cache.zrem("recent-days-project-rank-#{time}", deleted_data) unless deleted_data.blank?
@project_rank = $redis_cache.zrevrange("recent-days-project-rank", 0, 4, withscores: true) @project_rank = $redis_cache.zrevrange("recent-days-project-rank-#{time}", 0, 9, withscores: true)
rescue Exception => e rescue Exception => e
@project_rank = [] @project_rank = []
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

@ -40,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
@ -52,7 +53,7 @@ class ProjectsController < ApplicationController
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
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
OpenProjectDevOpsJob.perform_later(@project&.id, current_user.id)
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
@ -154,6 +155,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
@ -171,6 +181,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

View File

@ -1,5 +1,5 @@
class PublicKeysController < ApplicationController class PublicKeysController < ApplicationController
before_action :require_login before_action :require_login_cloud_ide_saas
before_action :find_public_key, only: [:destroy] before_action :find_public_key, only: [:destroy]
def index def index
@ -61,4 +61,6 @@ class PublicKeysController < ApplicationController
def find_public_key def find_public_key
@public_key = current_user.public_keys.find_by_id(params[:id]) @public_key = current_user.public_keys.find_by_id(params[:id])
end end
end end

View File

@ -67,6 +67,8 @@ class PullRequestsController < ApplicationController
@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)}"
@ -98,19 +100,8 @@ class PullRequestsController < ApplicationController
Issues::UpdateForm.new({subject: params[:title], description: params[:body].blank? ? params[:body] : params[:body].b}).validate! 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)
@ -160,6 +151,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
@ -181,7 +174,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.issue.reviews.take @last_review = @pull_request.reviews.take
end end
def pr_merge def pr_merge
@ -205,6 +198,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
@ -268,7 +263,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

View File

@ -1,391 +1,407 @@
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
include RepositoriesHelper include RepositoriesHelper
include ApplicationHelper include ApplicationHelper
include OperateProjectAbilityAble include OperateProjectAbilityAble
include Repository::LanguagesPercentagable include Repository::LanguagesPercentagable
before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror]
before_action :require_profile_completed, only: [:create_file] before_action :require_profile_completed, only: [:create_file]
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 files 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]
def files def files
result = @project.educoder? ? nil : Gitea::Repository::Files::GetService.call(@owner, @project.identifier, @ref, params[:search], @owner.gitea_token) result = @project.educoder? ? nil : Gitea::Repository::Files::GetService.call(@owner, @project.identifier, @ref, params[:search], @owner.gitea_token)
render json: result render json: result
end end
# 新版项目详情 # 新版项目详情
def detail def detail
@user = current_user @user = current_user
@result = Repositories::DetailService.call(@owner, @repository, @user) @result = Repositories::DetailService.call(@owner, @repository, @user)
@project_fork_id = @project.try(:forked_from_project_id) @project_fork_id = @project.try(:forked_from_project_id)
if @project_fork_id.present? if @project_fork_id.present?
@fork_project = Project.find_by(id: @project_fork_id) @fork_project = Project.find_by(id: @project_fork_id)
@fork_project_user = @fork_project.owner @fork_project_user = @fork_project.owner
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)
end end
def show def show
@user = current_user @user = current_user
@repo = @project.repository @repo = @project.repository
@result = @project.forge? ? Gitea::Repository::GetService.new(@owner, @project.identifier).call : nil @result = @project.forge? ? Gitea::Repository::GetService.new(@owner, @project.identifier).call : nil
@project_fork_id = @project.try(:forked_from_project_id) @project_fork_id = @project.try(:forked_from_project_id)
if @project_fork_id.present? if @project_fork_id.present?
@fork_project = Project.find_by(id: @project_fork_id) @fork_project = Project.find_by(id: @project_fork_id)
@fork_project_user = @fork_project.owner @fork_project_user = @fork_project.owner
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)
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
if @project.educoder? @week_project_visit_record.increment!(:visits)
@entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name) @month_project_visit_record.increment!(:visits)
else @project.increment!(:visits)
@entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id)
@entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : [] end
@path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" if @project.educoder?
end @entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name)
end else
@entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call
def top_counts @entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : []
@result = @project.educoder? ? nil : Gitea::Repository::GetService.new(@project.owner, @project.identifier).call @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/"
end end
end
def sub_entries
file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) def top_counts
@result = @project.educoder? ? nil : Gitea::Repository::GetService.new(@project.owner, @project.identifier).call
if @project.educoder? end
if params[:type] === 'file'
@sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) def sub_entries
logger.info "######### sub_entries: #{@sub_entries}" file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip))
return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1
if @project.educoder?
tmp_entries = { if params[:type] === 'file'
"content" => @sub_entries['data']['content'], @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri)
"type" => "blob" logger.info "######### sub_entries: #{@sub_entries}"
} return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1
@sub_entries = {
"trees"=>tmp_entries, tmp_entries = {
"commits" => [{}] "content" => @sub_entries['data']['content'],
} "type" => "blob"
else }
begin @sub_entries = {
@sub_entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder&.repo_name, {path: file_path_uri}) "trees"=>tmp_entries,
if @sub_entries.blank? || @sub_entries['status'].to_i === -1 "commits" => [{}]
@sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) }
return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1 else
tmp_entries = { begin
"content" => @sub_entries['data']['content'], @sub_entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder&.repo_name, {path: file_path_uri})
"type" => "blob" if @sub_entries.blank? || @sub_entries['status'].to_i === -1
} @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri)
@sub_entries = { return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1
"trees"=>tmp_entries, tmp_entries = {
"commits" => [{}] "content" => @sub_entries['data']['content'],
} "type" => "blob"
end }
rescue @sub_entries = {
return render_error('该文件暂未开放,敬请期待.') "trees"=>tmp_entries,
end "commits" => [{}]
end }
else end
@path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" rescue
interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref) return render_error('该文件暂未开放,敬请期待.')
if interactor.success? end
result = interactor.result end
@sub_entries = result.is_a?(Array) ? result.sort_by{ |hash| hash['type'] } : result else
else @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/"
render_error(interactor.error) interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref)
end if interactor.success?
end result = interactor.result
end @sub_entries = result.is_a?(Array) ? result.sort_by{ |hash| hash['type'] } : result
else
def commits render_error(interactor.error)
if @project.educoder? end
@commits = Educoder::Repository::Commits::ListService.call(@project&.project_educoder&.repo_name) end
else end
if params[:filepath].present?
file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) def commits
@hash_commit = Gitea::Repository::Commits::FileListService.new(@owner.login, @project.identifier, file_path_uri, if @project.educoder?
sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call @commits = Educoder::Repository::Commits::ListService.call(@project&.project_educoder&.repo_name)
else else
@hash_commit = Gitea::Repository::Commits::ListService.new(@owner.login, @project.identifier, if params[:filepath].present?
sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip))
end @hash_commit = Gitea::Repository::Commits::FileListService.new(@owner.login, @project.identifier, file_path_uri,
end sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call
end else
@hash_commit = Gitea::Repository::Commits::ListService.new(@owner.login, @project.identifier,
def commits_slice sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call
@hash_commit = Gitea::Repository::Commits::ListSliceService.call(@owner.login, @project.identifier, end
sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token) end
end end
def commit def commits_slice
@sha = params[:sha] @hash_commit = Gitea::Repository::Commits::ListSliceService.call(@owner.login, @project.identifier,
if @project.educoder? sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token)
return render_error('暂未开放,敬请期待.') end
else
@commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token) def commit
@commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token, {diff: true}) @sha = params[:sha]
end if @project.educoder?
end return render_error('暂未开放,敬请期待.')
else
def tags @commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token)
result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]}) @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)
@tags = result.is_a?(Hash) && result.key?(:status) ? [] : result end
end end
def contributors def tags
if params[:filepath].present? || @project.educoder? if params[:only_name].present?
@contributors = [] result = Gitea::Repository::Tags::ListNameService.call(@owner, @project.identifier, params[:name])
else
result = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) @tags = result.is_a?(Hash) && result.key?(:status) ? [] : result
@contributors = result.is_a?(Hash) && result.key?(:status) ? [] : result else
end name_result = Gitea::Repository::Tags::ListNameService.call(@owner, @project.identifier, params[:name])
rescue
@contributors = [] @tag_names = result.is_a?(Hash) && result.key?(:status) ? [] : name_result
end
result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]})
def edit
return render_forbidden if !@project.manager?(current_user) && !current_user.admin? @tags = result.is_a?(Hash) && result.key?(:status) ? [] : result
end end
end
def create_file
interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) def contributors
if interactor.success? if params[:filepath].present? || @project.educoder?
@file = interactor.result @contributors = []
# create_new_pr(params) else
#如果是更新流水线文件 result = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier)
if params[:pipeline_id] @contributors = result.is_a?(Hash) && result.key?(:status) ? [] : result
update_pipeline(params[:pipeline_id]) end
end rescue
else @contributors = []
render_error(interactor.error) end
end
end def edit
return render_forbidden if !@project.manager?(current_user) && !current_user.admin?
def update_pipeline(pipeline_id) end
pipeline = Ci::Pipeline.find(pipeline_id)
if pipeline def create_file
pipeline.update!(sync: 1) interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params)
end if interactor.success?
end @file = interactor.result
# create_new_pr(params)
def update_file #如果是更新流水线文件
interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) if params[:pipeline_id]
if interactor.success? update_pipeline(params[:pipeline_id])
@file = interactor.result end
# TODO: 是否创建pr else
# create_new_pr(params) render_error(interactor.error)
render_result(1, "更新成功") end
else end
render_error(interactor.error)
end def update_pipeline(pipeline_id)
end pipeline = Ci::Pipeline.find(pipeline_id)
if pipeline
def delete_file pipeline.update!(sync: 1)
interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) end
if interactor.success? end
@file = interactor.result
render_result(1, "文件删除成功") def update_file
else interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier))
render_error(interactor.error) if interactor.success?
end @file = interactor.result
end # TODO: 是否创建pr
# create_new_pr(params)
def repo_hook render_result(1, "更新成功")
else
end render_error(interactor.error)
end
def sync_mirror end
return render_error("正在镜像中..") if @repository.mirror.waiting?
def delete_file
@repository.sync_mirror! interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier))
SyncMirroredRepositoryJob.perform_later(@repository.id, current_user.id) if interactor.success?
render_ok @file = interactor.result
end render_result(1, "文件删除成功")
else
def readme render_error(interactor.error)
if params[:filepath].present? end
result = Gitea::Repository::Readme::DirService.call(@owner.login, @repository.identifier, params[:filepath], params[:ref], current_user&.gitea_token) end
else
result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token) def repo_hook
end
@path = GiteaService.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/" end
@readme = result[:status] === :success ? result[:body] : nil
@readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path) def sync_mirror
@readme['replace_content'] = readme_decode64_content(@readme, @owner, @repository, params[:ref], @path) return render_error("正在镜像中..") if @repository.mirror.waiting?
render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha", "replace_content")
rescue @repository.sync_mirror!
render json: nil SyncMirroredRepositoryJob.perform_later(@repository.id, current_user.id)
end render_ok
end
def languages
if @project.educoder? def readme
render json: {} if params[:filepath].present?
else result = Gitea::Repository::Readme::DirService.call(@owner.login, @repository.identifier, params[:filepath], params[:ref], current_user&.gitea_token)
render json: languages_precentagable else
end result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token)
end end
@path = GiteaService.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/"
def archive @readme = result[:status] === :success ? result[:body] : nil
domain = GiteaService.gitea_config[:domain] @readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path)
api_url = GiteaService.gitea_config[:base_url] @readme['replace_content'] = readme_decode64_content(@readme, @owner, @repository, params[:ref], @path)
archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{Addressable::URI.escape(params[:archive])}" render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha", "replace_content")
rescue
file_path = [domain, api_url, archive_url].join render json: nil
file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden? end
return render_not_found if !request.format.zip? && !request.format.gzip? def languages
if @project.educoder?
redirect_to file_path render json: {}
end else
render json: languages_precentagable
def raw end
domain = GiteaService.gitea_config[:domain] end
api_url = GiteaService.gitea_config[:base_url]
def archive
url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{Addressable::URI.escape(params[:filepath])}?ref=#{Addressable::URI.escape(params[:ref])}" domain = GiteaService.gitea_config[:domain]
file_path = [domain, api_url, url].join api_url = GiteaService.gitea_config[:base_url]
file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&") archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{Addressable::URI.escape(params[:archive])}"
redirect_to file_path file_path = [domain, api_url, archive_url].join
end file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden?
private return render_not_found if !request.format.zip? && !request.format.gzip?
def find_project redirect_to file_path
@project = Project.find params[:id] end
render_not_found("未找到相关的仓库") unless @project
end def raw
domain = GiteaService.gitea_config[:domain]
def find_project_with_includes api_url = GiteaService.gitea_config[:base_url]
@project = Project.includes(:repository, :owner, :watchers, :praise_treads).find params[:id]
end 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
def authorizate! file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&")
return if current_user && current_user.admin?
if @project.repository.hidden? && !@project.member?(current_user) redirect_to file_path
render_forbidden end
end
end private
# TODO 获取最新commit信息 def find_project
def project_commits @project = Project.find params[:id]
if params[:filepath].present? render_not_found("未找到相关的仓库") unless @project
file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) end
Gitea::Repository::Commits::FileListService.new(@project.owner.login, @project.identifier, file_path_uri,
sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call def find_project_with_includes
else @project = Project.includes(:repository, :owner, :watchers, :praise_treads).find params[:id]
Gitea::Repository::Commits::ListService.new(@project.owner.login, @project.identifier, end
sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call
end def authorizate!
end return if current_user && current_user.admin?
if @project.repository.hidden? && !@project.member?(current_user)
def get_statistics render_forbidden
@branches_count = @project.educoder? ? 0 : Gitea::Repository::Branches::ListService.new(@project.owner, @project.identifier).call&.size end
@tags_count = @project.educoder? ? 0 : Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @project.owner.login, @project.identifier).call&.size end
end
# TODO 获取最新commit信息
def get_ref def project_commits
@ref = params[:ref] || @project&.default_branch if params[:filepath].present?
end file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip))
Gitea::Repository::Commits::FileListService.new(@project.owner.login, @project.identifier, file_path_uri,
def get_latest_commit sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call
latest_commit = @project.educoder? ? nil : project_commits else
@latest_commit = latest_commit.present? ? latest_commit[:body][0] : nil Gitea::Repository::Commits::ListService.new(@project.owner.login, @project.identifier,
@commits_count = latest_commit.present? ? latest_commit[:total_count] : 0 sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call
end end
end
def content_params
{ def get_statistics
filepath: params[:filepath], @branches_count = @project.educoder? ? 0 : Gitea::Repository::Branches::ListService.new(@project.owner, @project.identifier).call&.size
branch: params[:branch], @tags_count = @project.educoder? ? 0 : Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @project.owner.login, @project.identifier).call&.size
new_branch: params[:new_branch], end
content: params[:content],
message: params[:message], def get_ref
committer: { @ref = params[:ref] || @project&.default_branch
email: current_user.mail, end
name: current_user.login
}, def get_latest_commit
identifier: @project.identifier latest_commit = @project.educoder? ? nil : project_commits
} @latest_commit = latest_commit.present? ? latest_commit[:body][0] : nil
end @commits_count = latest_commit.present? ? latest_commit[:total_count] : 0
end
def hook_params(hook_type, params)
# if hook_type == "push" def content_params
# # TODO hook返回的记录中暂时没有文件代码数量的增减暂时根据 commits数量来计算 {
# uploadPushInfo = { filepath: params[:filepath],
# "sha": params["commits"].present? ? params["commits"].last : "", branch: params[:branch],
# "branch": params["ref"].to_s.split("/").last, new_branch: params[:new_branch],
# "modification_lines": params["commits"].length content: params[:content],
# } message: params[:message],
# elsif hook_type == "pull_request" && params["action"].to_s == "closed" #合并请求合并后才会有上链操作 committer: {
# uploadPushInfo = { email: current_user.mail,
# "branch": params["base"]["ref"].to_s.split("/").last, name: current_user.login
# "sha": params["pull_request"]["merge_base"], },
# "modification_lines": 1 #pull_request中没有commits数量 identifier: @project.identifier
# } }
# else end
# uploadPushInfo = {}
# end def hook_params(hook_type, params)
# if hook_type == "push"
# uploadPushInfo # # TODO hook返回的记录中暂时没有文件代码数量的增减暂时根据 commits数量来计算
end # uploadPushInfo = {
# "sha": params["commits"].present? ? params["commits"].last : "",
def create_new_pr(params) # "branch": params["ref"].to_s.split("/").last,
if params[:new_branch].present? && params[:new_branch] != params[:branch] # "modification_lines": params["commits"].length
local_params = { # }
title: params[:message], #标题 # elsif hook_type == "pull_request" && params["action"].to_s == "closed" #合并请求合并后才会有上链操作
body: params[:content], #内容 # uploadPushInfo = {
head: params[:new_branch], #源分支 # "branch": params["base"]["ref"].to_s.split("/").last,
base: params[:branch], #目标分支 # "sha": params["pull_request"]["merge_base"],
milestone: 0 #里程碑,未与本地的里程碑关联 # "modification_lines": 1 #pull_request中没有commits数量
# }
} # else
requests_params = local_params.merge({ # uploadPushInfo = {}
assignee: current_user.try(:login), # end
assignees: [],
labels: [], # uploadPushInfo
due_date: Time.now end
})
def create_new_pr(params)
issue_params = { if params[:new_branch].present? && params[:new_branch] != params[:branch]
author_id: current_user.id, local_params = {
project_id: @project.id, title: params[:message], #标题
subject: params[:message], body: params[:content], #内容
description: params[:content], head: params[:new_branch], #源分支
assigned_to_id: nil, base: params[:branch], #目标分支
fixed_version_id: nil, milestone: 0 #里程碑,未与本地的里程碑关联
issue_tags_value: nil,
issue_classify: "pull_request", }
issue_type: "1", requests_params = local_params.merge({
tracker_id: 2, assignee: current_user.try(:login),
status_id: 1, assignees: [],
priority_id: params[:priority_id] || "2" labels: [],
} due_date: Time.now
@pull_issue = Issue.new(issue_params) })
if @pull_issue.save!
local_requests = PullRequest.new(local_params.merge(user_id: current_user.try(:id), project_id: @project.id, issue_id: @pull_issue.id)) issue_params = {
if local_requests.save author_id: current_user.id,
gitea_request = Gitea::PullRequest::CreateService.new(current_user.try(:gitea_token), @owner.login, @project.try(:identifier), requests_params).call project_id: @project.id,
if gitea_request[:status] == :success && local_requests.update_attributes(gpid: gitea_request["body"]["number"]) subject: params[:message],
local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") description: params[:content],
end assigned_to_id: nil,
end fixed_version_id: nil,
end issue_tags_value: nil,
end issue_classify: "pull_request",
end issue_type: "1",
tracker_id: 2,
end status_id: 1,
priority_id: params[:priority_id] || "2"
}
@pull_issue = Issue.new(issue_params)
if @pull_issue.save!
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
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"])
local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create")
end
end
end
end
end
end

View File

@ -5,7 +5,7 @@ class ReviewsController < ApplicationController
def create def create
return render_forbidden('您不是审查人员,无法进行审查!') if current_user&.id != @pull_request.issue.assigned_to_id return render_forbidden('您不是审查人员,无法进行审查!') if current_user&.id != @pull_request.issue.assigned_to_id
@journal, @review = Api::V1::Projects::PullRequests::Reviews::CreateService.call(@project, @pull_request, review_params, current_user) @review = Api::V1::Projects::Pulls::Reviews::CreateService.call(@project, @pull_request, review_params, current_user)
end end
private private

View File

@ -7,6 +7,7 @@ class SettingsController < ApplicationController
get_sub_competitions get_sub_competitions
get_personal_menu get_personal_menu
get_third_party get_third_party
get_third_party_new
get_top_system_notification get_top_system_notification
end end
@ -67,6 +68,24 @@ class SettingsController < ApplicationController
url: EducoderOauth.oauth_url url: EducoderOauth.oauth_url
} }
end end
def get_third_party_new
@third_party_new = []
@third_party_new << {
name: 'educoder',
url: EducoderOauth.oauth_url,
method: 'get'
}
platform_url = Rails.application.config_for(:configuration)['platform_url']
config = Rails.application.config_for(:configuration)
(config.dig("oauth").keys - ["educoder", "wechat"]).each do |provider|
@third_party_new << {
name: provider,
url: "#{platform_url}/auth/#{provider}",
method: 'get'
}
end
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

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

View File

@ -1,7 +1,7 @@
class Traces::BaseController < ApplicationController class Traces::BaseController < ApplicationController
helper_method :observed_logged_user?, :observed_user helper_method :observed_logged_user?, :observed_user
before_action :check_trace_system
def observed_user def observed_user
@_observed_user ||= (User.find_by_login(params[:user_id]) || User.find_by_id(params[:user_id])) @_observed_user ||= (User.find_by_login(params[:user_id]) || User.find_by_id(params[:user_id]))
@ -15,4 +15,12 @@ class Traces::BaseController < ApplicationController
def check_auth def check_auth
return render_forbidden unless current_user.admin? || observed_logged_user? return render_forbidden unless current_user.admin? || observed_logged_user?
end end
def check_trace_system
code, data, error = Trace::SystemInfoService.call(current_user.trace_token)
return render_ok({code: 501, data: {operate_time: data['operate_time']}, message: '系统维护中'}) if data['status'] === 0
rescue
# 这里根据需求跳转到404
return render_not_found
end
end end

View File

@ -3,6 +3,8 @@ class Traces::ProjectsController < Traces::BaseController
before_action :require_login before_action :require_login
before_action :load_project before_action :load_project
before_action :require_project_not_be_forked_project
before_action :set_trace_token_to_cookie
before_action :authorizate_user_can_edit_project!, except: [:task_results] before_action :authorizate_user_can_edit_project!, except: [:task_results]
def tasks def tasks
@ -35,7 +37,7 @@ class Traces::ProjectsController < Traces::BaseController
return render :json => {left_tasks_count: 5, data: []} if current_user.trace_user.nil? return render :json => {left_tasks_count: 5, data: []} if current_user.trace_user.nil?
code, data, error = Trace::CheckResultService.call(current_user.trace_token, @project, nil, page, limit) code, data, error = Trace::CheckResultService.call(current_user.trace_token, @project, nil, page, limit)
if code == 200 if code == 200
render :json => {left_tasks_count: 5 - @project.user_trace_tasks.size, data: data} render :json => {left_tasks_count: 5 - @project.user_trace_tasks.size, data: data, view_base: "#{Trace.trace_config[:view_domain]}/analysis_ccf/analysis-results/" }
else else
render_error("获取检测记录失败 Error:#{error}") render_error("获取检测记录失败 Error:#{error}")
end end
@ -86,4 +88,16 @@ class Traces::ProjectsController < Traces::BaseController
puts exception.message puts exception.message
normal_status(-1, exception.message) normal_status(-1, exception.message)
end end
def set_trace_token_to_cookie
cookies[:vue_admin_template_token] = {
:value => current_user&.trace_token,
:expires => 1.hours.from_now,
:domain => Trace.trace_config[:cookie_domain]
}
end
def require_project_not_be_forked_project
return render_error('fork仓库暂不支持代码溯源服务敬请谅解。') if @project.forked_from_project_id.present?
end
end end

View File

@ -1,7 +1,10 @@
class Users::IsPinnedProjectsController < Users::BaseController class Users::IsPinnedProjectsController < Users::BaseController
before_action :private_user_resources!, only: [:pin] before_action :private_user_resources!, only: [:pin]
def index def index
@is_pinned_projects = observed_user.pinned_projects.order(position: :desc, created_at: :asc).includes(project: [:project_category, :project_language, :repository]).order(position: :desc) @is_pinned_projects = observed_user.pinned_projects.left_joins(:project)
.where("projects.is_public = TRUE")
.order(position: :desc, created_at: :asc)
.includes(project: [:project_category, :project_language, :repository]).order(position: :desc)
@is_pinned_projects = kaminari_paginate(@is_pinned_projects) @is_pinned_projects = kaminari_paginate(@is_pinned_projects)
end end

View File

@ -57,6 +57,13 @@ class UsersController < ApplicationController
Cache::V2::OwnerCommonService.new(@user.id).read Cache::V2::OwnerCommonService.new(@user.id).read
end end
def action
if params[:action_id].present? && params[:action_type].present?
UserAction.create(:action_id => params[:action_id], :action_type => "#{params[:action_type]}", :user_id => User.current.id, :ip => request.remote_ip)
end
render_ok
end
def watch_users def watch_users
watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc") watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc")
if params[:search].present? if params[:search].present?
@ -109,6 +116,19 @@ class UsersController < ApplicationController
@user = current_user @user = current_user
end end
# cloudIDE saas定制
def info
@code = 1001
@message = "用户不存在"
if params[:token].present?
@user = User.try_to_autologin(params[:token])
if @user.present?
@code = 1000
@message = "success"
end
end
end
# 贴吧获取用户信接口 # 贴吧获取用户信接口
def get_user_info def get_user_info
begin begin
@ -349,7 +369,7 @@ class UsersController < ApplicationController
# 通过login参数查询头歌账号信息注册并登录 # 通过login参数查询头歌账号信息注册并登录
def autologin_register_by_educoder(edu_login) def autologin_register_by_educoder(edu_login)
req_params = { "login" => "#{edu_login}", "private_token" => "hriEn3UwXfJs3PmyXnSH" } req_params = { "login" => "#{edu_login}", "private_token" => "hriEn3UwXfJs3PmyXnqQ" }
api_url= "https://data.educoder.net" api_url= "https://data.educoder.net"
client = Faraday.new(url: api_url) client = Faraday.new(url: api_url)
response = client.public_send("get", "/api/sources/get_user_info_by_login", req_params) response = client.public_send("get", "/api/sources/get_user_info_by_login", req_params)

File diff suppressed because it is too large Load Diff

View File

@ -1126,21 +1126,21 @@ await octokit.request('POST /api/v1/yystopf/ceshi/contents/batch.json')
### 请求参数: ### 请求参数:
参数 | 必选 | 默认 | 类型 | 字段说明 参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ---------- --------- | ------- | ------- | -------- | ----------
|owner |是| |string |用户登录名 | |owner |是| |string |用户登录名 |
|repo |是| |string |项目标识identifier | |repo |是| |string |项目标识identifier |
|files.action_type |是| |string|操作类型 create: 创建 update: 更新 delete: 删除| |files.action_type |是| |string|操作类型 create: 创建 update: 更新 delete: 删除|
|files.content |是| |string|文件内容| |files.content |是| |string|文件内容|
|files.encoding |是| |string|文件编码方式 text 文本 base64 加密| |files.encoding |是| |string|文件编码方式 text 文本 base64 加密|
|files.file_path |是| |string|文件路径| |files.file_path |是| |string|文件路径|
|author_email |是| |string|作者邮箱| |author_email |否| 当前用户邮箱 |string|作者邮箱,不填时需要与作者名称同时为空|
|author_name |是| |string|作者名称| |author_name |否| 当前用户标识 |string|作者名称,不填时需要与作者邮箱同时为空|
|author_timeunix |是| |int|编码时间,精确到秒| |author_timeunix |否| 当前时间戳 |int|编码时间,精确到秒|
|committer_email |是| |string|提交者邮箱| |committer_email |否| 当前用户邮箱 |string|提交者邮箱,不填时需要与提交者名称同时为空|
|committer_name |是| |string|提交者名称| |committer_name |否| 当前用户标识 |string|提交者名称,不填时需要与提交者邮箱同时为空|
|committer_timeunix|是| |int|提交时间戳,精确到秒| |committer_timeunix|否| 当前时间戳 |int|提交时间戳,精确到秒|
|branch |是| |string|提交分支| |branch |是| |string|提交分支|
|new_branch |否| |string|如果需要创建新分支,这个需要填| |new_branch |否| |string|如果需要创建新分支,这个需要填|
|message |是| |string|提交信息| |message |是| |string|提交信息|
> 请求的JSON示例: > 请求的JSON示例:
@ -1990,25 +1990,27 @@ await octokit.request('GET /api/v1/yystopf/csfjkkj/compare.json')
|diff.files.is_created|bool|是否为新建文件| |diff.files.is_created|bool|是否为新建文件|
|diff.files.is_deleted|bool|是否为删除文件| |diff.files.is_deleted|bool|是否为删除文件|
|diff.files.is_bin|bool|是否为二进制文件| |diff.files.is_bin|bool|是否为二进制文件|
|diff.files.is_lfs_file|bool|| |diff.files.is_lfs_file|bool|是否为LFS文件|
|diff.files.is_renamed|bool|是否重命名| |diff.files.is_renamed|bool|是否重命名|
|diff.files.is_ambiguous|bool|| |diff.files.is_ambiguous|bool||
|diff.files.is_submodule|bool|是否为子模块| |diff.files.is_submodule|bool|是否为子模块|
|diff.files.sections.file_name|string|文件名称| |diff.files.sections.file_name|string|文件名称|
|diff.files.sections.name|string|| |diff.files.sections.name|string||
|diff.files.sections.lines.left_index|int|| |diff.files.sections.lines.left_index|int|文件变动之前所在行数|
|diff.files.sections.lines.right_index|int|| |diff.files.sections.lines.right_index|int|文件变动之后所在行数|
|diff.files.sections.lines.match|int|| |diff.files.sections.lines.match|int||
|diff.files.sections.lines.type|int|| |diff.files.sections.lines.type|int|文件变更类型|
|diff.files.sections.lines.content|string|| |diff.files.sections.lines.content|string|文件变更内容|
|diff.files.sections.lines.section_path|string|| |diff.files.sections.lines.section_path|string|文件路径|
|diff.files.sections.lines.section_last_left_index|int|| |diff.files.sections.lines.section_last_left_index|int||
|diff.files.sections.lines.section_last_right_index|int|| |diff.files.sections.lines.section_last_right_index|int||
|diff.files.sections.lines.section_left_index|int|| |diff.files.sections.lines.section_left_index|int|文件变更之前所在行数|
|diff.files.sections.lines.section_right_index|int|| |diff.files.sections.lines.section_right_index|int|文件变更之后所在行数(即:页面编辑器开始显示的行数)|
|diff.files.sections.lines.section_left_hunk_size|int|| |diff.files.sections.lines.section_left_hunk_size|int|文件变更之前的行数|
|diff.files.sections.lines.section_right_hunk_size|int|| |diff.files.sections.lines.section_right_hunk_size|int|文件变更之后的行数(及当前页面编辑器显示的总行数)|
|diff.files.is_incomplete|bool|是否不完整|
|diff.files.is_incomplete_line_too_long|bool|文件是否不完整是因为太长了|
|diff.files.is_protected|bool|文件是否被保护|
> 返回的JSON示例: > 返回的JSON示例:
@ -2281,7 +2283,7 @@ await octokit.request('POST /api/v1/yystopf/ceshi/webhooks.json')
|webhook.active |是| | bool | 是否激活| |webhook.active |是| | bool | 是否激活|
|webhook.branch_filter|否| |string|分支过滤| |webhook.branch_filter|否| |string|分支过滤|
|webhook.events |否| |array|触发事件| |webhook.events |否| |array|触发事件|
|webhook.type |否| gitea |string| hook类型gitea slack discord dingtalk telegram msteams feishu matrix jianmu|
触发事件字段说明 触发事件字段说明
参数| 含义| 参数| 含义|

View File

@ -0,0 +1,78 @@
# Teams
## 团队下新增所有的项目
团队下新增所有的项目
> 示例:
```shell
curl -X POST \
http://localhost:3000/api/organizations/ceshi_org/teams/28/team_projects/create_all
```
```javascript
await octokit.request('POST /api/organizations/ceshi_org/teams/28/team_projects/create_all.json')
```
### HTTP 请求
`POST /api/organizations/:organization/teams/:id/team_projects/create_all.json`
### 请求参数:
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
|organization |是| | string |组织标识 |
|id |是| | integer|团队 ID|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
<aside class="success">
Success Data.
</aside>
## 团队下删除所有的项目
团队下删除所有的项目
> 示例:
```shell
curl -X DELETE \
http://localhost:3000/api/organizations/ceshi_org/teams/28/team_projects/destroy_all
```
```javascript
await octokit.request('DELETE /api/organizations/ceshi_org/teams/28/team_projects/destroy_all.json')
```
### HTTP 请求
`DELETE /api/organizations/:organization/teams/:id/team_projects/destroy_all.json`
### 请求参数:
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
|organization |是| | string |组织标识 |
|id |是| | integer|团队 ID|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
<aside class="success">
Success Data.
</aside>

View File

@ -2304,4 +2304,189 @@ await octokit.request('GET /api/users/:login/applied_projects/:id/refuse.json')
"created_at": "2021-06-09 16:41", "created_at": "2021-06-09 16:41",
"time_ago": "7分钟前" "time_ago": "7分钟前"
} }
```
## 用户发送邮件验证码
用户发送邮件验证码
> 示例:
```shell
curl -X GET http://localhost:3000/api/v1/yystopf/send_email_vefify_code.json
```
```javascript
await octokit.request('GET /api/v1/:login/send_email_vefify_code.json')
```
### HTTP 请求
`GET /api/v1/:login/send_email_vefify_code.json`
### 请求字段说明:
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|login |string |用户标识 |
|code_type |int |10: 更新邮箱|
|email |string |邮箱|
|smscode |string |邮箱md5加密值|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
## 用户验证邮件验证码
用户验证邮件验证码
> 示例:
```shell
curl -X POST http://localhost:3000/api/v1/yystopf/check_email_verify_code.json
```
```javascript
await octokit.request('POST /api/v1/:login/check_email_verify_code.json')
```
### HTTP 请求
`POST /api/v1/:login/check_email_verify_code.json`
### 请求字段说明:
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|login |string |用户标识 |
|code_type |int |10: 更新邮箱|
|email |string |邮箱|
|code |string |邮箱验证码|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
## 用户验证密码
用户验证密码,检查是否和用户密码一致
> 示例:
```shell
curl -X POST http://localhost:3000/api/v1/yystopf/check_password.json
```
```javascript
await octokit.request('POST /api/v1/:login/check_password.json')
```
### HTTP 请求
`POST /api/v1/:login/check_password.json`
### 请求字段说明:
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|login |string |用户标识 |
|password |string |用户密码|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
## 用户验证邮箱
用户验证邮箱是否符合规范以及是否已被使用
> 示例:
```shell
curl -X POST http://localhost:3000/api/v1/yystopf/check_email.json
```
```javascript
await octokit.request('POST /api/v1/:login/check_email.json')
```
### HTTP 请求
`POST /api/v1/:login/check_email.json`
### 请求字段说明:
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|login |string |用户标识 |
|email |string |邮箱地址|
### 返回字段说明:
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```
## 用户更改邮箱
用户更改一个新的邮箱
> 示例:
```shell
curl -X PATCH http://localhost:3000/api/v1/yystopf/update_email.json
```
```javascript
await octokit.request('PATCH /api/v1/:login/update_email.json')
```
### HTTP 请求
`PATCH /api/v1/:login/update_email.json`
### 请求字段说明:
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|login |string |用户标识 |
|password |string |用户密码|
|email |string |邮箱地址|
|code |string |邮箱验证码|
> 请求的JSON示例:
```json
{
"password": "Aa19960425.",
"code": "657134",
"email": "yystopf@163.com"
}
```
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
``` ```

View File

@ -3,6 +3,8 @@ class Contents::CreateForm < BaseForm
validates :filepath, presence: true validates :filepath, presence: true
validates :new_branch, length: { maximum: 100, too_long: "过长,仅支持%{count}的长度"}
validate :check_branch validate :check_branch
def check_branch def check_branch

View File

@ -0,0 +1,2 @@
module Admins::NpsHelper
end

View File

@ -6,7 +6,7 @@ module Admins::ProjectsHelper
if owner.is_a?(User) if owner.is_a?(User)
link_to(project.owner&.real_name, "/#{project&.owner&.login}", target: '_blank') link_to(project.owner&.real_name, "/#{project&.owner&.login}", target: '_blank')
elsif owner.is_a?(Organization) elsif owner.is_a?(Organization)
link_to(project.owner&.real_name, "/organize/#{project&.owner&.login}", target: '_blank') link_to(project.owner&.real_name, "/#{project&.owner&.login}", target: '_blank')
else else
"" ""
end end

View File

@ -95,21 +95,31 @@ module ApplicationHelper
timePassed = currentTime - lastUpdateTime timePassed = currentTime - lastUpdateTime
timeIntoFormat = 0 timeIntoFormat = 0
updateAtValue = "" updateAtValue = ""
if timePassed < 0
if timePassed <= 0
updateAtValue = "刚刚" updateAtValue = "刚刚"
elsif timePassed < 2 * 1000
updateAtValue = "1秒前"
elsif timePassed < ONE_MINUTE elsif timePassed < ONE_MINUTE
updateAtValue = "1分钟前" updateAtValue = "1分钟前"
elsif timePassed < ONE_HOUR elsif timePassed < ONE_HOUR
timeIntoFormat = timePassed / ONE_MINUTE timeIntoFormat = timePassed / ONE_MINUTE
updateAtValue = timeIntoFormat.to_s + "分钟前" updateAtValue = timeIntoFormat.to_s + "分钟前"
elsif (timePassed < ONE_DAY) elsif (timePassed < ONE_DAY)
timeIntoFormat = (timePassed.to_f / ONE_HOUR).ceil timeIntoFormat = (timePassed.to_f / ONE_HOUR).round
timeIntoFormat == 1 if timeIntoFormat.to_i == 0
updateAtValue = timeIntoFormat.to_s + "小时前" updateAtValue = timeIntoFormat.to_s + "小时前"
elsif (timePassed < ONE_MONTH) elsif (timePassed < ONE_MONTH)
timeIntoFormat = (timePassed.to_f / ONE_DAY).ceil timeIntoFormat = (timePassed.to_f / ONE_DAY).round
timeIntoFormat == 1 if timeIntoFormat.to_i == 0
updateAtValue = timeIntoFormat.to_s + "天前"
elsif (timePassed < ONE_MONTH)
timeIntoFormat = (timePassed.to_f / ONE_DAY).round
timeIntoFormat == 1 if timeIntoFormat.to_i == 0
updateAtValue = timeIntoFormat.to_s + "天前" updateAtValue = timeIntoFormat.to_s + "天前"
elsif (timePassed < ONE_YEAR) elsif (timePassed < ONE_YEAR)
timeIntoFormat = (timePassed.to_f / ONE_MONTH).ceil timeIntoFormat = (timePassed.to_f / ONE_MONTH).round
timeIntoFormat == 1 if timeIntoFormat.to_i == 0
updateAtValue = timeIntoFormat.to_s + "个月前" updateAtValue = timeIntoFormat.to_s + "个月前"
else else
timeIntoFormat = timePassed / ONE_YEAR timeIntoFormat = timePassed / ONE_YEAR

View File

@ -63,6 +63,9 @@ module ProjectsHelper
project_category_id: project.project_category_id, project_category_id: project.project_category_id,
project_language_id: project.project_language_id, project_language_id: project.project_language_id,
license_id: project.license_id, license_id: project.license_id,
jianmu_devops: jianmu_devops_code(project, user),
jianmu_devops_url: jianmu_devops_url,
cloud_ide_saas_url: cloud_ide_saas_url(user),
ignore_id: project.ignore_id ignore_id: project.ignore_id
}).compact }).compact
@ -98,4 +101,53 @@ module ProjectsHelper
def render_educoder_avatar_url(project_educoder) def render_educoder_avatar_url(project_educoder)
[Rails.application.config_for(:configuration)['educoder']['cdn_url'], project_educoder&.image_url].join('/') [Rails.application.config_for(:configuration)['educoder']['cdn_url'], project_educoder&.image_url].join('/')
end end
# 静默登录方式:
#
# 数据格式为JSON
# {
# "userId": "xxx", // 用户唯一标识
# "ref": "xxx", // 仓库唯一标识
# "owner": "xxx", // 用户登录名或组织账号
# "timestamp": xxx // 当前时间戳,单位:毫秒
# }
# 加密方式把数据序列化成JSON字符串用Client Secret和固定IV5183666c72eec9e4对称加密模式AES-256-CBC
#
# API
# GEThttps://ci-v3.test.jianmuhub.com/oauth2/authorize?code=${encode(密文)}
def jianmu_devops_code(project, user)
if user.admin? || project.member?(user.id)
data = { userId: user.id, ref: project.identifier, owner: project.owner.login, timestamp: Time.now.to_i * 1000 }
# uid = EduSetting.get("jianmu_oauth2_uid") || 'oedKx4v-WyAfu2oy_AsFpFQCH_-g91ms0PQKN7YcEuw'
# app = Doorkeeper::Application.find_by(uid: uid)
key = 'bf3c199c2470cb477d907b1e0917c17b'
aes_encrypt(key, data.to_json)
end
end
def jianmu_devops_url
EduSetting.get("jianmu_devops_url") || "https://ci-v3.test.jianmuhub.com"
end
def cloud_ide_saas_url(user)
"" unless user.logged?
token = Token.get_token_from_user(user, "autologin")
oauth_url = "#{Rails.application.config_for(:configuration)['platform_url']}/api/users/info.json"
saas_url = EduSetting.get("cloud_ide_saas_url") || "https://saasfactory.test.opentrs.com"
"#{saas_url}/oauth/login?product_account_id=PA1001218&tenant_code=TI1001383&oauth_url=#{oauth_url}&token=#{token.value}"
end
def aes_encrypt(key, des_text)
# des_text='{"access_key_id":"STS.NTuC9RVmWfJqj3JkcMzPnDf7X","access_key_secret":"E8NxRZWGNxxMfwgt5nFLnBFgg6AzgXCZkSNCyqygLuHM","end_point":"oss-accelerate.aliyuncs.com","security_token":"CAIS8gF1q6Ft5B2yfSjIr5fACIPmu7J20YiaaBX7j2MYdt9Cq6Ocujz2IHhMenVhA+8Wv/02n2hR7PcYlq9IS55VWEqc/VXLaywQo22beIPkl5Gfz95t0e+IewW6Dxr8w7WhAYHQR8/cffGAck3NkjQJr5LxaTSlWS7OU/TL8+kFCO4aRQ6ldzFLKc5LLw950q8gOGDWKOymP2yB4AOSLjIx6lAt2T8vs/7hmZPFukSFtjCglL9J/baWC4O/csxhMK14V9qIx+FsfsLDqnUIs0YWpf0p3P0doGyf54vMWUM05A6dduPS7txkLAJwerjVl1/ADxc0/hqAASXhPeiktbmDjwvnSn4iKcSGQ+xoQB468eHXNdvf13dUlbbE1+JhRi0pZIB2UCtN9oTsLHcwIHt+EJaoMd3+hGwPVmvHSXzECDFHylZ8l/pzTwlE/aCtZyVmI5cZEvmWu2xBa3GRbULo7lLvyeX1cHTVmVWf4Nk6D09PzTU8qlAj","bucket":"edu-bigfiles1","region":"oss-cn-hangzhou","callback_url":"https://data.educoder.net/api/buckets/callback.json","bucket_host":"data.educoder.net"}'
# des = OpenSSL::Cipher::Cipher.new('aes-256-ctr')
des = OpenSSL::Cipher.new('AES-256-CBC')
des.encrypt
# des.padding =
des.key = key
des.iv = "5183666c72eec9e4"
result = des.update(des_text)
result << des.final
Base64.strict_encode64 result
end
end end

View File

@ -10,7 +10,7 @@ module RepositoriesHelper
end end
def download_type(str) def download_type(str)
default_type = %w(xlsx xls ppt pptx pdf zip 7z rar exe pdb obj idb RData rdata doc docx mpp vsdx dot otf eot ttf woff woff2 mp4 mov wmv flv mpeg avi avchd webm mkv apk) default_type = %w(ppt pptx pdf zip 7z rar exe pdb obj idb RData rdata doc docx mpp vsdx dot otf eot ttf woff woff2 mp4 mov wmv flv mpeg avi avchd webm mkv apk xlsx xls)
default_type.include?(str&.downcase) || str.blank? default_type.include?(str&.downcase) || str.blank?
end end
@ -91,7 +91,7 @@ module RepositoriesHelper
new_r_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{path_current}/#{path_last}&ref=#{ref}"].join new_r_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{path_current}/#{path_last}&ref=#{ref}"].join
end end
content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"") content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"")
rescue rescue
next next
end end
end end

View File

@ -123,7 +123,7 @@ module TagChosenHelper
cache_key = "project-#{project.id}/all_milestones/size-#{project.versions.size}/#{project.versions.maximum('updated_on')}" cache_key = "project-#{project.id}/all_milestones/size-#{project.versions.size}/#{project.versions.maximum('updated_on')}"
Rails.cache.fetch(cache_key) do Rails.cache.fetch(cache_key) do
project.versions.select(:id, :name, :status).collect do |event| project.versions.opening.select(:id, :name, :status).collect do |event|
{ {
id: event.id, id: event.id,
name: event.name, name: event.name,

View File

@ -62,7 +62,7 @@ module Gitea
file_params = {} file_params = {}
file_params = file_params.merge(branch: @params[:branch]) unless @params[:branch].blank? file_params = file_params.merge(branch: @params[:branch]) unless @params[:branch].blank?
file_params = file_params.merge(new_branch: @params[:new_branch]) unless @params[:new_branch].blank? file_params = file_params.merge(new_branch: @params[:new_branch]) unless @params[:new_branch].blank?
file_params = file_params.merge(content: Base64.encode64(@params[:content] || "")) file_params = file_params.merge(content: @params[:content] || "")
file_params = file_params.merge(message: @params[:message]) unless @params[:message].blank? file_params = file_params.merge(message: @params[:message]) unless @params[:message].blank?
file_params = file_params.merge(committer: @params[:committer]) file_params = file_params.merge(committer: @params[:committer])
file_params file_params

View File

@ -1,7 +1,7 @@
class MigrateRemoteRepositoryJob < ApplicationJob class MigrateRemoteRepositoryJob < ApplicationJob
queue_as :default queue_as :default
def perform(repo_id, token, params) def perform(repo_id, token, user_id, params)
repo = Repository.find_by(id: repo_id) repo = Repository.find_by(id: repo_id)
return if repo.blank? return if repo.blank?
@ -12,6 +12,10 @@ class MigrateRemoteRepositoryJob < ApplicationJob
if gitea_repository[0]==201 if gitea_repository[0]==201
repo&.project&.update_columns(gpid: gitea_repository[2]["id"]) repo&.project&.update_columns(gpid: gitea_repository[2]["id"])
repo&.mirror&.succeeded! repo&.mirror&.succeeded!
## open jianmu devops
project_id = repo&.project&.id
puts "############ mirror project_id,user_id: #{project_id},#{user_id} ############"
OpenProjectDevOpsJob.perform_later(project_id, user_id) if project_id.present? && user_id.present?
puts "############ mirror status: #{repo.mirror.status} ############" puts "############ mirror status: #{repo.mirror.status} ############"
else else
repo&.mirror&.failed! repo&.mirror&.failed!

View File

@ -0,0 +1,16 @@
class OpenProjectDevOpsJob < ApplicationJob
include ProjectsHelper
queue_as :message
def perform(project_id, user_id)
project = Project.find_by(id: project_id)
user = User.find_by(id: user_id)
code = jianmu_devops_code(project, user)
uri = URI.parse("#{jianmu_devops_url}/activate?code=#{URI.encode_www_form_component(code)}")
response = Net::HTTP.get_response(uri)
puts "jianmu_devops_url response.code ===== #{response.code}"
SendTemplateMessageJob.perform_later('ProjectOpenDevOps', user_id, project_id)
end
end

View File

@ -217,6 +217,14 @@ class SendTemplateMessageJob < ApplicationJob
receivers = project&.all_managers.where.not(id: operator&.id) receivers = project&.all_managers.where.not(id: operator&.id)
receivers_string, content, notification_url = MessageTemplate::ProjectPraised.get_message_content(receivers, operator, project) receivers_string, content, notification_url = MessageTemplate::ProjectPraised.get_message_content(receivers, operator, project)
Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {operator_id: operator.id, project_id: project.id}) Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {operator_id: operator.id, project_id: project.id})
when 'ProjectOpenDevOps'
operator_id, project_id = args[0], args[1]
operator = User.find_by_id(operator_id)
project = Project.find_by_id(project_id)
return unless operator.present? && project.present?
receivers = User.where(id: operator.id)
receivers_string, content, notification_url = MessageTemplate::ProjectOpenDevOps.get_message_content(receivers, operator, project)
Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {operator_id: operator.id, project_id: project.id})
when 'ProjectPullRequest' when 'ProjectPullRequest'
operator_id, pull_request_id = args[0], args[1] operator_id, pull_request_id = args[0], args[1]
operator = User.find_by_id(operator_id) operator = User.find_by_id(operator_id)

View File

@ -1,6 +1,6 @@
module CustomRegexp module CustomRegexp
PHONE = /1\d{10}/ PHONE = /1\d{10}/
EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/ EMAIL = /\A[a-zA-Z0-9]+([._\-\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/
LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}\z/ #只含有数字、字母、下划线不能以下划线开头和结尾 LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}\z/ #只含有数字、字母、下划线不能以下划线开头和结尾
LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/ LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/

View File

@ -15,7 +15,7 @@ module EducoderOauth::Service
result result
rescue Exception => e rescue Exception => e
raise Educoder::TipException.new(e.message) raise Gitlink::TipException.new(e.message)
end end
end end
@ -27,7 +27,7 @@ module EducoderOauth::Service
result = client.auth_code.get_token(code, redirect_uri: EducoderOauth.redirect_uri).to_hash result = client.auth_code.get_token(code, redirect_uri: EducoderOauth.redirect_uri).to_hash
return result return result
rescue Exception => e rescue Exception => e
raise Educoder::TipException.new(e.message) raise Gitlink::TipException.new(e.message)
end end
end end

View File

@ -1,11 +1,33 @@
class UserMailer < ApplicationMailer class UserMailer < ApplicationMailer
# 注意:这个地方一定要和你的邮箱服务域名一致 # 注意:这个地方一定要和你的邮箱服务域名一致
default from: 'notification@trustie.org' # default from: 'notification@trustie.org'
# default from: 'noreply@gitlink.org.cn'
# 用户注册验证码 default from: 'GitLink <noreply@gitlink.org.cn>'
def register_email(mail, code)
@code = code # 用户注册验证码
mail(to: mail, subject: 'Gitink | 注册验证码') def register_email(mail, code)
end @code = code
mail(to: mail, subject: 'Gitink | 注册验证码')
end end
# 用户找回密码
def find_password(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 找回密码验证码')
end
# 用户绑定邮箱
def bind_email(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 绑定邮箱验证码')
end
def update_email(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 更改邮箱验证码')
end
def feedback_email(mail, title, content)
mail(to: mail, subject: title, content_type: "text/html", body: content)
end
end

View File

@ -17,30 +17,69 @@ module ProjectOperable
owner.build_permit_team_projects!(id) owner.build_permit_team_projects!(id)
# 避免自己创建的项目,却无法拥有访问权,因为该用户所在团队暂未获得项目访问权 # 避免自己创建的项目,却无法拥有访问权,因为该用户所在团队暂未获得项目访问权
return if creator.nil? || owner.is_owner?(creator.id) return if creator.nil? || owner.is_owner?(creator.id)
add_member!(creator.id, "Manager") add_member!(creator.id, "Manager") if creator.is_a?(User)
end end
def add_member!(user_id, role_name='Developer') def add_member!(user_id, role_name='Developer')
if self.owner.is_a?(Organization) if self.owner.is_a?(Organization)
case role_name case role_name
when 'Manager' when 'Manager'
# 构建相应的团队
team = self.owner.teams.admin.take team = self.owner.teams.admin.take
team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
OrganizationUser.build(self.user_id, user_id) gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 新增对应的团队成员
team_user = TeamUser.build(self.user_id, user_id, team.id) team_user = TeamUser.build(self.user_id, user_id, team.id)
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
# 确保组织成员中有该用户
OrganizationUser.build(self.user_id, user_id)
when 'Developer' when 'Developer'
# 构建相应的团队
team = self.owner.teams.write.take team = self.owner.teams.write.take
team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
OrganizationUser.build(self.user_id, user_id) gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 新增对应的团队成员
team_user = TeamUser.build(self.user_id, user_id, team.id) team_user = TeamUser.build(self.user_id, user_id, team.id)
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
# 确保组织成员中有该用户
OrganizationUser.build(self.user_id, user_id)
when 'Reporter' when 'Reporter'
# 构建相应的团队
team = self.owner.teams.read.take team = self.owner.teams.read.take
team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
OrganizationUser.build(self.user_id, user_id) gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 新增对应的团队成员
team_user = TeamUser.build(self.user_id, user_id, team.id) team_user = TeamUser.build(self.user_id, user_id, team.id)
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
# 确保组织成员中有该用户
OrganizationUser.build(self.user_id, user_id)
end end
end end
member = members.create!(user_id: user_id, team_user_id: team_user&.id) member = members.create!(user_id: user_id, team_user_id: team_user&.id)
@ -71,26 +110,70 @@ module ProjectOperable
def change_member_role!(user_id, role) def change_member_role!(user_id, role)
member = self.member(user_id) member = self.member(user_id)
# 所有者为组织,并且该用户属于组织成员
if self.owner.is_a?(Organization) && member.team_user.present? if self.owner.is_a?(Organization) && member.team_user.present?
case role&.name case role&.name
when 'Manager' when 'Manager'
# 构建相应的团队
team = self.owner.teams.admin.take team = self.owner.teams.admin.take
team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 更改对应的团队成员
team_user = member.team_user
$gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
# 确保组织成员中有该用户
OrganizationUser.build(self.user_id, user_id) OrganizationUser.build(self.user_id, user_id)
team_user = member.team_user.update(team_id: team&.id)
when 'Developer' when 'Developer'
# 构建相应的团队
team = self.owner.teams.write.take team = self.owner.teams.write.take
team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
$gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 更改对应的团队成员
team_user = member.team_user
$gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
OrganizationUser.build(self.user_id, user_id) OrganizationUser.build(self.user_id, user_id)
team_user = member.team_user.update(team_id: team&.id)
when 'Reporter' when 'Reporter'
# 构建相应的团队
team = self.owner.teams.read.take team = self.owner.teams.read.take
team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team if team.nil?
TeamProject.build(self.user_id, team.id, self.id) team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
end
# 设置项目在团队中的访问权限
team_project = TeamProject.build(self.user_id, team.id, self.id)
tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
# 更改对应的团队成员
team_user = member.team_user
$gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
$gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
# 确保组织成员中有该用户
OrganizationUser.build(self.user_id, user_id) OrganizationUser.build(self.user_id, user_id)
team_user = member.team_user.update(team_id: team&.id)
end end
end end
member.member_roles.last.update_attributes!(role: role) member.member_roles.last.update_attributes!(role: role)

21
app/models/feedback.rb Normal file
View File

@ -0,0 +1,21 @@
# == Schema Information
#
# Table name: feedbacks
#
# id :integer not null, primary key
# user_id :integer
# content :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_feedbacks_on_user_id (user_id)
#
class Feedback < ApplicationRecord
belongs_to :user
has_many :feedback_message_histories, dependent: :destroy
end

View File

@ -0,0 +1,36 @@
# == Schema Information
#
# Table name: feedback_message_histories
#
# id :integer not null, primary key
# feedback_id :integer
# user_id :integer
# title :string(255)
# content :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_feedback_message_histories_on_feedback_id (feedback_id)
# index_feedback_message_histories_on_user_id (user_id)
#
class FeedbackMessageHistory < ApplicationRecord
belongs_to :feedback
belongs_to :user
before_validation :send_meessage_email, on: :create
private
def send_meessage_email
unless UserMailer.feedback_email(feedback&.user&.mail, title, content).deliver_now
errors[:title] << '邮件发送失败!'
end
rescue
errors[:title] << '邮件发送失败!'
end
end

View File

@ -11,4 +11,7 @@
class Ignore < ApplicationRecord class Ignore < ApplicationRecord
include Projectable include Projectable
validates :name, :content, presence: true
validates :name, uniqueness: { case_sensitive: false }
end end

View File

@ -69,7 +69,6 @@ class Issue < ApplicationRecord
has_many :issue_tags, through: :issue_tags_relates has_many :issue_tags, through: :issue_tags_relates
has_many :issue_times, dependent: :destroy has_many :issue_times, dependent: :destroy
has_many :issue_depends, dependent: :destroy has_many :issue_depends, dependent: :destroy
has_many :reviews, dependent: :destroy
scope :issue_includes, ->{includes(:user)} scope :issue_includes, ->{includes(:user)}
scope :issue_many_includes, ->{includes(journals: :user)} scope :issue_many_includes, ->{includes(journals: :user)}
scope :issue_issue, ->{where(issue_classify: [nil,"issue"])} scope :issue_issue, ->{where(issue_classify: [nil,"issue"])}

View File

@ -13,6 +13,15 @@
# comments_count :integer default("0") # comments_count :integer default("0")
# reply_id :integer # reply_id :integer
# review_id :integer # review_id :integer
# commit_id :string(255)
# diff :text(4294967295)
# line_code :string(255)
# path :string(255)
# state :integer default("0")
# resolve_at :datetime
# resolveer_id :integer
# need_respond :boolean default("0")
# updated_on :datetime
# #
# Indexes # Indexes
# #
@ -24,15 +33,22 @@
# #
class Journal < ApplicationRecord class Journal < ApplicationRecord
serialize :diff, JSON
alias_attribute :note, :notes
belongs_to :user belongs_to :user
belongs_to :issue, foreign_key: :journalized_id, :touch => true belongs_to :issue, foreign_key: :journalized_id, :touch => true, optional: true
belongs_to :journalized, polymorphic: true
belongs_to :review, optional: true
belongs_to :resolveer, class_name: 'User', foreign_key: :resolveer_id, optional: true
has_many :journal_details, :dependent => :delete_all has_many :journal_details, :dependent => :delete_all
has_many :attachments, as: :container, dependent: :destroy has_many :attachments, as: :container, dependent: :destroy
has_many :children_journals, class_name: 'Journal', foreign_key: :parent_id
scope :journal_includes, ->{includes(:user, :journal_details, :attachments)} scope :journal_includes, ->{includes(:user, :journal_details, :attachments)}
scope :parent_journals, ->{where(parent_id: nil)} scope :parent_journals, ->{where(parent_id: nil)}
scope :children_journals, lambda{|journal_id| where(parent_id: journal_id)} scope :children_journals, lambda{|journal_id| where(parent_id: journal_id)}
enum state: {opened: 0, resolved: 1, disabled: 2}
def is_journal_detail? def is_journal_detail?
self.notes.blank? && self.journal_details.present? self.notes.blank? && self.journal_details.present?

View File

@ -11,4 +11,8 @@
class License < ApplicationRecord class License < ApplicationRecord
include Projectable include Projectable
validates :name, :content, presence: true
validates :name, uniqueness: { case_sensitive: false }
end end

5
app/models/mark_file.rb Normal file
View File

@ -0,0 +1,5 @@
class MarkFile < ApplicationRecord
belongs_to :pull_request
end

View File

@ -13,6 +13,7 @@
# #
class MessageTemplate < ApplicationRecord class MessageTemplate < ApplicationRecord
# self.inheritance_column = nil
PLATFORM = 'GitLink' PLATFORM = 'GitLink'
def self.build_init_data def self.build_init_data
@ -51,6 +52,7 @@ class MessageTemplate < ApplicationRecord
email_html = File.read("#{email_template_html_dir}/project_milestone_completed.html") email_html = File.read("#{email_template_html_dir}/project_milestone_completed.html")
self.create(type: 'MessageTemplate::ProjectMilestoneCompleted', sys_notice: '在 <b>{nickname}/{repository}</b> 仓库,里程碑 <b>{name}</b> 的完成度已达到100%', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}', email: email_html, email_title: "#{PLATFORM}: 仓库 {nickname}/{repository} 有里程碑已完成") self.create(type: 'MessageTemplate::ProjectMilestoneCompleted', sys_notice: '在 <b>{nickname}/{repository}</b> 仓库,里程碑 <b>{name}</b> 的完成度已达到100%', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}', email: email_html, email_title: "#{PLATFORM}: 仓库 {nickname}/{repository} 有里程碑已完成")
self.create(type: 'MessageTemplate::ProjectPraised', sys_notice: '<b>{nickname1}</b> 点赞了你管理的仓库 <b>{nickname2}/{repository}</b>', notification_url: '{baseurl}/{login}') self.create(type: 'MessageTemplate::ProjectPraised', sys_notice: '<b>{nickname1}</b> 点赞了你管理的仓库 <b>{nickname2}/{repository}</b>', notification_url: '{baseurl}/{login}')
self.create(type: 'MessageTemplate::ProjectOpenDevOps', sys_notice: '您的仓库 <b>{repository}</b> 已成功开通引擎服务,可通过简单的节点编排完成自动化集成与部署。欢迎体验!', notification_url: '{baseurl}/{owner}/{identifier}/devops')
email_html = File.read("#{email_template_html_dir}/project_pull_request.html") email_html = File.read("#{email_template_html_dir}/project_pull_request.html")
self.create(type: 'MessageTemplate::ProjectPullRequest', sys_notice: '{nickname1}在 <b>{nickname2}/{repository}</b> 提交了一个合并请求:<b>{title}</b>', notification_url: '{baseurl}/{owner}/{identifier}/pulls/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 提交了一个合并请求") self.create(type: 'MessageTemplate::ProjectPullRequest', sys_notice: '{nickname1}在 <b>{nickname2}/{repository}</b> 提交了一个合并请求:<b>{title}</b>', notification_url: '{baseurl}/{owner}/{identifier}/pulls/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 提交了一个合并请求")
email_html = File.read("#{email_template_html_dir}/project_role.html") email_html = File.read("#{email_template_html_dir}/project_role.html")
@ -113,6 +115,6 @@ class MessageTemplate < ApplicationRecord
end end
def simple_type def simple_type
self.type.split("::")[-1] self.type.to_s.split("::")[-1]
end end
end end

View File

@ -0,0 +1,28 @@
# == Schema Information
#
# Table name: message_templates
#
# id :integer not null, primary key
# type :string(255)
# sys_notice :text(65535)
# email :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
# notification_url :string(255)
# email_title :string(255)
#
# 我管理的仓库项目设置被更改
class MessageTemplate::ProjectOpenDevOps < MessageTemplate
# MessageTemplate::ProjectOpenDevOps.get_message_content(User.where(login: 'yystopf'))
def self.get_message_content(receivers, user, project)
return '', '', '' if receivers.blank?
content = sys_notice.gsub('{repository}', project&.name)
url = notification_url.gsub('{owner}', project&.owner&.login).gsub('{identifier}', project&.identifier)
return receivers_string(receivers), content, url
rescue => e
Rails.logger.info("MessageTemplate::ProjectOpenDevOps.get_message_content [ERROR] #{e}")
return '', '', ''
end
end

View File

@ -0,0 +1,28 @@
# == Schema Information
#
# Table name: ob_repository_syncs
#
# id :integer not null, primary key
# project_id :integer
# user_id :integer
# name :string(255)
# github_address :string(255)
# gitee_address :string(255)
# github_token :string(255)
# gitee_token :string(255)
# sync_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_ob_repository_syncs_on_project_id (project_id)
# index_ob_repository_syncs_on_user_id (user_id)
#
class ObRepositorySync < ApplicationRecord
belongs_to :project
belongs_to :user
has_many :ob_repository_sync_jobs, dependent: :destroy
end

View File

@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: ob_repository_sync_jobs
#
# id :integer not null, primary key
# ob_repository_sync_id :integer
# github_branch :string(255)
# gitee_branch :string(255)
# gitlink_branch :string(255)
# job_type :string(255)
# base :string(255)
# job_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_ob_repository_sync_jobs_on_ob_repository_sync_id (ob_repository_sync_id)
#
class ObRepositorySyncJob < ApplicationRecord
belongs_to :ob_repository_sync
end

View File

@ -0,0 +1,27 @@
# == Schema Information
#
# Table name: open_users
#
# id :integer not null, primary key
# user_id :integer
# type :string(255)
# uid :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# extra :text(65535)
#
# Indexes
#
# index_open_users_on_type_and_uid (type,uid) UNIQUE
# index_open_users_on_user_id (user_id)
#
class OpenUsers::Gitee < OpenUser
def nickname
extra&.[]('nickname')
end
def en_type
'gitee'
end
end

View File

@ -0,0 +1,27 @@
# == Schema Information
#
# Table name: open_users
#
# id :integer not null, primary key
# user_id :integer
# type :string(255)
# uid :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# extra :text(65535)
#
# Indexes
#
# index_open_users_on_type_and_uid (type,uid) UNIQUE
# index_open_users_on_user_id (user_id)
#
class OpenUsers::Github < OpenUser
def nickname
extra&.[]('name')
end
def en_type
'github'
end
end

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