From 0d98890831032fdce5d56e301181585eacfa6caa Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 29 Aug 2022 11:00:13 +0800 Subject: [PATCH 01/91] =?UTF-8?q?0918=E5=88=86=E6=94=AF=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 50 ++++++++++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + routers/user/Invitation.go | 36 +++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 models/user_invitation.go create mode 100644 routers/user/Invitation.go diff --git a/models/user_invitation.go b/models/user_invitation.go new file mode 100644 index 000000000..db004b7e1 --- /dev/null +++ b/models/user_invitation.go @@ -0,0 +1,50 @@ +package models + +import ( + "fmt" + + "code.gitea.io/gitea/modules/timeutil" +) + +// Follow represents relations of user and his/her followers. +type Invitation struct { + ID int64 `xorm:"pk autoincr"` + SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` + UserID int64 `xorm:"NOT NULL DEFAULT 0"` + Phone string `xorm:"NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) + + userList := make([]*Invitation, 0) + + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + Find(&userList); err != nil { + return nil, 0 + } + return userList, len(userList) +} + +func InsertInvitaion(invitationUser *Invitation) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + statictisSess.Insert(invitationUser) +} + +func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "src_user_id =" + fmt.Sprint(srcUserId) + + userList := make([]*Invitation, 0) + + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + Find(&userList); err != nil { + return nil, 0 + } + return userList, len(userList) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9d83594fa..4982edd93 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -504,6 +504,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/forgot_password", user.ForgotPasswd) m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) + m.Get("/invitation_code", user.GetInvitaionCode) }) // ***** END: User ***** diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go new file mode 100644 index 000000000..12050d928 --- /dev/null +++ b/routers/user/Invitation.go @@ -0,0 +1,36 @@ +package user + +import ( + "strings" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/repository" +) + +func GetInvitaionCode(ctx *context.Context) { + + url := setting.RecommentRepoAddr + "invitaion_page" + result, err := repository.RecommendFromPromote(url) + resultJsonMap := make(map[string]interface{}, 0) + if err == nil { + for _, strLine := range result { + tmpIndex := strings.Index(strLine, "=") + if tmpIndex != -1 { + key := strLine[0:tmpIndex] + value := strLine[tmpIndex+1:] + resultJsonMap[key] = value + } + } + } + + if ctx.IsSigned { + resultJsonMap["invitaion_code"] = ctx.User.Name + + } + ctx.JSON(200, resultJsonMap) +} + +func RegisteUserByInvitaionCode(ctx *context.Context) { + +} From a51f73654381f289eeee98c353c092636e7dea1d Mon Sep 17 00:00:00 2001 From: zouap Date: Tue, 30 Aug 2022 17:39:04 +0800 Subject: [PATCH 02/91] =?UTF-8?q?=E8=80=81=E6=8B=89=E6=96=B0=E9=9C=80?= =?UTF-8?q?=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 32 ++++++++++++++++++-------- modules/auth/user_form.go | 3 ++- routers/routes/routes.go | 1 + routers/user/Invitation.go | 41 ++++++++++++++++++++++++++++++--- routers/user/auth.go | 46 ++++++++++++++++++++------------------ 5 files changed, 88 insertions(+), 35 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index db004b7e1..816cacdaf 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -11,28 +11,42 @@ type Invitation struct { ID int64 `xorm:"pk autoincr"` SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` UserID int64 `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"INDEX"` CreatedUnix timeutil.TimeStamp `xorm:"created"` } +func QueryInvitaionByPhone(phone string) []*Invitation { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "phone ='" + phone + "'" + invitationList := make([]*Invitation, 0) + if err := statictisSess.Table(new(Invitation)).Where(cond). + Find(&invitationList); err != nil { + return nil + } else { + return invitationList + } +} + func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) - userList := make([]*Invitation, 0) + invitationList := make([]*Invitation, 0) if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). - Find(&userList); err != nil { + Find(&invitationList); err != nil { return nil, 0 } - return userList, len(userList) + return invitationList, len(invitationList) } -func InsertInvitaion(invitationUser *Invitation) { +func InsertInvitaion(invitationUser *Invitation) error { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - statictisSess.Insert(invitationUser) + _, err := statictisSess.Insert(invitationUser) + return err } func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { @@ -40,11 +54,11 @@ func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { defer statictisSess.Close() cond := "src_user_id =" + fmt.Sprint(srcUserId) - userList := make([]*Invitation, 0) + invitationList := make([]*Invitation, 0) if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). - Find(&userList); err != nil { + Find(&invitationList); err != nil { return nil, 0 } - return userList, len(userList) + return invitationList, len(invitationList) } diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 130586a5a..885e3efbc 100755 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -86,6 +86,7 @@ type RegisterForm struct { Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` Agree bool + InvitaionCode string } // Validate valideates the fields @@ -372,7 +373,7 @@ func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) bind type PhoneNumberForm struct { PhoneNumber string `binding:"Required;MaxSize(20)"` - Mode int `binding:"Required"` + Mode int `binding:"Required"` SlideID string `binding:"Required;MaxSize(100)"` } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 4982edd93..3f927ea79 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -376,6 +376,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) + m.Get("/invitaion", user.GetInvitaionCode) m.Get("/login/phone", user.SignInPhone) m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) m.Group("", func() { diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go index 12050d928..fc0a03f45 100644 --- a/routers/user/Invitation.go +++ b/routers/user/Invitation.go @@ -1,9 +1,13 @@ package user import ( + "errors" "strings" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/repository" ) @@ -25,12 +29,43 @@ func GetInvitaionCode(ctx *context.Context) { } if ctx.IsSigned { - resultJsonMap["invitaion_code"] = ctx.User.Name - + resultJsonMap["invitaion_code"] = getInvitaionCode(ctx) } ctx.JSON(200, resultJsonMap) } -func RegisteUserByInvitaionCode(ctx *context.Context) { +func RegisteUserByInvitaionCode(form auth.RegisterForm, newUserId int64) error { + invitationcode := form.InvitaionCode + + re := models.QueryInvitaionByPhone(form.PhoneNumber) + if re != nil { + if len(re) > 0 { + log.Info("The phone has been invitated. so ingore it.") + return errors.New("The phone has been invitated.") + } + } + + user := parseInvitaionCode(invitationcode) + invitation := &models.Invitation{ + SrcUserID: user.ID, + UserID: newUserId, + Phone: form.PhoneNumber, + } + err := models.InsertInvitaion(invitation) + if err != nil { + log.Info("insert error," + err.Error()) + } + return err +} + +func getInvitaionCode(ctx *context.Context) string { + return ctx.User.Name +} +func parseInvitaionCode(invitationcode string) *models.User { + user, err := models.GetUserByName(invitationcode) + if err == nil { + return user + } + return nil } diff --git a/routers/user/auth.go b/routers/user/auth.go index 7cd6472ff..a95d0e3c4 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -8,11 +8,12 @@ package user import ( "errors" "fmt" - "github.com/gomodule/redigo/redis" "net/http" "strconv" "strings" + "github.com/gomodule/redigo/redis" + "code.gitea.io/gitea/modules/slideimage" phoneService "code.gitea.io/gitea/services/phone" @@ -352,18 +353,17 @@ func SignInPostCommon(ctx *context.Context, form auth.SignInForm) { ctx.Redirect(setting.AppSubURL + "/user/two_factor") } - func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsCloudBrainLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" - SignInPostCommon(ctx,form) + SignInPostCommon(ctx, form) } // SignInPost response for sign in request func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" - SignInPostCommon(ctx,form) + SignInPostCommon(ctx, form) } // TwoFactor shows the user a two-factor authentication page. @@ -1337,6 +1337,9 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) return } + if form.InvitaionCode != "" { + RegisteUserByInvitaionCode(ctx, form) + } u := &models.User{ Name: form.UserName, @@ -1919,7 +1922,7 @@ func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, for return } - if form.Mode==0 { //注册 + if form.Mode == 0 { //注册 if has { ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) @@ -1935,32 +1938,31 @@ func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, for } else { //修改手机号 mode=2 绑定手机 - u, err := models.GetUserByPhoneNumber(phoneNumber) - if err != nil && !models.IsErrUserNotExist(err) { - log.Warn("sql err", err) - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) - return - } - - if u != nil { + u, err := models.GetUserByPhoneNumber(phoneNumber) + if err != nil && !models.IsErrUserNotExist(err) { + log.Warn("sql err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } - if u.ID == ctx.User.ID { //没有修改手机号 - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) - return - } else { //修改的手机已经被别的用户注册 - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) - return - } + if u != nil { + if u.ID == ctx.User.ID { //没有修改手机号 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) + return + } else { //修改的手机已经被别的用户注册 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) + return } - } + } + } redisConn := labelmsg.Get() defer redisConn.Close() sendTimes, err := phoneService.GetPhoneNumberSendTimes(redisConn, phoneNumber) - if err != nil && err!=redis.ErrNil { + if err != nil && err != redis.ErrNil { log.Warn("redis err", err) ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) return From 2eeb3d53b6173622143124c2e85c24a615f7f63d Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 5 Sep 2022 10:47:35 +0800 Subject: [PATCH 03/91] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E8=80=81=E6=8B=89=E6=96=B0=E9=9C=80=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 6 +++--- modules/grampus/resty.go | 11 ++++++----- routers/api/v1/api.go | 1 + routers/repo/cloudbrain.go | 2 +- routers/repo/grampus.go | 30 ++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + routers/user/Invitation.go | 13 ++++++++++++- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index 816cacdaf..56de43d01 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -49,7 +49,7 @@ func InsertInvitaion(invitationUser *Invitation) error { return err } -func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { +func QueryInvitaionBySrcUserId(srcUserId int64) []*Invitation { statictisSess := xStatistic.NewSession() defer statictisSess.Close() cond := "src_user_id =" + fmt.Sprint(srcUserId) @@ -58,7 +58,7 @@ func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). Find(&invitationList); err != nil { - return nil, 0 + return nil } - return invitationList, len(invitationList) + return invitationList } diff --git a/modules/grampus/resty.go b/modules/grampus/resty.go index 5e8722b4b..593abccbb 100755 --- a/modules/grampus/resty.go +++ b/modules/grampus/resty.go @@ -1,14 +1,15 @@ package grampus import ( - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "crypto/tls" "encoding/json" "fmt" - "github.com/go-resty/resty/v2" "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "github.com/go-resty/resty/v2" ) var ( @@ -235,7 +236,7 @@ func GetTrainJobLog(jobID string) (string, error) { return logContent, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error()) } log.Error("GetTrainJobLog failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) - return logContent, fmt.Errorf("GetTrainJobLog failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + return logContent, fmt.Errorf("GetTrainJobLog failed(%d):%d(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) } logContent = res.String() diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0b941b400..3e588d942 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -969,6 +969,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", repo.GetModelArtsTrainJobVersion) m.Post("/stop_version", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo_ext.GrampusStopJob) m.Get("/log", repo_ext.GrampusGetLog) + m.Get("/download_log", repo_ext.GrampusDownloadLog) }) }) }, reqRepoReader(models.UnitTypeCloudBrain)) diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index c1e89dde5..457f275ed 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -2718,7 +2718,7 @@ func getTrainJobCommand(form auth.CreateCloudBrainForm) (string, error) { } } - command += "python /code/" + bootFile + param + " | tee " + cloudbrain.ModelMountPath + "/" + form.DisplayJobName + "-" + cloudbrain.LogFile + command += "python /code/" + bootFile + param + " > " + cloudbrain.ModelMountPath + "/" + form.DisplayJobName + "-" + cloudbrain.LogFile return command, nil } diff --git a/routers/repo/grampus.go b/routers/repo/grampus.go index 33e111df2..4b9ef621c 100755 --- a/routers/repo/grampus.go +++ b/routers/repo/grampus.go @@ -725,6 +725,36 @@ func GrampusTrainJobShow(ctx *context.Context) { ctx.HTML(http.StatusOK, tplGrampusTrainJobShow) } +func GrampusDownloadLog(ctx *context.Context) { + jobID := ctx.Params(":jobid") + job, err := models.GetCloudbrainByJobID(jobID) + if err != nil { + log.Error("GetCloudbrainByJobID failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + + content, err := grampus.GetTrainJobLog(job.JobID) + if err != nil { + log.Error("GetTrainJobLog failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + fileName := job.JobName + "-log.txt" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+fileName) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + var b []byte = []byte(content) + + ctx.Resp.Write(b) + + // ctx.JSON(http.StatusOK, map[string]interface{}{ + // "JobName": job.JobName, + // "Content": content, + // }) + + //return +} + func GrampusGetLog(ctx *context.Context) { jobID := ctx.Params(":jobid") job, err := models.GetCloudbrainByJobID(jobID) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 3f927ea79..bfa0552ac 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -506,6 +506,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) m.Get("/invitation_code", user.GetInvitaionCode) + m.Get("/invitation_tpl", user.InviationTpl) }) // ***** END: User ***** diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go index fc0a03f45..78718b33f 100644 --- a/routers/user/Invitation.go +++ b/routers/user/Invitation.go @@ -6,12 +6,17 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/repository" ) +const ( + tplInvitation base.TplName = "user/settings/invite" +) + func GetInvitaionCode(ctx *context.Context) { url := setting.RecommentRepoAddr + "invitaion_page" @@ -29,11 +34,17 @@ func GetInvitaionCode(ctx *context.Context) { } if ctx.IsSigned { - resultJsonMap["invitaion_code"] = getInvitaionCode(ctx) + resultJsonMap["invitation_code"] = getInvitaionCode(ctx) + resultJsonMap["invitation_users"] = models.QueryInvitaionBySrcUserId(ctx.User.ID) } + ctx.JSON(200, resultJsonMap) } +func InviationTpl(ctx *context.Context) { + ctx.HTML(200, tplInvitation) +} + func RegisteUserByInvitaionCode(form auth.RegisterForm, newUserId int64) error { invitationcode := form.InvitaionCode From a84152653a4bfe421d7bd4da3c786129c9267a54 Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 5 Sep 2022 11:37:08 +0800 Subject: [PATCH 04/91] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- routers/routes/routes.go | 7 +++---- routers/user/auth.go | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 142fde739..af139b7a9 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -375,8 +375,8 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/login/cloud_brain", user.SignInCloudBrain) m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) - - m.Get("/invitaion", user.GetInvitaionCode) + m.Get("/invitation_code", user.GetInvitaionCode) + m.Get("/invitation_tpl", user.InviationTpl) m.Get("/login/phone", user.SignInPhone) m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) m.Group("", func() { @@ -505,8 +505,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/forgot_password", user.ForgotPasswd) m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) - m.Get("/invitation_code", user.GetInvitaionCode) - m.Get("/invitation_tpl", user.InviationTpl) + }) // ***** END: User ***** diff --git a/routers/user/auth.go b/routers/user/auth.go index a95d0e3c4..3c80af4e1 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1337,9 +1337,6 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) return } - if form.InvitaionCode != "" { - RegisteUserByInvitaionCode(ctx, form) - } u := &models.User{ Name: form.UserName, @@ -1369,6 +1366,10 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo } log.Trace("Account created: %s", u.Name, ctx.Data["MsgID"]) + if form.InvitaionCode != "" { + RegisteUserByInvitaionCode(form, u.ID) + } + err := models.AddEmailAddress(&models.EmailAddress{ UID: u.ID, Email: form.Email, From 608859e5ce1d066eb6f9107ce52f304120c3285a Mon Sep 17 00:00:00 2001 From: chenshihai Date: Mon, 5 Sep 2022 14:08:38 +0800 Subject: [PATCH 05/91] Invite Friends --- options/locale/locale_en-US.ini | 1 + options/locale/locale_zh-CN.ini | 1 + public/img/ad/ad01.png | Bin 0 -> 13638 bytes public/img/ad/ad02.png | Bin 0 -> 32334 bytes public/img/ad/ad03.jpg | Bin 0 -> 36620 bytes templates/base/head_navbar.tmpl | 4 + templates/base/head_navbar_fluid.tmpl | 4 + templates/base/head_navbar_home.tmpl | 4 + templates/base/head_navbar_pro.tmpl | 4 + templates/user/auth/signup_inner.tmpl | 13 + templates/user/dashboard/repolist.tmpl | 3 + templates/user/profile.tmpl | 6 + templates/user/settings/invite.tmpl | 7 + web_src/js/features/ad.js | 87 ++++++ web_src/js/index.js | 1 + web_src/js/standalone/phoneverify.js | 20 +- web_src/less/standalone/_phoneverify.less | 3 +- web_src/vuepages/apis/modules/userinvite.js | 11 + web_src/vuepages/pages/user/invite/index.vue | 279 ++++++++++++++++++ .../pages/user/invite/vp-user-invite.js | 17 ++ 20 files changed, 459 insertions(+), 6 deletions(-) create mode 100644 public/img/ad/ad01.png create mode 100644 public/img/ad/ad02.png create mode 100644 public/img/ad/ad03.jpg create mode 100644 templates/user/settings/invite.tmpl create mode 100644 web_src/js/features/ad.js create mode 100644 web_src/vuepages/apis/modules/userinvite.js create mode 100644 web_src/vuepages/pages/user/invite/index.vue create mode 100644 web_src/vuepages/pages/user/invite/vp-user-invite.js diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3453344f7..30c3c3b5b 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -69,6 +69,7 @@ your_dashboard = Dashboard your_profile = Profile your_starred = Starred your_settings = Settings +invite_friends = Invite Friends all = All sources = Sources diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index d527218d3..e1354bdfd 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -69,6 +69,7 @@ your_dashboard=个人中心 your_profile=个人信息 your_starred=已点赞 your_settings=设置 +invite_friends=邀请好友 all=所有 sources=自建 diff --git a/public/img/ad/ad01.png b/public/img/ad/ad01.png new file mode 100644 index 0000000000000000000000000000000000000000..379c39bd155af3eb2ff359338f78a1713844ea34 GIT binary patch literal 13638 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 {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_fluid.tmpl b/templates/base/head_navbar_fluid.tmpl index 84781db11..9e31cc2db 100644 --- a/templates/base/head_navbar_fluid.tmpl +++ b/templates/base/head_navbar_fluid.tmpl @@ -177,6 +177,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + + {{.i18n.Tr "invite_friends"}} {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_home.tmpl b/templates/base/head_navbar_home.tmpl index 64e04b4c3..471540f64 100644 --- a/templates/base/head_navbar_home.tmpl +++ b/templates/base/head_navbar_home.tmpl @@ -160,6 +160,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_pro.tmpl b/templates/base/head_navbar_pro.tmpl index e9f662bbe..0b5babf6e 100644 --- a/templates/base/head_navbar_pro.tmpl +++ b/templates/base/head_navbar_pro.tmpl @@ -181,6 +181,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index 6ba10e8e7..43667f6f5 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -35,6 +35,9 @@ {{if .DisableRegistration}}

{{.i18n.Tr "auth.disable_register_prompt"}}

{{else}} +
+ 您的好友 Itx003 邀请你加入启智社区AI协作平台,畅享充沛的免费算力资源! +
@@ -71,6 +74,16 @@ {{template "user/auth/phone_verify" .}} {{end}} + +
+
+
+ 推荐人 +
+ +
+
+
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl index ff85d72d4..b89fdbf1f 100644 --- a/templates/user/dashboard/repolist.tmpl +++ b/templates/user/dashboard/repolist.tmpl @@ -18,6 +18,9 @@ v-cloak >
+
+ +
@@ -107,3 +107,29 @@
+ diff --git a/web_src/vuepages/pages/user/invite/index.vue b/web_src/vuepages/pages/user/invite/index.vue index 86cdc7a54..b49db76cb 100644 --- a/web_src/vuepages/pages/user/invite/index.vue +++ b/web_src/vuepages/pages/user/invite/index.vue @@ -6,25 +6,25 @@
- -
邀请好友来启智,用免费算力还能赚奖金!
+ +
{{ bannerTitle }}
- 新一期的开源打榜活动,每邀请一名好友注册并激活,就可以获得5打榜积分。快快邀请更多好友帮你冲击榜单吧~ - 点击查看活动详情 + {{ pageLinkDesc }} + 点击查看活动详情
- 启智AI协作平台是启智社区面向AI开发者提供的一站式AI开发协作平台,提供了代码托管、数据集管理、基于异构计算资源的模型调试与训练等功能。目前已经与鹏城云脑、中国算力网(C²NET)一期打通,免费提供丰富算力资源,支撑大家完成AI开发任务。 + {{ pageOpeniDesc }}
-
{{ sharedLink }}
-
推荐人:{{ sharedUser }}
+
{{ invitationLink + invitationCode }}
+
推荐人:{{ invitationCode }}
复制注册邀请链接
- +
@@ -43,9 +43,7 @@ {{ scope.row.statusStr }} - - - + {{end}}
  • {{svg "octicon-clock" 16}} {{.i18n.Tr "user.join_on"}} {{.Owner.CreatedUnix.FormatShort}}
  • diff --git a/web_src/js/features/ad.js b/web_src/js/features/ad.js index 55036c1d9..7e555e749 100644 --- a/web_src/js/features/ad.js +++ b/web_src/js/features/ad.js @@ -1,43 +1,71 @@ ; (function () { - const adList = [{ - id: 1, - pos: { - left: 50, - bottom: 50, + /*const adList = [ + { + "width": 144, + "height": 108, + "pos": { + "left": 50, + "bottom": 50 }, - src: '/img/ad/ad01.png', - url: '/user/invitation_tpl', - width: 144, - height: 108, - }/*, { - id: 2, - pos: { - right: 50, - bottom: 50, + "src": "https://git.openi.org.cn/OpenIOSSG/promote/raw/branch/master/imgs/invitation/pic-01.png", + "url": "/user/invitation_tpl", + "show": true + }, + { + "width": 144, + "height": 108, + "pos": { + "right": 50, + "bottom": 50 }, - src: '/img/ad/ad01.png', - url: '/user/invitation_tpl', - width: 144, - height: 108, - }*/]; + "src": "https://git.openi.org.cn/OpenIOSSG/promote/raw/branch/master/imgs/invitation/pic-01.png", + "url": "/user/invitation_tpl", + "show": false + } +];*/ + const exceptPages = [ + // '/user/invitation_tpl' + ]; + + function initAd() { + $.ajax({ + type: "GET", + url: "/dashboard/invitation", + dataType: "json", + data: { filename: 'ad-pop-up.json' }, + success: function (res) { + try { + var data = JSON.parse(res); + createAd(data); + } catch (err) { + console.log(err); + } + }, + error: function (err) { + console.log(err); + } + }); + } function createAd(adList) { const adInfoStr = window.localStorage.getItem('ads') || '{}'; let adInfoObj = JSON.parse(adInfoStr); const today = new Date(); - const timeEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1).getTime(); + const timeTodayEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1).getTime(); const now = Date.now(); + const expTime = now + 4 * 60 * 60 * 1000; if (!adInfoObj.expires || adInfoObj.expires <= now) { adInfoObj = { - expires: timeEnd, + expires: Math.min(timeTodayEnd, expTime), }; } for (var i = 0, iLen = adList.length; i < iLen; i++) { var adI = adList[i]; - var showOr = adInfoObj[adI.id] === false ? false : true; - adInfoObj[adI.id] = showOr; + if (adI.show === false) continue; + var showOr = adInfoObj[i] === false ? false : true; + adInfoObj[i] = showOr; if (!showOr) continue; - var adEl = $(`
    `); + adEl.data('data', adI); $('body').append(adEl); } window.localStorage.setItem('ads', JSON.stringify(adInfoObj)); @@ -73,15 +102,39 @@ var offSet = scrollTop - scrollTopOld; scrollTopOld = scrollTop; timeHandler && clearTimeout(timeHandler); - $('.__ad_c__').animate({ bottom: 50 + offSet + 'px' }, 0); + $('.__ad_c__').each(function (_, item) { + var self = $(item); + var adData = self.data('data'); + if (adData.pos.bottom !== undefined) { + self.animate({ bottom: adData.pos.bottom + offSet + 'px' }, 0); + } + if (adData.pos.top !== undefined) { + self.animate({ top: adData.pos.top - offSet + 'px' }, 0); + } + }) timeHandler = setTimeout(function () { - $('.__ad_c__').animate({ bottom: 50 + 'px' }, 0); + $('.__ad_c__').each(function (_, item) { + var self = $(item); + var adData = self.data('data'); + if (adData.pos.bottom !== undefined) { + self.animate({ bottom: adData.pos.bottom + 'px' }, 0); + } + if (adData.pos.top !== undefined) { + self.animate({ top: adData.pos.top + 'px' }, 0); + } + }) }, 20); }); } setTimeout(function () { - createAd(adList); + if (!$('meta[name="_uid"]').length) { // 未登录,不显示 + window.localStorage.removeItem('ads'); + return; + } + var pathName = window.location.pathname; + if (exceptPages.indexOf(pathName) > -1) return; // 排除页,不显示 + initAd(); initAdEvent(); }, 0); })(); From 65afb3bb73d4eb3d02418a64dc35486490ca7b79 Mon Sep 17 00:00:00 2001 From: zouap Date: Wed, 14 Sep 2022 16:59:05 +0800 Subject: [PATCH 31/91] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BF=90=E8=90=A5?= =?UTF-8?q?=E5=88=86=E6=9E=90=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 14 +++-- routers/api/v1/api.go | 10 ++++ routers/repo/user_invitation.go | 98 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 routers/repo/user_invitation.go diff --git a/models/user_invitation.go b/models/user_invitation.go index 4831e27da..5d9105fa7 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -32,18 +32,22 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } -func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { +func QueryInvitaionPage(startTime int64, endTime int64, start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) + cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) + allCount, err := statictisSess.Where(cond).Count(new(Invitation)) + if err != nil { + log.Info("query error." + err.Error()) + return nil, 0 + } invitationList := make([]*Invitation, 0) - - if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc").Limit(pageSize, start). Find(&invitationList); err != nil { return nil, 0 } - return invitationList, len(invitationList) + return invitationList, allCount } func InsertInvitaion(invitationUser *Invitation) error { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 3e588d942..36ba44ce5 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -572,6 +572,16 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/query_user_all", operationReq, repo_ext.QueryUserStaticAll) m.Get("/query_user_activity", operationReq, repo_ext.QueryUserActivity) m.Get("/query_user_login", operationReq, repo_ext.QueryUserLoginInfo) + + m.Get("/query_invitation_current_month", operationReq, repo_ext.QueryInvitationCurrentMonth) + m.Get("/query_invitation_current_week", operationReq, repo_ext.QueryInvitationCurrentWeek) + m.Get("/query_invitation_last_week", operationReq, repo_ext.QueryInvitationLastWeek) + m.Get("/query_invitation_current_year", operationReq, repo_ext.QueryInvitationCurrentYear) + m.Get("/query_invitation_last30_day", operationReq, repo_ext.QueryInvitationLast30Day) + m.Get("/query_invitation_last_month", operationReq, repo_ext.QueryInvitationLastMonth) + m.Get("/query_invitation_yesterday", operationReq, repo_ext.QueryInvitationYesterday) + m.Get("/query_invitation_all", operationReq, repo_ext.QueryInvitationAll) + //cloudbrain board m.Group("/cloudbrainboard", func() { m.Get("/downloadAll", repo.DownloadCloudBrainBoard) diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go new file mode 100644 index 000000000..462fe6b4a --- /dev/null +++ b/routers/repo/user_invitation.go @@ -0,0 +1,98 @@ +package repo + +import ( + "net/http" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" +) + +func QueryInvitationCurrentMonth(ctx *context.Context) { + + currentTimeNow := time.Now() + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationCurrentWeek(ctx *context.Context) { + currentTimeNow := time.Now() + offset := int(time.Monday - currentTimeNow.Weekday()) + if offset > 0 { + offset = -6 + } + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLastWeek(ctx *context.Context) { + currentTimeNow := time.Now() + offset := int(time.Monday - currentTimeNow.Weekday()) + if offset > 0 { + offset = -6 + } + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + pageStartTime := pageEndTime.AddDate(0, 0, -7) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationCurrentYear(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLast30Day(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLastMonth(ctx *context.Context) { + currentTimeNow := time.Now() + thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + pageStartTime := thisMonth.AddDate(0, -1, 0) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationYesterday(ctx *context.Context) { + currentTimeNow := time.Now().AddDate(0, 0, -1) + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationAll(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func queryData(ctx *context.Context, startTime int64, endTime int64) { + page, pageSize := getPageInfo(ctx) + result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) +} + +func getPageInfo(ctx *context.Context) (int, int) { + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + pageSize := ctx.QueryInt("pageSize") + if pageSize <= 0 { + pageSize = setting.UI.IssuePagingNum + } + return page, pageSize +} From 74a904064ddf30033c03f7a39e12cd4012d2279a Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 10:57:03 +0800 Subject: [PATCH 32/91] =?UTF-8?q?=E8=80=81=E6=8B=89=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=9F=E8=AE=A1=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_business_analysis.go | 77 ++++++++++++++++++-- models/user_business_struct.go | 21 ++++-- models/user_invitation.go | 17 ++--- routers/repo/user_invitation.go | 119 ++++++++++++++++++++----------- 4 files changed, 172 insertions(+), 62 deletions(-) diff --git a/models/user_business_analysis.go b/models/user_business_analysis.go index 0c67a569a..e99927e18 100644 --- a/models/user_business_analysis.go +++ b/models/user_business_analysis.go @@ -106,7 +106,8 @@ type UserBusinessAnalysisAll struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysis struct { @@ -193,7 +194,8 @@ type UserBusinessAnalysis struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisQueryOptions struct { @@ -354,6 +356,33 @@ func QueryRankList(key string, tableName string, limit int) ([]*UserBusinessAnal return userBusinessAnalysisAllList, int64(len(userBusinessAnalysisAllList)) } +func QueryUserInvitationDataByTableName(start int, pageSize int, tableName string, queryObj interface{}, userName string, invitationNum int) ([]*UserBusinessAnalysisAll, int64) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + var cond = builder.NewCond() + if len(userName) > 0 { + cond = cond.And( + builder.Like{"lower(name)", strings.ToLower(userName)}, + ) + } + cond = cond.And( + builder.Gte{"invitation_user_num": invitationNum}, + ) + + allCount, err := statictisSess.Where(cond).Count(queryObj) + if err != nil { + log.Info("query error." + err.Error()) + return nil, 0 + } + log.Info("query return total:" + fmt.Sprint(allCount)) + userBusinessAnalysisAllList := make([]*UserBusinessAnalysisAll, 0) + if err := statictisSess.Table(tableName).Where(cond).OrderBy("invitation_user_num desc,id asc").Limit(pageSize, start). + Find(&userBusinessAnalysisAllList); err != nil { + return nil, 0 + } + return userBusinessAnalysisAllList, allCount +} + func QueryUserStaticDataByTableName(start int, pageSize int, tableName string, queryObj interface{}, userName string) ([]*UserBusinessAnalysisAll, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() @@ -363,6 +392,7 @@ func QueryUserStaticDataByTableName(start int, pageSize int, tableName string, q builder.Like{"lower(name)", strings.ToLower(userName)}, ) } + allCount, err := statictisSess.Where(cond).Count(queryObj) if err != nil { log.Info("query error." + err.Error()) @@ -752,6 +782,8 @@ func refreshUserStaticTable(wikiCountMap map[string]int, tableName string, pageS CollectImage, CollectedImage := queryImageStars(start_unix, end_unix) RecommendImage := queryRecommedImage(start_unix, end_unix) + InvitationMap := queryUserInvitationCount(start_unix, end_unix) + DataDate := currentTimeNow.Format("2006-01-02") + " 00:01" cond := "type != 1 and is_active=true" @@ -825,7 +857,7 @@ func refreshUserStaticTable(wikiCountMap map[string]int, tableName string, pageS dateRecordAll.CollectImage = getMapValue(dateRecordAll.ID, CollectImage) dateRecordAll.CollectedImage = getMapValue(dateRecordAll.ID, CollectedImage) dateRecordAll.RecommendImage = getMapValue(dateRecordAll.ID, RecommendImage) - + dateRecordAll.InvitationUserNum = getMapValue(dateRecordAll.ID, InvitationMap) dateRecordAll.UserIndexPrimitive = getUserIndexFromAnalysisAll(dateRecordAll, ParaWeight) userIndexMap[dateRecordAll.ID] = dateRecordAll.UserIndexPrimitive if maxUserIndex < dateRecordAll.UserIndexPrimitive { @@ -888,7 +920,7 @@ func insertTable(dateRecords []UserBusinessAnalysisAll, tableName string, static insertBatchSql := "INSERT INTO public." + tableName + "(id, count_date, code_merge_count, commit_count, issue_count, comment_count, focus_repo_count, star_repo_count, watched_count, gitea_age_month, commit_code_size, commit_dataset_size, " + - "commit_model_count, solve_issue_count, encyclopedias_count, regist_date, create_repo_count, login_count, open_i_index, email, name, data_date,cloud_brain_task_num,gpu_debug_job,npu_debug_job,gpu_train_job,npu_train_job,npu_inference_job,gpu_bench_mark_job,cloud_brain_run_time,commit_dataset_num,user_index,user_location,focus_other_user,collect_dataset,collected_dataset,recommend_dataset,collect_image,collected_image,recommend_image,user_index_primitive,phone) " + + "commit_model_count, solve_issue_count, encyclopedias_count, regist_date, create_repo_count, login_count, open_i_index, email, name, data_date,cloud_brain_task_num,gpu_debug_job,npu_debug_job,gpu_train_job,npu_train_job,npu_inference_job,gpu_bench_mark_job,cloud_brain_run_time,commit_dataset_num,user_index,user_location,focus_other_user,collect_dataset,collected_dataset,recommend_dataset,collect_image,collected_image,recommend_image,user_index_primitive,phone,invitation_user_num) " + "VALUES" for i, record := range dateRecords { @@ -897,7 +929,7 @@ func insertTable(dateRecords []UserBusinessAnalysisAll, tableName string, static ", " + fmt.Sprint(record.WatchedCount) + ", " + fmt.Sprint(record.GiteaAgeMonth) + ", " + fmt.Sprint(record.CommitCodeSize) + ", " + fmt.Sprint(record.CommitDatasetSize) + ", " + fmt.Sprint(record.CommitModelCount) + ", " + fmt.Sprint(record.SolveIssueCount) + ", " + fmt.Sprint(record.EncyclopediasCount) + ", " + fmt.Sprint(record.RegistDate) + ", " + fmt.Sprint(record.CreateRepoCount) + ", " + fmt.Sprint(record.LoginCount) + ", " + fmt.Sprint(record.OpenIIndex) + ", '" + record.Email + "', '" + record.Name + "', '" + record.DataDate + "'," + fmt.Sprint(record.CloudBrainTaskNum) + "," + fmt.Sprint(record.GpuDebugJob) + "," + fmt.Sprint(record.NpuDebugJob) + "," + fmt.Sprint(record.GpuTrainJob) + "," + fmt.Sprint(record.NpuTrainJob) + "," + fmt.Sprint(record.NpuInferenceJob) + "," + fmt.Sprint(record.GpuBenchMarkJob) + "," + fmt.Sprint(record.CloudBrainRunTime) + "," + fmt.Sprint(record.CommitDatasetNum) + "," + fmt.Sprint(record.UserIndex) + ",'" + record.UserLocation + "'," + - fmt.Sprint(record.FocusOtherUser) + "," + fmt.Sprint(record.CollectDataset) + "," + fmt.Sprint(record.CollectedDataset) + "," + fmt.Sprint(record.RecommendDataset) + "," + fmt.Sprint(record.CollectImage) + "," + fmt.Sprint(record.CollectedImage) + "," + fmt.Sprint(record.RecommendImage) + "," + fmt.Sprint(record.UserIndexPrimitive) + ",'" + record.Phone + "')" + fmt.Sprint(record.FocusOtherUser) + "," + fmt.Sprint(record.CollectDataset) + "," + fmt.Sprint(record.CollectedDataset) + "," + fmt.Sprint(record.RecommendDataset) + "," + fmt.Sprint(record.CollectImage) + "," + fmt.Sprint(record.CollectedImage) + "," + fmt.Sprint(record.RecommendImage) + "," + fmt.Sprint(record.UserIndexPrimitive) + ",'" + record.Phone + "'" + "," + fmt.Sprint(record.InvitationUserNum) + ")" if i < (len(dateRecords) - 1) { insertBatchSql += "," } @@ -2173,6 +2205,41 @@ func queryCloudBrainTask(start_unix int64, end_unix int64) (map[int64]int, map[s return resultMap, resultItemMap } + +func queryUserInvitationCount(start_unix int64, end_unix int64) map[int64]int { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + + resultMap := make(map[int64]int) + cond := "created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix) + count, err := statictisSess.Where(cond).Count(new(Invitation)) + if err != nil { + log.Info("query queryUserInvitationCount error. return.") + return resultMap + } + var indexTotal int64 + indexTotal = 0 + for { + statictisSess.Select("id,src_user_id,user_id").Table("invitation").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal)) + invitationList := make([]*Invitation, 0) + statictisSess.Find(&invitationList) + log.Info("query invitationList size=" + fmt.Sprint(len(invitationList))) + for _, invitationRecord := range invitationList { + if _, ok := resultMap[invitationRecord.SrcUserID]; !ok { + resultMap[invitationRecord.SrcUserID] = 1 + } else { + resultMap[invitationRecord.SrcUserID] += 1 + } + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + log.Info("invitationList size=" + fmt.Sprint(len(resultMap))) + return resultMap +} + func setMapKey(key string, userId int64, value int, resultItemMap map[string]int) { newKey := fmt.Sprint(userId) + "_" + key if _, ok := resultItemMap[newKey]; !ok { diff --git a/models/user_business_struct.go b/models/user_business_struct.go index 36ef077e2..fe98be760 100644 --- a/models/user_business_struct.go +++ b/models/user_business_struct.go @@ -66,7 +66,8 @@ type UserBusinessAnalysisCurrentYear struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLast30Day struct { @@ -133,7 +134,8 @@ type UserBusinessAnalysisLast30Day struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLastMonth struct { @@ -200,7 +202,8 @@ type UserBusinessAnalysisLastMonth struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisCurrentMonth struct { @@ -267,7 +270,8 @@ type UserBusinessAnalysisCurrentMonth struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisCurrentWeek struct { @@ -335,7 +339,8 @@ type UserBusinessAnalysisCurrentWeek struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisYesterday struct { @@ -403,7 +408,8 @@ type UserBusinessAnalysisYesterday struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLastWeek struct { @@ -471,7 +477,8 @@ type UserBusinessAnalysisLastWeek struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserAnalysisPara struct { diff --git a/models/user_invitation.go b/models/user_invitation.go index 5d9105fa7..f0e99b1a7 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -9,14 +9,15 @@ import ( // Follow represents relations of user and his/her followers. type Invitation struct { - ID int64 `xorm:"pk autoincr"` - SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` - UserID int64 `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"INDEX"` - Avatar string `xorm:"-"` - Name string `xorm:"-"` - IsActive bool `xorm:"-"` - CreatedUnix timeutil.TimeStamp `xorm:"created"` + ID int64 `xorm:"pk autoincr"` + SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` + UserID int64 `xorm:"NOT NULL DEFAULT 0"` + Phone string `xorm:"INDEX"` + Avatar string `xorm:"-"` + Name string `xorm:"-"` + InvitationUserNum int `xorm:"-"` + IsActive bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } func QueryInvitaionByPhone(phone string) []*Invitation { diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 462fe6b4a..222fc1c75 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -2,7 +2,6 @@ package repo import ( "net/http" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -10,70 +9,106 @@ import ( ) func QueryInvitationCurrentMonth(ctx *context.Context) { + // userName := ctx.Query("userName") + // currentTimeNow := time.Now() + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - currentTimeNow := time.Now() - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + // queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) + //_, count := models.QueryUserStaticDataByTableName(1, 1, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth), userName, 1) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) } -func QueryInvitationCurrentWeek(ctx *context.Context) { - currentTimeNow := time.Now() - offset := int(time.Monday - currentTimeNow.Weekday()) - if offset > 0 { - offset = -6 +func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { + page, pageSize := getPageInfo(ctx) + userName := ctx.Query("userName") + resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) + result := make([]models.Invitation, 0) + for _, record := range resultRecord { + invi := models.Invitation{ + SrcUserID: record.ID, + Name: record.Name, + InvitationUserNum: record.InvitationUserNum, + Phone: record.Phone, + } + result = append(result, invi) } - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) +} + +func QueryInvitationCurrentWeek(ctx *context.Context) { + // currentTimeNow := time.Now() + // offset := int(time.Monday - currentTimeNow.Weekday()) + // if offset > 0 { + // offset = -6 + // } + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_week", new(models.UserBusinessAnalysisCurrentWeek)) } func QueryInvitationLastWeek(ctx *context.Context) { - currentTimeNow := time.Now() - offset := int(time.Monday - currentTimeNow.Weekday()) - if offset > 0 { - offset = -6 - } - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) - pageStartTime := pageEndTime.AddDate(0, 0, -7) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // offset := int(time.Monday - currentTimeNow.Weekday()) + // if offset > 0 { + // offset = -6 + // } + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + // pageStartTime := pageEndTime.AddDate(0, 0, -7) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last_week", new(models.UserBusinessAnalysisLastWeek)) } func QueryInvitationCurrentYear(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_year", new(models.UserBusinessAnalysisCurrentYear)) } func QueryInvitationLast30Day(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last30_day", new(models.UserBusinessAnalysisLast30Day)) } func QueryInvitationLastMonth(ctx *context.Context) { - currentTimeNow := time.Now() - thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - pageStartTime := thisMonth.AddDate(0, -1, 0) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + // pageStartTime := thisMonth.AddDate(0, -1, 0) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last_month", new(models.UserBusinessAnalysisLastMonth)) } func QueryInvitationYesterday(ctx *context.Context) { - currentTimeNow := time.Now().AddDate(0, 0, -1) - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now().AddDate(0, 0, -1) + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_yesterday", new(models.UserBusinessAnalysisYesterday)) } func QueryInvitationAll(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_all", new(models.UserBusinessAnalysisAll)) } func queryData(ctx *context.Context, startTime int64, endTime int64) { From 784c9efd8dc8f92da7e6a7d573e031866aed917c Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:05:26 +0800 Subject: [PATCH 33/91] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 8 +-- options/locale/locale_en-US.ini | 2 + options/locale/locale_zh-CN.ini | 2 + routers/api/v1/api.go | 1 + routers/repo/user_invitation.go | 115 ++++++++++++++++++++++++++++---- 5 files changed, 110 insertions(+), 18 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index f0e99b1a7..34d6b239f 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -33,18 +33,18 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } -func QueryInvitaionPage(startTime int64, endTime int64, start int, pageSize int) ([]*Invitation, int64) { +func QueryInvitaionPage(start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) + //cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) - allCount, err := statictisSess.Where(cond).Count(new(Invitation)) + allCount, err := statictisSess.Count(new(Invitation)) if err != nil { log.Info("query error." + err.Error()) return nil, 0 } invitationList := make([]*Invitation, 0) - if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc").Limit(pageSize, start). + if err := statictisSess.Table(new(Invitation)).OrderBy("created_unix desc").Limit(pageSize, start). Find(&invitationList); err != nil { return nil, 0 } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 70df4ad90..b8584c785 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -536,6 +536,8 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. +user.static.invitationNum=User Invitation Count +static.invitationsheetname=User Invitation static.sheetname=User Analysis static.id=ID static.name=User Name diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index e6128a859..51f150cee 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -541,7 +541,9 @@ form.name_reserved='%s' 用户名被保留。 form.name_pattern_not_allowed=用户名中不允许使用 "%s"。 form.name_chars_not_allowed=用户名 '%s' 包含无效字符。 +user.static.invitationNum=邀请用户数 static.sheetname=用户分析 +static.invitationsheetname=用户邀请分析 static.id=ID static.name=用户名 static.codemergecount=PR数 diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 36ba44ce5..ff5474914 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -581,6 +581,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/query_invitation_last_month", operationReq, repo_ext.QueryInvitationLastMonth) m.Get("/query_invitation_yesterday", operationReq, repo_ext.QueryInvitationYesterday) m.Get("/query_invitation_all", operationReq, repo_ext.QueryInvitationAll) + m.Get("/download_invitation_detail", operationReq, repo_ext.DownloadInvitationDetail) //cloudbrain board m.Group("/cloudbrainboard", func() { diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 222fc1c75..1a63ec1bc 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -1,11 +1,15 @@ package repo import ( + "fmt" "net/http" + "net/url" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "github.com/360EntSecGroup-Skylar/excelize/v2" ) func QueryInvitationCurrentMonth(ctx *context.Context) { @@ -14,30 +18,113 @@ func QueryInvitationCurrentMonth(ctx *context.Context) { // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - // queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) + //queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) //_, count := models.QueryUserStaticDataByTableName(1, 1, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth), userName, 1) queryDataFromStaticTable(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) } +func getInvitationExcelHeader(ctx *context.Context) map[string]string { + excelHeader := make([]string, 0) + excelHeader = append(excelHeader, ctx.Tr("user.static.id")) + excelHeader = append(excelHeader, ctx.Tr("user.static.name")) + excelHeader = append(excelHeader, ctx.Tr("user.static.invitationNum")) + excelHeader = append(excelHeader, ctx.Tr("user.static.phone")) + excelHeader = append(excelHeader, ctx.Tr("user.static.registdate")) + + excelHeaderMap := make(map[string]string, 0) + var i byte + i = 0 + for _, value := range excelHeader { + excelColumn := getColumn(i) + fmt.Sprint(1) + excelHeaderMap[excelColumn] = value + i++ + } + return excelHeaderMap +} + +func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.UserBusinessAnalysisAll) { + rows := fmt.Sprint(row) + var tmp byte + tmp = 0 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + tmp = tmp + 1 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.InvitationUserNum) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Phone) + tmp = tmp + 1 + + formatTime := userRecord.RegistDate.Format("2006-01-02 15:04:05") + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, formatTime[0:len(formatTime)-3]) + +} + +func DownloadInvitationDetail(ctx *context.Context) { + +} + func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { page, pageSize := getPageInfo(ctx) userName := ctx.Query("userName") - resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) - result := make([]models.Invitation, 0) - for _, record := range resultRecord { - invi := models.Invitation{ - SrcUserID: record.ID, - Name: record.Name, - InvitationUserNum: record.InvitationUserNum, - Phone: record.Phone, + IsReturnFile := ctx.QueryBool("IsReturnFile") + + if IsReturnFile { + //writer exec file. + xlsx := excelize.NewFile() + sheetName := ctx.Tr("user.static.invitationsheetname") + index := xlsx.NewSheet(sheetName) + xlsx.DeleteSheet("Sheet1") + excelHeader := getInvitationExcelHeader(ctx) + for k, v := range excelHeader { + //设置单元格的值 + xlsx.SetCellValue(sheetName, k, v) + } + _, count := models.QueryUserInvitationDataByTableName(1, 1, tableName, queryObj, "", 1) + var indexTotal int64 + indexTotal = 0 + row := 1 + for { + re, _ := models.QueryUserInvitationDataByTableName(int(indexTotal), PAGE_SIZE, tableName, queryObj, "", 1) + log.Info("return count=" + fmt.Sprint(count)) + for _, userRecord := range re { + row++ + writeInvitationExcel(row, xlsx, sheetName, userRecord) + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + //设置默认打开的表单 + xlsx.SetActiveSheet(index) + filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".xlsx" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, err := xlsx.WriteTo(ctx.Resp); err != nil { + log.Info("writer exel error." + err.Error()) + } + } else { + resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) + result := make([]models.Invitation, 0) + for _, record := range resultRecord { + invi := models.Invitation{ + SrcUserID: record.ID, + Name: record.Name, + InvitationUserNum: record.InvitationUserNum, + Phone: record.Phone, + CreatedUnix: record.RegistDate, + } + result = append(result, invi) } - result = append(result, invi) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) } - mapInterface := make(map[string]interface{}) - mapInterface["data"] = result - mapInterface["count"] = count - ctx.JSON(http.StatusOK, mapInterface) } func QueryInvitationCurrentWeek(ctx *context.Context) { From 0bb98eb80d07f9576cf04cc6b2dec367c27d53e8 Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:40:40 +0800 Subject: [PATCH 34/91] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- options/locale/locale_en-US.ini | 4 +- options/locale/locale_zh-CN.ini | 4 +- routers/repo/user_invitation.go | 88 ++++++++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b8584c785..b48c0fa6c 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -536,8 +536,10 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. -user.static.invitationNum=User Invitation Count +static.invitationdetailsheetname=User Invitation Detail +static.invitationNum=User Invitation Count static.invitationsheetname=User Invitation +static.srcUserId=Recommended User ID static.sheetname=User Analysis static.id=ID static.name=User Name diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 51f150cee..434a73986 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -541,8 +541,10 @@ form.name_reserved='%s' 用户名被保留。 form.name_pattern_not_allowed=用户名中不允许使用 "%s"。 form.name_chars_not_allowed=用户名 '%s' 包含无效字符。 -user.static.invitationNum=邀请用户数 +static.invitationdetailsheetname=用户邀请详细数据 +static.invitationNum=邀请用户数 static.sheetname=用户分析 +static.srcUserId=推荐用户ID static.invitationsheetname=用户邀请分析 static.id=ID static.name=用户名 diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 1a63ec1bc..47eb1a3e7 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -43,6 +43,25 @@ func getInvitationExcelHeader(ctx *context.Context) map[string]string { return excelHeaderMap } +func getInvitationDetailExcelHeader(ctx *context.Context) map[string]string { + excelHeader := make([]string, 0) + excelHeader = append(excelHeader, ctx.Tr("user.static.id")) + excelHeader = append(excelHeader, ctx.Tr("user.static.name")) + excelHeader = append(excelHeader, ctx.Tr("user.static.srcUserId")) + excelHeader = append(excelHeader, ctx.Tr("user.static.phone")) + excelHeader = append(excelHeader, ctx.Tr("user.static.registdate")) + + excelHeaderMap := make(map[string]string, 0) + var i byte + i = 0 + for _, value := range excelHeader { + excelColumn := getColumn(i) + fmt.Sprint(1) + excelHeaderMap[excelColumn] = value + i++ + } + return excelHeaderMap +} + func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.UserBusinessAnalysisAll) { rows := fmt.Sprint(row) var tmp byte @@ -63,8 +82,61 @@ func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRe } +func writeInvitationDetailExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.Invitation) { + rows := fmt.Sprint(row) + var tmp byte + tmp = 0 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + tmp = tmp + 1 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.SrcUserID) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Phone) + tmp = tmp + 1 + + formatTime := userRecord.CreatedUnix.Format("2006-01-02 15:04:05") + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, formatTime[0:len(formatTime)-3]) + +} + func DownloadInvitationDetail(ctx *context.Context) { + xlsx := excelize.NewFile() + sheetName := ctx.Tr("user.static.invitationdetailsheetname") + index := xlsx.NewSheet(sheetName) + xlsx.DeleteSheet("Sheet1") + excelHeader := getInvitationDetailExcelHeader(ctx) + for k, v := range excelHeader { + //设置单元格的值 + xlsx.SetCellValue(sheetName, k, v) + } + _, count := models.QueryInvitaionPage(1, 1) + var indexTotal int64 + indexTotal = 0 + row := 1 + for { + re, _ := models.QueryInvitaionPage(int(indexTotal), PAGE_SIZE) + log.Info("return count=" + fmt.Sprint(count)) + for _, userRecord := range re { + row++ + writeInvitationDetailExcel(row, xlsx, sheetName, userRecord) + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + //设置默认打开的表单 + xlsx.SetActiveSheet(index) + filename := sheetName + ".xlsx" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, err := xlsx.WriteTo(ctx.Resp); err != nil { + log.Info("writer exel error." + err.Error()) + } } func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { @@ -198,14 +270,14 @@ func QueryInvitationAll(ctx *context.Context) { queryDataFromStaticTable(ctx, "public.user_business_analysis_all", new(models.UserBusinessAnalysisAll)) } -func queryData(ctx *context.Context, startTime int64, endTime int64) { - page, pageSize := getPageInfo(ctx) - result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) - mapInterface := make(map[string]interface{}) - mapInterface["data"] = result - mapInterface["count"] = count - ctx.JSON(http.StatusOK, mapInterface) -} +// func queryData(ctx *context.Context, startTime int64, endTime int64) { +// page, pageSize := getPageInfo(ctx) +// result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) +// mapInterface := make(map[string]interface{}) +// mapInterface["data"] = result +// mapInterface["count"] = count +// ctx.JSON(http.StatusOK, mapInterface) +// } func getPageInfo(ctx *context.Context) (int, int) { page := ctx.QueryInt("page") From 97d7cd6d5b5fc06430556b3791a18658d728d13f Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:55:20 +0800 Subject: [PATCH 35/91] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 13 +++++++++++++ routers/repo/user_invitation.go | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index 34d6b239f..a462ee74a 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -33,6 +33,19 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } +func GetAllUserName() map[int64]string { + sess := x.NewSession() + defer sess.Close() + sess.Select("`user`.id,`user`.name,").Table("user") + userList := make([]*User, 0) + reMap := make(map[int64]string) + sess.Find(&userList) + for _, user := range userList { + reMap[user.ID] = user.Name + } + return reMap +} + func QueryInvitaionPage(start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 47eb1a3e7..b0aac4eed 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -86,7 +86,7 @@ func writeInvitationDetailExcel(row int, xlsx *excelize.File, sheetName string, rows := fmt.Sprint(row) var tmp byte tmp = 0 - xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.UserID) tmp = tmp + 1 xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) tmp = tmp + 1 @@ -112,7 +112,7 @@ func DownloadInvitationDetail(ctx *context.Context) { //设置单元格的值 xlsx.SetCellValue(sheetName, k, v) } - + userNameMap := models.GetAllUserName() _, count := models.QueryInvitaionPage(1, 1) var indexTotal int64 indexTotal = 0 @@ -122,6 +122,7 @@ func DownloadInvitationDetail(ctx *context.Context) { log.Info("return count=" + fmt.Sprint(count)) for _, userRecord := range re { row++ + userRecord.Name = userNameMap[userRecord.UserID] writeInvitationDetailExcel(row, xlsx, sheetName, userRecord) } indexTotal += PAGE_SIZE From b3f59f381985bd7acc5c53fb3eeeddafae76db03 Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:59:30 +0800 Subject: [PATCH 36/91] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index a462ee74a..7b0ca1cf9 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -36,7 +36,7 @@ func QueryInvitaionByPhone(phone string) []*Invitation { func GetAllUserName() map[int64]string { sess := x.NewSession() defer sess.Close() - sess.Select("`user`.id,`user`.name,").Table("user") + sess.Select("id,name").Table("user") userList := make([]*User, 0) reMap := make(map[int64]string) sess.Find(&userList) From 0fb745427b958e62003546b44dabaed4d5b3f405 Mon Sep 17 00:00:00 2001 From: zouap Date: Fri, 16 Sep 2022 11:18:18 +0800 Subject: [PATCH 37/91] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/ai_model_manage.go | 16 +++++++++ routers/repo/ai_model_manage.go | 58 ++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/models/ai_model_manage.go b/models/ai_model_manage.go index 0ea01d6e5..97cae95a0 100644 --- a/models/ai_model_manage.go +++ b/models/ai_model_manage.go @@ -286,6 +286,22 @@ func ModifyModelDescription(id string, description string) error { return nil } +func ModifyModelStatus(id string, modelSize int64, status int, modelPath string) error { + var sess *xorm.Session + sess = x.ID(id) + defer sess.Close() + re, err := sess.Cols("size", "status", "path").Update(&AiModelManage{ + Size: modelSize, + Status: status, + Path: modelPath, + }) + if err != nil { + return err + } + log.Info("success to update ModelStatus from db.re=" + fmt.Sprint((re))) + return nil +} + func ModifyModelNewProperty(id string, new int, versioncount int) error { var sess *xorm.Session sess = x.ID(id) diff --git a/routers/repo/ai_model_manage.go b/routers/repo/ai_model_manage.go index d01539a75..1b295660a 100644 --- a/routers/repo/ai_model_manage.go +++ b/routers/repo/ai_model_manage.go @@ -27,6 +27,9 @@ const ( MODEL_LATEST = 1 MODEL_NOT_LATEST = 0 MODEL_MAX_SIZE = 1024 * 1024 * 1024 + STATUS_COPY_MODEL = 1 + STATUS_FINISHED = 0 + STATUS_ERROR = 2 ) func saveModelByParameters(jobId string, versionName string, name string, version string, label string, description string, engine int, ctx *context.Context) error { @@ -62,13 +65,9 @@ func saveModelByParameters(jobId string, versionName string, name string, versio modelSelectedFile := ctx.Query("modelSelectedFile") //download model zip //train type if aiTask.ComputeResource == models.NPUResource { - modelPath, modelSize, err = downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - if err != nil { - log.Info("download model from CloudBrainTwo faild." + err.Error()) - return err - } cloudType = models.TypeCloudBrainTwo } else if aiTask.ComputeResource == models.GPUResource { + cloudType = models.TypeCloudBrainOne var ResourceSpecs *models.ResourceSpecs json.Unmarshal([]byte(setting.ResourceSpecs), &ResourceSpecs) for _, tmp := range ResourceSpecs.ResourceSpec { @@ -77,24 +76,8 @@ func saveModelByParameters(jobId string, versionName string, name string, versio aiTask.FlavorName = flaverName } } - modelPath, modelSize, err = downloadModelFromCloudBrainOne(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - if err != nil { - log.Info("download model from CloudBrainOne faild." + err.Error()) - return err - } - cloudType = models.TypeCloudBrainOne } - // else if cloudType == models.TypeC2Net { - // if aiTask.ComputeResource == models.NPUResource { - // modelPath, modelSize, err = downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - // if err != nil { - // log.Info("download model from CloudBrainTwo faild." + err.Error()) - // return err - // } - // } else if aiTask.ComputeResource == models.GPUResource { - - // } - // } + accuracy := make(map[string]string) accuracy["F1"] = "" accuracy["Recall"] = "" @@ -123,6 +106,7 @@ func saveModelByParameters(jobId string, versionName string, name string, versio Engine: int64(engine), TrainTaskInfo: string(aiTaskJson), Accuracy: string(accuracyJson), + Status: STATUS_COPY_MODEL, } err = models.SaveModelToDb(model) @@ -146,11 +130,41 @@ func saveModelByParameters(jobId string, versionName string, name string, versio models.UpdateRepositoryUnits(ctx.Repo.Repository, units, deleteUnitTypes) + go asyncToCopyModel(aiTask, id, modelSelectedFile) + log.Info("save model end.") notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, id, name, models.ActionCreateNewModelTask) return nil } +func asyncToCopyModel(aiTask *models.Cloudbrain, id string, modelSelectedFile string) { + if aiTask.ComputeResource == models.NPUResource { + modelPath, modelSize, err := downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) + if err != nil { + updateStatus(id, 0, STATUS_ERROR, modelPath) + log.Info("download model from CloudBrainTwo faild." + err.Error()) + } else { + updateStatus(id, modelSize, STATUS_FINISHED, modelPath) + } + } else if aiTask.ComputeResource == models.GPUResource { + + modelPath, modelSize, err := downloadModelFromCloudBrainOne(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) + if err != nil { + updateStatus(id, 0, STATUS_ERROR, modelPath) + log.Info("download model from CloudBrainOne faild." + err.Error()) + } else { + updateStatus(id, modelSize, STATUS_FINISHED, modelPath) + } + } +} + +func updateStatus(id string, modelSize int64, status int, modelPath string) { + err := models.ModifyModelStatus(id, modelSize, STATUS_FINISHED, modelPath) + if err != nil { + log.Info("update status error." + err.Error()) + } +} + func SaveNewNameModel(ctx *context.Context) { if !ctx.Repo.CanWrite(models.UnitTypeModelManage) { ctx.Error(403, ctx.Tr("repo.model_noright")) From 928a7b8d215cc76664e12cd2c291bf654632aa2c Mon Sep 17 00:00:00 2001 From: zhoupzh Date: Fri, 16 Sep 2022 16:07:39 +0800 Subject: [PATCH 38/91] fix issue --- templates/repo/modelmanage/index.tmpl | 4 +- web_src/js/components/Model.vue | 57 +++++++++++++++++++++------ web_src/js/features/i18nVue.js | 8 ++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/templates/repo/modelmanage/index.tmpl b/templates/repo/modelmanage/index.tmpl index 3a5240768..b2994f0c2 100644 --- a/templates/repo/modelmanage/index.tmpl +++ b/templates/repo/modelmanage/index.tmpl @@ -46,9 +46,9 @@
    {{template "repo/header" .}} -
    +
    {{template "base/alert" .}} -
    +