package main import ( "crypto/rand" "database/sql" "encoding/base64" "encoding/json" "fmt" "github.com/go-redis/redis" _ "github.com/lib/pq" "html/template" "io" "mime/multipart" "net/http" "os" "path/filepath" "time" ) func writeBody(reader io.ReadCloser, path string) error { out, err := os.Create(path) if err != nil { return err } defer out.Close() _, err = io.Copy(out, reader) if err != nil { return err } return out.Close() } func detectMimeType(path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err } buffer := make([]byte, 512) _, err = file.Read(buffer) if err != nil { return "", err } return http.DetectContentType(buffer), nil } func generateId() string { buffer := make([]byte, 4) rand.Read(buffer) return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buffer) } func createImage(config *Config, body io.ReadCloser, fileHeader *multipart.FileHeader) (Image, error) { id := generateId() path := filepath.Join(config.SourceFolder, id) err := writeBody(body, path) if err != nil { return Image{}, err } mimeType, err := detectMimeType(path) if err != nil { return Image{}, err } image := Image{ Id: id, OriginalName: filepath.Base(fileHeader.Filename), CreatedAt: time.Now(), MimeType: mimeType, } return image, nil } type UserInfo struct { Id string Name string Email string } func parseUser(r *http.Request) UserInfo { return UserInfo{ r.Header.Get("X-Auth-Subject"), r.Header.Get("X-Auth-Username"), r.Header.Get("X-Auth-Email"), } } type UploadData struct { User UserInfo Results []Result } func returnResult(w http.ResponseWriter, templateName string, data interface{}) error { pageTemplate, err := template.ParseFiles( "templates/_base.html", "templates/_header.html", "templates/_navigation.html", "templates/_footer.html", fmt.Sprintf("templates/%s", templateName), ) if err != nil { return err } err = pageTemplate.Execute(w, data) if err != nil { return err } return nil } func main() { config := NewConfigFromEnv() client := redis.NewClient(&redis.Options{ Addr: config.Redis.Address, Password: config.Redis.Password, }) db, err := sql.Open(config.Database.Format, config.Database.Url) if err != nil { panic(err) } imageServer := http.FileServer(http.Dir(config.TargetFolder)) assetServer := http.FileServer(http.Dir("assets")) http.HandleFunc("/upload/", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { user := parseUser(r) err := r.ParseMultipartForm(32 << 20) if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } } var images []Image var ids []string m := r.MultipartForm files := m.File["file"] for _, header := range files { file, err := header.Open() if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } return } image, err := createImage(&config, file, header) if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } return } images = append(images, image) ids = append(ids, image.Id) } pubsub := client.Subscribe(config.ResultChannel) waiting := make(map[string]bool) for _, image := range images { _, err = db.Exec("INSERT INTO images (id, owner, created_at, original_name, type) VALUES ($1, $2, $3, $4, $5)", image.Id, user.Id, image.CreatedAt, image.OriginalName, image.MimeType) if err != nil { panic(err) } data, err := json.Marshal(image) if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } return } fmt.Printf("Created task %s at %d\n", image.Id, time.Now().Unix()) client.RPush(fmt.Sprintf("queue:%s", config.ImageQueue), data) fmt.Printf("Submitted task %s at %d\n", image.Id, time.Now().Unix()) waiting[image.Id] = true } var results []Result for len(waiting) != 0 { message, err := pubsub.ReceiveMessage() if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } return } result := Result{} err = json.Unmarshal([]byte(message.Payload), &result) if err != nil { if err = returnResult(w, "upload.html", UploadData{ user, []Result{{ Success: false, Errors: []string{err.Error()}, }}, }); err != nil { panic(err) } return } fmt.Printf("Returned task %s at %d\n", result.Id, time.Now().Unix()) if _, ok := waiting[result.Id]; ok { delete(waiting, result.Id) results = append(results, result) } } if err = returnResult(w, "upload.html", UploadData{ user, results, }); err != nil { panic(err) } return } else { user := parseUser(r) if err = returnResult(w, "upload.html", UploadData{ user, []Result{}, }); err != nil { panic(err) } } }) http.HandleFunc("/me/images/", func(w http.ResponseWriter, r *http.Request) { user := parseUser(r) type ImageListData struct { User UserInfo Images []Image } result, err := db.Query(` SELECT id, coalesce(title, ''), coalesce(description, ''), coalesce(created_at, to_timestamp(0)), coalesce(original_name, ''), coalesce(type, '') FROM images WHERE owner = $1 `, user.Id) if err != nil { panic(err) } var images []Image for result.Next() { var info Image err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType) if err != nil { panic(err) } images = append(images, info) } if err = returnResult(w, "me_images.html", ImageListData{ user, images, }); err != nil { panic(err) } }) http.Handle("/assets/", http.StripPrefix("/assets/", assetServer)) http.Handle("/i/", http.StripPrefix("/i/", imageServer)) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { user := parseUser(r) type IndexData struct { User UserInfo } if err = returnResult(w, "index.html", IndexData{ user, }); err != nil { panic(err) } }) err = http.ListenAndServe(":8080", nil) if err != nil { panic(err) } }