diff --git a/example/.env.example b/example/.env.example new file mode 100644 index 0000000..4c82b10 --- /dev/null +++ b/example/.env.example @@ -0,0 +1 @@ +CLOUDINARY_URL=cloudinary://:@ \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..64ae7f3 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env \ No newline at end of file diff --git a/example/example.go b/example/example.go index aed6201..21a9c05 100644 --- a/example/example.go +++ b/example/example.go @@ -2,80 +2,128 @@ package main import ( "context" + "fmt" + "log" + "strings" + "github.com/cloudinary/cloudinary-go/v2" "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" - "log" +) + + +const ( + imageFilePath = "https://res.cloudinary.com/demo/image/upload/sample.jpg" + videoFilePath = "https://res.cloudinary.com/demo/video/upload/dog.mp4" ) func main() { + // Start by creating a new instance of Cloudinary using CLOUDINARY_URL environment variable. // Alternatively you can use cloudinary.NewFromParams() or cloudinary.NewFromURL(). - var cld, err = cloudinary.New() + cld, err := cloudinary.New() + if err != nil { + log.Fatalf("failed to initialize Cloudinary: %v", err) + } + ctx := context.Background() + + uploadResult := uploadImage(cld, ctx) + log.Println(uploadResult.SecureURL) + + imageURL := buildImageURL(cld) + log.Printf("Image URL: %s", imageURL) + + getAssetDetails(cld, ctx) + + searchAssets(cld, ctx) + + // Generate responsive srcset for the "logo" image + srcset, err := generateResponsiveSrcSet(cld, "logo") if err != nil { - log.Fatalf("Failed to intialize Cloudinary, %v", err) + log.Fatalf("failed to build srcset: %v", err) } + log.Printf("SrcSet: %s", srcset) + + // Upload a video with transformations applied on upload + videoResult := uploadVideoWithTransformations(cld, ctx) + log.Println(videoResult.SecureURL) + + // Delete a single asset by Public ID + // Note: Once an asset is deleted, the associated URL will no longer work. + // If you want to access the uploaded asset via the logged URL, comment out this deleteAsset() call. + deleteAsset(cld, ctx, "logo") + + // Bulk delete multiple assets by Public IDs + // Note: Assets deleted here will also become inaccessible through their URLs. + // Comment out this bulkDeleteAssets() call if you want to keep them available for testing. + bulkDeleteAssets(cld, ctx, []string{"old_img1", "old_img2", "old_img3"}) + + // List assets with pagination (retrieves up to 2 assets per page) + listAssetsWithPagination(cld, ctx, 2) +} - var ctx = context.Background() - - // Upload an image to your Cloudinary account from a specified URL. - // - // Alternatively you can provide a path to a local file on your filesystem, - // base64 encoded string, io.Reader and more. - // - // For additional information see: - // https://cloudinary.com/documentation/upload_images - // - // Upload can be greatly customized by specifying uploader.UploadParams, - // in this case we set the Public ID of the uploaded asset to "logo". +// Upload an image to your Cloudinary account from a specified URL. +// +// Alternatively you can provide a path to a local file on your filesystem, +// base64 encoded string, io.Reader and more. +// +// For additional information see: +// https://cloudinary.com/documentation/upload_images +// +// Upload can be greatly customized by specifying uploader.UploadParams, +// in this case we set the Public ID of the uploaded asset to "logo". +func uploadImage(cld *cloudinary.Cloudinary, ctx context.Context) *uploader.UploadResult { uploadResult, err := cld.Upload.Upload( ctx, - "https://cloudinary-res.cloudinary.com/image/upload/cloudinary_logo.png", - uploader.UploadParams{PublicID: "logo"}) + imageFilePath, + uploader.UploadParams{PublicID: "logo"}, + ) if err != nil { - log.Fatalf("Failed to upload file, %v\n", err) + log.Fatalf("failed to upload file: %v", err) } - log.Println(uploadResult.SecureURL) - // Prints something like: - // https://res.cloudinary.com//image/upload/v1615875158/logo.png - // uploadResult contains useful information about the asset, like Width, Height, Format, etc. // See uploader.UploadResult struct for more details. + return uploadResult +} - // We can also build an image URL using the Public ID. +// We can also build an image URL using the Public ID. +// +// Image can be further transformed and optimized as follows: +// Here the image is scaled to the width of 500 pixels. Format and quality are set to "auto". +func buildImageURL(cld *cloudinary.Cloudinary) string { image, err := cld.Image("logo") if err != nil { - log.Fatalf("Failed to build image URL, %v\n", err) + log.Fatalf("failed to build image URL: %v", err) } - // Image can be further transformed and optimized as follows: image.Transformation = "c_scale,w_500/f_auto/q_auto" - // Here the image is scaled to the width of 500 pixes. Format and quality are set to "auto". imageURL, err := image.String() if err != nil { - log.Fatalf("Failed to serialize image URL, %v\n", err) + log.Fatalf("failed to serialize image URL: %v", err) } - log.Printf("Image URL: %s", imageURL) - // Prints something like: - // https://res.cloudinary.com//image/upload/c_scale,w_500/f_auto/q_auto/logo + return imageURL +} - // Now we can use Admin API to see the details about the asset. - // The request can be customised by providing AssetParams. +// Now we can use Admin API to see the details about the asset. +// The request can be customised by providing AssetParams. +func getAssetDetails(cld *cloudinary.Cloudinary, ctx context.Context) { asset, err := cld.Admin.Asset(ctx, admin.AssetParams{PublicID: "logo"}) if err != nil { - log.Fatalf("Failed to get asset details, %v\n", err) + log.Fatalf("failed to get asset details: %v", err) } // Print some basic information about the asset. log.Printf("Public ID: %v, URL: %v\n", asset.PublicID, asset.SecureURL) +} - // Cloudinary also provides a very flexible Search API for filtering and retrieving - // information on all the assets in your account with the help of query expressions - // in a Lucene-like query language. +// Cloudinary also provides a very flexible Search API for filtering and retrieving +// information on all the assets in your account with the help of query expressions +// in a Lucene-like query language. +func searchAssets(cld *cloudinary.Cloudinary, ctx context.Context) { searchQuery := search.Query{ Expression: "resource_type:image AND uploaded_at>1d AND bytes<1m", SortBy: []search.SortByField{{"created_at": search.Descending}}, @@ -83,9 +131,8 @@ func main() { } searchResult, err := cld.Admin.Search(ctx, searchQuery) - if err != nil { - log.Fatalf("Failed to search for assets, %v\n", err) + log.Fatalf("failed to search for assets: %v", err) } log.Printf("Assets found: %v\n", searchResult.TotalCount) @@ -94,3 +141,110 @@ func main() { log.Printf("Public ID: %v, URL: %v\n", asset.PublicID, asset.SecureURL) } } + +// Generate a responsive srcset string for a given public ID by building URLs at multiple widths. +func generateResponsiveSrcSet(cld *cloudinary.Cloudinary, publicID string) (string, error) { + widths := []int{200, 400, 800, 1200} + var parts []string + + for _, w := range widths { + img, err := cld.Image(publicID) + if err != nil { + return "", fmt.Errorf("failed to initialize image %s: %w", publicID, err) + } + img.Transformation = fmt.Sprintf("c_scale,w_%d/f_auto/q_auto", w) + + url, err := img.String() + if err != nil { + return "", fmt.Errorf("failed to build URL for width %d: %w", w, err) + } + parts = append(parts, fmt.Sprintf("%s %dw", url, w)) + } + + return strings.Join(parts, ", "), nil +} + +// Upload a video with transformations applied on upload to generate posters or clips. +func uploadVideoWithTransformations(cld *cloudinary.Cloudinary, ctx context.Context) *uploader.UploadResult { + uploadResult, err := cld.Upload.Upload( + ctx, + videoFilePath, + uploader.UploadParams{ + PublicID: "promo_clip", + Folder: "videos/promos", + ResourceType: "video", + Eager: "c_fill,h_360,w_640,b_black|c_crop,ar_16:9,du_15", + Tags: []string{"video", "promo"}, + }, + ) + if err != nil { + log.Fatalf("failed to upload video: %v", err) + } + + // uploadResult.SecureURL points to the original video; uploadResult.Eager to derivatives. + return uploadResult +} + +// Delete a single asset by its Public ID. +func deleteAsset(cld *cloudinary.Cloudinary, ctx context.Context, publicID string) { + _, err := cld.Upload.Destroy( + ctx, + uploader.DestroyParams{PublicID: publicID, ResourceType: "image"}, + ) + if err != nil { + log.Fatalf("failed to delete asset %s: %v", publicID, err) + } + + // Asset deleted successfully. +} + +// Bulk delete multiple assets by their Public IDs. +func bulkDeleteAssets(cld *cloudinary.Cloudinary, ctx context.Context, publicIDs []string) { + resp, err := cld.Admin.DeleteAssets( + ctx, + admin.DeleteAssetsParams{PublicIDs: publicIDs}, + ) + if err != nil { + log.Fatalf("failed to bulk delete assets: %v", err) + } + + // Print details of deleted assets. + for publicID, status := range resp.Deleted { + log.Printf("Public ID: %s, Status: %s", publicID, status) + } + + // Print how many were deleted. + log.Printf("Deleted assets count: %d", len(resp.Deleted)) +} + +// List all assets in pages of up to maxResults, using cursor-based pagination. +func listAssetsWithPagination(cld *cloudinary.Cloudinary, ctx context.Context, maxResults int) { + nextCursor := "" + loopCount := 0 + maxLoops := 2 // Limit the number of loops for demo purposes + + for { + if loopCount >= maxLoops { + log.Println("Reached maximum number of loops for demo purposes.") + break + } + + page, err := cld.Admin.Assets( + ctx, + admin.AssetsParams{MaxResults: maxResults, NextCursor: nextCursor}, + ) + if err != nil { + log.Fatalf("failed to list assets: %v", err) + } + + for _, asset := range page.Assets { + log.Printf("Public ID: %v, URL: %v", asset.PublicID, asset.SecureURL) + } + + if page.NextCursor == "" { + break // no more assets + } + nextCursor = page.NextCursor + loopCount++ + } +}