Merge pull request '20221101版本' (#312) from Trustie/forgeplus:trustie_server into master

This commit is contained in:
xxq250 2022-11-03 11:11:34 +08:00
commit 991516ed70
253 changed files with 49601 additions and 1249077 deletions

View File

@ -135,4 +135,4 @@ gem 'doorkeeper'
gem 'doorkeeper-jwt'
gem 'gitea-client', '~> 0.10.2'
gem 'gitea-client', '~> 0.10.5'

View File

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

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-confirm.min";
@import "bootstrap-datetimepicker.min";
@import "bootstrap/bootstrap-toggle.min";
@import "codemirror/lib/codemirror";
@import "editormd/css/editormd.min";
@ -203,4 +204,14 @@ input.form-control {
color: #23272B;
font-size: 1rem;
}
}
.table th, .table td {
padding: 0.75rem 0.1rem;
vertical-align: top;
border-top: 1px solid #dee2e6;
}
.table .thead-light th{
white-space: nowrap;
}

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 = {
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)

View File

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

View File

@ -7,12 +7,12 @@ class Admins::MessageTemplatesController < Admins::BaseController
end
def new
@message_template = MessageTemplate::CustomTip.new
@message_template = MessageTemplate.new
end
def create
@message_template = MessageTemplate::CustomTip.new(ignore_params)
def create
@message_template = MessageTemplate::CustomTip.new(message_template_params)
@message_template.type = "MessageTemplate::CustomTip"
if @message_template.save!
redirect_to admins_message_templates_path
flash[:success] = "创建消息模板成功"
@ -47,7 +47,9 @@ class Admins::MessageTemplatesController < Admins::BaseController
private
def message_template_params
params.require(@message_template.type.split("::").join("_").underscore.to_sym).permit!
# type = @message_template.present? ? @message_template.type : "MessageTemplate::CustomTip"
# params.require(type.split("::").join("_").underscore.to_sym).permit!
params.require(:message_template).permit!
end
def get_template

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
class Admins::Topic::BannersController < Admins::Topic::BaseController
before_action :find_banner, only: [:edit, :update, :destroy]
def index
def index
@banners = paginate(::Topic::Banner)
@banners = paginate(::Topic::Banner.where("title like ?", "%#{params[:search]}%")) if params[:search].present?
end
def new

View File

@ -2,6 +2,7 @@ class Api::V1::BaseController < ApplicationController
include Api::ProjectHelper
include Api::UserHelper
include Api::PullHelper
# before_action :doorkeeper_authorize!
# skip_before_action :user_setup
@ -30,18 +31,25 @@ class Api::V1::BaseController < ApplicationController
# 具有对仓库的管理权限
def require_manager_above
@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
# 具有对仓库的操作权限
def require_operate_above
@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
# 具有对仓库的访问权限
def require_public_and_member_above
@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

View File

@ -1,9 +1,13 @@
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
@result_object = Api::V1::Projects::Contents::BatchCreateService.call(@project, batch_content_params, current_user&.gitea_token)
puts @result_object
@batch_content_params = batch_content_params
# 处理下author和committer信息如果没传则默认为当前用户信息
@batch_content_params.merge!(author_email: current_user.mail, author_name: current_user.login) if batch_content_params[:author_email].blank? && batch_content_params[:author_name].blank?
@batch_content_params.merge!(committer_email: current_user.mail, committer_name: current_user.login) if batch_content_params[:committer_email].blank? && batch_content_params[:committer_name].blank?
@result_object = Api::V1::Projects::Contents::BatchCreateService.call(@project, @batch_content_params, @project.owner.gitea_token)
end
private

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

View File

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

View File

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

View File

@ -20,9 +20,9 @@ class ApplicationController < ActionController::Base
# TODO
# check sql query time
before_action do
if request.subdomain === 'testforgeplus' || request.subdomain === "profiler"
Rack::MiniProfiler.authorize_request
end
# if request.subdomain === 'testforgeplus' || request.subdomain === "profiler"
# Rack::MiniProfiler.authorize_request
# 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)
@ -173,6 +173,25 @@ class ApplicationController < ActionController::Base
tip_exception(401, "请登录后再操作") unless User.current.logged?
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
tip_exception(411, "请完善资料后再操作") unless User.current.profile_is_completed?
end
@ -277,11 +296,11 @@ class ApplicationController < ActionController::Base
end
end
# if !User.current.logged? && Rails.env.development?
# user = User.find 1
# User.current = user
# start_user_session(user)
# end
if !User.current.logged? && Rails.env.development?
user = User.find 1
User.current = user
start_user_session(user)
end
# 测试版前端需求
@ -619,7 +638,7 @@ class ApplicationController < ActionController::Base
def kaminari_paginate(relation)
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 > 25) ? 25 : limit.to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i
relation.page(page).per(limit)
@ -704,9 +723,15 @@ class ApplicationController < ActionController::Base
# @project = nil if !@project.is_public?
# render_forbidden and return
else
logger.info "###########project not found"
@project = nil
render_not_found and return
if @project.present?
logger.info "########### has project and but can't read project"
@project = nil
render_forbidden and return
else
logger.info "###########project not found"
@project = nil
render_not_found and return
end
end
@project
end

View File

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

View File

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

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
@observe_user
end
# 是否具有查看用户或编辑用户的权限
def check_auth_for_observe_user
return render_forbidden unless current_user.admin? || @observe_user.id == current_user.id
end
def strip(str)
str.to_s.strip.presence
end
end

View File

@ -16,6 +16,7 @@ module RegisterHelper
return unless user.valid?
interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password})
result ={}
if interactor.success?
gitea_user = interactor.result
result = Gitea::User::GenerateTokenService.call(username, password)
@ -26,7 +27,7 @@ module RegisterHelper
result[:user] = {id: user.id, token: user.gitea_token}
end
else
result[:message] = interactor.error
result[:message] = interactor.result[:message]
end
result
end

View File

@ -3,7 +3,7 @@ module RenderHelper
render json: { status: 0, message: 'success' }.merge(data)
end
def render_error(message = '')
def render_error(message = '', status = -1)
render json: { status: status, message: message }
end

View File

@ -11,6 +11,7 @@ module Repository::LanguagesPercentagable
end
def update_project_language(language)
return if @project.project_language.present?
db_language = ProjectLanguage.find_or_create_by!(name: language.keys.first.downcase.upcase_first)
@project.update_column(:project_language_id, db_language.id)
rescue

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
@members = paginate(scope)
if @project.owner.is_a?(Organization) && (params[:page].to_i == 1 || params[:page].blank?) && !@project.members.exists?(user_id: current_user.id)
@current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take
end
end
def remove
@ -61,11 +64,14 @@ class MembersController < ApplicationController
end
def check_member_exists!
return render_error("user_id为#{params[:user_id]}的用户已经是项目成员") if member_exists?
@current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take
return render_error("#{@user&.nickname}已经是项目成员") if member_exists? || (params[:user_id].to_i == current_user.id && @current_user_header_team.present?)
end
def check_member_not_exists!
return render_error("user_id为#{params[:user_id]}的用户还不是项目成员") unless member_exists?
@current_user_header_team = Team.joins(:team_users, :team_projects).where(team_projects: {project_id: @project.id}, team_users: {user_id: current_user.id}).order(authorize: :desc).take
return render_error("用户为组织成员,请到组织下操作!") if (params[:user_id].to_i == current_user.id && @current_user_header_team.present?) && !member_exists?
return render_error("#{@user&.nickname}还不是项目成员") unless member_exists?
end
def check_user_profile_completed

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

@ -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
@organization_users = @organization.organization_users.includes(:user)
search = params[:search].to_s.downcase
user_condition_users = User.like(search).to_sql
team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql
users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users")
@organization_users = @organization_users.where(user_id: users).distinct
if params[:search].present?
search = params[:search].to_s.downcase
user_condition_users = User.like(search).to_sql
team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql
users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users")
@organization_users = @organization_users.where(user_id: users).distinct
end
@organization_users = kaminari_paginate(@organization_users)
end

View File

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

View File

@ -21,6 +21,17 @@ class Organizations::TeamProjectsController < Organizations::BaseController
tip_exception(e.message)
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
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do
@ -33,6 +44,17 @@ class Organizations::TeamProjectsController < Organizations::BaseController
tip_exception(e.message)
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
def load_organization
@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
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?
end

View File

@ -9,7 +9,7 @@ class Projects::WebhooksController < Projects::BaseController
def create
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?
form = Projects::Webhooks::CreateForm.new(webhook_params)
return render json: {status: -1, message: form.errors} unless form.validate!

View File

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

View File

@ -67,6 +67,8 @@ class PullRequestsController < ApplicationController
@pull_request, @gitea_pull_request = PullRequests::CreateService.call(current_user, @owner, @project, params)
if @gitea_pull_request[:status] == :success
@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('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)}"
@ -112,6 +114,9 @@ class PullRequestsController < ApplicationController
end
end
reviewers = User.where(id: params[:reviewer_ids])
@pull_request.reviewers = reviewers
if @issue.update_attributes(@issue_params)
if @pull_request.update_attributes(@local_params.compact)
gitea_pull = Gitea::PullRequest::UpdateService.call(@owner.login, @repository.identifier,
@ -181,7 +186,7 @@ class PullRequestsController < ApplicationController
@issue_assign_to = @issue.get_assign_user
@gitea_pull = Gitea::PullRequest::GetService.call(@owner.login,
@repository.identifier, @pull_request.gitea_number, current_user&.gitea_token)
@last_review = @pull_request.issue.reviews.take
@last_review = @pull_request.reviews.take
end
def pr_merge
@ -268,7 +273,7 @@ class PullRequestsController < ApplicationController
def get_relatived
@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_priories = IssuePriority&.select(:id,:name, :position).as_json
end

View File

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

View File

@ -1,7 +1,7 @@
class Traces::BaseController < ApplicationController
helper_method :observed_logged_user?, :observed_user
before_action :check_trace_system
def observed_user
@_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
return render_forbidden unless current_user.admin? || observed_logged_user?
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

View File

@ -3,6 +3,8 @@ class Traces::ProjectsController < Traces::BaseController
before_action :require_login
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]
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?
code, data, error = Trace::CheckResultService.call(current_user.trace_token, @project, nil, page, limit)
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
render_error("获取检测记录失败 Error:#{error}")
end
@ -86,4 +88,16 @@ class Traces::ProjectsController < Traces::BaseController
puts exception.message
normal_status(-1, exception.message)
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

View File

@ -1,7 +1,10 @@
class Users::IsPinnedProjectsController < Users::BaseController
before_action :private_user_resources!, only: [:pin]
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)
def index
@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)
end

View File

@ -109,6 +109,19 @@ class UsersController < ApplicationController
@user = current_user
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
begin
@ -349,7 +362,7 @@ class UsersController < ApplicationController
# 通过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"
client = Faraday.new(url: api_url)
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 |用户登录名 |
|repo |是| |string |项目标识identifier |
|files.action_type |是| |string|操作类型 create: 创建 update: 更新 delete: 删除|
|files.content |是| |string|文件内容|
|files.encoding |是| |string|文件编码方式 text 文本 base64 加密|
|files.file_path |是| |string|文件路径|
|author_email |是| |string|作者邮箱|
|author_name |是| |string|作者名称|
|author_timeunix |是| |int|编码时间,精确到秒|
|committer_email |是| |string|提交者邮箱|
|committer_name |是| |string|提交者名称|
|committer_timeunix|是| |int|提交时间戳,精确到秒|
|branch |是| |string|提交分支|
|new_branch |否| |string|如果需要创建新分支,这个需要填|
|message |是| |string|提交信息|
|owner |是| |string |用户登录名 |
|repo |是| |string |项目标识identifier |
|files.action_type |是| |string|操作类型 create: 创建 update: 更新 delete: 删除|
|files.content |是| |string|文件内容|
|files.encoding |是| |string|文件编码方式 text 文本 base64 加密|
|files.file_path |是| |string|文件路径|
|author_email |否| 当前用户邮箱 |string|作者邮箱,不填时需要与作者名称同时为空|
|author_name |否| 当前用户标识 |string|作者名称,不填时需要与作者邮箱同时为空|
|author_timeunix |否| 当前时间戳 |int|编码时间,精确到秒|
|committer_email |否| 当前用户邮箱 |string|提交者邮箱,不填时需要与提交者名称同时为空|
|committer_name |否| 当前用户标识 |string|提交者名称,不填时需要与提交者邮箱同时为空|
|committer_timeunix|否| 当前时间戳 |int|提交时间戳,精确到秒|
|branch |是| |string|提交分支|
|new_branch |否| |string|如果需要创建新分支,这个需要填|
|message |是| |string|提交信息|
> 请求的JSON示例:
@ -1990,25 +1990,27 @@ await octokit.request('GET /api/v1/yystopf/csfjkkj/compare.json')
|diff.files.is_created|bool|是否为新建文件|
|diff.files.is_deleted|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_ambiguous|bool||
|diff.files.is_submodule|bool|是否为子模块|
|diff.files.sections.file_name|string|文件名称|
|diff.files.sections.name|string||
|diff.files.sections.lines.left_index|int||
|diff.files.sections.lines.right_index|int||
|diff.files.sections.lines.left_index|int|文件变动之前所在行数|
|diff.files.sections.lines.right_index|int|文件变动之后所在行数|
|diff.files.sections.lines.match|int||
|diff.files.sections.lines.type|int||
|diff.files.sections.lines.content|string||
|diff.files.sections.lines.section_path|string||
|diff.files.sections.lines.type|int|文件变更类型|
|diff.files.sections.lines.content|string|文件变更内容|
|diff.files.sections.lines.section_path|string|文件路径|
|diff.files.sections.lines.section_last_left_index|int||
|diff.files.sections.lines.section_last_right_index|int||
|diff.files.sections.lines.section_left_index|int||
|diff.files.sections.lines.section_right_index|int||
|diff.files.sections.lines.section_left_hunk_size|int||
|diff.files.sections.lines.section_right_hunk_size|int||
|diff.files.sections.lines.section_left_index|int|文件变更之前所在行数|
|diff.files.sections.lines.section_right_index|int|文件变更之后所在行数(即:页面编辑器开始显示的行数)|
|diff.files.sections.lines.section_left_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示例:
@ -2281,7 +2283,7 @@ await octokit.request('POST /api/v1/yystopf/ceshi/webhooks.json')
|webhook.active |是| | bool | 是否激活|
|webhook.branch_filter|否| |string|分支过滤|
|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",
"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 :new_branch, length: { maximum: 100, too_long: "过长,仅支持%{count}的长度"}
validate :check_branch
def check_branch

View File

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

View File

@ -95,21 +95,31 @@ module ApplicationHelper
timePassed = currentTime - lastUpdateTime
timeIntoFormat = 0
updateAtValue = ""
if timePassed < 0
if timePassed <= 0
updateAtValue = "刚刚"
elsif timePassed < 2 * 1000
updateAtValue = "1秒前"
elsif timePassed < ONE_MINUTE
updateAtValue = "1分钟前"
elsif timePassed < ONE_HOUR
timeIntoFormat = timePassed / ONE_MINUTE
updateAtValue = timeIntoFormat.to_s + "分钟前"
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 + "小时前"
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 + "天前"
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 + "个月前"
else
timeIntoFormat = timePassed / ONE_YEAR

View File

@ -63,6 +63,9 @@ module ProjectsHelper
project_category_id: project.project_category_id,
project_language_id: project.project_language_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
}).compact
@ -98,4 +101,53 @@ module ProjectsHelper
def render_educoder_avatar_url(project_educoder)
[Rails.application.config_for(:configuration)['educoder']['cdn_url'], project_educoder&.image_url].join('/')
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

View File

@ -123,7 +123,7 @@ module TagChosenHelper
cache_key = "project-#{project.id}/all_milestones/size-#{project.versions.size}/#{project.versions.maximum('updated_on')}"
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,
name: event.name,

View File

@ -1,6 +1,6 @@
module CustomRegexp
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/ #只含有数字、字母、下划线不能以下划线开头和结尾
LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/

View File

@ -1,11 +1,16 @@
class UserMailer < ApplicationMailer
# 注意:这个地方一定要和你的邮箱服务域名一致
default from: 'notification@trustie.org'
# 用户注册验证码
def register_email(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 注册验证码')
end
end
class UserMailer < ApplicationMailer
# 注意:这个地方一定要和你的邮箱服务域名一致
# default from: 'notification@trustie.org'
default from: 'noreply@gitlink.org.cn'
# 用户注册验证码
def register_email(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 注册验证码')
end
def update_email(mail, code)
@code = code
mail(to: mail, subject: 'Gitink | 更改邮箱验证码')
end
end

View File

@ -17,30 +17,69 @@ module ProjectOperable
owner.build_permit_team_projects!(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
def add_member!(user_id, role_name='Developer')
if self.owner.is_a?(Organization)
case role_name
when 'Manager'
# 构建相应的团队
team = self.owner.teams.admin.take
team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
OrganizationUser.build(self.user_id, user_id)
if team.nil?
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 = 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'
# 构建相应的团队
team = self.owner.teams.write.take
team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
OrganizationUser.build(self.user_id, user_id)
if team.nil?
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)
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)
$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'
# 构建相应的团队
team = self.owner.teams.read.take
team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
OrganizationUser.build(self.user_id, user_id)
if team.nil?
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 = 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
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)
member = self.member(user_id)
# 所有者为组织,并且该用户属于组织成员
if self.owner.is_a?(Organization) && member.team_user.present?
case role&.name
when 'Manager'
# 构建相应的团队
team = self.owner.teams.admin.take
team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
if team.nil?
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)
team_user = member.team_user.update(team_id: team&.id)
when 'Developer'
# 构建相应的团队
team = self.owner.teams.write.take
team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
if team.nil?
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)
team_user = member.team_user.update(team_id: team&.id)
when 'Reporter'
# 构建相应的团队
team = self.owner.teams.read.take
team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team
TeamProject.build(self.user_id, team.id, self.id)
if team.nil?
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)
team_user = member.team_user.update(team_id: team&.id)
end
end
member.member_roles.last.update_attributes!(role: role)

View File

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

View File

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

View File

@ -13,6 +13,15 @@
# comments_count :integer default("0")
# reply_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
#
@ -24,15 +33,22 @@
#
class Journal < ApplicationRecord
serialize :diff, JSON
alias_attribute :note, :notes
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 :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 :parent_journals, ->{where(parent_id: nil)}
scope :children_journals, lambda{|journal_id| where(parent_id: journal_id)}
enum state: {opened: 0, resolved: 1, disabled: 2}
def is_journal_detail?
self.notes.blank? && self.journal_details.present?

View File

@ -11,4 +11,8 @@
class License < ApplicationRecord
include Projectable
validates :name, :content, presence: true
validates :name, uniqueness: { case_sensitive: false }
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
# self.inheritance_column = nil
PLATFORM = 'GitLink'
def self.build_init_data
@ -113,6 +114,6 @@ class MessageTemplate < ApplicationRecord
end
def simple_type
self.type.split("::")[-1]
self.type.to_s.split("::")[-1]
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

@ -81,15 +81,15 @@ class Organization < Owner
scope :with_visibility, ->(visibility) { joins(:organization_extension).where(organization_extensions: {visibility: visibility}) if visibility.present? }
after_save :reset_cache_data
# after_save :reset_cache_data
def gitea_token
team_users.joins(:team).where(teams: {authorize: "owner"}).take&.user&.gitea_token
end
def reset_cache_data
Cache::V2::OwnerCommonService.new(self.id).reset
end
# def reset_cache_data
# Cache::V2::OwnerCommonService.new(self.id).reset
# end
def self.build(name, nickname, gitea_token=nil)
self.create!(login: name, nickname: nickname, gitea_token: gitea_token)

View File

@ -12,4 +12,6 @@
class ProjectLanguage < ApplicationRecord
include Projectable
validates :name, uniqueness: { message: "已存在" }
end

View File

@ -32,12 +32,18 @@ class PullRequest < ApplicationRecord
belongs_to :issue
belongs_to :user
belongs_to :project, counter_cache: true, touch: true
# belongs_to :fork_project, foreign_key: :fork_project_id
belongs_to :fork_project, class_name: 'Project', foreign_key: :fork_project_id, optional: true
has_many :pull_request_assigns, foreign_key: :pull_request_id
has_many :pull_request_tags, foreign_key: :pull_request_id
has_many :project_trends, as: :trend, dependent: :destroy
has_many :attachments, as: :container, dependent: :destroy
has_one :gitea_pull, foreign_key: :id, primary_key: :gitea_id, class_name: 'Gitea::Pull'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :journal_details, through: :journals
has_many :reviews, dependent: :destroy
has_many :pull_requests_reviewers, dependent: :destroy
has_many :reviewers, through: :pull_requests_reviewers
has_many :mark_files, dependent: :destroy
scope :merged_and_closed, ->{where.not(status: 0)}
scope :opening, -> {where(status: 0)}

View File

@ -0,0 +1,21 @@
# == Schema Information
#
# Table name: pull_requests_reviewers
#
# id :integer not null, primary key
# pull_request_id :integer
# reviewer_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_pull_requests_reviewers_on_pull_request_id (pull_request_id)
# index_pull_requests_reviewers_on_reviewer_id (reviewer_id)
#
class PullRequestsReviewer < ApplicationRecord
belongs_to :pull_request
belongs_to :reviewers, class_name: 'User', foreign_key: :reviewer_id
end

View File

@ -49,7 +49,7 @@ class Repository < ApplicationRecord
end
def url
self['url'].blank? ? "#{Rails.application.config_for(:configuration)['platform_url']}/#{self.owner&.login}/#{self.identifier}.git" : self['url']
self['url'].blank? ? "#{GiteaService.gitea_config[:domain]}/#{self.owner&.login}/#{self.identifier}.git" : self['url']
end
# with repository is mirror

View File

@ -2,24 +2,24 @@
#
# Table name: reviews
#
# id :integer not null, primary key
# issue_id :integer
# reviewer_id :integer
# content :text(65535)
# commit_id :string(255)
# status :integer default("0")
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# reviewer_id :integer
# content :text(65535)
# commit_id :string(255)
# status :integer default("0")
# created_at :datetime not null
# updated_at :datetime not null
# pull_request_id :integer
#
# Indexes
#
# index_reviews_on_issue_id (issue_id)
# index_reviews_on_reviewer_id (reviewer_id)
# index_reviews_on_pull_request_id (pull_request_id)
# index_reviews_on_reviewer_id (reviewer_id)
#
class Review < ApplicationRecord
belongs_to :issue
belongs_to :pull_request
belongs_to :reviewer, class_name: 'User', foreign_key: :reviewer_id
has_one :journal, dependent: :destroy

View File

@ -70,4 +70,15 @@ class Team < ApplicationRecord
end
end
def to_gitea_hash
{
can_create_org_repo: self.can_create_org_project,
description: self.description || "",
includes_all_repositories: self.includes_all_project,
name: self.name,
permission: self.authorize,
units: self.team_units.pluck(:unit_type).map{|i| "repo.#{i}"}
}
end
end

View File

@ -0,0 +1,28 @@
# == Schema Information
#
# Table name: timeable_visit_records
#
# id :integer not null, primary key
# time :string(255)
# project_id :integer
# visits :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_timeable_visit_records_on_project_id (project_id)
# index_timeable_visit_records_on_time (time)
#
class TimeableVisitRecord < ApplicationRecord
belongs_to :project
def self.build(project_id)
week = TimeableVisitRecord.find_or_create_by!(time: Date.today.strftime("%Y-%W"), project_id: project_id)
month = TimeableVisitRecord.find_or_create_by!(time: Date.today.strftime("%Y%m"), project_id: project_id)
return week, month
end
end

View File

@ -32,7 +32,7 @@ class TraceUser < ApplicationRecord
self.username = user.login
self.password = SecureRandom.hex
self.unit = user.custom_department.blank? ? 'GitLink' : user.custom_department
self.telnumber = user.phone.blank? ? '13800000000' : user.phone
self.telnumber = user.phone || ""
self.email = user.mail
self.name = user.nickname.blank? ? user.login : user.nickname
@ -41,7 +41,7 @@ class TraceUser < ApplicationRecord
def build_token
return if username.blank? || password.blank? || unit.blank? || telnumber.blank? || email.blank? || name.blank?
return if username.blank? || password.blank? || unit.blank? || email.blank? || name.blank?
response = Trace::AddUserService.call(username, password, unit, telnumber, email, name)
self.token = response[1]['token']
@ -49,7 +49,7 @@ class TraceUser < ApplicationRecord
end
def refresh_token
return if username.blank? || password.blank? || unit.blank? || telnumber.blank? || email.blank? || name.blank?
return if username.blank? || password.blank? || unit.blank? || email.blank? || name.blank?
response = Trace::LoginService.call(username, password)
self.token = response[1]['token']

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

@ -0,0 +1,21 @@
# == Schema Information
#
# Table name: user_nps
#
# id :integer not null, primary key
# user_id :integer
# action_type :string(255)
# action_id :integer
# score :float(24)
# memo :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_user_nps_on_user_id (user_id)
#
class UserNp < ApplicationRecord
belongs_to :user, optional: true
end

View File

@ -32,7 +32,8 @@ class VersionRelease < ApplicationRecord
has_many :attachments, as: :container, dependent: :destroy
def update_sha
return if version_gid.blank?
git_release = Gitea::Versions::GetService.call(user.gitea_token, repository&.owner&.login, repository&.identifier, version_gid)
self.update(sha: git_release["sha"])
self.update(sha: git_release["sha"]) if git_release.present? && git_release.is_a?(Hash)
end
end

View File

@ -19,6 +19,9 @@ class Admins::UserQuery < ApplicationQuery
status = params[:status]
users = users.where(status: status) if status.present?
# 是否管理员
users = users.where(admin: params[:admin]) if params[:admin].present?
# 职业
users = users.joins(:user_extension).where(user_extensions: { identity: params[:identity] }) if params[:identity].present?
@ -27,6 +30,15 @@ class Admins::UserQuery < ApplicationQuery
users = users.joins(user_extension: :school).where(schools: { auto_users_trial: params[:auto_trial].to_i == 1 })
end
# 第三方账户类型
if params[:open_user_type].present?
if params[:open_user_type] == "Forge"
users = users.left_joins(:open_users).where(open_users: {id: nil})
else
users = users.joins(:open_users).where(open_users: {type: params[:open_user_type]})
end
end
# 关键字检索
keyword = params[:keyword].to_s.strip.presence
if keyword

View File

@ -29,6 +29,8 @@ class Projects::ListMyQuery < ApplicationQuery
projects = projects.where(user_id: user.id)
elsif params[:category].to_s == "watched" #我关注的
projects = projects.where.not(user_id: user.id).joins(:watchers).where(watchers: {watchable_type: "Project", user_id: user.id})
elsif params[:category].to_s == 'only_watched'
projects = projects.joins(:watchers).where(watchers: {watchable_type: "Project", user_id: user.id})
elsif params[:category].to_s == "forked" #我fork的
fork_ids = user.fork_users.select(:id, :fork_project_id).pluck(:fork_project_id)
projects = projects.where(id: fork_ids)

View File

@ -11,7 +11,7 @@ class Projects::ListQuery < ApplicationQuery
end
def call
collection = Project.all
collection = Project.visible
collection = filter_projects(collection)
sort = params[:sort_by] || "updated_on"
@ -25,7 +25,7 @@ class Projects::ListQuery < ApplicationQuery
def filter_projects(collection)
# collection = by_pinned(collection)
collection = by_search(collection)
collection = by_search(collection) if params[:search].present?
collection = by_project_type(collection)
collection = by_project_category(collection)
collection = by_project_language(collection)
@ -33,7 +33,13 @@ class Projects::ListQuery < ApplicationQuery
end
def by_search(items)
items.visible.by_name_or_identifier(params[:search])
ids = Projects::ElasticsearchService.call(params[:search])
items = items.where(platform: 'forge')
if ids.present?
items.where(id: ids).by_name_or_identifier(params[:search])
else
items.by_name_or_identifier(params[:search])
end
end
def by_project_type(items)

View File

@ -13,7 +13,6 @@ class Api::V1::Projects::Contents::BatchCreateService < ApplicationService
def initialize(project, params, token=nil)
puts params
@project = project
@owner = project&.owner.login
@repo = project&.identifier
@ -21,11 +20,11 @@ class Api::V1::Projects::Contents::BatchCreateService < ApplicationService
@files = params[:files]
@author_email = params[:author_email]
@author_name = params[:author_name]
@author_timeunix = params[:author_timeunix]
@author_timeunix = params[:author_timeunix] || Time.now.to_i
@branch = params[:branch]
@committer_email = params[:committer_email]
@committer_name = params[:committer_name]
@committer_timeunix = params[:committer_timeunix]
@committer_timeunix = params[:committer_timeunix] || Time.now.to_i
@message = params[:message]
@new_branch = params[:new_branch]
end
@ -63,6 +62,7 @@ class Api::V1::Projects::Contents::BatchCreateService < ApplicationService
committer: Time.at(committer_timeunix.to_i)
},
message: message,
branch: branch,
new_branch: new_branch,
signoff: false
}
@ -77,15 +77,16 @@ class Api::V1::Projects::Contents::BatchCreateService < ApplicationService
end
def excute_data_to_gitea
puts request_body.to_json
@gitea_data = $gitea_client.post_repos_contents_batch_by_owner_repo(owner, repo, {body: request_body.to_json, query: request_params}) rescue nil
raise Error, '创建文件失败!' unless @gitea_data.is_a?(Hash)
raise Error, '提交文件失败!' unless @gitea_data.is_a?(Hash)
end
def check_branch_exist
result = $gitea_client.get_repos_branch_name_set_by_owner_repo(owner, repo, {query: request_params} ) rescue nil
raise Error, '查询分支名称失败!' unless result.is_a?(Hash)
raise Error, '分支不存在!' unless result['branch_name'].include?(branch)
raise Error, '分支已存在!' if result['branch_name'].include?(new_branch) && new_branch.nil?
raise Error, '分支已存在!' if result['branch_name'].include?(new_branch) && !new_branch.nil?
end
end

View File

@ -0,0 +1,32 @@
class Api::V1::Projects::Pulls::GetService < ApplicationService
attr_reader :project, :pull_request, :owner, :repo, :index, :token
attr_accessor :gitea_data
def initialize(project, pull_request, token = nil)
@project = project
@pull_request = pull_request
@owner = project&.owner.login
@repo = project&.identifier
@index = pull_request.gitea_number
@token = token
end
def call
load_gitea_data
gitea_data
end
private
def request_params
{
access_token: token
}
end
def load_gitea_data
@gitea_data = $gitea_client.get_repos_pulls_by_owner_repo_index(owner, repo, index, {query: request_params})
# raise Error, '获取合并请求失败!' unless @gitea_data.is_a?(Hash)
end
end

View File

@ -0,0 +1,48 @@
class Api::V1::Projects::Pulls::Journals::CreateService < ApplicationService
include ActiveModel::Model
attr_reader :project, :pull_request, :issue, :parent_id, :line_code, :note, :commit_id, :path, :type, :diff, :review_id, :user
attr_accessor :journal
validates :type, inclusion: {in: %w(comment problem), message: '请输入正确的Type'}
def initialize(project, pull_request, params, user)
@project = project
@pull_request = pull_request
@issue = pull_request&.issue
@parent_id = params[:parent_id]
@line_code = params[:line_code]
@note = params[:note]
@commit_id = params[:commit_id]
@path = params[:path]
@type = params[:type] || "comment"
@diff = params[:diff]
@review_id = params[:review_id]
@user = user
end
def call
raise Error, errors.full_messages.join(", ") unless valid?
check_review_is_exists if review_id.present?
if type == 'problem'
create_problem_journal
else
create_comment_journal
end
journal
end
private
def create_comment_journal
@journal = pull_request.journals.create!(user_id: user&.id, notes: note, parent_id: parent_id, review_id: review_id, commit_id: commit_id, diff: diff, line_code: line_code, path: path)
end
def check_review_is_exists
raise Error, '合并请求审查不存在!' unless @pull_request.reviews.exists?(review_id)
end
def create_problem_journal
@journal = pull_request.journals.create!(user_id: user&.id, notes: note, parent_id: parent_id, review_id: review_id, commit_id: commit_id, diff: diff, line_code: line_code, path: path, need_respond: true)
end
end

View File

@ -0,0 +1,57 @@
class Api::V1::Projects::Pulls::Journals::ListService < ApplicationService
include ActiveModel::Model
attr_reader :project, :pull_request, :keyword, :review_id, :need_respond, :state, :parent_id, :path, :is_full, :sort_by, :sort_direction, :user
attr_accessor :queried_journals
validates :sort_by, inclusion: {in: Journal.column_names, message: '请输入正确的SortBy'}
validates :sort_direction, inclusion: {in: %w(asc desc), message: '请输入正确的SortDirection'}
validates :need_respond, inclusion: {in: [true, false], message: '请输入正确的NeedRespond'}, allow_nil: true
validates :is_full, inclusion: {in: [true, false], message: '请输入正确的IsFull'}
validates :state, inclusion: {in: %w(opened resolved disabled)}, allow_nil: true
def initialize(project, pull_request, params, user)
@project = project
@pull_request = pull_request
@keyword = params[:keyword]
@review_id = params[:review_id]
@need_respond = ActiveModel::Type::Boolean.new.cast(params[:need_respond])
@state = params[:state]
@parent_id = params[:parent_id]
@sort_by = params[:sort_by] || 'created_on'
@sort_direction = params[:sort_direction] || 'asc'
@is_full = ActiveModel::Type::Boolean.new.cast(params[:is_full]) || false
@path = params[:path]
@user = user
end
def call
raise Error, errors.full_messages.join(",") unless valid?
journal_query_data
queried_journals
end
private
def journal_query_data
journals = @pull_request.journals
if !is_full
if parent_id.present?
journals = journals.where(parent_id: parent_id)
else
journals = journals.parent_journals
end
end
journals = journals.where(review_id: review_id) if review_id.present?
journals = journals.where(need_respond: need_respond) if need_respond.present?
journals = journals.where(state: state) if state.present?
journals = journals.where(path: path) if path.present?
q = journals.ransack(notes_cont: keyword)
scope = q.result.includes(:user, :resolveer, :children_journals, review: [:reviewer, pull_request: :issue])
scope = scope.order("journals.#{sort_by} #{sort_direction}")
@queried_journals = scope
end
end

View File

@ -0,0 +1,38 @@
class Api::V1::Projects::Pulls::Journals::UpdateService < ApplicationService
include ActiveModel::Model
attr_reader :project, :pull_request, :journal, :note, :commit_id, :state, :user
attr_accessor :updated_journal
validates :state, inclusion: {in: %w(opened resolved disabled)}
def initialize(project, pull_request, journal, params, user)
@project = project
@pull_request = pull_request
@journal = journal
@note = params[:note]
@commit_id = params[:commit_id]
@state = params[:state]
@user = user
end
def call
raise Error, errors.full_messages.join(", ") unless valid?
update_journal
updated_journal
end
private
def update_journal
journal.attributes = {notes: note, commit_id: commit_id, state: state}
if state == 'resolved'
journal.resolve_at = Time.now
journal.resolveer_id = user.id
end
return Error, '保存评论失败!' unless journal.save
@updated_journal = journal.reload
end
end

View File

@ -0,0 +1,48 @@
class Api::V1::Projects::Pulls::ListService < ApplicationService
include ActiveModel::Model
attr_reader :project, :keyword, :status, :priority_id, :issue_tag_id, :version_id, :reviewer_id, :assign_user_id, :sort_by, :sort_direction
attr_accessor :queried_pull_requests
validates :status, inclusion: {in: [0, 1, 2], message: "请输入正确的Status"}, allow_nil: true
validates :sort_by, inclusion: {in: PullRequest.column_names, message: '请输入正确的SortBy'}
validates :sort_direction, inclusion: {in: %w(asc desc), message: '请输入正确的SortDirection'}
def initialize(project, params={})
@project = project
@keyword = params[:keyword]
@status = params[:status].present? ? params[:status].to_i : nil
@priority_id = params[:priority_id]
@issue_tag_id = params[:issue_tag_id]
@version_id = params[:version_id]
@reviewer_id = params[:reviewer_id]
@assign_user_id = params[:assign_user_id]
@sort_by = params[:sort_by] || 'created_at'
@sort_direction = params[:sort_direction] || 'desc'
end
def call
raise Error, errors.full_messages.join(",") unless valid?
pull_request_query_data
queried_pull_requests
end
private
def pull_request_query_data
pull_requests = @project.pull_requests
pull_requests = pull_requests.where(status: status) if status.present?
pull_requests = pull_requests.where(issues: {priority_id: priority_id}) if priority_id.present?
pull_requests = pull_requests.where(issue_tags: {id: issue_tag_id}) if issue_tag_id.present?
pull_requests = pull_requests.where(issues: {fixed_version_id: version_id}) if version_id.present?
pull_requests = pull_requests.where(users: {id: reviewer_id}) if reviewer_id.present?
pull_requests = pull_requests.where(issues: {assigned_to_id: assign_user_id}) if assign_user_id.present?
q = pull_requests.ransack(title_or_body_cont: keyword)
scope = q.result.includes(:fork_project, :journals, :reviews, :reviewers, issue: [:journals, :priority, :version, :issue_tags])
scope = scope.order("pull_requests.#{sort_by} #{sort_direction}")
@queried_pull_requests = scope
end
end

View File

@ -1,4 +1,4 @@
class Api::V1::Projects::PullRequests::Reviews::CreateService < ApplicationService
class Api::V1::Projects::Pulls::Reviews::CreateService < ApplicationService
include ActiveModel::Model
attr_reader :project, :pull_request, :issue, :status, :commit_id, :content, :current_user
@ -20,21 +20,18 @@ class Api::V1::Projects::PullRequests::Reviews::CreateService < ApplicationServi
raise Error, errors.full_messages.join(", ") unless valid?
ActiveRecord::Base.transaction do
create_review
create_journal
# create_journal
end
return @journal, @review
rescue
raise Error, '服务器错误,请联系系统管理员!'
@review
# rescue
# raise Error, '服务器错误,请联系系统管理员!'
end
private
def create_review
@review = issue.reviews.create!(status: status, content: content, commit_id: commit_id, reviewer_id: @current_user.id)
@review = pull_request.reviews.create!(status: status, content: content, commit_id: commit_id, reviewer_id: @current_user.id)
end
def create_journal
@journal = issue.journals.create!(notes: content, user_id: @current_user.id, review_id: @review.id)
end
end

View File

@ -0,0 +1,36 @@
class Api::V1::Projects::Pulls::Versions::GetDiffService < ApplicationService
attr_reader :project, :pull_request, :version_id, :owner, :repo, :index, :filepath, :token
attr_accessor :gitea_data
def initialize(project, pull_request, version_id, params, token=nil)
@project = project
@pull_request = pull_request
@version_id = version_id
@owner = project&.owner.login
@repo = project&.identifier
@index = pull_request.gitea_number
@filepath = params[:filepath]
@token = token
end
def call
load_gitea_data
gitea_data
end
private
def request_params
params = {
access_token: token
}
params.merge!(filepath: filepath) if filepath.present?
params
end
def load_gitea_data
@gitea_data = $gitea_client.get_repos_pulls_versions_diff_by_owner_repo_index_version_id(owner, repo, index, version_id, {query: request_params})
raise Error, '获取合并请求版本diff失败' unless @gitea_data.is_a?(Hash)
end
end

View File

@ -0,0 +1,36 @@
class Api::V1::Projects::Pulls::Versions::ListService < ApplicationService
attr_reader :project, :pull_request, :token, :owner, :repo, :index, :page, :limit
attr_accessor :gitea_data
def initialize(project, pull_request, params, token = nil)
@project = project
@pull_request = pull_request
@owner = project&.owner.login
@repo = project&.identifier
@page = params[:page] || 1
@limit = params[:limit] || 15
@index = pull_request.gitea_number
@token = token
end
def call
load_gitea_data
gitea_data
end
private
def request_params
{
access_token: token,
page: page,
limit: limit
}
end
def load_gitea_data
@gitea_data = $gitea_client.get_repos_pulls_versions_by_owner_repo_index(owner, repo, index, {query: request_params})
raise Error, '获取合并请求版本失败!' unless @gitea_data.is_a?(Hash)
end
end

View File

@ -1,14 +1,14 @@
class Api::V1::Projects::Webhooks::CreateService < ApplicationService
include ActiveModel::Model
attr_reader :project, :token, :owner, :repo, :active, :branch_filter, :content_type, :url, :http_method, :secret, :events
attr_reader :project, :token, :owner, :repo, :active, :branch_filter, :content_type, :url, :http_method, :secret, :events, :type
attr_accessor :gitea_data
validates :url, format: { with: URI::regexp(%w[http https]), message: "请输入正确的地址" }
validates :active, inclusion: {in: [true, false]}
validates :http_method, inclusion: { in: %w(POST GET), message: "请输入正确的请求方式"}
validates :content_type, inclusion: { in: %w(json form), message: "请输入正确的Content Type"}
validates :type, inclusion: {in: %w(gitea slack discord dingtalk telegram msteams feishu matrix jianmu), message: "请输入正确的Webhook Type"}
def initialize(project, params, token=nil)
@project = project
@owner = project&.owner.login
@ -20,6 +20,7 @@ class Api::V1::Projects::Webhooks::CreateService < ApplicationService
@http_method = params[:http_method]
@secret = params[:secret]
@events = params[:events]
@type = params[:type] || "gitea"
@token = token
end
@ -52,7 +53,7 @@ class Api::V1::Projects::Webhooks::CreateService < ApplicationService
secret: secret
},
events: events || [],
type: 'gitea',
type: type,
}
end

View File

@ -13,7 +13,7 @@ class Api::V1::Users::Projects::ListService < ApplicationService
def initialize(observe_user, params, current_user=nil)
@observe_user = observe_user
@category = params[:category] || 'all'
@is_public = params[:is_public]
@is_public = ActiveModel::Type::Boolean.new.cast(params[:is_public])
@project_type = params[:project_type]
@sort_by = params[:sort_by] || 'updated_on'
@sort_direction = params[:sort_direction] || 'desc'
@ -53,6 +53,8 @@ class Api::V1::Users::Projects::ListService < ApplicationService
projects = Project.from("( #{normal_projects} UNION #{org_projects} ) AS projects").distinct
when 'watched'
projects = projects.where.not(user_id: observe_user.id).joins(:watchers).where(watchers: {watchable_type: "Project", user_id: observe_user.id})
when 'only_watched'
projects = projects.where.joins(:watchers).where(watchers: {watchable_type: "Project", user_id: observe_user.id})
when 'forked'
fork_ids = observe_user.fork_users.select(:id, :fork_project_id).pluck(:fork_project_id)
projects = projects.where(id: fork_ids)

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