Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions server/app/admin_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ func (a *App) CreateNewAnnouncementHandler(req *http.Request) (interface{}, Resp
for _, user := range users {
subject, body := internal.AdminAnnouncementMailContent(adminAnnouncement.Subject, adminAnnouncement.Body, a.config.Server.Host, user.Name())

err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down Expand Up @@ -585,7 +585,7 @@ func (a *App) SendEmailHandler(req *http.Request) (interface{}, Response) {

subject, body := internal.AdminMailContent(fmt.Sprintf("Hey! 📢 %s", emailUser.Subject), emailUser.Body, a.config.Server.Host, user.Name())

err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down Expand Up @@ -663,7 +663,7 @@ func (a *App) notifyAdmins() {
subject, body := internal.NotifyAdminsMailContent(len(pending), a.config.Server.Host)

for _, admin := range admins {
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, admin.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, admin.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
}
Expand All @@ -680,7 +680,7 @@ func (a *App) notifyAdmins() {
subject, body := internal.NotifyAdminsMailLowBalanceContent(balance, a.config.Server.Host)

for _, admin := range admins {
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, admin.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, admin.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
}
Expand Down
1 change: 1 addition & 0 deletions server/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func (a *App) registerHandlers() {

invoiceRouter.HandleFunc("", WrapFunc(a.ListInvoicesHandler)).Methods("GET", "OPTIONS")
invoiceRouter.HandleFunc("/{id}", WrapFunc(a.GetInvoiceHandler)).Methods("GET", "OPTIONS")
invoiceRouter.HandleFunc("/download/{id}", WrapFunc(a.DownloadInvoiceHandler)).Methods("GET", "OPTIONS")
invoiceRouter.HandleFunc("/pay/{id}", WrapFunc(a.PayInvoiceHandler)).Methods("PUT", "OPTIONS")

notificationRouter.HandleFunc("", WrapFunc(a.ListNotificationsHandler)).Methods("GET", "OPTIONS")
Expand Down
108 changes: 94 additions & 14 deletions server/app/invoice_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -119,6 +121,64 @@ func (a *App) GetInvoiceHandler(req *http.Request) (interface{}, Response) {
}, Ok()
}

// DownloadInvoiceHandler downloads user's invoice by ID
// Example endpoint: Downloads user's invoice by ID
// @Summary Downloads user's invoice by ID
// @Description Downloads user's invoice by ID
// @Tags Invoice
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Invoice ID"
// @Success 200 {object} Response
// @Failure 400 {object} Response
// @Failure 401 {object} Response
// @Failure 404 {object} Response
// @Failure 500 {object} Response
// @Router /invoice/download/{id} [get]
func (a *App) DownloadInvoiceHandler(req *http.Request) (interface{}, Response) {
userID := req.Context().Value(middlewares.UserIDKey("UserID")).(string)

id, err := strconv.Atoi(mux.Vars(req)["id"])
if err != nil {
log.Error().Err(err).Send()
return nil, BadRequest(errors.New("failed to read invoice id"))
}

invoice, err := a.db.GetInvoice(id)
if err == gorm.ErrRecordNotFound {
return nil, NotFound(errors.New("invoice is not found"))
}
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

if userID != invoice.UserID {
return nil, NotFound(errors.New("invoice is not found"))
}

// Get downloads dir
homeDir, err := os.UserHomeDir()
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

downloadsDir := filepath.Join(homeDir, "Downloads")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the full downloads home directory configurable and fallback to this if not defined, this allows the operator of the service to e.g mount external drives or use nfs directories .. etc

pdfPath := filepath.Join(downloadsDir, fmt.Sprintf("invoice-%s-%d.pdf", invoice.UserID, invoice.ID))

err = os.WriteFile(pdfPath, invoice.FileData, 0644)
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

return ResponseMsg{
Message: fmt.Sprintf("Invoice is downloaded successfully at %s", pdfPath),
}, Ok()
}

// PayInvoiceHandler pay user's invoice
// Example endpoint: Pay user's invoice
// @Summary Pay user's invoice
Expand Down Expand Up @@ -213,7 +273,7 @@ func (a *App) monthlyInvoices() {
// Create invoices for all system users
for _, user := range users {
// 1. Create new monthly invoice
if err = a.createInvoice(user.ID.String(), now); err != nil {
if err = a.createInvoice(user, now); err != nil {
log.Error().Err(err).Send()
}

Expand Down Expand Up @@ -275,15 +335,15 @@ func (a *App) monthlyInvoices() {
}
}

func (a *App) createInvoice(userID string, now time.Time) error {
func (a *App) createInvoice(user models.User, now time.Time) error {
monthStart := time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, time.Local)

vms, err := a.db.GetAllSuccessfulVms(userID)
vms, err := a.db.GetAllSuccessfulVms(user.ID.String())
if err != nil && err != gorm.ErrRecordNotFound {
return err
}

k8s, err := a.db.GetAllSuccessfulK8s(userID)
k8s, err := a.db.GetAllSuccessfulK8s(user.ID.String())
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
Expand All @@ -308,6 +368,8 @@ func (a *App) createInvoice(userID string, now time.Time) error {
DeploymentResources: vm.Resources,
DeploymentType: "vm",
DeploymentID: vm.ID,
DeploymentName: vm.Name,
DeploymentCreatedAt: vm.CreatedAt,
HasPublicIP: vm.Public,
PeriodInHours: time.Since(usageStart).Hours(),
Cost: cost,
Expand All @@ -333,6 +395,8 @@ func (a *App) createInvoice(userID string, now time.Time) error {
DeploymentResources: cluster.Master.Resources,
DeploymentType: "k8s",
DeploymentID: cluster.ID,
DeploymentName: cluster.Master.Name,
DeploymentCreatedAt: cluster.CreatedAt,
HasPublicIP: cluster.Master.Public,
PeriodInHours: time.Since(usageStart).Hours(),
Cost: cost,
Expand All @@ -342,11 +406,22 @@ func (a *App) createInvoice(userID string, now time.Time) error {
}

if len(items) > 0 {
if err = a.db.CreateInvoice(&models.Invoice{
UserID: userID,
in := models.Invoice{
UserID: user.ID.String(),
Total: total,
Deployments: items,
}); err != nil {
}

// Creating pdf for invoice
pdfContent, err := internal.CreateInvoicePDF(in, user)
if err != nil {
return err
}

in.FileData = pdfContent

// Creating invoice in db
if err = a.db.CreateInvoice(&in); err != nil {
return err
}
}
Expand Down Expand Up @@ -415,14 +490,14 @@ func (a *App) sendInvoiceReminderToUser(userID, userEmail, userName string, now
}

for _, invoice := range invoices {
oneMonthsAgo := now.AddDate(0, -1, 0)
// oneMonthsAgo := now.AddDate(0, -1, 0)
oneWeekAgo := now.AddDate(0, 0, -7)

// check if the invoice created 1 months ago (not after it) and
// last remainder sent for this invoice was 7 days ago and
// last remainder sent for this invoice was before 7 days ago and
// invoice is not paid
if invoice.CreatedAt.Before(oneMonthsAgo) &&
invoice.LastReminderAt.Before(oneWeekAgo) &&
// invoice.CreatedAt.Before(oneMonthsAgo) &&
if invoice.LastReminderAt.Before(oneWeekAgo) &&
!invoice.Paid {
// overdue date starts after one month since invoice creation
overDueStart := invoice.CreatedAt.AddDate(0, 1, 0)
Expand All @@ -434,20 +509,25 @@ func (a *App) sendInvoiceReminderToUser(userID, userEmail, userName string, now

mailBody := "We hope this message finds you well.\n"
mailBody += fmt.Sprintf("Our records show that there is an outstanding invoice for %v %s associated with your account (%d). ", invoice.Total, currencyName, invoice.ID)
mailBody += fmt.Sprintf("As of today, the payment for this invoice is %d days overdue.", overDueDays)
if overDueDays > 0 {
mailBody += fmt.Sprintf("As of today, the payment for this invoice is %d days overdue.", overDueDays)
}
mailBody += "To avoid any interruptions to your services and the potential deletion of your deployments, "
mailBody += fmt.Sprintf("we kindly ask that you make the payment within the next %d days. If the invoice remains unpaid after this period, ", gracePeriod)
mailBody += "please be advised that the associated deployments will be deleted from our system.\n\n"

mailBody += "You can easily pay your invoice by charging balance, activating voucher or using cards.\n\n"
mailBody += "If you have already made the payment or need any assistance, "
mailBody += "please don't hesitate to reach out to us.\n\n"
mailBody += "We appreciate your prompt attention to this matter and thank you fosr being a valued customer."
mailBody += "We appreciate your prompt attention to this matter and thank you for being a valued customer."

subject := "Unpaid Invoice Notification – Action Required"
subject, body := internal.AdminMailContent(subject, mailBody, a.config.Server.Host, userName)

if err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, userEmail, subject, body); err != nil {
if err = internal.SendMail(
a.config.MailSender.Email, a.config.MailSender.SendGridKey, userEmail, subject, body,
fmt.Sprintf("invoice-%s-%d.pdf", invoice.UserID, invoice.ID), invoice.FileData,
); err != nil {
log.Error().Err(err).Send()
}

Expand Down
2 changes: 2 additions & 0 deletions server/app/payments_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ func (a *App) DeleteCardHandler(req *http.Request) (interface{}, Response) {
return nil, BadRequest(errors.New("you have active deployment and cannot delete the card"))
}

// TODO: deleting vms before the end of the month then deleting all cards case

// Update the default payment method for future payments (if deleted card is the default)
if card.PaymentMethodID == user.StripeDefaultPaymentID {
var newPaymentMethod string
Expand Down
10 changes: 5 additions & 5 deletions server/app/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func (a *App) SignUpHandler(req *http.Request) (interface{}, Response) {
// send verification code if user is not verified or not exist
code := internal.GenerateRandomCode()
subject, body := internal.SignUpMailContent(code, a.config.MailSender.Timeout, fmt.Sprintf("%s %s", signUp.FirstName, signUp.LastName), a.config.Server.Host)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, signUp.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, signUp.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down Expand Up @@ -245,7 +245,7 @@ func (a *App) VerifySignUpCodeHandler(req *http.Request) (interface{}, Response)
middlewares.UserCreations.WithLabelValues(user.ID.String(), user.Email).Inc()

subject, body := internal.WelcomeMailContent(user.Name(), a.config.Server.Host)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down Expand Up @@ -405,7 +405,7 @@ func (a *App) ForgotPasswordHandler(req *http.Request) (interface{}, Response) {
// send verification code
code := internal.GenerateRandomCode()
subject, body := internal.ResetPasswordMailContent(code, a.config.MailSender.Timeout, user.Name(), a.config.Server.Host)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, email.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, email.Email, subject, body, "")

if err != nil {
log.Error().Err(err).Send()
Expand Down Expand Up @@ -840,7 +840,6 @@ func (a *App) ActivateVoucherHandler(req *http.Request) (interface{}, Response)
// @Failure 500 {object} Response
// @Router /user/charge_balance [put]
func (a *App) ChargeBalance(req *http.Request) (interface{}, Response) {

userID := req.Context().Value(middlewares.UserIDKey("UserID")).(string)

var input ChargeBalance
Expand Down Expand Up @@ -919,6 +918,7 @@ func (a *App) ChargeBalance(req *http.Request) (interface{}, Response) {
// @Failure 500 {object} Response
// @Router /user [delete]
func (a *App) DeleteUserHandler(req *http.Request) (interface{}, Response) {
// TODO: delete customer from stripe
userID := req.Context().Value(middlewares.UserIDKey("UserID")).(string)
user, err := a.db.GetUserByID(userID)
if err == gorm.ErrRecordNotFound {
Expand All @@ -930,7 +930,7 @@ func (a *App) DeleteUserHandler(req *http.Request) (interface{}, Response) {
}

// 1. Create last invoice to pay if there were active deployments
if err := a.createInvoice(userID, time.Now()); err != nil {
if err := a.createInvoice(user, time.Now()); err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}
Expand Down
4 changes: 2 additions & 2 deletions server/app/voucher_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (a *App) UpdateVoucherHandler(req *http.Request) (interface{}, Response) {
subject, body = internal.RejectedVoucherMailContent(user.Name(), a.config.Server.Host)
}

err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down Expand Up @@ -228,7 +228,7 @@ func (a *App) ApproveAllVouchersHandler(req *http.Request) (interface{}, Respons
}

subject, body := internal.ApprovedVoucherMailContent(v.Voucher, user.Name(), a.config.Server.Host)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body)
err = internal.SendMail(a.config.MailSender.Email, a.config.MailSender.SendGridKey, user.Email, subject, body, "")
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down
Loading