go-examples/photoblog/photo/photo.go

191 lines
3.8 KiB
Go

package photo
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/gorilla/csrf"
"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"}
DATA = "/data/"
)
// 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,
}
}
// Upload page model
type PhotoUploadModel struct {
PhotoModel
CsrfToken string
}
// Constructor PhotoUploadModel
func NewPhotoUploadModel(username, tok string) *PhotoUploadModel {
return &PhotoUploadModel{
PhotoModel: *NewPhotoModel(username),
CsrfToken: tok,
}
}
// 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 {
relPath, err := filepath.Rel(app.DataDir.Name(), path)
if !info.IsDir() && IsPhoto(path) && err == nil {
photos = append(photos, NewTimedFile(info.ModTime(), DATA+relPath))
}
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 := NewPhotoUploadModel(app.Username(res, req), csrf.Token(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)
}