[CLD-7421][CLD-7420] Deprecate Self Serve: First Pass (#26668)
* Deprecate Self Serve: First Pass * Fix ci * Fix more ci * Remmove outdated server tests * Fix a missed spot opening purchase modal in Self Hosted * Fix i18n * Clean up some more server code, fix webapp test * Fix alignment of button * Fix linter * Fix i18n server side * Add back translation * Remove client functions * Put back client functions --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
|
||||
)
|
||||
|
||||
@@ -25,11 +24,6 @@ func (api *API) InitCloud() {
|
||||
|
||||
api.BaseRoutes.Cloud.Handle("/products/selfhosted", api.APISessionRequired(getSelfHostedProducts)).Methods("GET")
|
||||
|
||||
// POST /api/v4/cloud/payment
|
||||
// POST /api/v4/cloud/payment/confirm
|
||||
api.BaseRoutes.Cloud.Handle("/payment", api.APISessionRequired(createCustomerPayment)).Methods("POST")
|
||||
api.BaseRoutes.Cloud.Handle("/payment/confirm", api.APISessionRequired(confirmCustomerPayment)).Methods("POST")
|
||||
|
||||
// GET /api/v4/cloud/customer
|
||||
// PUT /api/v4/cloud/customer
|
||||
// PUT /api/v4/cloud/customer/address
|
||||
@@ -42,10 +36,6 @@ func (api *API) InitCloud() {
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.APISessionRequired(getInvoicesForSubscription)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:[_A-Za-z0-9]+}/pdf", api.APISessionRequired(getSubscriptionInvoicePDF)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/self-serve-status", api.APISessionRequired(getLicenseSelfServeStatus)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(changeSubscription)).Methods("PUT")
|
||||
|
||||
// GET /api/v4/cloud/request-trial
|
||||
api.BaseRoutes.Cloud.Handle("/request-trial", api.APISessionRequired(requestCloudTrial)).Methods("PUT")
|
||||
|
||||
// GET /api/v4/cloud/validate-business-email
|
||||
api.BaseRoutes.Cloud.Handle("/validate-business-email", api.APISessionRequired(validateBusinessEmail)).Methods("POST")
|
||||
@@ -59,8 +49,6 @@ func (api *API) InitCloud() {
|
||||
|
||||
// GET /api/v4/cloud/cws-health-check
|
||||
api.BaseRoutes.Cloud.Handle("/check-cws-connection", api.APIHandler(handleCheckCWSConnection)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.Cloud.Handle("/delete-workspace", api.APISessionRequired(selfServeDeleteWorkspace)).Methods("DELETE")
|
||||
}
|
||||
|
||||
func ensureCloudInterface(c *Context, where string) bool {
|
||||
@@ -134,131 +122,6 @@ func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.changeSubscription")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.license_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var subscriptionChange *model.SubscriptionChange
|
||||
if err = json.Unmarshal(bodyBytes, &subscriptionChange); err != nil || subscriptionChange == nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
currentSubscription, appErr := c.App.Cloud().GetSubscription(userId)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
changedSub, err := c.App.Cloud().ChangeSubscription(userId, currentSubscription.ID, subscriptionChange)
|
||||
if err != nil {
|
||||
appErr := model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
if err.Error() == "compliance-failed" {
|
||||
c.Logger.Error("Compliance check failed", mlog.Err(err))
|
||||
appErr.StatusCode = http.StatusUnprocessableEntity
|
||||
}
|
||||
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if subscriptionChange.Feedback != nil {
|
||||
c.App.Srv().GetTelemetryService().SendTelemetry("downgrade_feedback", subscriptionChange.Feedback.ToMap())
|
||||
}
|
||||
|
||||
json, err := json.Marshal(changedSub)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.changeSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
product, err := c.App.Cloud().GetCloudProduct(c.AppContext.Session().UserId, subscriptionChange.ProductID)
|
||||
if err != nil || product == nil {
|
||||
c.Logger.Error("Error finding the new cloud product", mlog.Err(err))
|
||||
}
|
||||
|
||||
if product.SKU == string(model.SkuCloudStarter) {
|
||||
w.Write(json)
|
||||
return
|
||||
}
|
||||
|
||||
isYearly := product.IsYearly()
|
||||
|
||||
// Log failures for purchase confirmation email, but don't show an error to the user so as not to confuse them
|
||||
// At this point, the upgrade is complete.
|
||||
if appErr := c.App.SendUpgradeConfirmationEmail(isYearly); appErr != nil {
|
||||
c.Logger.Error("Error sending purchase confirmation email", mlog.Err(appErr))
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.requestCloudTrial")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
// check if the email needs to be set
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
// this value will not be empty when both emails (user admin and CWS customer) are not business email and
|
||||
// a new business email was provided via the request business email modal
|
||||
var startTrialRequest *model.StartCloudTrialRequest
|
||||
if err = json.Unmarshal(bodyBytes, &startTrialRequest); err != nil || startTrialRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
changedSub, err := c.App.Cloud().RequestCloudTrial(c.AppContext.Session().UserId, startTrialRequest.SubscriptionID, startTrialRequest.Email)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(changedSub)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer c.App.Srv().Cloud.InvalidateCaches()
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.validateBusinessEmail")
|
||||
if !ensured {
|
||||
@@ -635,84 +498,6 @@ func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Reque
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func createCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.createCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("createCustomerPayment", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
intent, err := c.App.Cloud().CreateCustomerPayment(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(intent)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.confirmCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("confirmCustomerPayment", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirmRequest *model.ConfirmPaymentMethodRequest
|
||||
if err = json.Unmarshal(bodyBytes, &confirmRequest); err != nil || confirmRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.App.Cloud().ConfirmCustomerPayment(c.AppContext.Session().UserId, confirmRequest)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getInvoicesForSubscription")
|
||||
if !ensured {
|
||||
@@ -809,40 +594,6 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
switch event.Event {
|
||||
case model.EventTypeFailedPayment:
|
||||
if nErr := c.App.SendPaymentFailedEmail(event.FailedPayment); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeFailedPaymentNoCard:
|
||||
if nErr := c.App.SendNoCardPaymentFailedEmail(); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeSendUpgradeConfirmationEmail:
|
||||
|
||||
// isYearly determines whether to send the yearly or monthly Upgrade email
|
||||
isYearly := false
|
||||
if event.Subscription != nil && event.CloudWorkspaceOwner != nil {
|
||||
user, appErr := c.App.GetUserByUsername(event.CloudWorkspaceOwner.UserName)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", appErr.Id, nil, "", appErr.StatusCode).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the current cloud product to determine whether it's a monthly or yearly product
|
||||
product, err := c.App.Cloud().GetCloudProduct(user.Id, event.Subscription.ProductID)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
isYearly = product.IsYearly()
|
||||
}
|
||||
|
||||
if nErr := c.App.SendUpgradeConfirmationEmail(isYearly); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeSendAdminWelcomeEmail:
|
||||
user, appErr := c.App.GetUserByUsername(event.CloudWorkspaceOwner.UserName)
|
||||
if appErr != nil {
|
||||
@@ -868,19 +619,6 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err = model.NewAppError("SendCloudWelcomeEmail", "api.user.send_cloud_welcome_email.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
case model.EventTypeTriggerDelinquencyEmail:
|
||||
var emailToTrigger model.DelinquencyEmail
|
||||
if event.DelinquencyEmail != nil {
|
||||
emailToTrigger = model.DelinquencyEmail(event.DelinquencyEmail.EmailToTrigger)
|
||||
} else {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.delinquency_email.missing_email_to_trigger", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if nErr := c.App.SendDelinquencyEmail(emailToTrigger); nErr != nil {
|
||||
c.Err = nErr
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.cws_webhook_event_missing_error", nil, "", http.StatusNotFound)
|
||||
return
|
||||
@@ -902,37 +640,3 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfServeDeleteWorkspace(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ensured := ensureCloudInterface(c, "Api4.selfServeDeleteWorkspace")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
|
||||
var deleteRequest *model.WorkspaceDeletionRequest
|
||||
if err = json.Unmarshal(bodyBytes, &deleteRequest); err != nil || deleteRequest == nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.App.Cloud().SelfServeDeleteWorkspace(c.AppContext.Session().UserId, deleteRequest); err != nil {
|
||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.server.cws.delete_workspace.app_error", nil, "CWS Server failed to delete workspace.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
c.App.Srv().GetTelemetryService().SendTelemetry("delete_workspace_feedback", deleteRequest.Feedback.ToMap())
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
@@ -16,119 +16,6 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
func Test_getCloudLimits(t *testing.T) {
|
||||
t.Run("no license returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.App.Srv().RemoveLicense()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "Expected 403 forbidden")
|
||||
})
|
||||
|
||||
t.Run("non cloud license returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "Expected 403 forbidden")
|
||||
})
|
||||
|
||||
t.Run("error fetching limits returns internal server error", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusInternalServerError, r.StatusCode, "Expected 500 Internal Server Error")
|
||||
})
|
||||
|
||||
t.Run("unauthenticated users can not access", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Logout(context.Background())
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Nil(t, limits)
|
||||
require.Equal(t, http.StatusUnauthorized, r.StatusCode, "Expected 401 Unauthorized")
|
||||
})
|
||||
|
||||
t.Run("good request with cloud server", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := &mocks.CloudInterface{}
|
||||
ten := 10
|
||||
mockLimits := &model.ProductLimits{
|
||||
Messages: &model.MessagesLimits{
|
||||
History: &ten,
|
||||
},
|
||||
}
|
||||
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(mockLimits, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
limits, r, err := th.Client.GetProductLimits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Expected 200 OK")
|
||||
require.Equal(t, mockLimits, limits)
|
||||
require.Equal(t, *mockLimits.Messages.History, *limits.Messages.History)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetSubscription(t *testing.T) {
|
||||
deliquencySince := int64(2000000000)
|
||||
|
||||
@@ -215,119 +102,6 @@ func Test_GetSubscription(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_requestTrial(t *testing.T) {
|
||||
subscription := &model.Subscription{
|
||||
ID: "MySubscriptionID",
|
||||
CustomerID: "MyCustomer",
|
||||
ProductID: "SomeProductId",
|
||||
AddOns: []string{},
|
||||
StartAt: 1000000000,
|
||||
EndAt: 2000000000,
|
||||
CreateAt: 1000000000,
|
||||
Seats: 10,
|
||||
DNS: "some.dns.server",
|
||||
}
|
||||
|
||||
newValidBusinessEmail := model.StartCloudTrialRequest{Email: ""}
|
||||
|
||||
t.Run("NON Admin users are UNABLE to request the trial", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.Client.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, subscriptionChanged)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
|
||||
})
|
||||
|
||||
t.Run("ADMIN user are ABLE to request the trial", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, subscriptionChanged, subscription)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
|
||||
})
|
||||
|
||||
t.Run("ADMIN user are ABLE to request the trial with valid business email", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// patch the customer with the additional contact updated with the valid business email
|
||||
newValidBusinessEmail.Email = *model.NewString("valid.email@mattermost.com")
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||||
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "valid.email@mattermost.com").Return(subscription, nil)
|
||||
cloud.Mock.On("InvalidateCaches").Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(context.Background(), &newValidBusinessEmail)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, subscriptionChanged, subscription)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
|
||||
})
|
||||
|
||||
t.Run("Empty body returns bad request", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
r, err := th.SystemAdminClient.DoAPIPutBytes(context.Background(), "/cloud/request-trial", nil)
|
||||
require.Error(t, err)
|
||||
closeBody(r)
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_validateBusinessEmail(t *testing.T) {
|
||||
t.Run("Returns forbidden for invalid business email", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
@@ -643,58 +417,6 @@ func TestGetCloudProducts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetExpandStatsForSubscription(t *testing.T) {
|
||||
status := &model.SubscriptionLicenseSelfServeStatusResponse{
|
||||
IsExpandable: true,
|
||||
}
|
||||
|
||||
licenseId := "licenseID"
|
||||
|
||||
t.Run("NON Admin users are UNABLE to request expand stats for the subscription", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetLicenseSelfServeStatus", mock.Anything).Return(status, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
checksMade, r, err := th.Client.GetSubscriptionStatus(context.Background(), licenseId)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, checksMade)
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
|
||||
})
|
||||
|
||||
t.Run("Admin users are UNABLE to request licenses is expendable due missing the id", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("GetLicenseSelfServeStatus", mock.Anything).Return(status, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
checks, r, err := th.Client.GetSubscriptionStatus(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, checks)
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode, "400 Bad Request")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSelfHostedProducts(t *testing.T) {
|
||||
products := []*model.Product{
|
||||
{
|
||||
|
||||
@@ -4,20 +4,11 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
|
||||
)
|
||||
|
||||
// APIs for self-hosted workspaces to communicate with the backing customer & payments system.
|
||||
@@ -25,276 +16,12 @@ import (
|
||||
func (api *API) InitHostedCustomer() {
|
||||
// POST /api/v4/hosted_customer/available
|
||||
api.BaseRoutes.HostedCustomer.Handle("/signup_available", api.APISessionRequired(handleSignupAvailable)).Methods("GET")
|
||||
// POST /api/v4/hosted_customer/bootstrap
|
||||
api.BaseRoutes.HostedCustomer.Handle("/bootstrap", api.APISessionRequired(selfHostedBootstrap)).Methods("POST")
|
||||
// POST /api/v4/hosted_customer/customer
|
||||
api.BaseRoutes.HostedCustomer.Handle("/customer", api.APISessionRequired(selfHostedCustomer)).Methods("POST")
|
||||
// POST /api/v4/hosted_customer/confirm
|
||||
api.BaseRoutes.HostedCustomer.Handle("/confirm", api.APISessionRequired(selfHostedConfirm)).Methods("POST")
|
||||
// POST /api.v4/hosted_customer/confirm-expand
|
||||
api.BaseRoutes.HostedCustomer.Handle("/confirm-expand", api.APISessionRequired(selfHostedConfirmExpand)).Methods("POST")
|
||||
// GET /api/v4/hosted_customer/invoices
|
||||
api.BaseRoutes.HostedCustomer.Handle("/invoices", api.APISessionRequired(selfHostedInvoices)).Methods("GET")
|
||||
// GET /api/v4/hosted_customer/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf
|
||||
api.BaseRoutes.HostedCustomer.Handle("/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf", api.APISessionRequired(selfHostedInvoicePDF)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.HostedCustomer.Handle("/subscribe-newsletter", api.APIHandler(handleSubscribeToNewsletter)).Methods("POST")
|
||||
}
|
||||
|
||||
func ensureSelfHostedAdmin(c *Context, where string) {
|
||||
ensured := ensureCloudInterface(c, where)
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
license := c.App.Channels().License()
|
||||
|
||||
if license.IsCloud() {
|
||||
c.Err = model.NewAppError(where, "api.cloud.license_error", nil, "Cloud installations do not use this endpoint", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkSelfHostedPurchaseEnabled(c *Context) bool {
|
||||
config := c.App.Config()
|
||||
if config == nil {
|
||||
return false
|
||||
}
|
||||
enabled := config.ServiceSettings.SelfHostedPurchase
|
||||
return enabled != nil && *enabled
|
||||
}
|
||||
|
||||
func selfHostedBootstrap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedBootstrap"
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
reset := r.URL.Query().Get("reset") == "true"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
signupProgress, err := c.App.Cloud().BootstrapSelfHostedSignup(model.BootstrapSelfHostedSignupRequest{Email: user.Email, Reset: reset})
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
json, err := json.Marshal(signupProgress)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedCustomer"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var form *model.SelfHostedCustomerForm
|
||||
if err = json.Unmarshal(bodyBytes, &form); err != nil || form == nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
customerResponse, err := c.App.Cloud().CreateCustomerSelfHostedSignup(*form, user.Email)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(customerResponse)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedConfirm(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedConfirm"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirm model.SelfHostedConfirmPaymentMethodRequest
|
||||
err = json.Unmarshal(bodyBytes, &confirm)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
confirmResponse, err := c.App.Cloud().ConfirmSelfHostedSignup(confirm, user.Email)
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
|
||||
if err.Error() == strconv.Itoa(http.StatusUnprocessableEntity) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusUnprocessableEntity).Wrap(err)
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
license, appErr := c.App.Srv().Platform().SaveLicense([]byte(confirmResponse.License))
|
||||
if appErr != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
clientResponse, err := json.Marshal(model.SelfHostedSignupConfirmClientResponse{
|
||||
License: utils.GetClientLicense(license),
|
||||
Progress: confirmResponse.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := c.App.Cloud().ConfirmSelfHostedSignupLicenseApplication()
|
||||
if err != nil {
|
||||
c.Logger.Warn("Unable to confirm license application", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
_, _ = w.Write(clientResponse)
|
||||
}
|
||||
|
||||
func handleSignupAvailable(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.handleSignupAvailable"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if err := c.App.Cloud().SelfHostedSignupAvailable(); err != nil {
|
||||
if err.Error() == "upstream_off" {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusServiceUnavailable)
|
||||
} else {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
return
|
||||
}
|
||||
systemValue, err := c.App.Srv().Store().System().GetByName(model.SystemHostedPurchaseNeedsScreening)
|
||||
if err == nil && systemValue != nil {
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusTooEarly)
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfHostedInvoices(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedInvoices"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
invoices, err := c.App.Cloud().GetSelfHostedInvoices(c.AppContext)
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "404" {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotFound).Wrap(errors.New("invoices for license not found"))
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(invoices)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func selfHostedInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedInvoicePDF"
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pdfData, filename, appErr := c.App.Cloud().GetSelfHostedInvoicePDF(c.Params.InvoiceId)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||
return
|
||||
}
|
||||
|
||||
web.WriteFileResponse(
|
||||
filename,
|
||||
"application/pdf",
|
||||
int64(binary.Size(pdfData)),
|
||||
time.Now(),
|
||||
*c.App.Config().ServiceSettings.WebserverMode,
|
||||
bytes.NewReader(pdfData),
|
||||
false,
|
||||
w,
|
||||
r,
|
||||
)
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func handleSubscribeToNewsletter(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -326,80 +53,3 @@ func handleSubscribeToNewsletter(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func selfHostedConfirmExpand(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.selfHostedConfirmExpand"
|
||||
|
||||
ensureSelfHostedAdmin(c, where)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !checkSelfHostedPurchaseEnabled(c) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
var confirm model.SelfHostedConfirmPaymentMethodRequest
|
||||
err = json.Unmarshal(bodyBytes, &confirm)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if userErr != nil {
|
||||
c.Err = userErr
|
||||
return
|
||||
}
|
||||
|
||||
confirmResponse, err := c.App.Cloud().ConfirmSelfHostedExpansion(confirm, user.Email)
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
|
||||
if err.Error() == strconv.Itoa(http.StatusUnprocessableEntity) {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusUnprocessableEntity).Wrap(err)
|
||||
return
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
license, appErr := c.App.Srv().Platform().SaveLicense([]byte(confirmResponse.License))
|
||||
// dealing with an AppError
|
||||
if appErr != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
clientResponse, err := json.Marshal(model.SelfHostedSignupConfirmClientResponse{
|
||||
License: utils.GetClientLicense(license),
|
||||
Progress: confirmResponse.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
if confirmResponse != nil {
|
||||
c.App.NotifySelfHostedSignupProgress(confirmResponse.Progress, user.Id)
|
||||
}
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := c.App.Cloud().ConfirmSelfHostedSignupLicenseApplication()
|
||||
if err != nil {
|
||||
c.Logger.Warn("Unable to confirm license application", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
_, _ = w.Write(clientResponse)
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
var valFalse = false
|
||||
var valTrue = true
|
||||
|
||||
func TestSelfHostedBootstrap(t *testing.T) {
|
||||
t.Run("feature flag off returns not implemented", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "false")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valFalse })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusNotImplemented, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("cloud instances not allowed to bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("non-admins not allowed to bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusForbidden, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("self-hosted admins can bootstrap self-hosted signup", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
cloud := mocks.CloudInterface{}
|
||||
|
||||
cloud.Mock.On("BootstrapSelfHostedSignup", mock.Anything).Return(&model.BootstrapSelfHostedSignupResponse{Progress: "START"}, nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
response, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusOK, r.StatusCode)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "START", response.Progress)
|
||||
})
|
||||
|
||||
t.Run("team edition returns bad request instead of panicking", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = nil
|
||||
|
||||
th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
|
||||
os.Setenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE", "true")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SELFHOSTEDFIRSTTIMEPURCHASE")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SelfHostedPurchase = &valTrue })
|
||||
th.App.ReloadConfig()
|
||||
|
||||
_, r, err := th.Client.BootstrapSelfHostedSignup(context.Background(), model.BootstrapSelfHostedSignupRequest{Email: th.SystemAdminUser.Email})
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -325,8 +325,6 @@ type AppIface interface {
|
||||
SearchAllChannels(c request.CTX, term string, opts model.ChannelSearchOpts) (model.ChannelListWithTeamData, int64, *model.AppError)
|
||||
// SearchAllTeams returns a team list and the total count of the results
|
||||
SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError)
|
||||
// SendNoCardPaymentFailedEmail
|
||||
SendNoCardPaymentFailedEmail() *model.AppError
|
||||
// SessionHasPermissionToChannels returns true only if user has access to all channels.
|
||||
SessionHasPermissionToChannels(c request.CTX, session model.Session, channelIDs []string, permission *model.Permission) bool
|
||||
// SessionHasPermissionToManageBot returns nil if the session has access to manage the given bot.
|
||||
@@ -595,7 +593,6 @@ type AppIface interface {
|
||||
DoLocalRequest(c request.CTX, rawURL string, body []byte) (*http.Response, *model.AppError)
|
||||
DoLogin(c request.CTX, w http.ResponseWriter, r *http.Request, user *model.User, deviceID string, isMobile, isOAuthUser, isSaml bool) (*model.Session, *model.AppError)
|
||||
DoPostActionWithCookie(c request.CTX, postID, actionId, userID, selectedOption string, cookie *model.PostActionCookie) (string, *model.AppError)
|
||||
DoSubscriptionRenewalCheck()
|
||||
DoSystemConsoleRolesCreationMigration()
|
||||
DoUploadFile(c request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte, extractContent bool) (*model.FileInfo, *model.AppError)
|
||||
DoUploadFileExpectModification(c request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte, extractContent bool) (*model.FileInfo, []byte, *model.AppError)
|
||||
@@ -1086,18 +1083,15 @@ type AppIface interface {
|
||||
SendAckToPushProxy(ack *model.PushNotificationAck) error
|
||||
SendAutoResponse(rctx request.CTX, channel *model.Channel, receiver *model.User, post *model.Post) (bool, *model.AppError)
|
||||
SendAutoResponseIfNecessary(rctx request.CTX, channel *model.Channel, sender *model.User, post *model.Post) (bool, *model.AppError)
|
||||
SendDelinquencyEmail(emailToSend model.DelinquencyEmail) *model.AppError
|
||||
SendEmailVerification(user *model.User, newEmail, redirect string) *model.AppError
|
||||
SendEphemeralPost(c request.CTX, userID string, post *model.Post) *model.Post
|
||||
SendIPFiltersChangedEmail(c request.CTX, userID string) error
|
||||
SendNotifications(c request.CTX, post *model.Post, team *model.Team, channel *model.Channel, sender *model.User, parentPostList *model.PostList, setOnline bool) ([]string, error)
|
||||
SendNotifyAdminPosts(c request.CTX, workspaceName string, currentSKU string, trial bool) *model.AppError
|
||||
SendPasswordReset(email string, siteURL string) (bool, *model.AppError)
|
||||
SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.AppError
|
||||
SendPersistentNotifications() error
|
||||
SendReportToUser(rctx request.CTX, job *model.Job, format string) *model.AppError
|
||||
SendTestPushNotification(deviceID string) string
|
||||
SendUpgradeConfirmationEmail(isYearly bool) *model.AppError
|
||||
ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string)
|
||||
SessionHasPermissionTo(session model.Session, permission *model.Permission) bool
|
||||
SessionHasPermissionToAny(session model.Session, permissions []*model.Permission) bool
|
||||
|
||||
@@ -4,135 +4,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
func getCurrentPlanName(a *App) (string, *model.AppError) {
|
||||
subscription, err := a.Cloud().GetSubscription("")
|
||||
if err != nil {
|
||||
return "", model.NewAppError("getCurrentPlanName", "app.cloud.get_subscription.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
if subscription == nil {
|
||||
return "", model.NewAppError("getCurrentPlanName", "app.cloud.get_subscription.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
products, err := a.Cloud().GetCloudProducts("", false)
|
||||
if err != nil {
|
||||
return "", model.NewAppError("getCurrentPlanName", "app.cloud.get_cloud_products.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
if products == nil {
|
||||
return "", model.NewAppError("getCurrentPlanName", "app.cloud.get_cloud_products.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
planName := getCurrentProduct(subscription.ProductID, products).Name
|
||||
return planName, nil
|
||||
}
|
||||
|
||||
func (a *App) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.AppError {
|
||||
sysAdmins, err := a.getAllSystemAdmins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
planName, err := getCurrentPlanName(a)
|
||||
if err != nil {
|
||||
return model.NewAppError("SendPaymentFailedEmail", "app.cloud.get_current_plan_name.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
for _, admin := range sysAdmins {
|
||||
_, err := a.Srv().EmailService.SendPaymentFailedEmail(admin.Email, admin.Locale, failedPayment, planName, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending payment failed email", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentProduct(subscriptionProductID string, products []*model.Product) *model.Product {
|
||||
for _, product := range products {
|
||||
if product.ID == subscriptionProductID {
|
||||
return product
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SendDelinquencyEmail(emailToSend model.DelinquencyEmail) *model.AppError {
|
||||
sysAdmins, aErr := a.getAllSystemAdmins()
|
||||
if aErr != nil {
|
||||
return aErr
|
||||
}
|
||||
planName, aErr := getCurrentPlanName(a)
|
||||
if aErr != nil {
|
||||
return model.NewAppError("SendDelinquencyEmail", "app.cloud.get_current_plan_name.app_error", nil, "", http.StatusInternalServerError).Wrap(aErr)
|
||||
}
|
||||
|
||||
subscription, err := a.Cloud().GetSubscription("")
|
||||
if err != nil {
|
||||
return model.NewAppError("SendDelinquencyEmail", "app.cloud.get_subscription.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
if subscription == nil {
|
||||
return model.NewAppError("SendDelinquencyEmail", "app.cloud.get_subscription.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if subscription.DelinquentSince == nil {
|
||||
return model.NewAppError("SendDelinquencyEmail", "app.cloud.get_subscription_delinquency_date.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
delinquentSince := time.Unix(*subscription.DelinquentSince, 0)
|
||||
|
||||
delinquencyDate := delinquentSince.Format("01/02/2006")
|
||||
for _, admin := range sysAdmins {
|
||||
switch emailToSend {
|
||||
case model.DelinquencyEmail7:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail7(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL, planName)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 7", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail14:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail14(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL, planName)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 14", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail30:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail30(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL, planName)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 30", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail45:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail45(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL, planName, delinquencyDate)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 45", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail60:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail60(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 60", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail75:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail75(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL, planName, delinquencyDate)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 75", mlog.Err(err))
|
||||
}
|
||||
case model.DelinquencyEmail90:
|
||||
err := a.Srv().EmailService.SendDelinquencyEmail90(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending delinquency email 90", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError {
|
||||
if limits.Teams != nil && limits.Teams.Active != nil && *limits.Teams.Active > 0 {
|
||||
err := a.AdjustTeamsFromProductLimits(limits.Teams)
|
||||
@@ -144,86 +18,6 @@ func (a *App) AdjustInProductLimits(limits *model.ProductLimits, subscription *m
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNextBillingDateString() string {
|
||||
now := time.Now()
|
||||
t := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
||||
return fmt.Sprintf("%s %d, %d", t.Month(), t.Day(), t.Year())
|
||||
}
|
||||
|
||||
func (a *App) SendUpgradeConfirmationEmail(isYearly bool) *model.AppError {
|
||||
sysAdmins, e := a.getAllSystemAdmins()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if len(sysAdmins) == 0 {
|
||||
return model.NewAppError("app.SendCloudUpgradeConfirmationEmail", "app.user.send_emails.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
subscription, err := a.Cloud().GetSubscription("")
|
||||
if err != nil {
|
||||
return model.NewAppError("app.SendCloudUpgradeConfirmationEmail", "app.user.send_emails.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
billingDate := getNextBillingDateString()
|
||||
|
||||
// we want to at least have one email sent out to an admin
|
||||
countNotOks := 0
|
||||
|
||||
embeddedFiles := make(map[string]io.Reader)
|
||||
if isYearly {
|
||||
lastInvoice := subscription.LastInvoice
|
||||
if lastInvoice == nil {
|
||||
a.Log().Error("Last invoice not defined for the subscription", mlog.String("subscription", subscription.ID))
|
||||
} else {
|
||||
pdf, filename, pdfErr := a.Cloud().GetInvoicePDF("", lastInvoice.ID)
|
||||
if pdfErr != nil {
|
||||
a.Log().Error("Error retrieving the invoice for subscription id", mlog.String("subscription", subscription.ID), mlog.Err(pdfErr))
|
||||
} else {
|
||||
embeddedFiles = map[string]io.Reader{
|
||||
filename: bytes.NewReader(pdf),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, admin := range sysAdmins {
|
||||
name := admin.FirstName
|
||||
if name == "" {
|
||||
name = admin.Username
|
||||
}
|
||||
|
||||
err := a.Srv().EmailService.SendCloudUpgradeConfirmationEmail(admin.Email, name, billingDate, admin.Locale, *a.Config().ServiceSettings.SiteURL, subscription.GetWorkSpaceNameFromDNS(), isYearly, embeddedFiles)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending trial ended email to", mlog.String("email", admin.Email), mlog.Err(err))
|
||||
countNotOks++
|
||||
}
|
||||
}
|
||||
|
||||
// if not even one admin got an email, we consider that this operation errored
|
||||
if countNotOks == len(sysAdmins) {
|
||||
return model.NewAppError("app.SendCloudUpgradeConfirmationEmail", "app.user.send_emails.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNoCardPaymentFailedEmail
|
||||
func (a *App) SendNoCardPaymentFailedEmail() *model.AppError {
|
||||
sysAdmins, err := a.getAllSystemAdmins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, admin := range sysAdmins {
|
||||
err := a.Srv().EmailService.SendNoCardPaymentFailedEmail(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending payment failed email", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create/ Update a subscription history event
|
||||
func (a *App) SendSubscriptionHistoryEvent(userID string) (*model.SubscriptionHistory, error) {
|
||||
license := a.Srv().License()
|
||||
@@ -240,106 +34,3 @@ func (a *App) SendSubscriptionHistoryEvent(userID string) (*model.SubscriptionHi
|
||||
}
|
||||
return a.Cloud().CreateOrUpdateSubscriptionHistoryEvent(userID, int(userCount))
|
||||
}
|
||||
|
||||
func (a *App) DoSubscriptionRenewalCheck() {
|
||||
if !a.License().IsCloud() || !a.Config().FeatureFlags.CloudAnnualRenewals {
|
||||
return
|
||||
}
|
||||
|
||||
subscription, err := a.Cloud().GetSubscription("")
|
||||
if err != nil {
|
||||
a.Log().Error("Error getting subscription", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
if subscription == nil {
|
||||
a.Log().Error("Subscription not found")
|
||||
return
|
||||
}
|
||||
|
||||
if subscription.IsFreeTrial == "true" {
|
||||
return // Don't send renewal emails for free trials
|
||||
}
|
||||
|
||||
if model.BillingType(subscription.BillingType) == model.BillingTypeLicensed || model.BillingType(subscription.BillingType) == model.BillingTypeInternal {
|
||||
return // Don't send renewal emails for licensed or internal billing
|
||||
}
|
||||
|
||||
sysVar, err := a.Srv().Store().System().GetByName(model.CloudRenewalEmail)
|
||||
if err != nil {
|
||||
// We only care about the error if it wasn't a not found error
|
||||
if _, ok := err.(*store.ErrNotFound); !ok {
|
||||
a.Log().Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
prevSentEmail := int64(0)
|
||||
if sysVar != nil {
|
||||
// We don't care about parse errors because it's possible the value is empty, and we've already defaulted to 0
|
||||
prevSentEmail, _ = strconv.ParseInt(sysVar.Value, 10, 64)
|
||||
}
|
||||
|
||||
if subscription.WillRenew == "true" {
|
||||
// They've already completed the renewal process so no need to email them.
|
||||
// We can zero out the system variable so that this process will work again next year
|
||||
if prevSentEmail != 0 {
|
||||
sysVar.Value = "0"
|
||||
err = a.Srv().Store().System().SaveOrUpdate(sysVar)
|
||||
if err != nil {
|
||||
a.Log().Error("Error saving system variable", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var emailFunc func(email, locale, siteURL string) error
|
||||
|
||||
daysToExpiration := subscription.DaysToExpiration()
|
||||
|
||||
// Only send the email if within the period and it's not already been sent
|
||||
// This allows the email to send on day 59 if for whatever reason it was unable to on day 60
|
||||
if daysToExpiration <= 60 && daysToExpiration > 30 && prevSentEmail != 60 && !(prevSentEmail < 60) {
|
||||
emailFunc = a.Srv().EmailService.SendCloudRenewalEmail60
|
||||
prevSentEmail = 60
|
||||
} else if daysToExpiration <= 30 && daysToExpiration > 7 && prevSentEmail != 30 && !(prevSentEmail < 30) {
|
||||
emailFunc = a.Srv().EmailService.SendCloudRenewalEmail30
|
||||
prevSentEmail = 30
|
||||
} else if daysToExpiration <= 7 && daysToExpiration >= 0 && prevSentEmail != 7 {
|
||||
emailFunc = a.Srv().EmailService.SendCloudRenewalEmail7
|
||||
prevSentEmail = 7
|
||||
}
|
||||
|
||||
if emailFunc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sysAdmins, aErr := a.getAllSystemAdmins()
|
||||
if aErr != nil {
|
||||
a.Log().Error("Error getting sys admins", mlog.Err(aErr))
|
||||
return
|
||||
}
|
||||
|
||||
numFailed := 0
|
||||
for _, admin := range sysAdmins {
|
||||
err = emailFunc(admin.Email, admin.Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error("Error sending renewal email", mlog.Err(err))
|
||||
numFailed += 1
|
||||
}
|
||||
}
|
||||
|
||||
if numFailed == len(sysAdmins) {
|
||||
// If all emails failed, we don't want to update the system variable
|
||||
return
|
||||
}
|
||||
|
||||
updatedSysVar := &model.System{
|
||||
Name: model.CloudRenewalEmail,
|
||||
Value: strconv.FormatInt(prevSentEmail, 10),
|
||||
}
|
||||
|
||||
err = a.Srv().Store().System().SaveOrUpdate(updatedSysVar)
|
||||
if err != nil {
|
||||
a.Log().Error("Error saving system variable", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,44 +239,6 @@ func (es *Service) SendWelcomeEmail(userID string, email string, verified bool,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendCloudUpgradeConfirmationEmail(userEmail, name, date, locale, siteURL, workspaceName string, isYearly bool, embeddedFiles map[string]io.Reader) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
subject := T("api.templates.cloud_upgrade_confirmation.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["Title"] = T("api.templates.cloud_upgrade_confirmation.title")
|
||||
data.Props["SubTitle"] = T("api.templates.cloud_upgrade_confirmation_monthly.subtitle", map[string]any{"WorkspaceName": workspaceName, "Date": date})
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["ButtonURL"] = siteURL
|
||||
data.Props["Button"] = T("api.templates.cloud_welcome_email.button")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
|
||||
if isYearly {
|
||||
data.Props["SubTitle"] = T("api.templates.cloud_upgrade_confirmation_yearly.subtitle", map[string]any{"WorkspaceName": workspaceName})
|
||||
data.Props["ButtonURL"] = siteURL + "/admin_console/billing/billing_history"
|
||||
data.Props["Button"] = T("api.templates.cloud_welcome_email.yearly_plan_button")
|
||||
}
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_upgrade_confirmation", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isYearly {
|
||||
if err := es.SendMailWithEmbeddedFilesAndCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, embeddedFiles, "CloudUpgradeConfirmationEmail"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := es.sendEmailWithCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, "CloudUpgradeConfirmationEmail"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCloudWelcomeEmail sends the cloud version of the welcome email
|
||||
func (es *Service) SendCloudWelcomeEmail(userEmail, locale, teamInviteID, workSpaceName, dns, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
@@ -972,378 +934,6 @@ func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, ct
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendPaymentFailedEmail(email string, locale string, failedPayment *model.FailedPayment, planName, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.payment_failed.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.payment_failed.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.payment_failed.info1", map[string]any{"CardBrand": failedPayment.CardBrand, "LastFour": failedPayment.LastFour})
|
||||
data.Props["SubTitle2"] = T("api.templates.payment_failed.info2")
|
||||
data.Props["FailedReason"] = failedPayment.FailureMessage
|
||||
data.Props["SubTitle3"] = T("api.templates.payment_failed.info3", map[string]any{"Plan": planName})
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_45.button")
|
||||
data.Props["IncludeSecondaryActionButton"] = false
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("payment_failed_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "PaymentFailed"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendNoCardPaymentFailedEmail(email string, locale string, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.payment_failed_no_card.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.payment_failed_no_card.title")
|
||||
data.Props["Info1"] = T("api.templates.payment_failed_no_card.info1")
|
||||
data.Props["Info3"] = T("api.templates.payment_failed_no_card.info3")
|
||||
data.Props["Button"] = T("api.templates.payment_failed_no_card.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("payment_failed_no_card_body", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "NoCardPaymentFailed"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendDelinquencyEmail7(email, locale, siteURL, planName string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.payment_failed.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_7.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_7.subtitle1")
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_7.subtitle2", map[string]any{"Plan": planName})
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_7.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_7_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency7"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (es *Service) SendDelinquencyEmail14(email, locale, siteURL, planName string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_14.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_14.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_14.subtitle1")
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_14.subtitle2")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_14.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_14_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency14"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (es *Service) SendDelinquencyEmail30(email, locale, siteURL, planName string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_30.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_30.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_30.subtitle1", map[string]any{"Plan": planName})
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_30.subtitle2")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_30.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["BulletListItems"] = []string{T("api.templates.delinquency_30.bullet.message_history"), T("api.templates.delinquency_30.bullet.files")}
|
||||
data.Props["LimitsDocs"] = T("api.templates.delinquency_30.limits_documentation")
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_30_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency30"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendDelinquencyEmail45(email, locale, siteURL, planName, delinquencyDate string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_45.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_45.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_45.subtitle1", map[string]any{"DelinquencyDate": delinquencyDate})
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_45.subtitle2")
|
||||
data.Props["SubTitle3"] = T("api.templates.delinquency_45.subtitle3")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_45.button")
|
||||
data.Props["IncludeSecondaryActionButton"] = false
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_45_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency45"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendDelinquencyEmail60(email, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_60.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_60.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_60.subtitle1")
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_60.subtitle2")
|
||||
data.Props["SubTitle3"] = T("api.templates.delinquency_60.subtitle3")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_60.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["IncludeSecondaryActionButton"] = true
|
||||
data.Props["SecondaryActionButtonText"] = T("api.templates.delinquency_60.downgrade_to_free")
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
// 45 day template is the same as the 60 day one so its reused
|
||||
body, err := es.templatesContainer.RenderToString("cloud_45_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency60"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendDelinquencyEmail75(email, locale, siteURL, planName, delinquencyDate string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_75.subject", map[string]any{"Plan": planName})
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_75.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_75.subtitle1", map[string]any{"DelinquencyDate": delinquencyDate})
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_75.subtitle2", map[string]any{"Plan": planName})
|
||||
data.Props["SubTitle3"] = T("api.templates.delinquency_75.subtitle3")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_75.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["IncludeSecondaryActionButton"] = true
|
||||
data.Props["SecondaryActionButtonText"] = T("api.templates.delinquency_75.downgrade_to_free")
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
// 45 day template is the same as the 75 day one so its reused
|
||||
body, err := es.templatesContainer.RenderToString("cloud_45_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency75"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendDelinquencyEmail90(email, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.delinquency_90.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.delinquency_90.title")
|
||||
data.Props["SubTitle1"] = T("api.templates.delinquency_90.subtitle1", map[string]any{"SiteURL": siteURL})
|
||||
data.Props["SubTitle2"] = T("api.templates.delinquency_90.subtitle2")
|
||||
data.Props["SubTitle3"] = T("api.templates.delinquency_90.subtitle3")
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["Button"] = T("api.templates.delinquency_90.button")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["IncludeSecondaryActionButton"] = true
|
||||
data.Props["SecondaryActionButtonText"] = T("api.templates.delinquency_90.secondary_action_button")
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_90_day_arrears", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency90"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendCloudRenewalEmail60(email, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.cloud_renewal_60.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.cloud_renewal_60.title")
|
||||
data.Props["SubTitle"] = T("api.templates.cloud_renewal.subtitle")
|
||||
// TODO: use the open delinquency modal action
|
||||
data.Props["ButtonURL"] = siteURL + "/admin_console/billing/subscription"
|
||||
data.Props["Button"] = T("api.templates.cloud_renewal.button")
|
||||
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["Image"] = "payment_processing.png"
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_renewal_notification", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body, "CloudRenewal60"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendCloudRenewalEmail30(email, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.cloud_renewal_30.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.cloud_renewal_30.title")
|
||||
data.Props["SubTitle"] = T("api.templates.cloud_renewal.subtitle")
|
||||
// TODO: use the open delinquency modal action
|
||||
data.Props["ButtonURL"] = siteURL + "/admin_console/billing/subscription"
|
||||
data.Props["Button"] = T("api.templates.cloud_renewal.button")
|
||||
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["Image"] = "payment_processing.png"
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_renewal_notification", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body, "CloudRenewal30"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendCloudRenewalEmail7(email, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.cloud_renewal_7.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.cloud_renewal_7.title")
|
||||
data.Props["SubTitle"] = T("api.templates.cloud_renewal.subtitle")
|
||||
// TODO: use the open delinquency modal action
|
||||
data.Props["ButtonURL"] = siteURL + "/admin_console/billing/subscription"
|
||||
data.Props["Button"] = T("api.templates.cloud_renewal.button")
|
||||
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["Image"] = "purchase_alert.png"
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_renewal_notification", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body, "CloudRenewal7"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendRemoveExpiredLicenseEmail formats an email and uses the email service to send the email to user with link pointing to CWS
|
||||
// to renew the user license
|
||||
func (es *Service) SendRemoveExpiredLicenseEmail(ctaText, ctaLink, email, locale, siteURL string) error {
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -251,83 +248,6 @@ func TestSendInviteEmails(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendCloudUpgradedEmail(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th.ConfigureInbucketMail()
|
||||
|
||||
emailTo := "testclouduser@example.com"
|
||||
emailToUsername := strings.Split(emailTo, "@")[0]
|
||||
|
||||
t.Run("SendCloudMonthlyUpgradedEmail", func(t *testing.T) {
|
||||
verifyMailbox := func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
var resultsMailbox mail.JSONMessageHeaderInbucket
|
||||
err2 := mail.RetryInbucket(5, func() error {
|
||||
var err error
|
||||
resultsMailbox, err = mail.GetMailBox(emailTo)
|
||||
return err
|
||||
})
|
||||
if err2 != nil {
|
||||
t.Skipf("No email was received, maybe due load on the server: %v", err2)
|
||||
}
|
||||
|
||||
require.Len(t, resultsMailbox, 1)
|
||||
require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient")
|
||||
resultsEmail, err := mail.GetMessageFromMailbox(emailTo, resultsMailbox[0].ID)
|
||||
require.NoError(t, err, "Could not get message from mailbox")
|
||||
require.Contains(t, resultsEmail.Body.Text, "You are now upgraded!", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Contains(t, resultsEmail.Body.Text, "SomeName workspace has now been upgraded", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Contains(t, resultsEmail.Body.Text, "You'll be billed from", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Contains(t, resultsEmail.Body.Text, "Open Mattermost", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Len(t, resultsEmail.Attachments, 0)
|
||||
}
|
||||
mail.DeleteMailBox(emailTo)
|
||||
|
||||
// Send Update to Monthly Plan email
|
||||
err := th.service.SendCloudUpgradeConfirmationEmail(emailTo, emailToUsername, "June 23, 2200", th.BasicUser.Locale, "https://example.com", "SomeName", false, make(map[string]io.Reader))
|
||||
require.NoError(t, err)
|
||||
|
||||
verifyMailbox(t)
|
||||
})
|
||||
|
||||
t.Run("SendCloudYearlyUpgradedEmail", func(t *testing.T) {
|
||||
verifyMailbox := func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
var resultsMailbox mail.JSONMessageHeaderInbucket
|
||||
err2 := mail.RetryInbucket(5, func() error {
|
||||
var err error
|
||||
resultsMailbox, err = mail.GetMailBox(emailTo)
|
||||
return err
|
||||
})
|
||||
if err2 != nil {
|
||||
t.Skipf("No email was received, maybe due load on the server: %v", err2)
|
||||
}
|
||||
|
||||
require.Len(t, resultsMailbox, 1)
|
||||
require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient")
|
||||
resultsEmail, err := mail.GetMessageFromMailbox(emailTo, resultsMailbox[0].ID)
|
||||
require.NoError(t, err, "Could not get message from mailbox")
|
||||
require.Contains(t, resultsEmail.Body.Text, "You are now upgraded!", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Contains(t, resultsEmail.Body.Text, "SomeName workspace has now been upgraded", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Contains(t, resultsEmail.Body.Text, "View your invoice", "Wrong received message %s", resultsEmail.Body.Text)
|
||||
require.Len(t, resultsEmail.Attachments, 1)
|
||||
}
|
||||
mail.DeleteMailBox(emailTo)
|
||||
|
||||
// Send Update to Monthly Plan email
|
||||
var embeddedFiles = map[string]io.Reader{
|
||||
"filename": bytes.NewReader([]byte("Test")),
|
||||
}
|
||||
err := th.service.SendCloudUpgradeConfirmationEmail(emailTo, emailToUsername, "June 23, 2200", th.BasicUser.Locale, "https://example.com", "SomeName", true, embeddedFiles)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifyMailbox(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendCloudWelcomeEmail(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -182,78 +182,6 @@ func (_m *ServiceInterface) SendChangeUsernameEmail(newUsername string, _a1 stri
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendCloudRenewalEmail30 provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendCloudRenewalEmail30(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendCloudRenewalEmail30")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendCloudRenewalEmail60 provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendCloudRenewalEmail60(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendCloudRenewalEmail60")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendCloudRenewalEmail7 provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendCloudRenewalEmail7(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendCloudRenewalEmail7")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendCloudUpgradeConfirmationEmail provides a mock function with given fields: userEmail, name, trialEndDate, locale, siteURL, workspaceName, isYearly, embeddedFiles
|
||||
func (_m *ServiceInterface) SendCloudUpgradeConfirmationEmail(userEmail string, name string, trialEndDate string, locale string, siteURL string, workspaceName string, isYearly bool, embeddedFiles map[string]io.Reader) error {
|
||||
ret := _m.Called(userEmail, name, trialEndDate, locale, siteURL, workspaceName, isYearly, embeddedFiles)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendCloudUpgradeConfirmationEmail")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string, string, bool, map[string]io.Reader) error); ok {
|
||||
r0 = rf(userEmail, name, trialEndDate, locale, siteURL, workspaceName, isYearly, embeddedFiles)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendCloudWelcomeEmail provides a mock function with given fields: userEmail, locale, teamInviteID, workSpaceName, dns, siteURL
|
||||
func (_m *ServiceInterface) SendCloudWelcomeEmail(userEmail string, locale string, teamInviteID string, workSpaceName string, dns string, siteURL string) error {
|
||||
ret := _m.Called(userEmail, locale, teamInviteID, workSpaceName, dns, siteURL)
|
||||
@@ -290,132 +218,6 @@ func (_m *ServiceInterface) SendDeactivateAccountEmail(_a0 string, locale string
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail14 provides a mock function with given fields: _a0, locale, siteURL, planName
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail14(_a0 string, locale string, siteURL string, planName string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL, planName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail14")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL, planName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail30 provides a mock function with given fields: _a0, locale, siteURL, planName
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail30(_a0 string, locale string, siteURL string, planName string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL, planName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail30")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL, planName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail45 provides a mock function with given fields: _a0, locale, siteURL, planName, delinquencyDate
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail45(_a0 string, locale string, siteURL string, planName string, delinquencyDate string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL, planName, delinquencyDate)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail45")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL, planName, delinquencyDate)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail60 provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail60(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail60")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail7 provides a mock function with given fields: _a0, locale, siteURL, planName
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail7(_a0 string, locale string, siteURL string, planName string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL, planName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail7")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL, planName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail75 provides a mock function with given fields: _a0, locale, siteURL, planName, delinquencyDate
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail75(_a0 string, locale string, siteURL string, planName string, delinquencyDate string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL, planName, delinquencyDate)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail75")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL, planName, delinquencyDate)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendDelinquencyEmail90 provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendDelinquencyEmail90(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendDelinquencyEmail90")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendEmailChangeEmail provides a mock function with given fields: oldEmail, newEmail, locale, siteURL
|
||||
func (_m *ServiceInterface) SendEmailChangeEmail(oldEmail string, newEmail string, locale string, siteURL string) error {
|
||||
ret := _m.Called(oldEmail, newEmail, locale, siteURL)
|
||||
@@ -590,24 +392,6 @@ func (_m *ServiceInterface) SendMfaChangeEmail(_a0 string, activated bool, local
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendNoCardPaymentFailedEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendNoCardPaymentFailedEmail(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendNoCardPaymentFailedEmail")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendNotificationMail provides a mock function with given fields: to, subject, htmlBody
|
||||
func (_m *ServiceInterface) SendNotificationMail(to string, subject string, htmlBody string) error {
|
||||
ret := _m.Called(to, subject, htmlBody)
|
||||
@@ -672,34 +456,6 @@ func (_m *ServiceInterface) SendPasswordResetEmail(_a0 string, token *model.Toke
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendPaymentFailedEmail provides a mock function with given fields: _a0, locale, failedPayment, planName, siteURL
|
||||
func (_m *ServiceInterface) SendPaymentFailedEmail(_a0 string, locale string, failedPayment *model.FailedPayment, planName string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, failedPayment, planName, siteURL)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SendPaymentFailedEmail")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, *model.FailedPayment, string, string) (bool, error)); ok {
|
||||
return rf(_a0, locale, failedPayment, planName, siteURL)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, *model.FailedPayment, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, failedPayment, planName, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, *model.FailedPayment, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, failedPayment, planName, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendRemoveExpiredLicenseEmail provides a mock function with given fields: ctaText, ctaLink, _a2, locale, siteURL
|
||||
func (_m *ServiceInterface) SendRemoveExpiredLicenseEmail(ctaText string, ctaLink string, _a2 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(ctaText, ctaLink, _a2, locale, siteURL)
|
||||
|
||||
@@ -134,7 +134,6 @@ type ServiceInterface interface {
|
||||
SendVerifyEmail(userEmail, locale, siteURL, token, redirect string) error
|
||||
SendSignInChangeEmail(email, method, locale, siteURL string) error
|
||||
SendWelcomeEmail(userID string, email string, verified bool, disableWelcomeEmail bool, locale, siteURL, redirect string) error
|
||||
SendCloudUpgradeConfirmationEmail(userEmail, name, trialEndDate, locale, siteURL, workspaceName string, isYearly bool, embeddedFiles map[string]io.Reader) error
|
||||
SendCloudWelcomeEmail(userEmail, locale, teamInviteID, workSpaceName, dns, siteURL string) error
|
||||
SendPasswordChangeEmail(email, method, locale, siteURL string) error
|
||||
SendUserAccessTokenAddedEmail(email, locale, siteURL string) error
|
||||
@@ -147,19 +146,6 @@ type ServiceInterface interface {
|
||||
SendNotificationMail(to, subject, htmlBody string) error
|
||||
SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string, category string) error
|
||||
SendLicenseUpForRenewalEmail(email, name, locale, siteURL, ctaTitle, ctaLink, ctaText string, daysToExpiration int) error
|
||||
SendPaymentFailedEmail(email string, locale string, failedPayment *model.FailedPayment, planName, siteURL string) (bool, error)
|
||||
// Cloud delinquency email sequence
|
||||
SendDelinquencyEmail7(email, locale, siteURL, planName string) error
|
||||
SendDelinquencyEmail14(email, locale, siteURL, planName string) error
|
||||
SendDelinquencyEmail30(email, locale, siteURL, planName string) error
|
||||
SendDelinquencyEmail45(email, locale, siteURL, planName, delinquencyDate string) error
|
||||
SendDelinquencyEmail60(email, locale, siteURL string) error
|
||||
SendDelinquencyEmail75(email, locale, siteURL, planName, delinquencyDate string) error
|
||||
SendDelinquencyEmail90(email, locale, siteURL string) error
|
||||
SendCloudRenewalEmail60(email, locale, siteURL string) error
|
||||
SendCloudRenewalEmail30(email, locale, siteURL string) error
|
||||
SendCloudRenewalEmail7(email, locale, siteURL string) error
|
||||
SendNoCardPaymentFailedEmail(email string, locale string, siteURL string) error
|
||||
SendRemoveExpiredLicenseEmail(ctaText, ctaLink, email, locale, siteURL string) error
|
||||
AddNotificationEmailToBatch(user *model.User, post *model.Post, team *model.Team) *model.AppError
|
||||
GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string
|
||||
|
||||
@@ -4088,21 +4088,6 @@ func (a *OpenTracingAppLayer) DoPostActionWithCookie(c request.CTX, postID strin
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DoSubscriptionRenewalCheck() {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DoSubscriptionRenewalCheck")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
a.app.DoSubscriptionRenewalCheck()
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DoSystemConsoleRolesCreationMigration() {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DoSystemConsoleRolesCreationMigration")
|
||||
@@ -15956,28 +15941,6 @@ func (a *OpenTracingAppLayer) SendAutoResponseIfNecessary(rctx request.CTX, chan
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendDelinquencyEmail(emailToSend model.DelinquencyEmail) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendDelinquencyEmail")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SendDelinquencyEmail(emailToSend)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendEmailVerification(user *model.User, newEmail string, redirect string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendEmailVerification")
|
||||
@@ -16039,28 +16002,6 @@ func (a *OpenTracingAppLayer) SendIPFiltersChangedEmail(c request.CTX, userID st
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendNoCardPaymentFailedEmail() *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendNoCardPaymentFailedEmail")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SendNoCardPaymentFailedEmail()
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendNotifications(c request.CTX, post *model.Post, team *model.Team, channel *model.Channel, sender *model.User, parentPostList *model.PostList, setOnline bool) ([]string, error) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendNotifications")
|
||||
@@ -16127,28 +16068,6 @@ func (a *OpenTracingAppLayer) SendPasswordReset(email string, siteURL string) (b
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendPaymentFailedEmail")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SendPaymentFailedEmail(failedPayment)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendPersistentNotifications() error {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendPersistentNotifications")
|
||||
@@ -16232,28 +16151,6 @@ func (a *OpenTracingAppLayer) SendTestPushNotification(deviceID string) string {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendUpgradeConfirmationEmail(isYearly bool) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendUpgradeConfirmationEmail")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SendUpgradeConfirmationEmail(isYearly)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId string, destinationPluginId string) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ServeInterPluginRequest")
|
||||
|
||||
@@ -1364,12 +1364,6 @@ func (s *Server) doLicenseExpirationCheck() {
|
||||
return
|
||||
}
|
||||
|
||||
if license.IsCloud() {
|
||||
appInstance := New(ServerConnector(s.Channels()))
|
||||
appInstance.DoSubscriptionRenewalCheck()
|
||||
return
|
||||
}
|
||||
|
||||
users, err := s.Store().User().GetSystemAdminProfiles()
|
||||
if err != nil {
|
||||
mlog.Error("Failed to get system admins for license expired message from Mattermost.")
|
||||
|
||||
Ссылка в новой задаче
Block a user