diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 4bb2570..aae3b43 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -118,6 +118,20 @@ func FileServer(r chi.Router, path string, root http.FileSystem) { r.Get(path, func(w http.ResponseWriter, r *http.Request) { rctx := chi.RouteContext(r.Context()) pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*") + reqPath := strings.TrimPrefix(r.URL.Path, pathPrefix) + if reqPath == "" { + reqPath = "/" + } + + // Check if file exists in the static directory + f, err := root.Open(reqPath) + if err == nil { + f.Close() + } else { + // If not found, rewrite path to serve index.html for SPA routing + r.URL.Path = pathPrefix + "/index.html" + } + fs := http.StripPrefix(pathPrefix, http.FileServer(root)) fs.ServeHTTP(w, r) }) diff --git a/backend/internal/scraper/phimmoichill.go b/backend/internal/scraper/phimmoichill.go index 2626821..ddceb70 100644 --- a/backend/internal/scraper/phimmoichill.go +++ b/backend/internal/scraper/phimmoichill.go @@ -236,35 +236,36 @@ func (s *PhimMoiChillScraper) GetMovieDetail(slug string) (*models.RophimMovie, } }) - // Fallback: If no episodes found, finding "Xem phim" button for single movie - if len(episodes) == 0 { - // Common selectors for "Watch" button: .btn-watch, a:contains("Xem phim") - watchBtn := doc.Find("a.btn-watch, a.btn-see, ul.btn-block a") - if watchBtn.Length() > 0 { - href, _ := watchBtn.Attr("href") - if strings.Contains(href, "/xem/") { - episodes = append(episodes, models.Episode{ - Number: 1, - Title: "Full", - URL: href, - ServerName: "PhimMoiChill", - }) - } - } else { - // Try text content - doc.Find("a").Each(func(i int, s *goquery.Selection) { - if strings.Contains(strings.ToLower(s.Text()), "xem phim") { - href, _ := s.Attr("href") - if strings.Contains(href, "/xem/") { - episodes = append(episodes, models.Episode{ - Number: 1, - Title: "Full", - URL: href, - ServerName: "PhimMoiChill", - }) - return // Break - } + // Fallback/Main: Find "Xem phim" button which is often Episode 1 for series, + // or the only episode for movies. We always check this, not just when len(episodes)==0. + watchBtn := doc.Find("a.btn-watch, a.btn-see, ul.btn-block a") + var watchHref string + if watchBtn.Length() > 0 { + watchHref, _ = watchBtn.Attr("href") + } else { + doc.Find("a").Each(func(i int, s *goquery.Selection) { + if strings.Contains(strings.ToLower(s.Text()), "xem phim") { + href, _ := s.Attr("href") + if strings.Contains(href, "/xem/") { + watchHref = href } + } + }) + } + + if watchHref != "" && strings.Contains(watchHref, "/xem/") { + // Only add if Episode 1 is not already present + if _, exists := epMap[1]; !exists { + epMap[1] = len(episodes) + title := "Tập 1" + if len(episodes) == 0 { + title = "Full" + } + episodes = append(episodes, models.Episode{ + Number: 1, + Title: title, + URL: watchHref, + ServerName: "PhimMoiChill", }) } } diff --git a/backend/test_movie.go b/backend/test_movie.go new file mode 100644 index 0000000..89d1985 --- /dev/null +++ b/backend/test_movie.go @@ -0,0 +1,19 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "streamflow-backend/internal/scraper" +) + +func main() { + p := scraper.NewPhimMoiChillScraper() + movie, err := p.GetMovieDetail("vu-tru-cua-doi-ta-pm17193") + if err != nil { + log.Fatal(err) + } + + b, _ := json.MarshalIndent(movie, "", " ") + fmt.Println(string(b)) +} diff --git a/backend/test_search.go b/backend/test_search.go new file mode 100644 index 0000000..89802a0 --- /dev/null +++ b/backend/test_search.go @@ -0,0 +1,19 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "streamflow-backend/internal/scraper" +) + +func main() { + p := scraper.NewPhimMoiChillScraper() + movies, err := p.Search("vũ trụ của đôi ta", 1) + if err != nil { + log.Fatal(err) + } + + b, _ := json.MarshalIndent(movies, "", " ") + fmt.Println(string(b)) +} diff --git a/frontend-react/src/components/HomeContent.tsx b/frontend-react/src/components/HomeContent.tsx index 58729c0..232077b 100644 --- a/frontend-react/src/components/HomeContent.tsx +++ b/frontend-react/src/components/HomeContent.tsx @@ -68,7 +68,13 @@ export const HomeContent = ({ topPadding = "pt-24" }: HomeContentProps) => { if (!data || data.length === 0) { setHasMore(false); } else { - setMovies(prev => page === 1 ? data : [...prev, ...data]); + setMovies(prev => { + if (page === 1) return data; + // Deduplicate arrays when appending to prevent React StrictMode or fast-scroll double fetches + const existingIds = new Set(prev.map(m => m.id)); + const newUniqueMovies = data.filter((m: Movie) => !existingIds.has(m.id)); + return [...prev, ...newUniqueMovies]; + }); } } catch { console.error("Failed to fetch movies");