Photo Quotas & Postgres Fixes
* Add photo upload quotas. * Non-certified users can upload few photos; certified users more * Fix foreign key issues around deleting user profile photos for psql
This commit is contained in:
parent
967e149875
commit
71dfa76faa
|
@ -66,6 +66,10 @@ var (
|
|||
const (
|
||||
MaxPhotoWidth = 1280
|
||||
ProfilePhotoWidth = 512
|
||||
|
||||
// Quotas for uploaded photos.
|
||||
PhotoQuotaUncertified = 6
|
||||
PhotoQuotaCertified = 24
|
||||
)
|
||||
|
||||
// Variables set by main.go to make them readily available.
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
func Login() http.HandlerFunc {
|
||||
tmpl := templates.Must("account/login.html")
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var next = r.FormValue("next")
|
||||
|
||||
// Posting?
|
||||
if r.Method == http.MethodPost {
|
||||
|
@ -73,11 +74,18 @@ func Login() http.HandlerFunc {
|
|||
|
||||
// Redirect to their dashboard.
|
||||
session.Flash(w, r, "Login successful.")
|
||||
templates.Redirect(w, "/me")
|
||||
if strings.HasPrefix(next, "/") {
|
||||
templates.Redirect(w, next)
|
||||
} else {
|
||||
templates.Redirect(w, "/me")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(w, r, nil); err != nil {
|
||||
var vars = map[string]interface{}{
|
||||
"Next": next,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package photo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -131,11 +132,15 @@ func Delete() http.HandlerFunc {
|
|||
// Query params.
|
||||
photoID, err := strconv.Atoi(r.FormValue("id"))
|
||||
if err != nil {
|
||||
log.Error("photo.Delete: failed to parse `id` param (%s) as int: %s", r.FormValue("id"), err)
|
||||
session.FlashError(w, r, "Photo 'id' parameter required.")
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Page to redirect to in case of errors.
|
||||
redirect := fmt.Sprintf("%s?id=%d", r.URL.Path, photoID)
|
||||
|
||||
// Find this photo by ID.
|
||||
photo, err := models.GetPhoto(uint64(photoID))
|
||||
if err != nil {
|
||||
|
@ -162,10 +167,20 @@ func Delete() http.HandlerFunc {
|
|||
confirm := r.PostFormValue("confirm") == "true"
|
||||
if !confirm {
|
||||
session.FlashError(w, r, "Confirm you want to delete this photo.")
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
templates.Redirect(w, redirect)
|
||||
return
|
||||
}
|
||||
|
||||
// Was this our profile picture?
|
||||
if currentUser.ProfilePhotoID != nil && *currentUser.ProfilePhotoID == photo.ID {
|
||||
log.Debug("Delete Photo: was the user's profile photo, unset ProfilePhotoID")
|
||||
if err := currentUser.RemoveProfilePhoto(); err != nil {
|
||||
session.FlashError(w, r, "Error unsetting your current profile photo: %s", err)
|
||||
templates.Redirect(w, redirect)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the images from disk.
|
||||
for _, filename := range []string{
|
||||
photo.Filename,
|
||||
|
@ -180,7 +195,7 @@ func Delete() http.HandlerFunc {
|
|||
|
||||
if err := photo.Delete(); err != nil {
|
||||
session.FlashError(w, r, "Couldn't delete photo: %s", err)
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
templates.Redirect(w, redirect)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ func Upload() http.HandlerFunc {
|
|||
session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
|
||||
}
|
||||
|
||||
// Get the current user's quota.
|
||||
var photoCount, photoQuota = photo.QuotaForUser(user)
|
||||
vars["PhotoCount"] = photoCount
|
||||
vars["PhotoQuota"] = photoQuota
|
||||
|
||||
// Are they POSTing?
|
||||
if r.Method == http.MethodPost {
|
||||
var (
|
||||
|
@ -47,6 +52,13 @@ func Upload() http.HandlerFunc {
|
|||
confirm2 = r.PostFormValue("confirm2") == "true"
|
||||
)
|
||||
|
||||
// Are they at quota already?
|
||||
if photoCount >= photoQuota {
|
||||
session.FlashError(w, r, "You have too many photos to upload a new one. Please delete a photo to make room for a new one.")
|
||||
templates.Redirect(w, "/photo/u/"+user.Username)
|
||||
return
|
||||
}
|
||||
|
||||
// They checked both boxes. The browser shouldn't allow them to
|
||||
// post but validate it here anyway...
|
||||
if !confirm1 || !confirm2 {
|
||||
|
|
|
@ -21,8 +21,8 @@ func LoginRequired(handler http.Handler) http.Handler {
|
|||
user, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
log.Error("LoginRequired: %s", err)
|
||||
errhandler := templates.MakeErrorPage("Login Required", "You must be signed in to view this page.", http.StatusForbidden)
|
||||
errhandler.ServeHTTP(w, r)
|
||||
session.FlashError(w, r, "You must be signed in to view this page.")
|
||||
templates.Redirect(w, "/login?next="+r.URL.RawPath)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -89,8 +89,8 @@ func CertRequired(handler http.Handler) http.Handler {
|
|||
currentUser, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
log.Error("LoginRequired: %s", err)
|
||||
errhandler := templates.MakeErrorPage("Login Required", "You must be signed in to view this page.", http.StatusForbidden)
|
||||
errhandler.ServeHTTP(w, r)
|
||||
session.FlashError(w, r, "You must be signed in to view this page.")
|
||||
templates.Redirect(w, "/login?next="+r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,16 @@ func PaginateUserPhotos(userID uint64, visibility []PhotoVisibility, explicitOK
|
|||
return p, result.Error
|
||||
}
|
||||
|
||||
// CountPhotos returns the total number of photos on a user's account.
|
||||
func CountPhotos(userID uint64) (int64, error) {
|
||||
var count int64
|
||||
result := DB.Where(
|
||||
"user_id = ?",
|
||||
userID,
|
||||
).Model(&Photo{}).Count(&count)
|
||||
return count, result.Error
|
||||
}
|
||||
|
||||
// CountExplicitPhotos returns the number of explicit photos a user has (so non-explicit viewers can see some do exist)
|
||||
func CountExplicitPhotos(userID uint64, visibility []PhotoVisibility) (int64, error) {
|
||||
query := DB.Where(
|
||||
|
|
|
@ -32,7 +32,7 @@ type User struct {
|
|||
|
||||
// Relational tables.
|
||||
ProfileField []ProfileField
|
||||
ProfilePhotoID uint64
|
||||
ProfilePhotoID *uint64
|
||||
ProfilePhoto Photo `gorm:"foreignKey:profile_photo_id"`
|
||||
}
|
||||
|
||||
|
@ -329,6 +329,12 @@ func (u *User) ProfileFieldIn(field, substr string) bool {
|
|||
return strings.Contains(value, substr)
|
||||
}
|
||||
|
||||
// RemoveProfilePhoto sets profile_photo_id=null to unset the foreign key.
|
||||
func (u *User) RemoveProfilePhoto() error {
|
||||
result := DB.Model(&User{}).Where("id = ?", u.ID).Update("profile_photo_id", nil)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// Save user.
|
||||
func (u *User) Save() error {
|
||||
result := DB.Save(u)
|
||||
|
|
22
pkg/photo/quota.go
Normal file
22
pkg/photo/quota.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package photo
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/gosocial/pkg/config"
|
||||
"git.kirsle.net/apps/gosocial/pkg/models"
|
||||
)
|
||||
|
||||
// QuoteForUser returns the current photo usage quota for a given user.
|
||||
func QuotaForUser(u *models.User) (current, allowed int) {
|
||||
// Count their photos.
|
||||
count, _ := models.CountPhotos(u.ID)
|
||||
|
||||
// What is their quota at?
|
||||
var quota int
|
||||
if !u.Certified {
|
||||
quota = config.PhotoQuotaUncertified
|
||||
} else {
|
||||
quota = config.PhotoQuotaCertified
|
||||
}
|
||||
|
||||
return int(count), quota
|
||||
}
|
|
@ -41,6 +41,17 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
|
|||
return labels[1]
|
||||
}
|
||||
},
|
||||
"Pluralize": func(count int, labels ...string) string {
|
||||
if len(labels) < 2 {
|
||||
labels = []string{"", "s"}
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
return labels[0]
|
||||
} else {
|
||||
return labels[1]
|
||||
}
|
||||
},
|
||||
"Substring": func(value string, n int) string {
|
||||
if n > len(value) {
|
||||
return value
|
||||
|
@ -54,6 +65,9 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
|
|||
}
|
||||
return result
|
||||
},
|
||||
"SubtractInt": func(a, b int) int {
|
||||
return a - b
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<div class="block p-4">
|
||||
<form action="/login" method="POST">
|
||||
{{ InputCSRF }}
|
||||
<input type="hidden" name="next" value="{{.Next}}">
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="username">Username or email:</label>
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{.NameOrUsername}}</p>
|
||||
<p class="title is-4">{{$User.NameOrUsername}}</p>
|
||||
<p class="subtitle is-6">
|
||||
<span class="icon"><i class="fa fa-user"></i></span>
|
||||
<a href="/u/{{$User.Username}}" target="_blank">{{$User.Username}}</a>
|
||||
|
|
|
@ -57,6 +57,28 @@
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Quota notification -->
|
||||
{{if not .EditPhoto}}
|
||||
<div class="notification {{if ge .PhotoCount .PhotoQuota}}is-warning{{else}}is-info{{end}} block">
|
||||
<p class="block">
|
||||
You have currently uploaded <strong>{{.PhotoCount}}</strong> of your allowed {{.PhotoQuota}} photos.
|
||||
{{if ge .PhotoCount .PhotoQuota}}
|
||||
To upload a new photo, please <a href="/photo/u/{{.CurrentUser.Username}}">delete</a>
|
||||
an existing photo first to make room.
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<p class="block">
|
||||
You may upload <strong>{{SubtractInt .PhotoQuota .PhotoCount}}</strong> more photo{{Pluralize (SubtractInt .PhotoQuota .PhotoCount)}}.
|
||||
{{if not .CurrentUser.Certified}}
|
||||
After your account has been <a href="/photo/certification">certified</a>, you will be able to upload
|
||||
additional pictures.
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if or .EditPhoto (lt .PhotoCount .PhotoQuota)}}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
|
||||
|
@ -299,7 +321,8 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /columns -->
|
||||
{{end}}<!-- if under quota -->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
Reference in New Issue
Block a user