Compare commits
36 Commits
remote_db
...
retention-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c93f9adb02 | ||
|
|
aa3d852280 | ||
|
|
6b92bace0c | ||
|
|
70c857641c | ||
|
|
d5b137ea82 | ||
|
|
8b02edb19f | ||
|
|
daf1d632e7 | ||
|
|
0e435b6bcf | ||
|
|
a2643f2cd0 | ||
|
|
abe0ef0a03 | ||
|
|
0f24ba01f5 | ||
|
|
5d5e418676 | ||
|
|
ccfd7abf83 | ||
|
|
f609cef79e | ||
|
|
ab5c02d792 | ||
|
|
20a26eac3b | ||
|
|
e7b5815039 | ||
|
|
8e313840fd | ||
|
|
df8ca468db | ||
|
|
4623e05eb5 | ||
|
|
edf6baef6e | ||
|
|
611d2684c4 | ||
|
|
8bab350624 | ||
|
|
6474ff25a7 | ||
|
|
7e5a2c4377 | ||
|
|
f27a2c68ec | ||
|
|
709a3756f0 | ||
|
|
e1a0c1c847 | ||
|
|
763217c6df | ||
|
|
66d9c100ca | ||
|
|
ba2ae29e3a | ||
|
|
a72694116a | ||
|
|
3fc24d139b | ||
|
|
50ab944579 | ||
|
|
2b28661f89 | ||
|
|
cfa02f631c |
@@ -5,7 +5,6 @@ import (
|
||||
"database/sql"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/collections"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -15,6 +14,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/collections"
|
||||
|
||||
"github.com/ente-io/museum/ente/base"
|
||||
"github.com/ente-io/museum/pkg/controller/emergency"
|
||||
"github.com/ente-io/museum/pkg/controller/file_copy"
|
||||
@@ -983,9 +984,8 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
_ = castDb.DeleteUnclaimedCodes(context.Background(), timeUtil.MicrosecondsBeforeMinutes(60))
|
||||
dataCleanupCtrl.DeleteDataCron()
|
||||
})
|
||||
|
||||
schedule(c, "@every 24h", func() {
|
||||
emailNotificationCtrl.SendStorageLimitExceededMails()
|
||||
emailNotificationCtrl.SendStorageAlerts()
|
||||
})
|
||||
|
||||
schedule(c, "@every 1m", func() {
|
||||
@@ -996,6 +996,10 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
pushController.ClearExpiredTokens()
|
||||
})
|
||||
|
||||
// schedule(c, "@every 3s", func() {
|
||||
// emailNotificationCtrl.SendFamilyNudgeEmail()
|
||||
// })
|
||||
|
||||
scheduleAndRun(c, "@every 60m", func() {
|
||||
emergencyCtrl.SendRecoveryReminder()
|
||||
kexCtrl.DeleteOldKeys()
|
||||
|
||||
144
server/mail-templates/90_percent_storage_consumed.html
Normal file
144
server/mail-templates/90_percent_storage_consumed.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1,
|
||||
minimum-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f0f1f3;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 27px;
|
||||
margin: 0;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4f4;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-color: #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
max-width: 525px;
|
||||
margin: 0 auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0055d4;
|
||||
border-radius: 3px;
|
||||
text-decoration: none !important;
|
||||
color: #fff !important;
|
||||
font-weight: bold;
|
||||
padding: 10px 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #888;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0055d4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.wrap {
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
padding: 4px !important;
|
||||
width: 24px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="gutter" style="padding: 4px"> </div>
|
||||
<div class="wrap" style=" background-color: rgb(255, 255, 255); padding: 2px
|
||||
30px 30px 30px; max-width: 525px; margin: 0 auto; border-radius: 5px;
|
||||
font-size: 16px; ">
|
||||
<p>
|
||||
<p>
|
||||
You have used 90% of your available storage on Ente Photos. All your photos on Ente will continue to remain safe and accessible. However, new photos will not be backed up after you get to 100%.
|
||||
</p>
|
||||
<p>
|
||||
Please upgrade your plan so that all the new photos you take can be backed up. You can also use our referral program to get more storage at no additional cost.
|
||||
</p>
|
||||
<p>
|
||||
For any help, please reach out to support@ente.io or reply to this email.
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<div class="footer" style="text-align: center; font-size: 12px; color:
|
||||
rgb(136, 136, 136)" >
|
||||
<div>
|
||||
<a href="https://ente.io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/ente-green.png" style="width: 100px;
|
||||
padding: 24px" title="Ente" alt="Ente" /></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://fosstodon.org/@ente" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/mastodon-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="Mastodon"
|
||||
alt="Mastodon" /></a>
|
||||
<a href="https://twitter.com/enteio" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/twitter-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="Twitter" alt="Twitter" /></a>
|
||||
<a href="https://discord.ente.io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/discord-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="Discord" alt="Discord" /></a>
|
||||
<a href="https://github.com/ente-io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/github-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="GitHub" alt="GitHub" /></a>
|
||||
</div>
|
||||
<p>
|
||||
Ente Technologies, Inc.
|
||||
<br /> 1111B S Governors Ave 6032 Dover, DE 19904
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
139
server/mail-templates/nudge_for_family.html
Normal file
139
server/mail-templates/nudge_for_family.html
Normal file
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1,
|
||||
minimum-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f0f1f3;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 27px;
|
||||
margin: 0;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4f4;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-color: #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
max-width: 525px;
|
||||
margin: 0 auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0055d4;
|
||||
border-radius: 3px;
|
||||
text-decoration: none !important;
|
||||
color: #fff !important;
|
||||
font-weight: bold;
|
||||
padding: 10px 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #888;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0055d4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.wrap {
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
padding: 4px !important;
|
||||
width: 24px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="gutter" style="padding: 4px"> </div>
|
||||
<div class="wrap" style=" background-color: rgb(255, 255, 255); padding: 2px
|
||||
30px 30px 30px; max-width: 525px; margin: 0 auto; border-radius: 5px;
|
||||
font-size: 16px; " >
|
||||
<p>
|
||||
Your subscription includes our family plan where you can share your subscription with upto 5 additional members at no extra cost. <br>
|
||||
<br>
|
||||
You can add family members to your account by going to Settings -> Manage Subscriptions -> Manage Family. For more information, you can also visit
|
||||
<a href="https://help.ente.io/photos/features/family-plans">our help page</a> or reach out to support@ente.io
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<div class="footer" style="text-align: center; font-size: 12px; color:
|
||||
rgb(136, 136, 136)" >
|
||||
<div>
|
||||
<a href="https://ente.io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/ente-green.png" style="width: 100px;
|
||||
padding: 24px" title="Ente" alt="Ente" /></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://fosstodon.org/@ente" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/mastodon-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="Mastodon"
|
||||
alt="Mastodon" /></a>
|
||||
<a href="https://twitter.com/enteio" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/twitter-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="Twitter" alt="Twitter" /></a>
|
||||
<a href="https://discord.ente.io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/discord-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="Discord" alt="Discord" /></a>
|
||||
<a href="https://github.com/ente-io" target="_blank" ><img
|
||||
src="https://email-assets.ente.io/github-icon.png" class="footer-icons"
|
||||
style="width: 24px; padding: 4px" title="GitHub" alt="GitHub" /></a>
|
||||
</div>
|
||||
<p>
|
||||
Ente Technologies, Inc.
|
||||
<br /> 1111B S Governors Ave 6032 Dover, DE 19904
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/commonbilling"
|
||||
"strconv"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/commonbilling"
|
||||
|
||||
"github.com/ente-io/museum/pkg/repo/storagebonus"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/discord"
|
||||
@@ -292,6 +293,7 @@ func (c *BillingController) VerifySubscription(
|
||||
if err != nil {
|
||||
return ente.Subscription{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
log.Info("Replaced subscription")
|
||||
newSubscription.ID = currentSubscription.ID
|
||||
if paymentProvider == ente.PlayStore &&
|
||||
|
||||
@@ -2,15 +2,15 @@ package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/avct/uasurfer"
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/controller/lock"
|
||||
"github.com/ente-io/museum/pkg/repo"
|
||||
"github.com/ente-io/museum/pkg/utils/email"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
StorageLimitExceededMailLock = "storage_limit_exceeded_mail_lock"
|
||||
StorageLimitExceededTemplateID = "storage_limit_exceeded"
|
||||
StorageLimitExceededTemplate = "storage_limit_exceeded.html"
|
||||
StorageLimitExceededSubject = "[Alert] You have exceeded your storage limit"
|
||||
|
||||
FilesCollectedTemplate = "files_collected.html"
|
||||
FilesCollectedTemplateID = "files_collected"
|
||||
@@ -33,12 +34,19 @@ const (
|
||||
SubscriptionCancelledTemplate = "subscription_cancelled.html"
|
||||
FilesCollectedMuteDurationInMinutes = 10
|
||||
|
||||
StorageLimitExceededSubject = "[Alert] You have exceeded your storage limit"
|
||||
ReferralSuccessfulTemplate = "successful_referral.html"
|
||||
ReferralSuccessfulSubject = "You've earned 10 GB on Ente! 🎁"
|
||||
ReferralSuccessfulTemplate = "successful_referral.html"
|
||||
ReferralSuccessfulSubject = "You've earned 10 GB on Ente! 🎁"
|
||||
|
||||
StorageLimitExceedingID = "90_percent_consumed"
|
||||
StorageLimitExceedingTemplate = "90_percent_storage_consumed.html"
|
||||
StorageLimitExceedingSubject = "Your Ente storage is at 90% capacity"
|
||||
|
||||
LoginSuccessSubject = "New login to your Ente account"
|
||||
LoginSuccessTemplate = "on_login.html"
|
||||
|
||||
FamilyNudgeEmailTemplate = "nudge_for_family.html"
|
||||
FamilyNudgeSubject = "Share your Ente Subscription with your Family!"
|
||||
FamilyNudgeTemplateID = "family_nudge"
|
||||
)
|
||||
|
||||
type EmailNotificationController struct {
|
||||
@@ -60,7 +68,7 @@ func (c *EmailNotificationController) OnFirstFileUpload(userID int64, userAgent
|
||||
}
|
||||
err = email.SendTemplatedEmail([]string{user.Email}, "team@ente.io", "team@ente.io", FirstUploadEmailSubject, template, nil, nil)
|
||||
if err != nil {
|
||||
log.Error("Error sending first upload email ", err)
|
||||
log.Error("Error sending first upload email", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +166,7 @@ func (c *EmailNotificationController) OnSubscriptionCancelled(userID int64) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EmailNotificationController) SendStorageLimitExceededMails() {
|
||||
func (c *EmailNotificationController) SendStorageAlerts() {
|
||||
if c.isSendingStorageLimitExceededMails {
|
||||
log.Info("Skipping sending storage limit exceeded mails as another instance is still running")
|
||||
return
|
||||
@@ -171,33 +179,92 @@ func (c *EmailNotificationController) SendStorageLimitExceededMails() {
|
||||
return
|
||||
}
|
||||
defer c.LockController.ReleaseLock(StorageLimitExceededMailLock)
|
||||
users, err := c.UserRepo.GetUsersWithIndividualPlanWhoHaveExceededStorageQuota()
|
||||
if err != nil {
|
||||
log.Error("Error while fetching user list", err)
|
||||
return
|
||||
|
||||
// storageAlertGroups struct gets the list of both the users who have consumed
|
||||
// 90% storage and 100% of their subcriptions. Then, it ranges through
|
||||
// the slices of the both the users and inside this for loop, users from
|
||||
// both the slices are separately looped. This is done to avoid
|
||||
// duplication of a lot of code if both the users were ranged inside a loop
|
||||
// separately.
|
||||
storageAlertGroups := []struct {
|
||||
getListofSubscribers func() ([]ente.User, error)
|
||||
template string
|
||||
subject string
|
||||
notifID string
|
||||
}{
|
||||
{
|
||||
getListofSubscribers: func() ([]ente.User, error) {
|
||||
return c.UserRepo.GetUsersWithExceedingStorage(90)
|
||||
},
|
||||
template: StorageLimitExceedingTemplate,
|
||||
subject: StorageLimitExceedingSubject,
|
||||
notifID: StorageLimitExceedingID,
|
||||
},
|
||||
{
|
||||
getListofSubscribers: func() ([]ente.User, error) {
|
||||
return c.UserRepo.GetUsersWithExceedingStorage(100)
|
||||
},
|
||||
template: StorageLimitExceededTemplate,
|
||||
subject: StorageLimitExceededSubject,
|
||||
notifID: StorageLimitExceededTemplateID,
|
||||
},
|
||||
}
|
||||
for _, u := range users {
|
||||
lastNotificationTime, err := c.NotificationHistoryRepo.GetLastNotificationTime(u.ID, StorageLimitExceededTemplateID)
|
||||
logger := log.WithFields(log.Fields{
|
||||
"user_id": u.ID,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Could not fetch last notification time", err)
|
||||
for _, alertGroup := range storageAlertGroups {
|
||||
users, usersErr := alertGroup.getListofSubscribers()
|
||||
if usersErr != nil {
|
||||
log.WithError(usersErr).Error("Failed to get list of users")
|
||||
continue
|
||||
}
|
||||
if lastNotificationTime > 0 {
|
||||
continue
|
||||
for _, u := range users {
|
||||
lastNotificationTime, err := c.NotificationHistoryRepo.GetLastNotificationTime(u.ID, alertGroup.notifID)
|
||||
logger := log.WithFields(log.Fields{
|
||||
"user_id": u.ID,
|
||||
"group_id": alertGroup.notifID,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Could not fetch last notification time", err)
|
||||
continue
|
||||
}
|
||||
if lastNotificationTime == 0 {
|
||||
logger.Info("Alerting about storage limit exceeded")
|
||||
err = email.SendTemplatedEmail([]string{u.Email}, "team@ente.io", "team@ente.io", alertGroup.subject, alertGroup.template, nil, nil)
|
||||
if err != nil {
|
||||
logger.Info("Error notifying", err)
|
||||
continue
|
||||
}
|
||||
c.NotificationHistoryRepo.SetLastNotificationTimeToNow(u.ID, alertGroup.notifID)
|
||||
}
|
||||
}
|
||||
logger.Info("Alerting about storage limit exceeded")
|
||||
err = email.SendTemplatedEmail([]string{u.Email}, "team@ente.io", "team@ente.io", StorageLimitExceededSubject, StorageLimitExceededTemplate, nil, nil)
|
||||
if err != nil {
|
||||
logger.Info("Error notifying", err)
|
||||
continue
|
||||
}
|
||||
c.NotificationHistoryRepo.SetLastNotificationTimeToNow(u.ID, StorageLimitExceededTemplateID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EmailNotificationController) setStorageLimitExceededMailerJobStatus(isSending bool) {
|
||||
c.isSendingStorageLimitExceededMails = isSending
|
||||
}
|
||||
|
||||
func (c *EmailNotificationController) SendFamilyNudgeEmail() error {
|
||||
subscribedUsers, subUsersErr := c.UserRepo.GetSubscribedUsersWithoutFamily(30)
|
||||
if subUsersErr != nil {
|
||||
return stacktrace.Propagate(subUsersErr, "Failed to get subscribers")
|
||||
}
|
||||
log.Infof("Found %d subscribers to nudge for family", len(subscribedUsers))
|
||||
for _, user := range subscribedUsers {
|
||||
lastNudgeSent, lastNudgeErr := c.NotificationHistoryRepo.GetLastNotificationTime(user.ID, FamilyNudgeTemplateID)
|
||||
if lastNudgeErr != nil {
|
||||
log.WithError(lastNudgeErr).Error("Failed to set Notification History")
|
||||
continue
|
||||
}
|
||||
if lastNudgeSent == 0 {
|
||||
err := email.SendTemplatedEmail([]string{user.Email}, "team@ente.io", "team@ente.io", FamilyNudgeSubject, FamilyNudgeEmailTemplate, nil, nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to send family nudge email: ", err)
|
||||
continue
|
||||
}
|
||||
err = c.NotificationHistoryRepo.SetLastNotificationTimeToNow(user.ID, FamilyNudgeTemplateID)
|
||||
if err != nil {
|
||||
log.Error("Failed to set Notification History")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,7 +46,18 @@ type StripeController struct {
|
||||
const BufferPeriodOnPaymentFailureInDays = 7
|
||||
|
||||
// Return a new instance of StripeController
|
||||
func NewStripeController(plans ente.BillingPlansPerAccount, stripeClients ente.StripeClientPerAccount, billingRepo *repo.BillingRepository, fileRepo *repo.FileRepository, userRepo *repo.UserRepository, storageBonusRepo *storagebonus.Repository, discordController *discord.DiscordController, emailNotificationController *emailCtrl.EmailNotificationController, offerController *offer.OfferController, commonBillCtrl *commonbilling.Controller) *StripeController {
|
||||
func NewStripeController(
|
||||
plans ente.BillingPlansPerAccount,
|
||||
stripeClients ente.StripeClientPerAccount,
|
||||
billingRepo *repo.BillingRepository,
|
||||
fileRepo *repo.FileRepository,
|
||||
userRepo *repo.UserRepository,
|
||||
storageBonusRepo *storagebonus.Repository,
|
||||
discordController *discord.DiscordController,
|
||||
emailNotificationController *emailCtrl.EmailNotificationController,
|
||||
offerController *offer.OfferController,
|
||||
commonBillCtrl *commonbilling.Controller,
|
||||
) *StripeController {
|
||||
return &StripeController{
|
||||
StripeClients: stripeClients,
|
||||
BillingRepo: billingRepo,
|
||||
@@ -249,7 +260,7 @@ func (c *StripeController) handleCheckoutSessionCompleted(event stripe.Event, co
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
|
||||
return ente.StripeEventLog{}, stacktrace.Propagate(err, "Failed to change subscription")
|
||||
}
|
||||
return ente.StripeEventLog{UserID: userID, StripeSubscription: stripeSubscription, Event: event}, nil
|
||||
} else {
|
||||
|
||||
@@ -155,7 +155,7 @@ func (repo *BillingRepository) LogStripePush(eventLog ente.StripeEventLog) error
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// LogStripePush logs a subscription modification by an admin
|
||||
// LogAdminTriggeredSubscriptionUpdate logs a subscription modification by an admin
|
||||
func (repo *BillingRepository) LogAdminTriggeredSubscriptionUpdate(r ente.UpdateSubscriptionRequest) error {
|
||||
requestJSON, _ := json.Marshal(r)
|
||||
_, err := repo.DB.Exec(`INSERT INTO subscription_logs(user_id, payment_provider, notification, verification_response) VALUES($1, $2, $3, '{}'::json)`,
|
||||
|
||||
@@ -29,3 +29,13 @@ func (repo *NotificationHistoryRepository) SetLastNotificationTimeToNow(userID i
|
||||
userID, templateID, time.Microseconds())
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// DeleteLastNotificationTime deletes the last notification time for a user and template.
|
||||
// This will be used in the upcoming PR for resetting storage alerts notifications.
|
||||
func (repo *NotificationHistoryRepository) DeleteLastNotificationTime(userID int64, templateID string) error {
|
||||
_, err := repo.DB.Exec(`DELETE FROM notification_history WHERE user_id=$1 AND template_id='$2'`, userID, templateID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
t "time"
|
||||
|
||||
"github.com/ente-io/museum/pkg/repo/passkey"
|
||||
storageBonusRepo "github.com/ente-io/museum/pkg/repo/storagebonus"
|
||||
@@ -128,6 +129,39 @@ func (repo *UserRepository) GetAll(sinceTime int64, tillTime int64) ([]ente.User
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetSubscribedUsersWithoutFamily returns notification candidates who are on paid plan
|
||||
// but not part of any family plan.
|
||||
func (repo *UserRepository) GetSubscribedUsersWithoutFamily(accountAgeInDays int64) ([]ente.User, error) {
|
||||
rows, err := repo.DB.Query(`SELECT u.user_id, u.encrypted_email, u.email_decryption_nonce, u.email_hash, s.created_at
|
||||
FROM subscriptions s
|
||||
INNER JOIN users u ON s.user_id = u.user_id
|
||||
WHERE u.creation_time <= $1 AND u.family_admin_id IS NULL
|
||||
AND s.product_id != 'free'
|
||||
AND u.encrypted_email IS NOT NULL`, time.MicrosecondBeforeDays(int(accountAgeInDays)))
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
defer rows.Close()
|
||||
subscribedUsers := make([]ente.User, 0)
|
||||
for rows.Next() {
|
||||
var user ente.User
|
||||
var encryptedEmail, nonce []byte
|
||||
var createdAt t.Time
|
||||
|
||||
err := rows.Scan(&user.ID, &encryptedEmail, &nonce, &user.Hash, &createdAt)
|
||||
if err != nil {
|
||||
return subscribedUsers, stacktrace.Propagate(err, "user scan failed")
|
||||
}
|
||||
email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, nonce)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
user.Email = email
|
||||
subscribedUsers = append(subscribedUsers, user)
|
||||
}
|
||||
return subscribedUsers, nil
|
||||
}
|
||||
|
||||
// GetUserUsageWithSubData will return current storage usage & basic information about subscription for given list
|
||||
// of users. It's primarily used for fetching storage utilisation for a family/group of users
|
||||
func (repo *UserRepository) GetUserUsageWithSubData(ctx context.Context, userIds []int64) ([]ente.UserUsageWithSubData, error) {
|
||||
@@ -280,17 +314,17 @@ func (repo *UserRepository) GetPublicKey(userID int64) (string, error) {
|
||||
return publicKey, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// GetUsersWithIndividualPlanWhoHaveExceededStorageQuota returns list of users who have consumed their storage quota
|
||||
// and they are not part of any family plan
|
||||
func (repo *UserRepository) GetUsersWithIndividualPlanWhoHaveExceededStorageQuota() ([]ente.User, error) {
|
||||
// GetUsersWithExceedingStorage returns list of users who have consumed 90% or 100% of their total storage quota
|
||||
// depending on the percentageThreshold (90% or 100%) and they are not part of any family plan
|
||||
func (repo *UserRepository) GetUsersWithExceedingStorage(percentageThreshold int64) ([]ente.User, error) {
|
||||
rows, err := repo.DB.Query(`
|
||||
SELECT users.user_id, users.encrypted_email, users.email_decryption_nonce, users.email_hash, usage.storage_consumed, subscriptions.storage
|
||||
FROM users
|
||||
INNER JOIN usage
|
||||
ON users.user_id = usage.user_id
|
||||
INNER JOIN subscriptions
|
||||
ON users.user_id = subscriptions.user_id AND usage.storage_consumed > subscriptions.storage AND users.encrypted_email IS NOT NULL AND users.family_admin_id IS NULL;
|
||||
`)
|
||||
ON users.user_id = subscriptions.user_id AND usage.storage_consumed >= (subscriptions.storage * ($1 / 100.0) AND users.encrypted_email IS NOT NULL AND users.family_admin_id is NULL
|
||||
`, percentageThreshold)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -304,7 +338,7 @@ func (repo *UserRepository) GetUsersWithIndividualPlanWhoHaveExceededStorageQuot
|
||||
var user ente.User
|
||||
var encryptedEmail, nonce []byte
|
||||
var storageConsumed, subStorage int64
|
||||
err := rows.Scan(&user.ID, &encryptedEmail, &nonce, &user.Hash, &storageConsumed, &subStorage)
|
||||
err = rows.Scan(&user.ID, &encryptedEmail, &nonce, &user.Hash, &storageConsumed, &subStorage)
|
||||
if err != nil {
|
||||
return users, stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -312,15 +346,26 @@ func (repo *UserRepository) GetUsersWithIndividualPlanWhoHaveExceededStorageQuot
|
||||
if strings.EqualFold(user.Hash, fmt.Sprintf(DELETED_EMAIL_HASH_FORMAT, &user.ID)) || len(encryptedEmail) == 0 {
|
||||
continue
|
||||
}
|
||||
if refBonusStorage, ok := refBonus[user.ID]; ok {
|
||||
addOnBonusStorage := addOnBonus[user.ID]
|
||||
refBonusStorage := int64(0)
|
||||
addOnBonusStorage := int64(0)
|
||||
|
||||
if bonus, ok := refBonus[user.ID]; ok {
|
||||
refBonusStorage = bonus
|
||||
addOnBonusStorage = addOnBonus[user.ID]
|
||||
// cap usable ref bonus to the subscription storage + addOnBonus
|
||||
if refBonusStorage > (subStorage + addOnBonusStorage) {
|
||||
refBonusStorage = subStorage + addOnBonusStorage
|
||||
}
|
||||
if (storageConsumed) <= (subStorage + refBonusStorage + addOnBonusStorage) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
totalStorage := refBonusStorage + subStorage + addOnBonusStorage
|
||||
usagePercentage := (storageConsumed * 100.0) / totalStorage
|
||||
if percentageThreshold >= 100 && usagePercentage < 100.0 {
|
||||
continue
|
||||
}
|
||||
// when required percentage is below 100, skip email if the usage is below
|
||||
// percentageThreshold or usage above 100% (which means user has used all the storage)
|
||||
if percentageThreshold < 100 && (usagePercentage < percentageThreshold || usagePercentage >= 100.0) {
|
||||
continue
|
||||
}
|
||||
email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, nonce)
|
||||
if err != nil {
|
||||
@@ -328,6 +373,7 @@ func (repo *UserRepository) GetUsersWithIndividualPlanWhoHaveExceededStorageQuot
|
||||
}
|
||||
user.Email = email
|
||||
users = append(users, user)
|
||||
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user