uploading a profile photo now works
This commit is contained in:
@@ -641,6 +641,7 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalGetProp is a helper used by the property functions.
|
||||||
func internalGetProp(uid int32, ndx int32) (*UserProperties, error) {
|
func internalGetProp(uid int32, ndx int32) (*UserProperties, error) {
|
||||||
var err error = nil
|
var err error = nil
|
||||||
key := fmt.Sprintf("%d:%d", uid, ndx)
|
key := fmt.Sprintf("%d:%d", uid, ndx)
|
||||||
|
|||||||
+28
-4
@@ -13,7 +13,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
@@ -32,6 +32,15 @@ import (
|
|||||||
//go:embed static_images/*
|
//go:embed static_images/*
|
||||||
var static_images embed.FS
|
var static_images embed.FS
|
||||||
|
|
||||||
|
// Constants for default photo sizes.
|
||||||
|
const (
|
||||||
|
UserPhotoWidth = 100
|
||||||
|
UserPhotoHeight = 100
|
||||||
|
UserPhotoMaxBytes = 2097152 // 2 Mb
|
||||||
|
CommunityLogoWidth = 110
|
||||||
|
CommunityLogoHeight = 60
|
||||||
|
)
|
||||||
|
|
||||||
/* mimeTypeFromFilenane returns the MIME type of a file, given its filename.
|
/* mimeTypeFromFilenane returns the MIME type of a file, given its filename.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* filaname - The name of the file to be tested.
|
* filaname - The name of the file to be tested.
|
||||||
@@ -76,11 +85,26 @@ func AmServeImage(ctxt AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctxt.SetRC(http.StatusNotFound)
|
ctxt.SetRC(http.StatusNotFound)
|
||||||
// TODO: improve this error reporting
|
return ErrorPage(ctxt, err)
|
||||||
return "string", fmt.Sprintf("File not found: %s", ctxt.URLPath()), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AmProcessUploadedImage(fileheader *multipart.FileHeader, width, height int) ([]byte, string, error) {
|
/* AmProcessUploadedImage takes an image and resizes it to a specified size, returning its data.
|
||||||
|
* Parameters:
|
||||||
|
* fileheader - The multipart file header from the uploaded file.
|
||||||
|
* width - New image width in pizels.
|
||||||
|
* height - New image height in pixels.
|
||||||
|
* maxbytes - The maximum size of the user photo.
|
||||||
|
* Returns:
|
||||||
|
* Image data as a byte array.
|
||||||
|
* The MIME type of the image data.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmProcessUploadedImage(fileheader *multipart.FileHeader, width, height, maxbytes int) ([]byte, string, error) {
|
||||||
|
// test size
|
||||||
|
if fileheader.Size > int64(maxbytes) {
|
||||||
|
return nil, "", errors.New("file is too large; please try again")
|
||||||
|
}
|
||||||
|
|
||||||
// open the file
|
// open the file
|
||||||
file, err := fileheader.Open()
|
file, err := fileheader.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+1
-2
@@ -188,8 +188,7 @@
|
|||||||
<input type="hidden" name="{{ .Name }}_data" value="{{ .Value }}"/>
|
<input type="hidden" name="{{ .Name }}_data" value="{{ .Value }}"/>
|
||||||
<a href="/profile_photo?tgt={{ target | url }}"
|
<a href="/profile_photo?tgt={{ target | url }}"
|
||||||
class="border-2 border-gray-300 rounded hover:border-blue-500 transition-colors">
|
class="border-2 border-gray-300 rounded hover:border-blue-500 transition-colors">
|
||||||
<img src="/img/builtin/no-user.png"
|
<img src="{{ .Value }}" alt="Click to upload photo" class="w-25 h-25">
|
||||||
alt="Click to upload photo" class="w-25 h-25">
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "header" }}
|
{{ else if .Type == "header" }}
|
||||||
|
|||||||
+41
-11
@@ -22,6 +22,14 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// userPhotoURL returns the photo URL from the contact info, or a default.
|
||||||
|
func userPhotoURL(ci *database.ContactInfo) string {
|
||||||
|
if ci.PhotoURL != nil && *ci.PhotoURL != "" {
|
||||||
|
return *ci.PhotoURL
|
||||||
|
}
|
||||||
|
return "/img/builtin/no-user.png"
|
||||||
|
}
|
||||||
|
|
||||||
/* EditProfileForm renders the Amsterdam profile editing form.
|
/* EditProfileForm renders the Amsterdam profile editing form.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctxt - The AmContext for the request.
|
* ctxt - The AmContext for the request.
|
||||||
@@ -74,7 +82,7 @@ func EditProfileForm(ctxt ui.AmContext) (string, any, error) {
|
|||||||
dlg.Field("url").SetVal(ci.URL)
|
dlg.Field("url").SetVal(ci.URL)
|
||||||
dlg.Field("dob").SetDate(u.DOB)
|
dlg.Field("dob").SetDate(u.DOB)
|
||||||
dlg.Field("descr").SetVal(u.Description)
|
dlg.Field("descr").SetVal(u.Description)
|
||||||
// TODO: do something for user photo
|
dlg.Field("photo").Value = userPhotoURL(ci)
|
||||||
dlg.Field("pic_in_post").SetChecked(u.FlagValue(database.UserFlagPicturesInPosts))
|
dlg.Field("pic_in_post").SetChecked(u.FlagValue(database.UserFlagPicturesInPosts))
|
||||||
dlg.Field("no_mass_mail").SetChecked(u.FlagValue(database.UserFlagMassMailOptOut))
|
dlg.Field("no_mass_mail").SetChecked(u.FlagValue(database.UserFlagMassMailOptOut))
|
||||||
dlg.Field("locale").Value = prefs.ReadLocale()
|
dlg.Field("locale").Value = prefs.ReadLocale()
|
||||||
@@ -86,6 +94,14 @@ func EditProfileForm(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* EditProfile handles profile editing.
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for the request.
|
||||||
|
* Returns:
|
||||||
|
* Command string dictating what to be rendered.
|
||||||
|
* Data as a parameter for the command string.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
func EditProfile(ctxt ui.AmContext) (string, any, error) {
|
func EditProfile(ctxt ui.AmContext) (string, any, error) {
|
||||||
u := ctxt.CurrentUser()
|
u := ctxt.CurrentUser()
|
||||||
if u.IsAnon {
|
if u.IsAnon {
|
||||||
@@ -201,15 +217,22 @@ func ProfilePhotoForm(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
ci, err := u.ContactInfo()
|
ci, err := u.ContactInfo()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = ci
|
|
||||||
ctxt.VarMap().Set("target", target)
|
ctxt.VarMap().Set("target", target)
|
||||||
ctxt.VarMap().Set("photo_url", "/img/builtin/no-user.png")
|
ctxt.VarMap().Set("photo_url", userPhotoURL(ci))
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Upload User Photo")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "Upload User Photo")
|
||||||
return "framed_template", "photo_upload.jet", nil
|
return "framed_template", "photo_upload.jet", nil
|
||||||
}
|
}
|
||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ProfilePhoto handles processing the uploaded user photo..
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for the request.
|
||||||
|
* Returns:
|
||||||
|
* Command string dictating what to be rendered.
|
||||||
|
* Data as a parameter for the command string.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
||||||
u := ctxt.CurrentUser()
|
u := ctxt.CurrentUser()
|
||||||
if u.IsAnon {
|
if u.IsAnon {
|
||||||
@@ -229,7 +252,10 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
|||||||
if ctxt.FormFieldIsSet("upload") {
|
if ctxt.FormFieldIsSet("upload") {
|
||||||
file, err := ctxt.FormFile("thepic")
|
file, err := ctxt.FormFile("thepic")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
imageData, mimeType, err := ui.AmProcessUploadedImage(file, 200, 200)
|
var imageData []byte
|
||||||
|
var mimeType string
|
||||||
|
imageData, mimeType, err = ui.AmProcessUploadedImage(file, ui.UserPhotoWidth, ui.UserPhotoHeight,
|
||||||
|
ui.UserPhotoMaxBytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var img *database.ImageStore
|
var img *database.ImageStore
|
||||||
img, err = database.AmStoreImage(database.ImageTypeUserPhoto, u.Uid, mimeType, imageData)
|
img, err = database.AmStoreImage(database.ImageTypeUserPhoto, u.Uid, mimeType, imageData)
|
||||||
@@ -245,12 +271,13 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
ctxt.VarMap().Set("target", target)
|
ctxt.VarMap().Set("target", target)
|
||||||
ctxt.VarMap().Set("photo_url", "/img/builtin/no-user.png")
|
ctxt.VarMap().Set("photo_url", userPhotoURL(ci))
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Upload User Photo")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "Upload User Photo")
|
||||||
return "framed_template", "photo_upload.jet", nil
|
return "framed_template", "photo_upload.jet", nil
|
||||||
}
|
}
|
||||||
if ctxt.FormFieldIsSet("remove") {
|
if ctxt.FormFieldIsSet("remove") {
|
||||||
purl := ci.PhotoURL
|
purl := ci.PhotoURL
|
||||||
|
happy := false
|
||||||
if purl == nil || *purl == "" {
|
if purl == nil || *purl == "" {
|
||||||
// this is a no-op
|
// this is a no-op
|
||||||
return "redirect", "/profile?tgt=" + url.QueryEscape(target), nil
|
return "redirect", "/profile?tgt=" + url.QueryEscape(target), nil
|
||||||
@@ -261,12 +288,14 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
go func() {
|
if happy {
|
||||||
err := database.AmDeleteImage(int32(id))
|
go func() {
|
||||||
if err != nil {
|
err := database.AmDeleteImage(int32(id))
|
||||||
log.Errorf("unable to delete image ID %d: %v", id, err)
|
if err != nil {
|
||||||
}
|
log.Errorf("unable to delete image ID %d: %v", id, err)
|
||||||
}()
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ci.PhotoURL = nil
|
ci.PhotoURL = nil
|
||||||
@@ -274,6 +303,7 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
happy = true
|
||||||
return "redirect", "/profile?tgt=" + url.QueryEscape(target), nil
|
return "redirect", "/profile?tgt=" + url.QueryEscape(target), nil
|
||||||
}
|
}
|
||||||
return ui.ErrorPage(ctxt, errors.New("invalid button detected in photo upload"))
|
return ui.ErrorPage(ctxt, errors.New("invalid button detected in photo upload"))
|
||||||
|
|||||||
Reference in New Issue
Block a user