fix: implement watch progress and completion endpoints
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package playback
|
package playback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -227,11 +228,94 @@ func (h *Handler) HandleProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := middleware.GetUser(r.Context())
|
||||||
|
if user == nil {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
MalID int64 `json:"mal_id"`
|
||||||
|
Episode int `json:"episode"`
|
||||||
|
TimeSeconds float64 `json:"time_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fetch the anime info to seed the DB if it's the first time saving progress for this show
|
||||||
|
anime, err := h.jikanClient.GetAnimeByID(r.Context(), int(req.MalID))
|
||||||
|
var seed *database.UpsertAnimeParams
|
||||||
|
if err == nil {
|
||||||
|
seed = &database.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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.SaveProgress(r.Context(), user.ID, req.MalID, req.Episode, req.TimeSeconds, seed); err != nil {
|
||||||
|
log.Printf("failed to save progress: %v", err)
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleCompleteAnime(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleCompleteAnime(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := middleware.GetUser(r.Context())
|
||||||
|
if user == nil {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
MalID int64 `json:"mal_id"`
|
||||||
|
Episode int `json:"episode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed anime info if needed
|
||||||
|
anime, err := h.jikanClient.GetAnimeByID(r.Context(), int(req.MalID))
|
||||||
|
var seed *database.UpsertAnimeParams
|
||||||
|
if err == nil {
|
||||||
|
seed = &database.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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.CompleteAnime(r.Context(), user.ID, req.MalID, req.Episode, seed); err != nil {
|
||||||
|
log.Printf("failed to complete anime: %v", err)
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleEpisodeData(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleEpisodeData(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "video_player"}}
|
{{define "video_player"}}
|
||||||
<div data-video-player
|
<div data-video-player
|
||||||
data-anime-id="{{.WatchData.MalID}}"
|
data-mal-id="{{.WatchData.MalID}}"
|
||||||
data-current-episode="{{.WatchData.CurrentEpisode}}"
|
data-current-episode="{{.WatchData.CurrentEpisode}}"
|
||||||
data-total-episodes="{{.TotalEpisodes}}"
|
data-total-episodes="{{.TotalEpisodes}}"
|
||||||
data-initial-mode="{{.WatchData.InitialMode}}"
|
data-initial-mode="{{.WatchData.InitialMode}}"
|
||||||
@@ -9,7 +9,10 @@
|
|||||||
data-mode-sources='{{json .WatchData.ModeSources}}'
|
data-mode-sources='{{json .WatchData.ModeSources}}'
|
||||||
data-available-modes='{{json .WatchData.AvailableModes}}'
|
data-available-modes='{{json .WatchData.AvailableModes}}'
|
||||||
data-segments='{{json .WatchData.Segments}}'
|
data-segments='{{json .WatchData.Segments}}'
|
||||||
|
data-anime-title-english="{{.WatchData.Title}}"
|
||||||
|
data-anime-image="{{.WatchData.MalID}}"
|
||||||
class="group relative aspect-video w-full overflow-hidden bg-black">
|
class="group relative aspect-video w-full overflow-hidden bg-black">
|
||||||
|
|
||||||
|
|
||||||
<video class="h-full w-full cursor-pointer" preload="metadata" playsinline autoplay></video>
|
<video class="h-full w-full cursor-pointer" preload="metadata" playsinline autoplay></video>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user