diff --git a/internal/database/queries.sql b/internal/database/queries.sql index eb6633b..7490c2d 100644 --- a/internal/database/queries.sql +++ b/internal/database/queries.sql @@ -89,25 +89,60 @@ ON CONFLICT (anime_id, related_anime_id) DO UPDATE SET UPDATE anime SET status = ?, relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?; -- name: GetAnimeNeedingRelationSync :many -SELECT a.id, a.title_original -FROM watch_list_entry w -JOIN anime a ON w.anime_id = a.id -WHERE w.status IN ('completed', 'watching') - AND (a.relations_synced_at IS NULL OR a.relations_synced_at < datetime('now', '-7 days')) -GROUP BY a.id -ORDER BY MAX(w.updated_at) DESC +WITH RECURSIVE sequel_chain AS ( + SELECT a.id, a.title_original, a.relations_synced_at, w.updated_at as base_updated_at, 0 as depth + FROM watch_list_entry w + JOIN anime a ON w.anime_id = a.id + WHERE w.status IN ('completed', 'watching') + + UNION + + SELECT a.id, a.title_original, a.relations_synced_at, sc.base_updated_at, sc.depth + 1 + FROM sequel_chain sc + JOIN anime_relation r ON sc.id = r.anime_id AND r.relation_type = 'Sequel' + JOIN anime a ON r.related_anime_id = a.id + WHERE sc.depth < 10 +) +SELECT id, title_original +FROM sequel_chain +WHERE relations_synced_at IS NULL OR relations_synced_at < datetime('now', '-7 days') +GROUP BY id, title_original +ORDER BY MAX(base_updated_at) DESC, MIN(depth) ASC LIMIT 50; -- name: GetUpcomingSeasons :many +WITH RECURSIVE sequel_chain AS ( + SELECT + w.anime_id as root_id, + a.title_original as root_title, + r.related_anime_id as current_id, + 1 as depth + FROM watch_list_entry w + JOIN anime a ON w.anime_id = a.id + JOIN anime_relation r ON w.anime_id = r.anime_id + WHERE w.user_id = sqlc.arg('user_id') + AND w.status IN ('completed', 'watching') + AND r.relation_type = 'Sequel' + + UNION + + SELECT + sc.root_id, + sc.root_title, + r.related_anime_id, + sc.depth + 1 + FROM sequel_chain sc + JOIN anime_relation r ON sc.current_id = r.anime_id + WHERE r.relation_type = 'Sequel' AND sc.depth < 10 +) SELECT DISTINCT related.*, - a.title_original AS prequel_title -FROM watch_list_entry w -JOIN anime_relation r ON w.anime_id = r.anime_id -JOIN anime a ON w.anime_id = a.id -JOIN anime related ON r.related_anime_id = related.id -WHERE w.user_id = ? - AND w.status IN ('completed', 'watching') - AND r.relation_type = 'Sequel' - AND related.status IN ('Not yet aired', 'Currently Airing') + sc.root_title AS prequel_title +FROM sequel_chain sc +JOIN anime related ON sc.current_id = related.id +WHERE related.status IN ('Not yet aired', 'Currently Airing') + AND NOT EXISTS ( + SELECT 1 FROM watch_list_entry we + WHERE we.user_id = sqlc.arg('user_id') AND we.anime_id = related.id + ) ORDER BY related.id DESC; diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 5401333..c9b143f 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -114,13 +114,25 @@ func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) { } const getAnimeNeedingRelationSync = `-- name: GetAnimeNeedingRelationSync :many -SELECT a.id, a.title_original -FROM watch_list_entry w -JOIN anime a ON w.anime_id = a.id -WHERE w.status IN ('completed', 'watching') - AND (a.relations_synced_at IS NULL OR a.relations_synced_at < datetime('now', '-7 days')) -GROUP BY a.id -ORDER BY MAX(w.updated_at) DESC +WITH RECURSIVE sequel_chain AS ( + SELECT a.id, a.title_original, a.relations_synced_at, w.updated_at as base_updated_at, 0 as depth + FROM watch_list_entry w + JOIN anime a ON w.anime_id = a.id + WHERE w.status IN ('completed', 'watching') + + UNION + + SELECT a.id, a.title_original, a.relations_synced_at, sc.base_updated_at, sc.depth + 1 + FROM sequel_chain sc + JOIN anime_relation r ON sc.id = r.anime_id AND r.relation_type = 'Sequel' + JOIN anime a ON r.related_anime_id = a.id + WHERE sc.depth < 10 +) +SELECT id, title_original +FROM sequel_chain +WHERE relations_synced_at IS NULL OR relations_synced_at < datetime('now', '-7 days') +GROUP BY id, title_original +ORDER BY MAX(base_updated_at) DESC, MIN(depth) ASC LIMIT 50 ` @@ -169,17 +181,40 @@ func (q *Queries) GetSession(ctx context.Context, id string) (Session, error) { } const getUpcomingSeasons = `-- name: GetUpcomingSeasons :many +WITH RECURSIVE sequel_chain AS ( + SELECT + w.anime_id as root_id, + a.title_original as root_title, + r.related_anime_id as current_id, + 1 as depth + FROM watch_list_entry w + JOIN anime a ON w.anime_id = a.id + JOIN anime_relation r ON w.anime_id = r.anime_id + WHERE w.user_id = ?1 + AND w.status IN ('completed', 'watching') + AND r.relation_type = 'Sequel' + + UNION + + SELECT + sc.root_id, + sc.root_title, + r.related_anime_id, + sc.depth + 1 + FROM sequel_chain sc + JOIN anime_relation r ON sc.current_id = r.anime_id + WHERE r.relation_type = 'Sequel' AND sc.depth < 10 +) SELECT DISTINCT related.id, related.title_original, related.image_url, related.created_at, related.title_english, related.title_japanese, related.airing, related.status, related.relations_synced_at, - a.title_original AS prequel_title -FROM watch_list_entry w -JOIN anime_relation r ON w.anime_id = r.anime_id -JOIN anime a ON w.anime_id = a.id -JOIN anime related ON r.related_anime_id = related.id -WHERE w.user_id = ? - AND w.status IN ('completed', 'watching') - AND r.relation_type = 'Sequel' - AND related.status IN ('Not yet aired', 'Currently Airing') + sc.root_title AS prequel_title +FROM sequel_chain sc +JOIN anime related ON sc.current_id = related.id +WHERE related.status IN ('Not yet aired', 'Currently Airing') + AND NOT EXISTS ( + SELECT 1 FROM watch_list_entry we + WHERE we.user_id = sqlc.arg('user_id') AND we.anime_id = related.id + ) ORDER BY related.id DESC ` diff --git a/internal/features/anime/handler.go b/internal/features/anime/handler.go index 9af903c..a494c6d 100644 --- a/internal/features/anime/handler.go +++ b/internal/features/anime/handler.go @@ -328,6 +328,20 @@ func (h *Handler) HandleNotifications(w http.ResponseWriter, r *http.Request) { return } + templates.Notifications(watching).Render(r.Context(), w) +} + +func (h *Handler) HandleNotificationsUpcoming(w http.ResponseWriter, r *http.Request) { + userID := "" + if user, ok := r.Context().Value(middleware.UserContextKey).(*database.User); ok && user != nil { + userID = user.ID + } + + if userID == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + upcomingSeasons, err := h.svc.GetUpcomingSeasons(r.Context(), userID) if err != nil { log.Printf("upcoming seasons error: %v", err) @@ -335,5 +349,5 @@ func (h *Handler) HandleNotifications(w http.ResponseWriter, r *http.Request) { return } - templates.Notifications(watching, upcomingSeasons).Render(r.Context(), w) + templates.UpcomingSeasonsList(upcomingSeasons).Render(r.Context(), w) } diff --git a/internal/server/routes.go b/internal/server/routes.go index 2265e4b..15e8ebb 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -37,6 +37,7 @@ func NewRouter(cfg Config) http.Handler { mux.HandleFunc("/discover", animeHandler.HandleDiscover) mux.HandleFunc("/schedule", animeHandler.HandleSchedule) mux.HandleFunc("/notifications", animeHandler.HandleNotifications) + mux.HandleFunc("/notifications/upcoming", animeHandler.HandleNotificationsUpcoming) mux.HandleFunc("/api/schedule", animeHandler.HandleAPISchedule) mux.HandleFunc("/api/discover/airing", animeHandler.HandleAPIDiscoverAiring) mux.HandleFunc("/api/discover/upcoming", animeHandler.HandleAPIDiscoverUpcoming) diff --git a/internal/templates/notifications.templ b/internal/templates/notifications.templ index 601b913..d5d485f 100644 --- a/internal/templates/notifications.templ +++ b/internal/templates/notifications.templ @@ -9,7 +9,7 @@ type WatchingAnimeWithDetails struct { Anime jikan.Anime } -templ Notifications(watching []WatchingAnimeWithDetails, upcomingSeasons []database.GetUpcomingSeasonsRow) { +templ Notifications(watching []WatchingAnimeWithDetails) { @Layout("mal - notifications") {

upcoming episodes

@@ -31,17 +31,26 @@ templ Notifications(watching []WatchingAnimeWithDetails, upcomingSeasons []datab

upcoming seasons

because you've watched prequels

- if len(upcomingSeasons) == 0 { -
-

no upcoming seasons for anime you've watched

-

as you watch more shows, new seasons will appear here

-
- } else { -
- for _, item := range upcomingSeasons { - @UpcomingSeasonCard(item) - } +
+
+
+ syncing sequel graphs...
+
+
+ } +} + +templ UpcomingSeasonsList(upcomingSeasons []database.GetUpcomingSeasonsRow) { + if len(upcomingSeasons) == 0 { +
+

no upcoming seasons for anime you've watched

+

as you watch more shows, new seasons will appear here

+
+ } else { +
+ for _, item := range upcomingSeasons { + @UpcomingSeasonCard(item) }
} diff --git a/internal/templates/notifications_templ.go b/internal/templates/notifications_templ.go index e762af3..fbc8de2 100644 --- a/internal/templates/notifications_templ.go +++ b/internal/templates/notifications_templ.go @@ -17,7 +17,7 @@ type WatchingAnimeWithDetails struct { Anime jikan.Anime } -func Notifications(watching []WatchingAnimeWithDetails, upcomingSeasons []database.GetUpcomingSeasonsRow) templ.Component { +func Notifications(watching []WatchingAnimeWithDetails) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -75,32 +75,7 @@ func Notifications(watching []WatchingAnimeWithDetails, upcomingSeasons []databa return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

upcoming seasons

because you've watched prequels

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(upcomingSeasons) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

no upcoming seasons for anime you've watched

as you watch more shows, new seasons will appear here

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, item := range upcomingSeasons { - templ_7745c5c3_Err = UpcomingSeasonCard(item).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

upcoming seasons

because you've watched prequels

syncing sequel graphs...
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -114,6 +89,52 @@ func Notifications(watching []WatchingAnimeWithDetails, upcomingSeasons []databa }) } +func UpcomingSeasonsList(upcomingSeasons []database.GetUpcomingSeasonsRow) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if len(upcomingSeasons) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

no upcoming seasons for anime you've watched

as you watch more shows, new seasons will appear here

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range upcomingSeasons { + templ_7745c5c3_Err = UpcomingSeasonCard(item).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + func UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -130,92 +151,92 @@ func UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var3 := templ.GetChildren(ctx) - if templ_7745c5c3_Var3 == nil { - templ_7745c5c3_Var3 = templ.NopComponent + templ_7745c5c3_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" class=\"notification-card\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.ImageUrl != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"")") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" alt=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(displaySeasonTitle(item)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 63, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" loading=\"lazy\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
no image
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
no image
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(displaySeasonTitle(item)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 61, Col: 30} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
because you watched ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.PrequelTitle) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(displaySeasonTitle(item)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 64, Col: 125} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 70, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
because you watched ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.PrequelTitle) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 73, Col: 125} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -249,151 +270,151 @@ func NotificationCard(item WatchingAnimeWithDetails) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" class=\"notification-card\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.Entry.ImageUrl != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"")") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" alt=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(item.Entry)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 93, Col: 67} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" loading=\"lazy\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
no image
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
no image
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(item.Entry)) + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(item.Entry)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 91, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 100, Col: 30} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.Anime.Broadcast.String != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(item.Anime.Broadcast.String) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(item.Anime.Broadcast.String) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 95, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 104, Col: 71} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if item.Anime.Episodes > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.Entry.CurrentEpisode.Valid { - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d / %d eps", item.Entry.CurrentEpisode.Int64, item.Anime.Episodes)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 100, Col: 89} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("0 / %d eps", item.Anime.Episodes)) + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d / %d eps", item.Entry.CurrentEpisode.Int64, item.Anime.Episodes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 102, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 109, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + } else { + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("0 / %d eps", item.Anime.Episodes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 111, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else if item.Entry.CurrentEpisode.Valid && item.Entry.CurrentEpisode.Int64 > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d eps watched", item.Entry.CurrentEpisode.Int64)) + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d eps watched", item.Entry.CurrentEpisode.Int64)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 107, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/notifications.templ`, Line: 116, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/worker/relations.go b/internal/worker/relations.go index f32e342..acc3b72 100644 --- a/internal/worker/relations.go +++ b/internal/worker/relations.go @@ -24,9 +24,12 @@ func New(db *database.Queries, client *jikan.Client) *Worker { func (w *Worker) Start(ctx context.Context) { log.Println("Starting relations sync worker...") - ticker := time.NewTicker(2 * time.Minute) + ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() + // Run once immediately + w.syncRelations(ctx) + for { select { case <-ctx.Done(): @@ -38,7 +41,7 @@ func (w *Worker) Start(ctx context.Context) { } func (w *Worker) syncRelations(ctx context.Context) { - // Find up to 50 anime that need their relations synced + // Find up to 20 anime that need their relations synced animes, err := w.db.GetAnimeNeedingRelationSync(ctx) if err != nil { log.Printf("worker: failed to get anime needing sync: %v", err)