173 lines
3.4 KiB
Go
173 lines
3.4 KiB
Go
package photo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"meutel.net/meutel/go-examples/photoblog/admin"
|
|
)
|
|
|
|
const (
|
|
DISPLAY_SIZE = 5 // number of photos displayed on home
|
|
)
|
|
|
|
var (
|
|
PHOTOEXT = [3]string{".jpg", ".jpeg", ".png"}
|
|
PHOTOMIME = [2]string{"image/png", "image/jpeg"}
|
|
)
|
|
|
|
// Application
|
|
type PhotoBlog struct {
|
|
admin.AuthCookie
|
|
}
|
|
|
|
// Generic model
|
|
type PhotoModel struct {
|
|
Username string
|
|
Err error
|
|
Message string
|
|
}
|
|
|
|
// Constructor PhotoModel
|
|
func NewPhotoModel(username string) *PhotoModel {
|
|
return &PhotoModel{
|
|
Username: username,
|
|
}
|
|
}
|
|
|
|
// Home page model
|
|
type PhotoHomeModel struct {
|
|
PhotoModel
|
|
Photos []string
|
|
}
|
|
|
|
// Constructor PhotoHomeModel
|
|
func NewPhotoHomeModel(username string, photos []string) *PhotoHomeModel {
|
|
return &PhotoHomeModel{
|
|
PhotoModel: *NewPhotoModel(username),
|
|
Photos: photos,
|
|
}
|
|
}
|
|
|
|
// File and date of last modification
|
|
type TimedFile struct {
|
|
Mod time.Time
|
|
Path string
|
|
}
|
|
|
|
// Constructor TimedFile
|
|
func NewTimedFile(mod time.Time, path string) *TimedFile {
|
|
return &TimedFile{
|
|
mod,
|
|
path,
|
|
}
|
|
}
|
|
|
|
// TimedFile sort
|
|
type timeMap []*TimedFile
|
|
|
|
func (m timeMap) Len() int {
|
|
return len(m)
|
|
}
|
|
func (m timeMap) Less(i, j int) bool {
|
|
return m[i].Mod.After(m[j].Mod)
|
|
}
|
|
func (m timeMap) Swap(i, j int) {
|
|
m[i], m[j] = m[j], m[i]
|
|
}
|
|
|
|
// test if file is a photo (by extension)
|
|
func IsPhoto(path string) bool {
|
|
for _, ext := range PHOTOEXT {
|
|
if strings.HasSuffix(path, ext) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// test if mime type is a photo
|
|
func IsPhotoMime(mime string) bool {
|
|
for _, accepted := range PHOTOMIME {
|
|
if mime == accepted {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// get Latest files
|
|
func Latest(photos timeMap) []string {
|
|
latest := make([]string, 0, DISPLAY_SIZE)
|
|
sort.Sort(photos)
|
|
for i, v := range photos {
|
|
if i > DISPLAY_SIZE {
|
|
break
|
|
}
|
|
latest = append(latest, v.Path)
|
|
}
|
|
return latest
|
|
}
|
|
|
|
// Add new uploaded photo
|
|
func (app *PhotoBlog) AddPhoto(res http.ResponseWriter, req *http.Request) (string, error) {
|
|
file, header, err := req.FormFile("file")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
mime := header.Header.Get("Content-Type")
|
|
if !IsPhotoMime(mime) {
|
|
return "", errors.New("This file type is not accepted (" + mime + ")")
|
|
}
|
|
|
|
target, err := os.Create(filepath.Join(app.DataDir.Name(), app.Username(res, req), header.Filename))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer target.Close()
|
|
|
|
sz, err := io.Copy(target, file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("File uploaded (%d bytes)", sz), nil
|
|
}
|
|
|
|
// ROUTES //
|
|
|
|
// Home: list latest photos
|
|
func (app *PhotoBlog) HomePage(res http.ResponseWriter, req *http.Request) {
|
|
photos := make([]*TimedFile, 0)
|
|
filepath.Walk(app.DataDir.Name(), func(path string, info os.FileInfo, err error) error {
|
|
if !info.IsDir() && IsPhoto(path) {
|
|
photos = append(photos, NewTimedFile(info.ModTime(), path))
|
|
}
|
|
return nil
|
|
})
|
|
app.Templates.ExecuteTemplate(res, "home.html", NewPhotoHomeModel(app.Username(res, req), Latest(photos)))
|
|
}
|
|
|
|
// Upload: form upload photo
|
|
func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) {
|
|
if !app.IsLoggedIn(res, req) {
|
|
app.RedirectHome(res, req)
|
|
return
|
|
}
|
|
data := NewPhotoModel(app.Username(res, req))
|
|
if req.Method == http.MethodPost {
|
|
message, err := app.AddPhoto(res, req)
|
|
data.Err = err
|
|
data.Message = message
|
|
}
|
|
app.Templates.ExecuteTemplate(res, "upload.html", data)
|
|
}
|