diff --git a/build/notify/root/opt/yunion/share/notify/email_verify_template b/build/notify/root/opt/yunion/share/notify/email_verify_template
new file mode 100644
index 0000000000..0f4909da9e
--- /dev/null
+++ b/build/notify/root/opt/yunion/share/notify/email_verify_template
@@ -0,0 +1,66 @@
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+ 亲爱的{{.name}},您好:
+ |
+
+
+ | 请点击下面的链接,完成用户邮箱的激活: |
+
+
+ | {{.link}} |
+
+
+ |
+
+
+ | *为了确保您的帐号安全,该链接仅48小时内访问有效,请勿直接回复此邮件。 |
+
+
+ | 版权所有 © 北京云联万维科技有限公司 2018 保留一切权利 京ICP备17065753号 |
+
+
+
+
diff --git a/cmd/climc/shell/contacts.go b/cmd/climc/shell/contacts.go
index 8f37c44645..57c8817b78 100644
--- a/cmd/climc/shell/contacts.go
+++ b/cmd/climc/shell/contacts.go
@@ -167,17 +167,10 @@ func init() {
}
R(&ContactsVerifyOptions{}, "contact-verify-trigger", "Trigger contact verify", func(s *mcclient.ClientSession, args *ContactsVerifyOptions) error {
params := jsonutils.NewDict()
- params.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type")
- params.Add(jsonutils.NewString(args.CONTACT), "contact")
- /*
- if len(args.Email) > 0 {
- params.Add(jsonutils.NewString(args.Email), "email")
- }
- if len(args.Mobile) > 0 {
- params.Add(jsonutils.NewString(args.Mobile), "mobile")
- }
- */
-
+ tmpDict := jsonutils.NewDict()
+ tmpDict.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type")
+ tmpDict.Add(jsonutils.NewString(args.CONTACT), "contact")
+ params.Add(tmpDict, "contact")
_, err := modules.Contacts.PerformAction(s, args.UID, "verify", params)
if err != nil {
return err
diff --git a/cmd/climc/shell/notification.go b/cmd/climc/shell/notification.go
index a3d0320809..adffdb4d94 100644
--- a/cmd/climc/shell/notification.go
+++ b/cmd/climc/shell/notification.go
@@ -37,7 +37,9 @@ func init() {
Remark string `help:"Remark or description of the notification"`
Group bool `help:"Send to group"`
}
- R(&NotificationCreateOptions{}, "notify", "Send a notification to sb", func(s *mcclient.ClientSession, args *NotificationCreateOptions) error {
+ R(&NotificationCreateOptions{}, "notify", "Send a notification to someones", func(s *mcclient.ClientSession,
+ args *NotificationCreateOptions) error {
+
msg := notify.SNotifyMessage{}
if args.Group {
msg.Gid = args.Uid
diff --git a/cmd/climc/shell/notify_template.go b/cmd/climc/shell/notify_template.go
new file mode 100644
index 0000000000..b635e8eac6
--- /dev/null
+++ b/cmd/climc/shell/notify_template.go
@@ -0,0 +1,90 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package shell
+
+import (
+ "yunion.io/x/jsonutils"
+
+ "yunion.io/x/onecloud/pkg/mcclient"
+ "yunion.io/x/onecloud/pkg/mcclient/modules"
+ "yunion.io/x/onecloud/pkg/mcclient/options"
+)
+
+func init() {
+ type NotifyTemplateUpdateOptions struct {
+ CONTACTTYPE string `help:"the contanct type, such as 'email', 'mobile'"`
+ Topic string `help:"the topic of temlate, such as 'VERIFY', 'ALARM'"`
+ TemplateType string `help:"the type of template" choices:"content|title|remote"`
+ Content string `help:"the content of template"`
+ }
+ R(&NotifyTemplateUpdateOptions{}, "notify-template-update", "Create, update contact for user", func(s *mcclient.ClientSession,
+ args *NotifyTemplateUpdateOptions) error {
+ arr := jsonutils.NewArray()
+ tmpObj := jsonutils.NewDict()
+ tmpObj.Add(jsonutils.NewString(args.Topic), "topic")
+ tmpObj.Add(jsonutils.NewString(args.TemplateType), "template_type")
+ tmpObj.Add(jsonutils.NewString(args.Content), "content")
+ arr.Add(tmpObj)
+
+ params := jsonutils.NewDict()
+ params.Add(arr, "notifytemplates")
+
+ contact, err := modules.NotifyTemplates.PerformAction(s, args.CONTACTTYPE, "update-template", params)
+
+ if err != nil {
+ return err
+ }
+
+ printObject(contact)
+ return nil
+ })
+
+ type NotifyTemplateDeleteOptions struct {
+ CONTACTTYPE string `help:"the contanct type, such as 'email', 'mobile'"`
+ Topic string `help:"the topic of temlate, such as 'VERIFY', 'ALARM'"`
+ }
+
+ R(&NotifyTemplateDeleteOptions{}, "notify-template-delete", "delete notify template",
+ func(s *mcclient.ClientSession, args *NotifyTemplateDeleteOptions) error {
+
+ tmpObj := jsonutils.NewDict()
+ tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type")
+ tmpObj.Add(jsonutils.NewString(args.Topic), "topic")
+ _, err := modules.NotifyTemplates.DeleteContents(s, tmpObj)
+ if err != nil {
+ return err
+ }
+ return nil
+ })
+
+ type NotifyTemplateListOptions struct {
+ options.BaseListOptions
+ }
+
+ R(&NotifyTemplateListOptions{}, "notify-template-list", "List all notify template",
+ func(s *mcclient.ClientSession, args *NotifyTemplateListOptions) error {
+
+ params, err := args.BaseListOptions.Params()
+ if err != nil {
+ return err
+ }
+ result, err := modules.NotifyTemplates.List(s, params)
+ if err != nil {
+ return err
+ }
+ printList(result, modules.NotifyTemplates.GetColumns(s))
+ return nil
+ })
+}
diff --git a/pkg/cloudcommon/db/usercache.go b/pkg/cloudcommon/db/usercache.go
index 6a89f46b91..2a76b4587a 100644
--- a/pkg/cloudcommon/db/usercache.go
+++ b/pkg/cloudcommon/db/usercache.go
@@ -103,10 +103,10 @@ func (manager *SUserCacheManager) fetchUser(
return user, nil
}
}
- return manager.fetchUserFromKeystone(ctx, idStr)
+ return manager.FetchUserFromKeystone(ctx, idStr)
}
-func (manager *SUserCacheManager) fetchUserFromKeystone(ctx context.Context, idStr string) (*SUser, error) {
+func (manager *SUserCacheManager) FetchUserFromKeystone(ctx context.Context, idStr string) (*SUser, error) {
if len(idStr) == 0 {
log.Debugf("fetch empty user!!!!\n%s", debug.Stack())
return nil, fmt.Errorf("Empty idStr")
diff --git a/pkg/mcclient/modules/mod_contacts.go b/pkg/mcclient/modules/mod_contacts.go
index 6f8480d440..609f8dd94a 100644
--- a/pkg/mcclient/modules/mod_contacts.go
+++ b/pkg/mcclient/modules/mod_contacts.go
@@ -16,6 +16,7 @@ package modules
import (
"fmt"
+ "net/url"
"yunion.io/x/jsonutils"
@@ -44,6 +45,21 @@ func (this *ContactsManager) DoBatchDeleteContacts(s *mcclient.ClientSession, pa
return modulebase.Post(this.ResourceManager, s, path, params, this.Keyword)
}
+func (this *ContactsManager) PerformAction(session *mcclient.ClientSession, id string, action string,
+ params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
+
+ path := fmt.Sprintf("/%s/%s/%s?uname=true", this.ContextPath(nil), url.PathEscape(id), url.PathEscape(action))
+ return modulebase.Post(this.ResourceManager, session, path, params, this.KeywordPlural)
+}
+
+func (this *ContactsManager) Get(session *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject,
+ error) {
+
+ q := params.(*jsonutils.JSONDict)
+ q.Add(jsonutils.JSONTrue, "uname")
+ return this.ResourceManager.Get(session, id, params)
+}
+
var (
Contacts ContactsManager
)
diff --git a/pkg/mcclient/modules/mod_notify_template.go b/pkg/mcclient/modules/mod_notify_template.go
new file mode 100644
index 0000000000..7017473305
--- /dev/null
+++ b/pkg/mcclient/modules/mod_notify_template.go
@@ -0,0 +1,43 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package modules
+
+import (
+ "yunion.io/x/jsonutils"
+
+ "yunion.io/x/onecloud/pkg/mcclient"
+ "yunion.io/x/onecloud/pkg/mcclient/modulebase"
+)
+
+type NotifyTemplateManager struct {
+ modulebase.ResourceManager
+}
+
+func (nm *NotifyTemplateManager) DeleteContents(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
+ path := "/templates/delete-template"
+ return modulebase.Post(nm.ResourceManager, s, path, params, "")
+}
+
+var (
+ NotifyTemplates NotifyTemplateManager
+)
+
+func init() {
+ NotifyTemplates = NotifyTemplateManager{NewNotifyManager("notifytemplate", "notifytemplates",
+ []string{"Contact_Type", "Topic", "Template_Type"},
+ []string{})}
+
+ register(&NotifyTemplates)
+}
diff --git a/pkg/mcclient/modules/notify/mod_notification.go b/pkg/mcclient/modules/notify/mod_notification.go
index 330ba34b3c..21ffb8f5f5 100644
--- a/pkg/mcclient/modules/notify/mod_notification.go
+++ b/pkg/mcclient/modules/notify/mod_notification.go
@@ -42,7 +42,8 @@ type NotificationManager struct {
}
func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyMessage) error {
- _, err := manager.Create(s, jsonutils.Marshal(&msg))
+ path := "/" + manager.KeywordPlural
+ _, err := modulebase.Post(manager.ResourceManager, s, path, jsonutils.Marshal(&msg), manager.KeywordPlural)
return err
}
diff --git a/pkg/notify/cache/usercache.go b/pkg/notify/cache/usercache.go
index 90303ca9aa..2ee25e96e4 100644
--- a/pkg/notify/cache/usercache.go
+++ b/pkg/notify/cache/usercache.go
@@ -18,26 +18,21 @@ import (
"context"
"database/sql"
"fmt"
- "time"
- "yunion.io/x/log"
"yunion.io/x/pkg/errors"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
- "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
- "yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/httputils"
)
type SUserCacheManager struct {
- db.SKeystoneCacheObjectManager
+ db.SUserCacheManager
}
type SUser struct {
- db.SKeystoneCacheObject
+ db.SUser
}
func (user *SUser) GetModelManager() db.IModelManager {
@@ -46,13 +41,6 @@ func (user *SUser) GetModelManager() db.IModelManager {
var UserCacheManager *SUserCacheManager
-func init() {
- UserCacheManager = &SUserCacheManager{
- db.NewKeystoneCacheObjectManager(SUser{}, "users_cache_tbl", "user", "users")}
- // log.Debugf("initialize user cache manager %s", UserCacheManager.KeywordPlural())
- UserCacheManager.SetVirtualObject(UserCacheManager)
-}
-
func RegistUserCredCacheUpdater() {
auth.RegisterAuthHook(onAuthCompleteUpdateCache)
}
@@ -63,97 +51,18 @@ func onAuthCompleteUpdateCache(userCred mcclient.TokenCredential) {
func (ucm *SUserCacheManager) updateUserCache(userCred mcclient.TokenCredential) {
ucm.Save(context.Background(), userCred.GetUserId(), userCred.GetUserName(),
- userCred.GetDomainId())
+ userCred.GetDomainId(), userCred.GetDomainName())
}
-func (ucm *SUserCacheManager) FetchUserByIdOrName(idStr string) (*SUser, error) {
- obj, err := ucm.SKeystoneCacheObjectManager.FetchByIdOrName(nil, idStr)
- if err != nil {
- return nil, err
- }
- return obj.(*SUser), nil
-}
-
-func (ucm *SUserCacheManager) FetchUserById(idStr string) (*SUser, error) {
- obj, err := ucm.SKeystoneCacheObjectManager.FetchById(idStr)
- if err != nil {
- return nil, err
- }
- return obj.(*SUser), nil
-}
-
-func (ucm *SUserCacheManager) FetchUserByName(idStr string) (*SUser, error) {
- obj, err := ucm.SKeystoneCacheObjectManager.FetchByName(nil, idStr)
- if err != nil {
- return nil, err
- }
- return obj.(*SUser), nil
-}
-
-func (ucm *SUserCacheManager) Save(ctx context.Context, idStr string, name string, domainId string) (*SUser, error) {
- lockman.LockRawObject(ctx, ucm.KeywordPlural(), idStr)
- defer lockman.ReleaseRawObject(ctx, ucm.KeywordPlural(), idStr)
-
- objo, err := ucm.FetchById(idStr)
- if err != nil && err != sql.ErrNoRows {
- log.Errorf("FetchTenantbyId fail %s", err)
- return nil, err
- }
- now := time.Now().UTC()
- if err == nil {
- obj := objo.(*SUser)
- if obj.Id == idStr && obj.Name == name && obj.DomainId == domainId {
- db.Update(obj, func() error {
- obj.LastCheck = now
- return nil
- })
- return obj, nil
- }
- _, err = db.Update(obj, func() error {
- obj.Id = idStr
- obj.Name = name
- obj.DomainId = domainId
- obj.LastCheck = now
- return nil
- })
- if err != nil {
- return nil, err
- } else {
- return obj, nil
- }
- } else {
- objm, err := db.NewModelObject(ucm)
- obj := objm.(*SUser)
- obj.Id = idStr
- obj.Name = name
- obj.DomainId = domainId
- obj.LastCheck = now
- err = ucm.TableSpec().Insert(obj)
- if err != nil {
- return nil, err
- } else {
- return obj, nil
- }
- }
-}
-
-func (ucm *SUserCacheManager) fetchUserFromKeystone(ctx context.Context, idStr string) (*SUser, error) {
- if len(idStr) == 0 {
- return nil, fmt.Errorf("Empty idStr")
- }
- s := auth.GetAdminSession(ctx, consts.GetRegion(), "v3")
- user, err := modules.UsersV3.GetById(s, idStr, nil)
+func (ucm *SUserCacheManager) dealErrFromKeystone(err error) error {
if err != nil {
if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
- return nil, sql.ErrNoRows
+ return sql.ErrNoRows
+ } else {
+ return errors.Wrap(err, "fetch User info from keystone")
}
- log.Errorf("fetch project %s fail %s", idStr, err)
- return nil, errors.Wrap(err, "modules.Projects.Get")
}
- userId, _ := user.GetString("id")
- userName, _ := user.GetString("name")
- domainId, _ := user.GetString("domain_id")
- return ucm.Save(ctx, userId, userName, domainId)
+ return nil
}
func (ucm *SUserCacheManager) FetchUsersByIDs(ctx context.Context, ids []string) (map[string]SUser, error) {
@@ -174,32 +83,21 @@ func (ucm *SUserCacheManager) FetchUsersByIDs(ctx context.Context, ids []string)
if _, ok := ret[id]; ok {
continue
}
- user, err := ucm.fetchUserFromKeystone(ctx, id)
+ user, err := ucm.FetchUserFromKeystone(ctx, id)
if err != nil {
continue
}
- ret[id] = *user
+ ret[id] = SUser{*user}
}
return ret, nil
}
-func (ucm *SUserCacheManager) FetchUserByID(ctx context.Context, idStr string, noExpireCheck bool) (*SUser, error) {
-
- q := ucm.Query().Equals("id", idStr)
- uobj, err := db.NewModelObject(ucm)
+func (ucm *SUserCacheManager) FetchUserByIDOrName(ctx context.Context, idStr string) (*SUser, error) {
+ user, err := ucm.SUserCacheManager.FetchUserByIdOrName(ctx, idStr)
if err != nil {
- return nil, errors.Wrap(err, "NewModelObject")
+ return nil, err
}
- err = q.First(uobj)
- if err != nil && err != sql.ErrNoRows {
- return nil, errors.Wrap(err, "query")
- } else if uobj != nil {
- user := uobj.(*SUser)
- if noExpireCheck || !user.IsExpired() {
- return user, nil
- }
- }
- return ucm.fetchUserFromKeystone(ctx, idStr)
+ return &SUser{*user}, nil
}
func (ucm *SUserCacheManager) FetchUserLikeName(ctx context.Context, name string, noExpireCheck bool) ([]SUser,
diff --git a/pkg/notify/dispatcher.go b/pkg/notify/dispatcher.go
index e3e6139528..c45d9a8a8b 100644
--- a/pkg/notify/dispatcher.go
+++ b/pkg/notify/dispatcher.go
@@ -24,6 +24,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
+ "yunion.io/x/pkg/util/sets"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
@@ -228,8 +229,8 @@ func (self *NotifyModelDispatcher) VerifyTrigger(ctx context.Context, params map
contact, _ := data.GetString("contact")
contactType, _ := data.GetString("contact_type")
contacts, err := models.ContactManager.FetchByMore(uid, contact, contactType)
- if err != nil {
- return nil, errors.Error(fmt.Sprintf(`uid %q don't have contact %q of contact_type %q'`, uid, contact, contactType))
+ if err != nil || len(contacts) == 0 {
+ return nil, errors.Error(fmt.Sprintf("uid '%s' don't have contact '%s' of contact_type '%s'", uid, contact, contactType))
}
userCred := policy.FetchUserCredential(ctx)
scontact := contacts[0]
@@ -262,12 +263,16 @@ func (self *NotifyModelDispatcher) VerifyTrigger(ctx context.Context, params map
}
if scontact.Status == models.CONTACT_VERIFYING {
verifications, err := models.VerifyManager.FetchByCID(scontact.ID, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
- q = q.Equals("status", models.VERIFICATION_SENT).Desc("created_at")
+ q = q.In("status", []string{models.VERIFICATION_SENT, "init"}).Desc("created_at")
return q
})
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
+ if len(verifications) == 0 {
+ // no verifications in status "sent"
+ return makeNewVerify()
+ }
current := time.Now()
for _, verification := range verifications {
if current.After(verification.ExpireAt) {
@@ -292,8 +297,11 @@ func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uids2 []j
for i := range uids2 {
uids[i] = strings.Trim(uids2[i].String(), `"`)
}
-
- contacts, err := models.ContactManager.FetchByUIDs(uids)
+ uname := false
+ if v := ctx.Value("uname"); v != nil {
+ uname = true
+ }
+ contacts, err := models.ContactManager.FetchByUIDs(uids, uname)
if err != nil {
return httperrors.NewGeneralError(err)
}
@@ -425,6 +433,91 @@ func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr str
return nil
}
+func (self *NotifyModelDispatcher) UpdateTemplate(ctx context.Context, ctype string, query jsonutils.JSONObject,
+ datas []jsonutils.JSONObject) error {
+
+ type sTemplate struct {
+ ContactType string
+ Topic string
+ TemplateType string
+ Content string
+ }
+ templates := make([]sTemplate, 0, len(datas))
+ topics := sets.NewString()
+ for _, data := range datas {
+ var tem sTemplate
+ err := data.Unmarshal(&tem)
+ if err != nil {
+ return errors.Wrap(err, "data.Unmarshal")
+ }
+ if tem.TemplateType != models.TEMPLATE_TYPE_REMOTE && tem.TemplateType != models.
+ TEMPLATE_TYPE_CONTENT && tem.TemplateType != models.TEMPLATE_TYPE_TITLE {
+
+ return httperrors.NewInputParameterError("no support for such template type '%s'", tem.TemplateType)
+ }
+ tem.ContactType = ctype
+ tem.Topic = strings.ToUpper(tem.Topic)
+ templates = append(templates, tem)
+ topics.Insert(tem.Topic)
+ }
+
+ q := models.TemplateManager.Query().Equals("contact_type", ctype).In("topic", topics.List())
+ templateModels := make([]models.STemplate, 0, 1)
+ err := db.FetchModelObjects(models.ContactManager, q, &templateModels)
+ if err != nil {
+ log.Errorf("db.FetchModelObjects sql: %s", q.String())
+ return errors.Wrap(err, "db.FetchModelObjects")
+ }
+
+ templateMaps := make(map[string]*models.STemplate)
+ for i := range templateModels {
+ k := fmt.Sprintf("%s/%s/%s", templateModels[i].ContactType, templateModels[i].Topic, templateModels[i].TemplateType)
+ templateMaps[k] = &templateModels[i]
+ }
+
+ userCred := policy.FetchUserCredential(ctx)
+ for _, tem := range templates {
+ k := fmt.Sprintf("%s/%s/%s", tem.ContactType, tem.Topic, tem.TemplateType)
+ if tmod, ok := templateMaps[k]; ok {
+ updateData := jsonutils.NewDict()
+ updateData.Add(jsonutils.NewString(tem.Content), "content")
+ err = UpdateItem(models.TemplateManager, tmod, ctx, userCred, query, updateData)
+ if err != nil {
+ return errors.Wrapf(err, "fail to update template '%s'", tmod.ID)
+ }
+ continue
+ }
+ _, err = self.Create(ctx, query, jsonutils.Marshal(tem), nil)
+ if err != nil {
+ return errors.Wrapf(err, "fail to create template(contact_type: %s, topic: %s, template_type: %s)",
+ tem.ContactType, tem.Topic, tem.TemplateType)
+ }
+ }
+ return nil
+}
+
+func (self *NotifyModelDispatcher) DeleteTemplate(ctx context.Context, query jsonutils.JSONObject, ctype, topic string) error {
+
+ q := models.TemplateManager.Query().Equals("contact_type", ctype)
+ if len(topic) != 0 {
+ q = q.Equals("topic", topic)
+ }
+ templates := make([]models.STemplate, 0, 1)
+ err := db.FetchModelObjects(models.TemplateManager, q, &templates)
+ if err != nil {
+ log.Errorf("db.FetchModelObjects sql: %s", q.String())
+ return errors.Wrap(err, "db.FetchModelObjects")
+ }
+ userCred := policy.FetchUserCredential(ctx)
+ for i := range templates {
+ err = DeleteItem(&templates[i], ctx, userCred, query, jsonutils.JSONNull)
+ if err != nil {
+ return errors.Wrapf(err, "fail to delete template '%s'", templates[i].ID)
+ }
+ }
+ return nil
+}
+
// fetchEnv fetch handler, params, query and body from ctx(context.Context)
func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*NotifyModelDispatcher, map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) {
params, query, body := appsrv.FetchEnv(ctx, w, r)
diff --git a/pkg/notify/handlers.go b/pkg/notify/handlers.go
index a7b58c0dc0..b0d0990029 100644
--- a/pkg/notify/handlers.go
+++ b/pkg/notify/handlers.go
@@ -18,12 +18,15 @@ import (
"context"
"fmt"
"net/http"
+ "net/url"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
+ "yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/appsrv"
+ "yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/httperrors"
@@ -44,9 +47,35 @@ func InitHandlers(app *appsrv.Application) {
db.RegisterModelManager(models.ConfigManager)
db.RegisterModelManager(cache.UserCacheManager)
db.RegisterModelManager(cache.UserGroupCacheManager)
+ db.RegisterModelManager(models.TemplateManager)
AddNotifyDispatcher("/api/v1/", app)
}
+// Middleware
+func middleware(f appsrv.FilterHandler) appsrv.FilterHandler {
+ hander := func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ if _, ok := r.URL.Query()["uname"]; ok {
+ // Uname
+ params := appctx.AppContextParams(ctx)
+ if uid, ok := params[""]; ok {
+ userDetail, err := utils.GetUserByIDOrName(ctx, uid)
+ if err != nil {
+ httperrors.NotFoundError(w, "Uid or Uname Not Found")
+ return
+ }
+ params[""] = userDetail.Id
+ }
+ ctx = context.WithValue(ctx, "uname", true)
+ }
+ f(ctx, w, r)
+ }
+ if consts.IsRbacEnabled() {
+ return auth.AuthenticateWithDelayDecision(hander, true)
+ } else {
+ return auth.Authenticate(hander)
+ }
+}
+
func AddNotifyDispatcher(prefix string, app *appsrv.Application) {
var metadata map[string]interface{}
var tags map[string]string
@@ -56,89 +85,167 @@ func AddNotifyDispatcher(prefix string, app *appsrv.Application) {
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
app.AddHandler2("POST",
fmt.Sprintf("%s/%s//update-contact", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(contactUpdateHandler), metadata, "contact_update", tags)
+ middleware(contactUpdateHandler), metadata, "contact_update", tags)
// List
app.AddHandler2("GET",
fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(listHandler), metadata, "list_contacts", tags)
+ middleware(listHandler), metadata, "list_contacts", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/users", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(keyStoneUserListHandler), metadata, "list_users", tags)
+ middleware(keyStoneUserListHandler), metadata, "list_users", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(getHandler), metadata, "list_by_uid", tags)
+ middleware(getHandler), metadata, "list_by_uid", tags)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/delete-contact", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(deleteContactHandler), metadata, "delete", tags)
+ middleware(deleteContactHandler), metadata, "delete", tags)
// verify-trigger
app.AddHandler2("POST",
fmt.Sprintf("%s/%s//verify", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(verifyTriggerHandler), metadata, "verify_trigger", tags)
+ middleware(verifyTriggerHandler), metadata, "verify_trigger", tags)
// Verify Handler, this modelDispatcher need db.DBModelDispatcher'Create function to create Contact so this modelDispatcher is
// NotifyModelDispatcher whose DBModelDispatcher has modelManager models.ContactManager
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": models.VerifyManager.KeywordPlural()}
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, models.VerifyManager.KeywordPlural()),
- modelDispatcher.Filter(verifyHandler), metadata, "verify", tags)
+ middleware(verifyHandler), metadata, "verify", tags)
// notification Handler
modelDispatcher = NewNotifyModelDispatcher(models.NotificationManager)
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(notificationHandler), metadata, "send_notifications", tags)
+ middleware(notificationHandler), metadata, "send_notifications", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(listHandler), metadata, "send_notifications", tags)
+ middleware(listHandler), metadata, "send_notifications", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(listHandler), metadata, "list_notification_by_id", tags)
+ middleware(listHandler), metadata, "list_notification_by_id", tags)
// config Handler
modelDispatcher = NewNotifyModelDispatcher(models.ConfigManager)
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(configUpdateHandler), metadata, "update_configs", tags)
+ middleware(configUpdateHandler), metadata, "update_configs", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(configGetHandler), metadata, "get_configs", tags)
+ middleware(configGetHandler), metadata, "get_configs", tags)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
- modelDispatcher.Filter(configDeleteHandler), metadata, "delete_configs", tags)
+ middleware(configDeleteHandler), metadata, "delete_configs", tags)
// email handler for being compatible
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
- modelDispatcher.Filter(emailConfigUpdateHandler), metadata, "", tags)
+ middleware(emailConfigUpdateHandler), metadata, "", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
- modelDispatcher.Filter(emailConfigGetHandler), metadata, "", tags)
+ middleware(emailConfigGetHandler), metadata, "", tags)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
- modelDispatcher.Filter(emailConfigDeleteHandler), metadata, "", tags)
+ middleware(emailConfigDeleteHandler), metadata, "", tags)
app.AddHandler2("PUT",
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
- modelDispatcher.Filter(emailConfigUpdateHandler), metadata, "", tags)
+ middleware(emailConfigUpdateHandler), metadata, "", tags)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
- modelDispatcher.Filter(smsConfigUpdateHandler), metadata, "", tags)
+ middleware(smsConfigUpdateHandler), metadata, "", tags)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
- modelDispatcher.Filter(smsConfigGetHandler), metadata, "", tags)
+ middleware(smsConfigGetHandler), metadata, "", tags)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
- modelDispatcher.Filter(smsConfigDeleteHandler), metadata, "", tags)
+ middleware(smsConfigDeleteHandler), metadata, "", tags)
app.AddHandler2("PUT",
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
- modelDispatcher.Filter(smsConfigUpdateHandler), metadata, "", tags)
+ middleware(smsConfigUpdateHandler), metadata, "", tags)
+ // Contact Handler
+ modelDispatcher = NewNotifyModelDispatcher(models.TemplateManager)
+ metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
+ app.AddHandler2("POST",
+ fmt.Sprintf("%s/%s//update-template", prefix, modelDispatcher.KeywordPlural()),
+ middleware(templateUpdateHandler), metadata, "update_template", tags)
+ // List
+ app.AddHandler2("GET",
+ fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()),
+ middleware(listHandler), metadata, "list_template", tags)
+
+ app.AddHandler2("POST",
+ fmt.Sprintf("%s/%s/delete-template", prefix, modelDispatcher.KeywordPlural()),
+ middleware(deleteTemplateHandler), metadata, "delete", tags)
+
+ app.AddHandler2("POST",
+ fmt.Sprintf("%s/%s/email-url", prefix, modelDispatcher.KeywordPlural()),
+ middleware(updateEmailUrlHandler), metadata, "update_email_url", tags)
+
+ app.AddHandler2("GET",
+ fmt.Sprintf("%s/%s/email-url", prefix, modelDispatcher.KeywordPlural()),
+ middleware(getEmailUrlHandler), metadata, "get_email_url", tags)
+}
+
+func updateEmailUrlHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ userCred := policy.FetchUserCredential(ctx)
+ if !userCred.HasSystemAdminPrivilege() {
+ httperrors.ForbiddenError(w, "only system admin can update email url")
+ return
+ }
+ _, _, _, body := fetchEnv(ctx, w, r)
+ if !body.Contains("email_url") {
+ httperrors.InputParameterError(w, "miss email_url")
+ }
+ emailUrl, _ := body.GetString("email_url")
+ eUrl, err := url.Parse(emailUrl)
+ if err != nil {
+ httperrors.InputParameterError(w, "invalid url")
+ }
+ models.TemplateManager.SetEmailUrl(eUrl.String())
+}
+
+func getEmailUrlHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ ret := jsonutils.NewDict()
+ ret.Add(jsonutils.NewString(models.TemplateManager.GetEmailUrl()), "email_url")
+ appsrv.Send(w, ret.PrettyString())
+}
+
+func templateUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ manager, params, query, body := fetchEnv(ctx, w, r)
+ data, err := body.GetArray(manager.Keyword(), manager.KeywordPlural())
+ if err != nil {
+ httperrors.GeneralServerError(w, httperrors.NewInputParameterError("need %s or %s", manager.Keyword(),
+ manager.KeywordPlural()))
+ return
+ }
+ ctype := params[""]
+ if len(ctype) == 0 {
+ httperrors.InputParameterError(w, "ctype of template should not be empty")
+ }
+ err = manager.UpdateTemplate(ctx, ctype, mergeQueryParams(params, query), data)
+ if err != nil {
+ httperrors.GeneralServerError(w, err)
+ }
+}
+
+func deleteTemplateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ manager, _, query, body := fetchEnv(ctx, w, r)
+ if !body.Contains("contact_type") {
+ httperrors.InputParameterError(w, "miss contact_type")
+ return
+ }
+ ctype, _ := body.GetString("contact_type")
+ topic, _ := body.GetString("topic")
+ err := manager.DeleteTemplate(ctx, query, ctype, topic)
+ if err != nil {
+ httperrors.GeneralServerError(w, err)
+ }
}
func configDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@@ -211,14 +318,8 @@ func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Re
manager.KeywordPlural()))
return
}
- // check that if the uid is exist
+
uid := params[""]
- _, err = utils.GetUserByID(ctx, uid)
- if err != nil {
- log.Errorf(`uid %q not found`, uid)
- httperrors.NotFoundError(w, "Uid Not Found")
- return
- }
queryDict := mergeQueryParams(params, query)
update, _ := body.Bool(manager.Keyword(), "update_dingtalk")
if update {
@@ -258,7 +359,7 @@ func deleteContactHandler(ctx context.Context, w http.ResponseWriter, r *http.Re
// verify trigger handler
func verifyTriggerHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
manager, params, _, body := fetchEnv(ctx, w, r)
- data, err := body.Get(models.ContactManager.Keyword())
+ data, err := body.Get(models.ContactManager.Keyword(), models.ContactManager.KeywordPlural())
if err != nil {
httperrors.BadRequestError(w, "request body should have %s", manager.KeywordPlural())
return
diff --git a/pkg/notify/interface/interface.go b/pkg/notify/interface/interface.go
index 97027b669a..44eaaccac5 100644
--- a/pkg/notify/interface/interface.go
+++ b/pkg/notify/interface/interface.go
@@ -18,6 +18,7 @@ import (
"context"
"yunion.io/x/onecloud/pkg/mcclient"
+ "yunion.io/x/onecloud/pkg/notify/rpc/apis"
)
type INotifyService interface {
@@ -34,4 +35,8 @@ type IServiceConfigStore interface {
SetConfig(serviceName string, config SConfig) error
}
+type ITemplateStore interface {
+ NotifyFilter(contactType, topic, msg string) (params apis.SendParams, err error)
+}
+
type SConfig map[string]string
diff --git a/pkg/notify/models/initdb.go b/pkg/notify/models/initdb.go
index e662fca861..f82e4a4760 100644
--- a/pkg/notify/models/initdb.go
+++ b/pkg/notify/models/initdb.go
@@ -31,6 +31,7 @@ func InitDB() error {
VerifyManager,
NotificationManager,
ConfigManager,
+ TemplateManager,
} {
err := manager.InitializeData()
if err != nil {
diff --git a/pkg/notify/models/mod_contact.go b/pkg/notify/models/mod_contact.go
index 2667a4b053..c102390219 100644
--- a/pkg/notify/models/mod_contact.go
+++ b/pkg/notify/models/mod_contact.go
@@ -22,6 +22,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
+ "yunion.io/x/pkg/util/sets"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
@@ -99,20 +100,57 @@ func (self *SContactManager) InitializeData() error {
return nil
}
-func (self *SContactManager) FetchByUIDs(uids []string) ([]SContact, error) {
+func (self *SContactManager) FetchByUIDs(uids []string, uname bool) ([]SContact, error) {
+ var err error
+ if uname {
+ uids, err = self._UIDsFromUIDOrName(uids)
+ if err != nil {
+ return nil, err
+ }
+ }
q := self.Query()
q = q.Filter(sqlchemy.In(q.Field("uid"), uids))
records := make([]SContact, 0, len(uids))
- err := db.FetchModelObjects(self, q, &records)
+ err = db.FetchModelObjects(self, q, &records)
if err != nil {
return nil, err
}
return records, nil
}
+func (self *SContactManager) _UIDsFromUIDOrName(uidStrs []string) ([]string, error) {
+ users, err := utils.GetUsersWithoutRemote(uidStrs)
+ if err != nil {
+ return nil, err
+ }
+ uids := make([]string, 0, len(uidStrs))
+ uidSet := sets.NewString(uidStrs...)
+ var (
+ uid string
+ uname string
+ )
+ for i := range users {
+ uid = users[i].Id
+ uname = users[i].Name
+ if uidSet.Has(uid) {
+ uids = append(uids, uid)
+ uidSet.Delete(uid)
+ continue
+ }
+ if uidSet.Has(uname) {
+ uids = append(uids, uid)
+ uidSet.Delete(uname)
+ continue
+ }
+ }
+ for _, uid = range uidSet.UnsortedList() {
+ uids = append(uids, uid)
+ }
+ return uids, nil
+}
+
func (self *SContactManager) FetchByUIDAndCType(uid string, contactTypes []string) ([]SContact, error) {
- q := self.Query("id", "uid", "contact_type", "contact", "enabled")
- q = q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("uid"), uid), sqlchemy.In(q.Field("contact_type"), contactTypes)))
+ q := self.Query("id", "uid", "contact_type", "contact", "enabled").Equals("uid", uid).In("contact_type", contactTypes)
records := make([]SContact, 0, len(contactTypes))
err := db.FetchModelObjects(self, q, &records)
if err != nil {
@@ -122,8 +160,7 @@ func (self *SContactManager) FetchByUIDAndCType(uid string, contactTypes []strin
}
func (self *SContactManager) FetchByMore(uid, contact, contactType string) ([]SContact, error) {
- q := self.Query()
- q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("uid"), uid), sqlchemy.Equals(q.Field("contact"), contact), sqlchemy.Equals(q.Field("contact_type"), contactType)))
+ q := self.Query().Equals("uid", uid).Equals("contact", contact).Equals("contact_type", contactType)
records := make([]SContact, 0, 1)
err := db.FetchModelObjects(self, q, &records)
if err != nil {
@@ -215,6 +252,12 @@ func (self *SContactManager) GetAllNotify(ctx context.Context, ids []string, con
q := self.Query()
if !group {
+ if v := ctx.Value("uname"); v != nil {
+ ids, err = self._UIDsFromUIDOrName(ids)
+ if err != nil {
+ return nil, errors.Wrap(err, "fail to transfer array of UID or Uname to UIDs")
+ }
+ }
uids = ids
} else {
uid := make([]string, 0)
@@ -225,6 +268,7 @@ func (self *SContactManager) GetAllNotify(ctx context.Context, ids []string, con
}
uid = append(uid, tmpUids...)
}
+ uids = uid
}
q.Filter(sqlchemy.AND(sqlchemy.In(q.Field("uid"), uids), sqlchemy.Equals(q.Field("contact_type"),
contactType), sqlchemy.Equals(q.Field("status"), CONTACT_VERIFIED)))
diff --git a/pkg/notify/models/mod_notification.go b/pkg/notify/models/mod_notification.go
index 25b526c8be..bd48f9b745 100644
--- a/pkg/notify/models/mod_notification.go
+++ b/pkg/notify/models/mod_notification.go
@@ -468,11 +468,12 @@ func sendWithoutUserCred(notifications []SNotification) {
wg.Wait()
}
-func ReSend(minutes int) {
- scope := time.Duration(minutes) * time.Minute
+func ReSend(seconds int) {
+ scope := time.Duration(seconds) * time.Second
notifications, err := NotificationManager.FetchNotOK(time.Now().Add(-scope))
if err != nil {
return
}
+ log.Debugf("Start to resend message with a total of %d", len(notifications))
sendWithoutUserCred(notifications)
}
diff --git a/pkg/notify/models/mod_template.go b/pkg/notify/models/mod_template.go
new file mode 100644
index 0000000000..3d39979938
--- /dev/null
+++ b/pkg/notify/models/mod_template.go
@@ -0,0 +1,198 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package models
+
+import (
+ "bytes"
+ "context"
+ "database/sql"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "strings"
+ "sync"
+ ptem "text/template"
+
+ "yunion.io/x/jsonutils"
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/cloudcommon/db"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/mcclient"
+ "yunion.io/x/onecloud/pkg/notify/rpc/apis"
+ "yunion.io/x/onecloud/pkg/notify/template"
+)
+
+type STemplateManager struct {
+ SStandaloneResourceBaseManager
+}
+
+var TemplateManager *STemplateManager
+
+func init() {
+ TemplateManager = &STemplateManager{
+ SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager(
+ STemplate{},
+ "notify_t_template",
+ "notifytemplate",
+ "notifytemplates",
+ ),
+ }
+}
+
+const (
+ TEMPLATE_TYPE_TITLE = "title"
+ TEMPLATE_TYPE_CONTENT = "content"
+ TEMPLATE_TYPE_REMOTE = "remote"
+)
+
+var (
+ DefaultEmailUrl = ""
+ EmailUrl = ""
+ EmailUrlLock sync.RWMutex
+)
+
+type STemplate struct {
+ SStandaloneResourceBase
+
+ ContactType string `width:"16" nullable:"false" create:"required" update:"user" list:"user"`
+ Topic string `width:"20" nullable:"false" create:"required" update:"user" list:"user"`
+
+ // title | content | remote
+ TemplateType string `width:"10" nullable:"false" create:"required" update:"user" list:"user"`
+ Content string `length:"text" nullable:"false" create:"required" get:"user" list:"user"`
+}
+
+func (tm *STemplateManager) GetEmailUrl() string {
+ EmailUrlLock.RLock()
+ defer EmailUrlLock.RUnlock()
+ if len(EmailUrl) == 0 {
+ return DefaultEmailUrl
+ }
+ return EmailUrl
+}
+
+func (tm *STemplateManager) SetEmailUrl(url string) {
+ EmailUrlLock.Lock()
+ defer EmailUrlLock.Unlock()
+ EmailUrl = url
+}
+
+func (tm *STemplateManager) InitializeData() error {
+ q := tm.Query().Equals("contact_type", "email").Equals("topic", "VERIFY").Equals("template_type", "content")
+ count, _ := q.CountWithError()
+ if count > 0 {
+ return nil
+ }
+ content, err := ioutil.ReadFile(template.EMAIL_VERIFY_CONTENT_PATH)
+ if os.IsNotExist(err) {
+ return nil
+ }
+ if err != nil {
+ return errors.Wrapf(err, "os.Open for '%s'", template.EMAIL_VERIFY_CONTENT_PATH)
+ }
+ contentTem := STemplate{
+ ContactType: "email",
+ Topic: "VERIFY",
+ TemplateType: "content",
+ Content: string(content),
+ }
+ titleTem := STemplate{
+ ContactType: "email",
+ Topic: "VERIFY",
+ TemplateType: "title",
+ Content: template.EMAIL_VERIFY_TITLE,
+ }
+ err = tm.TableSpec().InsertOrUpdate(&contentTem)
+ if err != nil {
+ return errors.Wrap(err, "sqlchemy.TableSpec.InsertOrUpdate")
+ }
+ tm.TableSpec().InsertOrUpdate(&titleTem)
+ if err != nil {
+ return errors.Wrap(err, "sqlchemy.TableSpec.InsertOrUpdate")
+ }
+ return nil
+}
+
+// NotifyFilter will return the title and content generated by corresponding template.
+// Local cache about common template will be considered in case of performance issues.
+func (tm *STemplateManager) NotifyFilter(contactType, topic, msg string) (params apis.SendParams, err error) {
+ params.Topic = topic
+ templates := make([]STemplate, 0, 3)
+ q := tm.Query().Equals("contact_type", contactType).Equals("topic", strings.ToUpper(topic))
+ err = db.FetchModelObjects(tm, q, &templates)
+ if errors.Cause(err) == sql.ErrNoRows {
+ // no such template, return as is
+ params.Title = topic
+ params.Message = msg
+ return
+ }
+ if err != nil {
+ err = errors.Wrap(err, "db.FetchModelObjects")
+ return
+ }
+ for _, template := range templates {
+ var title, content string
+ switch template.TemplateType {
+ case TEMPLATE_TYPE_TITLE:
+ title, err = template.Execute(msg)
+ if err != nil {
+ return
+ }
+ params.Title = title
+ case TEMPLATE_TYPE_CONTENT:
+ content, err = template.Execute(msg)
+ if err != nil {
+ return
+ }
+ params.Message = content
+ case TEMPLATE_TYPE_REMOTE:
+ params.RemoteTemplate = template.Content
+ params.Message = msg
+ default:
+ err = errors.Error("no support template type")
+ return
+ }
+ }
+ return
+}
+
+func (tm *STemplate) Execute(str string) (string, error) {
+ tem, err := ptem.New("tmp").Parse(tm.Content)
+ if err != nil {
+ return "", errors.Wrapf(err, "Template.Parse for template %s", tm.GetId())
+ }
+ var buffer bytes.Buffer
+ tmpMap := make(map[string]interface{})
+ err = json.Unmarshal([]byte(str), &tmpMap)
+ if err != nil {
+ return "", errors.Wrap(err, "json.Unmarshal")
+ }
+ err = tem.Execute(&buffer, tmpMap)
+ if err != nil {
+ return "", errors.Wrap(err, "template,Execute")
+ }
+ return buffer.String(), nil
+}
+
+func (manager *STemplateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential,
+ ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
+
+ ty, _ := data.GetString("template_type")
+ if ty != TEMPLATE_TYPE_TITLE && ty != TEMPLATE_TYPE_CONTENT && ty != TEMPLATE_TYPE_REMOTE {
+ return nil, httperrors.NewInputParameterError("no such support for tempalte type %s", ty)
+ }
+ return data, nil
+}
diff --git a/pkg/notify/models/worker.go b/pkg/notify/models/worker.go
index 6b40673d0b..42403cd1fb 100644
--- a/pkg/notify/models/worker.go
+++ b/pkg/notify/models/worker.go
@@ -25,8 +25,9 @@ import (
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
+ "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
"yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/notify/options"
+ "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
"yunion.io/x/onecloud/pkg/notify/utils"
)
@@ -54,7 +55,7 @@ func sendone(ctx context.Context, userCred mcclient.TokenCredential, notificatio
err = NotifyService.Send(ctx, notification.ContactType, contact, notification.Topic, notification.Msg,
notification.Priority)
if err != nil {
- log.Errorf("Send notification failed because that %s.", err.Error())
+ log.Errorf("Send notification failed: %s.", err.Error())
notification.SetStatus(userCred, NOTIFY_FAIL, err.Error())
} else {
log.Debugf("send notification successfully")
@@ -82,7 +83,7 @@ func sendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, v
)
processId, token := verify.ID, verify.Token
if contactType == "email" {
- emailUrl := strings.Replace(options.Options.VerifyEmailUrl, "{0}", processId, 1)
+ emailUrl := strings.Replace(TemplateManager.GetEmailUrl(), "{0}", processId, 1)
emailUrl = strings.Replace(emailUrl, "{1}", token, 1)
// get uName
@@ -105,7 +106,10 @@ func sendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, v
err = NotifyService.Send(ctx, contactType, contact, "verify", msg, "")
if err != nil {
verify.SetStatus(userCred, VERIFICATION_SENT_FAIL, "")
- log.Errorf("Send verify message failed because that %s.", err.Error())
+ // notify the uid through the webconsole
+ notifyclient.RawNotify([]string{uid}, false, notify.NotifyByWebConsole, notify.NotifyPriorityCritical,
+ "Send Verify Message Failed", jsonutils.NewString(err.Error()))
+ log.Errorf("Send verify message failed: %s.", err.Error())
return
}
verify.SetStatus(userCred, VERIFICATION_SENT, "")
diff --git a/pkg/notify/options/options.go b/pkg/notify/options/options.go
index f92e069ece..500e645121 100644
--- a/pkg/notify/options/options.go
+++ b/pkg/notify/options/options.go
@@ -22,12 +22,13 @@ type NotifyOption struct {
options.CommonOptions
options.DBOptions
- DingtalkEnabled bool `help:"Enable dingtalk"`
- SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/notify"`
- UpdateInterval int `help:"Update send services interval(unit:s)" default:"30"`
- VerifyEmailUrl string `help:"url of verify email"`
- ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope minutes"`
- InitNotificationScope int `help:"initialize data of notification with in InitNotificationScope hours" default:"100"`
+ DingtalkEnabled bool `help:"Enable dingtalk"`
+ SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/notify"`
+ UpdateInterval int `help:"Update send services interval(unit:s)" default:"30"`
+ VerifyEmailUrl string `help:"url of verify email"`
+ ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope
+seconds"`
+ InitNotificationScope int `help:"initialize data of notification with in InitNotificationScope hours" default:"100"`
}
var Options NotifyOption
diff --git a/pkg/notify/rpc/apis/send_server.pb.go b/pkg/notify/rpc/apis/send_server.pb.go
index 2b30129f9a..f1db91f6b1 100644
--- a/pkg/notify/rpc/apis/send_server.pb.go
+++ b/pkg/notify/rpc/apis/send_server.pb.go
@@ -40,10 +40,12 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type SendParams struct {
- Contact string `protobuf:"bytes,1,opt,name=contact,proto3" json:"contact,omitempty"`
- Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"`
- Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
- Priority string `protobuf:"bytes,4,opt,name=Priority,proto3" json:"Priority,omitempty"`
+ Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"`
+ Topic string `protobuf:"bytes,2,opt,name=Topic,proto3" json:"Topic,omitempty"`
+ Title string `protobuf:"bytes,3,opt,name=Title,proto3" json:"Title,omitempty"`
+ Message string `protobuf:"bytes,4,opt,name=Message,proto3" json:"Message,omitempty"`
+ Priority string `protobuf:"bytes,5,opt,name=Priority,proto3" json:"Priority,omitempty"`
+ RemoteTemplate string `protobuf:"bytes,6,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -88,6 +90,13 @@ func (m *SendParams) GetTopic() string {
return ""
}
+func (m *SendParams) GetTitle() string {
+ if m != nil {
+ return m.Title
+ }
+ return ""
+}
+
func (m *SendParams) GetMessage() string {
if m != nil {
return m.Message
@@ -102,6 +111,13 @@ func (m *SendParams) GetPriority() string {
return ""
}
+func (m *SendParams) GetRemoteTemplate() string {
+ if m != nil {
+ return m.RemoteTemplate
+ }
+ return ""
+}
+
type UpdateConfigParams struct {
Configs map[string]string `protobuf:"bytes,1,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -262,28 +278,30 @@ func init() {
func init() { proto.RegisterFile("send_server.proto", fileDescriptor_63fdd68f7eb311f9) }
var fileDescriptor_63fdd68f7eb311f9 = []byte{
- // 325 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xcd, 0x4a, 0x03, 0x31,
- 0x10, 0xc7, 0xd9, 0x7e, 0xda, 0x69, 0x91, 0x1a, 0x8b, 0xc4, 0x3d, 0x95, 0x85, 0x42, 0x2f, 0xee,
- 0xa1, 0x22, 0x48, 0x2f, 0xa2, 0x52, 0x3c, 0x09, 0xa5, 0xd2, 0xb3, 0xa4, 0xdd, 0xe9, 0x12, 0xec,
- 0x6e, 0x96, 0x24, 0x2d, 0xec, 0x63, 0xf8, 0x2a, 0x3e, 0xa1, 0xe4, 0x63, 0xb5, 0xc5, 0x7a, 0xcb,
- 0x6f, 0x66, 0xfe, 0x99, 0xf9, 0x4f, 0x02, 0x17, 0x0a, 0xf3, 0xe4, 0x5d, 0xa1, 0xdc, 0xa3, 0x8c,
- 0x0b, 0x29, 0xb4, 0x20, 0x0d, 0x56, 0x70, 0x15, 0x49, 0x80, 0x37, 0xcc, 0x93, 0x39, 0x93, 0x2c,
- 0x53, 0x84, 0x42, 0x7b, 0x2d, 0x72, 0xcd, 0xd6, 0x9a, 0x06, 0xc3, 0x60, 0xdc, 0x59, 0x54, 0x48,
- 0x06, 0xd0, 0xd4, 0xa2, 0xe0, 0x6b, 0x5a, 0xb3, 0x71, 0x07, 0xa6, 0x3e, 0x43, 0xa5, 0x58, 0x8a,
- 0xb4, 0xee, 0xea, 0x3d, 0x92, 0x10, 0xce, 0xe6, 0x92, 0x0b, 0xc9, 0x75, 0x49, 0x1b, 0x36, 0xf5,
- 0xc3, 0xd1, 0x67, 0x00, 0x64, 0x59, 0x24, 0x4c, 0xe3, 0xb3, 0xc8, 0x37, 0x3c, 0xf5, 0xcd, 0x1f,
- 0x6c, 0xf3, 0x0d, 0x4f, 0x15, 0x0d, 0x86, 0xf5, 0x71, 0x77, 0x32, 0x8a, 0xcd, 0x88, 0xf1, 0xdf,
- 0xd2, 0xd8, 0x81, 0x9a, 0xe5, 0x5a, 0x96, 0x8b, 0x4a, 0x15, 0x4e, 0xa1, 0x77, 0x98, 0x20, 0x7d,
- 0xa8, 0x7f, 0x60, 0xe9, 0x9d, 0x98, 0xa3, 0x71, 0xb1, 0x67, 0xdb, 0x1d, 0x56, 0x2e, 0x2c, 0x4c,
- 0x6b, 0xf7, 0x41, 0x14, 0xc3, 0x60, 0xa9, 0x50, 0xf2, 0xe4, 0xa9, 0x7c, 0x15, 0x2b, 0xbe, 0x45,
- 0x3f, 0xd4, 0x15, 0xb4, 0x32, 0xcb, 0xfe, 0x1a, 0x4f, 0x51, 0x1b, 0x9a, 0xb3, 0xac, 0xd0, 0x65,
- 0x74, 0x03, 0x97, 0xc7, 0xc2, 0x05, 0x16, 0xdb, 0xd2, 0xe8, 0x76, 0x36, 0x5c, 0xe9, 0x1c, 0x4d,
- 0xbe, 0x02, 0xe8, 0x98, 0x85, 0x3f, 0xa6, 0x98, 0x6b, 0x32, 0x82, 0x86, 0x01, 0xd2, 0x77, 0x4e,
- 0x7f, 0x5f, 0x22, 0xec, 0xba, 0x88, 0xed, 0x41, 0xee, 0xa0, 0x77, 0xb8, 0x04, 0x42, 0xff, 0x5b,
- 0xcc, 0xb1, 0xec, 0x05, 0xce, 0x8f, 0x47, 0x23, 0xa1, 0x17, 0x9e, 0x70, 0x1a, 0x5e, 0x9f, 0xca,
- 0x59, 0x33, 0xab, 0x96, 0xfd, 0x31, 0xb7, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x49, 0x12, 0x30,
- 0x1e, 0x46, 0x02, 0x00, 0x00,
+ // 357 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xcf, 0x6a, 0xe3, 0x30,
+ 0x10, 0xc6, 0x71, 0xfe, 0x6e, 0x26, 0x21, 0x64, 0xb5, 0x61, 0xd1, 0xfa, 0x14, 0x0c, 0x59, 0x72,
+ 0x59, 0x1f, 0xb2, 0x2c, 0x2c, 0xb9, 0x94, 0x36, 0x84, 0x9e, 0x02, 0xc1, 0x4d, 0xce, 0x45, 0x89,
+ 0xa7, 0x41, 0xd4, 0xb6, 0x8c, 0xa4, 0x04, 0xfc, 0x18, 0x7d, 0x93, 0xd2, 0x27, 0x2c, 0x92, 0xe5,
+ 0x36, 0x69, 0xd3, 0x9b, 0x7f, 0xdf, 0xe8, 0x1b, 0xcf, 0x37, 0x12, 0x7c, 0x57, 0x98, 0xc5, 0xf7,
+ 0x0a, 0xe5, 0x11, 0x65, 0x98, 0x4b, 0xa1, 0x05, 0x69, 0xb0, 0x9c, 0xab, 0xe0, 0xd9, 0x03, 0xb8,
+ 0xc3, 0x2c, 0x5e, 0x31, 0xc9, 0x52, 0x45, 0x28, 0xb4, 0xe7, 0x22, 0xd3, 0x6c, 0xa7, 0xa9, 0x37,
+ 0xf2, 0x26, 0x9d, 0xa8, 0x42, 0x32, 0x84, 0xe6, 0x5a, 0xe4, 0x7c, 0x47, 0x6b, 0x56, 0x2f, 0xc1,
+ 0xaa, 0x5c, 0x27, 0x48, 0xeb, 0x4e, 0x35, 0x60, 0xba, 0x2c, 0x51, 0x29, 0xb6, 0x47, 0xda, 0x28,
+ 0xbb, 0x38, 0x24, 0x3e, 0x7c, 0x5b, 0x49, 0x2e, 0x24, 0xd7, 0x05, 0x6d, 0xda, 0xd2, 0x1b, 0x93,
+ 0xdf, 0xd0, 0x8f, 0x30, 0x15, 0x1a, 0xd7, 0x98, 0xe6, 0x09, 0xd3, 0x48, 0x5b, 0xf6, 0xc4, 0x07,
+ 0x35, 0x78, 0xf2, 0x80, 0x6c, 0xf2, 0x98, 0x69, 0x9c, 0x8b, 0xec, 0x81, 0xef, 0xdd, 0xe8, 0x57,
+ 0xd0, 0xde, 0x59, 0x56, 0xd4, 0x1b, 0xd5, 0x27, 0xdd, 0xe9, 0x38, 0x34, 0x09, 0xc3, 0xcf, 0x47,
+ 0xc3, 0x12, 0xd4, 0x22, 0xd3, 0xb2, 0x88, 0x2a, 0x97, 0x3f, 0x83, 0xde, 0x69, 0x81, 0x0c, 0xa0,
+ 0xfe, 0x88, 0x85, 0xdb, 0x83, 0xf9, 0x34, 0x69, 0x8f, 0x2c, 0x39, 0x60, 0xb5, 0x03, 0x0b, 0xb3,
+ 0xda, 0x7f, 0x2f, 0x08, 0x61, 0xb8, 0x51, 0x28, 0x79, 0x7c, 0x53, 0x2c, 0xc5, 0x96, 0x27, 0xe8,
+ 0x86, 0xfa, 0x09, 0xad, 0xd4, 0xb2, 0x6b, 0xe3, 0x28, 0x68, 0x43, 0x73, 0x91, 0xe6, 0xba, 0x08,
+ 0xfe, 0xc0, 0x8f, 0x73, 0x63, 0x84, 0x79, 0x52, 0x18, 0xdf, 0xc1, 0xca, 0x95, 0xaf, 0xa4, 0xe9,
+ 0x8b, 0x07, 0x1d, 0x73, 0x5d, 0xd7, 0x7b, 0xcc, 0x34, 0x19, 0x43, 0xc3, 0x00, 0x19, 0x94, 0x49,
+ 0xdf, 0xef, 0xd1, 0xef, 0x96, 0x8a, 0xfd, 0x07, 0xf9, 0x07, 0xbd, 0xd3, 0x25, 0x10, 0xfa, 0xd5,
+ 0x62, 0xce, 0x6d, 0xb7, 0xd0, 0x3f, 0x1f, 0x8d, 0xf8, 0xce, 0x78, 0x21, 0xa9, 0xff, 0xeb, 0x52,
+ 0xcd, 0x86, 0xd9, 0xb6, 0xec, 0x83, 0xfb, 0xfb, 0x1a, 0x00, 0x00, 0xff, 0xff, 0xd7, 0x6c, 0xcb,
+ 0x1b, 0x85, 0x02, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
diff --git a/pkg/notify/rpc/apis/send_server.proto b/pkg/notify/rpc/apis/send_server.proto
index e2060ecfc8..c065347486 100644
--- a/pkg/notify/rpc/apis/send_server.proto
+++ b/pkg/notify/rpc/apis/send_server.proto
@@ -1,12 +1,28 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
syntax = "proto3";
package apis;
message SendParams {
- string contact = 1;
- string topic = 2;
- string message = 3;
- string Priority = 4;
+ string Contact = 1;
+ string Topic = 2;
+ string Title = 3;
+ string Message = 4;
+ string Priority = 5;
+ string RemoteTemplate = 6;
}
message UpdateConfigParams {
diff --git a/pkg/notify/rpc/send.go b/pkg/notify/rpc/send.go
index e1d9405a48..b6db8fe127 100644
--- a/pkg/notify/rpc/send.go
+++ b/pkg/notify/rpc/send.go
@@ -50,14 +50,17 @@ type SRpcService struct {
SendServices *ServiceMap
socketFileDir string
configStore _interface.IServiceConfigStore
+ templateStore _interface.ITemplateStore
}
// NewSRpcService create a SRpcService
-func NewSRpcService(socketFileDir string, configStore _interface.IServiceConfigStore) *SRpcService {
+func NewSRpcService(socketFileDir string, configStore _interface.IServiceConfigStore,
+ tempalteStore _interface.ITemplateStore) *SRpcService {
return &SRpcService{
SendServices: NewServiceMap(),
socketFileDir: socketFileDir,
configStore: configStore,
+ templateStore: tempalteStore,
}
}
@@ -106,20 +109,24 @@ func (self *SRpcService) StopAll() {
// Send call the corresponding rpc server to send messager.
func (self *SRpcService) Send(ctx context.Context, contactType, contact, topic, msg, priority string) error {
- args := apis.SendParams{
- Contact: contact,
- Topic: topic,
- Message: msg,
- Priority: priority,
+ args, err := self.templateStore.NotifyFilter(contactType, topic, msg)
+ if err != nil {
+ return errors.Wrap(err, "templateStore.NotifyFilter")
}
+ args.Contact = contact
+ args.Priority = priority
+
f := func(service *apis.SendNotificationClient) (interface{}, error) {
log.Debugf("send one")
return service.Send(ctx, &args)
}
- _, err := self.execute(ctx, f, contactType)
- return err
+ _, err = self.execute(ctx, f, contactType)
+ if err != nil {
+ return errors.Wrapf(err, "contactType: %s", contactType)
+ }
+ return nil
}
// RestartService can restart remote rpc server and pass config info.
@@ -179,7 +186,7 @@ func (self *SRpcService) execute(ctx context.Context, f func(client *apis.SendNo
}
if st.Message() != ErrSendServiceNotInit.Error() {
- return nil, errors.Error(fmt.Sprintf("Send message failed because that %s.", st.Message()))
+ return nil, errors.Error(st.Message())
}
// if NOINIT, try to restart server and send again
@@ -197,7 +204,7 @@ func (self *SRpcService) execute(ctx context.Context, f func(client *apis.SendNo
return nil, errors.Wrap(ErrSendServiceNotFound, serviceName)
}
- return nil, errors.Error(fmt.Sprintf("Send message failed because that %s.", st.Message()))
+ return nil, errors.Error(st.Message())
}
}
return ret, nil
diff --git a/pkg/notify/service.go b/pkg/notify/service.go
index 95c2ea8e72..7b40a57e01 100644
--- a/pkg/notify/service.go
+++ b/pkg/notify/service.go
@@ -43,6 +43,9 @@ func StartService() {
baseOpts := &options.Options.BaseOptions
common_options.ParseOptions(opts, os.Args, "notify.conf", "notify")
+ // init email url
+ models.TemplateManager.SetEmailUrl(options.Options.VerifyEmailUrl)
+
// init auth
app.InitAuth(commonOpts, func() {
log.Infof("Auth complete!")
@@ -60,7 +63,7 @@ func StartService() {
cache.RegistUserCredCacheUpdater()
// init notify service
- models.NotifyService = rpc.NewSRpcService(opts.SocketFileDir, models.ConfigManager)
+ models.NotifyService = rpc.NewSRpcService(opts.SocketFileDir, models.ConfigManager, models.TemplateManager)
models.NotifyService.InitAll()
defer models.NotifyService.StopAll()
@@ -72,7 +75,8 @@ func StartService() {
resend := func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
models.ReSend(opts.ReSendScope)
}
- cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Minute, resend)
+ cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Second, resend)
+ cron.Start()
app.ServeForever(applicaion, baseOpts)
}
diff --git a/pkg/notify/template/doc.go b/pkg/notify/template/doc.go
new file mode 100644
index 0000000000..aebfaab473
--- /dev/null
+++ b/pkg/notify/template/doc.go
@@ -0,0 +1 @@
+package template // import "yunion.io/x/onecloud/pkg/notify/template"
diff --git a/pkg/notify/template/template.go b/pkg/notify/template/template.go
new file mode 100644
index 0000000000..2ecc8ab580
--- /dev/null
+++ b/pkg/notify/template/template.go
@@ -0,0 +1,20 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package template
+
+const (
+ EMAIL_VERIFY_CONTENT_PATH = "/opt/yunion/share/notify/email_verify_template"
+ EMAIL_VERIFY_TITLE = "Yunion Verify"
+)
diff --git a/pkg/notify/utils/keystone.go b/pkg/notify/utils/keystone.go
index d72c0703d1..5529906139 100644
--- a/pkg/notify/utils/keystone.go
+++ b/pkg/notify/utils/keystone.go
@@ -17,11 +17,26 @@ package utils
import (
"context"
+ "yunion.io/x/pkg/errors"
+ "yunion.io/x/sqlchemy"
+
+ "yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/notify/cache"
)
-func GetUserByID(ctx context.Context, id string) (*cache.SUser, error) {
- return cache.UserCacheManager.FetchUserByID(ctx, id, false)
+func GetUserByIDOrName(ctx context.Context, idStr string) (*cache.SUser, error) {
+ return cache.UserCacheManager.FetchUserByIDOrName(ctx, idStr)
+}
+
+func GetUsersWithoutRemote(idStr []string) ([]cache.SUser, error) {
+ q := cache.UserCacheManager.Query()
+ q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("id"), idStr), sqlchemy.In(q.Field("name"), idStr)))
+ users := make([]cache.SUser, 0, 1)
+ err := db.FetchModelObjects(cache.UserCacheManager, q, &users)
+ if err != nil {
+ return nil, errors.Wrapf(err, "fetch user cache failed")
+ }
+ return users, nil
}
func GetUserIdsLikeName(ctx context.Context, name string) ([]string, error) {
@@ -49,7 +64,7 @@ func GetUsersByGroupID(ctx context.Context, gid string) ([]string, error) {
}
func GetUsernameByID(ctx context.Context, id string) (string, error) {
- user, err := GetUserByID(ctx, id)
+ user, err := GetUserByIDOrName(ctx, id)
if err != nil {
return "", err
}