Download REST API endpoints (#72)
* backend and frontend hotfixes, see message Improved rendering on the frontend by cutting unecessary useStates. Backend side, downloads now auto resume even on application kill. * download rest api endpoints, general code refactor * download request json mappings
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package cli
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// FG
|
||||
Red = "\033[31m"
|
||||
@@ -12,12 +10,8 @@ const (
|
||||
Cyan = "\033[36m"
|
||||
Reset = "\033[0m"
|
||||
// BG
|
||||
BgRed = "\033[1;41m"
|
||||
BgBlue = "\033[1;44m"
|
||||
BgGreen = "\033[1;42m"
|
||||
BgRed = "\033[1;41m"
|
||||
BgBlue = "\033[1;44m"
|
||||
BgGreen = "\033[1;42m"
|
||||
BgMagenta = "\033[1;45m"
|
||||
)
|
||||
|
||||
// Formats a message with the specified ascii escape code, then reset.
|
||||
func Format(message string, code string) string {
|
||||
return fmt.Sprintf("%s%s%s", code, message, Reset)
|
||||
}
|
||||
|
||||
157
server/handlers/archive.go
Normal file
157
server/handlers/archive.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TOKEN_COOKIE_NAME = "jwt"
|
||||
)
|
||||
|
||||
type DirectoryEntry struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
SHASum string `json:"shaSum"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
IsVideo bool `json:"isVideo"`
|
||||
IsDirectory bool `json:"isDirectory"`
|
||||
}
|
||||
|
||||
func walkDir(root string) (*[]DirectoryEntry, error) {
|
||||
files := []DirectoryEntry{}
|
||||
|
||||
dirs, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if !utils.IsValidEntry(d) {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(root, d.Name())
|
||||
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = append(files, DirectoryEntry{
|
||||
Path: path,
|
||||
Name: d.Name(),
|
||||
Size: info.Size(),
|
||||
SHASum: utils.ShaSumString(path),
|
||||
IsVideo: utils.IsVideo(d),
|
||||
IsDirectory: d.IsDir(),
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
}
|
||||
|
||||
return &files, err
|
||||
}
|
||||
|
||||
type ListRequest struct {
|
||||
SubDir string `json:"subdir"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
}
|
||||
|
||||
func ListDownloaded(w http.ResponseWriter, r *http.Request) {
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
req := new(ListRequest)
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := walkDir(filepath.Join(root, req.SubDir))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.OrderBy == "modtime" {
|
||||
sort.SliceStable(*files, func(i, j int) bool {
|
||||
return (*files)[i].ModTime.After((*files)[j].ModTime)
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(files)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteRequest = DirectoryEntry
|
||||
|
||||
func DeleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(DeleteRequest)
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sum := utils.ShaSumString(req.Path)
|
||||
if sum != req.SHASum {
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(req.Path)
|
||||
if err != nil {
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode("ok")
|
||||
}
|
||||
|
||||
func SendFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := chi.URLParam(r, "id")
|
||||
|
||||
if path == "" {
|
||||
http.Error(w, "inexistent path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decodedStr := string(decoded)
|
||||
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
|
||||
// TODO: further path / file validations
|
||||
if strings.Contains(filepath.Dir(decodedStr), root) {
|
||||
// ctx.Response().Header.Set(
|
||||
// "Content-Disposition",
|
||||
// "inline; filename="+filepath.Base(decodedStr),
|
||||
// )
|
||||
|
||||
http.ServeFile(w, r, decodedStr)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
65
server/handlers/login.go
Normal file
65
server/handlers/login.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(LoginRequest)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Instance().GetConfig().RPCSecret != req.Secret {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expiresAt := time.Now().Add(time.Hour * 24 * 30)
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"expiresAt": expiresAt,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: expiresAt, // 30 days
|
||||
Value: tokenString,
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: time.Now(),
|
||||
Value: "",
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
@@ -66,8 +66,8 @@ type AbortRequest struct {
|
||||
// struct representing the intent to start a download
|
||||
type DownloadRequest struct {
|
||||
Id string
|
||||
URL string
|
||||
Path string
|
||||
Rename string
|
||||
Params []string
|
||||
URL string `json:"url"`
|
||||
Path string `json:"path"`
|
||||
Rename string `json:"rename"`
|
||||
Params []string `json:"params"`
|
||||
}
|
||||
|
||||
@@ -125,12 +125,18 @@ func (m *MemoryDB) Restore() {
|
||||
}
|
||||
|
||||
for _, proc := range session.Processes {
|
||||
m.table.Store(proc.Id, &Process{
|
||||
restored := &Process{
|
||||
Id: proc.Id,
|
||||
Url: proc.Info.URL,
|
||||
Info: proc.Info,
|
||||
Progress: proc.Progress,
|
||||
})
|
||||
}
|
||||
|
||||
m.table.Store(proc.Id, restored)
|
||||
|
||||
if restored.Progress.Percentage != "-1" {
|
||||
go restored.Start()
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(cli.BgGreen, "Successfully restored session", cli.Reset)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/marcopeocchi/fazzoletti/slices"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/cli"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
)
|
||||
|
||||
@@ -136,8 +137,11 @@ func (p *Process) Start() {
|
||||
Speed: stdout.Speed,
|
||||
ETA: stdout.Eta,
|
||||
}
|
||||
shortId := strings.Split(p.Id, "-")[0]
|
||||
log.Printf("[%s] %s %s\n", shortId, p.Url, p.Progress.Percentage)
|
||||
log.Println(
|
||||
cli.BgGreen, "DL", cli.Reset,
|
||||
cli.BgBlue, p.getShortId(), cli.Reset,
|
||||
p.Url, stdout.Percentage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -156,6 +160,14 @@ func (p *Process) Complete() {
|
||||
Speed: 0,
|
||||
ETA: 0,
|
||||
}
|
||||
|
||||
shortId := p.getShortId()
|
||||
|
||||
log.Println(
|
||||
cli.BgMagenta, "FINISH", cli.Reset,
|
||||
cli.BgBlue, shortId, cli.Reset,
|
||||
p.Url,
|
||||
)
|
||||
}
|
||||
|
||||
// Kill a process and remove it from the memory
|
||||
@@ -202,6 +214,12 @@ func (p *Process) GetFormatsSync() (DownloadFormats, error) {
|
||||
return DownloadFormats{}, err
|
||||
}
|
||||
|
||||
log.Println(
|
||||
cli.BgRed, "Metadata", cli.Reset,
|
||||
cli.BgBlue, p.getShortId(), cli.Reset,
|
||||
p.Url,
|
||||
)
|
||||
|
||||
go func() {
|
||||
decodingError = json.NewDecoder(stdout).Decode(&info)
|
||||
wg.Done()
|
||||
@@ -248,6 +266,12 @@ func (p *Process) SetMetadata() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println(
|
||||
cli.BgRed, "Metadata", cli.Reset,
|
||||
cli.BgBlue, p.getShortId(), cli.Reset,
|
||||
p.Url,
|
||||
)
|
||||
|
||||
err = json.NewDecoder(stdout).Decode(&info)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -260,3 +284,7 @@ func (p *Process) SetMetadata() error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Process) getShortId() string {
|
||||
return strings.Split(p.Id, "-")[0]
|
||||
}
|
||||
|
||||
25
server/rest/container.go
Normal file
25
server/rest/container.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
||||
)
|
||||
|
||||
func Container(db *internal.MemoryDB, mq *internal.MessageQueue) *Handler {
|
||||
var (
|
||||
service = ProvideService(db, mq)
|
||||
handler = ProvideHandler(service)
|
||||
)
|
||||
return handler
|
||||
}
|
||||
|
||||
func ApplyRouter(db *internal.MemoryDB, mq *internal.MessageQueue) func(chi.Router) {
|
||||
h := Container(db, mq)
|
||||
|
||||
return func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Post("/exec", h.Exec())
|
||||
r.Get("/running", h.Running())
|
||||
}
|
||||
}
|
||||
@@ -1,212 +1,56 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
TOKEN_COOKIE_NAME = "jwt"
|
||||
)
|
||||
|
||||
type DirectoryEntry struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
SHASum string `json:"shaSum"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
IsVideo bool `json:"isVideo"`
|
||||
IsDirectory bool `json:"isDirectory"`
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func walkDir(root string) (*[]DirectoryEntry, error) {
|
||||
files := []DirectoryEntry{}
|
||||
func (h *Handler) Exec() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
dirs, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
for _, d := range dirs {
|
||||
if !utils.IsValidEntry(d) {
|
||||
continue
|
||||
req := internal.DownloadRequest{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
path := filepath.Join(root, d.Name())
|
||||
|
||||
info, err := d.Info()
|
||||
id, err := h.service.Exec(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
files = append(files, DirectoryEntry{
|
||||
Path: path,
|
||||
Name: d.Name(),
|
||||
Size: info.Size(),
|
||||
SHASum: utils.ShaSumString(path),
|
||||
IsVideo: utils.IsVideo(d),
|
||||
IsDirectory: d.IsDir(),
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
}
|
||||
|
||||
return &files, err
|
||||
}
|
||||
|
||||
type ListRequest struct {
|
||||
SubDir string `json:"subdir"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
}
|
||||
|
||||
func ListDownloaded(w http.ResponseWriter, r *http.Request) {
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
req := new(ListRequest)
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := walkDir(filepath.Join(root, req.SubDir))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.OrderBy == "modtime" {
|
||||
sort.SliceStable(*files, func(i, j int) bool {
|
||||
return (*files)[i].ModTime.After((*files)[j].ModTime)
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(files)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
err = json.NewEncoder(w).Encode(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteRequest = DirectoryEntry
|
||||
func (h *Handler) Running() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
func DeleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(DeleteRequest)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
res, err := h.service.Running(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
sum := utils.ShaSumString(req.Path)
|
||||
if sum != req.SHASum {
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(req.Path)
|
||||
if err != nil {
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode("ok")
|
||||
}
|
||||
|
||||
func SendFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := chi.URLParam(r, "id")
|
||||
|
||||
if path == "" {
|
||||
http.Error(w, "inexistent path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decodedStr := string(decoded)
|
||||
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
|
||||
// TODO: further path / file validations
|
||||
if strings.Contains(filepath.Dir(decodedStr), root) {
|
||||
// ctx.Response().Header.Set(
|
||||
// "Content-Disposition",
|
||||
// "inline; filename="+filepath.Base(decodedStr),
|
||||
// )
|
||||
|
||||
http.ServeFile(w, r, decodedStr)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(LoginRequest)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Instance().GetConfig().RPCSecret != req.Secret {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expiresAt := time.Now().Add(time.Hour * 24 * 30)
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"expiresAt": expiresAt,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: expiresAt, // 30 days
|
||||
Value: tokenString,
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: time.Now(),
|
||||
Value: "",
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
34
server/rest/provider.go
Normal file
34
server/rest/provider.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
service *Service
|
||||
handler *Handler
|
||||
|
||||
serviceOnce sync.Once
|
||||
handlerOnce sync.Once
|
||||
)
|
||||
|
||||
func ProvideService(db *internal.MemoryDB, mq *internal.MessageQueue) *Service {
|
||||
serviceOnce.Do(func() {
|
||||
service = &Service{
|
||||
db: db,
|
||||
mq: mq,
|
||||
}
|
||||
})
|
||||
return service
|
||||
}
|
||||
|
||||
func ProvideHandler(svc *Service) *Handler {
|
||||
handlerOnce.Do(func() {
|
||||
handler = &Handler{
|
||||
service: svc,
|
||||
}
|
||||
})
|
||||
return handler
|
||||
}
|
||||
38
server/rest/service.go
Normal file
38
server/rest/service.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *internal.MemoryDB
|
||||
mq *internal.MessageQueue
|
||||
}
|
||||
|
||||
func (s *Service) Exec(req internal.DownloadRequest) (string, error) {
|
||||
p := &internal.Process{
|
||||
Url: req.URL,
|
||||
Params: req.Params,
|
||||
Output: internal.DownloadOutput{
|
||||
Path: req.Path,
|
||||
Filename: req.Rename,
|
||||
},
|
||||
}
|
||||
|
||||
id := s.db.Set(p)
|
||||
s.mq.Publish(p)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *Service) Running(ctx context.Context) (*[]internal.ProcessResponse, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context cancelled")
|
||||
default:
|
||||
return s.db.All(), nil
|
||||
}
|
||||
}
|
||||
24
server/rpc/container.go
Normal file
24
server/rpc/container.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
||||
)
|
||||
|
||||
// Dependency injection container.
|
||||
func Container(db *internal.MemoryDB, mq *internal.MessageQueue) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
mq: mq,
|
||||
}
|
||||
}
|
||||
|
||||
// RPC service must be registered before applying this router!
|
||||
func ApplyRouter() func(chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Get("/ws", WebSocket)
|
||||
r.Post("/http", Post)
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,6 @@ type Args struct {
|
||||
Params []string
|
||||
}
|
||||
|
||||
// Dependency injection container.
|
||||
func Container(db *internal.MemoryDB, mq *internal.MessageQueue) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
mq: mq,
|
||||
}
|
||||
}
|
||||
|
||||
// Exec spawns a Process.
|
||||
// The result of the execution is the newly spawned process Id.
|
||||
func (s *Service) Exec(args internal.DownloadRequest, result *string) error {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/handlers"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/rest"
|
||||
@@ -68,23 +69,22 @@ func newServer(c serverConfig) *http.Server {
|
||||
// Archive routes
|
||||
r.Route("/archive", func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Post("/downloaded", rest.ListDownloaded)
|
||||
r.Post("/delete", rest.DeleteFile)
|
||||
r.Get("/d/{id}", rest.SendFile)
|
||||
r.Post("/downloaded", handlers.ListDownloaded)
|
||||
r.Post("/delete", handlers.DeleteFile)
|
||||
r.Get("/d/{id}", handlers.SendFile)
|
||||
})
|
||||
|
||||
// Authentication routes
|
||||
r.Route("/auth", func(r chi.Router) {
|
||||
r.Post("/login", rest.Login)
|
||||
r.Get("/logout", rest.Logout)
|
||||
r.Post("/login", handlers.Login)
|
||||
r.Get("/logout", handlers.Logout)
|
||||
})
|
||||
|
||||
// RPC handlers
|
||||
r.Route("/rpc", func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Get("/ws", ytdlpRPC.WebSocket)
|
||||
r.Post("/http", ytdlpRPC.Post)
|
||||
})
|
||||
r.Route("/rpc", ytdlpRPC.ApplyRouter())
|
||||
|
||||
// REST API handlers
|
||||
r.Route("/api/v1", rest.ApplyRouter(c.db, c.mq))
|
||||
|
||||
return &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", c.port),
|
||||
|
||||
Reference in New Issue
Block a user