diff --git a/api/reddit/check_subreddit.go b/api/reddit/check_subreddit.go index 949a14a..5598a02 100644 --- a/api/reddit/check_subreddit.go +++ b/api/reddit/check_subreddit.go @@ -25,7 +25,7 @@ func (reddit *Reddit) CheckSubreddit(ctx context.Context, params CheckSubredditP ctx = caller.WithContext(ctx, caller.New(2)) - url := fmt.Sprintf("https://reddit.com/%s/%s.json?limit=1", params.SubredditType, params.Subreddit) + url := fmt.Sprintf("https://reddit.com/%s/%s.json?limit=1", params.SubredditType.Code(), params.Subreddit) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) if err != nil { return actual, errs.Wrapw(err, "failed to create request", "url", url, "params", params) diff --git a/api/reddit/get_posts.go b/api/reddit/get_posts.go index ec5c327..b319656 100644 --- a/api/reddit/get_posts.go +++ b/api/reddit/get_posts.go @@ -57,7 +57,12 @@ func (s SubredditType) Code() string { } func (s SubredditType) String() string { - return s.Code() + switch s { + case SubredditTypeUser: + return "User" + default: + return "Subreddit" + } } type GetPostsParam struct { diff --git a/db/migrations/20240527205312_trigger_refactors.sql b/db/migrations/20240527205312_trigger_refactors.sql new file mode 100644 index 0000000..7fa4e5b --- /dev/null +++ b/db/migrations/20240527205312_trigger_refactors.sql @@ -0,0 +1,26 @@ +-- +goose Up +-- +goose StatementBegin +DROP TRIGGER IF EXISTS update_subreddits_timestamp; -- Faulty trigger. Must be removed and never recovered. + +CREATE TRIGGER subreddits_update_timestamp_on_update AFTER UPDATE ON subreddits FOR EACH ROW +BEGIN + UPDATE subreddits SET updated_at = unixepoch() WHERE name = old.name; +END; + +CREATE TRIGGER devices_update_timestamp_on_update AFTER UPDATE ON devices FOR EACH ROW +BEGIN + UPDATE devices SET updated_at = unixepoch() WHERE slug = old.slug; +END; + +CREATE TRIGGER subreddits_update_timestamp_on_image_insert AFTER INSERT ON images FOR EACH ROW +BEGIN + UPDATE subreddits SET updated_at = unixepoch() WHERE name = new.subreddit; -- new -> image row. +END; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TRIGGER IF EXISTS subreddits_update_timestamp_on_update; +DROP TRIGGER IF EXISTS devices_update_timestamp_on_update; +DROP TRIGGER IF EXISTS subreddits_update_timestamp_on_image_insert; +-- +goose StatementEnd diff --git a/server/routes/page_subreddits.go b/server/routes/page_subreddits.go index 7df6208..8ed0ef5 100644 --- a/server/routes/page_subreddits.go +++ b/server/routes/page_subreddits.go @@ -7,7 +7,7 @@ import ( "github.com/tigorlazuardi/redmage/pkg/errs" "github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/views" - "github.com/tigorlazuardi/redmage/views/subredditsview" + "github.com/tigorlazuardi/redmage/views/subreddits" ) func (routes *Routes) PageSubreddits(rw http.ResponseWriter, r *http.Request) { @@ -19,7 +19,7 @@ func (routes *Routes) PageSubreddits(rw http.ResponseWriter, r *http.Request) { var params api.ListSubredditsParams params.FillFromQuery(r.URL.Query()) - var data subredditsview.Data + var data subreddits.Data var err error data.Subreddits, err = routes.API.ListSubredditsWithCover(ctx, params) @@ -28,13 +28,13 @@ func (routes *Routes) PageSubreddits(rw http.ResponseWriter, r *http.Request) { code, message := errs.HTTPMessage(err) rw.WriteHeader(code) data.Error = message - if err := subredditsview.Subreddit(c, data).Render(ctx, rw); err != nil { + if err := subreddits.View(c, data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render subreddits") } return } - if err := subredditsview.Subreddit(c, data).Render(r.Context(), rw); err != nil { + if err := subreddits.View(c, data).Render(r.Context(), rw); err != nil { log.New(ctx).Err(err).Error("failed to render subreddits view") rw.WriteHeader(http.StatusInternalServerError) } diff --git a/server/routes/page_subreddits_add.go b/server/routes/page_subreddits_add.go index 281c334..bb5d364 100644 --- a/server/routes/page_subreddits_add.go +++ b/server/routes/page_subreddits_add.go @@ -5,7 +5,7 @@ import ( "github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/views" - "github.com/tigorlazuardi/redmage/views/subredditsview/addview" + "github.com/tigorlazuardi/redmage/views/subreddits/put" ) func (routes *Routes) PageSubredditsAdd(rw http.ResponseWriter, r *http.Request) { @@ -14,7 +14,13 @@ func (routes *Routes) PageSubredditsAdd(rw http.ResponseWriter, r *http.Request) c := views.NewContext(routes.Config, r) - if err := addview.Addview(c).Render(ctx, rw); err != nil { + data := put.Data{Title: "Add Subreddit"} + + if err := put.View(c, data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render subreddits add page") } + + // if err := addview.Addview(c).Render(ctx, rw); err != nil { + // log.New(ctx).Err(err).Error("failed to render subreddits add page") + // } } diff --git a/server/routes/routes.go b/server/routes/routes.go index a4edc4a..761fec3 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -60,7 +60,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) { router.Post("/subreddits/add", routes.SubredditsCreateHTMX) router.Post("/subreddits/start", routes.SubredditStartDownloadHTMX) - router.Post("/subreddits/check", routes.SubredditCheckHTMX) + router.Get("/subreddits/check", routes.SubredditCheckHTMX) router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX) router.Get("/devices/add/validate/slug", routes.DevicesValidateSlugHTMX) diff --git a/server/routes/subreddit_check.go b/server/routes/subreddit_check.go index 962e8c5..4ad37d4 100644 --- a/server/routes/subreddit_check.go +++ b/server/routes/subreddit_check.go @@ -10,7 +10,7 @@ import ( "github.com/tigorlazuardi/redmage/api/reddit" "github.com/tigorlazuardi/redmage/pkg/errs" "github.com/tigorlazuardi/redmage/pkg/log" - "github.com/tigorlazuardi/redmage/views/subredditsview/addview" + "github.com/tigorlazuardi/redmage/views/subreddits/put" ) func (routes *Routes) SubredditsCheckAPI(rw http.ResponseWriter, r *http.Request) { @@ -48,17 +48,15 @@ func (routes *Routes) SubredditsCheckAPI(rw http.ResponseWriter, r *http.Request } func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request) { - var data addview.SubredditInputData - name := r.FormValue("name") - data.Value = name - - var subtype reddit.SubredditType + var ( + data put.NameInputData + subtype reddit.SubredditType + ) + data.Value = r.FormValue("name") _ = subtype.Parse(r.FormValue("type")) - data.Type = subtype - - if name == "" { - if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil { + if data.Value == "" { + if err := put.NameInput(data).Render(r.Context(), rw); err != nil { log.New(r.Context()).Err(err).Error("failed to render subreddit input form") } return @@ -68,7 +66,7 @@ func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request defer span.End() params := api.SubredditCheckParam{ - Subreddit: name, + Subreddit: data.Value, SubredditType: subtype, } @@ -78,7 +76,7 @@ func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request code, message := errs.HTTPMessage(err) rw.WriteHeader(code) data.Error = message - if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil { + if err := put.NameInput(data).Render(r.Context(), rw); err != nil { log.New(r.Context()).Err(err).Error("failed to render subreddit input form") } return @@ -92,7 +90,7 @@ func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request code, message := errs.HTTPMessage(err) rw.WriteHeader(code) data.Error = message - if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil { + if err := put.NameInput(data).Render(r.Context(), rw); err != nil { log.New(r.Context()).Err(err).Error("failed to render subreddit input form") } return @@ -101,15 +99,15 @@ func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request if exist { rw.WriteHeader(http.StatusConflict) data.Error = "subreddit already registered" - if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil { + if err := put.NameInput(data).Render(r.Context(), rw); err != nil { log.New(r.Context()).Err(err).Error("failed to render subreddit input form") } return } - data.Valid = true + data.Valid = fmt.Sprintf("%s is valid", subtype) - if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil { + if err := put.NameInput(data).Render(r.Context(), rw); err != nil { log.New(r.Context()).Err(err).Error("failed to render subreddit input form") } } diff --git a/server/routes/subreddit_create.go b/server/routes/subreddit_create.go index 91857de..6450436 100644 --- a/server/routes/subreddit_create.go +++ b/server/routes/subreddit_create.go @@ -7,7 +7,6 @@ import ( "net/http" "strconv" - "github.com/a-h/templ" "github.com/robfig/cron/v3" "github.com/tigorlazuardi/redmage/api" "github.com/tigorlazuardi/redmage/api/reddit" @@ -72,13 +71,12 @@ func (routes *Routes) SubredditsCreateHTMX(rw http.ResponseWriter, r *http.Reque ctx, span := tracer.Start(r.Context(), "*Routes.SubredditsCreateHTMX") defer span.End() - sub, errComponents := subredditsDataFromRequest(r) - if len(errComponents) > 0 { - rw.WriteHeader(http.StatusBadRequest) - for _, err := range errComponents { - if e := err.Render(ctx, rw); e != nil { - log.New(ctx).Err(e).Error("failed to render error") - } + sub, err := subredditsDataFromRequest(r) + if err != nil { + code, message := errs.HTTPMessage(err) + rw.WriteHeader(code) + if err := components.ErrorNotication(message).Render(ctx, rw); err != nil { + log.New(ctx).Err(err).Error("failed to render error notification") } return } @@ -113,36 +111,31 @@ func (routes *Routes) SubredditsCreateHTMX(rw http.ResponseWriter, r *http.Reque } return } + if fetch, _ := strconv.ParseBool(r.FormValue("fetch")); fetch { + _ = routes.API.PubsubStartDownloadSubreddit(ctx, api.PubsubStartDownloadSubredditParams{ + Subreddit: sub.Name, + }) + } rw.Header().Set("HX-Redirect", "/subreddits") rw.WriteHeader(http.StatusCreated) _, _ = rw.Write([]byte("Subreddit created")) } -func subredditsDataFromRequest(r *http.Request) (sub *models.Subreddit, errs []templ.Component) { +func subredditsDataFromRequest(r *http.Request) (sub *models.Subreddit, err error) { sub = &models.Subreddit{} var t reddit.SubredditType - err := t.Parse(r.FormValue("type")) + err = t.Parse(r.FormValue("type")) if err != nil { - errs = append(errs, addview.SubredditTypeInput(addview.SubredditTypeData{ - Value: strconv.Itoa(int(t)), - Error: err.Error(), - HXSwapOOB: "true", - })) - - return nil, errs + return nil, errs. + Wrapw(err, "invalid subreddit type", "type", r.FormValue("type")). + Code(http.StatusBadRequest) } sub.Subtype = int32(t) sub.Name = r.FormValue("name") if sub.Name == "" { - errs = append(errs, addview.SubredditInputForm(addview.SubredditInputData{ - Value: sub.Name, - Error: "name is required", - Type: t, - HXSwapOOB: "true", - })) - return nil, errs + return nil, errs.Fail("name is required").Code(http.StatusBadRequest) } enableSchedule, _ := strconv.Atoi(r.FormValue("enable_schedule")) @@ -157,27 +150,21 @@ func subredditsDataFromRequest(r *http.Request) (sub *models.Subreddit, errs []t } if sub.EnableSchedule == 1 { - sub.Schedule = r.FormValue("schedule") - _, err = cronParser.Parse(sub.Schedule) + schedule := r.FormValue("schedule") + _, err = cron.ParseStandard(schedule) if err != nil { - errs = append(errs, addview.ScheduleInput(addview.ScheduleInputData{ - Value: sub.Schedule, - Error: fmt.Sprintf("invalid cron schedule: %s", err), - HXSwapOOB: "true", - })) + return nil, errs.Wrapf(err, "invalid cron schedule: %s", err).Code(http.StatusBadRequest) } + sub.Schedule = schedule } countback, _ := strconv.Atoi(r.FormValue("countback")) sub.Countback = int32(countback) if sub.Countback < 1 { - errs = append(errs, addview.CountbackInput(addview.CountbackInputData{ - Value: int64(sub.Countback), - Error: "countback must be 1 or higher", - })) + return nil, errs.Fail("countback must be 1 or higher").Code(http.StatusBadRequest) } - return sub, errs + return sub, nil } var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) diff --git a/server/routes/subreddit_validate_schedule.go b/server/routes/subreddit_validate_schedule.go index 91e873d..5f7628f 100644 --- a/server/routes/subreddit_validate_schedule.go +++ b/server/routes/subreddit_validate_schedule.go @@ -7,28 +7,28 @@ import ( "time" "github.com/tigorlazuardi/redmage/pkg/log" - "github.com/tigorlazuardi/redmage/views/subredditsview/addview" + "github.com/tigorlazuardi/redmage/views/subreddits/put" ) func (routes *Routes) SubredditValidateScheduleHTMX(rw http.ResponseWriter, r *http.Request) { ctx, span := tracer.Start(r.Context(), "*Routes.SubredditValidateScheduleHTMX") defer span.End() - var data addview.ScheduleInputData + var data put.ScheduleInputData enabled, _ := strconv.Atoi(r.FormValue("enable_schedule")) data.Disabled = enabled == 0 data.Value = r.FormValue("schedule") if data.Value == "" { - if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil { + if err := put.ScheduleInput(data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render schedule input") } return } if data.Disabled { - if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil { + if err := put.ScheduleInput(data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render schedule input") } return @@ -37,7 +37,7 @@ func (routes *Routes) SubredditValidateScheduleHTMX(rw http.ResponseWriter, r *h scheduler, err := cronParser.Parse(data.Value) if err != nil { data.Error = fmt.Sprintf("Invalid schedule format: %s", err.Error()) - if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil { + if err := put.ScheduleInput(data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render schedule input") } return @@ -45,9 +45,9 @@ func (routes *Routes) SubredditValidateScheduleHTMX(rw http.ResponseWriter, r *h next := scheduler.Next(time.Now()) - data.Valid = fmt.Sprintf("Syntax is valid. Next run at: %s", next.Format("Monday, _2 January 2006 15:04 MST")) + data.Valid = fmt.Sprintf("Syntax is valid. Next run at: %s", next.Format("Monday, _2 January 2006 15:04:05 MST")) - if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil { + if err := put.ScheduleInput(data).Render(ctx, rw); err != nil { log.New(ctx).Err(err).Error("failed to render schedule input") } } diff --git a/views/subreddits/put/countback.templ b/views/subreddits/put/countback.templ new file mode 100644 index 0000000..0659345 --- /dev/null +++ b/views/subreddits/put/countback.templ @@ -0,0 +1,44 @@ +package put + +import "strconv" + +type CountbackInputData struct { + Value int64 +} + +func (c *CountbackInputData) GetValue() string { + if c.Value < 1 { + return "100" + } + return strconv.FormatInt(c.Value, 10) +} + +templ CountbackInput(data CountbackInputData) { + +} diff --git a/views/subreddits/put/fetch.templ b/views/subreddits/put/fetch.templ new file mode 100644 index 0000000..cbfd672 --- /dev/null +++ b/views/subreddits/put/fetch.templ @@ -0,0 +1,32 @@ +package put + +templ FetchCheckbox() { +
Click here to add a new subreddit.
+ } else { +{ data.Name }
+Click here to add a new subreddit.
- } else { -{ data.Name }
-