| -rw-r--r-- | config/config.go | 19 | ||||
| -rw-r--r-- | config/config.local.yaml | 5 | ||||
| -rw-r--r-- | config/config.prod.yaml | 4 | ||||
| -rw-r--r-- | config/config.stage.yaml | 4 | ||||
| -rw-r--r-- | internal/blogtrigger/blogtrigger.go | 8 | ||||
| -rw-r--r-- | internal/mailer/mailer.go | 71 | ||||
| -rw-r--r-- | internal/router/client-cache.go | 18 | ||||
| -rw-r--r-- | internal/router/lang-user-unsubscribe.go | 4 | ||||
| -rw-r--r-- | main.go | 5 | ||||
| -rw-r--r-- | views/pages/blog-catalogue.html | 8 |
10 files changed, 34 insertions, 112 deletions
diff --git a/config/config.go b/config/config.go index 1393dd8..d5dab2d 100644 --- a/config/config.go +++ b/config/config.go @@ -62,18 +62,13 @@ type Sqlite3Config struct { } type MailConfig struct { - ClientHost string `json:"ClientHost" yaml:"clientHost" validate:"required"` - MailHost string `json:"MailHost" yaml:"mailHost" validate:"required"` - PublicName string `json:"PublicName" yaml:"publicName" validate:"required"` - MailAddress string `json:"MailAddress" yaml:"mailAddress" validate:"required"` - Username string `json:"Username" yaml:"username" validate:"required"` - Password string `json:"Password" yaml:"password" validate:"required"` - Salt string `json:"Salt" yaml:"salt" validate:"required"` - Trigger TriggerConfig `json:"Trigger" yaml:"trigger" validate:"required"` -} - -type TriggerConfig struct { - OnNewPost string `json:"OnNewPost" yaml:"onNewPost" validate:"cron,required"` + ClientHost string `json:"ClientHost" yaml:"clientHost" validate:"required"` + MailHost string `json:"MailHost" yaml:"mailHost" validate:"required"` + PublicName string `json:"PublicName" yaml:"publicName" validate:"required"` + MailAddress string `json:"MailAddress" yaml:"mailAddress" validate:"required"` + Username string `json:"Username" yaml:"username" validate:"required"` + Password string `json:"Password" yaml:"password" validate:"required"` + Salt string `json:"Salt" yaml:"salt" validate:"required"` } func LoadConfig(path string, config *Config) error { diff --git a/config/config.local.yaml b/config/config.local.yaml index e23c808..008fa93 100644 --- a/config/config.local.yaml +++ b/config/config.local.yaml @@ -32,8 +32,7 @@ auth: db: type: sqlite3 config: - # dsn: 'file:/tmp/auth.db?cache=shared&mode=rwc&_journal_mode=WAL' - dsn: 'file:/tmp/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' + dsn: 'file:/tmp/auth.db?cache=shared&mode=rwc' salt: '123' mail: clientHost: '127.0.0.1:3000' @@ -43,5 +42,3 @@ mail: username: '${MAIL_USERNAME}' password: '${MAIL_PASSWORD}' salt: '${MAIL_SALT}' - trigger: - onNewPost: "* * * * *" diff --git a/config/config.prod.yaml b/config/config.prod.yaml index 0bee79d..4c0f425 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&_journal_mode=WAL' + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2' salt: '${AUTH_SALT}' mail: clientHost: '${FQDN}' @@ -42,5 +42,3 @@ mail: username: '${MAIL_USERNAME}' password: '${MAIL_PASSWORD}' salt: '${MAIL_SALT}' - trigger: - onNewPost: "0/5 * * * *" diff --git a/config/config.stage.yaml b/config/config.stage.yaml index 1c15dae..003ec05 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&_journal_mode=WAL' + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2' salt: '${AUTH_SALT}' mail: clientHost: '${FQDN}' @@ -42,5 +42,3 @@ mail: username: '${MAIL_USERNAME}' password: '${MAIL_PASSWORD}' salt: '${MAIL_SALT}' - trigger: - onNewPost: "0/3 * * * *" diff --git a/internal/blogtrigger/blogtrigger.go b/internal/blogtrigger/blogtrigger.go index c5b3097..ab66981 100644 --- a/internal/blogtrigger/blogtrigger.go +++ b/internal/blogtrigger/blogtrigger.go @@ -16,7 +16,7 @@ type BlogTriggerScheduler struct { onTrigger func([]*b2.BlogPage) error } -func NewBlogTriggerScheduler(b2Client *b2.B2Client, availableLanguages []config.AvailableLanguageConfig, cron string, onTrigger func([]*b2.BlogPage) error) (*BlogTriggerScheduler, error) { +func NewBlogTriggerScheduler(b2Client *b2.B2Client, availableLanguages []config.AvailableLanguageConfig, onTrigger func([]*b2.BlogPage) error) (*BlogTriggerScheduler, error) { s, err := gocron.NewScheduler() if err != nil { return nil, fmt.Errorf("failed to create new scheduler: %w", err) @@ -30,7 +30,7 @@ func NewBlogTriggerScheduler(b2Client *b2.B2Client, availableLanguages []config. bts := &BlogTriggerScheduler{s, knownBlogPages, b2Client, onTrigger} defer bts.s.Start() - bts.s.NewJob(gocron.CronJob(cron, false), gocron.NewTask(func(bts *BlogTriggerScheduler) { + bts.s.NewJob(gocron.CronJob("0/5 * * * *", false), gocron.NewTask(func(bts *BlogTriggerScheduler) { posts, err := bts.scan() if err != nil { slog.Error("failed to execute scanning new blog pages cron job", slog.String("error", err.Error())) @@ -49,10 +49,6 @@ 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/mailer/mailer.go b/internal/mailer/mailer.go index d6dbd17..c146548 100644 --- a/internal/mailer/mailer.go +++ b/internal/mailer/mailer.go @@ -152,20 +152,12 @@ func (m *Mailer) MailIsTaken(email string) (bool, error) { return false, fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "MailIsTaken")) - defer func(tx *sql.Tx) { - if err = tx.Commit(); err != nil { - tx.Rollback() - } - slog.Debug("ended db transaction", slog.String("method", "MailIsTaken")) - }(tx) - var rows *sql.Rows if rows, err = tx.Query(`SELECT email FROM user_email_table WHERE email=? LIMIT 1;`, email); err != nil { tx.Rollback() - return false, fmt.Errorf("failed to query user-email settings in db: %s", err) } + defer tx.Commit() defer rows.Close() isTaken := rows.Next() @@ -178,19 +170,12 @@ func (m *Mailer) GetInfo(userIdHash []byte) (email string, lang string, err erro return "", "", fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "GetInfo")) - defer func(tx *sql.Tx) { - if err = tx.Commit(); err != nil { - tx.Rollback() - } - slog.Debug("ended db transaction", slog.String("method", "GetInfo")) - }(tx) - var rows *sql.Rows if rows, err = tx.Query(`SELECT email, lang FROM user_email_table WHERE user_id=? LIMIT 1;`, userIdHash); err != nil { tx.Rollback() return "", "", fmt.Errorf("failed to query user-email settings in db: %s", err) } + defer tx.Commit() defer rows.Close() if !rows.Next() { @@ -317,22 +302,18 @@ func (m *Mailer) Verify(verificationCodeEncoded string, lang string) error { return fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "Verify")) if _, err = tx.Exec(`INSERT INTO user_email_table(user_id, email, lang) VALUES(?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET email=excluded.email, lang=excluded.lang;`, m.GetHash(string(userId)), address, lang); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "Verify")) return fmt.Errorf("failed to configure user-email settings in db: %s", err) } if err = tx.Commit(); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "Verify")) return fmt.Errorf("failed to commit transaction to db: %s", err) } - slog.Debug("ended db transaction", slog.String("method", "Verify")) m.verificationCodes.Del(verificationCode) delete(m.lostMailMap, verificationSegments[0]) @@ -345,14 +326,6 @@ func (m *Mailer) GetSubscriptions(userId string) (subscriptionType SubscriptionT return None, nil, fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "GetSubscriptions")) - defer func(tx *sql.Tx) { - if err = tx.Commit(); err != nil { - tx.Rollback() - } - slog.Debug("ended db transaction", slog.String("method", "GetSubscriptions")) - }(tx) - hash := m.GetHash(userId) var rows *sql.Rows @@ -360,6 +333,7 @@ func (m *Mailer) GetSubscriptions(userId string) (subscriptionType SubscriptionT tx.Rollback() return None, nil, fmt.Errorf("failed to query user-to-tags table in db for the user: %s", err) } + defer tx.Commit() defer rows.Close() if !rows.Next() { @@ -387,8 +361,6 @@ func (m *Mailer) Subscribe(userIdHash []byte, subscriptionType SubscriptionType, return fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "Subscribe")) - slices.Sort(tags) tagsOutput := "" switch subscriptionType { @@ -404,16 +376,13 @@ func (m *Mailer) Subscribe(userIdHash []byte, subscriptionType SubscriptionType, ON CONFLICT(user_id) DO UPDATE SET tags=excluded.tags;`, userIdHash, tagsOutput); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "Subscribe")) return fmt.Errorf("failed to configure user-to-tags table in db for the user: %s", err) } if err = tx.Commit(); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "Subscribe")) return fmt.Errorf("failed to commit transaction to db: %s", err) } - slog.Debug("ended db transaction", slog.String("method", "Subscribe")) return nil } @@ -424,11 +393,9 @@ func (m *Mailer) NewPost(post *b2.BlogPage) error { return fmt.Errorf("failed to initialize transaction with db: %s", err) } - slog.Debug("began db transaction", slog.String("method", "NewPost")) var rows *sql.Rows if rows, err = tx.Query(`SELECT user_id, tags FROM subscription_user_to_tags_table;`); err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewPost")) return fmt.Errorf("failed to query user-to-tags table in db: %s", err) } @@ -452,11 +419,21 @@ 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, ""}) + }{userId, email}) continue } @@ -466,7 +443,7 @@ rowLoop: usersToSend = append(usersToSend, struct { userId []byte email string - }{userId, ""}) + }{userId, email}) continue rowLoop } } @@ -474,27 +451,9 @@ rowLoop: tx.Commit() 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/client-cache.go b/internal/router/client-cache.go index e7cda30..d607cdd 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -31,12 +31,10 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { if err != nil { return nil, fmt.Errorf("fail to init transaction with db to fill cache: %w", err) } - slog.Debug("began db transaction", slog.String("method", "NewClientCache")) rows, err := tx.Query("select * from blog_likes;") if err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return nil, fmt.Errorf("fail to query db for blog_likes to fill cache: %w", err) } @@ -64,7 +62,6 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { rows, err = tx.Query("select * from blog_views;") if err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return nil, fmt.Errorf("fail to query db for blog_views to fill cache: %w", err) } @@ -84,11 +81,8 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { } if err = tx.Commit(); err != nil { - tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return nil, fmt.Errorf("fail to commit transaction in db: %w", err) } - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return &ClientCache{ hashMap: make(map[string]string), @@ -105,28 +99,18 @@ func (c *ClientCache) Close() error { if err != nil { return fmt.Errorf("fail to init transaction with db to dump cache: %w", err) } - slog.Debug("began db transaction in ClientCache.Close") if err = batchSave(tx, "blog_likes", c.likePageMap); err != nil { tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") return fmt.Errorf("fail to save blog_likes: %s", err) } if err = batchSave(tx, "blog_views", c.viewPageMap); err != nil { tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") return fmt.Errorf("fail to save blog_views: %s", err) } - if err = tx.Commit(); err != nil { - tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") - return fmt.Errorf("fail to commit all the changes related to cache: %s", err) - } - - slog.Debug("ended db transaction in ClientCache.Close") - return nil + return tx.Commit() } func (c *ClientCache) GetHash(id string) string { diff --git a/internal/router/lang-user-unsubscribe.go b/internal/router/lang-user-unsubscribe.go index c39f5e7..a6ca89f 100644 --- a/internal/router/lang-user-unsubscribe.go +++ b/internal/router/lang-user-unsubscribe.go @@ -35,19 +35,17 @@ 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 status = fiber.ErrInternalServerError.Code } else { - statusColor = "0, 255, 0" + statusColor = "0, 255, 0," statusEmoji = "♡⸜(˶˃ ᵕ ˂˶)⸝♡" statusText = l[lang].UnsubscribePage.Success status = fiber.StatusOK @@ -153,7 +153,7 @@ func main() { os.Exit(1) } - router.BlogTrigger, err = blogtrigger.NewBlogTriggerScheduler(b2Client, cfg.AvailableLanguages, cfg.Mail.Trigger.OnNewPost, func(bp []*b2.BlogPage) error { + router.BlogTrigger, err = blogtrigger.NewBlogTriggerScheduler(b2Client, cfg.AvailableLanguages, func(bp []*b2.BlogPage) error { for _, post := range bp { if err := router.Mailer.NewPost(post); err != nil { return err @@ -202,9 +202,6 @@ 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())) } diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html index d209205..8e91798 100644 --- a/views/pages/blog-catalogue.html +++ b/views/pages/blog-catalogue.html @@ -1,10 +1,10 @@ {{ define "body" }} -<div class="flex flex-row ar-lt-0.8:flex-col text-base h-full"> +<div class="flex flex-row ar-lt-0.8:flex-col text-base h-[100%]"> <form hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target=".blog-cards" hx-swap="innerHTML" - class="tags-list ar-gt-0.8:min-w-[10dvh] ar-gt-0.8:max-w-[20dvh] ar-gt-0.8:w-fit ar-lt-0.8:w-full ar-lt-0.8:max-h-[30%] flex shrink-0 flex-col ar-gt-0.8:mr-6 bg-background-light inset-shadow-[0_0_0.4rem_black]"> + class="tags-list ar-gt-0.8:min-w-[10dvh] ar-gt-0.8:max-w-[20dvh] ar-gt-0.8:w-fit ar-lt-0.8:w-[100%] ar-lt-0.8:max-h-[30%] flex shrink-0 flex-col ar-gt-0.8:mr-6 bg-background-light inset-shadow-[0_0_0.4rem_black]"> <div class="flex flex-col overflow-y-auto"> <fieldset> - <legend class="font-bold w-full text-center">{{ .L.BlogSearch.TagsHeader }}</legend> + <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.TagsHeader }}</legend> <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 xs:grid-cols-4 sm:grid-cols-5 grid-flow-row-dense"> <div class="m-1"> <label class="font-bold"><input type="checkbox" id="tagsAllCheckbox" onclick="selectAll();"> {{ .L.BlogSearch.ChooseAllTags }}</label> @@ -17,7 +17,7 @@ </div> </fieldset> <fieldset class="mt-1"> - <legend class="font-bold w-full text-center">{{ .L.BlogSearch.OrderByHeader }}</legend> + <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.OrderByHeader }}</legend> <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 grid-rows-2 grid-flow-col"> <div class="m-1"> <label><input type="radio" id="sortTitleAsc" name="sort" value="titleAsc" {{ if eq .QuerySort "titleAsc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-a-z"></i></label> |