mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-24 10:23:43 +08:00
756 lines
24 KiB
Go
756 lines
24 KiB
Go
// 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)
|
|
}
|
|
|
|
pulls := make([]string, len(pullCtypes))
|
|
for i := range pullCtypes {
|
|
pulls[i], _ = pullCtypes[i].GetString()
|
|
}
|
|
|
|
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()
|
|
contact, err := models.ContactManager.FetchByUIDs(ctx, []string{idstr}, false)
|
|
if err != nil {
|
|
log.Errorf(err.Error())
|
|
return ret, nil
|
|
}
|
|
if len(contact) == 0 {
|
|
return nil, nil
|
|
}
|
|
outDetails, err := contact[0].GetExtraDetails(ctx, userCred, ret, false)
|
|
if err != nil {
|
|
log.Errorf(err.Error())
|
|
return ret, nil
|
|
}
|
|
out := jsonutils.Marshal(outDetails)
|
|
out.(*jsonutils.JSONDict).Set("created_at", jsonutils.NewString(contact[0].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(err.Error())
|
|
}
|
|
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
|
|
}
|