Browse Source

Merge pull request 'WIP:blockchain' (#155) from blockchain into develop

Reviewed-by: 林嘉怡 <2441898885@qq.com>
Reviewed-by: yuyuanshifu <747342561@qq.com>
master
yuyuanshifu 4 years ago
parent
commit
50d41bc07a
24 changed files with 751 additions and 272 deletions
  1. BIN
      docs/开源社区平台与区块链平台对接方案.docx
  2. +101
    -0
      models/block_chain_issue.go
  3. +28
    -12
      models/blockchain.go
  4. +4
    -0
      models/issue.go
  5. +12
    -1
      models/pull.go
  6. +9
    -0
      models/pull_list.go
  7. +9
    -3
      models/repo.go
  8. +16
    -0
      models/repo_unit.go
  9. +14
    -0
      models/unit.go
  10. +2
    -2
      models/user.go
  11. +2
    -0
      modules/auth/repo_form.go
  12. +49
    -15
      modules/blockchain/resty.go
  13. +1
    -0
      modules/context/repo.go
  14. +8
    -8
      modules/timer/timer.go
  15. +6
    -2
      options/locale/locale_zh-CN.ini
  16. +93
    -53
      routers/repo/blockchain.go
  17. +42
    -0
      routers/repo/issue.go
  18. +8
    -0
      routers/repo/pull.go
  19. +6
    -0
      routers/routes/routes.go
  20. +18
    -0
      templates/repo/blockchain/index.tmpl
  21. +6
    -1
      templates/repo/header.tmpl
  22. +0
    -1
      templates/repo/issue/milestone_issues.tmpl
  23. +215
    -173
      templates/repo/issue/new_form.tmpl
  24. +102
    -1
      templates/repo/issue/view_content/pull.tmpl

BIN
docs/开源社区平台与区块链平台对接方案.docx View File


+ 101
- 0
models/block_chain_issue.go View File

@@ -0,0 +1,101 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
"time"
)

type BlockChainIssueStatus int
const (
BlockChainIssueInit BlockChainIssueStatus = iota
BlockChainIssueSuccess
BlockChainIssueFailed
)
type BlockChainIssue struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX NOT NULL unique"`
Contributor string `xorm:"INDEX NOT NULL"`
ContractAddress string `xorm:"INDEX NOT NULL"`
Status BlockChainIssueStatus `xorm:"INDEX NOT NULL DEFAULT 0"`
Amount int64 `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
RepoID int64 `xorm:"INDEX"`
TransactionHash string `xorm:"INDEX"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
DeletedAt time.Time `xorm:"deleted"`

User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
}

func getBlockChainIssueByID(e Engine, id int64) (*BlockChainIssue, error) {
blockChainIssue := new(BlockChainIssue)
has, err := e.ID(id).Get(blockChainIssue)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("get block_chain by id failed(%d)", id)
}
return blockChainIssue, nil
}

func GetBlockChainIssueByID(id int64) (*BlockChainIssue, error) {
return getBlockChainIssueByID(x, id)
}

func getBlockChainIssueByPrID(e Engine, prId int64) (*BlockChainIssue, error) {
blockChainIssue := new(BlockChainIssue)
has, err := e.Where("pr_id = ?", prId).Get(blockChainIssue)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("get block_chain by pr_id failed(%d)", prId)
}
return blockChainIssue, nil
}

func GetBlockChainIssueByPrID(prId int64) (*BlockChainIssue, error) {
return getBlockChainIssueByPrID(x, prId)
}

func getBlockChainIssueByCommitID(e Engine, commitID string) (*BlockChainIssue, error) {
blockChainIssue := new(BlockChainIssue)
has, err := e.Where("commit_id = ?", commitID).Get(blockChainIssue)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("get block_chain_issue by commitID failed(%s)", commitID)
}
return blockChainIssue, nil
}

func GetBlockChainIssueByCommitID(commitID string) (*BlockChainIssue, error) {
return getBlockChainIssueByCommitID(x, commitID)
}

func updateBlockChainIssueCols(e Engine, blockChainIssue *BlockChainIssue, cols ...string) error {
_, err := e.ID(blockChainIssue.ID).Cols(cols...).Update(blockChainIssue)
return err
}

func UpdateBlockChainIssueCols(blockChainIssue *BlockChainIssue, cols ...string) error {
return updateBlockChainIssueCols(x, blockChainIssue, cols...)
}

func GetBlockChainUnSuccessIssues() ([]*BlockChainIssue, error) {
blockChainIssues := make([]*BlockChainIssue, 0, 10)
return blockChainIssues, x.
Where("status != ?", BlockChainIssueSuccess).
Find(&blockChainIssues)
}

func InsertBlockChainIssue(blockChainIssue *BlockChainIssue) (_ *BlockChainIssue, err error) {

if _, err := x.Insert(blockChainIssue); err != nil {
return nil, err
}

return blockChainIssue, nil
}

+ 28
- 12
models/blockchain.go View File

@@ -15,18 +15,19 @@ const (
)

type BlockChain struct {
ID int64 `xorm:"pk autoincr"`
CommitID string `xorm:"INDEX NOT NULL"`
Contributor string `xorm:"INDEX NOT NULL"`
ContractAddress string `xorm:"INDEX NOT NULL"`
Status BlockChainCommitStatus `xorm:"INDEX NOT NULL DEFAULT 0"`
Amount int64 `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
RepoID int64 `xorm:"INDEX"`
TransactionHash string `xorm:"INDEX"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
DeletedAt time.Time `xorm:"deleted"`
ID int64 `xorm:"pk autoincr"`
PrID int64 `xorm:"INDEX NOT NULL unique"`
CommitID string `xorm:"INDEX NOT NULL unique"`
Contributor string `xorm:"INDEX NOT NULL"`
ContractAddress string `xorm:"INDEX NOT NULL"`
Status BlockChainCommitStatus `xorm:"INDEX NOT NULL DEFAULT 0"`
Amount int64 `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
RepoID int64 `xorm:"INDEX"`
TransactionHash string `xorm:"INDEX"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
DeletedAt time.Time `xorm:"deleted"`

User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
@@ -47,6 +48,21 @@ func GetBlockChainByID(id int64) (*BlockChain, error) {
return getBlockChainByID(x, id)
}

func getBlockChainByPrID(e Engine, prId int64) (*BlockChain, error) {
blockChain := new(BlockChain)
has, err := e.Where("pr_id = ?", prId).Get(blockChain)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("get block_chain by pr_id failed(%d)", prId)
}
return blockChain, nil
}

func GetBlockChainByPrID(prId int64) (*BlockChain, error) {
return getBlockChainByPrID(x, prId)
}

func getBlockChainByCommitID(e Engine, commitID string) (*BlockChain, error) {
blockChain := new(BlockChain)
has, err := e.Where("commit_id = ?", commitID).Get(blockChain)


+ 4
- 0
models/issue.go View File

@@ -66,6 +66,10 @@ type Issue struct {
// IsLocked limits commenting abilities to users on an issue
// with write access
IsLocked bool `xorm:"NOT NULL DEFAULT false"`

//block_chain
Amount int64
IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"`
}

var (


+ 12
- 1
models/pull.go View File

@@ -36,6 +36,13 @@ const (
PullRequestStatusError
)

const (
PullRequestAmountZero int = 0
PullRequestAmountOne int = 100
PullRequestAmountTwo int = 200
PullRequestAmountMax int = 300
)

// PullRequest represents relation between pull request and repositories.
type PullRequest struct {
ID int64 `xorm:"pk autoincr"`
@@ -65,6 +72,10 @@ type PullRequest struct {
MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"`

isHeadRepoLoaded bool `xorm:"-"`

//block_chain
IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"`
Amount int `xorm:"INDEX NOT NULL DEFAULT 0"`
}

// MustHeadUserName returns the HeadRepo's username if failed return blank
@@ -396,7 +407,7 @@ func (pr *PullRequest) SetMerged() (bool, error) {
return false, fmt.Errorf("Issue.changeStatus: %v", err)
}

if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix, amount").Update(pr); err != nil {
return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
}



+ 9
- 0
models/pull_list.go View File

@@ -5,6 +5,7 @@
package models

import (
"code.gitea.io/gitea/modules/setting"
"fmt"

"code.gitea.io/gitea/modules/base"
@@ -170,3 +171,11 @@ func (prs PullRequestList) invalidateCodeComments(e Engine, doer *User, repo *gi
func (prs PullRequestList) InvalidateCodeComments(doer *User, repo *git.Repository, branch string) error {
return prs.invalidateCodeComments(x, doer, repo, branch)
}

func GetUnTransformedMergedPullRequests() ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 10)
return prs, x.
Where("has_merged = ? AND pull_request.is_transformed = ? AND to_timestamp(merged_unix) >= ?",true, false, setting.CommitValidDate).
Join("INNER", "issue", "issue.id = pull_request.issue_id").
Find(&prs)
}

+ 9
- 3
models/repo.go View File

@@ -206,9 +206,9 @@ type Repository struct {
Avatar string `xorm:"VARCHAR(64)"`

//blockchain
ContractAddress string `xorm:"INDEX"`
Balance int64 `xorm:"NOT NULL DEFAULT 0"`
BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"`
ContractAddress string `xorm:"INDEX"`
Balance string `xorm:"NOT NULL DEFAULT '0'"`
BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"`

// git clone total count
CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"`
@@ -1105,6 +1105,12 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Type: tp,
Config: &CloudBrainConfig{EnableCloudBrain: true},
})
} else if tp == UnitTypeBlockChain {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &BlockChainConfig{EnableBlockChain: true},
})
} else {
units = append(units, RepoUnit{
RepoID: repo.ID,


+ 16
- 0
models/repo_unit.go View File

@@ -141,6 +141,20 @@ func (cfg *CloudBrainConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}

type BlockChainConfig struct {
EnableBlockChain bool
}

// FromDB fills up a CloudBrainConfig from serialized format.
func (cfg *BlockChainConfig) FromDB(bs []byte) error {
return json.Unmarshal(bs, &cfg)
}

// ToDB exports a CloudBrainConfig to a serialized format.
func (cfg *BlockChainConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}

// BeforeSet is invoked from XORM before setting the value of a field of this object.
func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
switch colName {
@@ -160,6 +174,8 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
r.Config = new(DatasetConfig)
case UnitTypeCloudBrain:
r.Config = new(CloudBrainConfig)
case UnitTypeBlockChain:
r.Config = new(BlockChainConfig)
default:
panic("unrecognized repo unit type: " + com.ToStr(*val))
}


+ 14
- 0
models/unit.go View File

@@ -26,6 +26,7 @@ const (
UnitTypeExternalTracker // 7 ExternalTracker
UnitTypeDatasets UnitType = 10 // 10 Dataset
UnitTypeCloudBrain UnitType = 11 // 11 CloudBrain
UnitTypeBlockChain UnitType = 12 // 12 BlockChain
)

// Value returns integer value for unit type
@@ -53,6 +54,8 @@ func (u UnitType) String() string {
return "UnitTypeDataset"
case UnitTypeCloudBrain:
return "UnitTypeCloudBrain"
case UnitTypeBlockChain:
return "UnitTypeBlockChain"
}
return fmt.Sprintf("Unknown UnitType %d", u)
}
@@ -76,6 +79,7 @@ var (
UnitTypeExternalTracker,
UnitTypeDatasets,
UnitTypeCloudBrain,
UnitTypeBlockChain,
}

// DefaultRepoUnits contains the default unit types
@@ -87,6 +91,7 @@ var (
UnitTypeWiki,
UnitTypeDatasets,
UnitTypeCloudBrain,
UnitTypeBlockChain,
}

// NotAllowedDefaultRepoUnits contains units that can't be default
@@ -268,6 +273,14 @@ var (
6,
}

UnitBlockChain = Unit{
UnitTypeBlockChain,
"repo.blockchains",
"/blockchains",
"repo.blockchains.desc",
7,
}

// Units contains all the units
Units = map[UnitType]Unit{
UnitTypeCode: UnitCode,
@@ -279,6 +292,7 @@ var (
UnitTypeExternalWiki: UnitExternalWiki,
UnitTypeDatasets: UnitDataset,
UnitTypeCloudBrain: UnitCloudBrain,
UnitTypeBlockChain: UnitBlockChain,
}
)



+ 2
- 2
models/user.go View File

@@ -174,8 +174,8 @@ type User struct {
Token string `xorm:"VARCHAR(1024)"`

//BlockChain
PublicKey string `xorm`
PrivateKey string `xorm`
PublicKey string `xorm:"INDEX"`
PrivateKey string `xorm:"INDEX"`
}

// SearchOrganizationsOptions options to filter organizations


+ 2
- 0
modules/auth/repo_form.go View File

@@ -369,6 +369,7 @@ type CreateIssueForm struct {
AssigneeID int64
Content string
Files []string
Rewards int64
}

// Validate validates the fields
@@ -489,6 +490,7 @@ type MergePullRequestForm struct {
MergeTitleField string
MergeMessageField string
ForceMerge *bool `json:"force_merge,omitempty"`
BlockChainAmount int
}

// Validate validates the fields


+ 49
- 15
modules/blockchain/resty.go View File

@@ -2,6 +2,7 @@ package blockchain

import (
"fmt"
"strconv"

"code.gitea.io/gitea/modules/setting"
"github.com/go-resty/resty/v2"
@@ -13,11 +14,10 @@ var (

const (
UrlCreateAccount = "createAccount"
UrlGetBalance = "getBalance"
UrlNewRepo = "newRepo"
UrlContribute = "contribute"

ActionCommit = "commit"
UrlGetBalance = "getBalance"
UrlNewRepo = "newRepo"
UrlContribute = "contribute"
UrlSetIssue = "setIssue"

Success = 0
)
@@ -31,7 +31,7 @@ type CreateAccountResult struct {
type GetBalanceResult struct {
Code int `json:"code"`
Msg string `json:"message"`
Payload map[string]interface{} `json:"data"`
Data string `json:"data"`
}

type NewRepoResult struct {
@@ -41,9 +41,15 @@ type NewRepoResult struct {
}

type ContributeResult struct {
Code int `json:"code"`
Msg string `json:"message"`
Payload map[string]interface{} `json:"data"`
Code int `json:"code"`
Msg string `json:"message"`
//Payload map[string]interface{} `json:"data"`
}

type SetIssueResult struct {
Code int `json:"code"`
Msg string `json:"message"`
//Data string `json:"data"`
}

func getRestyClient() *resty.Client {
@@ -122,18 +128,18 @@ func GetBalance(contractAddress, contributor string) (*GetBalanceResult, error)
return &result, nil
}

func Contribute(contractAddress, contributor, action, commitId string, codeLine int) (*ContributeResult, error) {
func Contribute(contractAddress, contributor, commitId string, amount int64) (*ContributeResult, error) {
client := getRestyClient()
var result ContributeResult

strAmount := strconv.FormatInt(amount, 10)
res, err := client.R().
SetHeader("Accept", "application/json").
SetQueryParams(map[string]string{
"contractAddress": contractAddress,
"contributor": contributor,
"action": action,
"commitId": commitId,
"amount": string(codeLine),
"contractAddress" : contractAddress,
"contributor" : contributor,
"commitId": commitId,
"amount": strAmount,
}).
SetResult(&result).
Get(setting.BlockChainHost + UrlContribute)
@@ -148,3 +154,31 @@ func Contribute(contractAddress, contributor, action, commitId string, codeLine

return &result, nil
}

func SetIssue(contractAddress, contributor string, issueId int64, amount int64) (*SetIssueResult, error) {
client := getRestyClient()
var result SetIssueResult

strAmount := strconv.FormatInt(amount, 10)
strIssue := strconv.FormatInt(issueId, 10)
res, err := client.R().
SetHeader("Accept", "application/json").
SetQueryParams(map[string]string{
"contractAddress" : contractAddress,
"contributor" : contributor,
"issueId": strIssue,
"amount": strAmount,
}).
SetResult(&result).
Get(setting.BlockChainHost + UrlSetIssue)

if err != nil {
return nil, fmt.Errorf("resty SetIssue: %v", err)
}

if result.Code != Success {
return &result, fmt.Errorf("SetIssue err: %s", res.String())
}

return &result, nil
}

+ 1
- 0
modules/context/repo.go View File

@@ -820,5 +820,6 @@ func UnitTypes() macaron.Handler {
ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki
ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki
ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker
ctx.Data["UnitTypeBlockChain"] = models.UnitTypeBlockChain
}
}

+ 8
- 8
modules/timer/timer.go View File

@@ -12,16 +12,16 @@ func init() {
spec := "*/10 * * * *"
c.AddFunc(spec, repo.HandleUnDecompressAttachment)

//specCheckBlockChainUserSuccess := "*/10 * * * *"
//c.AddFunc(specCheckBlockChainUserSuccess, repo.HandleBlockChainUnSuccessUsers)
specCheckBlockChainUserSuccess := "*/10 * * * *"
c.AddFunc(specCheckBlockChainUserSuccess, repo.HandleBlockChainUnSuccessUsers)

//specCheckRepoBlockChainSuccess := "*/5 * * * *"
//c.AddFunc(specCheckRepoBlockChainSuccess, repo.HandleBlockChainUnSuccessRepos)
specCheckRepoBlockChainSuccess := "*/1 * * * *"
c.AddFunc(specCheckRepoBlockChainSuccess, repo.HandleBlockChainUnSuccessRepos)

//specCheckUnTransformedActions := "*/1 * * * *"
//c.AddFunc(specCheckUnTransformedActions, repo.HandleUnTransformedActions)
specCheckUnTransformedPRs := "*/1 * * * *"
c.AddFunc(specCheckUnTransformedPRs, repo.HandleBlockChainMergedPulls)

//specCheckBlockChainCommitSuccess := "*/3 * * * *"
//c.AddFunc(specCheckBlockChainCommitSuccess, repo.HandleBlockChainUnSuccessCommits)
specCheckBlockChainCommitSuccess := "*/3 * * * *"
c.AddFunc(specCheckBlockChainCommitSuccess, repo.HandleBlockChainUnSuccessCommits)
c.Start()
}

+ 6
- 2
options/locale/locale_zh-CN.ini View File

@@ -268,7 +268,7 @@ twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设
twofa_scratch_token_incorrect=你的验证口令不正确。
login_userpass=登录
login_openid=OpenID
login_cloudbrain=云脑用户登录
login_cloudbrain=用户登录
oauth_signup_tab=注册帐号
oauth_signup_title=添加电子邮件和密码 (用于帐号恢复)
oauth_signup_submit=完成账号
@@ -753,7 +753,10 @@ cloudbrain.new=新建任务
cloudbrain.desc=云脑功能
cloudbrain.cancel=取消
cloudbrain.commit_image=提交
clone_cnt=次下载
balance=余额
balance.total_view=余额总览
balance.available=可用余额:
balance.disable=不可用余额:

template.items=模板选项
template.git_content=Git数据(默认分支)
@@ -936,6 +939,7 @@ issues.filter_labels=筛选标签
issues.filter_reviewers=筛选审核者
issues.new=创建任务
issues.new.title_empty=标题不能为空
issues.new.rewards_error=奖励金额错误:不能小于0,且不能大于自身余额
issues.new.labels=标签
issues.new.add_labels_title=添加标签
issues.new.no_label=未选择标签


+ 93
- 53
routers/repo/blockchain.go View File

@@ -1,14 +1,14 @@
package repo

import (
"code.gitea.io/gitea/modules/repository"
"encoding/json"
"strconv"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/blockchain"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"encoding/json"
"net/http"
"strconv"
)

type BlockChainInitNotify struct {
@@ -20,6 +20,29 @@ type BlockChainCommitNotify struct {
CommitID string `json:"commitId"`
TransactionHash string `json:"txHash"`
}
const (
tplBlockChainIndex base.TplName = "repo/blockchain/index"
)

func BlockChainIndex(ctx *context.Context) {
repo := ctx.Repo.Repository
if repo.ContractAddress == "" || ctx.User.PublicKey == ""{
log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", repo.RepoID, ctx.User.ID)
ctx.HTML(http.StatusInternalServerError, tplBlockChainIndex)
return
}

res, err := blockchain.GetBalance(repo.ContractAddress, ctx.User.PublicKey)
if err != nil {
log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err)
ctx.HTML(http.StatusInternalServerError, tplBlockChainIndex)
return
}

ctx.Data["balance"] = res.Data
ctx.Data["PageIsBlockChain"] = true
ctx.HTML(200, tplBlockChainIndex)
}

func HandleBlockChainInitNotify(ctx *context.Context) {
var req BlockChainInitNotify
@@ -130,7 +153,6 @@ func HandleBlockChainUnSuccessRepos() {
continue
}
strRepoID := strconv.FormatInt(repo.ID, 10)
log.Info(strRepoID)
_, err = blockchain.NewRepo(strRepoID, repo.Owner.PublicKey, repo.Name)
if err != nil {
log.Error("blockchain.NewRepo(%s) failed:%v", strRepoID, err)
@@ -148,7 +170,7 @@ func HandleBlockChainUnSuccessCommits() {
}

for _, block_chain := range blockChains {
_, err = blockchain.Contribute(block_chain.ContractAddress, block_chain.Contributor, blockchain.ActionCommit, block_chain.CommitID, int(block_chain.Amount))
_, err = blockchain.Contribute(block_chain.ContractAddress, block_chain.Contributor, block_chain.CommitID, block_chain.Amount)
if err != nil {
log.Error("blockchain.Contribute(%s) failed:%v", block_chain.CommitID, err)
}
@@ -180,71 +202,89 @@ func HandleBlockChainUnSuccessUsers() {
return
}

func HandleUnTransformedActions() {
actions, err := models.GetUnTransformedActions()
func HandleBlockChainMergedPulls() {
prs, err := models.GetUnTransformedMergedPullRequests()
if err != nil {
log.Error("GetUnTransformedActions failed:", err.Error())
log.Error("GetUnTransformedMergedPullRequests failed:", err.Error())
return
}

isTransformed := true
for _, pr := range prs {
_, err = models.GetBlockChainByPrID(pr.ID)
if err == nil {
log.Info("the pr(%s) has been transformed", pr.MergedCommitID)
continue
}

for _, action := range actions {
var content repository.PushCommits
err = json.Unmarshal([]byte(action.Content), &content)
err = pr.LoadIssue()
if err != nil {
isTransformed = false
log.Error("json.Unmarshal action.Content(%s) failed:%v", action.Content, err)
break
log.Error("LoadIssue(%s) failed:%v", pr.MergedCommitID, err)
continue
}

repo, err := models.GetRepositoryByID(action.RepoID)
poster, err := models.GetUserByID(pr.Issue.PosterID)
if err != nil {
isTransformed = false
log.Error("GetRepositoryByID(%d) failed:%v", action.RepoID, err)
break
log.Error("GetUserByID(%s) failed:%v", pr.MergedCommitID, err)
continue
}

if len(poster.PrivateKey) == 0 || len(poster.PublicKey) == 0 {
log.Error("the user has not been init in block_chain:", poster.Name)
continue
}

if repo.ContractAddress == "" {
isTransformed = false
repo, err := models.GetRepositoryByID(pr.HeadRepoID)
if err != nil {
log.Error("GetUserByID(%s) failed:%v", pr.MergedCommitID, err)
continue
}

if len(repo.ContractAddress) == 0 {
log.Error("the repo(%s) has not been initialized in block_chain", repo.Name)
break
continue
}

for _, commit := range content.Commits {
_, err = models.GetBlockChainByCommitID(commit.Sha1)
if err == nil {
log.Info("the commit(%s) has been transformed", commit.Sha1)
continue
}

user, err := models.GetUserByName(commit.CommitterName)
if err != nil {
isTransformed = false
log.Error("GetUserByName(%s) failed:%v", commit.CommitterName, err)
break
}

blockChain := models.BlockChain{
CommitID: commit.Sha1,
Contributor: user.PublicKey,
ContractAddress: repo.ContractAddress,
Status: models.BlockChainCommitInit,
Amount: 1,
UserID: action.UserID,
RepoID: action.RepoID,
}
_, err = models.InsertBlockChain(&blockChain)
if err != nil {
isTransformed = false
log.Error("InsertBlockChain(%s) failed:%v", commit.Sha1, err)
break
}
blockChain := models.BlockChain{
Contributor : poster.PublicKey,
PrID : pr.ID,
CommitID : pr.MergedCommitID,
ContractAddress : repo.ContractAddress,
Status : models.BlockChainCommitInit,
Amount : int64(pr.Amount),
UserID : poster.ID,
RepoID : pr.HeadRepoID,
}
_, err = models.InsertBlockChain(&blockChain)
if err != nil {
log.Error("InsertBlockChain(%s) failed:%v", pr.MergedCommitID, err)
continue
}

pr.IsTransformed = true
pr.UpdateCols("is_transformed")

_, err = blockchain.Contribute(repo.ContractAddress, poster.PublicKey, pr.MergedCommitID, int64(pr.Amount))
if err != nil {
log.Error("Contribute(%s) failed:%v", pr.MergedCommitID, err)
}
}

log.Info("", isTransformed)
return
}

func HandleBlockChainUnSuccessIssues() {
issues, err := models.GetBlockChainUnSuccessCommits()
if err != nil {
log.Error("GetBlockChainUnSuccessIssues failed:", err.Error())
return
}

for _, issue := range issues {
_, err = blockchain.SetIssue(issue.ContractAddress, issue.Contributor, issue.ID, issue.Amount)
if err != nil {
log.Error("SetIssue(%s) failed:%v", issue.CommitID, err)
}
}

return
}

+ 42
- 0
routers/repo/issue.go View File

@@ -509,6 +509,21 @@ func NewIssue(ctx *context.Context) {

ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypeIssues)

/*if ctx.Repo.Repository.ContractAddress == "" || ctx.User.PublicKey == ""{
log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", ctx.Repo.Repository.ID, ctx.User.ID)
ctx.HTML(http.StatusInternalServerError, tplIssueNew)
return
}

res, err := blockchain.GetBalance(ctx.Repo.Repository.ContractAddress, ctx.User.PublicKey)
if err != nil {
log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err)
ctx.HTML(http.StatusInternalServerError, tplIssueNew)
return
}

ctx.Data["balance"] = res.Data*/

ctx.HTML(200, tplIssueNew)
}

@@ -637,6 +652,33 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
Ref: form.Ref,
}

/*if repo.ContractAddress == "" || ctx.User.PublicKey == ""{
log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", issue.RepoID, ctx.User.ID)
ctx.HTML(http.StatusInternalServerError, tplIssueNew)
return
}

res, err := blockchain.GetBalance(repo.ContractAddress, ctx.User.PublicKey)
if err != nil {
log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err)
ctx.HTML(http.StatusInternalServerError, tplIssueNew)
return
}

balance, err := com.StrTo(res.Data).Int64()
if err != nil {
log.Error("balance(%s) convert failed:%v", res.Data, err)
ctx.HTML(http.StatusInternalServerError, tplIssueNew)
return
}

if form.Rewards < 0 || form.Rewards > balance {
ctx.RenderWithErr(ctx.Tr("repo.issues.new.rewards_error"), tplIssueNew, form)
return
}

issue.Amount = form.Rewards*/

if err := issue_service.NewIssue(repo, issue, labelIDs, attachments, assigneeIDs); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())


+ 8
- 0
routers/repo/pull.go View File

@@ -810,6 +810,14 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
return
}

if form.BlockChainAmount < int(models.PullRequestAmountZero) || form.BlockChainAmount > int(models.PullRequestAmountMax) {
log.Error("amount set error(0-300)")
ctx.RenderWithErr("amount set error(0-300)", tplIssueView, form)
return
}

pr.Amount = form.BlockChainAmount

if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))


+ 6
- 0
routers/routes/routes.go View File

@@ -568,6 +568,8 @@ func RegisterRoutes(m *macaron.Macaron) {
reqRepoDatasetWriter := context.RequireRepoWriter(models.UnitTypeDatasets)
reqRepoCloudBrainReader := context.RequireRepoReader(models.UnitTypeCloudBrain)
reqRepoCloudBrainWriter := context.RequireRepoWriter(models.UnitTypeCloudBrain)
//reqRepoBlockChainReader := context.RequireRepoReader(models.UnitTypeBlockChain)
//reqRepoBlockChainWriter := context.RequireRepoWriter(models.UnitTypeBlockChain)

// ***** START: Organization *****
m.Group("/org", func() {
@@ -911,6 +913,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)
}, context.RepoRef())

m.Group("/blockchain", func() {
m.Get("", repo.BlockChainIndex)
}, context.RepoRef())

m.Group("/wiki", func() {
m.Get("/?:page", repo.Wiki)
m.Get("/_pages", repo.WikiPages)


+ 18
- 0
templates/repo/blockchain/index.tmpl View File

@@ -0,0 +1,18 @@
{{template "base/head" .}}
<div class="repository balance view">
{{template "repo/header" .}}
<div class="ui container">
<h3 class="ui top attached header">
{{.i18n.Tr "repo.balance.total_view"}}
</h3>

<div class="ui attached segment">
<div class="inline field">
<span class="fitted">{{.i18n.Tr "repo.balance.available"}}</span>
<span class="fitted">{{.balance}}</span>
</div>
</div>

</div>
</div>
{{template "base/footer" .}}

+ 6
- 1
templates/repo/header.tmpl View File

@@ -145,6 +145,11 @@
</a>
{{end}}

<a class="{{if .PageIsBlockChain}}active{{end}} item " href="{{.RepoLink}}/blockchain">
{{svg "octicon-law" 16}}
{{.i18n.Tr "repo.balance"}}
</a>

{{template "custom/extra_tabs" .}}

{{if .Permission.IsAdmin}}
@@ -158,4 +163,4 @@
{{end}}
</div>
<div class="ui tabs divider"></div>
</div>
</div>

+ 0
- 1
templates/repo/issue/milestone_issues.tmpl View File

@@ -188,7 +188,6 @@
{{end}}
<div class="ui {{if .IsClosed}}{{if .IsPull}}{{if .PullRequest.HasMerged}}purple{{else}}red{{end}}{{else}}red{{end}}{{else}}{{if .IsRead}}white{{else}}green{{end}}{{end}} label">#{{.Index}}</div>
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a>

{{if .IsPull }}
{{if (index $.CommitStatus .PullRequest.ID)}}
{{template "repo/commit_status" (index $.CommitStatus .PullRequest.ID)}}


+ 215
- 173
templates/repo/issue/new_form.tmpl View File

@@ -1,182 +1,224 @@
<form class="ui comment form stackable grid" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
{{if .Flash}}
<div class="sixteen wide column">
{{template "base/alert" .}}
</div>
{{end}}
<div class="twelve wide column">
<div class="ui comments">
<div class="comment">
<a class="avatar" href="{{.SignedUser.HomeLink}}">
<img src="{{.SignedUser.RelAvatarLink}}">
</a>
<div class="ui segment content">
<div class="field">
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255">
{{if .PageIsComparePull}}
<div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div>
{{end}}
</div>
{{template "repo/issue/comment_tab" .}}
<div class="text right">
<button class="ui green button" tabindex="6">
{{if .PageIsComparePull}}
{{.i18n.Tr "repo.pulls.create"}}
{{else}}
{{.i18n.Tr "repo.issues.create"}}
{{end}}
</button>
</div>
</div>
</div>
</div>
</div>
<!-- -->
<form class="ui comment form stackable grid" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
{{if .Flash}}
<div class="sixteen wide column">
{{template "base/alert" .}}
</div>
{{end}}
<div class="twelve wide column">
<div class="ui comments">
<div class="comment">
<a class="avatar" href="{{.SignedUser.HomeLink}}">
<img src="{{.SignedUser.RelAvatarLink}}">
</a>
<div class="ui segment content">
<div class="field">
<!-- -->
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255">
{{if .PageIsComparePull}}
<div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div>
{{end}}
</div>

<div class="four wide column">
<div class="ui segment metas">
{{template "repo/issue/branch_selector_field" .}}
<!-- 项目奖励输入框 -->
<!-- <div class="field">
<!-- value="{{.dog}}" ->
<input name="dog" id="issue_reward" placeholder="项目奖励" value="asdfas" tabindex="3" autofocus>
</div> -->

<input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-label dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="filter menu" data-id="#label_ids">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_labels_title"}}</div>
{{if or .Labels .OrgLabels}}
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_labels"}}">
</div>
{{end}}
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div>
{{if or .Labels .OrgLabels}}
{{range .Labels}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
<div class="ui divider"></div>
{{range .OrgLabels}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
{{else}}
<div class="header" style="text-transform: none;font-size:14px;">{{.i18n.Tr "repo.issues.new.no_items"}}</div>
{{end}}
</div>
</div>
<div class="ui labels list">
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
{{range .Labels}}
<a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a>
{{end}}
{{range .OrgLabels}}
<a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a>
{{end}}
</div>
{{template "repo/issue/comment_tab" .}}
<div class="text right">
<button class="ui green button" tabindex="6">
{{if .PageIsComparePull}}
{{.i18n.Tr "repo.pulls.create"}}
{{else}}
{{.i18n.Tr "repo.issues.create"}}
{{end}}
</button>
</div>
</div>
</div>
</div>
</div>

<div class="ui divider"></div>
<div class="four wide column">
<div class="ui segment metas">
{{template "repo/issue/branch_selector_field" .}}

<input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="menu">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_milestone_title"}}</div>
{{if or .OpenMilestones .ClosedMilestones}}
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_milestones"}}">
</div>
{{end}}
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div>
{{if and (not .OpenMilestones) (not .ClosedMilestones)}}
<div class="header" style="text-transform: none;font-size:14px;">
{{.i18n.Tr "repo.issues.new.no_items"}}
</div>
{{else}}
{{if .OpenMilestones}}
<div class="divider"></div>
<div class="header">
{{svg "octicon-milestone" 16}}
{{.i18n.Tr "repo.issues.new.open_milestone"}}
</div>
{{range .OpenMilestones}}
<div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</div>
{{end}}
{{end}}
{{if .ClosedMilestones}}
<div class="divider"></div>
<div class="header">
{{svg "octicon-milestone" 16}}
{{.i18n.Tr "repo.issues.new.closed_milestone"}}
</div>
{{range .ClosedMilestones}}
<a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</a>
{{end}}
{{end}}
{{end}}
</div>
</div>
<div class="ui select-milestone list">
<span class="no-select item {{if .Milestone}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_milestone"}}</span>
<div class="selected">
{{if .Milestone}}
<a class="item" href="{{.RepoLink}}/issues?milestone={{.Milestone.ID}}"> {{.Milestone.Name}}</a>
{{end}}
</div>
</div>
<input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-label dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="filter menu" data-id="#label_ids">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_labels_title"}}</div>
{{if or .Labels .OrgLabels}}
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_labels"}}">
</div>
{{end}}
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div>
{{if or .Labels .OrgLabels}}
{{range .Labels}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
<div class="ui divider"></div>
{{range .OrgLabels}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
{{else}}
<div class="header" style="text-transform: none;font-size:14px;">{{.i18n.Tr "repo.issues.new.no_items"}}</div>
{{end}}
</div>
</div>
<div class="ui labels list">
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
{{range .Labels}}
<a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a>
{{end}}
{{range .OrgLabels}}
<a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a>
{{end}}
</div>

<div class="ui divider"></div>
<div class="ui divider"></div>

<input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="filter menu" data-id="#assignee_ids">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_assignees_title"}}</div>
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_assignees"}}">
</div>
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignees"}}</div>
{{range .Assignees}}
<a class="item" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
<span class="octicon-check invisible">{{svg "octicon-check" 16}}</span>
<span class="text">
<img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}
</span>
</a>
{{end}}
</div>
</div>
<div class="ui assignees list">
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">
{{.i18n.Tr "repo.issues.new.no_assignees"}}
</span>
{{range .Assignees}}
<a style="padding: 5px;color:rgba(0, 0, 0, 0.87);" class="hide item" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
<img class="ui avatar image" src="{{.RelAvatarLink}}" style="vertical-align: middle;">&nbsp;{{.GetDisplayName}}
</a>
{{end}}
</div>
</div>
</div>
<input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="menu">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_milestone_title"}}</div>
{{if or .OpenMilestones .ClosedMilestones}}
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_milestones"}}">
</div>
{{end}}
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div>
{{if and (not .OpenMilestones) (not .ClosedMilestones)}}
<div class="header" style="text-transform: none;font-size:14px;">
{{.i18n.Tr "repo.issues.new.no_items"}}
</div>
{{else}}
{{if .OpenMilestones}}
<div class="divider"></div>
<div class="header">
{{svg "octicon-milestone" 16}}
{{.i18n.Tr "repo.issues.new.open_milestone"}}
</div>
{{range .OpenMilestones}}
<div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</div>
{{end}}
{{end}}
{{if .ClosedMilestones}}
<div class="divider"></div>
<div class="header">
{{svg "octicon-milestone" 16}}
{{.i18n.Tr "repo.issues.new.closed_milestone"}}
</div>
{{range .ClosedMilestones}}
<a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</a>
{{end}}
{{end}}
{{end}}
</div>
</div>
<div class="ui select-milestone list">
<span class="no-select item {{if .Milestone}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_milestone"}}</span>
<div class="selected">
{{if .Milestone}}
<a class="item" href="{{.RepoLink}}/issues?milestone={{.Milestone.ID}}"> {{.Milestone.Name}}</a>
{{end}}
</div>
</div>

<div class="ui divider"></div>

<input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown">
<span class="text">
<strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
{{svg "octicon-gear" 16}}
{{end}}
</span>
<div class="filter menu" data-id="#assignee_ids">
<div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_assignees_title"}}</div>
<div class="ui icon search input">
<i class="search icon"></i>
<input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_assignees"}}">
</div>
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignees"}}</div>
{{range .Assignees}}
<a class="item" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
<span class="octicon-check invisible">{{svg "octicon-check" 16}}</span>
<span class="text">
<img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}
</span>
</a>
{{end}}
</div>
</div>
<div class="ui assignees list">
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">
{{.i18n.Tr "repo.issues.new.no_assignees"}}
</span>
{{range .Assignees}}
<a style="padding: 5px;color:rgba(0, 0, 0, 0.87);" class="hide item" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
<img class="ui avatar image" src="{{.RelAvatarLink}}" style="vertical-align: middle;">&nbsp;{{.GetDisplayName}}
</a>
{{end}}
</div>
</div>
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script>
// $(document).ready(function(){
// var reward_value = $('.ui.form').form('get value', 'dog')
// var reward_value = $('form').form('get value', 'dog')
// console.log(reward_value)
// alert(reward_value)
// $('.ui.green.button').click(function(){
// $('.ui.form')
// .form({
// // on: 'blur',
// inline: true,
// fields: {
// dog: {
// identifier: 'dog',
// rules: [
// {
// type: 'empty',
// prompt: '请您输入项目奖励'
// },
// {
// type : 'integer[0..100]',
// prompt : '项目奖励必须为整数,请您输入有效奖励金额'
// }
// ]
// }
// },
// onFailure: function(e){
// return false;
// }
// });
// });
// </script>
{{if .PageIsComparePull}}
<script>window.wipPrefixes = {{.PullRequestWorkInProgressPrefixes}}</script>
<script>window.wipPrefixes = {{.PullRequestWorkInProgressPrefixes}}</script>
{{end}}


+ 102
- 1
templates/repo/issue/view_content/pull.tmpl View File

@@ -1,3 +1,9 @@
<style>
.ui.selection.dropdown {
padding-top: 0em;
padding-bottom: 0em;
}
</style>
{{if gt (len .PullReviewers) 0}}
<div class="comment box">
<div class="content">
@@ -58,6 +64,8 @@
</div>
</div>
{{end}}

<!-- 合并请求 -->
<div class="timeline-item comment merge box">
<a class="timeline-avatar text {{if .Issue.PullRequest.HasMerged}}purple
{{- else if .Issue.IsClosed}}grey
@@ -188,6 +196,8 @@
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
</div>
{{end}}

<!-- 合并请求 -->
{{if .AllowMerge}}
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
{{$approvers := .Issue.PullRequest.GetApprovers}}
@@ -197,6 +207,21 @@
<div class="ui form merge-fields" style="display: none">
<form action="{{.Link}}/merge" method="post">
{{.CsrfTokenHtml}}
<!-- 下拉框 -->
<div class="field">
<div class="ui dropdown selection">
<input type="hidden" name="block_chain_amount">
<div class="default text">项目奖励</div>
<i class="dropdown icon"></i>
<div class="menu" >
<div class="item" data-value=0>0</div>
<div class="item" data-value=100>100</div>
<div class="item" data-value=200>200</div>
<div class="item" data-value=300>300</div>
</div>
</div>
</div>

<div class="field">
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}">
</div>
@@ -206,7 +231,7 @@
<button class="ui green button" type="submit" name="do" value="merge">
{{$.i18n.Tr "repo.pulls.merge_pull_request"}}
</button>
<button class="ui button merge-cancel">
<button class="ui button merge-cancel" onclick="$('.ui.form.merge-fields')[0].reset();">
{{$.i18n.Tr "cancel"}}
</button>
</form>
@@ -216,6 +241,21 @@
<div class="ui form rebase-fields" style="display: none">
<form action="{{.Link}}/merge" method="post">
{{.CsrfTokenHtml}}
<!-- 下拉框 -->
<div class="field">
<div class="ui dropdown selection">
<input type="hidden" name="block_chain_amount">
<div class="default text">项目奖励</div>
<i class="dropdown icon"></i>
<div class="menu" >
<div class="item" data-value=0>0</div>
<div class="item" data-value=100>100</div>
<div class="item" data-value=200>200</div>
<div class="item" data-value=300>300</div>
</div>
</div>
</div>

<button class="ui green button" type="submit" name="do" value="rebase">
{{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}}
</button>
@@ -229,6 +269,21 @@
<div class="ui form rebase-merge-fields" style="display: none">
<form action="{{.Link}}/merge" method="post">
{{.CsrfTokenHtml}}
<!-- 下拉框 -->
<div class="field">
<div class="ui dropdown selection">
<input type="hidden" name="block_chain_amount">
<div class="default text">项目奖励</div>
<i class="dropdown icon"></i>
<div class="menu" >
<div class="item" data-value=0>0</div>
<div class="item" data-value=100>100</div>
<div class="item" data-value=200>200</div>
<div class="item" data-value=300>300</div>
</div>
</div>
</div>

<div class="field">
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}">
</div>
@@ -248,6 +303,21 @@
<div class="ui form squash-fields" style="display: none">
<form action="{{.Link}}/merge" method="post">
{{.CsrfTokenHtml}}
<!-- 下拉框 -->
<div class="field">
<div class="ui dropdown selection">
<input type="hidden" name="block_chain_amount">
<div class="default text">项目奖励</div>
<i class="dropdown icon"></i>
<div class="menu" >
<div class="item" data-value=0>0</div>
<div class="item" data-value=100>100</div>
<div class="item" data-value=200>200</div>
<div class="item" data-value=300>300</div>
</div>
</div>
</div>

<div class="field">
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage}}">
</div>
@@ -377,3 +447,34 @@
</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script>

$(document)
.ready(function() {
$('.ui.dropdown.selection').dropdown();

$('.ui.green.button').click(function(){
$('.ui.form')
.form({
on: 'blur',
inline: true,
fields: {
block_chain_amount: {
identifier: 'block_chain_amount',
rules: [
{
type : 'empty',
prompt : '项目奖励不能为空'
}
]
}
},
onFailure: function(e){
return false;
}
});
});
})
</script>

Loading…
Cancel
Save