feat: migrate watchlist module to modular domain pattern
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"mal/internal/database"
|
"mal/internal/database"
|
||||||
"mal/internal/auth"
|
"mal/internal/auth"
|
||||||
"mal/internal/anime"
|
"mal/internal/anime"
|
||||||
|
"mal/internal/watchlist"
|
||||||
"mal/internal/server"
|
"mal/internal/server"
|
||||||
"mal/internal/templates"
|
"mal/internal/templates"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ func NewApp() *fx.App {
|
|||||||
jikan.Module,
|
jikan.Module,
|
||||||
auth.Module,
|
auth.Module,
|
||||||
anime.Module,
|
anime.Module,
|
||||||
|
watchlist.Module,
|
||||||
templates.Module,
|
templates.Module,
|
||||||
server.Module,
|
server.Module,
|
||||||
fx.Decorate(func(r *templates.Renderer) render.HTMLRender {
|
fx.Decorate(func(r *templates.Renderer) render.HTMLRender {
|
||||||
|
|||||||
26
internal/domain/watchlist.go
Normal file
26
internal/domain/watchlist.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"mal/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WatchlistEntry = db.WatchListEntry
|
||||||
|
type UserWatchListRow = db.GetUserWatchListRow
|
||||||
|
|
||||||
|
type WatchlistService interface {
|
||||||
|
UpdateEntry(ctx context.Context, userID string, animeID int64, status string) error
|
||||||
|
RemoveEntry(ctx context.Context, userID string, animeID int64) error
|
||||||
|
GetWatchlist(ctx context.Context, userID string) ([]UserWatchListRow, error)
|
||||||
|
DeleteContinueWatching(ctx context.Context, userID string, animeID int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type WatchlistRepository interface {
|
||||||
|
UpsertAnime(ctx context.Context, arg db.UpsertAnimeParams) (db.Anime, error)
|
||||||
|
GetAnime(ctx context.Context, id int64) (db.Anime, error)
|
||||||
|
UpsertWatchListEntry(ctx context.Context, arg db.UpsertWatchListEntryParams) (db.WatchListEntry, error)
|
||||||
|
DeleteWatchListEntry(ctx context.Context, arg db.DeleteWatchListEntryParams) error
|
||||||
|
GetUserWatchList(ctx context.Context, userID string) ([]db.GetUserWatchListRow, error)
|
||||||
|
DeleteContinueWatchingEntry(ctx context.Context, arg db.DeleteContinueWatchingEntryParams) error
|
||||||
|
SaveWatchProgress(ctx context.Context, arg db.SaveWatchProgressParams) error
|
||||||
|
}
|
||||||
95
internal/watchlist/handler/handler.go
Normal file
95
internal/watchlist/handler/handler.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mal/internal/domain"
|
||||||
|
"mal/internal/server"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WatchlistHandler struct {
|
||||||
|
svc domain.WatchlistService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWatchlistHandler(svc domain.WatchlistService) *WatchlistHandler {
|
||||||
|
return &WatchlistHandler{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WatchlistHandler) Register(r *gin.Engine) {
|
||||||
|
r.POST("/api/watchlist", h.HandleUpdateWatchlist)
|
||||||
|
r.DELETE("/api/watchlist/:id", h.HandleDeleteWatchlist)
|
||||||
|
r.DELETE("/api/continue-watching/:id", h.HandleDeleteContinueWatching)
|
||||||
|
r.GET("/watchlist", h.HandleGetWatchlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WatchlistHandler) HandleUpdateWatchlist(c *gin.Context) {
|
||||||
|
userID := "" // TODO: get from auth context
|
||||||
|
animeID, _ := strconv.ParseInt(c.PostForm("anime_id"), 10, 64)
|
||||||
|
status := c.PostForm("status")
|
||||||
|
|
||||||
|
if animeID <= 0 || status == "" {
|
||||||
|
c.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.svc.UpdateEntry(c.Request.Context(), userID, animeID, status)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WatchlistHandler) HandleDeleteWatchlist(c *gin.Context) {
|
||||||
|
userID := "" // TODO: get from auth context
|
||||||
|
animeID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
|
||||||
|
if animeID <= 0 {
|
||||||
|
c.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.svc.RemoveEntry(c.Request.Context(), userID, animeID)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WatchlistHandler) HandleDeleteContinueWatching(c *gin.Context) {
|
||||||
|
userID := "" // TODO: get from auth context
|
||||||
|
animeID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
|
||||||
|
if animeID <= 0 {
|
||||||
|
c.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.svc.DeleteContinueWatching(c.Request.Context(), userID, animeID)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WatchlistHandler) HandleGetWatchlist(c *gin.Context) {
|
||||||
|
userID := "" // TODO: get from auth context
|
||||||
|
entries, err := h.svc.GetWatchlist(c.Request.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "watchlist.gohtml", gin.H{
|
||||||
|
"Entries": entries,
|
||||||
|
"CurrentPath": "/watchlist",
|
||||||
|
})
|
||||||
|
}
|
||||||
23
internal/watchlist/module.go
Normal file
23
internal/watchlist/module.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package watchlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mal/internal/server"
|
||||||
|
"mal/internal/watchlist/handler"
|
||||||
|
"mal/internal/watchlist/repository"
|
||||||
|
"mal/internal/watchlist/service"
|
||||||
|
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
repository.NewWatchlistRepository,
|
||||||
|
service.NewWatchlistService,
|
||||||
|
handler.NewWatchlistHandler,
|
||||||
|
),
|
||||||
|
fx.Provide(
|
||||||
|
server.AsRouteRegister(func(h *handler.WatchlistHandler) server.RouteRegister {
|
||||||
|
return h
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
43
internal/watchlist/repository/repository.go
Normal file
43
internal/watchlist/repository/repository.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"mal/internal/db"
|
||||||
|
"mal/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type watchlistRepository struct {
|
||||||
|
queries *db.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWatchlistRepository(queries *db.Queries) domain.WatchlistRepository {
|
||||||
|
return &watchlistRepository{queries: queries}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) UpsertAnime(ctx context.Context, arg db.UpsertAnimeParams) (db.Anime, error) {
|
||||||
|
return r.queries.UpsertAnime(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) GetAnime(ctx context.Context, id int64) (db.Anime, error) {
|
||||||
|
return r.queries.GetAnime(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) UpsertWatchListEntry(ctx context.Context, arg db.UpsertWatchListEntryParams) (db.WatchListEntry, error) {
|
||||||
|
return r.queries.UpsertWatchListEntry(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) DeleteWatchListEntry(ctx context.Context, arg db.DeleteWatchListEntryParams) error {
|
||||||
|
return r.queries.DeleteWatchListEntry(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) GetUserWatchList(ctx context.Context, userID string) ([]db.GetUserWatchListRow, error) {
|
||||||
|
return r.queries.GetUserWatchList(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) DeleteContinueWatchingEntry(ctx context.Context, arg db.DeleteContinueWatchingEntryParams) error {
|
||||||
|
return r.queries.DeleteContinueWatchingEntry(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *watchlistRepository) SaveWatchProgress(ctx context.Context, arg db.SaveWatchProgressParams) error {
|
||||||
|
return r.queries.SaveWatchProgress(ctx, arg)
|
||||||
|
}
|
||||||
67
internal/watchlist/service/service.go
Normal file
67
internal/watchlist/service/service.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"mal/integrations/jikan"
|
||||||
|
"mal/internal/db"
|
||||||
|
"mal/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type watchlistService struct {
|
||||||
|
repo domain.WatchlistRepository
|
||||||
|
jikan *jikan.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWatchlistService(repo domain.WatchlistRepository, jikan *jikan.Client) domain.WatchlistService {
|
||||||
|
return &watchlistService{repo: repo, jikan: jikan}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchlistService) UpdateEntry(ctx context.Context, userID string, animeID int64, status string) error {
|
||||||
|
_, err := s.repo.GetAnime(ctx, animeID)
|
||||||
|
if err != nil {
|
||||||
|
anime, err := s.jikan.GetAnimeByID(ctx, int(animeID))
|
||||||
|
if err == nil {
|
||||||
|
_, _ = s.repo.UpsertAnime(ctx, db.UpsertAnimeParams{
|
||||||
|
ID: int64(anime.MalID),
|
||||||
|
TitleOriginal: anime.Title,
|
||||||
|
TitleEnglish: sql.NullString{String: anime.TitleEnglish, Valid: anime.TitleEnglish != ""},
|
||||||
|
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
|
||||||
|
ImageUrl: anime.ImageURL(),
|
||||||
|
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.repo.UpsertWatchListEntry(ctx, db.UpsertWatchListEntryParams{
|
||||||
|
UserID: userID,
|
||||||
|
AnimeID: animeID,
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchlistService) RemoveEntry(ctx context.Context, userID string, animeID int64) error {
|
||||||
|
return s.repo.DeleteWatchListEntry(ctx, db.DeleteWatchListEntryParams{
|
||||||
|
UserID: userID,
|
||||||
|
AnimeID: animeID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchlistService) GetWatchlist(ctx context.Context, userID string) ([]domain.UserWatchListRow, error) {
|
||||||
|
return s.repo.GetUserWatchList(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchlistService) DeleteContinueWatching(ctx context.Context, userID string, animeID int64) error {
|
||||||
|
_ = s.repo.DeleteContinueWatchingEntry(ctx, db.DeleteContinueWatchingEntryParams{
|
||||||
|
UserID: userID,
|
||||||
|
AnimeID: animeID,
|
||||||
|
})
|
||||||
|
return s.repo.SaveWatchProgress(ctx, db.SaveWatchProgressParams{
|
||||||
|
UserID: userID,
|
||||||
|
AnimeID: animeID,
|
||||||
|
CurrentEpisode: sql.NullInt64{Valid: false},
|
||||||
|
CurrentTimeSeconds: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user