mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
Refactor for Notify (#7611)
* refactor(notify): ALL * feat(climc): Notify v2 * feat: support notify for codegen.py * delete deprecated code and Maintain SDK compatibility * Adjust the template for mail validation to support captcha * fix: The issue of notify refactoring * add copyright for notifyv2 cli * Adjust according to the advice of make check * Remove utils package in notify * fix import format * remove redundant Template module * fix validateCreate and validateUpdate of notifytemplate * fix update issues of receivers * fix: Add SetStageComplete in tasks
This commit is contained in:
@@ -47,16 +47,13 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 10px;">请点击下面的链接,完成用户邮箱的激活:</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 30px;"><a href="{{.link}}">{{.link}}</a></td>
|
||||
<td style="padding-bottom: 10px;">您正在验证邮箱,请在验证码输入框中输入:{{.code}},已完成验证。</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="width: 96%;">
|
||||
<td colspan="2" style="border-top: 1px dashed #ccc; color: #333; font-size: 12px; padding-bottom: 10px; font-weight: 100;">*为了确保您的帐号安全,该链接仅48小时内访问有效,请勿直接回复此邮件。</td>
|
||||
<td colspan="2" style="border-top: 1px dashed #ccc; color: #333; font-size: 12px; padding-bottom: 10px; font-weight: 100;">如非本人操作,请及时登录平台并修改密码以保证账户的安全</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="background: #333; text-align: right; padding-right: 20px; font-size: 12px; color: #fff; height: 50px;">版权所有 © {{.copyright}} 保留一切权利</td>
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/meter"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/misc"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/monitor"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/notify"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/notifyv2"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/servicetree"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/yunionconf"
|
||||
)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
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 ContactGroupsListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
R(&ContactGroupsListOptions{}, "contact-group-list", "List all contact groups for all the domainsconta", func(s *mcclient.ClientSession, args *ContactGroupsListOptions) error {
|
||||
var params *jsonutils.JSONDict
|
||||
{
|
||||
var err error
|
||||
params, err = args.BaseListOptions.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
result, err := modules.ContactGroups.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printList(result, modules.ContactGroups.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
/**
|
||||
* 操作用户的通信地址(如果用户的通信地址不存在则进行添加;如果已存在则进行修改;如果设置空则进行删除。)
|
||||
*/
|
||||
type ContactsUpdateOptions struct {
|
||||
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
|
||||
CONTACTTYPE string `help:"The contact type email|mobile" choices:"email|mobile|dingtalk"`
|
||||
CONTACT string `help:"The contacts details mobile number or email address or dingtalk's userid, if set it the empty str means delete"`
|
||||
Status string `help:"Enabled or disabled contact status" choices:"enable|disable"`
|
||||
Pull []string `help:"pull some subcontacts(e.g., dingtalk, feishu, etc) related to mobile"`
|
||||
}
|
||||
R(&ContactsUpdateOptions{}, "contact-update", "Create, delete or update contact for user", func(s *mcclient.ClientSession, args *ContactsUpdateOptions) error {
|
||||
arr := jsonutils.NewArray()
|
||||
tmpObj := jsonutils.NewDict()
|
||||
tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type")
|
||||
tmpObj.Add(jsonutils.NewString(args.CONTACT), "contact")
|
||||
if len(args.Status) > 0 {
|
||||
if args.Status == "disable" {
|
||||
tmpObj.Add(jsonutils.NewInt(0), "enabled")
|
||||
} else {
|
||||
tmpObj.Add(jsonutils.NewInt(1), "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
arr.Add(tmpObj)
|
||||
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(arr, "contacts")
|
||||
|
||||
pulls := jsonutils.NewArray()
|
||||
for _, pull := range args.Pull {
|
||||
pulls.Add(jsonutils.NewString(pull))
|
||||
}
|
||||
params.Add(pulls, "pull")
|
||||
|
||||
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printObject(contact)
|
||||
return nil
|
||||
})
|
||||
|
||||
type ContactsPullOptions struct {
|
||||
UID string `help:"The user you wanna pull contact"`
|
||||
CONTACTTYPE string `help:"The contact type"`
|
||||
}
|
||||
R(&ContactsPullOptions{}, "contact-pull", "Pull contact", func(s *mcclient.ClientSession, args *ContactsPullOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("contacts", jsonutils.NewArray())
|
||||
params.Set("pull", jsonutils.NewArray(jsonutils.NewString(args.CONTACTTYPE)))
|
||||
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(contact)
|
||||
return nil
|
||||
})
|
||||
|
||||
type ContactsDeleteOptions struct {
|
||||
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
|
||||
CONTACTTYPE string `help:"The contact type email|mobile|dingtalk" choices:"email|mobile|dingtalk"`
|
||||
}
|
||||
R(&ContactsDeleteOptions{}, "contact-delete", "Delete contact for user", func(s *mcclient.ClientSession, args *ContactsDeleteOptions) error {
|
||||
arr := jsonutils.NewArray()
|
||||
tmpObj := jsonutils.NewDict()
|
||||
tmpObj.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type")
|
||||
tmpObj.Add(jsonutils.NewString(""), "contact")
|
||||
arr.Add(tmpObj)
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(arr, "contacts")
|
||||
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(contact)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 获得所有用户的所有通信地址列表
|
||||
*/
|
||||
type ContactsListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
R(&ContactsListOptions{}, "contact-list", "List all contacts for all users", func(s *mcclient.ClientSession, args *ContactsListOptions) error {
|
||||
var params *jsonutils.JSONDict
|
||||
{
|
||||
var err error
|
||||
params, err = args.BaseListOptions.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
}
|
||||
params.Add(jsonutils.JSONTrue, "details")
|
||||
|
||||
result, err := modules.Contacts.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printList(result, modules.Contacts.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 获得一个用户全部通信地址
|
||||
*/
|
||||
type ContactsListForUserOptions struct {
|
||||
options.BaseListOptions
|
||||
UID string `help:"The user you wanna find contact from (Keystone User ID)"`
|
||||
}
|
||||
R(&ContactsListForUserOptions{}, "contact-show", "List all contacts for the users", func(s *mcclient.ClientSession, args *ContactsListForUserOptions) error {
|
||||
var params *jsonutils.JSONDict
|
||||
{
|
||||
var err error
|
||||
params, err = args.BaseListOptions.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
params.Add(jsonutils.JSONTrue, "details")
|
||||
|
||||
result, err := modules.Contacts.CustomizedGet(s, args.UID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contactsStr, err := result.GetString("details")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
contactsJson, err := jsonutils.ParseString(contactsStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contacts, err := contactsJson.GetArray()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printList(&modulebase.ListResult{Data: contacts}, nil)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 触发验证通信地址操作
|
||||
*/
|
||||
type ContactsVerifyOptions struct {
|
||||
UID string `help:"The user you wanna verify contact for (Keystone User ID)"`
|
||||
CONTACT_TYPE string `help:"The contact type email|mobile"`
|
||||
CONTACT string `help:"The contacts details mobile number or email address"`
|
||||
}
|
||||
R(&ContactsVerifyOptions{}, "contact-verify-trigger", "Trigger contact verify", func(s *mcclient.ClientSession, args *ContactsVerifyOptions) error {
|
||||
tmpDict := jsonutils.NewDict()
|
||||
tmpDict.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type")
|
||||
tmpDict.Add(jsonutils.NewString(args.CONTACT), "contact")
|
||||
_, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", tmpDict)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
type ContactsBatchDeleteOptions struct {
|
||||
UIDS []string `help:"All user'id you wanna to delete contacts (Keystone User ID)"`
|
||||
}
|
||||
R(&ContactsBatchDeleteOptions{}, "contact-delete", "Delete all contacts for the user", func(s *mcclient.ClientSession, args *ContactsBatchDeleteOptions) error {
|
||||
arr := jsonutils.NewArray()
|
||||
for _, f := range args.UIDS {
|
||||
arr.Add(jsonutils.NewString(f))
|
||||
}
|
||||
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(arr, "contacts")
|
||||
|
||||
contact, err := modules.Contacts.DoBatchDeleteContacts(s, params)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printObject(contact)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
/**
|
||||
* 查询邮件配置信息
|
||||
*/
|
||||
type EmailConfigShowOptions struct {
|
||||
TYPE string `help:"type "`
|
||||
}
|
||||
R(&EmailConfigShowOptions{}, "email-config-show", "Show email-config details",
|
||||
func(s *mcclient.ClientSession, args *EmailConfigShowOptions) error {
|
||||
result, err := modules.EmailConfigs.Get(s, args.TYPE, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 增加邮件配置信息
|
||||
*/
|
||||
type EmailConfigCreateOptions struct {
|
||||
USERNAME string `help:"Username for email sender"`
|
||||
PASSWORD string `help:"Password for email sender"`
|
||||
HOSTNAME string `help:"Email server name"`
|
||||
SSLGLOBAL string `help:"use ssl_global"`
|
||||
HOSTPORT int64 `help:"Email server port"`
|
||||
}
|
||||
|
||||
R(&EmailConfigCreateOptions{}, "email-config-create", "Create a Email Config",
|
||||
func(s *mcclient.ClientSession, args *EmailConfigCreateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.USERNAME), "username")
|
||||
params.Add(jsonutils.NewString(args.PASSWORD), "password")
|
||||
params.Add(jsonutils.NewString(args.HOSTNAME), "hostname")
|
||||
params.Add(jsonutils.NewString(args.SSLGLOBAL), "ssl_global")
|
||||
params.Add(jsonutils.NewInt(args.HOSTPORT), "hostport")
|
||||
|
||||
result, err := modules.EmailConfigs.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
type EmailConfigUpdateOptions struct {
|
||||
TYPE string `help:"type of email "`
|
||||
USERNAME string `help:"Username for email sender"`
|
||||
PASSWORD string `help:"Password for email sender"`
|
||||
HOSTNAME string `help:"Email server name"`
|
||||
SSLGLOBAL string `help:"use ssl_global"`
|
||||
HOSTPORT int64 `help:"Email server port"`
|
||||
}
|
||||
R(&EmailConfigUpdateOptions{}, "email-config-update", "Update a email-config", func(s *mcclient.ClientSession, args *EmailConfigUpdateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.USERNAME), "username")
|
||||
params.Add(jsonutils.NewString(args.PASSWORD), "password")
|
||||
params.Add(jsonutils.NewString(args.HOSTNAME), "hostname")
|
||||
params.Add(jsonutils.NewString(args.SSLGLOBAL), "ssl_global")
|
||||
params.Add(jsonutils.NewInt(args.HOSTPORT), "hostport")
|
||||
|
||||
result, err := modules.EmailConfigs.Put(s, args.TYPE, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
type EmailConfigDeleteOptions struct {
|
||||
TYPE string `help:"type of email "`
|
||||
}
|
||||
R(&EmailConfigDeleteOptions{}, "email-config-delete", "Delete a email config", func(s *mcclient.ClientSession, args *EmailConfigDeleteOptions) error {
|
||||
result, e := modules.EmailConfigs.Delete(s, args.TYPE, nil)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/notify"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
/**
|
||||
* 新建一个通知发送任务
|
||||
*/
|
||||
|
||||
type NotificationCreateOptions struct {
|
||||
CONTACTTYPE string `help:"User's contacts type"`
|
||||
TOPIC string `help:"Title or topic of the notification"`
|
||||
PRIORITY string `help:"Priority of the notification" choices:"normal|important|fatal"`
|
||||
MSG string `help:"The content of the notification"`
|
||||
Remark string `help:"Remark or description of the notification"`
|
||||
Group bool `help:"Send to group"`
|
||||
}
|
||||
type NotificationCreateSingleOptions struct {
|
||||
UID string `help:"The user you wanna sent to (Keystone User ID)"`
|
||||
NotificationCreateOptions
|
||||
}
|
||||
|
||||
R(&NotificationCreateSingleOptions{}, "notify", "Send a notification to someone", func(s *mcclient.ClientSession,
|
||||
args *NotificationCreateSingleOptions) error {
|
||||
|
||||
msg := notify.SNotifyMessage{}
|
||||
if args.Group {
|
||||
msg.Gid = []string{args.UID}
|
||||
} else {
|
||||
msg.Uid = []string{args.UID}
|
||||
}
|
||||
|
||||
msg.ContactType = notify.TNotifyChannel(args.CONTACTTYPE)
|
||||
msg.Topic = args.TOPIC
|
||||
msg.Priority = notify.TNotifyPriority(args.PRIORITY)
|
||||
msg.Msg = args.MSG
|
||||
msg.Remark = args.Remark
|
||||
|
||||
err := notify.Notifications.Send(s, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
type NotificationCreateMultiOptions struct {
|
||||
Uid []string `help:"The user you wanna sent to (Keystone User ID)"`
|
||||
NotificationCreateOptions
|
||||
}
|
||||
|
||||
R(&NotificationCreateMultiOptions{}, "notify-batch", "Send a notification to someones",
|
||||
func(s *mcclient.ClientSession, args *NotificationCreateMultiOptions) error {
|
||||
|
||||
msg := notify.SNotifyMessage{}
|
||||
if args.Group {
|
||||
msg.Gid = args.Uid
|
||||
} else {
|
||||
msg.Uid = args.Uid
|
||||
}
|
||||
|
||||
msg.ContactType = notify.TNotifyChannel(args.CONTACTTYPE)
|
||||
msg.Topic = args.TOPIC
|
||||
msg.Priority = notify.TNotifyPriority(args.PRIORITY)
|
||||
msg.Msg = args.MSG
|
||||
msg.Remark = args.Remark
|
||||
|
||||
err := notify.Notifications.Send(s, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 发送全局通知
|
||||
*/
|
||||
type NotificationBroadcastOptions struct {
|
||||
// CONTACTTYPE string `help:"User's contacts type, cloud be email|mobile|dingtalk|/webconsole" choices:"email|mobile|dingtalk|webconsole"`
|
||||
Topic string `required:"true" help:"Title or topic of the notification"`
|
||||
Priority string `help:"Priority of the notification" choices:"normal|important|fatal" default:"normal"`
|
||||
Msg string `help:"The content of the notification"`
|
||||
Remark string `help:"Remark or description of the notification"`
|
||||
// Group bool `help:"Send to group"`
|
||||
}
|
||||
|
||||
R(&NotificationBroadcastOptions{}, "notify-broadcast", "Send a notification to all online users", func(s *mcclient.ClientSession, args *NotificationBroadcastOptions) error {
|
||||
msg := notify.SNotifyMessage{}
|
||||
msg.Broadcast = true
|
||||
msg.ContactType = notify.NotifyByWebConsole
|
||||
msg.Topic = args.Topic
|
||||
msg.Priority = notify.TNotifyPriority(args.Priority)
|
||||
msg.Msg = args.Msg
|
||||
msg.Remark = args.Remark
|
||||
|
||||
err := notify.Notifications.Send(s, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
/**
|
||||
* 修改通知发送任务的状态
|
||||
*/
|
||||
type NotificationUpdateCallbackOptions struct {
|
||||
ID string `help:"ID of the notification send task"`
|
||||
STATUS string `help:"Notification send status" choices:"sent_ok|send_fail"`
|
||||
Remark string `help:"Remark or description of the operation or fail reason"`
|
||||
}
|
||||
R(&NotificationUpdateCallbackOptions{}, "notification-update-callback", "UpdateItem send status of the notification task", func(s *mcclient.ClientSession, args *NotificationUpdateCallbackOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.STATUS), "status")
|
||||
if len(args.Remark) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Remark), "remark")
|
||||
}
|
||||
|
||||
notification, err := notify.Notifications.Put(s, args.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(notification)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 查询已发送的通知任务
|
||||
*/
|
||||
type NotificationListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
R(&NotificationListOptions{}, "notify-list", "List notification history", func(s *mcclient.ClientSession, args *NotificationListOptions) error {
|
||||
result, err := notify.Notifications.List(s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printList(result, notify.Notifications.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
type ConfigCreate2Options struct {
|
||||
CONTACTTYPE string `help:"contact type (email|sms_aliyun or others)"`
|
||||
CONFIGS []string `help:"config (k, v)"`
|
||||
}
|
||||
R(&ConfigCreate2Options{}, "notify-config-update", "config update, example: notify_config-update email mail.smtp.hostname hostname mail.smtp.hostport 123.", func(s *mcclient.ClientSession, args *ConfigCreate2Options) error {
|
||||
tmp := jsonutils.NewDict()
|
||||
for i := 0; i+1 < len(args.CONFIGS); i += 2 {
|
||||
tmp.Add(jsonutils.NewString(args.CONFIGS[i+1]), args.CONFIGS[i])
|
||||
}
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(tmp, args.CONTACTTYPE)
|
||||
ret, err := modules.Configs.Create(s, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ConfigCreate2Options{}, "notify-config-validate", "config validate", func(s *mcclient.ClientSession,
|
||||
args *ConfigCreate2Options) error {
|
||||
tmp := jsonutils.NewDict()
|
||||
for i := 0; i+1 < len(args.CONFIGS); i += 2 {
|
||||
tmp.Add(jsonutils.NewString(args.CONFIGS[i+1]), args.CONFIGS[i])
|
||||
}
|
||||
ret, err := modules.Configs.PerformAction(s, args.CONTACTTYPE, "validate", tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
|
||||
type ConfigGet2Options struct {
|
||||
TYPE string `help:"contact type (email|sms_aliyun or others)"`
|
||||
}
|
||||
R(&ConfigGet2Options{}, "notify-config-show", "config show", func(s *mcclient.ClientSession, args *ConfigGet2Options) error {
|
||||
result, err := modules.Configs.Get(s, args.TYPE, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
R(&ConfigGet2Options{}, "notify-config-delete", "config delete", func(s *mcclient.ClientSession, args *ConfigGet2Options) error {
|
||||
result, err := modules.Configs.Delete(s, args.TYPE, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
/**
|
||||
* 查询短信配置信息
|
||||
*/
|
||||
type SmsConfigShowOptions struct {
|
||||
TYPE string `help:"type "`
|
||||
}
|
||||
R(&SmsConfigShowOptions{}, "sms-config-show", "Show sms config details",
|
||||
func(s *mcclient.ClientSession, args *SmsConfigShowOptions) error {
|
||||
result, err := modules.SmsConfigs.Get(s, args.TYPE, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 增加短信配置信息
|
||||
*/
|
||||
type SmsConfigCreateOptions struct {
|
||||
TYPE string `help:"sms vendor"`
|
||||
ACCESSKEYID string `help:"ACCESSKEYID for sms vendor"`
|
||||
ACCESSKEYSECRET string `help:"ACCESSKEYSECRET for sms vendor"`
|
||||
SIGNATURE string `help:"SIGNATURE for sms vendor"`
|
||||
SmsTemplateOne string `help:"Sms TemplateOne"`
|
||||
SmsTemplateTwo string `help:"Sms TemplateTwo"`
|
||||
SmsTemplateThree string `help:"Sms TemplateThree"`
|
||||
SmsCheckCode string `help:"Sms Check Code "`
|
||||
}
|
||||
|
||||
R(&SmsConfigCreateOptions{}, "sms-config-create", "Create a sms Config",
|
||||
func(s *mcclient.ClientSession, args *SmsConfigCreateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.TYPE), "type")
|
||||
params.Add(jsonutils.NewString(args.ACCESSKEYID), "access_key_id")
|
||||
params.Add(jsonutils.NewString(args.ACCESSKEYSECRET), "access_key_secret")
|
||||
params.Add(jsonutils.NewString(args.SIGNATURE), "signature")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateOne), "sms_template_one")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateTwo), "sms_template_two")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateThree), "sms_template_three")
|
||||
params.Add(jsonutils.NewString(args.SmsCheckCode), "sms_check_code")
|
||||
|
||||
result, err := modules.SmsConfigs.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
type SmsConfigUpdateOptions struct {
|
||||
TYPE string `help:"sms vendor"`
|
||||
ACCESSKEYID string `help:"ACCESSKEYID for sms vendor"`
|
||||
ACCESSKEYSECRET string `help:"ACCESSKEYSECRET for sms vendor"`
|
||||
SIGNATURE string `help:"SIGNATURE for sms vendor"`
|
||||
SmsTemplateOne string `help:"Sms TemplateOne"`
|
||||
SmsTemplateTwo string `help:"Sms TemplateTwo"`
|
||||
SmsTemplateThree string `help:"Sms TemplateThree"`
|
||||
SmsCheckCode string `help:"Sms Check Code "`
|
||||
}
|
||||
R(&SmsConfigUpdateOptions{}, "sms-config-update", "Update a sms-config", func(s *mcclient.ClientSession, args *SmsConfigUpdateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
|
||||
params.Add(jsonutils.NewString(args.TYPE), "type")
|
||||
params.Add(jsonutils.NewString(args.ACCESSKEYID), "access_key_id")
|
||||
params.Add(jsonutils.NewString(args.ACCESSKEYSECRET), "access_key_secret")
|
||||
params.Add(jsonutils.NewString(args.SIGNATURE), "signature")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateOne), "sms_template_one")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateTwo), "sms_template_two")
|
||||
params.Add(jsonutils.NewString(args.SmsTemplateThree), "sms_template_three")
|
||||
params.Add(jsonutils.NewString(args.SmsCheckCode), "sms_check_code")
|
||||
|
||||
result, err := modules.SmsConfigs.Put(s, args.TYPE, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
type SmsConfigDeleteOptions struct {
|
||||
TYPE string `help:"sms vendor"`
|
||||
}
|
||||
R(&SmsConfigDeleteOptions{}, "sms-config-delete", "Delete a sms config", func(s *mcclient.ClientSession, args *SmsConfigDeleteOptions) error {
|
||||
result, e := modules.SmsConfigs.Delete(s, args.TYPE, nil)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
/**
|
||||
* 通信地址验证
|
||||
*/
|
||||
type ContactsVerifyOptions struct {
|
||||
ID string `help:"Verification process ID"`
|
||||
TOKEN string `help:"Temporary token issued to the user when the validation is triggered"`
|
||||
}
|
||||
R(&ContactsVerifyOptions{}, "contact-verify", "Trigger contact verify", func(s *mcclient.ClientSession, args *ContactsVerifyOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.TOKEN), "token")
|
||||
|
||||
result, err := modules.Verifications.Get(s, args.ID, params)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notify
|
||||
package notifyv2
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
123
cmd/climc/shell/notifyv2/config.go
Normal file
123
cmd/climc/shell/notifyv2/config.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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 notifyv2
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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 ConfigListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
R(&ConfigListOptions{}, "notify-config-list", "List notify config", func(s *mcclient.ClientSession, args *ConfigListOptions) error {
|
||||
params, err := options.ListStructToParams(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.NotifyConfig.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.NotifyConfig.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
type ConfigCreateOptions struct {
|
||||
TYPE string `help:"Type contact config"`
|
||||
Configs []string `help:"Config content, format: 'key:value'"`
|
||||
}
|
||||
R(&ConfigCreateOptions{}, "notify-config-create", "Create notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error {
|
||||
configs := jsonutils.NewDict()
|
||||
for _, kv := range args.Configs {
|
||||
index := strings.IndexByte(kv, ':')
|
||||
configs.Set(kv[:index], jsonutils.NewString(kv[index+1:]))
|
||||
}
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("type", jsonutils.NewString(args.TYPE))
|
||||
params.Set("content", configs)
|
||||
ret, err := modules.NotifyConfig.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ConfigCreateOptions{}, "notify-config-update", "Update notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error {
|
||||
configs := jsonutils.NewDict()
|
||||
for _, kv := range args.Configs {
|
||||
index := strings.IndexByte(kv, ':')
|
||||
configs.Set(kv[:index], jsonutils.NewString(kv[index+1:]))
|
||||
}
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("content", configs)
|
||||
|
||||
id, err := configIdFromType(s, args.TYPE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret, err := modules.NotifyConfig.Update(s, id, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type ConfigOptions struct {
|
||||
TYPE string `help:"Type contact config"`
|
||||
}
|
||||
R(&ConfigOptions{}, "notify-config-delete", "Delete notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error {
|
||||
id, err := configIdFromType(s, args.TYPE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret, err := modules.NotifyConfig.Delete(s, id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ConfigOptions{}, "notify-config-show", "Show notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error {
|
||||
listParams := jsonutils.NewDict()
|
||||
listParams.Set("type", jsonutils.NewString(args.TYPE))
|
||||
list, err := modules.NotifyConfig.List(s, listParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := list.Data[0]
|
||||
printObject(data)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func configIdFromType(s *mcclient.ClientSession, t string) (string, error) {
|
||||
listParams := jsonutils.NewDict()
|
||||
listParams.Set("type", jsonutils.NewString(t))
|
||||
list, err := modules.NotifyConfig.List(s, listParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
id, err := list.Data[0].GetString("id")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
78
cmd/climc/shell/notifyv2/notification.go
Normal file
78
cmd/climc/shell/notifyv2/notification.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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 notifyv2
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
type NotificationCreateInput struct {
|
||||
Receivers []string `help:"ID or Name of Receiver"`
|
||||
ContactType string `help:"Contact type of receiver"`
|
||||
TOPIC string `help:"Topic"`
|
||||
Priority string `help:"Priority"`
|
||||
MESSAGE string `help:"Message"`
|
||||
}
|
||||
R(&NotificationCreateInput{}, "notify-send", "Send a notify message", func(s *mcclient.ClientSession, args *NotificationCreateInput) error {
|
||||
input := api.NotificationCreateInput{
|
||||
Receivers: args.Receivers,
|
||||
ContactType: args.ContactType,
|
||||
Topic: args.TOPIC,
|
||||
Priority: args.Priority,
|
||||
Message: args.MESSAGE,
|
||||
}
|
||||
ret, err := modules.Notification.Create(s, jsonutils.Marshal(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type NotificationInput struct {
|
||||
ID string `help:"Id of notification"`
|
||||
}
|
||||
R(&NotificationInput{}, "notify-show", "Show a notify message", func(s *mcclient.ClientSession, args *NotificationInput) error {
|
||||
ret, err := modules.Notification.Get(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type NotificationListInput struct {
|
||||
options.BaseListOptions
|
||||
|
||||
ContactType string `help:"contact_type"`
|
||||
ReceiverId string `help:"receiver_id"`
|
||||
}
|
||||
R(&NotificationListInput{}, "notify-list", "List notify message", func(s *mcclient.ClientSession, args *NotificationListInput) error {
|
||||
params, err := options.ListStructToParams(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret, err := modules.Notification.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(ret, modules.Notification.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
165
cmd/climc/shell/notifyv2/receiver.go
Normal file
165
cmd/climc/shell/notifyv2/receiver.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// 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 notifyv2
|
||||
|
||||
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 ReceiverListOptions struct {
|
||||
options.BaseListOptions
|
||||
UId string `help:"user id in keystone"`
|
||||
UName string `help:"user name in keystone"`
|
||||
EnabledContactType string `help:"enabled contact type"`
|
||||
VerifiedContactType string `help:"verified contact type"`
|
||||
}
|
||||
R(&ReceiverListOptions{}, "notify-receiver-list", "List notify receiver", func(s *mcclient.ClientSession, args *ReceiverListOptions) error {
|
||||
params, err := options.ListStructToParams(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.NotifyReceiver.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.NotifyReceiver.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
type ReceiverCreateOptions struct {
|
||||
UID string `help:"user id in keystone"`
|
||||
Email string `help:"email of receiver"`
|
||||
Mobile string `help:"mobile of receiver"`
|
||||
EnabledContactTypes []string `help:"enabled contact type"`
|
||||
}
|
||||
R(&ReceiverCreateOptions{}, "notify-receiver-create", "Create notify receiver", func(s *mcclient.ClientSession, args *ReceiverCreateOptions) error {
|
||||
params := jsonutils.Marshal(args).(*jsonutils.JSONDict)
|
||||
receiver, err := modules.NotifyReceiver.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(receiver)
|
||||
return nil
|
||||
})
|
||||
type ReceiverUpdateInput struct {
|
||||
ID string `help:"Id or Name of receiver"`
|
||||
Email string `help:"email of receiver"`
|
||||
Mobile string `help:"mobile of receiver"`
|
||||
EnabledContactType []string `help:"enabled contact type"`
|
||||
}
|
||||
R(&ReceiverUpdateInput{}, "notify-receiver-update", "Update notify receiver", func(s *mcclient.ClientSession, args *ReceiverUpdateInput) error {
|
||||
params := jsonutils.NewDict()
|
||||
if len(args.Email) > 0 {
|
||||
params.Set("email", jsonutils.NewString(args.Email))
|
||||
}
|
||||
if len(args.Mobile) > 0 {
|
||||
params.Set("mobile", jsonutils.NewString(args.Mobile))
|
||||
}
|
||||
if len(args.EnabledContactType) > 0 {
|
||||
params.Set("enabled_contact_types", jsonutils.NewStringArray(args.EnabledContactType))
|
||||
}
|
||||
ret, err := modules.NotifyReceiver.Update(s, args.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type ReceiverOptions struct {
|
||||
ID string `help:"Id or Name of receiver"`
|
||||
}
|
||||
R(&ReceiverOptions{}, "notify-receiver-show", "Show notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
ret, err := modules.NotifyReceiver.Get(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ReceiverOptions{}, "notify-receiver-delete", "Delete notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
receiver, err := modules.NotifyReceiver.Delete(s, args.ID, jsonutils.NewDict())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(receiver)
|
||||
return nil
|
||||
})
|
||||
R(&ReceiverOptions{}, "notify-receiver-enable", "Enable notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "enable", jsonutils.NewDict())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ReceiverOptions{}, "notify-receiver-disable", "Disable notify receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "disable", jsonutils.NewDict())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type ReceiverTriggerVerifyInput struct {
|
||||
ID string `help:"Id or Name of receiver"`
|
||||
ContactType string `help:"Contact type to trigger verify" choices:"email|mobile"`
|
||||
}
|
||||
R(&ReceiverTriggerVerifyInput{}, "notify-receiver-trigger-verify", "Trigger verification for receiver about some contact", func(s *mcclient.ClientSession, args *ReceiverTriggerVerifyInput) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("contact_type", jsonutils.NewString(args.ContactType))
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "trigger-verify", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type ReceiverVerifyInput struct {
|
||||
ID string `help:"Id or Name of receiver"`
|
||||
ContactType string `help:"Contact type to trigger verify" choices:"email|mobile"`
|
||||
Token string `help:"Token from verify message sent to you"`
|
||||
}
|
||||
R(&ReceiverVerifyInput{}, "notify-receiver-verify", "Verify receiver about some contact type", func(s *mcclient.ClientSession, args *ReceiverVerifyInput) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("contact_type", jsonutils.NewString(args.ContactType))
|
||||
params.Set("token", jsonutils.NewString(args.Token))
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "verify", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ReceiverOptions{}, "notify-receiver-enable", "Enable receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "enable", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&ReceiverOptions{}, "notify-receiver-disable", "Disable receiver", func(s *mcclient.ClientSession, args *ReceiverOptions) error {
|
||||
ret, err := modules.NotifyReceiver.PerformAction(s, args.ID, "disable", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
106
cmd/climc/shell/notifyv2/template.go
Normal file
106
cmd/climc/shell/notifyv2/template.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 notifyv2
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
type TemplateCreateInput struct {
|
||||
NAME string `help:"Name"`
|
||||
ContactType string `help:"Contact type, specifically, setting it to all means all contact type"`
|
||||
TemplateType string `help:"Template type"`
|
||||
Topic string `help:"Template topic"`
|
||||
Content string `help:"Template content"`
|
||||
Example string `help:"Example for using this template"`
|
||||
}
|
||||
R(&TemplateCreateInput{}, "notify-template-create", "Create notify template", func(s *mcclient.ClientSession, args *TemplateCreateInput) error {
|
||||
input := api.TemplateCreateInput{
|
||||
ContactType: args.ContactType,
|
||||
TemplateType: args.TemplateType,
|
||||
Topic: args.Topic,
|
||||
Content: args.Content,
|
||||
Example: args.Example,
|
||||
}
|
||||
input.Name = args.NAME
|
||||
ret, err := modules.NotifyTemplate.Create(s, jsonutils.Marshal(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type TemplateListInput struct {
|
||||
options.BaseListOptions
|
||||
|
||||
ContactType string `help:"Contact type"`
|
||||
TemplateType string `help:"Template type"`
|
||||
Topic string `help:"Topic"`
|
||||
}
|
||||
R(&TemplateListInput{}, "notify-template-list", "List notify template", func(s *mcclient.ClientSession, args *TemplateListInput) error {
|
||||
params, err := options.ListStructToParams(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list, err := modules.NotifyTemplate.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(list, modules.NotifyTemplate.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
type TemplateInput struct {
|
||||
ID string `help:"id or name of template"`
|
||||
}
|
||||
R(&TemplateInput{}, "notify-template-get", "Get notify template", func(s *mcclient.ClientSession, args *TemplateInput) error {
|
||||
ret, err := modules.NotifyTemplate.Get(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
type TemplateUpdateInput struct {
|
||||
ID string `help:"id or name of template"`
|
||||
Content string `help:"Template content"`
|
||||
Example string `help:"Example for using this template"`
|
||||
}
|
||||
R(&TemplateUpdateInput{}, "notify-template-update", "Update notify template", func(s *mcclient.ClientSession, args *TemplateUpdateInput) error {
|
||||
input := api.TemplateUpdateInput{
|
||||
Content: args.Content,
|
||||
Example: args.Example,
|
||||
}
|
||||
ret, err := modules.NotifyTemplate.Update(s, args.ID, jsonutils.Marshal(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
R(&TemplateInput{}, "notify-template-delete", "Delete notify template", func(s *mcclient.ClientSession, args *TemplateInput) error {
|
||||
ret, err := modules.NotifyTemplate.Delete(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(ret)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -15,9 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/notify"
|
||||
"yunion.io/x/onecloud/pkg/notify/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
notify.StartService()
|
||||
service.StartService()
|
||||
}
|
||||
|
||||
63
pkg/apis/notify/config.go
Normal file
63
pkg/apis/notify/config.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
type ConfigCreateInput struct {
|
||||
apis.StandaloneResourceCreateInput
|
||||
|
||||
// description: config type
|
||||
// required: true
|
||||
// example: feishu
|
||||
Type string `json:"type"`
|
||||
|
||||
// description: config content
|
||||
// required: true
|
||||
// example: {"app_id": "123456", "app_secret": "feishu_nihao"}
|
||||
Content jsonutils.JSONObject `json:"content"`
|
||||
}
|
||||
|
||||
type ConfigDetails struct {
|
||||
apis.StandaloneResourceDetails
|
||||
|
||||
SConfig
|
||||
}
|
||||
|
||||
type ConfigListInput struct {
|
||||
apis.StandaloneResourceListInput
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ConfigValidateInput struct {
|
||||
// description: config type
|
||||
// required: true
|
||||
// example: feishu
|
||||
Type string `json:"type"`
|
||||
|
||||
// description: config content
|
||||
// required: true
|
||||
// example: {"app_id": "123456", "app_secret": "feishu_nihao"}
|
||||
Content jsonutils.JSONObject `json:"content"`
|
||||
}
|
||||
|
||||
type ConfigValidateOutput struct {
|
||||
IsValid bool `json:"is_valid"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
63
pkg/apis/notify/const.go
Normal file
63
pkg/apis/notify/const.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 notify
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
const (
|
||||
SERVICE_TYPE = apis.SERVICE_TYPE_NOTIFY
|
||||
SERVICE_VERSION = ""
|
||||
|
||||
EMAIL = "email"
|
||||
MOBILE = "mobile"
|
||||
DINGTALK = "dingtalk"
|
||||
FEISHU = "feishu"
|
||||
WEBCONSOLE = "webconsole"
|
||||
WORKWX = "workwx"
|
||||
FEISHU_ROBOT = "feishu-robot"
|
||||
DINGTALK_ROBOT = "dingtalk-robot"
|
||||
WORKWX_ROBOT = "workwx-robot"
|
||||
|
||||
ROBOT = "robot"
|
||||
|
||||
RECEIVER_NOTIFICATION_RECEIVED = "received" // Received a task about sending a notification
|
||||
RECEIVER_NOTIFICATION_SENT = "sending" // Nofity module has sent notification, but result unkown
|
||||
RECEIVER_NOTIFICATION_OK = "sent_ok" // Notification was sent successfully
|
||||
RECEIVER_NOTIFICATION_FAIL = "sent_fail" // That sent a notification is failed
|
||||
|
||||
VERIFICATION_SENT = "sent" // Verification was sent
|
||||
VERIFICATION_SENT_FAIL = "sent_fail" // Verification was sent failed
|
||||
VERIFICATION_VERIFIED = "verified" // Verification was verified
|
||||
VERIFICATION_TOKEN_EXPIRED = "Verification code expired"
|
||||
VERIFICATION_TOKEN_INVALID = "Incorrect verification code"
|
||||
|
||||
RECEIVER_STATUS_READY = "ready"
|
||||
RECEIVER_STATUS_PULLING = "pulling"
|
||||
RECEIVER_STATUS_PULL_FAILED = "pull_failed"
|
||||
|
||||
NOTIFICATION_PRIORITY_IMPORTANT = "important"
|
||||
NOTIFICATION_PRIORITY_CRITICAL = "fatal"
|
||||
NOTIFICATION_PRIORITY_NORMAL = "normal"
|
||||
|
||||
NOTIFICATION_STATUS_RECEIVED = "received"
|
||||
NOTIFICATION_STATUS_SENDING = "sending"
|
||||
NOTIFICATION_STATUS_FAILED = "failed"
|
||||
NOTIFICATION_STATUS_OK = "ok"
|
||||
NOTIFICATION_STATUS_PART_OK = "part_ok"
|
||||
|
||||
TEMPLATE_TYPE_TITLE = "title"
|
||||
TEMPLATE_TYPE_CONTENT = "content"
|
||||
TEMPLATE_TYPE_REMOTE = "remote"
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
type ContactDetails struct {
|
||||
apis.ResourceBaseDetails
|
||||
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Details string `json:"details"`
|
||||
}
|
||||
@@ -15,18 +15,62 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
type NotificationDetails struct {
|
||||
apis.ResourceBaseDetails
|
||||
type NotificationCreateInput struct {
|
||||
apis.StatusStandaloneResourceCreateInput
|
||||
|
||||
UserList jsonutils.JSONObject `json:"user_list"`
|
||||
// description: ids or names of receiver
|
||||
// required: false
|
||||
// example: {"adfb720ccdd34c638346ea4fa7a713a8", "zhangsan"}
|
||||
Receivers []string `json:"receivers"`
|
||||
// description: direct contact, admin privileges required
|
||||
// required: false
|
||||
Contacts []string `json:"contacts"`
|
||||
// description: contact type
|
||||
// required: ture
|
||||
// example: email
|
||||
ContactType string `json:"contact_type"`
|
||||
// description: notification topic
|
||||
// required: true
|
||||
// example: IMAGE_ACTIVE
|
||||
Topic string `json:"topic"`
|
||||
// description: notification priority
|
||||
// required: false
|
||||
// enum: fatal,important,nomal
|
||||
// example: normal
|
||||
Priority string `json:"priority"`
|
||||
// description: message content or jsonobject
|
||||
// required: ture
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ReceiveDetail struct {
|
||||
ReceiverId string `json:"receiver_id"`
|
||||
ReceiverName string `json:"receiver_name"`
|
||||
Contact string `json:"contact"`
|
||||
SendAt time.Time `json:"sendAt"`
|
||||
SendBy string `json:"send_by"`
|
||||
Status string `json:"status"`
|
||||
FailedReason string `json:"failed_reason"`
|
||||
}
|
||||
|
||||
type NotificationDetails struct {
|
||||
apis.StatusStandaloneResourceDetails
|
||||
|
||||
SNotification
|
||||
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
ReceiveDetails []ReceiveDetail `json:"receive_details"`
|
||||
}
|
||||
|
||||
type NotificationListInput struct {
|
||||
Scope string `json:"scope"`
|
||||
ContactType string `json:"contact_type"`
|
||||
apis.StatusStandaloneResourceListInput
|
||||
|
||||
ContactType string
|
||||
ReceiverId string
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
const (
|
||||
SERVICE_TYPE = apis.SERVICE_TYPE_NOTIFY
|
||||
SERVICE_VERSION = ""
|
||||
)
|
||||
110
pkg/apis/notify/receiver.go
Normal file
110
pkg/apis/notify/receiver.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
type ReceiverCreateInput struct {
|
||||
apis.StatusStandaloneResourceCreateInput
|
||||
apis.DomainizedResourceCreateInput
|
||||
apis.EnabledBaseResourceCreateInput
|
||||
|
||||
// description: user id in keystone
|
||||
// example: adfb720ccdd34c638346ea4fa7a713a8
|
||||
UID string `json:"uid"`
|
||||
|
||||
// description: user name in keystone
|
||||
// example: hello
|
||||
UName string `json:"uname"`
|
||||
|
||||
// description: user email
|
||||
// example: example@gmail.com
|
||||
Email string `json:"email"`
|
||||
|
||||
// description: user mobile
|
||||
// example: 17812345678
|
||||
Mobile string `json:"mobile"`
|
||||
|
||||
// description: enabled contact types for user
|
||||
// example: {"email", "mobile", "feishu", "dingtalk", "workwx"}
|
||||
EnabledContactTypes []string `json:"enabled_contact_types"`
|
||||
}
|
||||
|
||||
type ReceiverDetails struct {
|
||||
apis.StatusStandaloneResourceDetails
|
||||
apis.DomainizedResourceInfo
|
||||
|
||||
SReceiver
|
||||
|
||||
// description: enabled contact types for user
|
||||
// example: eamil, mobile, feishu, dingtalk, workwx
|
||||
EnabledContactTypes []string `json:"enabled_contact_types"`
|
||||
|
||||
// description: verified contact types for user
|
||||
// example: email, mobile, feishu, dingtalk, workwx
|
||||
VerifiedContactTypes []string `json:"verified_contact_types"`
|
||||
}
|
||||
|
||||
type ReceiverListInput struct {
|
||||
apis.StatusStandaloneResourceListInput
|
||||
apis.DomainizedResourceListInput
|
||||
apis.EnabledResourceBaseListInput
|
||||
|
||||
UID string `json:"uid"`
|
||||
|
||||
UName string `json:"uname"`
|
||||
|
||||
EnabledContactType string `json:"enabled_contact_type"`
|
||||
|
||||
VerifiedContactType string `json:"verified_contact_type"`
|
||||
}
|
||||
|
||||
type ReceiverUpdateInput struct {
|
||||
apis.StatusStandaloneResourceBaseUpdateInput
|
||||
|
||||
// description: user email
|
||||
// example: example@gmail.com
|
||||
Email string `json:"email"`
|
||||
|
||||
// description: user mobile
|
||||
// example: 17812345678
|
||||
Mobile string `json:"mobile"`
|
||||
|
||||
// description: enabled contacts for user
|
||||
// example: {"email", "mobile", "feishu", "dingtalk", "workwx"}
|
||||
EnabledContactTypes []string `json:"enabled_contact_types"`
|
||||
}
|
||||
|
||||
type ReceiverTriggerVerifyInput struct {
|
||||
// description: contact type
|
||||
// required: true
|
||||
// example: email
|
||||
// enum: email,mobile
|
||||
ContactType string `json:"contact_type"`
|
||||
}
|
||||
|
||||
type ReceiverVerifyInput struct {
|
||||
// description: Contact type
|
||||
// required: true
|
||||
// example: email
|
||||
// enum: email,mobile
|
||||
ContactType string `json:"contact_type"`
|
||||
// description: token user input
|
||||
// required: true
|
||||
// example: 123456
|
||||
Token string `json:"token"`
|
||||
}
|
||||
82
pkg/apis/notify/template.go
Normal file
82
pkg/apis/notify/template.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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 notify
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
type TemplateCreateInput struct {
|
||||
apis.StandaloneResourceCreateInput
|
||||
|
||||
// description: Contact type, specifically, setting it to all means all contact type
|
||||
// require: true
|
||||
// example: email
|
||||
ContactType string `json:"contact_type"`
|
||||
// description: Template type
|
||||
// enum: title,content,remote
|
||||
// example: title
|
||||
TemplateType string `json:"template_type"`
|
||||
|
||||
// description: Template topic
|
||||
// required: true
|
||||
// example: IMAGE_ACTIVE
|
||||
Topic string `json:"topic"`
|
||||
|
||||
// description: Template content
|
||||
// required: true
|
||||
// example: 镜像 {{.name}} 上传完成
|
||||
Content string `json:"content"`
|
||||
// description: Example for using this template
|
||||
// required: true
|
||||
// example: {"name": "centos7.6"}
|
||||
Example string `json:"example"`
|
||||
}
|
||||
|
||||
type TemplateListInput struct {
|
||||
apis.StandaloneResourceListInput
|
||||
|
||||
// description: Contact type, specifically, setting it to all means all contact type
|
||||
// require: true
|
||||
// example: email
|
||||
ContactType string `json:"contact_type"`
|
||||
|
||||
// description: Template type
|
||||
// enum: title,content,remote
|
||||
// example: title
|
||||
TemplateType string `json:"template_type"`
|
||||
|
||||
// description: template topic
|
||||
// required: true
|
||||
// example: IMAGE_ACTIVE
|
||||
Topic string `json:"topic"`
|
||||
}
|
||||
|
||||
type TemplateUpdateInput struct {
|
||||
apis.StandaloneResourceCreateInput
|
||||
|
||||
// description: template content
|
||||
// required: true
|
||||
// example: 镜像 {{.name}} 上传完成
|
||||
Content string `json:"content"`
|
||||
// description: all example for using this template
|
||||
// required: true
|
||||
// example: {"name": "centos7.6"}
|
||||
Example string `json:"example"`
|
||||
}
|
||||
|
||||
type TemplateDetails struct {
|
||||
apis.StandaloneResourceDetails
|
||||
|
||||
STemplate
|
||||
}
|
||||
101
pkg/apis/notify/zz_generated.model.go
Normal file
101
pkg/apis/notify/zz_generated.model.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by model-api-gen. DO NOT EDIT.
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
// SConfig is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SConfig.
|
||||
type SConfig struct {
|
||||
apis.SStandaloneResourceBase
|
||||
Type string `json:"type"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
// SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SNotification.
|
||||
type SNotification struct {
|
||||
apis.SStatusStandaloneResourceBase
|
||||
ContactType string `json:"contact_type"`
|
||||
// swagger:ignore
|
||||
Topic string `json:"topic"`
|
||||
Priority string `json:"priority"`
|
||||
// swagger:ignore
|
||||
Message string `json:"message"`
|
||||
ReceivedAt time.Time `json:"received_at"`
|
||||
SendTimes int `json:"send_times"`
|
||||
}
|
||||
|
||||
// SReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SReceiver.
|
||||
type SReceiver struct {
|
||||
apis.SStatusStandaloneResourceBase
|
||||
apis.SDomainizedResourceBase
|
||||
apis.SEnabledResourceBase
|
||||
Email string `json:"email"`
|
||||
Mobile string `json:"mobile"`
|
||||
// swagger:ignore
|
||||
EnabledEmail *bool `json:"enabled_email,omitempty"`
|
||||
// swagger:ignore
|
||||
VerifiedEmail *bool `json:"verified_email,omitempty"`
|
||||
// swagger:ignore
|
||||
EnabledMobile *bool `json:"enabled_mobile,omitempty"`
|
||||
// swagger:ignore
|
||||
VerifiedMobile *bool `json:"verified_mobile,omitempty"`
|
||||
}
|
||||
|
||||
// SReceiverNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SReceiverNotification.
|
||||
type SReceiverNotification struct {
|
||||
apis.SJointResourceBase
|
||||
RecevierID string `json:"recevier_id"`
|
||||
NotificationID string `json:"notification_id"`
|
||||
// ignore if RecevierID is not empty
|
||||
Contact string `json:"contact"`
|
||||
SendBy string `json:"send_by"`
|
||||
Status string `json:"status"`
|
||||
FailedReason string `json:"failed_reason"`
|
||||
}
|
||||
|
||||
// SSubContact is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubContact.
|
||||
type SSubContact struct {
|
||||
apis.SResourceBase
|
||||
// id of receiver user
|
||||
RecevierID string `json:"recevier_id"`
|
||||
Type string `json:"type"`
|
||||
Contact string `json:"contact"`
|
||||
ParentContactType string `json:"parent_contact_type"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Verified *bool `json:"verified,omitempty"`
|
||||
}
|
||||
|
||||
// STemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STemplate.
|
||||
type STemplate struct {
|
||||
apis.SStandaloneResourceBase
|
||||
ContactType string `json:"contact_type"`
|
||||
Topic string `json:"topic"`
|
||||
// title | content | remote
|
||||
TemplateType string `json:"template_type"`
|
||||
Content string `json:"content"`
|
||||
Example string `json:"example"`
|
||||
}
|
||||
|
||||
// SVerification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SVerification.
|
||||
type SVerification struct {
|
||||
apis.SResourceBase
|
||||
ReceiverId string `json:"receiver_id"`
|
||||
ContactType string `json:"contact_type"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
@@ -74,6 +74,14 @@ func NewNotifyManager(keyword, keywordPlural string, columns, adminColumns []str
|
||||
Keyword: keyword, KeywordPlural: keywordPlural}
|
||||
}
|
||||
|
||||
func NewNotifyv2Manager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager {
|
||||
return modulebase.ResourceManager{
|
||||
BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_NOTIFY, "", "v2", columns, adminColumns),
|
||||
Keyword: keyword,
|
||||
KeywordPlural: keywordPlural,
|
||||
}
|
||||
}
|
||||
|
||||
func NewJointComputeManager(keyword, keywordPlural string, columns, adminColumns []string, master, slave modulebase.Manager) modulebase.JointResourceManager {
|
||||
return modulebase.JointResourceManager{
|
||||
ResourceManager: NewComputeManager(keyword, keywordPlural, columns, adminColumns),
|
||||
|
||||
@@ -21,13 +21,43 @@ type ConfigsManager struct {
|
||||
}
|
||||
|
||||
var (
|
||||
NotifyReceiver modulebase.ResourceManager
|
||||
NotifyConfig modulebase.ResourceManager
|
||||
Notification modulebase.ResourceManager
|
||||
NotifyTemplate modulebase.ResourceManager
|
||||
Configs ConfigsManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Configs = ConfigsManager{NewNotifyManager("config", "configs",
|
||||
NotifyReceiver = NewNotifyv2Manager(
|
||||
"receiver",
|
||||
"receivers",
|
||||
[]string{"ID", "Name", "Email", "Mobile", "Enabled_Contact_Types", "Verified_Contact_Types"},
|
||||
[]string{},
|
||||
[]string{})}
|
||||
)
|
||||
register(&NotifyReceiver)
|
||||
|
||||
register(&Configs)
|
||||
NotifyConfig = NewNotifyv2Manager(
|
||||
"notifyconfig",
|
||||
"notifyconfigs",
|
||||
[]string{"Type", "Content"},
|
||||
[]string{},
|
||||
)
|
||||
register(&NotifyConfig)
|
||||
|
||||
Notification = NewNotifyv2Manager(
|
||||
"notification",
|
||||
"notifications",
|
||||
[]string{"Title", "Content", "ContactType", "Priority", "Receiver_Details"},
|
||||
[]string{},
|
||||
)
|
||||
register(&Notification)
|
||||
|
||||
NotifyTemplate = NewNotifyv2Manager(
|
||||
"notifytemplate",
|
||||
"notifytemplates",
|
||||
[]string{"ID", "Name", "Contact_Type", "Topic", "Template_Type", "Content", "Example"},
|
||||
[]string{},
|
||||
)
|
||||
register(&NotifyTemplate)
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
// 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 := "/notifytemplates/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)
|
||||
}
|
||||
@@ -15,9 +15,9 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
@@ -39,26 +39,54 @@ type SNotifyMessage struct {
|
||||
Broadcast bool `json:"broadcast,omitempty"`
|
||||
}
|
||||
|
||||
type SNotifyV2Message struct {
|
||||
ReceiverIds []string `json:"receiver_ids"`
|
||||
ContactType string `json:"contact_type"`
|
||||
Topic string `json:"topic"`
|
||||
Priority string `json:"priority"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type NotificationManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyMessage) error {
|
||||
params := jsonutils.Marshal(&msg)
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(params, manager.Keyword)
|
||||
receiverIds := make([]string, 0, len(msg.Uid))
|
||||
if len(msg.Gid) > 0 {
|
||||
// fetch uid
|
||||
uidSet := sets.NewString()
|
||||
for _, gid := range msg.Gid {
|
||||
users, err := modules.Groups.GetUsers(s, gid, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Groups.GetUsers for group %q", gid)
|
||||
}
|
||||
for i := range users.Data {
|
||||
id, _ := users.Data[i].GetString("id")
|
||||
uidSet.Insert(id)
|
||||
}
|
||||
}
|
||||
for _, uid := range uidSet.UnsortedList() {
|
||||
receiverIds = append(receiverIds, uid)
|
||||
}
|
||||
}
|
||||
receiverIds = append(receiverIds, msg.Uid...)
|
||||
|
||||
path := fmt.Sprintf("/%s?uname=true", manager.ContextPath(nil))
|
||||
_, err := modulebase.Post(manager.ResourceManager, s, path, body, manager.KeywordPlural)
|
||||
v2msg := SNotifyV2Message{
|
||||
ReceiverIds: receiverIds,
|
||||
ContactType: string(msg.ContactType),
|
||||
Topic: msg.Topic,
|
||||
Priority: string(msg.Priority),
|
||||
Message: msg.Msg,
|
||||
}
|
||||
params := jsonutils.Marshal(&v2msg)
|
||||
|
||||
_, err := manager.Create(s, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
Notifications = NotificationManager{
|
||||
modules.NewNotifyManager("notification", "notifications",
|
||||
[]string{"id", "uid", "contact_type", "topic", "priority", "msg", "received_at", "send_by", "status", "create_at", "update_at", "delete_at", "create_by", "update_by", "delete_by", "is_deleted", "broadcast", "remark"},
|
||||
[]string{}),
|
||||
modules.Notification,
|
||||
}
|
||||
|
||||
modules.Register(&Notifications)
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
// TODO: fix this test
|
||||
/*
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
)
|
||||
|
||||
func TestNotificationManager(t *testing.T) {
|
||||
msg := SNotifyMessage{
|
||||
Uid: "testuser",
|
||||
ContactType: []TNotifyChannel{
|
||||
NotifyByEmail, NotifyByWebConsole,
|
||||
},
|
||||
Topic: "test message",
|
||||
Priority: NotifyPriorityNormal,
|
||||
Msg: "This is a test message. Yey!!",
|
||||
Remark: "Yunion",
|
||||
}
|
||||
msgJson := jsonutils.Marshal(msg)
|
||||
t.Logf("msg: %s", msgJson)
|
||||
}*/
|
||||
188
pkg/notify/cache/user_group_cache.go
vendored
188
pkg/notify/cache/user_group_cache.go
vendored
@@ -1,188 +0,0 @@
|
||||
// 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/compare"
|
||||
|
||||
"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/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
type SUserGroupCacheManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
type SUserGroup struct {
|
||||
db.SResourceBase
|
||||
UserId string
|
||||
GroupId string
|
||||
LastCheck time.Time `nullable:"false"`
|
||||
}
|
||||
|
||||
func (ug *SUserGroup) GetModelManager() db.IModelManager {
|
||||
return UserGroupCacheManager
|
||||
}
|
||||
|
||||
var UserGroupCacheManager *SUserGroupCacheManager
|
||||
|
||||
func init() {
|
||||
UserGroupCacheManager = &SUserGroupCacheManager{db.NewResourceBaseManager(
|
||||
SUserGroup{},
|
||||
"user_group_cache_tbl",
|
||||
"usergroup",
|
||||
"usergroups",
|
||||
)}
|
||||
}
|
||||
|
||||
func (ug *SUserGroup) IsExpired() bool {
|
||||
if ug.LastCheck.IsZero() {
|
||||
return true
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if ug.LastCheck.Add(consts.GetTenantCacheExpireSeconds()).Before(now) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (manager *SUserGroupCacheManager) FetchByGroupId(ctx context.Context, groupId string) ([]SUserGroup, error) {
|
||||
q := manager.Query().Equals("gourp_id", groupId)
|
||||
ugs := make([]SUserGroup, 0)
|
||||
err := db.FetchModelObjects(manager, q, &ugs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var needSync bool
|
||||
if len(ugs) == 0 {
|
||||
needSync = true
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
expireTime := now.Add(-consts.GetTenantCacheExpireSeconds())
|
||||
for i := range ugs {
|
||||
if ugs[i].LastCheck.Before(expireTime) {
|
||||
needSync = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needSync {
|
||||
return ugs, nil
|
||||
}
|
||||
ugs, syncResult, err := manager.Sync(ctx, ugs, groupId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if syncResult.IsError() {
|
||||
log.Errorf(syncResult.Result())
|
||||
}
|
||||
return ugs, nil
|
||||
}
|
||||
|
||||
func (manager *SUserGroupCacheManager) Sync(ctx context.Context, ugCache []SUserGroup, groupId string) ([]SUserGroup,
|
||||
compare.SyncResult, error) {
|
||||
lockman.LockRawObject(ctx, manager.KeywordPlural(), groupId)
|
||||
defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), groupId)
|
||||
|
||||
syncResult := compare.SyncResult{}
|
||||
|
||||
// It's to query all groups and their users.
|
||||
query := jsonutils.NewDict()
|
||||
query.Set("scope", jsonutils.NewString("system"))
|
||||
query.Set("system", jsonutils.JSONTrue)
|
||||
|
||||
s := auth.GetAdminSession(ctx, consts.GetRegion(), "v3")
|
||||
users, err := modules.Groups.GetUsers(s, groupId, query)
|
||||
if err != nil {
|
||||
return nil, syncResult, errors.Wrap(err, "fetch users by group id from keystone failed")
|
||||
}
|
||||
newUgCache := make([]SUserGroup, len(users.Data))
|
||||
for i := range users.Data {
|
||||
userId, _ := users.Data[i].GetString("id")
|
||||
newUgCache[i] = SUserGroup{
|
||||
UserId: userId,
|
||||
GroupId: groupId,
|
||||
}
|
||||
}
|
||||
added := make([]SUserGroup, 0)
|
||||
removed := make([]SUserGroup, 0)
|
||||
commondb := make([]SUserGroup, 0)
|
||||
compareSets(ugCache, newUgCache, &added, &removed, &commondb)
|
||||
now := time.Now().UTC()
|
||||
for i := range added {
|
||||
added[i].LastCheck = now
|
||||
err := manager.TableSpec().Insert(ctx, &added[i])
|
||||
if err != nil {
|
||||
syncResult.AddError(err)
|
||||
} else {
|
||||
syncResult.Add()
|
||||
}
|
||||
}
|
||||
|
||||
for i := range commondb {
|
||||
ug := &commondb[i]
|
||||
_, err := db.Update(ug, func() error {
|
||||
ug.LastCheck = now
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
syncResult.UpdateError(err)
|
||||
} else {
|
||||
syncResult.Update()
|
||||
}
|
||||
}
|
||||
|
||||
for i := range removed {
|
||||
ug := &removed[i]
|
||||
_, err := db.Update(ug, func() error {
|
||||
return ug.MarkDelete()
|
||||
})
|
||||
if err != nil {
|
||||
syncResult.DeleteError(err)
|
||||
} else {
|
||||
syncResult.Delete()
|
||||
}
|
||||
}
|
||||
|
||||
return newUgCache, syncResult, nil
|
||||
}
|
||||
|
||||
func compareSets(dbs, remotes []SUserGroup, added, removed, commondb *[]SUserGroup) {
|
||||
dbmap := make(map[string]SUserGroup)
|
||||
for i := range dbs {
|
||||
dbmap[dbs[i].UserId] = dbs[i]
|
||||
}
|
||||
|
||||
for i := range remotes {
|
||||
userId := remotes[i].UserId
|
||||
if _, ok := dbmap[userId]; ok {
|
||||
*commondb = append(*commondb, remotes[i])
|
||||
} else {
|
||||
*added = append(*added, remotes[i])
|
||||
}
|
||||
delete(dbmap, userId)
|
||||
}
|
||||
for _, v := range dbmap {
|
||||
*removed = append(*removed, v)
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
const (
|
||||
EMAIL_KEYWORDPLURAL = "email_configs"
|
||||
EMAIL_KEYWORD = "email_config"
|
||||
EMAIL = "email"
|
||||
|
||||
SMS_KEYWORDPLURAL = "sms_configs"
|
||||
SMS_KEYWORD = "sms_config"
|
||||
SMS = "mobile"
|
||||
)
|
||||
|
||||
func emailConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// do not need modify
|
||||
manager, _, _, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": EMAIL,
|
||||
}
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func emailConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, query, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": EMAIL,
|
||||
}
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
data, _ := ret.Get("config")
|
||||
dataDict := data.(*jsonutils.JSONDict)
|
||||
output := jsonutils.NewDict()
|
||||
output.Add(dataDict, EMAIL_KEYWORD)
|
||||
appsrv.SendJSON(w, output)
|
||||
}
|
||||
|
||||
func emailConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
body, _ = body.Get(EMAIL_KEYWORD)
|
||||
bodyRet := jsonutils.DeepCopy(body)
|
||||
newBody := jsonutils.NewDict()
|
||||
newBody.Add(body, EMAIL)
|
||||
err := manager.UpdateConfig(ctx, newBody)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
data := jsonutils.NewDict()
|
||||
data.Add(jsonutils.NewInt(200), "code")
|
||||
data.Add(jsonutils.NewString("OK"), "title")
|
||||
data.Add(bodyRet, "message")
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, EMAIL_KEYWORD)
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
func smsConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// do not need modify
|
||||
manager, _, _, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": SMS,
|
||||
}
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func smsConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, query, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": SMS,
|
||||
}
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
// modify
|
||||
data, _ := ret.Get("config")
|
||||
appsrv.SendJSON(w, jsonutils.Marshal(map[string]jsonutils.JSONObject{
|
||||
SMS_KEYWORD: data,
|
||||
}))
|
||||
}
|
||||
|
||||
func smsConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
body, _ = body.Get(SMS_KEYWORD)
|
||||
newBody := jsonutils.NewDict()
|
||||
newBody.Add(body, SMS)
|
||||
err := manager.UpdateConfig(ctx, newBody)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
data := jsonutils.NewDict()
|
||||
data.Add(jsonutils.NewInt(200), "code")
|
||||
data.Add(jsonutils.NewString("OK"), "title")
|
||||
data.Add(body, "message")
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, SMS_KEYWORD)
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appctx"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
noutils "yunion.io/x/onecloud/pkg/notify/utils"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
|
||||
// NotifyModelDispatcher is designed to complete some function that db.DBModelDispatcher can't.
|
||||
// The apis of notify module has a certain degree of particularity so that we can't use common function.
|
||||
type NotifyModelDispatcher struct {
|
||||
db.DBModelDispatcher
|
||||
}
|
||||
|
||||
func NewNotifyModelDispatcher(manager db.IModelManager) *NotifyModelDispatcher {
|
||||
return &NotifyModelDispatcher{*db.NewModelHandler(manager)}
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) GetConfig(ctx context.Context, params map[string]string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
listResult, err := self.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configs := jsonutils.NewDict()
|
||||
for _, data := range listResult.Data {
|
||||
key, _ := data.GetString("key_text")
|
||||
value, _ := data.Get("value_text")
|
||||
configs.Add(value, key)
|
||||
}
|
||||
cType, ok := params["<type>"]
|
||||
if ok {
|
||||
configs = models.ConfigManager.Database2Display(cType, configs)
|
||||
}
|
||||
output := jsonutils.NewDict()
|
||||
output.Add(configs, models.ConfigManager.Keyword())
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) DeleteConfig(ctx context.Context, params map[string]string) error {
|
||||
contactType := params["<type>"]
|
||||
configs, err := models.ConfigManager.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Get Config by contactType failed")
|
||||
}
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
for i := range configs {
|
||||
err = DeleteItem(&configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Delete part of old one, so please input new data again.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateConfig update config and restart corresponding send service.
|
||||
func (self *NotifyModelDispatcher) UpdateConfig(ctx context.Context, body jsonutils.JSONObject) error {
|
||||
data := body.(*jsonutils.JSONDict)
|
||||
contactType := data.SortedKeys()[0]
|
||||
originData, err := models.ConfigManager.GetConfig(contactType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, _ := data.Get(contactType)
|
||||
data = tmp.(*jsonutils.JSONDict)
|
||||
data = models.ConfigManager.Display2Database(contactType, data)
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
// If no config of type 'contactType' in database, create news.
|
||||
// Else delete original ones and create news.
|
||||
if len(originData) != 0 {
|
||||
// delete original
|
||||
configs, err := models.ConfigManager.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Get Config by contactType failed")
|
||||
}
|
||||
for i := range configs {
|
||||
err = DeleteItem(&configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Delete part of old one, so please input new data again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("update body: %s", data)
|
||||
keys := data.SortedKeys()
|
||||
config := make(map[string]string)
|
||||
createDataList := make([]jsonutils.JSONObject, 0, len(keys))
|
||||
|
||||
// Extract data
|
||||
for _, key := range keys {
|
||||
createData := jsonutils.NewDict()
|
||||
tmp, _ = data.Get(key)
|
||||
createData.Add(tmp, "value_text")
|
||||
createData.Add(jsonutils.NewString(key), "key_text")
|
||||
createData.Add(jsonutils.NewString(contactType), "type")
|
||||
createDataList = append(createDataList, createData)
|
||||
value, _ := tmp.GetString()
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
// validate configs
|
||||
log.Debugf("config: %#v", config)
|
||||
isValid, message, err := models.NotifyService.ValidateConfig(ctx, contactType, config)
|
||||
if err != nil {
|
||||
if errors.Cause(err) != errors.ErrNotImplemented {
|
||||
return httperrors.NewInternalServerError("Validate Config error: %s", err.Error())
|
||||
}
|
||||
isValid = true
|
||||
}
|
||||
if !isValid {
|
||||
return httperrors.NewInputParameterError("validate failed: %s", message)
|
||||
}
|
||||
|
||||
// create
|
||||
for _, createData := range createDataList {
|
||||
_, err := self.Create(ctx, jsonutils.NewDict(), createData, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Create config %s for contact type %s failed", createData.String(), contactType)
|
||||
}
|
||||
}
|
||||
|
||||
// update config
|
||||
models.RestartService(config, contactType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) ValidateConfig(ctx context.Context, contactType string, body jsonutils.JSONObject) error {
|
||||
dict, ok := body.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return httperrors.NewInputParameterError("")
|
||||
}
|
||||
dict = models.ConfigManager.Display2Database(contactType, dict)
|
||||
configs := make(map[string]string)
|
||||
for _, key := range dict.SortedKeys() {
|
||||
value, err := dict.GetString(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "jsonutils.JsonDict.GetString")
|
||||
}
|
||||
configs[key] = value
|
||||
}
|
||||
isValid, message, err := models.NotifyService.ValidateConfig(ctx, contactType, configs)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotImplemented {
|
||||
return httperrors.NewNotImplementedError("validating config of %s", contactType)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if isValid == false {
|
||||
return httperrors.NewInputParameterError(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNotification create new notifications and send them through rpc.RpcService.
|
||||
// If data contains 'gid' field, that means that send message to all users in group.
|
||||
// Else send messager to user whose uid equals 'uid' in data.
|
||||
func (self *NotifyModelDispatcher) CreateNotification(ctx context.Context, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
// Get all contacts info of group if data contains "gid".
|
||||
// If no contact, return ErrContactNotFound.
|
||||
contactType, _ := data.GetString("contact_type")
|
||||
group := false
|
||||
var ids []string
|
||||
if data.Contains("gid") {
|
||||
group = true
|
||||
ids = self.getIds(data, "gid")
|
||||
} else {
|
||||
ids = self.getIds(data, "uid")
|
||||
}
|
||||
contacts, err := models.ContactManager.GetAllNotify(ctx, ids, contactType, group)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(errors.Wrap(err, "get all contacts error"))
|
||||
}
|
||||
|
||||
notificationIDs, err := models.NotificationManager.BatchCreate(ctx, data, contacts)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(jsonutils.NewStringArray(notificationIDs), "notifications")
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) getIds(data jsonutils.JSONObject, key string) []string {
|
||||
var ids []string
|
||||
tmpIds, err := data.GetArray(key)
|
||||
if err != nil {
|
||||
id, _ := data.GetString(key)
|
||||
ids = make([]string, 1)
|
||||
ids[0] = id
|
||||
} else {
|
||||
ids = noutils.JsonArrayToStringArray(tmpIds)
|
||||
}
|
||||
// remove entry in which empty content
|
||||
ret := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if len(id) == 0 {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, id)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Verify process:
|
||||
// 1.fetch verify by ID; 2.check that if verify is expired;
|
||||
// 3.if not check that if token is correct and update status of contact whose id is verify's CID
|
||||
// 4.otherwise generate a new verify and delete old one
|
||||
func (self *NotifyModelDispatcher) Verify(ctx context.Context, params map[string]string, query jsonutils.JSONObject) error {
|
||||
processID := params["<id>"]
|
||||
token, _ := query.GetString("token")
|
||||
manager := models.VerifyManager
|
||||
verifys, err := manager.FetchByID(processID)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
var verifition models.SVerify
|
||||
if len(verifys) == 0 {
|
||||
return httperrors.NewNotFoundError("%s verify record not found", processID)
|
||||
}
|
||||
current, have := time.Now(), false
|
||||
for i := range verifys {
|
||||
if current.Before(verifys[i].ExpireAt) {
|
||||
verifition = verifys[i]
|
||||
have = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_EXPIRED)
|
||||
}
|
||||
if verifition.Token != token {
|
||||
return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_INVALID)
|
||||
}
|
||||
// modify contact's status and verified time.
|
||||
data := jsonutils.NewDict()
|
||||
data.Set("status", jsonutils.NewString(models.VERIFICATION_VERIFIED))
|
||||
data.Set("verified_at", jsonutils.NewTimeString(current))
|
||||
_, err = self.Update(ctx, verifition.CID, jsonutils.NewDict(), data, nil)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyTrigger process:
|
||||
// 1.fetch contact by the information in data
|
||||
// 2.if contact'status is 'init', make a new verify and send a verify message to the contact adress
|
||||
// 3.if contact'status is 'verifying', fetch verify by CID, generate a new verify if it has expired
|
||||
// or return a error mention that "please don't try again".
|
||||
func (self *NotifyModelDispatcher) VerifyTrigger(ctx context.Context, params map[string]string, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
uid := params["<uid>"]
|
||||
contact, _ := data.GetString("contact")
|
||||
contactType, _ := data.GetString("contact_type")
|
||||
contacts, err := models.ContactManager.FetchByMore(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]
|
||||
|
||||
makeNewVerify := func() (jsonutils.JSONObject, error) {
|
||||
verification := models.NewSVerify(contactType, scontact.ID)
|
||||
err = models.VerifyManager.Create(ctx, userCred, verification)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
// update contact state
|
||||
if scontact.Status != models.CONTACT_VERIFYING {
|
||||
scontact.SetStatus(userCred, models.CONTACT_VERIFYING, "")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
processID := verification.ID
|
||||
err := models.SendVerifyMessage(ctx, userCred, verification, &scontact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := map[string]map[string]string{
|
||||
"contact": {
|
||||
"process_id": processID,
|
||||
},
|
||||
}
|
||||
return jsonutils.Marshal(ret), nil
|
||||
}
|
||||
if scontact.Status == models.CONTACT_INIT {
|
||||
return makeNewVerify()
|
||||
}
|
||||
if scontact.Status == models.CONTACT_VERIFYING {
|
||||
verifications, err := models.VerifyManager.FetchByCID(scontact.ID, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
|
||||
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) {
|
||||
//delete old one
|
||||
err = DeleteItem(&verification, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
return makeNewVerify()
|
||||
}
|
||||
}
|
||||
return nil, httperrors.NewGeneralError(models.ErrVeritying)
|
||||
}
|
||||
|
||||
return jsonutils.JSONNull, nil
|
||||
}
|
||||
|
||||
// DeleteContacts delete a group of contacts
|
||||
func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uidArray []jsonutils.JSONObject) error {
|
||||
// Get all id of uid
|
||||
uids := make([]string, len(uidArray))
|
||||
log.Debugf("uidArray: %s", uidArray)
|
||||
for i := range uidArray {
|
||||
uids[i], _ = uidArray[i].GetString()
|
||||
}
|
||||
log.Debugf("uids: %#v", uids)
|
||||
uname := false
|
||||
if v := ctx.Value("uname"); v != nil {
|
||||
uname = true
|
||||
}
|
||||
contacts, err := models.ContactManager.FetchByUIDs(ctx, uids, uname)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
deleteFailed := make([]string, 0, 1)
|
||||
for _, contact := range contacts {
|
||||
err = DeleteItem(&contact, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
deleteFailed = append(deleteFailed, contact.ID)
|
||||
}
|
||||
}
|
||||
// clean cache
|
||||
noutils.DeleteUsers(ctx, userCred, uids)
|
||||
if len(deleteFailed) != 0 {
|
||||
errInfo := strings.Join(deleteFailed, ", ") + " ; these contact delete failed."
|
||||
return errors.Error(errInfo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContacts analysis the data and update corresponding contacts if they exist in the database create new ones.
|
||||
func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr string, query jsonutils.JSONObject,
|
||||
datas []jsonutils.JSONObject, pullCtypes []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) (jsonutils.JSONObject,
|
||||
error) {
|
||||
|
||||
type pair struct {
|
||||
contact string
|
||||
enabled string
|
||||
}
|
||||
|
||||
// contactInfos will be used to find all contact info need to update.
|
||||
// And others will be created.
|
||||
contactInfos := make(map[string]pair)
|
||||
contactTypes := make([]string, len(datas))
|
||||
for i := range datas {
|
||||
contactType, _ := datas[i].GetString("contact_type")
|
||||
if _, ok := models.UpdateNotAllow[contactType]; ok {
|
||||
continue
|
||||
}
|
||||
contact, _ := datas[i].GetString("contact")
|
||||
enabled := "-1"
|
||||
if datas[i].Contains("enabled") {
|
||||
enabled, _ = datas[i].GetString("enabled")
|
||||
}
|
||||
contactInfos[contactType] = pair{contact, enabled}
|
||||
contactTypes[i] = contactType
|
||||
}
|
||||
|
||||
records, err := models.ContactManager.FetchByUIDAndCType(idstr, contactTypes)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
// updateFailed record the information of failed update
|
||||
updateFailed := make([]string, 0, 1)
|
||||
deleteFailed := make([]string, 0, 1)
|
||||
// UpdateItem contact info
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
for i := range records {
|
||||
contactType := records[i].ContactType
|
||||
pairUpdate := contactInfos[contactType]
|
||||
if len(pairUpdate.contact) == 0 {
|
||||
// delete
|
||||
err = DeleteItem(&records[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
deleteFailed = append(deleteFailed, fmt.Sprintf(`uid:%q, contact_type:%q`, idstr, contactType))
|
||||
}
|
||||
continue
|
||||
}
|
||||
updateData := jsonutils.NewDict()
|
||||
updateData.Set("contact", jsonutils.NewString(pairUpdate.contact))
|
||||
if pairUpdate.enabled != "-1" {
|
||||
updateData.Set("enabled", jsonutils.NewString(pairUpdate.enabled))
|
||||
}
|
||||
if records[i].Contact != pairUpdate.contact {
|
||||
updateData.Set("status", jsonutils.NewString(models.CONTACT_INIT))
|
||||
}
|
||||
// update is not relational
|
||||
//updateData.Set("status", jsonutils.NewString("init"))
|
||||
err = UpdateItem(models.ContactManager, &records[i], ctx, userCred, jsonutils.JSONNull, updateData)
|
||||
if err != nil {
|
||||
updateFailed = append(updateFailed, fmt.Sprintf(`uid:%q, contact_type:%q, contact:%q`, idstr, contactType, pairUpdate.contact))
|
||||
}
|
||||
delete(contactInfos, contactType)
|
||||
}
|
||||
|
||||
// createFailed record the information of failed creation
|
||||
createFailed := make([]string, 0, 1)
|
||||
// Create contact info
|
||||
type CreateData struct {
|
||||
UID string
|
||||
ContactType string
|
||||
Contact string
|
||||
Enabled string
|
||||
}
|
||||
newDatas := make([]CreateData, 0, len(contactInfos))
|
||||
for conType, conPair := range contactInfos {
|
||||
tmp := CreateData{
|
||||
UID: idstr,
|
||||
ContactType: conType,
|
||||
Contact: conPair.contact,
|
||||
}
|
||||
if conPair.enabled != "-1" {
|
||||
tmp.Enabled = conPair.enabled
|
||||
}
|
||||
newDatas = append(newDatas, tmp)
|
||||
}
|
||||
|
||||
// Enable closing feishu and dingtalk notify.
|
||||
// Temporary solution for 3.2.
|
||||
allPullType := sets.NewString(models.FEISHU, models.DINGTALK)
|
||||
|
||||
pulls := make([]string, len(pullCtypes))
|
||||
for i := range pullCtypes {
|
||||
pulls[i], _ = pullCtypes[i].GetString()
|
||||
allPullType.Delete(pulls[i])
|
||||
}
|
||||
|
||||
// delete first
|
||||
records, err = models.ContactManager.FetchByUIDAndCType(idstr, allPullType.UnsortedList())
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
for i := range records {
|
||||
err := DeleteItem(&records[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
deleteFailed = append(deleteFailed, fmt.Sprintf(`uid:%q, contact_type:%q`, idstr, records[i].ContactType))
|
||||
}
|
||||
}
|
||||
|
||||
if len(pulls) > 0 {
|
||||
contacts, err := models.ContactManager.FetchByUIDAndCType(idstr, pulls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set := sets.NewString(pulls...)
|
||||
for i := range contacts {
|
||||
set.Delete(contacts[i].ContactType)
|
||||
}
|
||||
for _, ct := range set.UnsortedList() {
|
||||
tmp := CreateData{
|
||||
UID: idstr,
|
||||
ContactType: ct,
|
||||
Enabled: "1",
|
||||
Contact: "user_id",
|
||||
}
|
||||
newDatas = append(newDatas, tmp)
|
||||
}
|
||||
}
|
||||
log.Debugf("newDatas: %s", newDatas)
|
||||
|
||||
for _, newData := range newDatas {
|
||||
_, err := self.Create(ctx, jsonutils.NewDict(), jsonutils.Marshal(newData), ctxIds)
|
||||
if err != nil {
|
||||
createFailed = append(createFailed, fmt.Sprintf(`uid:%q, contact_type:%q, contact:%q`, idstr,
|
||||
newData.ContactType, newData.Contact))
|
||||
}
|
||||
}
|
||||
|
||||
// generate error through updateFailed and createFailed
|
||||
if len(updateFailed) != 0 || len(createFailed) != 0 || len(deleteFailed) != 0 {
|
||||
var errInfoBuffer strings.Builder
|
||||
if len(updateFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(updateFailed, "; "))
|
||||
errInfoBuffer.WriteString(" update failed. ")
|
||||
}
|
||||
if len(deleteFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(updateFailed, "; "))
|
||||
errInfoBuffer.WriteString(" delete failed. ")
|
||||
}
|
||||
if len(createFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(createFailed, "; "))
|
||||
errInfoBuffer.WriteString(" create failed. ")
|
||||
}
|
||||
errInfo := errInfoBuffer.String()
|
||||
return nil, httperrors.NewGeneralError(errors.Error(errInfo))
|
||||
}
|
||||
|
||||
models.PullContact(idstr, pulls)
|
||||
|
||||
// keep the return value same as this of the GET interface
|
||||
ret := jsonutils.NewDict()
|
||||
contacts, err := models.ContactManager.FetchByUIDs(ctx, []string{idstr}, false)
|
||||
if err != nil {
|
||||
log.Errorf("fetch contact %s: %v", idstr, err)
|
||||
return ret, nil
|
||||
}
|
||||
if len(contacts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
contact := contacts[0]
|
||||
outDetails, err := contact.GetExtraDetails(ctx, userCred, ret, false)
|
||||
if err != nil {
|
||||
log.Errorf("fetch contact details %s(%s): %v",
|
||||
contact.GetName(), contact.GetId(), err)
|
||||
return ret, nil
|
||||
}
|
||||
out := jsonutils.Marshal(outDetails)
|
||||
out.(*jsonutils.JSONDict).Set("created_at", jsonutils.NewString(contact.CreatedAt.String()))
|
||||
return out, 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.TemplateManager, 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)
|
||||
metadata := appctx.AppContextMetadata(ctx)
|
||||
manager, ok := metadata["manager"].(*NotifyModelDispatcher)
|
||||
if !ok {
|
||||
log.Fatalf("No manager found for URL: %s", r.URL)
|
||||
}
|
||||
return manager, params, query, body
|
||||
}
|
||||
|
||||
func mergeQueryParams(params map[string]string, query jsonutils.JSONObject, excludes ...string) jsonutils.JSONObject {
|
||||
if query == nil {
|
||||
query = jsonutils.NewDict()
|
||||
}
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
for k, v := range params {
|
||||
if !utils.IsInStringArray(k, excludes) {
|
||||
queryDict.Add(jsonutils.NewString(v), k[1:len(k)-1])
|
||||
}
|
||||
}
|
||||
return queryDict
|
||||
}
|
||||
|
||||
// DeleteItem delete a database record corresponding to model
|
||||
func DeleteItem(model db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
lockman.LockObject(ctx, model)
|
||||
defer lockman.ReleaseObject(ctx, model)
|
||||
err := model.ValidateDeleteCondition(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("validate delete condition error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = db.CustomizeDelete(model, ctx, userCred, query, data)
|
||||
if err != nil {
|
||||
log.Errorf("customize delete error: %s", err)
|
||||
return httperrors.NewNotAcceptableError("%v", err)
|
||||
}
|
||||
model.PreDelete(ctx, userCred)
|
||||
err = model.Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
log.Errorf("Delete error %s", err)
|
||||
return err
|
||||
}
|
||||
model.PostDelete(ctx, userCred)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateItem update a database record corresponding to model whose update fields are in data
|
||||
func UpdateItem(manager db.IModelManager, item db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
lockman.LockObject(ctx, item)
|
||||
defer lockman.ReleaseObject(ctx, item)
|
||||
var err error
|
||||
err = item.ValidateUpdateCondition(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("validate update condition error: %s", err)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
dataDict, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return httperrors.NewInternalServerError("Invalid data JSONObject")
|
||||
}
|
||||
|
||||
dataDict, err = db.ValidateUpdateData(item, ctx, userCred, query, dataDict)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("validate update data error: %s", err)
|
||||
log.Errorf(errMsg)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
item.PreUpdate(ctx, userCred, query, dataDict)
|
||||
|
||||
diff, err := db.Update(item, func() error {
|
||||
filterData := dataDict.CopyIncludes(updateFields(manager, userCred)...)
|
||||
err = filterData.Unmarshal(item)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("unmarshal fail: %s", err)
|
||||
log.Errorf(errMsg)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("save update error: %s", err)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
db.OpsLog.LogEvent(item, db.ACT_UPDATE, diff, userCred)
|
||||
|
||||
item.PostUpdate(ctx, userCred, query, data)
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// get the field of model which is d
|
||||
func updateFields(manager db.IModelManager, userCred mcclient.TokenCredential) []string {
|
||||
ret := make([]string, 0)
|
||||
for _, col := range manager.TableSpec().Columns() {
|
||||
tags := col.Tags()
|
||||
update := tags["update"]
|
||||
if allowAction(manager, userCred, update, db.IsAllowUpdate) {
|
||||
ret = append(ret, col.Name())
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func allowAction(manager db.IResource, userCred mcclient.TokenCredential, action string, testfunc func(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, manager db.IResource) bool) bool {
|
||||
if action == "user" {
|
||||
return true
|
||||
}
|
||||
if action == "domain" && (testfunc(rbacutils.ScopeDomain, userCred, manager) || testfunc(rbacutils.ScopeSystem, userCred, manager)) {
|
||||
return true
|
||||
}
|
||||
if action == "admin" && testfunc(rbacutils.ScopeSystem, userCred, manager) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,450 +0,0 @@
|
||||
// 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 notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/notify/cache"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
var API_VERSION = "api/v1"
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
// add version handler with API_VERSION prefix
|
||||
app.AddDefaultHandler("GET", API_VERSION+"/version", appsrv.VersionHandler, "version")
|
||||
db.AddScopeResourceCountHandler(API_VERSION, app)
|
||||
|
||||
db.RegisterModelManager(models.ContactManager)
|
||||
db.RegisterModelManager(models.VerifyManager)
|
||||
db.RegisterModelManager(models.NotificationManager)
|
||||
db.RegisterModelManager(models.ConfigManager)
|
||||
db.RegisterModelManager(cache.UserCacheManager)
|
||||
db.RegisterModelManager(cache.UserGroupCacheManager)
|
||||
db.RegisterModelManager(models.TemplateManager)
|
||||
AddNotifyDispatcher(API_VERSION, 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
|
||||
log.Debugf("url.Query has a uname")
|
||||
params := appctx.AppContextParams(ctx)
|
||||
if uid, ok := params["<uid>"]; ok {
|
||||
userDetail, err := utils.GetUserByIDOrName(ctx, uid)
|
||||
if err != nil {
|
||||
httperrors.NotFoundError(ctx, w, "Uid or Uname Not Found")
|
||||
return
|
||||
}
|
||||
log.Debugf("find userDetail, id: %s, name: %s", userDetail.Id, userDetail.Name)
|
||||
params["<uid>"] = 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
|
||||
|
||||
// Contact Handler
|
||||
modelDispatcher := NewNotifyModelDispatcher(models.ContactManager)
|
||||
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<uid>/update-contact", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(contactUpdateHandler), metadata, "contact_update", tags)
|
||||
// List
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(listHandler), metadata, "list_contacts", tags)
|
||||
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/users", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(keyStoneUserListHandler), metadata, "list_users", tags)
|
||||
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<uid>", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(getHandler), metadata, "list_by_uid", tags)
|
||||
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/delete-contact", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(deleteContactHandler), metadata, "delete", tags)
|
||||
|
||||
// verify-trigger
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<uid>/verify", prefix, modelDispatcher.KeywordPlural()),
|
||||
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/<id>", prefix, models.VerifyManager.KeywordPlural()),
|
||||
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()),
|
||||
middleware(notificationHandler), metadata, "send_notifications", tags)
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(listHandler), metadata, "send_notifications", tags)
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<id>", prefix, modelDispatcher.KeywordPlural()),
|
||||
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()),
|
||||
middleware(configUpdateHandler), metadata, "update_configs", tags)
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(configGetHandler), metadata, "get_configs", tags)
|
||||
app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(configDeleteHandler), metadata, "delete_configs", tags)
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<type>/validate", prefix, modelDispatcher.KeywordPlural()),
|
||||
middleware(configValidateHandler), metadata, "validate_configs", tags)
|
||||
|
||||
// email handler for being compatible
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
|
||||
middleware(emailConfigUpdateHandler), metadata, "", tags)
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
middleware(emailConfigGetHandler), metadata, "", tags)
|
||||
app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
middleware(emailConfigDeleteHandler), metadata, "", tags)
|
||||
app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
middleware(emailConfigUpdateHandler), metadata, "", tags)
|
||||
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
|
||||
middleware(smsConfigUpdateHandler), metadata, "", tags)
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
middleware(smsConfigGetHandler), metadata, "", tags)
|
||||
app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
middleware(smsConfigDeleteHandler), metadata, "", tags)
|
||||
app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
middleware(smsConfigUpdateHandler), metadata, "", tags)
|
||||
|
||||
// Template 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/<ctype>/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)
|
||||
}
|
||||
|
||||
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(ctx, w, httperrors.NewInputParameterError("need %s and %s",
|
||||
manager.Keyword(), manager.KeywordPlural()))
|
||||
return
|
||||
}
|
||||
ctype := params["<ctype>"]
|
||||
if len(ctype) == 0 {
|
||||
httperrors.InputParameterError(ctx, w, "ctype of template should not be empty")
|
||||
}
|
||||
err = manager.UpdateTemplate(ctx, ctype, mergeQueryParams(params, query), data)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, 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(ctx, 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(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func configDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, _, _ := fetchEnv(ctx, w, r)
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func configGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
func configUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
body, err := body.Get(models.ConfigManager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need config or configs"))
|
||||
return
|
||||
}
|
||||
err = manager.UpdateConfig(ctx, body)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func configValidateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, _, body := fetchEnv(ctx, w, r)
|
||||
body, err := body.Get(models.ConfigManager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need config"))
|
||||
}
|
||||
ctype := params["<type>"]
|
||||
err = manager.ValidateConfig(ctx, ctype, body)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func notificationHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
data, err := body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(ctx, w, "request body should contain %s", manager.Keyword())
|
||||
return
|
||||
}
|
||||
if !data.Contains("gid") && !data.Contains("uid") {
|
||||
httperrors.MissingParameterError(ctx, w, "gid | uid")
|
||||
return
|
||||
}
|
||||
_, err = manager.CreateNotification(ctx, data)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// verify handler
|
||||
func verifyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
err := manager.Verify(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
}
|
||||
}
|
||||
|
||||
// contact update handler
|
||||
func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, body := fetchEnv(ctx, w, r)
|
||||
|
||||
var data []jsonutils.JSONObject
|
||||
data, err := body.GetArray(manager.Keyword(), manager.KeywordPlural())
|
||||
if err != nil {
|
||||
log.Errorf("body: %s, err: %s\n", body.String(), err)
|
||||
httperrors.GeneralServerError(ctx, w, httperrors.NewInputParameterError("need %s and %s",
|
||||
manager.Keyword(), manager.KeywordPlural()))
|
||||
return
|
||||
}
|
||||
log.Debugf("data: %s", data)
|
||||
pullCtypes, _ := body.GetArray(manager.Keyword(), "pull")
|
||||
log.Debugf("pullCtypes: %s", pullCtypes)
|
||||
|
||||
uid := params["<uid>"]
|
||||
out, err := manager.UpdateContacts(ctx, uid, mergeQueryParams(params, query), data, pullCtypes, nil)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
httperrors.BadRequestError(ctx, w, "")
|
||||
return
|
||||
}
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, wrap(out, manager.Keyword()))
|
||||
}
|
||||
|
||||
// delete contact handler
|
||||
func deleteContactHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
|
||||
var data []jsonutils.JSONObject
|
||||
var err error
|
||||
if body != nil {
|
||||
data, err = body.GetArray(manager.KeywordPlural())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(ctx, w, "request body should have %s", manager.KeywordPlural())
|
||||
return
|
||||
}
|
||||
}
|
||||
err = manager.DeleteContacts(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("delete contact of %s failed, error: %s", data, err)
|
||||
httperrors.GeneralServerError(ctx, w, errors.Error("delete failed"))
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(ctx, w, "request body should have %s", manager.Keyword())
|
||||
return
|
||||
}
|
||||
ret, err := manager.VerifyTrigger(ctx, params, data)
|
||||
if err != nil {
|
||||
log.Errorf("verifyTrigger failed beacause %s", err)
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
// list handler for all resource in notify module
|
||||
func listHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, modulebase.ListResult2JSONWithKey(listResult, manager.KeywordPlural()))
|
||||
}
|
||||
|
||||
func getHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
var data jsonutils.JSONObject
|
||||
if len(listResult.Data) == 0 {
|
||||
data = jsonutils.NewDict()
|
||||
} else {
|
||||
data = listResult.Data[0]
|
||||
}
|
||||
appsrv.SendJSON(w, wrap(data, manager.Keyword()))
|
||||
}
|
||||
|
||||
func wrap(data jsonutils.JSONObject, key string) jsonutils.JSONObject {
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, key)
|
||||
return ret
|
||||
}
|
||||
|
||||
// offset ang limit is not useable for here
|
||||
func keyStoneUserListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, query, _ := fetchEnv(ctx, w, r)
|
||||
haveContacts := jsonutils.QueryBoolean(query, "have_contacts", false)
|
||||
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
s := auth.GetSession(ctx, userCred, options.Options.Region, "")
|
||||
users, err := modules.UsersV3.List(s, query)
|
||||
if err != nil {
|
||||
log.Errorf("keystone list error: %s", err)
|
||||
httperrors.InternalServerError(ctx, w, err.Error())
|
||||
return
|
||||
}
|
||||
q := models.ContactManager.Query("uid").GroupBy("uid")
|
||||
row, err := q.Rows()
|
||||
if err != nil {
|
||||
log.Errorf("get contact's uid error: %s", err)
|
||||
httperrors.InternalServerError(ctx, w, err.Error())
|
||||
return
|
||||
}
|
||||
defer row.Close()
|
||||
uidSet, uid := make(map[string]struct{}), ""
|
||||
for row.Next() {
|
||||
row.Scan(&uid)
|
||||
uidSet[uid] = struct{}{}
|
||||
}
|
||||
|
||||
type sPair struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
newDatas := make([]sPair, 0, len(users.Data))
|
||||
|
||||
for _, data := range users.Data {
|
||||
id, _ := data.GetString("id")
|
||||
name, _ := data.GetString("name")
|
||||
if _, ok := uidSet[id]; ok {
|
||||
if haveContacts {
|
||||
newDatas = append(newDatas, sPair{id, name})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if haveContacts {
|
||||
continue
|
||||
}
|
||||
newDatas = append(newDatas, sPair{id, name})
|
||||
}
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(jsonutils.Marshal(newDatas), manager.Keyword())
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package _interface
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -28,6 +28,7 @@ type INotifyService interface {
|
||||
RestartService(ctx context.Context, config SConfig, serviceName string)
|
||||
Send(ctx context.Context, contactType, contact, topic, msg, priority string) error
|
||||
ContactByMobile(ctx context.Context, mobile, serviceName string) (string, error)
|
||||
BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error)
|
||||
ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, message string, err error)
|
||||
}
|
||||
|
||||
296
pkg/notify/models/config.go
Normal file
296
pkg/notify/models/config.go
Normal file
@@ -0,0 +1,296 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
notifyv2 "yunion.io/x/onecloud/pkg/notify"
|
||||
"yunion.io/x/onecloud/pkg/notify/oldmodels"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
type SConfigManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ConfigManager *SConfigManager
|
||||
|
||||
func init() {
|
||||
ConfigManager = &SConfigManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SConfig{},
|
||||
"configs_tbl",
|
||||
"notifyconfig",
|
||||
"notifyconfigs",
|
||||
),
|
||||
}
|
||||
ConfigManager.SetVirtualObject(ConfigManager)
|
||||
}
|
||||
|
||||
type SConfig struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
Type string `width:"15" nullable:"false" create:"required" get:"admin" list:"admin"`
|
||||
Content jsonutils.JSONObject `nullable:"false" create:"required" update:"admin" get:"admin" list:"admin"`
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ConfigCreateInput) (api.ConfigCreateInput, error) {
|
||||
var err error
|
||||
input.StandaloneResourceCreateInput, err = cm.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneResourceCreateInput)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX, api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT}) {
|
||||
return input, httperrors.NewInputParameterError("unkown type %q", input.Type)
|
||||
}
|
||||
if input.Content == nil {
|
||||
return input, httperrors.NewMissingParameterError("content")
|
||||
}
|
||||
config, err := cm.GetConfigByType(input.Type)
|
||||
if err == nil && config != nil {
|
||||
return input, httperrors.NewDuplicateResourceError("duplicate type %q", input.Type)
|
||||
}
|
||||
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
||||
return input, err
|
||||
}
|
||||
// validate
|
||||
configs := make(map[string]string)
|
||||
err = input.Content.Unmarshal(&configs)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
isValid, message, err := NotifyService.ValidateConfig(ctx, input.Type, configs)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotImplemented {
|
||||
return input, httperrors.NewNotImplementedError("validating config of %s", input.Type)
|
||||
}
|
||||
return input, err
|
||||
}
|
||||
if !isValid {
|
||||
return input, httperrors.NewInputParameterError(message)
|
||||
}
|
||||
if len(input.Name) == 0 {
|
||||
input.Name = input.Type
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ConfigListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := self.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q = q.NotEquals("type", api.WEBCONSOLE)
|
||||
if len(input.Type) > 0 {
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), input.Type))
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.ConfigDetails, error) {
|
||||
return api.ConfigDetails{}, nil
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) FetchCustomizeColumns(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
objs []interface{},
|
||||
fields stringutils2.SSortedStrings,
|
||||
isList bool,
|
||||
) []api.ConfigDetails {
|
||||
sRows := cm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
rows := make([]api.ConfigDetails, len(objs))
|
||||
for i := range rows {
|
||||
rows[i].StandaloneResourceDetails = sRows[i]
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||||
q, err := cm.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
|
||||
if err != nil {
|
||||
return q, nil
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ConfigListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := cm.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) AllowPerformValidate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, cm, "validate")
|
||||
}
|
||||
|
||||
func (cm *SConfigManager) PerformValidate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigValidateInput) (api.ConfigValidateOutput, error) {
|
||||
var (
|
||||
output api.ConfigValidateOutput
|
||||
err error
|
||||
)
|
||||
if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX, api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT}) {
|
||||
return output, httperrors.NewInputParameterError("unkown type %q", input.Type)
|
||||
}
|
||||
if input.Content == nil {
|
||||
return output, httperrors.NewMissingParameterError("content")
|
||||
}
|
||||
// validate
|
||||
configs := make(map[string]string)
|
||||
err = input.Content.Unmarshal(&configs)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
isValid, message, err := NotifyService.ValidateConfig(ctx, input.Type, configs)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotImplemented {
|
||||
return output, httperrors.NewNotImplementedError("validating config of %s", input.Type)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
if !isValid {
|
||||
output.IsValid = false
|
||||
output.Message = message
|
||||
} else {
|
||||
output.IsValid = true
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) InitializeData() error {
|
||||
ctx := context.Background()
|
||||
userCred := auth.AdminCredential()
|
||||
// fetch all configs
|
||||
configs := make([]oldmodels.SConfig, 0, 5)
|
||||
q := oldmodels.ConfigManager.Query()
|
||||
err := db.FetchModelObjects(oldmodels.ConfigManager, q, &configs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.FetchModelObjects")
|
||||
}
|
||||
|
||||
// build type==>config map
|
||||
tcMap := make(map[string][]*oldmodels.SConfig)
|
||||
for i := range configs {
|
||||
t := configs[i].Type
|
||||
if _, ok := tcMap[t]; !ok {
|
||||
tcMap[t] = make([]*oldmodels.SConfig, 0, 3)
|
||||
}
|
||||
tcMap[t] = append(tcMap[t], &configs[i])
|
||||
}
|
||||
|
||||
for t, configs := range tcMap {
|
||||
cMap := make(map[string]string)
|
||||
for _, config := range configs {
|
||||
cMap[config.KeyText] = config.ValueText
|
||||
}
|
||||
newConfig := SConfig{
|
||||
Type: t,
|
||||
Content: jsonutils.Marshal(cMap),
|
||||
}
|
||||
err := self.TableSpec().Insert(ctx, &newConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "TableSpec().Insert")
|
||||
}
|
||||
for _, config := range configs {
|
||||
err := config.Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init webconsole's config
|
||||
q = self.Query().Equals("type", api.WEBCONSOLE)
|
||||
wsConfigs := make([]SConfig, 0, 2)
|
||||
err = db.FetchModelObjects(self, q, &wsConfigs)
|
||||
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
||||
return errors.Wrap(err, "db.FetchModelObjects")
|
||||
}
|
||||
if len(wsConfigs) > 1 {
|
||||
for i := 1; i < len(wsConfigs); i++ {
|
||||
err := wsConfigs[i].Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Delete redundant")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var config *SConfig
|
||||
if len(wsConfigs) > 0 {
|
||||
config = &wsConfigs[0]
|
||||
} else {
|
||||
config = &SConfig{
|
||||
Type: api.WEBCONSOLE,
|
||||
}
|
||||
}
|
||||
config.Content = jsonutils.Marshal(map[string]string{
|
||||
"auth_uri": options.Options.AuthURL,
|
||||
"admin_user": options.Options.AdminUser,
|
||||
"admin_password": options.Options.AdminPassword,
|
||||
"admin_tenant_name": options.Options.AdminProject,
|
||||
})
|
||||
err = self.TableSpec().InsertOrUpdate(context.TODO(), config)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch all SConfig struct which type is contactType.
|
||||
func (self *SConfigManager) GetConfigByType(contactType string) (*SConfig, error) {
|
||||
var config SConfig
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
|
||||
err := q.First(&config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail to fetch SConfigs by type")
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) GetConfig(contactType string) (notifyv2.SConfig, error) {
|
||||
config, err := self.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(map[string]string)
|
||||
err = config.Content.Unmarshal(&ret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail unmarshal config content")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) SetConfig(contactType string, config notifyv2.SConfig) error {
|
||||
content := jsonutils.Marshal(config)
|
||||
sConfig := &SConfig{
|
||||
Type: contactType,
|
||||
Content: content,
|
||||
}
|
||||
return self.TableSpec().InsertOrUpdate(context.Background(), sConfig)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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
|
||||
|
||||
const (
|
||||
EMAIL = "email"
|
||||
MOBILE = "mobile"
|
||||
DINGTALK = "dingtalk"
|
||||
FEISHU = "feishu"
|
||||
WEBCONSOLE = "webconsole"
|
||||
ROBOT = "robot"
|
||||
|
||||
NOTIFY_RECEIVED = "received" // Received a task about sending a notification
|
||||
NOTIFY_SENT = "sent" // Nofity module has sent notification, but result unkown
|
||||
NOTIFY_OK = "sent_ok" // Notification was sent successfully
|
||||
NOTIFY_FAIL = "sent_fail" // That sent a notification is failed
|
||||
|
||||
CONTACT_INIT = "init" // Contact's status is init which means no verifying
|
||||
CONTACT_VERIFYING = "verifying" // Contact's status is verifying
|
||||
CONTACT_VERIFIED = "verified" // Contact's status is verified
|
||||
|
||||
VERIFICATION_SENT = "sent" // Verification was sent
|
||||
VERIFICATION_SENT_FAIL = "sent_fail" // Verification was sent failed
|
||||
VERIFICATION_VERIFIED = "verified" // Verification was verified
|
||||
VERIFICATION_TOKEN_EXPIRED = "Verification code expired"
|
||||
VERIFICATION_TOKEN_INVALID = "Incorrect verification code"
|
||||
)
|
||||
|
||||
// Dingtalk account will be update automatically as mobile number change so that update dingtalk is not allowed
|
||||
// In webconsole, uid is the same as contact.
|
||||
var UpdateNotAllow = map[string]struct{}{
|
||||
DINGTALK: {},
|
||||
FEISHU: {},
|
||||
WEBCONSOLE: {},
|
||||
}
|
||||
@@ -27,8 +27,7 @@ func InitDB() error {
|
||||
* initialization order matters, do not change the order
|
||||
*/
|
||||
|
||||
ContactManager,
|
||||
VerifyManager,
|
||||
ReceiverManager,
|
||||
NotificationManager,
|
||||
ConfigManager,
|
||||
TemplateManager,
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
_interface "yunion.io/x/onecloud/pkg/notify/interface"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
)
|
||||
|
||||
type SConfigManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ConfigManager *SConfigManager
|
||||
|
||||
func init() {
|
||||
ConfigManager = &SConfigManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SConfig{},
|
||||
"notify_t_config",
|
||||
"config",
|
||||
"configs",
|
||||
),
|
||||
}
|
||||
ConfigManager.SetVirtualObject(ConfigManager)
|
||||
}
|
||||
|
||||
// SConfig is a table which storage (k,v) and its type.
|
||||
// The three important concepts are key, value and type.
|
||||
// Key and type uniquely identify a value.
|
||||
type SConfig struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
Type string `width:"15" nullable:"false" create:"required" list:"user"`
|
||||
KeyText string `width:"30" nullable:"false" create:"required" list:"user"`
|
||||
ValueText string `width:"256" nullable:"false" create:"required" list:"user"`
|
||||
}
|
||||
|
||||
// ListItemFilter is a hook function belong to IModelManager interface when Listing.
|
||||
// This will Called in yunion.io/x/onecloud/pkg/cloudcommon/db.List function.
|
||||
func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
if !query.Contains("type") {
|
||||
return q, nil
|
||||
}
|
||||
contactType, _ := query.GetString("type")
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
|
||||
return q, nil
|
||||
}
|
||||
|
||||
// GetValue fetch the SConfig struct corresponding to key and type.
|
||||
func (self *SConfigManager) GetValue(key, contactType string) (*SConfig, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("type"), contactType), sqlchemy.Equals(q.Field("key_text"), key)))
|
||||
configs := make([]SConfig, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &configs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Fetch SConfig by key and type failed")
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
return nil, errors.Error("There is no SConfig whose key and type meet the requirement")
|
||||
}
|
||||
return &configs[0], nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) InitializeData() error {
|
||||
q := self.Query()
|
||||
q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsNotNull(q.Field("created_at")),
|
||||
sqlchemy.IsNotNull(q.Field("deleted_at")), sqlchemy.IsTrue(q.Field("deleted")),
|
||||
sqlchemy.IsNotNull(q.Field("update_by")), sqlchemy.IsNotNull(q.Field("delete_by"))))
|
||||
n, err := q.CountWithError()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
log.Debugf("need to init data for %s", self.TableSpec().Name())
|
||||
// need to update
|
||||
sql := fmt.Sprintf("update %s set updated_at=gmt_modified, deleted=is_deleted, created_at=gmt_create, deleted_at=gmt_deleted, update_by=modified_by, delete_by=deleted_by", self.TableSpec().Name())
|
||||
q := sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
sql = fmt.Sprintf("update %s set type='mobile' where type='sms_aliyun'", self.TableSpec().Name())
|
||||
q = sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
}
|
||||
|
||||
// init webconsole's config
|
||||
sql := fmt.Sprintf("update %s set deleted='1' where type='webconsole'", self.TableSpec().Name())
|
||||
q = sqlchemy.NewRawQuery(sql)
|
||||
q.Row()
|
||||
configs := []SConfig{
|
||||
{
|
||||
Type: "webconsole",
|
||||
KeyText: "auth_uri",
|
||||
ValueText: options.Options.AuthURL,
|
||||
},
|
||||
{
|
||||
Type: "webconsole",
|
||||
KeyText: "admin_user",
|
||||
ValueText: options.Options.AdminUser,
|
||||
},
|
||||
{
|
||||
Type: "webconsole",
|
||||
KeyText: "admin_password",
|
||||
ValueText: options.Options.AdminPassword,
|
||||
},
|
||||
{
|
||||
Type: "webconsole",
|
||||
KeyText: "admin_tenant_name",
|
||||
ValueText: options.Options.AdminProject,
|
||||
},
|
||||
}
|
||||
for _, config := range configs {
|
||||
err := self.TableSpec().Insert(context.TODO(), &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch all SConfig struct which type is contactType.
|
||||
func (self *SConfigManager) GetConfigByType(contactType string) ([]SConfig, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
|
||||
configs := make([]SConfig, 0, 5)
|
||||
err := db.FetchModelObjects(self, q, &configs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Fetch SConfigs by type failed")
|
||||
}
|
||||
//if len(configs) == 0 {
|
||||
// return nil, errors.Error("There is no SConfig whose type meet the requirement")
|
||||
//}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) GetConfig(contactType string) (_interface.SConfig, error) {
|
||||
configs, err := self.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(map[string]string)
|
||||
for i := range configs {
|
||||
ret[configs[i].KeyText] = configs[i].ValueText
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) SetConfig(contactType string, config _interface.SConfig) error {
|
||||
return fmt.Errorf("SetConfig Not Implemented")
|
||||
}
|
||||
|
||||
type SConvertFunc func(*jsonutils.JSONDict) *jsonutils.JSONDict
|
||||
|
||||
var (
|
||||
// toDisplay store SConvertFunc which convert the data from client to the form required by database of contactType
|
||||
toDisplay map[string]SConvertFunc
|
||||
// fromDisplay store SConvertFunc which convert the data from database to the form required by client of contactType
|
||||
fromDisplay map[string]SConvertFunc
|
||||
)
|
||||
|
||||
func init() {
|
||||
toDisplay = map[string]SConvertFunc{
|
||||
EMAIL: emailToDisplay,
|
||||
}
|
||||
fromDisplay = map[string]SConvertFunc{
|
||||
EMAIL: emailFromDisplay,
|
||||
}
|
||||
}
|
||||
|
||||
func emailFromDisplay(dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
keys := dict.SortedKeys()
|
||||
newKey := ""
|
||||
for _, key := range keys {
|
||||
switch key {
|
||||
case "username", "password":
|
||||
newKey = "mail." + key
|
||||
case "hostname", "hostport":
|
||||
newKey = "mail.smtp." + key
|
||||
case "ssl_global":
|
||||
newKey = "mail.global.ssl"
|
||||
}
|
||||
v, _ := dict.Get(key)
|
||||
dict.Add(v, newKey)
|
||||
dict.Remove(key)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func emailToDisplay(dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
keys := dict.SortedKeys()
|
||||
for _, key := range keys {
|
||||
newKey := ""
|
||||
value, _ := dict.Get(key)
|
||||
switch key {
|
||||
case "mail.username", "mail.password":
|
||||
newKey = key[5:]
|
||||
case "mail.smtp.hostname":
|
||||
newKey = key[10:]
|
||||
case "mail.smtp.hostport":
|
||||
newKey = key[10:]
|
||||
portStr, _ := value.GetString()
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
value = jsonutils.NewInt(int64(port))
|
||||
case "mail.global.ssl":
|
||||
newKey = "ssl_global"
|
||||
sslStr, _ := value.GetString()
|
||||
ssl, _ := strconv.ParseBool(sslStr)
|
||||
value = jsonutils.NewBool(ssl)
|
||||
}
|
||||
dict.Add(value, newKey)
|
||||
dict.Remove(key)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func (self *SConfigManager) Display2Database(cType string, dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
cf, ok := fromDisplay[cType]
|
||||
if ok {
|
||||
return cf(dict)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func (self *SConfigManager) Database2Display(cType string, dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
cf, ok := toDisplay[cType]
|
||||
if ok {
|
||||
return cf(dict)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"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/notify/utils"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
type SContactManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ContactManager *SContactManager
|
||||
|
||||
func init() {
|
||||
ContactManager = &SContactManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SContact{},
|
||||
"notify_t_contacts",
|
||||
"contact",
|
||||
"contacts",
|
||||
),
|
||||
}
|
||||
ContactManager.SetVirtualObject(ContactManager)
|
||||
}
|
||||
|
||||
type SContact struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required" update:"user" list:"user" get:"user"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required" update:"user"`
|
||||
Contact string `width:"64" nullable:"false" create:"required" update:"user"`
|
||||
Enabled string `width:"5" nullable:"false" default:"1" create:"optional" update:"user"`
|
||||
VerifiedAt time.Time `update:"user"`
|
||||
}
|
||||
|
||||
func (self *SContactManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SContactManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SContactManager) ResourceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeUser
|
||||
}
|
||||
|
||||
func (self *SContactManager) NamespaceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeUser
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchOwnerId(ctx context.Context,
|
||||
data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
|
||||
return db.FetchUserInfo(ctx, data)
|
||||
}
|
||||
|
||||
func (self *SContactManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider,
|
||||
scope rbacutils.TRbacScope) *sqlchemy.SQuery {
|
||||
if owner != nil {
|
||||
if scope == rbacutils.ScopeUser {
|
||||
if len(owner.GetUserId()) > 0 {
|
||||
q = q.Equals("uid", owner.GetUserId())
|
||||
}
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (self *SContactManager) InitializeData() error {
|
||||
q := self.Query()
|
||||
q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsTrue(q.Field("deleted"))))
|
||||
n, err := q.CountWithError()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
log.Debugf("no need to init data for %s", self.TableSpec().Name())
|
||||
// no need to init data
|
||||
return nil
|
||||
}
|
||||
log.Debugf("need to init data for %s", self.TableSpec().Name())
|
||||
sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name())
|
||||
q = sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchByUIDs fetch all SContancts whose uid included in uids.
|
||||
// If some elements of uids are uname of users, setting param 'uname' as true will fetch correct results.
|
||||
func (self *SContactManager) FetchByUIDs(ctx context.Context, uids []string, uname bool) ([]SContact, error) {
|
||||
var err error
|
||||
if uname {
|
||||
uids, err = self._UIDsFromUIDOrName(ctx, 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) _UIDsFromUIDOrName(ctx context.Context, uidStrs []string) ([]string, error) {
|
||||
users, err := utils.GetUsersWithoutRemote(ctx, 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)
|
||||
}
|
||||
log.Debugf("uids %s => %s", uidStrs, uids)
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchByUIDAndCType(uid string, contactTypes []string) ([]SContact, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchByMore(uid, contact, contactType string) ([]SContact, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContact) getMoreDetail(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
out api.ContactDetails) (api.ContactDetails, error) {
|
||||
|
||||
uname, err := utils.GetUsernameByID(ctx, self.UID)
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
uname = self.UID
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
q := ContactManager.Query().Equals("uid", self.UID)
|
||||
contacts := make([]SContact, 0)
|
||||
err = db.FetchModelObjects(ContactManager, q, &contacts)
|
||||
if err != nil {
|
||||
return out, errors.Wrapf(err, "fetch Contacts of uid %s error", self.UID)
|
||||
}
|
||||
out.UID = self.UID
|
||||
out.Name = uname
|
||||
out.Details = jsonutils.Marshal(contacts).String()
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (manager *SContactManager) FetchCustomizeColumns(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
objs []interface{},
|
||||
fields stringutils2.SSortedStrings,
|
||||
isList bool,
|
||||
) []api.ContactDetails {
|
||||
rows := make([]api.ContactDetails, len(objs))
|
||||
|
||||
stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
|
||||
var err error
|
||||
for i := range rows {
|
||||
rows[i] = api.ContactDetails{
|
||||
ResourceBaseDetails: stdRows[i],
|
||||
}
|
||||
rows[i], err = objs[i].(*SContact).getMoreDetail(ctx, userCred, rows[i])
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (self *SContact) GetExtraDetails(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
isList bool,
|
||||
) (api.ContactDetails, error) {
|
||||
return api.ContactDetails{}, nil
|
||||
}
|
||||
|
||||
// 联系方式列表
|
||||
func (self *SContactManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
if queryDict.Contains("uid") {
|
||||
uid, _ := queryDict.GetString("uid")
|
||||
q = q.Equals("uid", uid)
|
||||
}
|
||||
// for now
|
||||
if queryDict.Contains("filter") {
|
||||
filterCon, _ := queryDict.GetString("filter")
|
||||
queryDict.Remove("filter")
|
||||
contain := "name.contains("
|
||||
index := strings.Index(filterCon, contain)
|
||||
if index < 0 {
|
||||
return q, nil
|
||||
}
|
||||
filterCon = filterCon[index+len(contain):]
|
||||
index = strings.Index(filterCon, ")")
|
||||
if index < 0 {
|
||||
return q, nil
|
||||
}
|
||||
name := filterCon[:index]
|
||||
ids, err := utils.GetUserIdsLikeName(ctx, name)
|
||||
if err != nil {
|
||||
return q, nil
|
||||
}
|
||||
q = q.In("uid", ids)
|
||||
}
|
||||
|
||||
scopeStr, err := query.GetString("scope")
|
||||
if err != nil {
|
||||
scopeStr = "project"
|
||||
}
|
||||
scope := rbacutils.TRbacScope(scopeStr)
|
||||
|
||||
switch {
|
||||
case scope.HigherEqual(rbacutils.ScopeSystem):
|
||||
case scope.HigherEqual(rbacutils.ScopeDomain):
|
||||
uids, err := self.uidsInDomain(ctx, userCred)
|
||||
if err != nil {
|
||||
return q, err
|
||||
}
|
||||
q = q.In("uid", uids)
|
||||
default:
|
||||
q = q.Equals("uid", userCred.GetUserId())
|
||||
}
|
||||
|
||||
q = q.GroupBy("uid").Desc("created_at")
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) uidsInDomain(ctx context.Context, userCred mcclient.TokenCredential) ([]string, error) {
|
||||
session := auth.GetSession(ctx, userCred, "", "")
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("scope", jsonutils.NewString("domain"))
|
||||
ret, err := modules.UsersV3.List(session, params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "modules.Userv3.List")
|
||||
}
|
||||
uids := make([]string, 0, len(ret.Data))
|
||||
for i := range ret.Data {
|
||||
id, _ := ret.Data[i].GetString("id")
|
||||
uids = append(uids, id)
|
||||
}
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
// Contacts query all contacts by uids and contactType
|
||||
func (self *SContactManager) Contacts(uids []string, contactType string) ([]SContact, error) {
|
||||
contacts := make([]SContact, 0, len(uids))
|
||||
if contactType == WEBCONSOLE {
|
||||
for _, uid := range uids {
|
||||
contacts = append(contacts, SContact{
|
||||
UID: uid,
|
||||
ContactType: WEBCONSOLE,
|
||||
Contact: uid,
|
||||
})
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
queryCon := contactType
|
||||
if strings.Contains(contactType, ROBOT) {
|
||||
queryCon = MOBILE
|
||||
}
|
||||
q := self.Query().Equals("contact_type", queryCon).Equals("enabled", "1").In("uid", uids)
|
||||
err := db.FetchModelObjects(self, q, &contacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For Robot Sender, only one message of the same content is sent for multiple users,
|
||||
// so the user's contact information is a collection of all contact information
|
||||
if strings.Contains(contactType, ROBOT) {
|
||||
// hack
|
||||
contactVals := make([]string, len(contacts))
|
||||
uidVals := make([]string, len(contacts))
|
||||
for i := range contacts {
|
||||
contactVals[i] = contacts[i].Contact
|
||||
uidVals[i] = contacts[i].UID
|
||||
}
|
||||
contacts = []SContact{
|
||||
{
|
||||
UID: strings.Join(uidVals, ","),
|
||||
ContactType: contactType,
|
||||
Contact: strings.Join(contactVals, ","),
|
||||
},
|
||||
}
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) GetAllNotify(ctx context.Context, ids []string, contactType string, group bool) ([]SContact, error) {
|
||||
var uids []string
|
||||
var err error
|
||||
|
||||
if !group {
|
||||
if v := ctx.Value("uname"); v != nil {
|
||||
ids, err = self._UIDsFromUIDOrName(ctx, 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)
|
||||
for _, id := range uids {
|
||||
tmpUids, err := utils.GetUsersByGroupID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uid = append(uid, tmpUids...)
|
||||
}
|
||||
uids = uid
|
||||
}
|
||||
return self.Contacts(uids, contactType)
|
||||
}
|
||||
|
||||
type SContactResponse struct {
|
||||
Id string
|
||||
Name string
|
||||
Details string
|
||||
}
|
||||
|
||||
func NewSContactResponse(ctx context.Context, uid string, details string) SContactResponse {
|
||||
name, _ := utils.GetUsernameByID(ctx, uid)
|
||||
return SContactResponse{
|
||||
Id: uid,
|
||||
Name: name,
|
||||
Details: details,
|
||||
}
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/cache"
|
||||
_interface "yunion.io/x/onecloud/pkg/notify/interface"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
type SNotificationManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var NotificationManager *SNotificationManager
|
||||
var NotifyService _interface.INotifyService
|
||||
|
||||
func init() {
|
||||
NotificationManager = &SNotificationManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SNotification{},
|
||||
"notify_t_notification",
|
||||
"notification",
|
||||
"notifications",
|
||||
),
|
||||
}
|
||||
NotificationManager.SetVirtualObject(NotificationManager)
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) ResourceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeUser
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) NamespaceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeUser
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) FetchOwnerId(ctx context.Context,
|
||||
data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
|
||||
return db.FetchUserInfo(ctx, data)
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider,
|
||||
scope rbacutils.TRbacScope) *sqlchemy.SQuery {
|
||||
if owner != nil {
|
||||
if scope == rbacutils.ScopeUser {
|
||||
if len(owner.GetUserId()) > 0 {
|
||||
q = q.Equals("uid", owner.GetUserId())
|
||||
}
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
type SNotification struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required" list:"user" index:"true"`
|
||||
Topic string `width:"128" nullable:"true" create:"optional" list:"user"`
|
||||
Priority string `width:"16" nullable:"true" create:"optional" list:"user"`
|
||||
Msg string `create:"required"`
|
||||
ReceivedAt time.Time `nullable:"true" list:"user" create:"optional"`
|
||||
SendAt time.Time `nullable:"false"`
|
||||
SendBy string `width:"128" nullable:"false"`
|
||||
// ClusterID identify message with same topic, msg, priority
|
||||
ClusterID string `width:"128" charset:"ascii" primary:"true" create:"optional" list:"user" get:"user"`
|
||||
}
|
||||
|
||||
type UserDetail struct {
|
||||
Status string
|
||||
Name string
|
||||
ReceivedAt time.Time
|
||||
}
|
||||
|
||||
func (manager *SNotificationManager) FetchCustomizeColumns(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
objs []interface{},
|
||||
fields stringutils2.SSortedStrings,
|
||||
isList bool,
|
||||
) []api.NotificationDetails {
|
||||
rows := make([]api.NotificationDetails, len(objs))
|
||||
|
||||
resRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
|
||||
for i := range rows {
|
||||
rows[i] = api.NotificationDetails{
|
||||
ResourceBaseDetails: resRows[i],
|
||||
}
|
||||
rows[i], _ = objs[i].(*SNotification).getMoreDetails(ctx, query, rows[i])
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func (self *SNotification) GetExtraDetails(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
isList bool,
|
||||
) (api.NotificationDetails, error) {
|
||||
return api.NotificationDetails{}, nil
|
||||
}
|
||||
|
||||
func (self *SNotification) getMoreDetails(ctx context.Context, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) {
|
||||
var err error
|
||||
|
||||
var scopeStr string
|
||||
scopeStr, err = query.GetString("scope")
|
||||
if err != nil {
|
||||
scopeStr = "system"
|
||||
}
|
||||
scope := rbacutils.TRbacScope(scopeStr)
|
||||
|
||||
var userDetails []UserDetail
|
||||
if scope.HigherEqual(rbacutils.ScopeSystem) {
|
||||
userDetails, err = NotificationManager.fetchUserDetailByClusterID(ctx, self.ClusterID)
|
||||
if err != nil {
|
||||
return out, errors.Wrap(err, "NotificationManager.fetchUserDetailByClusterID")
|
||||
}
|
||||
} else {
|
||||
userDetail := UserDetail{
|
||||
Status: self.Status,
|
||||
Name: self.UID,
|
||||
ReceivedAt: self.ReceivedAt,
|
||||
}
|
||||
name, err := utils.GetUsernameByID(ctx, self.UID)
|
||||
if err == nil && len(name) != 0 {
|
||||
userDetail.Name = name
|
||||
}
|
||||
userDetails = []UserDetail{userDetail}
|
||||
}
|
||||
|
||||
out.UserList = jsonutils.Marshal(userDetails)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowList(userCred, self)
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowCreate(userCred, self)
|
||||
}
|
||||
|
||||
type sUpdate struct {
|
||||
ID string
|
||||
UID string
|
||||
Topic string
|
||||
Priority string
|
||||
ContactType string
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) InitializeData() error {
|
||||
scope := time.Duration(options.Options.InitNotificationScope) * time.Hour
|
||||
time := time.Now().Add(-scope)
|
||||
q := self.Query("id", "uid", "topic", "priority", "contact_type").GE("created_at",
|
||||
time).Desc("received_at").Equals("contact_type", "webconsole")
|
||||
q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("cluster_id")), sqlchemy.IsEmpty(q.Field("cluster_id"))))
|
||||
rows, err := q.Rows()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updates, update := make([]sUpdate, 0, 10), sUpdate{}
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&update.ID, &update.UID, &update.Topic, &update.Priority, &update.ContactType)
|
||||
if err == nil {
|
||||
updates = append(updates, update)
|
||||
}
|
||||
}
|
||||
log.Debugf("this is total %d updates", len(updates))
|
||||
|
||||
// updates is too little
|
||||
//if len(updates) < 100 {
|
||||
// updates = updates[:0]
|
||||
// q := self.Query("id", "uid", "topic", "priority", "contact_type").Desc("received_at").Equals("contact_type",
|
||||
// "webconsole").Limit(500)
|
||||
// q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("cluster_id")), sqlchemy.IsEmpty(q.Field("cluster_id"))))
|
||||
// rows, err := q.Rows()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// for rows.Next() {
|
||||
// err := rows.Scan(&update.ID, &update.UID, &update.Topic, &update.Priority, &update.ContactType)
|
||||
// if err == nil {
|
||||
// updates = append(updates, update)
|
||||
// }
|
||||
// }
|
||||
// log.Debugf("this is total %d updates", len(updates))
|
||||
//}
|
||||
|
||||
cache := make([]string, 0, 10)
|
||||
if len(updates) > 0 {
|
||||
cache = append(cache, updates[0].ID)
|
||||
}
|
||||
for i := 1; i < len(updates); i++ {
|
||||
if updates[i].Topic == updates[i-1].Topic && updates[i].Priority == updates[i-1].Priority {
|
||||
|
||||
cache = append(cache, updates[i].ID)
|
||||
continue
|
||||
}
|
||||
err = self.syncDatabase(cache)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "exec sql error")
|
||||
}
|
||||
cache = cache[:0]
|
||||
if i < len(updates)-1 {
|
||||
cache = append(cache, updates[i].ID)
|
||||
}
|
||||
}
|
||||
if len(cache) == 0 {
|
||||
return nil
|
||||
}
|
||||
err = self.syncDatabase(cache)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "exec sql error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) syncDatabase(ids []string) error {
|
||||
|
||||
sql := "update %s set updated_at=update_at, deleted=is_deleted, cluster_id='%s' where id in %s"
|
||||
|
||||
newUid := DefaultUUIDGenerator()
|
||||
|
||||
buffer := new(strings.Builder)
|
||||
buffer.WriteString("(")
|
||||
for _, id := range ids {
|
||||
buffer.WriteString("'")
|
||||
buffer.WriteString(id)
|
||||
buffer.WriteString("', ")
|
||||
}
|
||||
newSql := fmt.Sprintf(sql, self.TableSpec().Name(), newUid, buffer.String()[:buffer.Len()-2]+")")
|
||||
q := sqlchemy.NewRawQuery(newSql)
|
||||
rows, err := q.Rows()
|
||||
defer rows.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// 通知消息列表
|
||||
func (self *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential,
|
||||
input api.NotificationListInput) (*sqlchemy.SQuery, error) {
|
||||
|
||||
// no domainID for now
|
||||
scopeStr := "system"
|
||||
if len(input.Scope) > 0 {
|
||||
scopeStr = input.Scope
|
||||
}
|
||||
scope := rbacutils.TRbacScope(scopeStr)
|
||||
|
||||
if !scope.HigherEqual(rbacutils.ScopeSystem) {
|
||||
q = q.Equals("uid", userCred.GetUserId())
|
||||
}
|
||||
|
||||
if len(input.ContactType) > 0 {
|
||||
q = q.Equals("contact_type", input.ContactType)
|
||||
}
|
||||
|
||||
q = q.GroupBy("cluster_id").Desc("received_at")
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) BatchCreate(ctx context.Context, data jsonutils.JSONObject, contacts []SContact) ([]string, error) {
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
ownerID, err := utils.FetchOwnerId(ctx, NotificationManager, userCred, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
msg, _ := data.GetString("msg")
|
||||
priority, _ := data.GetString("priority")
|
||||
topic, _ := data.GetString("topic")
|
||||
createFailed, createSuccess, contactSuccess := make([]string, 0), make([]*SNotification, 0, len(contacts)/2), make([]string, 0, len(contacts)/2)
|
||||
|
||||
now, clusterId := time.Now(), DefaultUUIDGenerator()
|
||||
for i := range contacts {
|
||||
createData := map[string]interface{}{
|
||||
"uid": contacts[i].UID,
|
||||
"contact_type": contacts[i].ContactType,
|
||||
"topic": topic,
|
||||
"priority": priority,
|
||||
"msg": msg,
|
||||
"received_at": now,
|
||||
"send_by": userCred.GetUserId(),
|
||||
"status": NOTIFY_RECEIVED,
|
||||
"cluster_id": clusterId,
|
||||
}
|
||||
model, err := db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, jsonutils.Marshal(createData), ownerID)
|
||||
if err != nil {
|
||||
createFailed = append(createFailed, contacts[i].ID)
|
||||
} else {
|
||||
createSuccess = append(createSuccess, model.(*SNotification))
|
||||
contactSuccess = append(contactSuccess, contacts[i].Contact)
|
||||
}
|
||||
}
|
||||
Send(createSuccess, userCred, contactSuccess)
|
||||
if len(createFailed) != 0 {
|
||||
errInfo := new(bytes.Buffer)
|
||||
errInfo.WriteString("notifications whose uid are ")
|
||||
for i := range createFailed {
|
||||
errInfo.WriteString(createFailed[i])
|
||||
errInfo.WriteString(", ")
|
||||
}
|
||||
errInfo.Truncate(errInfo.Len() - 2)
|
||||
errInfo.WriteString("created failed.")
|
||||
log.Errorf(errInfo.String())
|
||||
return nil, errors.Error("Not all notifications were sent successfully")
|
||||
}
|
||||
notificationIDs := make([]string, len(createSuccess))
|
||||
for i := range createSuccess {
|
||||
notificationIDs[i] = createSuccess[i].ID
|
||||
}
|
||||
return notificationIDs, nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) fetchUserDetailByClusterID(ctx context.Context, clusterID string) ([]UserDetail,
|
||||
error) {
|
||||
q := self.Query("uid", "status", "received_at").Equals("cluster_id", clusterID)
|
||||
row, err := q.Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]UserDetail, 0)
|
||||
userIds := make([]string, 0)
|
||||
var userId, status string
|
||||
var receviedAt time.Time
|
||||
for row.Next() {
|
||||
err := row.Scan(&userId, &status, &receviedAt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sql.row parse error")
|
||||
}
|
||||
userIds = append(userIds, userId)
|
||||
ret = append(ret, UserDetail{
|
||||
Status: status,
|
||||
Name: userId,
|
||||
ReceivedAt: receviedAt,
|
||||
})
|
||||
}
|
||||
|
||||
userMap, err := cache.UserCacheManager.FetchUsersByIDs(ctx, userIds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetch users by ids failed")
|
||||
}
|
||||
for i := range ret {
|
||||
if user, ok := userMap[ret[i].Name]; ok {
|
||||
ret[i].Name = user.Name
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) FetchFailed(lastTime time.Time) ([]SNotification, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.AND(sqlchemy.GE(q.Field("created_at"), lastTime), sqlchemy.Equals(q.Field("status"), NOTIFY_FAIL)))
|
||||
records := make([]SNotification, 0, 10)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SNotification) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error {
|
||||
if self.Status == status {
|
||||
return nil
|
||||
}
|
||||
oldStatus := self.Status
|
||||
_, err := db.Update(self, func() error {
|
||||
self.Status = status
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userCred != nil {
|
||||
notes := fmt.Sprintf("%s=>%s", oldStatus, status)
|
||||
if len(reason) > 0 {
|
||||
notes = fmt.Sprintf("%s: %s", notes, reason)
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SNotification) SetSentAndTime(userCred mcclient.TokenCredential) error {
|
||||
status := NOTIFY_SENT
|
||||
if self.Status == status {
|
||||
return nil
|
||||
}
|
||||
oldStatus := self.Status
|
||||
_, err := db.Update(self, func() error {
|
||||
self.Status = status
|
||||
self.SendAt = time.Now()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reason := "sent notification"
|
||||
if userCred != nil {
|
||||
notes := fmt.Sprintf("%s=>%s", oldStatus, status)
|
||||
if len(reason) > 0 {
|
||||
notes = fmt.Sprintf("%s: %s", notes, reason)
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SNotification) SetStatusWithoutUserCred(status string) error {
|
||||
_, err := db.Update(self, func() error {
|
||||
self.Status = status
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func sendWithoutUserCred(notifications []SNotification) {
|
||||
// limit the number of Concurrency
|
||||
Max := 10
|
||||
limit := make(chan struct{}, Max)
|
||||
sendone := func(notification SNotification) {
|
||||
defer func() {
|
||||
<-limit
|
||||
}()
|
||||
// Get contact
|
||||
contact, err := ContactManager.FetchByUIDAndCType(notification.UID, []string{notification.ContactType})
|
||||
if err != nil {
|
||||
log.Debugf("fail to fetch contacts with uid '%s' in ReSend Cron Job", notification.UID)
|
||||
return
|
||||
}
|
||||
if len(contact) == 0 {
|
||||
return
|
||||
}
|
||||
// sent_at update todo
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_SENT)
|
||||
err = NotifyService.Send(context.Background(), notification.ContactType, contact[0].Contact, notification.Topic,
|
||||
notification.Msg,
|
||||
notification.Priority)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Send notification failed in ReSend Cron Job: %s.", err.Error())
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_FAIL)
|
||||
} else {
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_OK)
|
||||
}
|
||||
}
|
||||
for i := range notifications {
|
||||
limit <- struct{}{}
|
||||
go sendone(notifications[i])
|
||||
}
|
||||
// wait all finish
|
||||
for i := 0; i < Max; i++ {
|
||||
limit <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func ReSend(seconds int) {
|
||||
scope := time.Duration(seconds+30) * time.Second
|
||||
notifications, err := NotificationManager.FetchFailed(time.Now().Add(-scope))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("Start to resend message with a total of %d", len(notifications))
|
||||
sendWithoutUserCred(notifications)
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
type SVerifyManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var VerifyManager *SVerifyManager
|
||||
|
||||
func init() {
|
||||
VerifyManager = &SVerifyManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SVerify{},
|
||||
"notify_t_verify",
|
||||
"verification",
|
||||
"verifications",
|
||||
),
|
||||
}
|
||||
VerifyManager.SetVirtualObject(VerifyManager)
|
||||
}
|
||||
|
||||
type SVerify struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
CID string `width:"128" nullable:"false" create:"required" list:"user"`
|
||||
Token string `width:"200" nullable:"false" create:"required" list:"user"`
|
||||
SendAt time.Time `nullable:"true" create:"optional"`
|
||||
ExpireAt time.Time `create:"required" list:"user"`
|
||||
}
|
||||
|
||||
// NewSVerify Generate a SVerify instance which implement a Verification Token.
|
||||
func NewSVerify(contactType string, cid string) *SVerify {
|
||||
var token string
|
||||
var expireAt time.Time
|
||||
now := time.Now()
|
||||
if contactType == EMAIL {
|
||||
token = utils.GenerateEmailToken(32)
|
||||
expireAt = now.Add(12 * time.Hour)
|
||||
} else {
|
||||
token = utils.GenerateMobileToken()
|
||||
expireAt = now.Add(2 * time.Minute)
|
||||
}
|
||||
ret := &SVerify{
|
||||
CID: cid,
|
||||
Token: token,
|
||||
ExpireAt: expireAt,
|
||||
SendAt: now,
|
||||
}
|
||||
ret.ID = DefaultUUIDGenerator()
|
||||
ret.SetModelManager(VerifyManager, ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) InitializeData() error {
|
||||
q := self.Query()
|
||||
q = q.Filter(sqlchemy.OR(sqlchemy.IsNotNull(q.Field("updated_at")), sqlchemy.IsTrue(q.Field("deleted"))))
|
||||
n, err := q.CountWithError()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
log.Debugf("no need to init data for %s", self.TableSpec().Name())
|
||||
// no need to init data
|
||||
return nil
|
||||
}
|
||||
log.Debugf("need to init data for %s", self.TableSpec().Name())
|
||||
sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name())
|
||||
q = sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) FetchByCID(cid string, filter func(q *sqlchemy.SQuery) *sqlchemy.SQuery) ([]SVerify, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("cid"), cid))
|
||||
q = filter(q)
|
||||
records := make([]SVerify, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) FetchByID(id string) ([]SVerify, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("id"), id))
|
||||
records := make([]SVerify, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) Create(ctx context.Context, userCred mcclient.TokenCredential, verify *SVerify) error {
|
||||
data := jsonutils.Marshal(verify)
|
||||
ownerID, err := utils.FetchOwnerId(ctx, self, userCred, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, data, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
419
pkg/notify/models/notification.go
Normal file
419
pkg/notify/models/notification.go
Normal file
@@ -0,0 +1,419 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
notifyv2 "yunion.io/x/onecloud/pkg/notify"
|
||||
"yunion.io/x/onecloud/pkg/notify/oldmodels"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
type SNotificationManager struct {
|
||||
db.SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var NotificationManager *SNotificationManager
|
||||
var NotifyService notifyv2.INotifyService
|
||||
|
||||
func init() {
|
||||
NotificationManager = &SNotificationManager{
|
||||
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
|
||||
SNotification{},
|
||||
"notifications_tbl",
|
||||
"notification",
|
||||
"notifications",
|
||||
),
|
||||
}
|
||||
NotificationManager.SetVirtualObject(NotificationManager)
|
||||
}
|
||||
|
||||
type SNotification struct {
|
||||
db.SStatusStandaloneResourceBase
|
||||
|
||||
ContactType string `width:"16" nullable:"false" create:"required" list:"user" get:"user" index:"true"`
|
||||
// swagger:ignore
|
||||
Topic string `width:"128" nullable:"true" create:"required"`
|
||||
Priority string `width:"16" nullable:"true" create:"optional" list:"user" get:"user"`
|
||||
// swagger:ignore
|
||||
Message string `create:"required"`
|
||||
ReceivedAt time.Time `nullable:"true" list:"user" get:"user"`
|
||||
SendTimes int
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) {
|
||||
// check uids, rids and contacts
|
||||
if len(input.Receivers) == 0 && len(input.Contacts) == 0 {
|
||||
return input, httperrors.NewMissingParameterError("receivers | contacts")
|
||||
}
|
||||
// check receivers
|
||||
if len(input.Receivers) > 0 {
|
||||
receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...)
|
||||
if err != nil {
|
||||
return input, errors.Wrap(err, "ReceiverManager.FetchByIDs")
|
||||
}
|
||||
idSet := sets.NewString()
|
||||
nameSet := sets.NewString()
|
||||
for i := range receivers {
|
||||
idSet.Insert(receivers[i].Id)
|
||||
nameSet.Insert(receivers[i].Name)
|
||||
}
|
||||
for _, re := range input.Receivers {
|
||||
if idSet.Has(re) || nameSet.Has(re) {
|
||||
continue
|
||||
}
|
||||
return input, httperrors.NewInputParameterError("no such receiver whose uid is %q", re)
|
||||
}
|
||||
input.Receivers = idSet.UnsortedList()
|
||||
}
|
||||
nowStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
// hack
|
||||
input.Name = fmt.Sprintf("%s(%s)", input.Topic, nowStr)
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
n.ReceivedAt = time.Now()
|
||||
n.Id = db.DefaultUUIDGenerator()
|
||||
var input api.NotificationCreateInput
|
||||
err := data.Unmarshal(&input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range input.Receivers {
|
||||
_, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], n.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "ReceiverNotificationManager.Create")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *SNotification) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
n.SetStatus(userCred, api.NOTIFICATION_STATUS_RECEIVED, "")
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("NotificationSendTask newTask error %v", err)
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) FetchCustomizeColumns(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
objs []interface{},
|
||||
fields stringutils2.SSortedStrings,
|
||||
isList bool,
|
||||
) []api.NotificationDetails {
|
||||
rows := make([]api.NotificationDetails, len(objs))
|
||||
|
||||
resRows := nm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
|
||||
var err error
|
||||
for i := range rows {
|
||||
rows[i], err = objs[i].(*SNotification).getMoreDetails(ctx, query, rows[i])
|
||||
if err != nil {
|
||||
log.Errorf("Notification.getMoreDetails: %v", err)
|
||||
}
|
||||
rows[i].StatusStandaloneResourceDetails = resRows[i]
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func (n *SNotification) GetExtraDetails(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
isList bool,
|
||||
) (api.NotificationDetails, error) {
|
||||
return api.NotificationDetails{}, nil
|
||||
}
|
||||
|
||||
func (n *SNotification) ReceiverNotificationsNotOK() ([]SReceiverNotification, error) {
|
||||
rnq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK)
|
||||
rns := make([]SReceiverNotification, 0, 1)
|
||||
err := db.FetchModelObjects(ReceiverNotificationManager, rnq, &rns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rns, nil
|
||||
}
|
||||
|
||||
func (n *SNotification) ReceiveDetails() ([]api.ReceiveDetail, error) {
|
||||
subRQ := ReceiverManager.Query("id", "name").SubQuery()
|
||||
q := ReceiverNotificationManager.Query("receiver_id", "notification_id", "contact", "send_at", "send_by", "status", "failed_reason").Equals("notification_id", n.Id)
|
||||
q.AppendField(subRQ.Field("name", "receiver_name"))
|
||||
q = q.Join(subRQ, sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")))
|
||||
ret := make([]api.ReceiveDetail, 0, 2)
|
||||
err := q.All(&ret)
|
||||
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
||||
log.Errorf("SQuery.All: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (n *SNotification) getMoreDetails(ctx context.Context, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) {
|
||||
// get title adn content
|
||||
p, err := TemplateManager.NotifyFilter(n.ContactType, n.Topic, n.Message)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Title = p.Title
|
||||
out.Content = p.Message
|
||||
// get receive details
|
||||
out.ReceiveDetails, err = n.ReceiveDetails()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
if !data.Contains("contacts") {
|
||||
return db.IsAdminAllowCreate(userCred, nm)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) ResourceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeUser
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) NamespaceScope() rbacutils.TRbacScope {
|
||||
return rbacutils.ScopeSystem
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
return db.FetchUserInfo(ctx, data)
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery {
|
||||
if owner == nil {
|
||||
return q
|
||||
}
|
||||
switch scope {
|
||||
case rbacutils.ScopeDomain:
|
||||
subRq := ReceiverManager.Query("id").Equals("domain_id", owner.GetDomainId()).SubQuery()
|
||||
subRNq := ReceiverNotificationManager.Query("notification_id").Join(subRq, sqlchemy.Equals(q.Field("receiver_id"), subRq.Field("id"))).SubQuery()
|
||||
q = q.Join(subRNq, sqlchemy.Equals(q.Field("id"), subRNq.Field("notification_id")))
|
||||
case rbacutils.ScopeProject, rbacutils.ScopeUser:
|
||||
subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", owner.GetUserId()).SubQuery()
|
||||
q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (n *SNotification) AddOne() error {
|
||||
_, err := db.Update(n, func() error {
|
||||
n.SendTimes += 1
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
NOTIFY_RECEIVED = "received" // Received a task about sending a notification
|
||||
NOTIFY_SENT = "sent" // Nofity module has sent notification, but result unkown
|
||||
NOTIFY_OK = "sent_ok" // Notification was sent successfully
|
||||
NOTIFY_FAIL = "sent_fail" // That sent a notification is failed
|
||||
NOTIFY_REMOVED = "removed"
|
||||
)
|
||||
|
||||
func (self *SNotificationManager) singleRowLineQuery(sqlStr string, dest ...interface{}) error {
|
||||
q := sqlchemy.NewRawQuery(sqlStr)
|
||||
rows, err := q.Rows()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "q.Rows")
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err := rows.Scan(dest...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "rows.Scan")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) InitializeData() error {
|
||||
// check
|
||||
sqlStr := fmt.Sprintf(
|
||||
"select count(*) as total from (select cluster_id from %s where status='%s' and contact_type='webconsole' group by cluster_id) as cluster",
|
||||
oldmodels.NotificationManager.TableSpec().Name(),
|
||||
NOTIFY_REMOVED,
|
||||
)
|
||||
var count int
|
||||
err := self.singleRowLineQuery(sqlStr, &count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count >= options.Options.MaxSyncNotification {
|
||||
return nil
|
||||
}
|
||||
|
||||
limitTimeStr := time.Now().Add(time.Duration(-30) * time.Hour * 24).Format("2006-01-02 15:04:05")
|
||||
|
||||
// get min received_at
|
||||
var minReceivedAt time.Time
|
||||
sqlStr = fmt.Sprintf(
|
||||
"select min(received_at) as min_received_at from (select received_at from %s where received_at > '%s' and contact_type = 'webconsole' group by cluster_id order by received_at desc limit %d) as cluster",
|
||||
oldmodels.NotificationManager.TableSpec().Name(),
|
||||
limitTimeStr,
|
||||
options.Options.MaxSyncNotification,
|
||||
)
|
||||
err = self.singleRowLineQuery(sqlStr, &minReceivedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("minReceivedAt: %s", minReceivedAt)
|
||||
|
||||
ctx := context.Background()
|
||||
q := oldmodels.NotificationManager.Query().Equals("contact_type", api.WEBCONSOLE).GT("received_at", minReceivedAt).NotEquals("status", NOTIFY_REMOVED)
|
||||
n := q.Count()
|
||||
log.Infof("total %d notifications to sync", n)
|
||||
oldNotifications := make([]oldmodels.SNotification, 0, n)
|
||||
err = db.FetchModelObjects(oldmodels.NotificationManager, q, &oldNotifications)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.FetchModelObjects")
|
||||
}
|
||||
|
||||
// build cluster=>Notification
|
||||
cnMap := make(map[string][]*oldmodels.SNotification)
|
||||
for i := range oldNotifications {
|
||||
clusterId := oldNotifications[i].ClusterID
|
||||
if _, ok := cnMap[clusterId]; !ok {
|
||||
cnMap[clusterId] = make([]*oldmodels.SNotification, 0, 2)
|
||||
}
|
||||
cnMap[clusterId] = append(cnMap[clusterId], &oldNotifications[i])
|
||||
}
|
||||
|
||||
for _, oldNotifications := range cnMap {
|
||||
oldNotificaion := oldNotifications[0]
|
||||
newNotification := SNotification{
|
||||
ContactType: oldNotificaion.ContactType,
|
||||
Topic: oldNotificaion.Topic,
|
||||
Priority: oldNotificaion.Priority,
|
||||
Message: oldNotificaion.Msg,
|
||||
ReceivedAt: oldNotificaion.ReceivedAt,
|
||||
}
|
||||
newNotification.Id = db.DefaultUUIDGenerator()
|
||||
statusMap := make(map[string]int, 4)
|
||||
for _, oldNotificaion := range oldNotifications {
|
||||
rn := SReceiverNotification{
|
||||
ReceiverID: oldNotificaion.UID,
|
||||
NotificationID: newNotification.Id,
|
||||
SendAt: oldNotificaion.SendAt,
|
||||
SendBy: oldNotificaion.SendBy,
|
||||
Status: oldNotificaion.Status,
|
||||
}
|
||||
if rn.Status == NOTIFY_SENT {
|
||||
rn.Status = api.NOTIFICATION_STATUS_SENDING
|
||||
}
|
||||
statusMap[rn.Status] += 1
|
||||
err := ReceiverNotificationManager.TableSpec().Insert(ctx, &rn)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "TableSpec().Insert")
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case statusMap[api.RECEIVER_NOTIFICATION_OK] == len(oldNotifications):
|
||||
newNotification.Status = api.NOTIFICATION_STATUS_OK
|
||||
case statusMap[api.RECEIVER_NOTIFICATION_RECEIVED] == len(oldNotifications):
|
||||
newNotification.Status = api.NOTIFICATION_STATUS_RECEIVED
|
||||
case statusMap[api.RECEIVER_NOTIFICATION_FAIL] == len(oldNotifications):
|
||||
newNotification.Status = api.NOTIFICATION_STATUS_FAILED
|
||||
case statusMap[api.RECEIVER_NOTIFICATION_FAIL] == 0 && statusMap[api.RECEIVER_NOTIFICATION_SENT] > 0:
|
||||
newNotification.Status = api.NOTIFICATION_STATUS_SENDING
|
||||
default:
|
||||
newNotification.Status = api.NOTIFICATION_STATUS_PART_OK
|
||||
}
|
||||
err := self.TableSpec().InsertOrUpdate(ctx, &newNotification)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "TableSpec().InsertOrUpdate")
|
||||
}
|
||||
|
||||
// mark removed
|
||||
for _, oldNotificaion := range oldNotifications {
|
||||
_, err := db.Update(oldNotificaion, func() error {
|
||||
oldNotificaion.Status = NOTIFY_REMOVED
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 通知消息列表
|
||||
func (nm *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NotificationListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := nm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(input.ContactType) > 0 {
|
||||
q = q.Equals("contact_type", input.ContactType)
|
||||
}
|
||||
if len(input.ReceiverId) > 0 {
|
||||
subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", input.ReceiverId).SubQuery()
|
||||
q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||||
q := nm.Query().NotEquals("status", api.NOTIFICATION_STATUS_OK).LT("send_times", options.Options.MaxSendTimes)
|
||||
ns := make([]SNotification, 0, 2)
|
||||
err := db.FetchModelObjects(nm, q, &ns)
|
||||
if err != nil {
|
||||
log.Errorf("fail to FetchModelObjects: %v", err)
|
||||
return
|
||||
}
|
||||
for i := range ns {
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", &ns[i], userCred, nil, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("NotificationSendTask newTask error %v", err)
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
884
pkg/notify/models/receiver.go
Normal file
884
pkg/notify/models/receiver.go
Normal file
@@ -0,0 +1,884 @@
|
||||
// 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 (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/informer"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/notify/oldmodels"
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
var (
|
||||
AllContactTypes = []string{
|
||||
api.EMAIL,
|
||||
api.MOBILE,
|
||||
api.DINGTALK,
|
||||
api.FEISHU,
|
||||
api.WORKWX,
|
||||
api.WEBCONSOLE,
|
||||
}
|
||||
AllSubContactTypes = []string{
|
||||
api.DINGTALK,
|
||||
api.FEISHU,
|
||||
api.WORKWX,
|
||||
api.WEBCONSOLE,
|
||||
}
|
||||
AllRobotContactTypes = []string{
|
||||
api.FEISHU_ROBOT,
|
||||
api.DINGTALK_ROBOT,
|
||||
api.WORKWX_ROBOT,
|
||||
}
|
||||
)
|
||||
|
||||
type SReceiverManager struct {
|
||||
db.SStatusStandaloneResourceBaseManager
|
||||
db.SDomainizedResourceBaseManager
|
||||
db.SEnabledResourceBaseManager
|
||||
}
|
||||
|
||||
var ReceiverManager *SReceiverManager
|
||||
|
||||
func init() {
|
||||
ReceiverManager = &SReceiverManager{
|
||||
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
|
||||
SReceiver{},
|
||||
"receivers_tbl",
|
||||
"receiver",
|
||||
"receivers",
|
||||
),
|
||||
}
|
||||
ReceiverManager.SetVirtualObject(ReceiverManager)
|
||||
}
|
||||
|
||||
type SReceiver struct {
|
||||
db.SStatusStandaloneResourceBase
|
||||
db.SDomainizedResourceBase
|
||||
db.SEnabledResourceBase
|
||||
|
||||
Email string `width:"64" nullable:"false" create:"optional" update:"user" get:"user" list:"admin"`
|
||||
Mobile string `width:"16" nullable:"false" create:"optional" update:"user" get:"user" list:"admin"`
|
||||
|
||||
// swagger:ignore
|
||||
EnabledEmail tristate.TriState `nullable:"false" default:"false" update:"user"`
|
||||
// swagger:ignore
|
||||
VerifiedEmail tristate.TriState `nullable:"false" default:"false" update:"user"`
|
||||
|
||||
// swagger:ignore
|
||||
EnabledMobile tristate.TriState `nullable:"false" default:"false" update:"user"`
|
||||
// swagger:ignore
|
||||
VerifiedMobile tristate.TriState `nullable:"false" default:"false" update:"user"`
|
||||
|
||||
// swagger:ignore
|
||||
subContactCache map[string]*SSubContact `json:"-"`
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) InitializeData() error {
|
||||
ctx := context.Background()
|
||||
userCred := auth.AdminCredential()
|
||||
log.Infof("Init Receiver...")
|
||||
// Fetch all old SContact
|
||||
q := oldmodels.ContactManager.Query()
|
||||
contacts := make([]oldmodels.SContact, 0, 50)
|
||||
err := db.FetchModelObjects(oldmodels.ContactManager, q, &contacts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.FetchModelObjects")
|
||||
}
|
||||
|
||||
// build uid map
|
||||
uids := make([]string, 0, 10)
|
||||
contactMap := make(map[string][]*oldmodels.SContact, 10)
|
||||
for i := range contacts {
|
||||
uid := contacts[i].UID
|
||||
if _, ok := contactMap[uid]; !ok {
|
||||
contactMap[uid] = make([]*oldmodels.SContact, 0, 4)
|
||||
uids = append(uids, uid)
|
||||
}
|
||||
contactMap[uid] = append(contactMap[uid], &contacts[i])
|
||||
}
|
||||
|
||||
// build uid->uname map
|
||||
userMap, err := oldmodels.UserCacheManager.FetchUsersByIDs(context.Background(), uids)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "oldmodels.UserCacheManager.FetchUsersByIDs")
|
||||
}
|
||||
|
||||
// build Receivers
|
||||
for uid, contacts := range contactMap {
|
||||
var receiver SReceiver
|
||||
receiver.subContactCache = make(map[string]*SSubContact)
|
||||
receiver.Enabled = tristate.True
|
||||
receiver.Status = api.RECEIVER_STATUS_READY
|
||||
receiver.Id = uid
|
||||
user, ok := userMap[uid]
|
||||
if !ok {
|
||||
log.Errorf("no user %q in usercache", uid)
|
||||
} else {
|
||||
receiver.Name = user.Name
|
||||
receiver.DomainId = user.DomainId
|
||||
}
|
||||
webconsole := false
|
||||
for _, contact := range contacts {
|
||||
switch contact.ContactType {
|
||||
case api.EMAIL:
|
||||
receiver.Email = contact.Contact
|
||||
if contact.Enabled == "1" {
|
||||
receiver.EnabledEmail = tristate.True
|
||||
} else {
|
||||
receiver.EnabledEmail = tristate.False
|
||||
}
|
||||
if contact.Status == oldmodels.CONTACT_VERIFIED {
|
||||
receiver.VerifiedEmail = tristate.True
|
||||
} else {
|
||||
receiver.VerifiedEmail = tristate.False
|
||||
}
|
||||
case api.MOBILE:
|
||||
receiver.Mobile = contact.Contact
|
||||
if contact.Enabled == "1" {
|
||||
receiver.EnabledMobile = tristate.True
|
||||
} else {
|
||||
receiver.EnabledMobile = tristate.False
|
||||
}
|
||||
if contact.Status == oldmodels.CONTACT_VERIFIED {
|
||||
receiver.VerifiedMobile = tristate.True
|
||||
} else {
|
||||
receiver.VerifiedMobile = tristate.False
|
||||
}
|
||||
default:
|
||||
var subContact SSubContact
|
||||
subContact.Type = contact.ContactType
|
||||
if subContact.Type == api.WEBCONSOLE {
|
||||
webconsole = true
|
||||
subContact.Contact = uid
|
||||
} else {
|
||||
subContact.Contact = contact.Contact
|
||||
}
|
||||
subContact.ReceiverID = uid
|
||||
subContact.ParentContactType = api.MOBILE
|
||||
if contact.Enabled == "1" {
|
||||
subContact.Enabled = tristate.True
|
||||
} else {
|
||||
subContact.Enabled = tristate.False
|
||||
}
|
||||
if contact.Status == oldmodels.CONTACT_VERIFIED && len(contact.Contact) > 0 {
|
||||
subContact.Verified = tristate.True
|
||||
} else {
|
||||
subContact.Verified = tristate.False
|
||||
}
|
||||
receiver.subContactCache[contact.ContactType] = &subContact
|
||||
}
|
||||
}
|
||||
if !webconsole {
|
||||
receiver.subContactCache[api.WEBCONSOLE] = &SSubContact{
|
||||
ReceiverID: receiver.Id,
|
||||
Type: api.WEBCONSOLE,
|
||||
Contact: receiver.Id,
|
||||
ParentContactType: "",
|
||||
Enabled: tristate.True,
|
||||
Verified: tristate.True,
|
||||
}
|
||||
}
|
||||
err := rm.TableSpec().InsertOrUpdate(ctx, &receiver)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "InsertOrUpdate")
|
||||
}
|
||||
err = receiver.PushCache(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "PushCache")
|
||||
}
|
||||
//delete old one
|
||||
for _, contact := range contacts {
|
||||
err := contact.Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ReceiverCreateInput) (api.ReceiverCreateInput, error) {
|
||||
var err error
|
||||
input.StatusStandaloneResourceCreateInput, err = rm.SStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusStandaloneResourceCreateInput)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
// check uid
|
||||
session := auth.GetSession(ctx, userCred, "", "")
|
||||
if len(input.UID) > 0 {
|
||||
userObj, err := modules.UsersV3.GetById(session, input.UID, nil)
|
||||
if err != nil {
|
||||
if jErr, ok := err.(*httputils.JSONClientError); ok {
|
||||
if jErr.Code == 404 {
|
||||
return input, httperrors.NewInputParameterError("no such user")
|
||||
}
|
||||
}
|
||||
return input, err
|
||||
}
|
||||
uname, _ := userObj.GetString("name")
|
||||
input.UName = uname
|
||||
domainId, _ := userObj.GetString("domain_id")
|
||||
input.ProjectDomainId = domainId
|
||||
} else {
|
||||
if len(input.UName) == 0 {
|
||||
return input, httperrors.NewMissingParameterError("uid or uname")
|
||||
} else {
|
||||
userObj, err := modules.UsersV3.GetByName(session, input.UName, nil)
|
||||
if err != nil {
|
||||
if jErr, ok := err.(*httputils.JSONClientError); ok {
|
||||
if jErr.Code == 404 {
|
||||
return input, httperrors.NewInputParameterError("no such user")
|
||||
}
|
||||
}
|
||||
return input, err
|
||||
}
|
||||
uid, _ := userObj.GetString("id")
|
||||
input.UID = uid
|
||||
domainId, _ := userObj.GetString("domain_id")
|
||||
input.ProjectDomainId = domainId
|
||||
}
|
||||
}
|
||||
// hack
|
||||
input.Name = input.UName
|
||||
// validate email
|
||||
if ok := regutils.MatchEmail(input.Email); !ok {
|
||||
return input, httperrors.NewInputParameterError("invalid email")
|
||||
}
|
||||
// validate mobile
|
||||
if ok := regutils.MatchMobile(input.Mobile); !ok {
|
||||
return input, httperrors.NewInputParameterError("invalid mobile")
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) IsEnabledContactType(ct string) (bool, error) {
|
||||
if utils.IsInStringArray(ct, AllRobotContactTypes) {
|
||||
return true, nil
|
||||
}
|
||||
cts, err := r.GetEnabledContactTypes()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "GetEnabledContactTypes")
|
||||
}
|
||||
return utils.IsInStringArray(ct, cts), nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) IsVerifiedContactType(ct string) (bool, error) {
|
||||
if utils.IsInStringArray(ct, AllRobotContactTypes) {
|
||||
return true, nil
|
||||
}
|
||||
cts, err := r.GetVerifiedContactTypes()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "GetVerifiedContactTypes")
|
||||
}
|
||||
return utils.IsInStringArray(ct, cts), nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) GetEnabledContactTypes() ([]string, error) {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, 0, 1)
|
||||
// for email and mobile
|
||||
if r.EnabledEmail.IsTrue() {
|
||||
ret = append(ret, api.EMAIL)
|
||||
}
|
||||
if r.EnabledMobile.IsTrue() {
|
||||
ret = append(ret, api.MOBILE)
|
||||
}
|
||||
for subct, subc := range r.subContactCache {
|
||||
if subc.Enabled.IsTrue() {
|
||||
ret = append(ret, subct)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) setEnabledContactType(contactType string, enabled bool) {
|
||||
switch contactType {
|
||||
case api.EMAIL:
|
||||
r.EnabledEmail = tristate.NewFromBool(enabled)
|
||||
case api.MOBILE:
|
||||
r.EnabledMobile = tristate.NewFromBool(enabled)
|
||||
default:
|
||||
if sc, ok := r.subContactCache[contactType]; ok {
|
||||
sc.Enabled = tristate.NewFromBool(enabled)
|
||||
} else {
|
||||
r.subContactCache[contactType] = &SSubContact{
|
||||
Type: contactType,
|
||||
ReceiverID: r.Id,
|
||||
Enabled: tristate.NewFromBool(enabled),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SReceiver) SetEnabledContactTypes(contactTypes []string) error {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return err
|
||||
}
|
||||
ctSet := sets.NewString(contactTypes...)
|
||||
for _, ct := range AllContactTypes {
|
||||
if ctSet.Has(ct) {
|
||||
r.setEnabledContactType(ct, true)
|
||||
} else {
|
||||
r.setEnabledContactType(ct, false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) MarkContactTypeVerified(contactType string) error {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if sc, ok := r.subContactCache[contactType]; ok {
|
||||
sc.Verified = tristate.True
|
||||
} else {
|
||||
r.subContactCache[contactType] = &SSubContact{
|
||||
ReceiverID: r.Id,
|
||||
Verified: tristate.True,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) setVerifiedContactType(contactType string, enabled bool) {
|
||||
switch contactType {
|
||||
case api.EMAIL:
|
||||
r.VerifiedEmail = tristate.NewFromBool(enabled)
|
||||
case api.MOBILE:
|
||||
r.VerifiedMobile = tristate.NewFromBool(enabled)
|
||||
default:
|
||||
if sc, ok := r.subContactCache[contactType]; ok {
|
||||
sc.Verified = tristate.NewFromBool(enabled)
|
||||
} else {
|
||||
r.subContactCache[contactType] = &SSubContact{
|
||||
ReceiverID: r.Id,
|
||||
Verified: tristate.NewFromBool(enabled),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SReceiver) GetVerifiedContactTypes() ([]string, error) {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, 0, 1)
|
||||
// for email and mobile
|
||||
if r.VerifiedEmail.IsTrue() {
|
||||
ret = append(ret, api.EMAIL)
|
||||
}
|
||||
if r.VerifiedMobile.IsTrue() {
|
||||
ret = append(ret, api.MOBILE)
|
||||
}
|
||||
for subct, subc := range r.subContactCache {
|
||||
if subc.Verified.IsTrue() {
|
||||
ret = append(ret, subct)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) SetVerifiedContactTypes(contactTypes []string) error {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return err
|
||||
}
|
||||
ctSet := sets.NewString(contactTypes...)
|
||||
for _, ct := range AllContactTypes {
|
||||
if ctSet.Has(ct) {
|
||||
r.setVerifiedContactType(ct, true)
|
||||
} else {
|
||||
r.setVerifiedContactType(ct, false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) PullCache(force bool) error {
|
||||
if !force && r.subContactCache != nil {
|
||||
return nil
|
||||
}
|
||||
cache, err := SubContactManager.fetchMapByReceiverID(r.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.subContactCache = cache
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) PushCache(ctx context.Context) error {
|
||||
for subct, subc := range r.subContactCache {
|
||||
err := SubContactManager.TableSpec().InsertOrUpdate(ctx, subc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "fail to save subcontact %q to db", subct)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) EnabledContactFilter(contactType string, q *sqlchemy.SQuery) *sqlchemy.SQuery {
|
||||
subQuery := SubContactManager.Query("receiver_id").Equals("type", contactType).IsTrue("enabled").SubQuery()
|
||||
q = q.Join(subQuery, sqlchemy.Equals(subQuery.Field("receiver_id"), q.Field("id")))
|
||||
return q
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) VerifiedContactFilter(contactType string, q *sqlchemy.SQuery) *sqlchemy.SQuery {
|
||||
subQuery := SubContactManager.Query("receiver_id").Equals("type", contactType).IsTrue("verified").SubQuery()
|
||||
q = q.Join(subQuery, sqlchemy.Equals(subQuery.Field("receiver_id"), q.Field("id")))
|
||||
return q
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ReceiverListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := rm.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err = rm.SDomainizedResourceBaseManager.ListItemFilter(ctx, q, userCred, input.DomainizedResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err = rm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(input.UID) > 0 {
|
||||
q = q.Equals("id", input.UID)
|
||||
}
|
||||
if len(input.UName) > 0 {
|
||||
q = q.Equals("name", input.UName)
|
||||
}
|
||||
if len(input.EnabledContactType) > 0 {
|
||||
q = rm.EnabledContactFilter(input.EnabledContactType, q)
|
||||
}
|
||||
if len(input.VerifiedContactType) > 0 {
|
||||
q = rm.VerifiedContactFilter(input.VerifiedContactType, q)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.ReceiverDetails {
|
||||
sRows := rm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
dRows := rm.SDomainizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||||
rows := make([]api.ReceiverDetails, len(objs))
|
||||
var err error
|
||||
for i := range rows {
|
||||
rows[i].StatusStandaloneResourceDetails = sRows[i]
|
||||
rows[i].DomainizedResourceInfo = dRows[i]
|
||||
user := objs[i].(*SReceiver)
|
||||
if rows[i].EnabledContactTypes, err = user.GetEnabledContactTypes(); err != nil {
|
||||
log.Errorf("GetEnabledContactTypes: %v", err)
|
||||
}
|
||||
if rows[i].VerifiedContactTypes, err = user.GetVerifiedContactTypes(); err != nil {
|
||||
log.Errorf("GetVerifiedContactTypes: %v", err)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||||
q, err := rm.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err = rm.SDomainizedResourceBaseManager.QueryDistinctExtraField(q, field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ReceiverListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := rm.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err = rm.SDomainizedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.DomainizedResourceListInput)
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
r.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
||||
// set status
|
||||
r.SetStatus(userCred, api.RECEIVER_STATUS_PULLING, "")
|
||||
logclient.AddActionLogWithContext(ctx, r, logclient.ACT_CREATE, nil, userCred, true)
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "SubcontactPullTask", r, userCred, nil, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("ContactPullTask newTask error %v", err)
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SReceiver) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
err := r.SStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var input api.ReceiverCreateInput
|
||||
err = data.Unmarshal(&input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// set id and name
|
||||
r.Id = input.UID
|
||||
r.Name = input.UName
|
||||
r.DomainId = input.ProjectDomainId
|
||||
if input.Enabled == nil {
|
||||
r.Enabled = tristate.True
|
||||
}
|
||||
err = r.SetEnabledContactTypes(input.EnabledContactTypes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "SetEnabledContactTypes")
|
||||
}
|
||||
err = r.PushCache(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "PushCache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverUpdateInput) (api.ReceiverUpdateInput, error) {
|
||||
var err error
|
||||
input.StatusStandaloneResourceBaseUpdateInput, err = r.SStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.StatusStandaloneResourceBaseUpdateInput)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
// validate email
|
||||
if ok := regutils.MatchEmail(input.Email); !ok {
|
||||
return input, httperrors.NewInputParameterError("invalid email")
|
||||
}
|
||||
// validate mobile
|
||||
if ok := regutils.MatchMobile(input.Mobile); !ok {
|
||||
return input, httperrors.NewInputParameterError("invalid mobile")
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
r.SStatusStandaloneResourceBase.PreUpdate(ctx, userCred, query, data)
|
||||
var input api.ReceiverUpdateInput
|
||||
err := data.Unmarshal(&input)
|
||||
if err != nil {
|
||||
log.Errorf("fail to unmarshal to ContactUpdateInput: %v", err)
|
||||
}
|
||||
err = r.PullCache(false)
|
||||
if err != nil {
|
||||
log.Errorf("PullCache: %v", err)
|
||||
}
|
||||
err = r.SetEnabledContactTypes(input.EnabledContactTypes)
|
||||
if len(input.Email) != 0 {
|
||||
r.VerifiedEmail = tristate.False
|
||||
for _, c := range r.subContactCache {
|
||||
if c.ParentContactType == input.Email {
|
||||
c.Verified = tristate.False
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(input.Mobile) != 0 {
|
||||
r.VerifiedMobile = tristate.False
|
||||
for _, c := range r.subContactCache {
|
||||
if c.ParentContactType == input.Mobile {
|
||||
c.Verified = tristate.False
|
||||
}
|
||||
}
|
||||
}
|
||||
err = r.PushCache(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("PushCache: %v", err)
|
||||
}
|
||||
err = ReceiverManager.TableSpec().InsertOrUpdate(ctx, r)
|
||||
if err != nil {
|
||||
log.Errorf("InsertOrUpdate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
r.SStatusStandaloneResourceBase.PostUpdate(ctx, userCred, query, data)
|
||||
// set status
|
||||
r.SetStatus(userCred, api.RECEIVER_STATUS_PULLING, "")
|
||||
logclient.AddActionLogWithContext(ctx, r, logclient.ACT_UPDATE, nil, userCred, true)
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "SubcontactPullTask", r, userCred, nil, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("ContactPullTask newTask error %v", err)
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SReceiver) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
err := r.PullCache(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sc := range r.subContactCache {
|
||||
err := sc.Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.SStatusStandaloneResourceBase.Delete(ctx, userCred)
|
||||
}
|
||||
|
||||
func (r *SReceiver) IsOwner(userCred mcclient.TokenCredential) bool {
|
||||
return r.Id == userCred.GetUserId()
|
||||
}
|
||||
|
||||
func (r *SReceiver) AllowPerformTriggerVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "trigger_verify")
|
||||
}
|
||||
|
||||
func (r *SReceiver) PerformTriggerVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverTriggerVerifyInput) (jsonutils.JSONObject, error) {
|
||||
if len(input.ContactType) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("contact_type")
|
||||
}
|
||||
if !utils.IsInStringArray(input.ContactType, []string{api.EMAIL, api.MOBILE}) {
|
||||
return nil, httperrors.NewInputParameterError("not support such contact type %q", input.ContactType)
|
||||
}
|
||||
_, err := VerificationManager.Create(ctx, r.Id, input.ContactType)
|
||||
if err == ErrVerifyFrequently {
|
||||
return nil, httperrors.NewForbiddenError("Send verify message too frequently, please try again later")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "VerificationSendTask", r, userCred, params, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("ContactPullTask newTask error %v", err)
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) AllowPerformVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "verify")
|
||||
}
|
||||
|
||||
func (r *SReceiver) PerformVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverVerifyInput) (jsonutils.JSONObject, error) {
|
||||
if len(input.ContactType) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("contact_type")
|
||||
}
|
||||
if !utils.IsInStringArray(input.ContactType, []string{api.EMAIL, api.MOBILE}) {
|
||||
return nil, httperrors.NewInputParameterError("not support such contact type %q", input.ContactType)
|
||||
}
|
||||
verification, err := VerificationManager.Get(r.Id, input.ContactType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if verification.Token != input.Token {
|
||||
return nil, httperrors.NewInputParameterError("wrong token")
|
||||
}
|
||||
_, err = db.Update(r, func() error {
|
||||
switch input.ContactType {
|
||||
case api.EMAIL:
|
||||
r.VerifiedEmail = tristate.True
|
||||
case api.MOBILE:
|
||||
r.VerifiedMobile = tristate.True
|
||||
default:
|
||||
// no way
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (r *SReceiver) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) bool {
|
||||
return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "enable")
|
||||
}
|
||||
|
||||
func (r *SReceiver) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) {
|
||||
err := db.EnabledPerformEnable(r, ctx, userCred, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformDisableInput) bool {
|
||||
return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "disable")
|
||||
}
|
||||
|
||||
func (r *SReceiver) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformDisableInput) (jsonutils.JSONObject, error) {
|
||||
err := db.EnabledPerformEnable(r, ctx, userCred, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Implemente interface EventHandler
|
||||
func (rm *SReceiverManager) OnAdd(obj *jsonutils.JSONDict) {
|
||||
// do nothing
|
||||
return
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) OnUpdate(oldObj, newObj *jsonutils.JSONDict) {
|
||||
userId, _ := newObj.GetString("id")
|
||||
receivers, err := rm.FetchByIDs(context.Background(), userId)
|
||||
if err != nil {
|
||||
log.Errorf("fail to FetchByIDs: %v", err)
|
||||
return
|
||||
}
|
||||
receiver := &receivers[0]
|
||||
uname, _ := newObj.GetString("name")
|
||||
domainId, _ := newObj.GetString("domain_id")
|
||||
if receiver.Name == uname && receiver.DomainId == domainId {
|
||||
return
|
||||
}
|
||||
_, err = db.Update(receiver, func() error {
|
||||
receiver.Name = uname
|
||||
receiver.DomainId = domainId
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("fail to update uname of contact %q: %v", receiver.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) OnDelete(obj *jsonutils.JSONDict) {
|
||||
userId, _ := obj.GetString("id")
|
||||
receivers, err := rm.FetchByIDs(context.Background(), userId)
|
||||
if err != nil {
|
||||
log.Errorf("fail to FetchByIDs: %v", err)
|
||||
return
|
||||
}
|
||||
receiver := &receivers[0]
|
||||
err = receiver.Delete(context.Background(), auth.GetAdminSession(context.Background(), "", "").GetToken())
|
||||
if err != nil {
|
||||
log.Errorf("fail to delete contact %q: %v", receiver.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) StartWatchUserInKeystone() error {
|
||||
adminSession := auth.GetAdminSession(context.Background(), "", "")
|
||||
watchMan, err := informer.NewWatchManagerBySession(adminSession)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resMan := &modules.UsersV3
|
||||
return watchMan.For(resMan).AddEventHandler(context.Background(), rm)
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) FetchByIDs(ctx context.Context, ids ...string) ([]SReceiver, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var err error
|
||||
q := rm.Query()
|
||||
if len(ids) == 1 {
|
||||
q = q.Equals("id", ids[0])
|
||||
} else {
|
||||
q = q.In("id", ids)
|
||||
}
|
||||
contacts := make([]SReceiver, 0, len(ids))
|
||||
err = db.FetchModelObjects(rm, q, &contacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (rm *SReceiverManager) FetchByIdOrNames(ctx context.Context, idOrNames ...string) ([]SReceiver, error) {
|
||||
if len(idOrNames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var err error
|
||||
q := rm.Query()
|
||||
if len(idOrNames) == 1 {
|
||||
q = q.Filter(sqlchemy.OR(
|
||||
sqlchemy.Equals(q.Field("id"), idOrNames[0]),
|
||||
sqlchemy.Equals(q.Field("name"), idOrNames[0]),
|
||||
))
|
||||
} else {
|
||||
q = q.Filter(sqlchemy.OR(
|
||||
sqlchemy.In(q.Field("id"), idOrNames),
|
||||
sqlchemy.In(q.Field("name"), idOrNames),
|
||||
))
|
||||
}
|
||||
receivers := make([]SReceiver, 0, len(idOrNames))
|
||||
err = db.FetchModelObjects(rm, q, &receivers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return receivers, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) GetExtraDetails(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
isList bool,
|
||||
) (api.ReceiverDetails, error) {
|
||||
return api.ReceiverDetails{}, nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) SetContact(cType string, contact string) error {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return err
|
||||
}
|
||||
switch cType {
|
||||
case api.EMAIL:
|
||||
r.Email = contact
|
||||
case api.MOBILE:
|
||||
r.Mobile = contact
|
||||
default:
|
||||
if sc, ok := r.subContactCache[cType]; ok {
|
||||
sc.Contact = contact
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SReceiver) GetContact(cType string) (string, error) {
|
||||
if err := r.PullCache(false); err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch {
|
||||
case cType == api.EMAIL:
|
||||
return r.Email, nil
|
||||
case cType == api.MOBILE:
|
||||
return r.Mobile, nil
|
||||
case utils.IsInStringArray(cType, AllRobotContactTypes):
|
||||
return r.Mobile, nil
|
||||
default:
|
||||
if sc, ok := r.subContactCache[cType]; ok {
|
||||
return sc.Contact, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
109
pkg/notify/models/receiver_notification.go
Normal file
109
pkg/notify/models/receiver_notification.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
var ReceiverNotificationManager *SReceiverNotificationManager
|
||||
|
||||
func init() {
|
||||
db.InitManager(func() {
|
||||
ReceiverNotificationManager = &SReceiverNotificationManager{
|
||||
SJointResourceBaseManager: db.NewJointResourceBaseManager(
|
||||
SReceiverNotification{},
|
||||
"receivernotification_tbl",
|
||||
"receivernotification",
|
||||
"receivernotifications",
|
||||
NotificationManager,
|
||||
ReceiverManager,
|
||||
),
|
||||
}
|
||||
ReceiverNotificationManager.SetVirtualObject(ReceiverNotificationManager)
|
||||
})
|
||||
}
|
||||
|
||||
type SReceiverNotificationManager struct {
|
||||
db.SJointResourceBaseManager
|
||||
}
|
||||
|
||||
// +onecloud:swagger-gen-ignore
|
||||
type SReceiverNotification struct {
|
||||
db.SJointResourceBase
|
||||
|
||||
ReceiverID string `width:"128" charset:"ascii" nullable:"false"`
|
||||
NotificationID string `width:"128" charset:"ascii" nullable:"false"`
|
||||
// ignore if ReceiverID is not empty
|
||||
Contact string `width:"128" nullable:"false"`
|
||||
SendAt time.Time `nullable:"false"`
|
||||
SendBy string `width:"128" nullable:"false"`
|
||||
Status string `width:"36" charset:"ascii"`
|
||||
FailedReason string `width:"1024"`
|
||||
}
|
||||
|
||||
func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverID, notificationID string) (*SReceiverNotification, error) {
|
||||
rn := &SReceiverNotification{
|
||||
ReceiverID: receiverID,
|
||||
NotificationID: notificationID,
|
||||
Status: api.RECEIVER_NOTIFICATION_RECEIVED,
|
||||
SendBy: userCred.GetUserId(),
|
||||
}
|
||||
return rn, rnm.TableSpec().Insert(ctx, rn)
|
||||
}
|
||||
|
||||
func (rnm *SReceiverNotificationManager) GetMasterFieldName() string {
|
||||
return "notification_id"
|
||||
}
|
||||
|
||||
func (rnm *SReceiverNotificationManager) GetSlaveFieldName() string {
|
||||
return "receiver_id"
|
||||
}
|
||||
|
||||
func (rnm *SReceiverNotificationManager) CreateWithoutReceiver(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) {
|
||||
rn := &SReceiverNotification{
|
||||
NotificationID: notificationID,
|
||||
Contact: contact,
|
||||
Status: api.RECEIVER_NOTIFICATION_RECEIVED,
|
||||
SendBy: userCred.GetUserId(),
|
||||
}
|
||||
return rn, rnm.TableSpec().Insert(ctx, rn)
|
||||
}
|
||||
|
||||
func (rn *SReceiverNotification) Receiver() (*SReceiver, error) {
|
||||
q := ReceiverManager.Query().Equals("id", rn.ReceiverID)
|
||||
var receiver SReceiver
|
||||
err := q.First(&receiver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &receiver, nil
|
||||
}
|
||||
|
||||
func (rn *SReceiverNotification) BeforeSend(ctx context.Context, sendTime time.Time) error {
|
||||
if sendTime.IsZero() {
|
||||
sendTime = time.Now()
|
||||
}
|
||||
_, err := db.Update(rn, func() error {
|
||||
rn.SendAt = sendTime
|
||||
rn.Status = api.RECEIVER_NOTIFICATION_SENT
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (rn *SReceiverNotification) AfterSend(ctx context.Context, success bool, reason string) error {
|
||||
_, err := db.Update(rn, func() error {
|
||||
if success {
|
||||
rn.Status = api.RECEIVER_NOTIFICATION_OK
|
||||
} else {
|
||||
rn.Status = api.RECEIVER_NOTIFICATION_FAIL
|
||||
rn.FailedReason = reason
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
106
pkg/notify/models/subcontact.go
Normal file
106
pkg/notify/models/subcontact.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 (
|
||||
"yunion.io/x/pkg/tristate"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
type SSubContactManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
// +onecloud:swagger-gen-ignore
|
||||
type SSubContact struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
// id of receiver user
|
||||
ReceiverID string `width:"128" nullable:"false" index:"true"`
|
||||
Type string `width:"16" nullable:"false" index:"true"`
|
||||
Contact string `width:"128" nullable:"false"`
|
||||
ParentContactType string `width:"16" nullable:"false"`
|
||||
Enabled tristate.TriState `nullable:"false" default:"false"`
|
||||
Verified tristate.TriState `nullable:"false" default:"false"`
|
||||
}
|
||||
|
||||
var SubContactManager *SSubContactManager
|
||||
|
||||
var (
|
||||
vTrue = true
|
||||
vFalse = false
|
||||
|
||||
pTrue = &vTrue
|
||||
pFalse = &vFalse
|
||||
)
|
||||
|
||||
func init() {
|
||||
SubContactManager = &SSubContactManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SSubContact{},
|
||||
"subcontacts_tbl",
|
||||
"subcontact",
|
||||
"subcontacts",
|
||||
),
|
||||
}
|
||||
SubContactManager.SetVirtualObject(SubContactManager)
|
||||
}
|
||||
|
||||
func (scm *SSubContactManager) fetchMapByReceiverID(receiverID string) (map[string]*SSubContact, error) {
|
||||
q := scm.Query().Equals("receiver_id", receiverID)
|
||||
scontacts := make([]SSubContact, 0, 3)
|
||||
err := db.FetchModelObjects(scm, q, &scontacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(map[string]*SSubContact, len(scontacts))
|
||||
for i := range scontacts {
|
||||
ret[scontacts[i].Type] = &scontacts[i]
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (sc *SSubContact) Enable() error {
|
||||
return sc.Update(nil, pTrue, nil)
|
||||
}
|
||||
|
||||
func (sc *SSubContact) Disable() error {
|
||||
return sc.Update(nil, pFalse, nil)
|
||||
}
|
||||
|
||||
func (sc *SSubContact) Verify() error {
|
||||
return sc.Update(nil, nil, pTrue)
|
||||
}
|
||||
|
||||
func (sc *SSubContact) Disverify() error {
|
||||
return sc.Update(nil, nil, pFalse)
|
||||
}
|
||||
|
||||
func (sc *SSubContact) Update(contact *string, enabled *bool, verified *bool) error {
|
||||
_, err := db.Update(sc, func() error {
|
||||
if contact != nil {
|
||||
sc.Contact = *contact
|
||||
}
|
||||
if enabled != nil {
|
||||
sc.Enabled = tristate.NewFromBool(*enabled)
|
||||
}
|
||||
if verified != nil {
|
||||
sc.Verified = tristate.NewFromBool(*verified)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -27,8 +27,10 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
@@ -40,31 +42,29 @@ import (
|
||||
)
|
||||
|
||||
type STemplateManager struct {
|
||||
SStandaloneResourceBaseManager
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var TemplateManager *STemplateManager
|
||||
|
||||
func init() {
|
||||
TemplateManager = &STemplateManager{
|
||||
SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager(
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
STemplate{},
|
||||
"notify_t_template",
|
||||
"template_tbl",
|
||||
"notifytemplate",
|
||||
"notifytemplates",
|
||||
),
|
||||
}
|
||||
TemplateManager.SetVirtualObject(TemplateManager)
|
||||
}
|
||||
|
||||
const (
|
||||
TEMPLATE_TYPE_TITLE = "title"
|
||||
TEMPLATE_TYPE_CONTENT = "content"
|
||||
TEMPLATE_TYPE_REMOTE = "remote"
|
||||
CONTACTTYPE_ALL = "all"
|
||||
)
|
||||
|
||||
type STemplate struct {
|
||||
SStandaloneResourceBase
|
||||
db.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"`
|
||||
@@ -72,20 +72,17 @@ type STemplate struct {
|
||||
// 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" update:"user"`
|
||||
Example string `nullable:"false" created:"required" get:"user" list:"user" update:"user"`
|
||||
}
|
||||
|
||||
const (
|
||||
verifyUrlPath = "/email-verification/id/{0}/token/{1}?region=%s"
|
||||
templatePath = "/opt/yunion/share/template"
|
||||
)
|
||||
|
||||
func (tm *STemplateManager) GetEmailUrl() string {
|
||||
if len(options.Options.ApiServer) > 0 {
|
||||
return httputils.JoinPath(options.Options.ApiServer, fmt.Sprintf(verifyUrlPath, options.Options.Region))
|
||||
}
|
||||
return options.Options.VerifyEmailUrl
|
||||
}
|
||||
|
||||
var templatePath = "/opt/yunion/share/template"
|
||||
|
||||
func (tm *STemplateManager) defaultTemplate() ([]STemplate, error) {
|
||||
templates := make([]STemplate, 0, 4)
|
||||
@@ -143,6 +140,12 @@ func (tm *STemplateManager) GetCompanyInfo(ctx context.Context) (SCompanyInfo, e
|
||||
return info, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ForceInitType = []string{
|
||||
api.EMAIL,
|
||||
}
|
||||
)
|
||||
|
||||
func (tm *STemplateManager) InitializeData() error {
|
||||
templates, err := tm.defaultTemplate()
|
||||
if err != nil {
|
||||
@@ -151,12 +154,40 @@ func (tm *STemplateManager) InitializeData() error {
|
||||
for _, template := range templates {
|
||||
q := tm.Query().Equals("contact_type", template.ContactType).Equals("topic", template.Topic).Equals("template_type", template.TemplateType)
|
||||
count, _ := q.CountWithError()
|
||||
if count > 0 {
|
||||
if count > 0 && !utils.IsInStringArray(template.ContactType, ForceInitType) {
|
||||
continue
|
||||
}
|
||||
err := tm.TableSpec().InsertOrUpdate(context.TODO(), &template)
|
||||
if count == 0 {
|
||||
err := tm.TableSpec().Insert(context.TODO(), &template)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sqlchemy.TableSpec.InsertOrUpdate")
|
||||
return errors.Wrap(err, "sqlchemy.TableSpec.Insert")
|
||||
}
|
||||
continue
|
||||
}
|
||||
oldTemplates := make([]STemplate, 0, 1)
|
||||
err := db.FetchModelObjects(tm, q, &oldTemplates)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.FetchModelObjects")
|
||||
}
|
||||
// delete addtion
|
||||
var (
|
||||
ctx = context.Background()
|
||||
userCred = auth.AdminCredential()
|
||||
)
|
||||
for i := 1; i < len(oldTemplates); i++ {
|
||||
err := oldTemplates[i].Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "STemplate.Delete")
|
||||
}
|
||||
}
|
||||
// update
|
||||
oldTemplate := &oldTemplates[0]
|
||||
_, err = db.Update(oldTemplate, func() error {
|
||||
oldTemplate.Content = template.Content
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.Update")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -182,19 +213,19 @@ func (tm *STemplateManager) NotifyFilter(contactType, topic, msg string) (params
|
||||
for _, template := range templates {
|
||||
var title, content string
|
||||
switch template.TemplateType {
|
||||
case TEMPLATE_TYPE_TITLE:
|
||||
case api.TEMPLATE_TYPE_TITLE:
|
||||
title, err = template.Execute(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
params.Title = title
|
||||
case TEMPLATE_TYPE_CONTENT:
|
||||
case api.TEMPLATE_TYPE_CONTENT:
|
||||
content, err = template.Execute(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
params.Message = content
|
||||
case TEMPLATE_TYPE_REMOTE:
|
||||
case api.TEMPLATE_TYPE_REMOTE:
|
||||
params.RemoteTemplate = template.Content
|
||||
params.Message = msg
|
||||
default:
|
||||
@@ -223,29 +254,69 @@ func (tm *STemplate) Execute(str string) (string, error) {
|
||||
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)
|
||||
func (tm *STemplateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.TemplateCreateInput) (api.TemplateCreateInput, error) {
|
||||
if !utils.IsInStringArray(input.TemplateType, []string{
|
||||
api.TEMPLATE_TYPE_CONTENT, api.TEMPLATE_TYPE_REMOTE, api.TEMPLATE_TYPE_TITLE,
|
||||
}) {
|
||||
return input, httperrors.NewInputParameterError("no such support for tempalte type %s", input.TemplateType)
|
||||
}
|
||||
return data, nil
|
||||
if input.TemplateType != api.TEMPLATE_TYPE_REMOTE {
|
||||
if err := tm.validate(input.Content, input.Example); err != nil {
|
||||
return input, httperrors.NewInputParameterError(err.Error())
|
||||
}
|
||||
}
|
||||
if len(input.Name) == 0 {
|
||||
input.Name = fmt.Sprintf("%s-%s-%s", input.ContactType, input.Topic, input.TemplateType)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (self *STemplateManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
if queryDict.Contains("topic") {
|
||||
val, _ := queryDict.GetString("topic")
|
||||
q = q.Equals("topic", val)
|
||||
func (tm *STemplateManager) validate(template string, example string) error {
|
||||
// check example availability
|
||||
tem, err := ptem.New("tmp").Parse(template)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid template")
|
||||
}
|
||||
if queryDict.Contains("template_type") {
|
||||
val, _ := queryDict.GetString("template_type")
|
||||
q = q.Equals("template_type", val)
|
||||
var buffer bytes.Buffer
|
||||
tmpMap := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(example), &tmpMap)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid example")
|
||||
}
|
||||
if queryDict.Contains("contact_type") {
|
||||
val, _ := queryDict.GetString("contact_type")
|
||||
q = q.Equals("contact_type", val)
|
||||
err = tem.Execute(&buffer, tmpMap)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid example")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tm *STemplateManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.TemplateListInput) (*sqlchemy.SQuery, error) {
|
||||
q, err := tm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(input.Topic) > 0 {
|
||||
q = q.Equals("topic", input.Topic)
|
||||
}
|
||||
if len(input.TemplateType) > 0 {
|
||||
q = q.Equals("template_type", input.TemplateType)
|
||||
}
|
||||
if len(input.ContactType) > 0 {
|
||||
q = q.Equals("contact_type", input.ContactType)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (t *STemplate) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.TemplateUpdateInput) (api.TemplateUpdateInput, error) {
|
||||
if t.TemplateType == api.TEMPLATE_TYPE_REMOTE {
|
||||
return input, nil
|
||||
}
|
||||
if err := TemplateManager.validate(input.Content, input.Example); err != nil {
|
||||
return input, httperrors.NewInputParameterError(err.Error())
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (t *STemplate) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.TemplateDetails, error) {
|
||||
return api.TemplateDetails{}, nil
|
||||
}
|
||||
108
pkg/notify/models/verify.go
Normal file
108
pkg/notify/models/verify.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
)
|
||||
|
||||
type SVerificationManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var VerificationManager *SVerificationManager
|
||||
|
||||
func init() {
|
||||
VerificationManager = &SVerificationManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SVerification{},
|
||||
"verification_tbl",
|
||||
"verification",
|
||||
"verifications",
|
||||
),
|
||||
}
|
||||
VerificationManager.SetVirtualObject(VerificationManager)
|
||||
}
|
||||
|
||||
// +onecloud:swagger-gen-ignore
|
||||
type SVerification struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
ReceiverId string `width:"128" nullable:"false"`
|
||||
ContactType string `width:"16" nullable:"false"`
|
||||
Token string `width:"200" nullable:"false"`
|
||||
}
|
||||
|
||||
var ErrVerifyFrequently = errors.Error("Send validation messages too frequently")
|
||||
|
||||
func (vm *SVerificationManager) generateVerifyToken() string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
token := fmt.Sprintf("%06v", rnd.Int31n(1000000))
|
||||
return token
|
||||
}
|
||||
|
||||
func (vm *SVerificationManager) Create(ctx context.Context, receiverId, contactType string) (*SVerification, error) {
|
||||
// try to reuse
|
||||
ret, err := vm.Get(receiverId, contactType)
|
||||
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
if ret == nil {
|
||||
ret = &SVerification{
|
||||
ReceiverId: receiverId,
|
||||
ContactType: contactType,
|
||||
Token: vm.generateVerifyToken(),
|
||||
}
|
||||
err := vm.TableSpec().Insert(ctx, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
now := time.Now()
|
||||
if now.Before(ret.CreatedAt.Add(time.Duration(options.Options.VerifyExpireInterval) * time.Minute)) {
|
||||
return nil, ErrVerifyFrequently
|
||||
}
|
||||
_, err := db.Update(ret, func() error {
|
||||
ret.Token = vm.generateVerifyToken()
|
||||
ret.CreatedAt = now
|
||||
ret.UpdatedAt = now
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (vm *SVerificationManager) Get(receiverId, contactType string) (*SVerification, error) {
|
||||
q := vm.Query().Equals("receiver_id", receiverId).Equals("contact_type", contactType)
|
||||
var verification SVerification
|
||||
err := q.First(&verification)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verification.SetModelManager(vm, &verification)
|
||||
return &verification, nil
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
var workMan *appsrv.SWorkerManager
|
||||
|
||||
func init() {
|
||||
workMan = appsrv.NewWorkerManager("NotifyWokerManager", 16, 512, false)
|
||||
}
|
||||
|
||||
func Send(notifications []*SNotification, userCred mcclient.TokenCredential, contacts []string) {
|
||||
|
||||
for i := range notifications {
|
||||
notification, contact := notifications[i], contacts[i]
|
||||
workMan.Run(func() {
|
||||
sendone(context.Background(), userCred, notification, contact)
|
||||
}, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func sendone(ctx context.Context, userCred mcclient.TokenCredential, notification *SNotification, contact string) {
|
||||
err := notification.SetSentAndTime(userCred)
|
||||
if err != nil {
|
||||
log.Errorf("Change notification's status failed.")
|
||||
return
|
||||
}
|
||||
err = NotifyService.Send(ctx, notification.ContactType, contact, notification.Topic, notification.Msg,
|
||||
notification.Priority)
|
||||
if err != nil {
|
||||
log.Errorf("Send notification failed: %s.", err.Error())
|
||||
notification.SetStatus(userCred, NOTIFY_FAIL, err.Error())
|
||||
} else {
|
||||
log.Debugf("send notification successfully")
|
||||
notification.SetStatus(userCred, NOTIFY_OK, "")
|
||||
}
|
||||
}
|
||||
|
||||
func RestartService(config map[string]string, serviceName string) {
|
||||
workMan.Run(func() {
|
||||
NotifyService.RestartService(context.Background(), config, serviceName)
|
||||
}, nil, nil)
|
||||
}
|
||||
|
||||
func SendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, verify *SVerify,
|
||||
contact *SContact) error {
|
||||
var (
|
||||
err error
|
||||
msg string
|
||||
)
|
||||
info, err := TemplateManager.GetCompanyInfo(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("unable to try to get company info: %s", err.Error())
|
||||
}
|
||||
processId, token := verify.ID, verify.Token
|
||||
if contact.ContactType == "email" {
|
||||
emailUrl := strings.Replace(TemplateManager.GetEmailUrl(), "{0}", processId, 1)
|
||||
emailUrl = strings.Replace(emailUrl, "{1}", token, 1)
|
||||
|
||||
// get uName
|
||||
uName, err := utils.GetUsernameByID(ctx, contact.UID)
|
||||
if err != nil || len(uName) == 0 {
|
||||
uName = "用户"
|
||||
}
|
||||
data := struct {
|
||||
Name string
|
||||
Link string
|
||||
SCompanyInfo
|
||||
}{
|
||||
Name: uName,
|
||||
Link: emailUrl,
|
||||
SCompanyInfo: info,
|
||||
}
|
||||
msg = jsonutils.Marshal(data).String()
|
||||
} else if contact.ContactType == "mobile" {
|
||||
msg = fmt.Sprintf(`{"code": "%s"}`, token)
|
||||
} else {
|
||||
// todo
|
||||
return nil
|
||||
}
|
||||
|
||||
err = NotifyService.Send(ctx, contact.ContactType, contact.Contact, "verify", msg, "")
|
||||
if err != nil {
|
||||
verify.SetStatus(userCred, VERIFICATION_SENT_FAIL, "")
|
||||
// set contact's status as "init"
|
||||
contact.SetStatus(userCred, CONTACT_INIT, "send verify message failed")
|
||||
log.Errorf("Send verify message failed: %s.", err.Error())
|
||||
return errors.Wrap(err, "Send Verify Message Failed")
|
||||
}
|
||||
verify.SetStatus(userCred, VERIFICATION_SENT, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
func PullContact(uid string, contactTypes []string) {
|
||||
for i := range contactTypes {
|
||||
ct := contactTypes[i]
|
||||
workMan.Run(func() {
|
||||
pullContact(context.Background(), uid, ct)
|
||||
}, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func pullContact(ctx context.Context, uid string, contactType string) {
|
||||
contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, contactType})
|
||||
if err != nil {
|
||||
log.Errorf("fetch contacts error")
|
||||
}
|
||||
if len(contacts) == 0 {
|
||||
return
|
||||
}
|
||||
var mobileContact, subContact *SContact
|
||||
for i := range contacts {
|
||||
if contacts[i].ContactType == MOBILE {
|
||||
mobileContact = &contacts[i]
|
||||
} else {
|
||||
subContact = &contacts[i]
|
||||
}
|
||||
}
|
||||
if mobileContact == nil {
|
||||
return
|
||||
}
|
||||
|
||||
userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, contactType)
|
||||
if err != nil {
|
||||
log.Errorf("fetch %s contact by mobile failed: %s", contactType, err.Error())
|
||||
}
|
||||
if subContact != nil {
|
||||
subContact.SetModelManager(ContactManager, subContact)
|
||||
origin := subContact.Contact
|
||||
_, err := db.Update(subContact, func() error {
|
||||
subContact.Contact = userid
|
||||
subContact.VerifiedAt = time.Now()
|
||||
if subContact.Status != CONTACT_VERIFIED {
|
||||
subContact.Status = CONTACT_VERIFIED
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("update %s contact userid %s => %s failed", contactType, origin, userid)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
contact := SContact{
|
||||
UID: uid,
|
||||
ContactType: contactType,
|
||||
Contact: userid,
|
||||
Enabled: "1",
|
||||
VerifiedAt: time.Now(),
|
||||
}
|
||||
contact.Status = CONTACT_VERIFIED
|
||||
|
||||
err = ContactManager.TableSpec().Insert(ctx, &contact)
|
||||
if err != nil {
|
||||
log.Errorf("create new %s contact failed", contactType)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
package oldmodels
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -12,4 +12,4 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package _interface // import "yunion.io/x/onecloud/pkg/notify/interface"
|
||||
package oldmodels // import "yunion.io/x/onecloud/pkg/notify/oldmodels"
|
||||
44
pkg/notify/oldmodels/mod_config.go
Normal file
44
pkg/notify/oldmodels/mod_config.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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 oldmodels
|
||||
|
||||
type SConfigManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ConfigManager *SConfigManager
|
||||
|
||||
func init() {
|
||||
ConfigManager = &SConfigManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SConfig{},
|
||||
"notify_t_config",
|
||||
"oldconfig",
|
||||
"oldconfigs",
|
||||
),
|
||||
}
|
||||
ConfigManager.SetVirtualObject(ConfigManager)
|
||||
}
|
||||
|
||||
// SConfig is a table which storage (k,v) and its type.
|
||||
// The three important concepts are key, value and type.
|
||||
// Key and type uniquely identify a value.
|
||||
type SConfig struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
Type string `width:"15" nullable:"false" create:"required" list:"user"`
|
||||
KeyText string `width:"30" nullable:"false" create:"required" list:"user"`
|
||||
ValueText string `width:"256" nullable:"false" create:"required" list:"user"`
|
||||
}
|
||||
53
pkg/notify/oldmodels/mod_contact.go
Normal file
53
pkg/notify/oldmodels/mod_contact.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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 oldmodels
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type SContactManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ContactManager *SContactManager
|
||||
|
||||
func init() {
|
||||
ContactManager = &SContactManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SContact{},
|
||||
"notify_t_contacts",
|
||||
"contact",
|
||||
"contacts",
|
||||
),
|
||||
}
|
||||
ContactManager.SetVirtualObject(ContactManager)
|
||||
}
|
||||
|
||||
const (
|
||||
CONTACT_INIT = "init" // Contact's status is init which means no verifying
|
||||
CONTACT_VERIFYING = "verifying" // Contact's status is verifying
|
||||
CONTACT_VERIFIED = "verified" // Contact's status is verified
|
||||
)
|
||||
|
||||
type SContact struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required" update:"user" list:"user" get:"user"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required" update:"user"`
|
||||
Contact string `width:"64" nullable:"false" create:"required" update:"user"`
|
||||
Enabled string `width:"5" nullable:"false" default:"1" create:"optional" update:"user"`
|
||||
VerifiedAt time.Time `update:"user"`
|
||||
}
|
||||
52
pkg/notify/oldmodels/mod_notification.go
Normal file
52
pkg/notify/oldmodels/mod_notification.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 oldmodels
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type SNotificationManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var NotificationManager *SNotificationManager
|
||||
|
||||
func init() {
|
||||
NotificationManager = &SNotificationManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SNotification{},
|
||||
"notify_t_notification",
|
||||
"oldnotification",
|
||||
"oldnotifications",
|
||||
),
|
||||
}
|
||||
NotificationManager.SetVirtualObject(NotificationManager)
|
||||
}
|
||||
|
||||
type SNotification struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required" list:"user" index:"true"`
|
||||
Topic string `width:"128" nullable:"true" create:"optional" list:"user"`
|
||||
Priority string `width:"16" nullable:"true" create:"optional" list:"user"`
|
||||
Msg string `create:"required"`
|
||||
ReceivedAt time.Time `nullable:"true" list:"user" create:"optional"`
|
||||
SendAt time.Time `nullable:"false"`
|
||||
SendBy string `width:"128" nullable:"false"`
|
||||
// ClusterID identify message with same topic, msg, priority
|
||||
ClusterID string `width:"128" charset:"ascii" primary:"true" create:"optional" list:"user" get:"user"`
|
||||
}
|
||||
51
pkg/notify/oldmodels/mod_template.go
Normal file
51
pkg/notify/oldmodels/mod_template.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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 oldmodels
|
||||
|
||||
type STemplateManager struct {
|
||||
SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var TemplateManager *STemplateManager
|
||||
|
||||
func init() {
|
||||
TemplateManager = &STemplateManager{
|
||||
SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager(
|
||||
STemplate{},
|
||||
"notify_t_template",
|
||||
"oldtemplate",
|
||||
"oldtemplates",
|
||||
),
|
||||
}
|
||||
TemplateManager.SetVirtualObject(TemplateManager)
|
||||
}
|
||||
|
||||
const (
|
||||
TEMPLATE_TYPE_TITLE = "title"
|
||||
TEMPLATE_TYPE_CONTENT = "content"
|
||||
TEMPLATE_TYPE_REMOTE = "remote"
|
||||
CONTACTTYPE_ALL = "all"
|
||||
)
|
||||
|
||||
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" update:"user"`
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
package oldmodels
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
package oldmodels
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
package oldmodels
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -42,7 +42,7 @@ var UserCacheManager *SUserCacheManager
|
||||
func init() {
|
||||
dbUserCacheManager := db.SUserCacheManager{
|
||||
SKeystoneCacheObjectManager: db.NewKeystoneCacheObjectManager(
|
||||
db.SUser{}, "users_cache_tbl", "user", "users"),
|
||||
db.SUser{}, "users_cache_tbl", "olduser", "oldusers"),
|
||||
}
|
||||
UserCacheManager = &SUserCacheManager{
|
||||
dbUserCacheManager,
|
||||
@@ -22,17 +22,16 @@ type NotifyOption struct {
|
||||
common_options.CommonOptions
|
||||
common_options.DBOptions
|
||||
|
||||
DingtalkEnabled bool `help:"Enable dingtalk"`
|
||||
SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/notify"`
|
||||
SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/socket"`
|
||||
UpdateInterval int `help:"Update send services interval(unit:min)" default:"30"`
|
||||
// VerifyEmailUrlPath string `help:"url of verify email" json:"verify_email_url_path"`
|
||||
|
||||
// Deprecated
|
||||
VerifyEmailUrl string `help:"url of verify email" json:"verify_email_url"`
|
||||
|
||||
ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope seconds" default:"30"`
|
||||
ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope seconds" default:"60"`
|
||||
MaxSendTimes int `help:"Resend all messages whose sendTimes less than MaxSendTimes" default:"2"`
|
||||
|
||||
InitNotificationScope int `help:"initialize data of notification with in InitNotificationScope hours" default:"100"`
|
||||
MaxSyncNotification int `help:"The max number of notification sync from old data source" default:"1000"`
|
||||
|
||||
VerifyExpireInterval int `help:"expire interval of verify message; minutes" default:"5"`
|
||||
}
|
||||
|
||||
var Options NotifyOption
|
||||
|
||||
@@ -58,3 +58,9 @@ func (c *SendNotificationClient) UseridByMobile(ctx context.Context, in *UseridB
|
||||
defer cancel()
|
||||
return c.sendAgentClient.UseridByMobile(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *SendNotificationClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.CallTimeout)
|
||||
defer cancel()
|
||||
return c.sendAgentClient.BatchSend(ctx, in, opts...)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: send_server.proto
|
||||
|
||||
@@ -313,6 +299,163 @@ func (m *ValidateConfigReply) GetMsg() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type BatchSendParams struct {
|
||||
Contacts []string `protobuf:"bytes,1,rep,name=Contacts,proto3" json:"Contacts,omitempty"`
|
||||
Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,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"`
|
||||
RemoteTemplate string `protobuf:"bytes,5,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) Reset() { *m = BatchSendParams{} }
|
||||
func (m *BatchSendParams) String() string { return proto.CompactTextString(m) }
|
||||
func (*BatchSendParams) ProtoMessage() {}
|
||||
func (*BatchSendParams) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_63fdd68f7eb311f9, []int{6}
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_BatchSendParams.Unmarshal(m, b)
|
||||
}
|
||||
func (m *BatchSendParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_BatchSendParams.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *BatchSendParams) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_BatchSendParams.Merge(m, src)
|
||||
}
|
||||
func (m *BatchSendParams) XXX_Size() int {
|
||||
return xxx_messageInfo_BatchSendParams.Size(m)
|
||||
}
|
||||
func (m *BatchSendParams) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_BatchSendParams.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_BatchSendParams proto.InternalMessageInfo
|
||||
|
||||
func (m *BatchSendParams) GetContacts() []string {
|
||||
if m != nil {
|
||||
return m.Contacts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) GetTitle() string {
|
||||
if m != nil {
|
||||
return m.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) GetPriority() string {
|
||||
if m != nil {
|
||||
return m.Priority
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BatchSendParams) GetRemoteTemplate() string {
|
||||
if m != nil {
|
||||
return m.RemoteTemplate
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type FailedRecord struct {
|
||||
Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"`
|
||||
Reason string `protobuf:"bytes,2,opt,name=Reason,proto3" json:"Reason,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *FailedRecord) Reset() { *m = FailedRecord{} }
|
||||
func (m *FailedRecord) String() string { return proto.CompactTextString(m) }
|
||||
func (*FailedRecord) ProtoMessage() {}
|
||||
func (*FailedRecord) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_63fdd68f7eb311f9, []int{7}
|
||||
}
|
||||
|
||||
func (m *FailedRecord) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_FailedRecord.Unmarshal(m, b)
|
||||
}
|
||||
func (m *FailedRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_FailedRecord.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *FailedRecord) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_FailedRecord.Merge(m, src)
|
||||
}
|
||||
func (m *FailedRecord) XXX_Size() int {
|
||||
return xxx_messageInfo_FailedRecord.Size(m)
|
||||
}
|
||||
func (m *FailedRecord) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_FailedRecord.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_FailedRecord proto.InternalMessageInfo
|
||||
|
||||
func (m *FailedRecord) GetContact() string {
|
||||
if m != nil {
|
||||
return m.Contact
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *FailedRecord) GetReason() string {
|
||||
if m != nil {
|
||||
return m.Reason
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BatchSendReply struct {
|
||||
FailedRecords []*FailedRecord `protobuf:"bytes,1,rep,name=FailedRecords,proto3" json:"FailedRecords,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BatchSendReply) Reset() { *m = BatchSendReply{} }
|
||||
func (m *BatchSendReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*BatchSendReply) ProtoMessage() {}
|
||||
func (*BatchSendReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_63fdd68f7eb311f9, []int{8}
|
||||
}
|
||||
|
||||
func (m *BatchSendReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_BatchSendReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *BatchSendReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_BatchSendReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *BatchSendReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_BatchSendReply.Merge(m, src)
|
||||
}
|
||||
func (m *BatchSendReply) XXX_Size() int {
|
||||
return xxx_messageInfo_BatchSendReply.Size(m)
|
||||
}
|
||||
func (m *BatchSendReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_BatchSendReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_BatchSendReply proto.InternalMessageInfo
|
||||
|
||||
func (m *BatchSendReply) GetFailedRecords() []*FailedRecord {
|
||||
if m != nil {
|
||||
return m.FailedRecords
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*SendParams)(nil), "apis.SendParams")
|
||||
proto.RegisterType((*UpdateConfigParams)(nil), "apis.UpdateConfigParams")
|
||||
@@ -321,37 +464,47 @@ func init() {
|
||||
proto.RegisterType((*Empty)(nil), "apis.Empty")
|
||||
proto.RegisterType((*UseridByMobileReply)(nil), "apis.UseridByMobileReply")
|
||||
proto.RegisterType((*ValidateConfigReply)(nil), "apis.ValidateConfigReply")
|
||||
proto.RegisterType((*BatchSendParams)(nil), "apis.BatchSendParams")
|
||||
proto.RegisterType((*FailedRecord)(nil), "apis.FailedRecord")
|
||||
proto.RegisterType((*BatchSendReply)(nil), "apis.BatchSendReply")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("send_server.proto", fileDescriptor_63fdd68f7eb311f9) }
|
||||
|
||||
var fileDescriptor_63fdd68f7eb311f9 = []byte{
|
||||
// 398 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xcf, 0x6a, 0xdb, 0x40,
|
||||
0x10, 0xc6, 0x51, 0xfc, 0x47, 0xc9, 0x24, 0x98, 0x74, 0x13, 0xca, 0x56, 0xa7, 0x20, 0x70, 0xf1,
|
||||
0xa5, 0x3a, 0xb8, 0x14, 0x8a, 0x2f, 0xc5, 0x35, 0xa6, 0x27, 0x83, 0x51, 0xed, 0x5e, 0xcb, 0xda,
|
||||
0x9a, 0x8a, 0xa5, 0x92, 0x56, 0xec, 0xae, 0x0d, 0x7a, 0x8c, 0xbe, 0x49, 0x5f, 0xaf, 0xb7, 0xb2,
|
||||
0x7f, 0x54, 0x5b, 0xad, 0x9b, 0x9b, 0x7e, 0xdf, 0xcc, 0x37, 0xcc, 0x7c, 0x5a, 0x78, 0xa1, 0xb0,
|
||||
0xca, 0xbe, 0x2a, 0x94, 0x47, 0x94, 0x49, 0x2d, 0x85, 0x16, 0xa4, 0xcf, 0x6a, 0xae, 0xe2, 0x9f,
|
||||
0x01, 0xc0, 0x67, 0xac, 0xb2, 0x35, 0x93, 0xac, 0x54, 0x84, 0x42, 0xb8, 0x10, 0x95, 0x66, 0x7b,
|
||||
0x4d, 0x83, 0xa7, 0x60, 0x72, 0x93, 0xb6, 0x48, 0x1e, 0x61, 0xb0, 0x11, 0x35, 0xdf, 0xd3, 0x2b,
|
||||
0xab, 0x3b, 0xb0, 0x2a, 0xd7, 0x05, 0xd2, 0x9e, 0x57, 0x0d, 0x98, 0x29, 0x2b, 0x54, 0x8a, 0xe5,
|
||||
0x48, 0xfb, 0x6e, 0x8a, 0x47, 0x12, 0xc1, 0xf5, 0x5a, 0x72, 0x21, 0xb9, 0x6e, 0xe8, 0xc0, 0x96,
|
||||
0xfe, 0x30, 0x79, 0x0d, 0xa3, 0x14, 0x4b, 0xa1, 0x71, 0x83, 0x65, 0x5d, 0x30, 0x8d, 0x74, 0x68,
|
||||
0x3b, 0xfe, 0x52, 0xe3, 0x1f, 0x01, 0x90, 0x6d, 0x9d, 0x31, 0x8d, 0x0b, 0x51, 0x7d, 0xe3, 0xb9,
|
||||
0x5f, 0xfd, 0x03, 0x84, 0x7b, 0xcb, 0x8a, 0x06, 0x4f, 0xbd, 0xc9, 0xed, 0x74, 0x9c, 0x98, 0x0b,
|
||||
0x93, 0x7f, 0x5b, 0x13, 0x07, 0x6a, 0x59, 0x69, 0xd9, 0xa4, 0xad, 0x2b, 0x9a, 0xc1, 0xdd, 0x79,
|
||||
0x81, 0xdc, 0x43, 0xef, 0x3b, 0x36, 0x3e, 0x07, 0xf3, 0x69, 0xae, 0x3d, 0xb2, 0xe2, 0x80, 0x6d,
|
||||
0x06, 0x16, 0x66, 0x57, 0xef, 0x83, 0x38, 0x81, 0xc7, 0xad, 0x42, 0xc9, 0xb3, 0x8f, 0xcd, 0x4a,
|
||||
0xec, 0x78, 0x81, 0x7e, 0xa9, 0x97, 0x30, 0x2c, 0x2d, 0xfb, 0x31, 0x9e, 0xe2, 0x10, 0x06, 0xcb,
|
||||
0xb2, 0xd6, 0x4d, 0xfc, 0x06, 0x1e, 0xba, 0xc6, 0x14, 0xeb, 0xa2, 0x31, 0xbe, 0x83, 0x95, 0x5b,
|
||||
0x9f, 0xa3, 0x78, 0x0e, 0x0f, 0x5f, 0x58, 0xc1, 0x4f, 0x17, 0xb9, 0x76, 0x0a, 0x21, 0x57, 0xb6,
|
||||
0x60, 0xfb, 0xaf, 0xd3, 0x16, 0xcd, 0x11, 0xa5, 0xca, 0xfd, 0xc2, 0xe6, 0x73, 0xfa, 0x2b, 0x80,
|
||||
0x1b, 0xf3, 0xc7, 0xe7, 0x39, 0x56, 0x9a, 0x8c, 0xa1, 0x6f, 0x80, 0xdc, 0xbb, 0xb0, 0x4e, 0x4f,
|
||||
0x21, 0xba, 0x75, 0x8a, 0x5d, 0x93, 0xbc, 0x83, 0xbb, 0xf3, 0x1c, 0x09, 0xfd, 0x5f, 0xb6, 0x5d,
|
||||
0xdb, 0x12, 0x46, 0xdd, 0x75, 0x9f, 0x31, 0xbe, 0x72, 0x95, 0x4b, 0xe7, 0x7d, 0x82, 0x51, 0x37,
|
||||
0x24, 0x12, 0xf9, 0x31, 0x17, 0x32, 0x6f, 0x07, 0x5d, 0x88, 0x75, 0x37, 0xb4, 0x4f, 0xff, 0xed,
|
||||
0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x8d, 0x12, 0x77, 0x0f, 0x03, 0x00, 0x00,
|
||||
// 497 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xdd, 0x6a, 0xd4, 0x40,
|
||||
0x14, 0xc7, 0xc9, 0x7e, 0xef, 0xe9, 0xba, 0xd6, 0xe9, 0x5a, 0xc6, 0x5c, 0x95, 0x40, 0xa5, 0x37,
|
||||
0xe6, 0xa2, 0x22, 0x2c, 0xbd, 0xd1, 0xb6, 0xac, 0x82, 0x50, 0x28, 0xb1, 0xf5, 0x56, 0xa6, 0xc9,
|
||||
0x71, 0x1d, 0x4c, 0x32, 0x21, 0x33, 0x2d, 0xe4, 0x31, 0x7c, 0x04, 0xdf, 0x40, 0xf0, 0x05, 0x65,
|
||||
0x3e, 0xb2, 0x9b, 0xac, 0xbb, 0xed, 0x5d, 0x7e, 0xe7, 0x8b, 0x39, 0xe7, 0xff, 0x27, 0xf0, 0x42,
|
||||
0x62, 0x9e, 0x7c, 0x93, 0x58, 0x3e, 0x60, 0x19, 0x16, 0xa5, 0x50, 0x82, 0xf4, 0x58, 0xc1, 0x65,
|
||||
0xf0, 0xc7, 0x03, 0xf8, 0x82, 0x79, 0x72, 0xcd, 0x4a, 0x96, 0x49, 0x42, 0x61, 0x78, 0x29, 0x72,
|
||||
0xc5, 0x62, 0x45, 0xbd, 0x23, 0xef, 0x64, 0x1c, 0xd5, 0x48, 0x66, 0xd0, 0xbf, 0x11, 0x05, 0x8f,
|
||||
0x69, 0xc7, 0xc4, 0x2d, 0x98, 0x28, 0x57, 0x29, 0xd2, 0xae, 0x8b, 0x6a, 0xd0, 0x53, 0xae, 0x50,
|
||||
0x4a, 0xb6, 0x44, 0xda, 0xb3, 0x53, 0x1c, 0x12, 0x1f, 0x46, 0xd7, 0x25, 0x17, 0x25, 0x57, 0x15,
|
||||
0xed, 0x9b, 0xd4, 0x8a, 0xc9, 0x6b, 0x98, 0x46, 0x98, 0x09, 0x85, 0x37, 0x98, 0x15, 0x29, 0x53,
|
||||
0x48, 0x07, 0xa6, 0x62, 0x23, 0x1a, 0xfc, 0xf2, 0x80, 0xdc, 0x16, 0x09, 0x53, 0x78, 0x29, 0xf2,
|
||||
0xef, 0x7c, 0xe9, 0x9e, 0xfe, 0x1e, 0x86, 0xb1, 0x61, 0x49, 0xbd, 0xa3, 0xee, 0xc9, 0xde, 0xe9,
|
||||
0x71, 0xa8, 0x37, 0x0c, 0xff, 0x2f, 0x0d, 0x2d, 0xc8, 0x45, 0xae, 0xca, 0x2a, 0xaa, 0xbb, 0xfc,
|
||||
0x33, 0x98, 0x34, 0x13, 0x64, 0x1f, 0xba, 0x3f, 0xb1, 0x72, 0x77, 0xd0, 0x9f, 0x7a, 0xdb, 0x07,
|
||||
0x96, 0xde, 0x63, 0x7d, 0x03, 0x03, 0x67, 0x9d, 0xb9, 0x17, 0x84, 0x30, 0xbb, 0x95, 0x58, 0xf2,
|
||||
0xe4, 0xa2, 0xba, 0x12, 0x77, 0x3c, 0x45, 0xf7, 0xa8, 0x43, 0x18, 0x64, 0x86, 0xdd, 0x18, 0x47,
|
||||
0xc1, 0x10, 0xfa, 0x8b, 0xac, 0x50, 0x55, 0xf0, 0x06, 0x0e, 0xda, 0x8d, 0x11, 0x16, 0x69, 0xa5,
|
||||
0xfb, 0xee, 0x4d, 0xb8, 0xee, 0xb3, 0x14, 0x9c, 0xc3, 0xc1, 0x57, 0x96, 0xf2, 0xf5, 0x46, 0xb6,
|
||||
0x9c, 0xc2, 0x90, 0x4b, 0x93, 0x30, 0xf5, 0xa3, 0xa8, 0x46, 0xbd, 0x44, 0x26, 0x97, 0xee, 0xc1,
|
||||
0xfa, 0x33, 0xf8, 0xed, 0xc1, 0xf3, 0x0b, 0xa6, 0xe2, 0x1f, 0x0d, 0xd9, 0x7d, 0x18, 0x39, 0x9d,
|
||||
0xed, 0xf1, 0xc6, 0xd1, 0x8a, 0xd7, 0x12, 0x77, 0x76, 0x48, 0xdc, 0xdd, 0x2d, 0x71, 0xef, 0x49,
|
||||
0x89, 0xfb, 0x5b, 0x25, 0xfe, 0x00, 0x93, 0x8f, 0x8c, 0xa7, 0x98, 0x44, 0x18, 0x8b, 0x32, 0x79,
|
||||
0xc4, 0x96, 0x87, 0x30, 0x88, 0x90, 0x49, 0x91, 0xbb, 0xe7, 0x39, 0x0a, 0x3e, 0xc3, 0x74, 0xb5,
|
||||
0xa4, 0xbd, 0xd1, 0x1c, 0x9e, 0x35, 0x67, 0xd6, 0x2e, 0x21, 0xd6, 0x25, 0xcd, 0x54, 0xd4, 0x2e,
|
||||
0x3c, 0xfd, 0xdb, 0x81, 0xb1, 0x9e, 0x73, 0xbe, 0xc4, 0x5c, 0x91, 0x63, 0xe8, 0x69, 0x20, 0xfb,
|
||||
0xb6, 0x71, 0x7d, 0x45, 0x7f, 0xcf, 0x46, 0x8c, 0xb0, 0xe4, 0x1d, 0x4c, 0x9a, 0xce, 0x23, 0x74,
|
||||
0x97, 0x1b, 0xdb, 0x6d, 0x0b, 0x98, 0xb6, 0x05, 0x7e, 0xa4, 0xf1, 0x95, 0xcd, 0x6c, 0x33, 0xc4,
|
||||
0x27, 0x98, 0xb6, 0x6d, 0x45, 0x7c, 0x37, 0x66, 0x8b, 0x4b, 0xeb, 0x41, 0xdb, 0x8c, 0x38, 0x87,
|
||||
0xf1, 0xea, 0x8e, 0xe4, 0xa5, 0xad, 0xdb, 0x70, 0x8f, 0x3f, 0xdb, 0x08, 0x9b, 0xce, 0xbb, 0x81,
|
||||
0xf9, 0xcd, 0xbc, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x71, 0x6f, 0x12, 0x61, 0x7b, 0x04, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -370,6 +523,7 @@ type SendAgentClient interface {
|
||||
UpdateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*Empty, error)
|
||||
ValidateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*ValidateConfigReply, error)
|
||||
UseridByMobile(ctx context.Context, in *UseridByMobileParams, opts ...grpc.CallOption) (*UseridByMobileReply, error)
|
||||
BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error)
|
||||
}
|
||||
|
||||
type sendAgentClient struct {
|
||||
@@ -416,12 +570,22 @@ func (c *sendAgentClient) UseridByMobile(ctx context.Context, in *UseridByMobile
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sendAgentClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) {
|
||||
out := new(BatchSendReply)
|
||||
err := c.cc.Invoke(ctx, "/apis.SendAgent/BatchSend", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SendAgentServer is the server API for SendAgent service.
|
||||
type SendAgentServer interface {
|
||||
Send(context.Context, *SendParams) (*Empty, error)
|
||||
UpdateConfig(context.Context, *UpdateConfigParams) (*Empty, error)
|
||||
ValidateConfig(context.Context, *UpdateConfigParams) (*ValidateConfigReply, error)
|
||||
UseridByMobile(context.Context, *UseridByMobileParams) (*UseridByMobileReply, error)
|
||||
BatchSend(context.Context, *BatchSendParams) (*BatchSendReply, error)
|
||||
}
|
||||
|
||||
// UnimplementedSendAgentServer can be embedded to have forward compatible implementations.
|
||||
@@ -440,6 +604,9 @@ func (*UnimplementedSendAgentServer) ValidateConfig(ctx context.Context, req *Up
|
||||
func (*UnimplementedSendAgentServer) UseridByMobile(ctx context.Context, req *UseridByMobileParams) (*UseridByMobileReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UseridByMobile not implemented")
|
||||
}
|
||||
func (*UnimplementedSendAgentServer) BatchSend(ctx context.Context, req *BatchSendParams) (*BatchSendReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method BatchSend not implemented")
|
||||
}
|
||||
|
||||
func RegisterSendAgentServer(s *grpc.Server, srv SendAgentServer) {
|
||||
s.RegisterService(&_SendAgent_serviceDesc, srv)
|
||||
@@ -517,6 +684,24 @@ func _SendAgent_UseridByMobile_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SendAgent_BatchSend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(BatchSendParams)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SendAgentServer).BatchSend(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/apis.SendAgent/BatchSend",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SendAgentServer).BatchSend(ctx, req.(*BatchSendParams))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _SendAgent_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "apis.SendAgent",
|
||||
HandlerType: (*SendAgentServer)(nil),
|
||||
@@ -537,6 +722,10 @@ var _SendAgent_serviceDesc = grpc.ServiceDesc{
|
||||
MethodName: "UseridByMobile",
|
||||
Handler: _SendAgent_UseridByMobile_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "BatchSend",
|
||||
Handler: _SendAgent_BatchSend_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "send_server.proto",
|
||||
|
||||
@@ -45,9 +45,27 @@ message ValidateConfigReply {
|
||||
string msg = 2;
|
||||
}
|
||||
|
||||
message BatchSendParams {
|
||||
repeated string Contacts = 1;
|
||||
string Title = 2;
|
||||
string Message = 3;
|
||||
string Priority = 4;
|
||||
string RemoteTemplate = 5;
|
||||
}
|
||||
|
||||
message FailedRecord {
|
||||
string Contact = 1;
|
||||
string Reason = 2;
|
||||
}
|
||||
|
||||
message BatchSendReply {
|
||||
repeated FailedRecord FailedRecords = 1;
|
||||
}
|
||||
|
||||
service SendAgent {
|
||||
rpc Send(SendParams) returns (Empty);
|
||||
rpc UpdateConfig(UpdateConfigParams) returns (Empty);
|
||||
rpc ValidateConfig(UpdateConfigParams) returns (ValidateConfigReply);
|
||||
rpc UseridByMobile(UseridByMobileParams) returns (UseridByMobileReply);
|
||||
rpc BatchSend (BatchSendParams) returns (BatchSendReply);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
_interface "yunion.io/x/onecloud/pkg/notify/interface"
|
||||
notifyv2 "yunion.io/x/onecloud/pkg/notify"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/rpc/apis"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
@@ -49,13 +49,13 @@ const (
|
||||
type SRpcService struct {
|
||||
SendServices *ServiceMap
|
||||
socketFileDir string
|
||||
configStore _interface.IServiceConfigStore
|
||||
templateStore _interface.ITemplateStore
|
||||
configStore notifyv2.IServiceConfigStore
|
||||
templateStore notifyv2.ITemplateStore
|
||||
}
|
||||
|
||||
// NewSRpcService create a SRpcService
|
||||
func NewSRpcService(socketFileDir string, configStore _interface.IServiceConfigStore,
|
||||
tempalteStore _interface.ITemplateStore) *SRpcService {
|
||||
func NewSRpcService(socketFileDir string, configStore notifyv2.IServiceConfigStore,
|
||||
tempalteStore notifyv2.ITemplateStore) *SRpcService {
|
||||
return &SRpcService{
|
||||
SendServices: NewServiceMap(),
|
||||
socketFileDir: socketFileDir,
|
||||
@@ -129,10 +129,36 @@ func (self *SRpcService) Send(ctx context.Context, contactType, contact, topic,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SRpcService) BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error) {
|
||||
args, err := self.templateStore.NotifyFilter(contactType, topic, message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "templateStore.NotifyFilter")
|
||||
}
|
||||
|
||||
batchSendParams := apis.BatchSendParams{
|
||||
Contacts: contacts,
|
||||
Title: args.Title,
|
||||
Message: args.Message,
|
||||
Priority: args.Priority,
|
||||
RemoteTemplate: args.RemoteTemplate,
|
||||
}
|
||||
|
||||
f := func(service *apis.SendNotificationClient) (interface{}, error) {
|
||||
return service.BatchSend(ctx, &batchSendParams)
|
||||
}
|
||||
|
||||
ret, err := self.execute(ctx, f, contactType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "contactType '%s'", contactType)
|
||||
}
|
||||
reply := ret.(*apis.BatchSendReply)
|
||||
return reply.FailedRecords, nil
|
||||
}
|
||||
|
||||
// RestartService can restart remote rpc server and pass config info.
|
||||
// This function should be call immediately after init notify server firstly
|
||||
// This function should be call immediately after accept the request about changing config.
|
||||
func (self *SRpcService) RestartService(ctx context.Context, config _interface.SConfig, serviceName string) {
|
||||
func (self *SRpcService) RestartService(ctx context.Context, config notifyv2.SConfig, serviceName string) {
|
||||
_, err := self.restartWithConfig(ctx, serviceName, config)
|
||||
if err != nil {
|
||||
log.Debugf("restart service failed: %s", err)
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache // import "yunion.io/x/onecloud/pkg/notify/cache"
|
||||
package service // import "yunion.io/x/onecloud/pkg/notify/service"
|
||||
63
pkg/notify/service/handlers.go
Normal file
63
pkg/notify/service/handlers.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/oldmodels"
|
||||
)
|
||||
|
||||
const (
|
||||
API_VERSION = "v2"
|
||||
)
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
db.InitAllManagers()
|
||||
|
||||
db.RegistUserCredCacheUpdater()
|
||||
|
||||
db.AddScopeResourceCountHandler(API_VERSION, app)
|
||||
|
||||
// Data migration
|
||||
db.RegisterModelManager(oldmodels.NotificationManager)
|
||||
db.RegisterModelManager(oldmodels.ContactManager)
|
||||
db.RegisterModelManager(oldmodels.ConfigManager)
|
||||
db.RegisterModelManager(oldmodels.TemplateManager)
|
||||
db.RegisterModelManager(oldmodels.UserCacheManager)
|
||||
|
||||
taskman.AddTaskHandler(API_VERSION, app)
|
||||
for _, manager := range []db.IModelManager{
|
||||
taskman.TaskManager,
|
||||
taskman.SubTaskManager,
|
||||
taskman.TaskObjectManager,
|
||||
|
||||
db.UserCacheManager,
|
||||
db.TenantCacheManager,
|
||||
models.SubContactManager,
|
||||
models.VerificationManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
}
|
||||
for _, manager := range []db.IModelManager{
|
||||
db.OpsLog,
|
||||
db.Metadata,
|
||||
|
||||
models.ReceiverManager,
|
||||
models.NotificationManager,
|
||||
models.ConfigManager,
|
||||
models.TemplateManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
handler := db.NewModelHandler(manager)
|
||||
dispatcher.AddModelDispatcher(API_VERSION, app, handler)
|
||||
}
|
||||
for _, manager := range []db.IJointModelManager{
|
||||
models.ReceiverNotificationManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
handler := db.NewJointModelHandler(manager)
|
||||
dispatcher.AddJointModelDispatcher(API_VERSION, app, handler)
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notify
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -29,12 +28,11 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/cache"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
_ "yunion.io/x/onecloud/pkg/notify/policy"
|
||||
"yunion.io/x/onecloud/pkg/notify/rpc"
|
||||
_ "yunion.io/x/onecloud/pkg/notify/tasks"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
@@ -60,9 +58,6 @@ func StartService() {
|
||||
db.EnsureAppInitSyncDB(applicaion, dbOpts, models.InitDB)
|
||||
defer cloudcommon.CloseDB()
|
||||
|
||||
// init cache
|
||||
cache.RegistUserCredCacheUpdater()
|
||||
|
||||
// init notify service
|
||||
models.NotifyService = rpc.NewSRpcService(opts.SocketFileDir, models.ConfigManager, models.TemplateManager)
|
||||
models.NotifyService.InitAll()
|
||||
@@ -73,10 +68,7 @@ func StartService() {
|
||||
cron.AddJobAtIntervals("UpdateServices", time.Duration(opts.UpdateInterval)*time.Minute, models.NotifyService.UpdateServices)
|
||||
|
||||
// wrapped func to resend notifications
|
||||
resend := func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||||
models.ReSend(opts.ReSendScope)
|
||||
}
|
||||
cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Second, resend)
|
||||
cron.AddJobAtIntervals("ReSendNotifications", time.Duration(opts.ReSendScope)*time.Second, models.NotificationManager.ReSend)
|
||||
cron.Start()
|
||||
|
||||
app.ServeForever(applicaion, baseOpts)
|
||||
@@ -12,4 +12,4 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils // import "yunion.io/x/onecloud/pkg/notify/utils"
|
||||
package tasks // import "yunion.io/x/onecloud/pkg/notify/tasks"
|
||||
148
pkg/notify/tasks/notifications_send_task.go
Normal file
148
pkg/notify/tasks/notifications_send_task.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
apis "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
type NotificationSendTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(NotificationSendTask{})
|
||||
}
|
||||
|
||||
func (self *NotificationSendTask) taskFailed(ctx context.Context, notification *models.SNotification, reason string, all bool) {
|
||||
log.Errorf("fail to send notification %q", notification.GetId())
|
||||
if all {
|
||||
notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_FAILED, reason)
|
||||
} else {
|
||||
notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_PART_OK, reason)
|
||||
}
|
||||
notification.AddOne()
|
||||
logclient.AddActionLogWithContext(ctx, notification, logclient.ACT_SEND_NOTIFICATION, reason, self.UserCred, false)
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(reason))
|
||||
}
|
||||
|
||||
func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
notification := obj.(*models.SNotification)
|
||||
if notification.Status == apis.NOTIFICATION_STATUS_OK {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
return
|
||||
}
|
||||
rns, err := notification.ReceiverNotificationsNotOK()
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, notification, "fail to fetch ReceiverNotifications", true)
|
||||
return
|
||||
}
|
||||
notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_SENDING, "")
|
||||
|
||||
// sort out what needs to be sent
|
||||
failedRecord := make([]string, 0)
|
||||
sendFail := func(rn *models.SReceiverNotification, reason string) {
|
||||
rn.AfterSend(ctx, false, reason)
|
||||
failedRecord = append(failedRecord, fmt.Sprintf("%s: %s", rn.ReceiverID, reason))
|
||||
}
|
||||
|
||||
// build contactMap
|
||||
contactMap := make(map[string]*models.SReceiverNotification)
|
||||
for i := range rns {
|
||||
if len(rns[i].ReceiverID) == 0 {
|
||||
contactMap[rns[i].Contact] = &rns[i]
|
||||
continue
|
||||
}
|
||||
receiver, err := rns[i].Receiver()
|
||||
if err != nil {
|
||||
sendFail(&rns[i], fmt.Sprintf("fail to fetch Receiver: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
// check receiver enabled
|
||||
if receiver.Enabled.IsFalse() {
|
||||
sendFail(&rns[i], fmt.Sprintf("disabled receiver"))
|
||||
continue
|
||||
}
|
||||
// check contact enabled
|
||||
enabled, err := receiver.IsEnabledContactType(notification.ContactType)
|
||||
if err != nil {
|
||||
sendFail(&rns[i], fmt.Sprintf("IsEnabledContactType error for receiver: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
if !enabled {
|
||||
sendFail(&rns[i], fmt.Sprintf("disabled contactType %q", notification.ContactType))
|
||||
continue
|
||||
}
|
||||
|
||||
// check contact verified
|
||||
verified, err := receiver.IsVerifiedContactType(notification.ContactType)
|
||||
if err != nil {
|
||||
sendFail(&rns[i], fmt.Sprintf("IsVerifiedContactType error for receiver: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
if !verified {
|
||||
sendFail(&rns[i], fmt.Sprintf("unverified contactType %q", notification.ContactType))
|
||||
continue
|
||||
}
|
||||
|
||||
contact, err := receiver.GetContact(notification.ContactType)
|
||||
if err != nil {
|
||||
reason := fmt.Sprintf("fail to fetch contact: %s", err.Error())
|
||||
sendFail(&rns[i], reason)
|
||||
continue
|
||||
}
|
||||
contactMap[contact] = &rns[i]
|
||||
}
|
||||
|
||||
if len(contactMap) == 0 {
|
||||
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true)
|
||||
}
|
||||
|
||||
// set status before send
|
||||
now := time.Now()
|
||||
contacts := make([]string, 0, len(contactMap))
|
||||
for c, rn := range contactMap {
|
||||
rn.BeforeSend(ctx, now)
|
||||
contacts = append(contacts, c)
|
||||
}
|
||||
|
||||
// send
|
||||
ret, err := models.NotifyService.BatchSend(ctx, contacts, notification.ContactType, notification.Topic, notification.Message, notification.Priority)
|
||||
if err != nil {
|
||||
for _, rn := range contactMap {
|
||||
rn.AfterSend(ctx, false, err.Error())
|
||||
}
|
||||
failedRecord = append(failedRecord, fmt.Sprintf("others: %s", err.Error()))
|
||||
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true)
|
||||
return
|
||||
}
|
||||
|
||||
// check result
|
||||
for _, fd := range ret {
|
||||
rn := contactMap[fd.Contact]
|
||||
rn.AfterSend(ctx, false, fd.Reason)
|
||||
failedRecord = append(failedRecord, fmt.Sprintf("%s: %s", rn.ReceiverID, fd.Reason))
|
||||
}
|
||||
if len(failedRecord) == len(contacts) {
|
||||
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true)
|
||||
return
|
||||
}
|
||||
if len(failedRecord) > 0 {
|
||||
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), false)
|
||||
return
|
||||
}
|
||||
log.Infof("successfully send notification %q", notification.GetId())
|
||||
notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_OK, "")
|
||||
logclient.AddActionLogWithContext(ctx, notification, logclient.ACT_SEND_NOTIFICATION, "", self.UserCred, true)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
87
pkg/notify/tasks/subcontact_pull_task.go
Normal file
87
pkg/notify/tasks/subcontact_pull_task.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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 tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
apis "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
var PullContactType = []string{
|
||||
apis.DINGTALK,
|
||||
apis.FEISHU,
|
||||
apis.WORKWX,
|
||||
}
|
||||
|
||||
type SubcontactPullTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(SubcontactPullTask{})
|
||||
}
|
||||
|
||||
func (self *SubcontactPullTask) taskFailed(ctx context.Context, receiver *models.SReceiver, reason string) {
|
||||
log.Errorf("fail to pull subcontact of receiver %q: %s", receiver.Id, reason)
|
||||
receiver.SetStatus(self.UserCred, apis.RECEIVER_STATUS_PULL_FAILED, reason)
|
||||
logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_PULL_SUBCONTACT, reason, self.UserCred, false)
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(reason))
|
||||
}
|
||||
|
||||
func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
// pull contacts
|
||||
receiver := obj.(*models.SReceiver)
|
||||
if len(receiver.Mobile) == 0 {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
return
|
||||
}
|
||||
enabledContactTypes, _ := receiver.GetEnabledContactTypes()
|
||||
for _, cType := range enabledContactTypes {
|
||||
if !utils.IsInStringArray(cType, PullContactType) {
|
||||
continue
|
||||
}
|
||||
userid, err := models.NotifyService.ContactByMobile(ctx, receiver.Mobile, cType)
|
||||
if err != nil {
|
||||
reason := fmt.Sprintf("fail to get %s contact by mobile %q: %v", cType, receiver.Mobile, err)
|
||||
self.taskFailed(ctx, receiver, reason)
|
||||
return
|
||||
}
|
||||
receiver.SetContact(cType, userid)
|
||||
receiver.MarkContactTypeVerified(cType)
|
||||
}
|
||||
receiver.SetContact(apis.WEBCONSOLE, receiver.Id)
|
||||
receiver.MarkContactTypeVerified(apis.WEBCONSOLE)
|
||||
// push cache
|
||||
err := receiver.PushCache(ctx)
|
||||
if err != nil {
|
||||
reason := fmt.Sprintf("PushCache: %v", err)
|
||||
self.taskFailed(ctx, receiver, reason)
|
||||
return
|
||||
}
|
||||
// success
|
||||
receiver.SetStatus(self.UserCred, apis.RECEIVER_STATUS_READY, "")
|
||||
logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_PULL_SUBCONTACT, "", self.UserCred, true)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
76
pkg/notify/tasks/verification_send_task.go
Normal file
76
pkg/notify/tasks/verification_send_task.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
type VerificationSendTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(VerificationSendTask{})
|
||||
}
|
||||
|
||||
func (self *VerificationSendTask) taskFailed(ctx context.Context, receiver *models.SReceiver, reason string) {
|
||||
log.Errorf("fail to send verification: %s", reason)
|
||||
logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_SEND_VERIFICATION, reason, self.UserCred, false)
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(reason))
|
||||
}
|
||||
|
||||
func (self *VerificationSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
receiver := obj.(*models.SReceiver)
|
||||
contactType, _ := self.Params.GetString("contact_type")
|
||||
verification, err := models.VerificationManager.Get(receiver.GetId(), contactType)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, receiver, fmt.Sprintf("VerificationManager.Get for receiver_id %q and contact_type %q: %s", receiver.GetId(), contactType, err.Error()))
|
||||
return
|
||||
}
|
||||
contact, err := receiver.GetContact(contactType)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, receiver, fmt.Sprintf("fail to get contact(type: %s): %s", contactType, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// build message
|
||||
var message string
|
||||
switch contactType {
|
||||
case api.EMAIL:
|
||||
info, err := models.TemplateManager.GetCompanyInfo(ctx)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, receiver, fmt.Sprintf("fail to get company info: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
data := struct {
|
||||
models.SCompanyInfo
|
||||
Name string
|
||||
Code string
|
||||
}{
|
||||
Name: receiver.Name,
|
||||
Code: verification.Token,
|
||||
SCompanyInfo: info,
|
||||
}
|
||||
message = jsonutils.Marshal(data).String()
|
||||
case api.MOBILE:
|
||||
message = fmt.Sprintf(`{"code": "%s"}`, verification.Token)
|
||||
default:
|
||||
// no way
|
||||
}
|
||||
err = models.NotifyService.Send(ctx, contactType, contact, "verify", message, "")
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, receiver, err.Error())
|
||||
return
|
||||
}
|
||||
logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_SEND_VERIFICATION, "", self.UserCred, true)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/cache"
|
||||
)
|
||||
|
||||
func GetUserByIDOrName(ctx context.Context, idStr string) (*cache.SUser, error) {
|
||||
return cache.UserCacheManager.FetchUserByIDOrName(ctx, idStr)
|
||||
}
|
||||
|
||||
func GetUsersWithoutRemote(ctx context.Context, 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))).Desc("updated_at")
|
||||
return cache.UserCacheManager.FetchUserFromLoaclCache(ctx, q)
|
||||
}
|
||||
|
||||
func DeleteUsers(ctx context.Context, userCred mcclient.TokenCredential, ids []string) error {
|
||||
users, err := GetUsersWithoutRemote(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range users {
|
||||
err := users[i].Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "delete cache.SUser %s error", users[i].Id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUserIdsLikeName(ctx context.Context, name string) ([]string, error) {
|
||||
users, err := cache.UserCacheManager.FetchUserLikeName(ctx, name, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, len(users))
|
||||
for i := range users {
|
||||
ret[i] = users[i].Id
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func GetUsersByGroupID(ctx context.Context, gid string) ([]string, error) {
|
||||
ret, err := cache.UserGroupCacheManager.FetchByGroupId(ctx, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]string, len(ret))
|
||||
for i := range ret {
|
||||
ids[i] = ret[i].UserId
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func GetUsernameByID(ctx context.Context, id string) (string, error) {
|
||||
user, err := GetUserByIDOrName(ctx, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user.Name, nil
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
|
||||
func FetchOwnerId(ctx context.Context, manager db.IModelManager, userCred mcclient.TokenCredential, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
var ownerId mcclient.IIdentityProvider
|
||||
var err error
|
||||
if manager.ResourceScope() != rbacutils.ScopeSystem {
|
||||
ownerId, err = manager.FetchOwnerId(ctx, data)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
}
|
||||
if ownerId == nil {
|
||||
ownerId = userCred
|
||||
}
|
||||
return ownerId, nil
|
||||
}
|
||||
|
||||
func GenerateMobileToken() string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
token := fmt.Sprintf("%06v", rnd.Int31n(1000000))
|
||||
return token
|
||||
}
|
||||
|
||||
func GenerateEmailToken(tokenLen int) string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
token := new(bytes.Buffer)
|
||||
for token.Len() < tokenLen {
|
||||
token.WriteString(fmt.Sprintf("%x", rnd.Int31()))
|
||||
}
|
||||
return token.String()[:tokenLen]
|
||||
}
|
||||
|
||||
func JsonArrayToStringArray(src []jsonutils.JSONObject) []string {
|
||||
des := make([]string, len(src))
|
||||
for i := range src {
|
||||
des[i], _ = src[i].GetString()
|
||||
}
|
||||
return des
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenEmailToken(t *testing.T) {
|
||||
s := GenerateEmailToken(32)
|
||||
if len(s) != 32 {
|
||||
t.Error("email token length should be 32")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenMobileToke(t *testing.T) {
|
||||
s := GenerateMobileToken()
|
||||
if len(s) != 6 {
|
||||
t.Errorf("mobile token length should be 6")
|
||||
}
|
||||
}
|
||||
@@ -205,4 +205,8 @@ const (
|
||||
|
||||
ACT_UPDATE_BILLING_OPTIONS = "更新账单文件"
|
||||
ACT_UPDATE_CREDENTIAL = "更新账号密码"
|
||||
|
||||
ACT_PULL_SUBCONTACT = "拉取联系方式"
|
||||
ACT_SEND_NOTIFICATION = "发送通知消息"
|
||||
ACT_SEND_VERIFICATION = "发送验证消息"
|
||||
)
|
||||
|
||||
@@ -56,6 +56,8 @@ def run_swagger_yaml(svc, swagger_pkg_dir, output_dir):
|
||||
cmd.extend(["-o", pjoin(output_dir, "swagger_%s.yaml" % svc)])
|
||||
run_cmd(cmd)
|
||||
|
||||
def remove_prefix(text, prefix):
|
||||
return text[text.startswith(prefix) and len(prefix):]
|
||||
|
||||
class FuncDispatcher(object):
|
||||
|
||||
@@ -71,7 +73,7 @@ class FuncDispatcher(object):
|
||||
func = getattr(self, attr)
|
||||
if not callable(func):
|
||||
continue
|
||||
svc = attr.lstrip('gen_')
|
||||
svc = remove_prefix(attr, 'gen_')
|
||||
gen_dict[svc] = func
|
||||
self.gen_dict = gen_dict
|
||||
|
||||
@@ -131,6 +133,7 @@ class ModelAPI(FuncDispatcher):
|
||||
self.run_model("compute")
|
||||
self.run_model("image")
|
||||
self.run_model("cloudid")
|
||||
self.run_model("notify")
|
||||
|
||||
def gen_monitor(self):
|
||||
self.run(pkg=["monitor", "models"], out=["monitor"])
|
||||
@@ -174,6 +177,8 @@ class SwaggerCode(FuncDispatcher):
|
||||
def gen_monitor(self):
|
||||
self.run("monitor", pkg=["models"], out="monitor")
|
||||
|
||||
def gen_notify(self):
|
||||
self.run("notify", pkg=["models"], out="notify")
|
||||
|
||||
class SwaggerYAML(FuncDispatcher):
|
||||
|
||||
@@ -204,6 +209,8 @@ class SwaggerYAML(FuncDispatcher):
|
||||
def gen_monitor(self):
|
||||
self.run("monitor")
|
||||
|
||||
def gen_notify(self):
|
||||
self.run("notify")
|
||||
|
||||
class SwaggerServe(object):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user