feat: migrate watchlist module to modular domain pattern
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"mal/internal/database"
|
||||
"mal/internal/auth"
|
||||
"mal/internal/anime"
|
||||
"mal/internal/watchlist"
|
||||
"mal/internal/server"
|
||||
"mal/internal/templates"
|
||||
|
||||
@@ -18,6 +19,7 @@ func NewApp() *fx.App {
|
||||
jikan.Module,
|
||||
auth.Module,
|
||||
anime.Module,
|
||||
watchlist.Module,
|
||||
templates.Module,
|
||||
server.Module,
|
||||
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