diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..1cec4c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Please submit to goravel/goravel (请提交至 goravel/goravel) + url: https://github.com/goravel/goravel/issues/new/choose + about: Thanks in advance for your feedback (提前感谢您的反馈) \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5918f5e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# https://docs.github.com/zh/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: gomod + directory: / + labels: + - "🤖 Dependencies" + schedule: + interval: daily + open-pull-requests-limit: 100 + - package-ecosystem: github-actions + directory: / + labels: + - "🤖 Dependencies" + schedule: + interval: daily \ No newline at end of file diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000..a9f9a12 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,23 @@ +name: Codecov +on: + push: + branches: + - master + pull_request: +jobs: + codecov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: 'stable' + - name: Install dependencies 📦 + run: go mod tidy + - name: Run tests with coverage ✅ + run: go test -v -coverprofile="coverage.out" ./... + - name: Upload coverage report to Codecov 📝 + uses: codecov/codecov-action@v3 + with: + file: ./coverage.out + token: ${{ secrets.CODECOV }} \ No newline at end of file diff --git a/.github/workflows/cr.yml b/.github/workflows/cr.yml new file mode 100644 index 0000000..df197cc --- /dev/null +++ b/.github/workflows/cr.yml @@ -0,0 +1,22 @@ +name: Code Review + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + types: [ opened, reopened, synchronize, labeled ] + +jobs: + test: + if: ${{ contains(github.event.pull_request.labels.*.name, '🚀 Review Ready') }} + runs-on: ubuntu-latest + steps: + - uses: anc95/ChatGPT-CodeReview@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_API_ENDPOINT: https://api.openai-sb.com/v1 + MODEL: gpt-3.5-turbo + PROMPT: "Below is a code patch, please help me do a brief code review on it. Only answer important bug risks and/or important improvement suggestions, Answer must be concisely and short: " \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..1ad3059 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: Lint +on: + push: + branches: + - master + pull_request: +permissions: + contents: read +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: 'stable' + - name: Lint 🎨 + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --timeout=30m ./... \ No newline at end of file diff --git a/.github/workflows/pr-check-title.yml b/.github/workflows/pr-check-title.yml new file mode 100644 index 0000000..2cc8837 --- /dev/null +++ b/.github/workflows/pr-check-title.yml @@ -0,0 +1,11 @@ +name: PR Check Title +on: + pull_request: +jobs: + pr-check-title: + runs-on: ubuntu-latest + steps: + - uses: Slashgear/action-check-pr-title@v4.3.0 + with: + regexp: "^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release|revert)(\\(.+\\))?!?: .+" + helpMessage: "Your PR title is invalid. Please follow the Conventional Commits specification: https://www.conventionalcommits.org/en/v1.0.0/" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..506f012 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test +on: + push: + branches: + - master + pull_request: +env: + CLOUDINARY_ACCESS_KEY_ID: ${{ secrets.CLOUDINARY_ACCESS_KEY_ID }} + CLOUDINARY_ACCESS_KEY_SECRET: ${{ secrets.CLOUDINARY_ACCESS_KEY_SECRET }} + CLOUDINARY_CLOUD: ${{ secrets.CLOUDINARY_CLOUD }} +jobs: + test: + strategy: + matrix: + go: [ '1.20' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + - name: Install dependencies 📦 + run: go mod tidy + - name: Run tests + run: go test ./... \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aded737 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 goravel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 036626c..7102439 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # cloudinary -A cloudinary driver for facades.Storage of Goravel. +A cloudinary driver for `facades.Storage()` of Goravel. ## Version | goravel/cloudinary | cloudinary | goravel/framework | -| --- | --- |-------------------| -| 1.0.* | 1.13.* | v1.12.0 | +|--------------------|------------|-------------------| +| v1.0.0 | v2.3.0 | v1.12.0 | ## Install 1. Add package @@ -24,8 +24,10 @@ A cloudinary driver for facades.Storage of Goravel. 3. Add cloudinary disk to `config/filesystems.go` file ```go // config/filesystems.go + ... + import ( - cloudinaryFacades "github.com/goravel/cloudinary" + cloudinaryfacades "github.com/goravel/cloudinary/facades" "github.com/goravel/framework/filesystem" ) @@ -33,11 +35,18 @@ A cloudinary driver for facades.Storage of Goravel. ... "cloudinary": map[string]any{ "driver": "custom", - "cloud": "your_cloud_name", - "key": "your_api_key", - "secret": "your_api_secret", + "cloud": config.Env("CLOUDINARY_CLOUD"), + "key": config.Env("CLOUDINARY_ACCESS_KEY_ID"), + "secret": config.Env("CLOUDINARY_ACCESS_KEY_SECRET"), + // resource_types is optional + // it is used to limit the types of files that can be uploaded to cloudinary disk + "resource_types": map[string][]string{ + "image": {"png"}, + "video": {}, + "raw": {"txt", "pdf"}, + }, "via": func()(filestystem.Disk, error) { - return cloudinaryFacades.Cloudinary("cloudinary"), nil + return cloudinaryfacades.Cloudinary("cloudinary"), nil // The `cloudinary` value is the `disks` key }, } } diff --git a/cloudinary.go b/cloudinary.go new file mode 100644 index 0000000..c9d7826 --- /dev/null +++ b/cloudinary.go @@ -0,0 +1,384 @@ +package cloudinary + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/cloudinary/cloudinary-go/v2" + "github.com/cloudinary/cloudinary-go/v2/api" + "github.com/cloudinary/cloudinary-go/v2/api/admin" + "github.com/cloudinary/cloudinary-go/v2/api/admin/search" + "github.com/cloudinary/cloudinary-go/v2/api/uploader" + "github.com/gookit/color" + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/filesystem" +) + +type Cloudinary struct { + ctx context.Context + config config.Config + instance *cloudinary.Cloudinary + disk string +} + +func NewCloudinary(ctx context.Context, config config.Config, disk string) (*Cloudinary, error) { + cloudName := config.GetString(fmt.Sprintf("filesystems.disks.%s.cloud", disk)) + apiKey := config.GetString(fmt.Sprintf("filesystems.disks.%s.key", disk)) + apiSecret := config.GetString(fmt.Sprintf("filesystems.disks.%s.secret", disk)) + if apiSecret == "" || apiKey == "" || cloudName == "" { + return nil, fmt.Errorf("cloudinary config not found for disk %s", disk) + } + client, err := cloudinary.NewFromParams(cloudName, apiKey, apiSecret) + if err != nil { + color.Redln("[Cloudinary] init disk error: ", err) + return nil, err + } + return &Cloudinary{ + ctx: ctx, + config: config, + instance: client, + disk: disk, + }, nil +} + +// AllDirectories returns all the directories within a given directory and all its subdirectories. +func (r *Cloudinary) AllDirectories(path string) ([]string, error) { + var result []string + folders, err := r.instance.Admin.SubFolders(r.ctx, admin.SubFoldersParams{ + Folder: validPath(path), + }) + if err != nil { + return nil, err + } + + for _, folder := range folders.Folders { + result = append(result, folder.Path) + // Recursively call to get directories in the subdirectory + subdirs, err := r.AllDirectories(folder.Path) + if err != nil { + return nil, err + } + result = append(result, subdirs...) + } + + return result, nil +} + +// AllFiles returns all the files from the given directory including all its subdirectories. +func (r *Cloudinary) AllFiles(path string) ([]string, error) { + var result []string + assetTypes := []api.AssetType{api.Image, api.Video, api.File} + for _, assetType := range assetTypes { + nextCursor := "" + for { + response, err := r.instance.Admin.Assets(r.ctx, admin.AssetsParams{ + Prefix: validPath(path), + DeliveryType: "upload", + AssetType: assetType, + MaxResults: 500, + NextCursor: nextCursor, + }) + if err != nil { + return nil, err + } + + for _, folder := range response.Assets { + result = append(result, folder.PublicID) + } + + nextCursor = response.NextCursor + if nextCursor == "" { + break // Exit the loop when there is no next cursor + } + } + } + return result, nil +} + +// Copy copies a file to a new location. +func (r *Cloudinary) Copy(source, destination string) error { + result, err := r.instance.Upload.Upload(r.ctx, r.Url(source), uploader.UploadParams{ + PublicID: destination, + ResourceType: "auto", + }) + if err != nil { + return err + } + if result.Error.Message != "" { + return fmt.Errorf("copy file error: %#v", result.Error) + } + return nil +} + +// Delete deletes a file. +func (r *Cloudinary) Delete(file ...string) error { + for _, f := range file { + asset, err := r.getAsset(f) + if err != nil { + return err + } + result, err := r.instance.Upload.Destroy(r.ctx, uploader.DestroyParams{ + PublicID: asset.PublicID, + Invalidate: api.Bool(true), + ResourceType: asset.ResourceType, + }) + if err != nil { + return err + } + if result.Result != "ok" { + return fmt.Errorf("delete file error: %+v", result.Error) + } + } + return nil +} + +// DeleteDirectory deletes a directory. +func (r *Cloudinary) DeleteDirectory(directory string) error { + assetTypes := []api.AssetType{api.Image, api.Video, api.File} + for _, assetType := range assetTypes { + _, err := r.instance.Admin.DeleteAssetsByPrefix(r.ctx, admin.DeleteAssetsByPrefixParams{ + Prefix: []string{validPath(directory)}, + AssetType: assetType, + }) + if err != nil { + return err + } + } + + _, err := r.instance.Admin.DeleteFolder(r.ctx, admin.DeleteFolderParams{ + Folder: directory, + }) + if err != nil { + return err + } + return nil +} + +// Directories return all the directories within a given directory. +func (r *Cloudinary) Directories(path string) ([]string, error) { + folders, err := r.instance.Admin.SubFolders(r.ctx, admin.SubFoldersParams{ + Folder: validPath(path), + }) + if err != nil { + return nil, err + } + var result []string + for _, folder := range folders.Folders { + result = append(result, folder.Path) + } + return result, nil +} + +// Exists checks if a file exists in the Cloudinary storage. +func (r *Cloudinary) Exists(file string) bool { + _, err := r.getAsset(file) + return err == nil +} + +// Files returns all the files from the given directory. +func (r *Cloudinary) Files(path string) ([]string, error) { + folders, err := r.instance.Admin.Search(r.ctx, search.Query{ + Expression: fmt.Sprintf("folder:%s", validPath(path)), + SortBy: []search.SortByField{ + {"public_id": search.Ascending}, + }, + }) + if err != nil { + return nil, err + } + var result []string + for _, folder := range folders.Assets { + result = append(result, folder.PublicID) + } + return result, nil +} + +// Get returns the contents of a file. +func (r *Cloudinary) Get(file string) (string, error) { + rawContent, err := GetRawContent(r.Url(file)) + if err != nil { + return "", err + } + return string(rawContent), nil +} + +// LastModified returns the last modified time of a file. +func (r *Cloudinary) LastModified(file string) (time.Time, error) { + resource, err := r.getAsset(file) + if err != nil { + return time.Time{}, err + } + return resource.CreatedAt, nil +} + +// MakeDirectory creates a directory. +func (r *Cloudinary) MakeDirectory(directory string) error { + result, err := r.instance.Admin.CreateFolder(r.ctx, admin.CreateFolderParams{ + Folder: directory, + }) + if err != nil { + return err + } + if !result.Success { + return fmt.Errorf("make directory error: %+v", result.Error) + } + return nil +} + +// MimeType returns the mime-type of a file. +func (r *Cloudinary) MimeType(file string) (string, error) { + resource, err := r.getAsset(file) + if err != nil { + return "", err + } + // Check if the resource format is empty, return only the resource type. + if resource.Format == "" { + return resource.ResourceType, nil + } + // Replace 'jpg' with 'jpeg' in the format if it is 'jpg' + format := strings.ReplaceAll(resource.Format, "jpg", "jpeg") + + return resource.ResourceType + "/" + format, nil +} + +// Missing checks if a file is missing. +func (r *Cloudinary) Missing(file string) bool { + return !r.Exists(file) +} + +// Move moves a file to a new location. +func (r *Cloudinary) Move(source, destination string) error { + asset, err := r.getAsset(source) + if err != nil { + return err + } + rename, err := r.instance.Upload.Rename(r.ctx, uploader.RenameParams{ + FromPublicID: asset.PublicID, + ToPublicID: destination, + ResourceType: asset.ResourceType, + }) + if err != nil { + return err + } + if rename.Error != nil { + return fmt.Errorf("move file error: %#v", rename.Error) + } + return nil +} + +// Path returns the full path for a file. +func (r *Cloudinary) Path(file string) string { + return validPath(file) +} + +// Put stores a new file on the disk. +func (r *Cloudinary) Put(file, content string) error { + tempFile, err := r.tempFile(content) + defer os.Remove(tempFile.Name()) + if err != nil { + return err + } + _, err = r.instance.Upload.Upload(r.ctx, tempFile.Name(), uploader.UploadParams{ + PublicID: file, + UseFilename: api.Bool(true), + UniqueFilename: api.Bool(false), + ResourceType: "auto", + }) + return err +} + +// PutFile stores a new file on the disk. +func (r *Cloudinary) PutFile(path string, source filesystem.File) (string, error) { + uploadResult, err := r.instance.Upload.Upload(r.ctx, source.File(), uploader.UploadParams{ + Folder: validPath(path), + UseFilename: api.Bool(true), + UniqueFilename: api.Bool(false), + }) + if err != nil { + return "", err + } + return uploadResult.PublicID, nil +} + +// PutFileAs stores a new file on the disk. +func (r *Cloudinary) PutFileAs(path string, source filesystem.File, name string) (string, error) { + uploadResult, err := r.instance.Upload.Upload(r.ctx, source.File(), uploader.UploadParams{ + Folder: validPath(path), + PublicID: name, + UseFilename: api.Bool(true), + UniqueFilename: api.Bool(false), + }) + if err != nil { + return "", err + } + return uploadResult.PublicID, nil +} + +// Size returns the file size of a given file. +func (r *Cloudinary) Size(file string) (int64, error) { + resource, err := r.getAsset(file) + if err != nil { + return 0, err + } + return int64(resource.Bytes), nil +} + +// TemporaryUrl get the temporary url of a file. +func (r *Cloudinary) TemporaryUrl(file string, time time.Time) (string, error) { + return "", errors.New("cloudinary doesn't support temporary url") +} + +// WithContext sets the context for the driver. +func (r *Cloudinary) WithContext(ctx context.Context) filesystem.Driver { + driver, err := NewCloudinary(ctx, r.config, r.disk) + if err != nil { + color.Redf("[Cloudinary] init disk error: %+v\n", err) + return nil + } + return driver +} + +// Url returns the url for a file. +func (r *Cloudinary) Url(file string) string { + asset, err := r.getAsset(file) + if err != nil { + return "" + } + return asset.SecureURL +} + +func (r *Cloudinary) getAsset(path string) (*uploader.ExplicitResult, error) { + // TODO: Search if there is a better way to get asset info + assetTypes := []api.AssetType{api.Image, api.Video, api.File} + for _, assetType := range assetTypes { + explicit, err := r.instance.Upload.Explicit(r.ctx, uploader.ExplicitParams{ + PublicID: path, + Type: "upload", + ResourceType: string(assetType), + }) + if err != nil { + return nil, err + } + if explicit.Error.Message == "" { + return explicit, nil + } + } + return nil, errors.New("file not found") +} + +func (r *Cloudinary) tempFile(content string) (*os.File, error) { + tempFile, err := os.CreateTemp(os.TempDir(), "goravel-") + if err != nil { + return nil, err + } + + if _, err := tempFile.WriteString(content); err != nil { + return nil, err + } + + return tempFile, nil +} diff --git a/cloudinary_test.go b/cloudinary_test.go new file mode 100644 index 0000000..2193723 --- /dev/null +++ b/cloudinary_test.go @@ -0,0 +1,428 @@ +package cloudinary + +import ( + "context" + "crypto/rand" + "io" + "math/big" + "mime" + "net/http" + "os" + "regexp" + "testing" + "time" + + "github.com/gookit/color" + configmocks "github.com/goravel/framework/contracts/config/mocks" + contractsfilesystem "github.com/goravel/framework/contracts/filesystem" + "github.com/goravel/framework/support/carbon" + "github.com/stretchr/testify/assert" +) + +func TestStorage(t *testing.T) { + if os.Getenv("CLOUDINARY_ACCESS_KEY_ID") == "" { + color.Redln("No filesystem tests run, please add Cloudinary configuration: CLOUDINARY_ACCESS_KEY_ID= CLOUDINARY_ACCESS_KEY_SECRET= CLOUDINARY_CLOUD= go test ./...") + return + } + + assert.Nil(t, os.WriteFile("test.txt", []byte("Goravel"), 0644)) + + mockConfig := &configmocks.Config{} + mockConfig.On("GetString", "filesystems.disks.cloudinary.key").Return(os.Getenv("CLOUDINARY_ACCESS_KEY_ID")) + mockConfig.On("GetString", "filesystems.disks.cloudinary.secret").Return(os.Getenv("CLOUDINARY_ACCESS_KEY_SECRET")) + mockConfig.On("GetString", "filesystems.disks.cloudinary.cloud").Return(os.Getenv("CLOUDINARY_CLOUD")) + + var driver contractsfilesystem.Driver + randNum, err := rand.Int(rand.Reader, big.NewInt(1000)) + rootFolder := randNum.String() + "/" + assert.Nil(t, err) + driver, err = NewCloudinary(context.Background(), mockConfig, "cloudinary") + assert.NotNil(t, driver) + assert.Nil(t, err) + + tests := []struct { + name string + setup func() + }{ + { + name: "AllDirectories", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"AllDirectories/1.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllDirectories/2.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllDirectories/3/3.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllDirectories/3/5/6/6.txt", "Goravel")) + assert.Nil(t, driver.MakeDirectory(rootFolder+"AllDirectories/3/4")) + assert.True(t, driver.Exists(rootFolder+"AllDirectories/1.txt")) + assert.True(t, driver.Exists(rootFolder+"AllDirectories/2.txt")) + assert.True(t, driver.Exists(rootFolder+"AllDirectories/3/3.txt")) + assert.True(t, driver.Exists(rootFolder+"AllDirectories/3/5/6/6.txt")) + files, err := driver.AllDirectories(rootFolder + "AllDirectories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllDirectories/3", rootFolder + "AllDirectories/3/4", rootFolder + "AllDirectories/3/5", rootFolder + "AllDirectories/3/5/6"}, files) + files, err = driver.AllDirectories("./" + rootFolder + "AllDirectories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllDirectories/3", rootFolder + "AllDirectories/3/4", rootFolder + "AllDirectories/3/5", rootFolder + "AllDirectories/3/5/6"}, files) + files, err = driver.AllDirectories("/" + rootFolder + "AllDirectories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllDirectories/3", rootFolder + "AllDirectories/3/4", rootFolder + "AllDirectories/3/5", rootFolder + "AllDirectories/3/5/6"}, files) + files, err = driver.AllDirectories("./" + rootFolder + "AllDirectories/") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllDirectories/3", rootFolder + "AllDirectories/3/4", rootFolder + "AllDirectories/3/5", rootFolder + "AllDirectories/3/5/6"}, files) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"AllDirectories")) + }, + }, + { + name: "AllFiles", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"AllFiles/1.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllFiles/2.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllFiles/3/3.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"AllFiles/3/4/4.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"AllFiles/1.txt")) + assert.True(t, driver.Exists(rootFolder+"AllFiles/2.txt")) + assert.True(t, driver.Exists(rootFolder+"AllFiles/3/3.txt")) + assert.True(t, driver.Exists(rootFolder+"AllFiles/3/4/4.txt")) + files, err := driver.AllFiles(rootFolder + "AllFiles") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllFiles/1.txt", rootFolder + "AllFiles/2.txt", rootFolder + "AllFiles/3/3.txt", rootFolder + "AllFiles/3/4/4.txt"}, files) + files, err = driver.AllFiles("./" + rootFolder + "AllFiles") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllFiles/1.txt", rootFolder + "AllFiles/2.txt", rootFolder + "AllFiles/3/3.txt", rootFolder + "AllFiles/3/4/4.txt"}, files) + files, err = driver.AllFiles("/" + rootFolder + "AllFiles") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllFiles/1.txt", rootFolder + "AllFiles/2.txt", rootFolder + "AllFiles/3/3.txt", rootFolder + "AllFiles/3/4/4.txt"}, files) + files, err = driver.AllFiles("./" + rootFolder + "AllFiles/") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "AllFiles/1.txt", rootFolder + "AllFiles/2.txt", rootFolder + "AllFiles/3/3.txt", rootFolder + "AllFiles/3/4/4.txt"}, files) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"AllFiles")) + }, + }, + { + name: "Copy", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Copy/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Copy/1.txt")) + assert.Nil(t, driver.Copy(rootFolder+"Copy/1.txt", rootFolder+"Copy1/1.txt")) + assert.True(t, driver.Exists(rootFolder+"Copy/1.txt")) + assert.True(t, driver.Exists(rootFolder+"Copy1/1.txt")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Copy")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Copy1")) + }, + }, + { + name: "Delete", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Delete/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Delete/1.txt")) + assert.Nil(t, driver.Delete(rootFolder+"Delete/1.txt")) + assert.True(t, driver.Missing(rootFolder+"Delete/1.txt")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Delete")) + }, + }, + { + name: "DeleteDirectory", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"DeleteDirectory/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"DeleteDirectory/1.txt")) + fileInfo := &File{path: "logo.png"} + path, err := driver.PutFile(rootFolder+"DeleteDirectory", fileInfo) + assert.Nil(t, err) + assert.True(t, driver.Exists(path)) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"DeleteDirectory")) + assert.True(t, driver.Missing(rootFolder+"DeleteDirectory/1.txt")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"DeleteDirectory")) + }, + }, + { + name: "Directories", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Directories/1.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Directories/2.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Directories/3/3.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Directories/3/5/5.txt", "Goravel")) + assert.Nil(t, driver.MakeDirectory(rootFolder+"Directories/3/4")) + assert.True(t, driver.Exists(rootFolder+"Directories/1.txt")) + assert.True(t, driver.Exists(rootFolder+"Directories/2.txt")) + assert.True(t, driver.Exists(rootFolder+"Directories/3/3.txt")) + assert.True(t, driver.Exists(rootFolder+"Directories/3/5/5.txt")) + files, err := driver.Directories(rootFolder + "Directories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Directories/3"}, files) + files, err = driver.Directories("./" + rootFolder + "Directories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Directories/3"}, files) + files, err = driver.Directories("/" + rootFolder + "Directories") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Directories/3"}, files) + files, err = driver.Directories("./" + rootFolder + "Directories/") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Directories/3"}, files) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Directories")) + }, + }, + { + name: "Files", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Files/1.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Files/2.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Files/3/3.txt", "Goravel")) + assert.Nil(t, driver.Put(rootFolder+"Files/3/4/4.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Files/1.txt")) + assert.True(t, driver.Exists(rootFolder+"Files/2.txt")) + assert.True(t, driver.Exists(rootFolder+"Files/3/3.txt")) + assert.True(t, driver.Exists(rootFolder+"Files/3/4/4.txt")) + files, err := driver.Files(rootFolder + "Files") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Files/1.txt", rootFolder + "Files/2.txt"}, files) + files, err = driver.Files("./" + rootFolder + "Files") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Files/1.txt", rootFolder + "Files/2.txt"}, files) + files, err = driver.Files("/" + rootFolder + "Files") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Files/1.txt", rootFolder + "Files/2.txt"}, files) + files, err = driver.Files("./" + rootFolder + "Files/") + assert.Nil(t, err) + assert.Equal(t, []string{rootFolder + "Files/1.txt", rootFolder + "Files/2.txt"}, files) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Files")) + }, + }, + { + name: "Get", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Get/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Get/1.txt")) + data, err := driver.Get(rootFolder + "Get/1.txt") + assert.Nil(t, err) + assert.Equal(t, "Goravel", data) + length, err := driver.Size(rootFolder + "Get/1.txt") + assert.Nil(t, err) + assert.Equal(t, int64(7), length) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Get")) + }, + }, + { + name: "LastModified", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"LastModified/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"LastModified/1.txt")) + assert.Nil(t, driver.Put(rootFolder+"LastModified/1.txt", "Goravel-new")) + date, err := driver.LastModified(rootFolder + "LastModified/1.txt") + assert.Nil(t, err) + + l, err := time.LoadLocation("UTC") + assert.Nil(t, err) + assert.Equal(t, carbon.Now().ToStdTime().In(l).Format("2006-01-02 15"), date.Format("2006-01-02 15")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"LastModified")) + }, + }, + { + name: "MakeDirectory", + setup: func() { + assert.Nil(t, driver.MakeDirectory(rootFolder+"MakeDirectory1/")) + assert.Nil(t, driver.MakeDirectory(rootFolder+"MakeDirectory2")) + assert.Nil(t, driver.MakeDirectory(rootFolder+"MakeDirectory3/MakeDirectory4")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"MakeDirectory1")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"MakeDirectory2")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"MakeDirectory3")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"MakeDirectory4")) + }, + }, + { + name: "MimeType", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"MimeType/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"MimeType/1.txt")) + mimeType, err := driver.MimeType(rootFolder + "MimeType/1.txt") + assert.Nil(t, err) + mediaType, _, err := mime.ParseMediaType(mimeType) + assert.Nil(t, err) + assert.Equal(t, "raw", mediaType) + + fileInfo := &File{path: "logo.png"} + path, err := driver.PutFile(rootFolder+"MimeType", fileInfo) + assert.Nil(t, err) + assert.True(t, driver.Exists(path)) + mimeType, err = driver.MimeType(path) + assert.Nil(t, err) + assert.Equal(t, "image/png", mimeType) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"MimeType")) + }, + }, + { + name: "Move", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Move/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Move/1.txt")) + assert.Nil(t, driver.Move(rootFolder+"Move/1.txt", rootFolder+"Move1/1.txt")) + assert.True(t, driver.Missing(rootFolder+"Move/1.txt")) + assert.True(t, driver.Exists(rootFolder+"Move1/1.txt")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Move")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Move1")) + }, + }, + { + name: "Put", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Put/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Put/1.txt")) + assert.True(t, driver.Missing(rootFolder+"Put/2.txt")) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Put")) + }, + }, + { + name: "PutFile_Image", + setup: func() { + fileInfo := &File{path: "logo.png"} + path, err := driver.PutFile(rootFolder+"PutFile1", fileInfo) + assert.Nil(t, err) + assert.True(t, driver.Exists(path)) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"PutFile1")) + }, + }, + { + name: "PutFile_Text", + setup: func() { + fileInfo := &File{path: "test.txt"} + path, err := driver.PutFile(rootFolder+"PutFile", fileInfo) + assert.Nil(t, err) + assert.True(t, driver.Exists(path)) + data, err := driver.Get(path) + assert.Nil(t, err) + assert.Equal(t, "Goravel", data) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"PutFile")) + }, + }, + { + name: "PutFileAs_Text", + setup: func() { + fileInfo := &File{path: "test.txt"} + path, err := driver.PutFileAs(rootFolder+"PutFileAs", fileInfo, "text") + assert.Nil(t, err) + assert.Equal(t, rootFolder+"PutFileAs/text.txt", path) + assert.True(t, driver.Exists(path)) + data, err := driver.Get(path) + assert.Nil(t, err) + assert.Equal(t, "Goravel", data) + + path, err = driver.PutFileAs(rootFolder+"PutFileAs", fileInfo, "text1.txt") + assert.Nil(t, err) + assert.Equal(t, rootFolder+"PutFileAs/text1.txt", path) + assert.True(t, driver.Exists(path)) + data, err = driver.Get(path) + assert.Nil(t, err) + assert.Equal(t, "Goravel", data) + + assert.Nil(t, driver.DeleteDirectory(rootFolder+"PutFileAs")) + }, + }, + { + name: "PutFileAs_Image", + setup: func() { + fileInfo := &File{path: "logo.png"} + path, err := driver.PutFileAs(rootFolder+"PutFileAs1", fileInfo, "image") + assert.Nil(t, err) + assert.Equal(t, rootFolder+"PutFileAs1/image", path) + assert.True(t, driver.Exists(path)) + + path, err = driver.PutFileAs(rootFolder+"PutFileAs1", fileInfo, "image1.png") + assert.Nil(t, err) + assert.Equal(t, rootFolder+"PutFileAs1/image1.png", path) + assert.True(t, driver.Exists(path)) + + assert.Nil(t, driver.DeleteDirectory(rootFolder+"PutFileAs1")) + }, + }, + { + name: "Size", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Size/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Size/1.txt")) + length, err := driver.Size(rootFolder + "Size/1.txt") + assert.Nil(t, err) + assert.Equal(t, int64(7), length) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Size")) + }, + }, + { + name: "TemporaryUrl", + setup: func() { + url, err := driver.TemporaryUrl(rootFolder+"TemporaryUrl/1.txt", time.Now().Add(5*time.Second)) + assert.NotNil(t, err) + assert.Empty(t, url) + }, + }, + { + name: "Url", + setup: func() { + assert.Nil(t, driver.Put(rootFolder+"Url/1.txt", "Goravel")) + assert.True(t, driver.Exists(rootFolder+"Url/1.txt")) + url := driver.Url(rootFolder + "Url/1.txt") + matches := regexp.MustCompile(`/v\d+/(.+)`).FindStringSubmatch(url) + assert.Equal(t, rootFolder+"Url/1.txt", matches[1]) + resp, err := http.Get(url) + assert.Nil(t, err) + content, err := io.ReadAll(resp.Body) + assert.Nil(t, resp.Body.Close()) + assert.Nil(t, err) + assert.Equal(t, "Goravel", string(content)) + assert.Nil(t, driver.DeleteDirectory(rootFolder+"Url")) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.setup() + }) + } + + assert.Nil(t, os.Remove("test.txt")) +} + +type File struct { + path string +} + +func (f *File) Disk(disk string) contractsfilesystem.File { + return &File{} +} + +func (f *File) Extension() (string, error) { + return "", nil +} + +func (f *File) File() string { + return f.path +} + +func (f *File) GetClientOriginalName() string { + return "" +} + +func (f *File) GetClientOriginalExtension() string { + return "" +} + +func (f *File) HashName(path ...string) string { + return "" +} + +func (f *File) LastModified() (time.Time, error) { + return time.Now(), nil +} + +func (f *File) MimeType() (string, error) { + return "", nil +} + +func (f *File) Size() (int64, error) { + return 0, nil +} + +func (f *File) Store(path string) (string, error) { + return "", nil +} + +func (f *File) StoreAs(path string, name string) (string, error) { + return "", nil +} diff --git a/facades/cloudinary.go b/facades/cloudinary.go new file mode 100644 index 0000000..a6a0d4d --- /dev/null +++ b/facades/cloudinary.go @@ -0,0 +1,19 @@ +package facades + +import ( + "log" + + "github.com/goravel/framework/contracts/filesystem" + + "github.com/goravel/cloudinary" +) + +func Cloudinary(disk string) filesystem.Driver { + instance, err := cloudinary.App.MakeWith(cloudinary.Binding, map[string]any{"disk": disk}) + if err != nil { + log.Println(err) + return nil + } + + return instance.(*cloudinary.Cloudinary) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6b42e4a --- /dev/null +++ b/go.mod @@ -0,0 +1,30 @@ +module github.com/goravel/cloudinary + +go 1.20 + +require ( + github.com/cloudinary/cloudinary-go/v2 v2.3.0 + github.com/gookit/color v1.5.3 + github.com/goravel/framework v1.12.7 + github.com/stretchr/testify v1.8.4 + golang.org/x/net v0.12.0 +) + +require ( + github.com/creasty/defaults v1.5.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang-module/carbon/v2 v2.2.3 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/schema v1.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.56.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3fc4dc6 --- /dev/null +++ b/go.sum @@ -0,0 +1,69 @@ +github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/cloudinary/cloudinary-go/v2 v2.3.0 h1:EmnVzTm6JRt7j1eyIIaNOv0F3646rpy2VQ5+DTpw6Kk= +github.com/cloudinary/cloudinary-go/v2 v2.3.0/go.mod h1:jtSxa6xbzvu4IwChRJVDcXwVXrTRczhbvq3Z1VSoFdk= +github.com/creasty/defaults v1.5.1 h1:j8WexcS3d/t4ZmllX4GEkl4wIB/trOr035ajcLHCISM= +github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/golang-module/carbon/v2 v2.2.3 h1:WvGIc5+qzq9drNzH+Gnjh1TZ0JgDY/IA+m2Dvk7Qm4Q= +github.com/golang-module/carbon/v2 v2.2.3/go.mod h1:LdzRApgmDT/wt0eNT8MEJbHfJdSqCtT46uZhfF30dqI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= +github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= +github.com/goravel/framework v1.12.7 h1:jaofjOXpuSs2ISOM7C/+RTo6N22ZFbDY2R+L127t3lU= +github.com/goravel/framework v1.12.7/go.mod h1:+dssXEcaQvWKjcudzhD9SSwKTMwpziEmL+Cw4rTZRNM= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/heimdalr/dag v1.0.1/go.mod h1:t+ZkR+sjKL4xhlE1B9rwpvwfo+x+2R0363efS+Oghns= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..1d68459 Binary files /dev/null and b/logo.png differ diff --git a/service_provider.go b/service_provider.go new file mode 100644 index 0000000..7114807 --- /dev/null +++ b/service_provider.go @@ -0,0 +1,24 @@ +package cloudinary + +import ( + "github.com/goravel/framework/contracts/foundation" + "golang.org/x/net/context" +) + +const Binding = "goravel.cloudinary" + +var App foundation.Application + +type ServiceProvider struct { +} + +func (receiver *ServiceProvider) Register(app foundation.Application) { + App = app + + app.BindWith(Binding, func(app foundation.Application, parameters map[string]any) (any, error) { + return NewCloudinary(context.Background(), app.MakeConfig(), parameters["disk"].(string)) + }) +} +func (receiver *ServiceProvider) Boot(app foundation.Application) { + +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..9e7e8fb --- /dev/null +++ b/utils.go @@ -0,0 +1,36 @@ +package cloudinary + +import ( + "fmt" + "io" + "net/http" + "path/filepath" + "strings" +) + +// GetRawContent retrieves the raw content of a file from the provided URL. +func GetRawContent(url string) ([]byte, error) { + // Make an HTTP GET request to fetch the file data + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("error fetching raw content: %w", err) + } + + rawContent, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if err := resp.Body.Close(); err != nil { + return nil, err + } + + return rawContent, nil +} + +func validPath(path string) string { + realPath := strings.TrimPrefix(path, "."+string(filepath.Separator)) + realPath = strings.TrimPrefix(realPath, string(filepath.Separator)) + realPath = strings.TrimPrefix(realPath, ".") + realPath = strings.TrimSuffix(realPath, string(filepath.Separator)) + return realPath +}