From 52007d940e7e85de3486715b2b164dbec58ec81b Mon Sep 17 00:00:00 2001 From: minoplhy Date: Wed, 10 Apr 2024 00:07:53 +0700 Subject: [PATCH] init --- .env | 4 + .gitignore | 1 + go.mod | 5 ++ go.sum | 2 + main.go | 116 ++++++++++++++++++++++++++ src/handler/chibisafe.go | 170 +++++++++++++++++++++++++++++++++++++++ src/handler/file.go | 60 ++++++++++++++ 7 files changed, 358 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 src/handler/chibisafe.go create mode 100644 src/handler/file.go diff --git a/.env b/.env new file mode 100644 index 0000000..9db334d --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# something like this -> +# https://domain.tld +CHIBISAFE_BASEPATH= +HOST= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95a48fb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +uploads \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1201e0a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/minoplhy/chibisafe_netstorage_middleman + +go 1.22.2 + +require github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..096a4e8 --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + "github.com/joho/godotenv" + "github.com/minoplhy/chibisafe_netstorage_middleman/src/handler" +) + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + // Check is already done in main() + Chibisafe_basepath := os.Getenv("CHIBISAFE_BASEPATH") + + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // truncated for brevity + + // The argument to FormFile must match the name attribute + // of the file input on the frontend + file, fileHeader, err := r.FormFile("file") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + log.Panic(err) + return + } + defer file.Close() + + API_Key := r.Header.Get("x-api-key") + if API_Key == "" { + http.Error(w, "X-api-key is empty!", http.StatusBadRequest) + log.Panicf("X-api-key is empty!") + return + } + log.Printf("Received a successful POST from %s", r.RemoteAddr) + tempfilepath := handler.GetTempFilename(fileHeader.Filename) + log.Printf("Successfully obtained temporary Filename : %s", tempfilepath) + handler.SaveFile(tempfilepath, file) + handler.DiscardFile(file) + + PostData := handler.UploadPostMeta{ + ContentType: fileHeader.Header.Get("Content-Type"), + Name: fileHeader.Filename, + FileSize: fileHeader.Size, + } + + chibisafe_post, _ := handler.UploadPost(Chibisafe_basepath, PostData, API_Key) + var chibisafe_Response_Metadata handler.UploadResponseMeta + err = json.Unmarshal(chibisafe_post, &chibisafe_Response_Metadata) + if err != nil { + log.Panic(err) + return + } + log.Printf("Successfully obtained PUT key with identifier: %s", chibisafe_Response_Metadata.Identifier) + + _, err = handler.NetworkStoragePut(chibisafe_Response_Metadata.URL, PostData.ContentType, tempfilepath) + if err != nil { + log.Panic(err) + return + } + log.Printf("Successfully PUT file to Network Storage with identifier: %s", chibisafe_Response_Metadata.Identifier) + + PostProcessData := handler.UploadProcessMeta{ + Name: fileHeader.Filename, + ContentType: fileHeader.Header.Get("Content-Type"), + Identifier: chibisafe_Response_Metadata.Identifier, + } + + PostProcess, _ := handler.UploadProcessPost(Chibisafe_basepath, PostData.ContentType, chibisafe_Response_Metadata.Identifier, API_Key, PostProcessData) + var PostProcessResponse handler.UploadProcessResponseMeta + err = json.Unmarshal(PostProcess, &PostProcessResponse) + if err != nil { + log.Panic(err) + return + } + log.Printf("Successfully Processed Response with identifier: %s and UUID: %s", PostProcessResponse.Name, PostProcessResponse.UUID) + + err = handler.DeleteFile(tempfilepath) + if err != nil { + log.Panic(err) + return + } + log.Printf("Successfully Deleted Temporary file from local disk Filename: %s", tempfilepath) + + fmt.Fprintf(w, "%s", PostProcessResponse.URL) +} + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal("Cannot load .env file!") + } + + Chibisafe_basepath := os.Getenv("CHIBISAFE_BASEPATH") + Host := os.Getenv("HOST") + + if Chibisafe_basepath == "" { + log.Fatal("CHIBISAFE_BASEPATH environment is not set!") + } + if Host == "" { + Host = "127.0.0.1:4000" + } + + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/upload", uploadHandler) + + if err := http.ListenAndServe(Host, mux); err != nil { + log.Fatal(err) + } +} diff --git a/src/handler/chibisafe.go b/src/handler/chibisafe.go new file mode 100644 index 0000000..3b4014b --- /dev/null +++ b/src/handler/chibisafe.go @@ -0,0 +1,170 @@ +package handler + +import ( + "bytes" + "encoding/json" + "io" + "log" + "net/http" + "os" +) + +type UploadPostMeta struct { + Name string `json:"name"` + FileSize int64 `json:"size"` + ContentType string `json:"contentType"` +} + +type UploadResponseMeta struct { + URL string `json:"url"` + Identifier string `json:"identifier"` + PublicURL string `json:"publicUrl"` +} + +type UploadProcessMeta struct { + Name string `json:"name"` + Identifier string `json:"identifier"` + ContentType string `json:"type"` +} + +type UploadProcessResponseMeta struct { + Name string `json:"name"` + UUID string `json:"uuid"` + URL string `json:"url"` +} + +func UploadPost(BasePath string, PostData UploadPostMeta, accessKey string) ([]byte, error) { + URL := BasePath + "/api/upload" + // Convert PostData to JSON + PostDataJson, err := json.Marshal(PostData) + if err != nil { + log.Panic(err) + return nil, err + } + + // Create a new request with POST method and request body + req, err := http.NewRequest("POST", URL, bytes.NewBuffer(PostDataJson)) + if err != nil { + log.Panic(err) + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-API-Key", accessKey) + + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + log.Panic(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + BodyRead, err := io.ReadAll(resp.Body) + if err != nil { + log.Panic(err) + return nil, err + } + return BodyRead, nil + } else { + log.Panicf("Output from %s : %d", URL, resp.StatusCode) + return nil, nil + } +} + +// PUT to Network Storage(S3) +// URL argument is given from UploadPost Response! +func NetworkStoragePut(URL string, ContentType string, filepath string) ([]byte, error) { + // Open the file + file, err := os.Open(filepath) + if err != nil { + log.Panic(err) + return nil, err + } + defer file.Close() + + // Create a buffer to store the file contents + var buffer bytes.Buffer + _, err = io.Copy(&buffer, file) + if err != nil { + log.Panic(err) + return nil, err + } + + // Create an HTTP client + client := &http.Client{} + + // Create a PUT request with the file contents + req, err := http.NewRequest("PUT", URL, &buffer) + if err != nil { + log.Panic(err) + return nil, err + } + defer req.Body.Close() + + // Set appropriate headers for the file + req.Header.Set("Content-Type", ContentType) + + // Send the request + resp, err := client.Do(req) + if err != nil { + log.Panic(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + BodyRead, err := io.ReadAll(resp.Body) + if err != nil { + log.Panic(err) + return nil, err + } + return BodyRead, nil + } else { + log.Panicf("Output from %s : %d", URL, resp.StatusCode) + return nil, nil + } +} + +func UploadProcessPost(BasePath string, ContentType string, Identifier string, accessKey string, PostData UploadProcessMeta) ([]byte, error) { + URL := BasePath + "/api/upload/process" + // Convert PostData to JSON + PostDataJson, err := json.Marshal(PostData) + if err != nil { + log.Panic(err) + return nil, err + } + + // Create a new request with POST method and request body + req, err := http.NewRequest("POST", URL, bytes.NewBuffer(PostDataJson)) + if err != nil { + log.Panic(err) + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-API-Key", accessKey) + + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + log.Panic(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + BodyRead, err := io.ReadAll(resp.Body) + if err != nil { + log.Panic(err) + return nil, err + } + return BodyRead, nil + } else { + log.Panicf("Output from %s : %d", URL, resp.StatusCode) + return nil, nil + } +} diff --git a/src/handler/file.go b/src/handler/file.go new file mode 100644 index 0000000..2bebd4a --- /dev/null +++ b/src/handler/file.go @@ -0,0 +1,60 @@ +package handler + +import ( + "fmt" + "io" + "log" + "mime/multipart" + "os" + "path/filepath" + "time" +) + +func GetTempFilename(Filename string) string { + filename := fmt.Sprintf("./uploads/%d%s", time.Now().UnixNano(), filepath.Ext(Filename)) + return filename +} + +func SaveFile(filename string, file multipart.File) error { + err := os.MkdirAll("./uploads", os.ModePerm) + if err != nil { + log.Panic(err) + return err + } + + dst, err := os.Create(filename) + if err != nil { + log.Panic(err) + return err + } + + defer dst.Close() + + // Copy the uploaded file to the filesystem + // at the specified destination + _, err = io.Copy(dst, file) + if err != nil { + log.Panic(err) + return err + } + return nil +} + +func DeleteFile(filePath string) error { + err := os.Remove(filePath) + if err != nil { + log.Panic(err) + return err + } + return nil +} + +func DiscardFile(file multipart.File) error { + // Clear the data from memory + _, err := io.Copy(io.Discard, file) + if err != nil { + log.Panic(err) + return err + } + return nil +}