services/collage/go/main.go (133 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "fmt" "io" "log" "net/http" "os" "path/filepath" "cloud.google.com/go/firestore" "cloud.google.com/go/storage" "github.com/kelseyhightower/envconfig" "gopkg.in/gographics/imagick.v2/imagick" ) type config struct { BucketThumbnails string `split_words:"true"` Port int `default:"8080"` } func main() { var env config envconfig.Process("", &env) http.HandleFunc("/", handleRequest(env)) log.Printf("Start listening on port %d", env.Port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", env.Port), nil)) } func handleRequest(env config) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.URL.Path != "/" { http.Error(w, "404 not found.", http.StatusNotFound) return } if r.Method != "POST" { http.Error(w, "404 Not Found.", http.StatusNotFound) return } log.Printf("Got POST on /") fireClient, err := firestore.NewClient(ctx, firestore.DetectProjectID) if err != nil { log.Printf("Failed to get 'pictures' firestore collection: %v", err) http.Error(w, "Failed to get 'pictures' firestore collection", http.StatusInternalServerError) return } docs, err := fireClient.Collection("pictures").OrderBy("created", firestore.Desc).Limit(4).Documents(ctx).GetAll() if err != nil { log.Printf("Failed to get last pictures: %v", err) http.Error(w, "Failed to get last pictures", http.StatusInternalServerError) return } if len(docs) != 4 { w.WriteHeader(http.StatusNoContent) fmt.Fprint(w, "No collage created.") return } storageClient, err := storage.NewClient(ctx) if err != nil { log.Printf("Error getting storage client: %v", err) http.Error(w, "Error getting storage client", http.StatusInternalServerError) return } localFiles := make([]string, 4) for i, doc := range docs { file := doc.Ref.ID localFile := filepath.Join("/tmp", file) localFiles[i] = localFile err = downloadObject(ctx, storageClient, env.BucketThumbnails, file, localFile) if err != nil { http.Error(w, "Error downloading image from bucket", http.StatusInternalServerError) return } defer os.Remove(localFile) } log.Print("Downloaded all thumbnails") collagePath := filepath.Join("/tmp", "collage.png") imagick.Initialize() defer imagick.Terminate() args := []string{"convert", "(", localFiles[0], localFiles[1], "+append", ")", "(", localFiles[2], localFiles[3], "+append", ")", "-size", "400x400", "xc:none", "-background", "none", "-append", "-trim", collagePath, } _, err = imagick.ConvertImageCommand(args) if err != nil { log.Printf("Error during convert command: %v", err) http.Error(w, "Error creating collage", http.StatusInternalServerError) return } err = uploadObject(ctx, storageClient, collagePath, env.BucketThumbnails, "collage.png") if err != nil { http.Error(w, "Error creating collage.png on bucket", http.StatusInternalServerError) return } defer os.Remove(collagePath) w.WriteHeader(http.StatusNoContent) } } // downloadObject copies an object of a given bucket to a local file func downloadObject(ctx context.Context, client *storage.Client, bucket string, name string, filename string) error { src, err := client.Bucket(bucket).Object(name).NewReader(ctx) if err != nil { log.Printf("Error creating reader on object: %v", err) return err } defer src.Close() dest, err := os.Create(filename) if err != nil { log.Printf("Error creating destination file: %v", err) return err } defer dest.Close() _, err = io.Copy(dest, src) if err != nil { log.Printf("Error copying object to file: %v", err) return err } return nil } // uploadObject copies a local file into an object of a given bucket func uploadObject(ctx context.Context, client *storage.Client, filename string, bucket string, name string) error { src, err := os.Open(filename) if err != nil { log.Printf("Error opening source file: %v", err) return err } defer src.Close() dest := client.Bucket(bucket).Object(name).NewWriter(ctx) defer dest.Close() _, err = io.Copy(dest, src) if err != nil { log.Printf("Error copying file to object: %v", err) return err } return nil }