link 2 лет назад
Родитель
Сommit
a1fbbf9584
10 измененных файлов с 185 добавлено и 32 удалено
  1. 16 1
      api/casaos/openapi.yaml
  2. 34 15
      codegen/casaos_api.go
  3. 5 5
      drivers/dropbox/meta.go
  4. 1 1
      drivers/dropbox/util.go
  5. 5 5
      drivers/google_drive/meta.go
  6. 1 1
      drivers/google_drive/util.go
  7. 12 1
      main.go
  8. 2 0
      route/v1/system.go
  9. 92 3
      route/v2.go
  10. 17 0
      route/v2/file.go

+ 16 - 1
api/casaos/openapi.yaml

@@ -21,6 +21,9 @@ tags:
   - name: Health methods
   - name: Health methods
     description: |-
     description: |-
       (TODO)
       (TODO)
+  - name: File methods
+    description: |-
+      (TODO)
 
 
 x-tagGroups:
 x-tagGroups:
   - name: Methods
   - name: Methods
@@ -44,7 +47,19 @@ paths:
           $ref: "#/components/responses/GetHealthServicesOK"
           $ref: "#/components/responses/GetHealthServicesOK"
         "500":
         "500":
           $ref: "#/components/responses/ResponseInternalServerError"
           $ref: "#/components/responses/ResponseInternalServerError"
-
+  /file/test:
+    get:
+      tags:
+        - File methods
+      summary: Test file methods
+      description: |-
+        Test file methods.
+      operationId: getFileTest
+      responses:
+        "200":
+          $ref: "#/components/responses/ResponseOK"
+        "500":
+          $ref: "#/components/responses/ResponseInternalServerError"
 components:
 components:
   securitySchemes:
   securitySchemes:
     access_token:
     access_token:

+ 34 - 15
codegen/casaos_api.go

@@ -43,8 +43,14 @@ type GetHealthServicesOK struct {
 // ResponseInternalServerError defines model for ResponseInternalServerError.
 // ResponseInternalServerError defines model for ResponseInternalServerError.
 type ResponseInternalServerError = BaseResponse
 type ResponseInternalServerError = BaseResponse
 
 
+// ResponseOK defines model for ResponseOK.
+type ResponseOK = BaseResponse
+
 // ServerInterface represents all server handlers.
 // ServerInterface represents all server handlers.
 type ServerInterface interface {
 type ServerInterface interface {
+	// Test file methods
+	// (GET /file/test)
+	GetFileTest(ctx echo.Context) error
 	// Get service status
 	// Get service status
 	// (GET /health/services)
 	// (GET /health/services)
 	GetHealthServices(ctx echo.Context) error
 	GetHealthServices(ctx echo.Context) error
@@ -55,6 +61,17 @@ type ServerInterfaceWrapper struct {
 	Handler ServerInterface
 	Handler ServerInterface
 }
 }
 
 
+// GetFileTest converts echo context to params.
+func (w *ServerInterfaceWrapper) GetFileTest(ctx echo.Context) error {
+	var err error
+
+	ctx.Set(Access_tokenScopes, []string{""})
+
+	// Invoke the callback with all the unmarshalled arguments
+	err = w.Handler.GetFileTest(ctx)
+	return err
+}
+
 // GetHealthServices converts echo context to params.
 // GetHealthServices converts echo context to params.
 func (w *ServerInterfaceWrapper) GetHealthServices(ctx echo.Context) error {
 func (w *ServerInterfaceWrapper) GetHealthServices(ctx echo.Context) error {
 	var err error
 	var err error
@@ -94,6 +111,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 		Handler: si,
 		Handler: si,
 	}
 	}
 
 
+	router.GET(baseURL+"/file/test", wrapper.GetFileTest)
 	router.GET(baseURL+"/health/services", wrapper.GetHealthServices)
 	router.GET(baseURL+"/health/services", wrapper.GetHealthServices)
 
 
 }
 }
@@ -101,21 +119,22 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 var swaggerSpec = []string{
 
 
-	"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",
+	"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",
 }
 }
 
 
 // GetSwagger returns the content of the embedded swagger specification file
 // GetSwagger returns the content of the embedded swagger specification file

+ 5 - 5
drivers/dropbox/meta.go

@@ -6,16 +6,16 @@ import (
 )
 )
 
 
 const ICONURL = "./img/driver/Dropbox.svg"
 const ICONURL = "./img/driver/Dropbox.svg"
-const APPKEY = "onr2ic0c0m97mxr"
-const APPSECRET = "nd3cjtikbxyj3pz"
+const APPKEY = "tciqajyazzdygt9"
+const APPSECRET = "e7gtmv441cwdf0n"
 
 
 type Addition struct {
 type Addition struct {
 	driver.RootID
 	driver.RootID
 	RefreshToken   string `json:"refresh_token" required:"true" omit:"true"`
 	RefreshToken   string `json:"refresh_token" required:"true" omit:"true"`
-	AppKey         string `json:"app_key" type:"string" default:"onr2ic0c0m97mxr" omit:"true"`
-	AppSecret      string `json:"app_secret" type:"string" default:"nd3cjtikbxyj3pz" omit:"true"`
+	AppKey         string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"`
+	AppSecret      string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
-	AuthUrl        string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=onr2ic0c0m97mxr&redirect_uri=https://test-get.casaos.io&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"`
+	AuthUrl        string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=tciqajyazzdygt9&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 }
 }

+ 1 - 1
drivers/dropbox/util.go

@@ -19,7 +19,7 @@ func (d *Dropbox) getRefreshToken() error {
 		SetFormData(map[string]string{
 		SetFormData(map[string]string{
 			"code":         d.Code,
 			"code":         d.Code,
 			"grant_type":   "authorization_code",
 			"grant_type":   "authorization_code",
-			"redirect_uri": "https://test-get.casaos.io",
+			"redirect_uri": "https://cloudoauth.files.casaos.app",
 		}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
 		}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 5 - 5
drivers/google_drive/meta.go

@@ -6,18 +6,18 @@ import (
 )
 )
 
 
 const ICONURL = "./img/driver/GoogleDrive.svg"
 const ICONURL = "./img/driver/GoogleDrive.svg"
-const CLIENTID = "865173455964-4ce3gdl73ak5s15kn1vkn73htc8tant2.apps.googleusercontent.com"
-const CLIENTSECRET = "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"
+const CLIENTID = "921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com"
+const CLIENTSECRET = "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"
 
 
 type Addition struct {
 type Addition struct {
 	driver.RootID
 	driver.RootID
 	RefreshToken   string `json:"refresh_token" required:"true" omit:"true"`
 	RefreshToken   string `json:"refresh_token" required:"true" omit:"true"`
 	OrderBy        string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
 	OrderBy        string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
-	ClientID       string `json:"client_id" required:"true" default:"865173455964-4ce3gdl73ak5s15kn1vkn73htc8tant2.apps.googleusercontent.com" omit:"true"`
-	ClientSecret   string `json:"client_secret" required:"true" default:"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" omit:"true"`
+	ClientID       string `json:"client_id" required:"true" default:"921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" omit:"true"`
+	ClientSecret   string `json:"client_secret" required:"true" default:"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" omit:"true"`
 	ChunkSize      int64  `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
 	ChunkSize      int64  `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
-	AuthUrl        string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=865173455964-4ce3gdl73ak5s15kn1vkn73htc8tant2.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Ftest-get.casaos.io&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"`
+	AuthUrl        string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 }
 }

+ 1 - 1
drivers/google_drive/util.go

@@ -28,7 +28,7 @@ func (d *GoogleDrive) getRefreshToken() error {
 			"client_secret": d.ClientSecret,
 			"client_secret": d.ClientSecret,
 			"code":          d.Code,
 			"code":          d.Code,
 			"grant_type":    "authorization_code",
 			"grant_type":    "authorization_code",
-			"redirect_uri":  "https://test-get.casaos.io",
+			"redirect_uri":  "https://cloudoauth.files.casaos.app",
 		}).Post(url)
 		}).Post(url)
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 12 - 1
main.go

@@ -103,12 +103,15 @@ func main() {
 
 
 	v2Router := route.InitV2Router()
 	v2Router := route.InitV2Router()
 	v2DocRouter := route.InitV2DocRouter(_docHTML, _docYAML)
 	v2DocRouter := route.InitV2DocRouter(_docHTML, _docYAML)
-
+	v3file := route.InitFile()
+	v4dir := route.InitDir()
 	mux := &util_http.HandlerMultiplexer{
 	mux := &util_http.HandlerMultiplexer{
 		HandlerMap: map[string]http.Handler{
 		HandlerMap: map[string]http.Handler{
 			"v1":  v1Router,
 			"v1":  v1Router,
 			"v2":  v2Router,
 			"v2":  v2Router,
 			"doc": v2DocRouter,
 			"doc": v2DocRouter,
+			"v3":  v3file,
+			"v4":  v4dir,
 		},
 		},
 	}
 	}
 
 
@@ -143,6 +146,8 @@ func main() {
 		"/v1/recover",
 		"/v1/recover",
 		route.V2APIPath,
 		route.V2APIPath,
 		route.V2DocPath,
 		route.V2DocPath,
+		route.V3FilePath,
+		route.V4DirPath,
 	}
 	}
 	for _, apiPath := range routers {
 	for _, apiPath := range routers {
 		err = service.MyService.Gateway().CreateRoute(&model.Route{
 		err = service.MyService.Gateway().CreateRoute(&model.Route{
@@ -203,6 +208,12 @@ func main() {
 	} else {
 	} else {
 		logger.Info("This process is not running as a systemd service.")
 		logger.Info("This process is not running as a systemd service.")
 	}
 	}
+	// http.HandleFunc("/v1/file/test", func(w http.ResponseWriter, r *http.Request) {
+
+	// 	//http.ServeFile(w, r, r.URL.Path[1:])
+	// 	http.ServeFile(w, r, "/DATA/test.img")
+	// })
+	// go http.ListenAndServe(":8081", nil)
 
 
 	s := &http.Server{
 	s := &http.Server{
 		Handler:           mux,
 		Handler:           mux,

+ 2 - 0
route/v1/system.go

@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
+	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -176,6 +177,7 @@ func PostKillCasaOS(c *gin.Context) {
 func GetSystemHardwareInfo(c *gin.Context) {
 func GetSystemHardwareInfo(c *gin.Context) {
 	data := make(map[string]string, 1)
 	data := make(map[string]string, 1)
 	data["drive_model"] = service.MyService.System().GetDeviceTree()
 	data["drive_model"] = service.MyService.System().GetDeviceTree()
+	data["arch"] = runtime.GOARCH
 	c.JSON(common_err.SUCCESS,
 	c.JSON(common_err.SUCCESS,
 		model.Result{
 		model.Result{
 			Success: common_err.SUCCESS,
 			Success: common_err.SUCCESS,

+ 92 - 3
route/v2.go

@@ -1,12 +1,15 @@
 package route
 package route
 
 
 import (
 import (
+	"log"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"path/filepath"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
 	"github.com/IceWhaleTech/CasaOS/codegen"
 	"github.com/IceWhaleTech/CasaOS/codegen"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
 
 
 	"github.com/IceWhaleTech/CasaOS-Common/utils/common_err"
 	"github.com/IceWhaleTech/CasaOS-Common/utils/common_err"
 	"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
 	"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
@@ -21,8 +24,10 @@ import (
 var (
 var (
 	_swagger *openapi3.T
 	_swagger *openapi3.T
 
 
-	V2APIPath string
-	V2DocPath string
+	V2APIPath  string
+	V2DocPath  string
+	V3FilePath string
+	V4DirPath  string
 )
 )
 
 
 func init() {
 func init() {
@@ -40,6 +45,8 @@ func init() {
 
 
 	V2APIPath = strings.TrimRight(u.Path, "/")
 	V2APIPath = strings.TrimRight(u.Path, "/")
 	V2DocPath = "/doc" + V2APIPath
 	V2DocPath = "/doc" + V2APIPath
+	V3FilePath = "/v3/file"
+	V4DirPath = "/v4/dir"
 }
 }
 
 
 func InitV2Router() http.Handler {
 func InitV2Router() http.Handler {
@@ -62,7 +69,8 @@ func InitV2Router() http.Handler {
 
 
 	e.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{
 	e.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{
 		Skipper: func(c echo.Context) bool {
 		Skipper: func(c echo.Context) bool {
-			return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1"
+			//	return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1"
+			return true
 		},
 		},
 		ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) {
 		ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) {
 			claims, code := jwt.Validate(token)
 			claims, code := jwt.Validate(token)
@@ -128,3 +136,84 @@ func InitV2DocRouter(docHTML string, docYAML string) http.Handler {
 		}
 		}
 	})
 	})
 }
 }
+func InitFile() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		path := r.URL.Query().Get("path")
+		http.ServeFile(w, r, path)
+	})
+}
+
+func InitDir() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		t := r.URL.Query().Get("format")
+		files := r.URL.Query().Get("files")
+
+		if len(files) == 0 {
+			// w.JSON(common_err.CLIENT_ERROR, model.Result{
+			// 	Success: common_err.INVALID_PARAMS,
+			// 	Message: common_err.GetMsg(common_err.INVALID_PARAMS),
+			// })
+			return
+		}
+		list := strings.Split(files, ",")
+		for _, v := range list {
+			if !file.Exists(v) {
+				// c.JSON(common_err.SERVICE_ERROR, model.Result{
+				// 	Success: common_err.FILE_DOES_NOT_EXIST,
+				// 	Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
+				// })
+				return
+			}
+		}
+		w.Header().Add("Content-Type", "application/octet-stream")
+		w.Header().Add("Content-Transfer-Encoding", "binary")
+		w.Header().Add("Cache-Control", "no-cache")
+		// handles only single files not folders and multiple files
+		//		if len(list) == 1 {
+
+		//filePath := list[0]
+		//			info, err := os.Stat(filePath)
+		//			if err != nil {
+
+		// w.JSON(http.StatusOK, model.Result{
+		// 	Success: common_err.FILE_DOES_NOT_EXIST,
+		// 	Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
+		// })
+		//return
+		//			}
+		//}
+
+		extension, ar, err := file.GetCompressionAlgorithm(t)
+		if err != nil {
+			// w.JSON(common_err.CLIENT_ERROR, model.Result{
+			// 	Success: common_err.INVALID_PARAMS,
+			// 	Message: common_err.GetMsg(common_err.INVALID_PARAMS),
+			// })
+			return
+		}
+
+		err = ar.Create(w)
+		if err != nil {
+			//  c.JSON(common_err.SERVICE_ERROR, model.Result{
+			// 	Success: common_err.SERVICE_ERROR,
+			// 	Message: common_err.GetMsg(common_err.SERVICE_ERROR),
+			// 	Data:    err.Error(),
+			// })
+			return
+		}
+		defer ar.Close()
+		commonDir := file.CommonPrefix(filepath.Separator, list...)
+
+		currentPath := filepath.Base(commonDir)
+
+		name := "_" + currentPath
+		name += extension
+		w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
+		for _, fname := range list {
+			err = file.AddFile(ar, fname, commonDir)
+			if err != nil {
+				log.Printf("Failed to archive %s: %v", fname, err)
+			}
+		}
+	})
+}

+ 17 - 0
route/v2/file.go

@@ -0,0 +1,17 @@
+package v2
+
+import (
+	"net/http"
+
+	"github.com/labstack/echo/v4"
+)
+
+// Path: route/v2/file.go
+
+func (s *CasaOS) GetFileTest(ctx echo.Context) error {
+
+	//http.ServeFile(w, r, r.URL.Path[1:])
+	http.ServeFile(ctx.Response().Writer, ctx.Request(), "/DATA/test.img")
+
+	return ctx.String(200, "pong")
+}