From df7a0ae1c0fc177cba2eae8cc73fe21d2afdd229 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Fri, 24 Oct 2025 20:40:00 +0700 Subject: feat: allow to unsubscribe from an email --- internal/router/lang-user-unsubscribe.go | 69 ++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 internal/router/lang-user-unsubscribe.go (limited to 'internal/router/lang-user-unsubscribe.go') diff --git a/internal/router/lang-user-unsubscribe.go b/internal/router/lang-user-unsubscribe.go new file mode 100644 index 0000000..a6ca89f --- /dev/null +++ b/internal/router/lang-user-unsubscribe.go @@ -0,0 +1,69 @@ +package router + +import ( + "fmt" + "html/template" + "log/slog" + + "github.com/SayaAndy/saya-today-web/config" + "github.com/SayaAndy/saya-today-web/locale" + "github.com/gofiber/fiber/v2" +) + +func init() { + assert(0, tm.Add("unsubscribe-page", "views/pages/unsubscribe-page.html")) +} + +func Lang_User_Unsubscribe(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + lang := c.Params("lang") + for _, availableLang := range langs { + if availableLang.Name == lang { + goto langIsAvailable + } + } + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang)) + + langIsAvailable: + var statusEmoji, statusText, statusColor string + var status int + + unsubscribeCode := c.FormValue("code") + if unsubscribeCode == "" { + statusColor = "0, 0, 255" + statusEmoji = "(╭ರ_•́)" + statusText = l[lang].UnsubscribePage.UnsetCode + status = fiber.ErrBadRequest.Code + } else if clientError, serverError := Mailer.Unsubscribe(unsubscribeCode); clientError != nil { + statusColor = "255, 0, 0" + statusEmoji = "(͠≖~≖ ͡ )" + statusText = l[lang].UnsubscribePage.InvalidCode + status = fiber.ErrBadRequest.Code + } else if serverError != nil { + statusColor = "255, 128, 0" + statusEmoji = "( ˶°ㅁ°) !!" + statusText = l[lang].UnsubscribePage.OnServerError + status = fiber.ErrInternalServerError.Code + } else { + statusColor = "0, 255, 0," + statusEmoji = "♡⸜(˶˃ ᵕ ˂˶)⸝♡" + statusText = l[lang].UnsubscribePage.Success + status = fiber.StatusOK + } + + content, err := tm.Render("unsubscribe-page", fiber.Map{ + "Lang": lang, + "L": l[lang], + "StatusEmoji": statusEmoji, + "StatusText": statusText, + "StatusColor": template.HTML(statusColor), + }) + if err != nil { + slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/user/unsubscribe"), slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") + } + + return c.Type("html").Status(status).Send(content) + } +} -- cgit v1.3.1+13 From 634fa487aa91c1a12af0ddb0cb0744c27df12c04 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Sat, 25 Oct 2025 19:41:49 +0700 Subject: feat: enable WAL journal mode for db fix: green colors on unsubscribe page feat: properly shutdown blog trigger scheduler --- config/config.local.yaml | 2 +- config/config.prod.yaml | 2 +- config/config.stage.yaml | 2 +- internal/blogtrigger/blogtrigger.go | 4 ++++ internal/router/lang-user-unsubscribe.go | 2 +- main.go | 3 +++ 6 files changed, 11 insertions(+), 4 deletions(-) (limited to 'internal/router/lang-user-unsubscribe.go') diff --git a/config/config.local.yaml b/config/config.local.yaml index d40e8e9..327bad4 100644 --- a/config/config.local.yaml +++ b/config/config.local.yaml @@ -32,7 +32,7 @@ auth: db: type: sqlite3 config: - dsn: 'file:/tmp/auth.db?cache=shared&mode=rwc' + dsn: 'file:/tmp/auth.db?cache=shared&mode=rwc&_journal_mode=WAL' salt: '123' mail: clientHost: '127.0.0.1:3000' diff --git a/config/config.prod.yaml b/config/config.prod.yaml index 7a9e5ae..0bee79d 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -32,7 +32,7 @@ auth: db: type: sqlite3 config: - dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2' + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' salt: '${AUTH_SALT}' mail: clientHost: '${FQDN}' diff --git a/config/config.stage.yaml b/config/config.stage.yaml index 8a5fde1..1c15dae 100644 --- a/config/config.stage.yaml +++ b/config/config.stage.yaml @@ -32,7 +32,7 @@ auth: db: type: sqlite3 config: - dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2' + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' salt: '${AUTH_SALT}' mail: clientHost: '${FQDN}' diff --git a/internal/blogtrigger/blogtrigger.go b/internal/blogtrigger/blogtrigger.go index 9b016e8..c5b3097 100644 --- a/internal/blogtrigger/blogtrigger.go +++ b/internal/blogtrigger/blogtrigger.go @@ -49,6 +49,10 @@ func NewBlogTriggerScheduler(b2Client *b2.B2Client, availableLanguages []config. return bts, nil } +func (bts *BlogTriggerScheduler) Close() error { + return bts.s.Shutdown() +} + func (bts *BlogTriggerScheduler) scan() (newPages []*b2.BlogPage, err error) { newPages = make([]*b2.BlogPage, 0) for lang := range bts.knownBlogPages { diff --git a/internal/router/lang-user-unsubscribe.go b/internal/router/lang-user-unsubscribe.go index a6ca89f..81d1bfb 100644 --- a/internal/router/lang-user-unsubscribe.go +++ b/internal/router/lang-user-unsubscribe.go @@ -45,7 +45,7 @@ func Lang_User_Unsubscribe(l map[string]*locale.LocaleConfig, langs []config.Ava statusText = l[lang].UnsubscribePage.OnServerError status = fiber.ErrInternalServerError.Code } else { - statusColor = "0, 255, 0," + statusColor = "0, 255, 0" statusEmoji = "♡⸜(˶˃ ᵕ ˂˶)⸝♡" statusText = l[lang].UnsubscribePage.Success status = fiber.StatusOK diff --git a/main.go b/main.go index bd552a1..174854d 100644 --- a/main.go +++ b/main.go @@ -202,6 +202,9 @@ func main() { <-sigChan slog.Info("gracefully shutting down...") + if err = router.BlogTrigger.Close(); err != nil { + slog.Error("fail to shutdown blog trigger scheduler", slog.String("error", err.Error())) + } if err = app.Shutdown(); err != nil { slog.Error("fail to shutdown fiber server", slog.String("error", err.Error())) } -- cgit v1.3.1+13 From 629e275e5270ed146f9cd6dba25383cf80022909 Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Sat, 25 Oct 2025 21:48:36 +0700 Subject: fix: make statements sequential in mailer new post method feat: switch mail service --- internal/mailer/mailer.go | 33 +++++++++++++++++++------------- internal/router/lang-user-unsubscribe.go | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) (limited to 'internal/router/lang-user-unsubscribe.go') diff --git a/internal/mailer/mailer.go b/internal/mailer/mailer.go index 847ebde..d6dbd17 100644 --- a/internal/mailer/mailer.go +++ b/internal/mailer/mailer.go @@ -349,8 +349,8 @@ func (m *Mailer) GetSubscriptions(userId string) (subscriptionType SubscriptionT defer func(tx *sql.Tx) { if err = tx.Commit(); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "GetSubscriptions")) } + slog.Debug("ended db transaction", slog.String("method", "GetSubscriptions")) }(tx) hash := m.GetHash(userId) @@ -452,21 +452,11 @@ rowLoop: continue } - email, lang, err := m.GetInfo(userId) - if err != nil { - slog.Warn("failed to get info about the user", slog.String("error", err.Error()), slog.Int("index", i), slog.String("user_id", base64.RawStdEncoding.EncodeToString(userId))) - continue - } - - if lang != post.Lang { - continue - } - if tagsString == "_all" { usersToSend = append(usersToSend, struct { userId []byte email string - }{userId, email}) + }{userId, ""}) continue } @@ -476,7 +466,7 @@ rowLoop: usersToSend = append(usersToSend, struct { userId []byte email string - }{userId, email}) + }{userId, ""}) continue rowLoop } } @@ -486,8 +476,25 @@ rowLoop: rows.Close() slog.Debug("ended db transaction", slog.String("method", "NewPost")) + for i := range usersToSend { + email, lang, err := m.GetInfo(usersToSend[i].userId) + if err != nil { + slog.Warn("failed to get info about the user", slog.String("error", err.Error()), slog.Int("index", i), slog.String("user_id", base64.RawStdEncoding.EncodeToString(userId))) + continue + } + + if lang != post.Lang { + continue + } + + usersToSend[i].email = email + } + messages := make([]*mail.Msg, 0, len(usersToSend)) for _, user := range usersToSend { + if user.email == "" { + continue + } unsubscribeCodeBytes := make([]byte, 8) rand.Read(unsubscribeCodeBytes) unsubscribeCode := binary.LittleEndian.Uint64(unsubscribeCodeBytes) diff --git a/internal/router/lang-user-unsubscribe.go b/internal/router/lang-user-unsubscribe.go index 81d1bfb..c39f5e7 100644 --- a/internal/router/lang-user-unsubscribe.go +++ b/internal/router/lang-user-unsubscribe.go @@ -35,11 +35,13 @@ func Lang_User_Unsubscribe(l map[string]*locale.LocaleConfig, langs []config.Ava statusText = l[lang].UnsubscribePage.UnsetCode status = fiber.ErrBadRequest.Code } else if clientError, serverError := Mailer.Unsubscribe(unsubscribeCode); clientError != nil { + slog.Info("got a client error when unsubscribing", slog.String("error", clientError.Error())) statusColor = "255, 0, 0" statusEmoji = "(͠≖~≖ ͡ )" statusText = l[lang].UnsubscribePage.InvalidCode status = fiber.ErrBadRequest.Code } else if serverError != nil { + slog.Error("got a server error when unsubscribing", slog.String("error", serverError.Error())) statusColor = "255, 128, 0" statusEmoji = "( ˶°ㅁ°) !!" statusText = l[lang].UnsubscribePage.OnServerError -- cgit v1.3.1+13