diff --git a/.gitignore b/.gitignore index 912c21d..e6f2079 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ frontend/.pnp.loader.mjs frontend/.yarn/install-state.gz .db.lock livestreams.dat +# Generated by `make gen` +server/rest/ogen diff --git a/Dockerfile b/Dockerfile index 4c2d909..5630793 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,8 @@ WORKDIR /usr/src/yt-dlp-webui COPY . . COPY --from=ui /usr/src/yt-dlp-webui/frontend /usr/src/yt-dlp-webui/frontend -RUN CGO_ENABLED=0 GOOS=linux go build -o yt-dlp-webui +RUN make gen && \ + CGO_ENABLED=0 GOOS=linux go build -o yt-dlp-webui # ----------------------------------------------------------------------------- # dependencies ---------------------------------------------------------------- diff --git a/Makefile b/Makefile index 02cbcd0..3f62d66 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,13 @@ fe: dev: cd frontend && pnpm dev -all: +gen: +ifeq (, $(shell which ogen)) + go install github.com/ogen-go/ogen/cmd/ogen@v1.4.1 +endif + go generate ./... + +all: gen $(MAKE) fe && cd .. CGO_ENABLED=0 go build -o yt-dlp-webui main.go diff --git a/go.mod b/go.mod index d7a65bf..5e45657 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,13 @@ require ( github.com/coreos/go-oidc/v3 v3.11.0 github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/cors v1.2.1 + github.com/go-faster/errors v0.7.1 + github.com/go-faster/jx v1.1.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 + github.com/ogen-go/ogen v1.4.1 + go.uber.org/multierr v1.11.0 golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.8.0 golang.org/x/sys v0.25.0 @@ -18,16 +22,24 @@ require ( ) require ( + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-faster/yaml v0.4.6 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/kr/pretty v0.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/segmentio/asm v1.2.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/text v0.18.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect modernc.org/libc v1.61.0 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index 7f0f38c..c4f32b4 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,24 @@ github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/ github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= +github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -24,39 +36,60 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ogen-go/ogen v1.4.1 h1:uVZWKw80eZaMQu7LpTJxpHqwu6oMqYuh8ElqP1Mf2bI= +github.com/ogen-go/ogen v1.4.1/go.mod h1:YeliH7gAS6QToqDqIM5BrnEUOiXiqCnNqNwzEpebDsY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= diff --git a/nix/common.nix b/nix/common.nix index f0a67ae..f5e72fe 100644 --- a/nix/common.nix +++ b/nix/common.nix @@ -1,9 +1,21 @@ -{ lib }: { +{ lib, pkgs }: { version = "v3.1.2"; meta = { description = "A terrible web ui for yt-dlp. Designed to be self-hosted."; homepage = "https://github.com/marcopeocchi/yt-dlp-web-ui"; license = lib.licenses.mpl20; }; -} + ogen = pkgs.buildGo123Module { + pname = "ogen"; + version = "v1.4.1"; + + src = pkgs.fetchFromGitHub { + owner = "ogen-go"; + repo = "ogen"; + rev = "v1.4.1"; + sha256 = "sha256-SwJY9VQafclAxEQ/cbRJALvMLlnSIItIOz92XzuCoCk="; + }; + vendorHash = "sha256-IxG7y0Zy0DerCh5DRdSWSaD643BG/8Wj2wuYvkn+XzE="; + }; +} diff --git a/nix/devShell.nix b/nix/devShell.nix index 8bb9d96..0ec1f9f 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -1,9 +1,13 @@ -{ inputsFrom ? [ ], mkShell, yt-dlp, nodejs, go }: +{ inputsFrom ? [ ], mkShell, yt-dlp, nodejs, go, lib, pkgs }: +let + common = import ./common.nix { inherit lib; inherit pkgs; }; +in mkShell { inherit inputsFrom; packages = [ yt-dlp nodejs go + common.ogen ]; } diff --git a/nix/frontend.nix b/nix/frontend.nix index 17fc437..7b52bb1 100644 --- a/nix/frontend.nix +++ b/nix/frontend.nix @@ -1,9 +1,10 @@ { lib +, pkgs , stdenv , nodejs , pnpm }: -let common = import ./common.nix { inherit lib; }; in +let common = import ./common.nix { inherit lib; inherit pkgs; }; in stdenv.mkDerivation (finalAttrs: { pname = "yt-dlp-web-ui-frontend"; diff --git a/nix/server.nix b/nix/server.nix index b20a3cd..d23d4b6 100644 --- a/nix/server.nix +++ b/nix/server.nix @@ -1,7 +1,7 @@ -{ yt-dlp-web-ui-frontend, buildGo123Module, lib, makeWrapper, yt-dlp, ... }: +{ pkgs, yt-dlp-web-ui-frontend, buildGo123Module, lib, makeWrapper, yt-dlp, ... }: let fs = lib.fileset; - common = import ./common.nix { inherit lib; }; + common = import ./common.nix { inherit lib; inherit pkgs; }; in buildGo123Module { pname = "yt-dlp-web-ui"; @@ -35,16 +35,18 @@ buildGo123Module { # https://github.com/golang/go/issues/44507 preBuild = '' cp -r ${yt-dlp-web-ui-frontend} frontend + + go generate ./... ''; - nativeBuildInputs = [ makeWrapper ]; + nativeBuildInputs = [ makeWrapper common.ogen ]; postInstall = '' wrapProgram $out/bin/yt-dlp-web-ui \ --prefix PATH : ${lib.makeBinPath [ yt-dlp ]} ''; - vendorHash = "sha256-c7IdCmYJEn5qJn3K8wt0qz3t0Nq9rbgWp1eONlCJOwM="; + vendorHash = "sha256-9QZRj3HBNJI0Iv/6QSFb8lbcsPyLXV3rPJ3Ige8Ww9o="; meta = common.meta // { mainProgram = "yt-dlp-web-ui"; diff --git a/server/rest/.ogen.yaml b/server/rest/.ogen.yaml new file mode 100644 index 0000000..1338b0b --- /dev/null +++ b/server/rest/.ogen.yaml @@ -0,0 +1,5 @@ +generator: + features: + disable: + - 'paths/client' + - 'ogen/otel' diff --git a/server/rest/container.go b/server/rest/container.go index 1ced07f..7f3b801 100644 --- a/server/rest/container.go +++ b/server/rest/container.go @@ -1,10 +1,14 @@ package rest import ( - "github.com/go-chi/chi/v5" + "log/slog" + "net/http" + "os" + "github.com/marcopeocchi/yt-dlp-web-ui/server/config" middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware" "github.com/marcopeocchi/yt-dlp-web-ui/server/openid" + "github.com/marcopeocchi/yt-dlp-web-ui/server/rest/ogen" ) func Container(args *ContainerArgs) *Handler { @@ -15,26 +19,25 @@ func Container(args *ContainerArgs) *Handler { return handler } -func ApplyRouter(args *ContainerArgs) func(chi.Router) { +func ApplyRouter(args *ContainerArgs) http.Handler { h := Container(args) - return func(r chi.Router) { - if config.Instance().RequireAuth { - r.Use(middlewares.Authenticated) - } - if config.Instance().UseOpenId { - r.Use(openid.Middleware) - } - r.Post("/exec", h.Exec()) - r.Post("/execPlaylist", h.ExecPlaylist()) - r.Post("/execLivestream", h.ExecLivestream()) - r.Get("/running", h.Running()) - r.Get("/version", h.GetVersion()) - r.Get("/cookies", h.GetCookies()) - r.Post("/cookies", h.SetCookies()) - r.Delete("/cookies", h.DeleteCookies()) - r.Post("/template", h.AddTemplate()) - r.Get("/template/all", h.GetTemplates()) - r.Delete("/template/{id}", h.DeleteTemplate()) + srv, err := ogen.NewServer(h, &secHandler{}, ogen.WithPathPrefix("/api/v1")) + if err != nil { + slog.Error("create the REST server", + slog.String("err", err.Error())) + + os.Exit(1) + } + + var hand http.Handler = srv + if config.Instance().RequireAuth { + hand = middlewares.Authenticated(hand) } + + if config.Instance().UseOpenId { + hand = openid.Middleware(hand) + } + + return hand } diff --git a/server/rest/handlers.go b/server/rest/handlers.go index 95b14a4..86c2787 100644 --- a/server/rest/handlers.go +++ b/server/rest/handlers.go @@ -1,13 +1,15 @@ +//go:generate ogen --target ./ogen -package ogen --clean ../../openapi/openapi.json package rest import ( - "encoding/json" - "net/http" + "context" - "github.com/go-chi/chi/v5" "github.com/marcopeocchi/yt-dlp-web-ui/server/internal" + "github.com/marcopeocchi/yt-dlp-web-ui/server/rest/ogen" ) +var _ ogen.Handler = &Handler{} + type Handler struct { service *Service } @@ -16,256 +18,172 @@ type Handler struct { REST version of the JSON-RPC interface */ -func (h *Handler) Exec() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - w.Header().Set("Content-Type", "application/json") - - var req internal.DownloadRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - id, err := h.service.Exec(req) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode(id) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } +func transformInternalDownloadRequest(req *ogen.DownloadRequest) internal.DownloadRequest { + var iReq internal.DownloadRequest + if req.URL.Set { + iReq.URL = req.URL.Value.String() } -} -func (h *Handler) ExecPlaylist() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - w.Header().Set("Content-Type", "application/json") - - var req internal.DownloadRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - err := h.service.ExecPlaylist(req) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := json.NewEncoder(w).Encode("ok"); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + if req.Path.Set { + iReq.Path = req.Path.Value } -} -func (h *Handler) ExecLivestream() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - w.Header().Set("Content-Type", "application/json") - - var req internal.DownloadRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - h.service.ExecLivestream(req) - - err := json.NewEncoder(w).Encode("ok") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + if req.Rename.Set { + iReq.Rename = req.Rename.Value } -} - -func (h *Handler) Running() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - w.Header().Set("Content-Type", "application/json") + iReq.Params = req.Params - 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) - } - } + return iReq } -func (h *Handler) GetCookies() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - cookies, err := h.service.GetCookies(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } +func (h *Handler) AddDownload(ctx context.Context, req *ogen.DownloadRequest) (ogen.AddDownloadRes, error) { + iReq := transformInternalDownloadRequest(req) - res := &internal.SetCookiesRequest{ - Cookies: string(cookies), - } - - if err := json.NewEncoder(w).Encode(res); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + id, err := h.service.Exec(iReq) + if err != nil { + return nil, err } -} - -func (h *Handler) SetCookies() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - w.Header().Set("Content-Type", "application/json") - - req := new(internal.SetCookiesRequest) - - err := json.NewDecoder(r.Body).Decode(req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - err = h.service.SetCookies(r.Context(), req.Cookies) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode("ok") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } + res := ogen.AddDownloadOKApplicationJSON(id) + return &res, nil } -func (h *Handler) DeleteCookies() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - err := h.service.SetCookies(r.Context(), "") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } +func (h *Handler) AddDownloadPlaylist(ctx context.Context, req *ogen.DownloadRequest) (ogen.AddDownloadPlaylistRes, error) { + iReq := transformInternalDownloadRequest(req) - err = json.NewEncoder(w).Encode("ok") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + if err := h.service.ExecPlaylist(iReq); err != nil { + return nil, err } + + ok := ogen.AddDownloadPlaylistOKOk + return &ok, nil } -func (h *Handler) AddTemplate() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() +func (h *Handler) AddDwonloadLivestream(ctx context.Context, req *ogen.DownloadRequest) (ogen.AddDwonloadLivestreamRes, error) { + iReq := transformInternalDownloadRequest(req) - w.Header().Set("Content-Type", "application/json") + h.service.ExecLivestream(iReq) - req := new(internal.CustomTemplate) + ok := ogen.AddDwonloadLivestreamOKOk + return &ok, nil +} - err := json.NewDecoder(r.Body).Decode(req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } +func (h *Handler) Running(ctx context.Context) (ogen.RunningRes, error) { + iRes, err := h.service.Running(ctx) + if err != nil { + return nil, err + } - if req.Name == "" || req.Content == "" { - http.Error(w, "Invalid template", http.StatusBadRequest) - return - } + res := ogen.RunningOKApplicationJSON{} + for _, r := range *iRes { + res = append(res, ogen.ProcessResponse{ + Progress: ogen.DownloadProgress{ + ProcessStatus: r.Progress.Status, + Percentage: r.Progress.Percentage, + Speed: r.Progress.Speed, + Eta: r.Progress.ETA, + }, + Info: ogen.DownloadInfo{ + URL: r.Info.URL, + Title: r.Info.Title, + Thumbnail: r.Info.Thumbnail, + Resolution: r.Info.Resolution, + Size: int(r.Info.Size), + Vcodec: r.Info.VCodec, + Acodec: r.Info.ACodec, + Extension: r.Info.Extension, + OriginalURL: r.Info.OriginalURL, + CreatedAt: r.Info.CreatedAt, + }, + Output: ogen.DownloadOutput{ + Path: r.Output.Path, + Filename: r.Output.Filename, + SavedFilePath: r.Output.SavedFilePath, + }, + }) + } - err = h.service.SaveTemplate(r.Context(), req) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + return &res, nil +} - err = json.NewEncoder(w).Encode("ok") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } +func (h *Handler) GetVersion(ctx context.Context) (*ogen.GetVersionResponse, error) { + rpc, ytdlp, err := h.service.GetVersion(ctx) + if err != nil { + return nil, err } -} -func (h *Handler) GetTemplates() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + return &ogen.GetVersionResponse{ + RpcVersion: rpc, + YtdlpVersion: ytdlp, + }, nil +} - w.Header().Set("Content-Type", "application/json") +func (h *Handler) GetCookies(ctx context.Context) (*ogen.SetCookiesRequest, error) { + cookies, err := h.service.GetCookies(ctx) + if err != nil { + return nil, err + } - templates, err := h.service.GetTemplates(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + return &ogen.SetCookiesRequest{ + Cookies: string(cookies), + }, nil +} - err = json.NewEncoder(w).Encode(templates) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } +func (h *Handler) SetCookies(ctx context.Context, req *ogen.SetCookiesRequest) (ogen.SetCookiesRes, error) { + if err := h.service.SetCookies(ctx, req.Cookies); err != nil { + return nil, err } -} -func (h *Handler) DeleteTemplate() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + ok := ogen.SetCookiesOKOk + return &ok, nil +} - w.Header().Set("Content-Type", "application/json") +func (h *Handler) DeleteCookies(ctx context.Context) (ogen.DeleteCookiesOK, error) { + if err := h.service.SetCookies(ctx, ""); err != nil { + return "", err + } - id := chi.URLParam(r, "id") + return ogen.DeleteCookiesOKOk, nil +} - err := h.service.DeleteTemplate(r.Context(), id) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } +func (h *Handler) AddTemplate(ctx context.Context, req *ogen.CustomTemplate) (ogen.AddTemplateRes, error) { + var iReq internal.CustomTemplate + iReq.Id = req.ID + iReq.Name = req.Name + iReq.Content = req.Content - err = json.NewEncoder(w).Encode("ok") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + if err := h.service.SaveTemplate(ctx, &iReq); err != nil { + return nil, err } + + ok := ogen.AddTemplateOKOk + return &ok, nil } -func (h *Handler) GetVersion() http.HandlerFunc { - type Response struct { - RPCVersion string `json:"rpcVersion"` - YtdlpVersion string `json:"ytdlpVersion"` +func (h *Handler) TemplateAllGet(ctx context.Context) ([]ogen.CustomTemplate, error) { + templates, err := h.service.GetTemplates(ctx) + if err != nil { + return nil, err } - return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - w.Header().Set("Content-Type", "application/json") - - rpcVersion, ytdlpVersion, err := h.service.GetVersion(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + res := []ogen.CustomTemplate{} + for _, t := range *templates { + res = append(res, ogen.CustomTemplate{ + ID: t.Id, + Name: t.Name, + Content: t.Content, + }) + } - res := Response{ - RPCVersion: rpcVersion, - YtdlpVersion: ytdlpVersion, - } + return res, nil +} - if err := json.NewEncoder(w).Encode(res); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } +func (h *Handler) TemplateIDDelete(ctx context.Context, params ogen.TemplateIDDeleteParams) (ogen.TemplateIDDeleteRes, error) { + if err := h.service.DeleteTemplate(ctx, params.ID); err != nil { + return nil, err } + + ok := ogen.TemplateIDDeleteOKOk + return &ok, nil } diff --git a/server/rest/security.go b/server/rest/security.go new file mode 100644 index 0000000..5b66001 --- /dev/null +++ b/server/rest/security.go @@ -0,0 +1,16 @@ +package rest + +import ( + "context" + + "github.com/marcopeocchi/yt-dlp-web-ui/server/rest/ogen" +) + +var _ ogen.SecurityHandler = &secHandler{} + +type secHandler struct{} + +func (s secHandler) HandleAPIKey(ctx context.Context, operationName string, t ogen.APIKey) (context.Context, error) { + // We ignore this, since is handled by the chi middleware + return ctx, nil +} diff --git a/server/server.go b/server/server.go index f757d13..50cae6b 100644 --- a/server/server.go +++ b/server/server.go @@ -205,7 +205,7 @@ func newServer(c serverConfig) *http.Server { r.Route("/rpc", ytdlpRPC.ApplyRouter()) // REST API handlers - r.Route("/api/v1", rest.ApplyRouter(&rest.ContainerArgs{ + r.Mount("/api/v1", rest.ApplyRouter(&rest.ContainerArgs{ DB: c.db, MDB: c.mdb, MQ: c.mq,