Certification photo admin updates
This commit is contained in:
parent
7ef22db5e2
commit
3d4c728d75
|
@ -8,12 +8,14 @@ import (
|
|||
"strconv"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/geoip"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/mail"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/photo"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
"code.nonshy.com/nonshy/website/pkg/utility"
|
||||
)
|
||||
|
||||
// CertificationRequiredError handles the error page when a user is denied due to lack of certification.
|
||||
|
@ -123,6 +125,7 @@ func Certification() http.HandlerFunc {
|
|||
cert.Status = models.CertificationPhotoPending
|
||||
cert.Filename = filename
|
||||
cert.AdminComment = ""
|
||||
cert.IPAddress = utility.IPAddress(r)
|
||||
if err := cert.Save(); err != nil {
|
||||
session.FlashError(w, r, "Error saving your CertificationPhoto: %s", err)
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
|
@ -281,6 +284,9 @@ func AdminCertification() http.HandlerFunc {
|
|||
} else {
|
||||
cert.Status = models.CertificationPhotoRejected
|
||||
cert.AdminComment = comment
|
||||
if comment == "(ignore)" {
|
||||
cert.AdminComment = ""
|
||||
}
|
||||
if err := cert.Save(); err != nil {
|
||||
session.FlashError(w, r, "Failed to save CertificationPhoto: %s", err)
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
|
@ -291,6 +297,13 @@ func AdminCertification() http.HandlerFunc {
|
|||
user.Certified = false
|
||||
user.Save()
|
||||
|
||||
// Did we silently ignore it?
|
||||
if comment == "(ignore)" {
|
||||
session.FlashError(w, r, "The certification photo was ignored with no comment, and will not notify the sender.")
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify the user about this rejection.
|
||||
notif := &models.Notification{
|
||||
UserID: user.ID,
|
||||
|
@ -374,21 +387,27 @@ func AdminCertification() http.HandlerFunc {
|
|||
session.FlashError(w, r, "Couldn't load certification photos from DB: %s", err)
|
||||
}
|
||||
|
||||
// Map user IDs.
|
||||
var userIDs = []uint64{}
|
||||
// Map user IDs and GeoIP insights.
|
||||
var (
|
||||
userIDs = []uint64{}
|
||||
ipAddresses = []string{}
|
||||
)
|
||||
for _, p := range photos {
|
||||
userIDs = append(userIDs, p.UserID)
|
||||
ipAddresses = append(ipAddresses, p.IPAddress)
|
||||
}
|
||||
insightsMap := geoip.MapInsights(ipAddresses)
|
||||
userMap, err := models.MapUsers(currentUser, userIDs)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't map user IDs: %s", err)
|
||||
}
|
||||
|
||||
var vars = map[string]interface{}{
|
||||
"View": view,
|
||||
"Photos": photos,
|
||||
"UserMap": userMap,
|
||||
"Pager": pager,
|
||||
"View": view,
|
||||
"Photos": photos,
|
||||
"UserMap": userMap,
|
||||
"InsightsMap": insightsMap,
|
||||
"Pager": pager,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/utility"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
@ -22,6 +23,7 @@ type Insights struct {
|
|||
PostalCode string
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
FlagEmoji string
|
||||
}
|
||||
|
||||
// IsZero checks if the insights are unpopulated.
|
||||
|
@ -53,6 +55,15 @@ func (i Insights) Short() string {
|
|||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// Medium prints a summary including country flag emoji with all fields except lat/long.
|
||||
func (i Insights) Medium() string {
|
||||
var parts = []string{
|
||||
strings.Join([]string{i.FlagEmoji, i.CountryCode}, " "),
|
||||
}
|
||||
parts = append(parts, i.Short())
|
||||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// GetRequestInsights returns structured insights based on the current HTTP request.
|
||||
func GetRequestInsights(r *http.Request) (Insights, error) {
|
||||
var (
|
||||
|
@ -69,6 +80,12 @@ func GetInsights(ip net.IP) (Insights, error) {
|
|||
return Insights{}, err
|
||||
}
|
||||
|
||||
// Country flag emoji.
|
||||
emoji, err := CountryFlagEmoji(city.Country.IsoCode)
|
||||
if err != nil {
|
||||
emoji = "🏴☠️"
|
||||
}
|
||||
|
||||
var result = Insights{
|
||||
City: city.City.Names["en"],
|
||||
CountryCode: city.Country.IsoCode,
|
||||
|
@ -77,6 +94,7 @@ func GetInsights(ip net.IP) (Insights, error) {
|
|||
PostalCode: city.Postal.Code,
|
||||
Latitude: city.Location.Latitude,
|
||||
Longitude: city.Location.Longitude,
|
||||
FlagEmoji: emoji,
|
||||
}
|
||||
for _, sub := range city.Subdivisions {
|
||||
if name, ok := sub.Names["en"]; ok {
|
||||
|
@ -87,6 +105,30 @@ func GetInsights(ip net.IP) (Insights, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
type InsightsMap map[string]Insights
|
||||
|
||||
func (i InsightsMap) Get(key string) Insights {
|
||||
return i[key]
|
||||
}
|
||||
|
||||
// MapInsights returns a hash map of IP address (strings) to their Insights.
|
||||
func MapInsights(addrs []string) InsightsMap {
|
||||
var result = map[string]Insights{}
|
||||
for _, addr := range addrs {
|
||||
if _, ok := result[addr]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(addr)
|
||||
insights, err := GetInsights(ip)
|
||||
if err != nil {
|
||||
log.Error("MapInsights(%s): %s", addr, err)
|
||||
}
|
||||
result[addr] = insights
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRequestCity returns the GeoIP City result for the current HTTP request.
|
||||
func GetRequestCity(r *http.Request) (*geoip2.City, error) {
|
||||
var (
|
||||
|
|
|
@ -14,6 +14,7 @@ type CertificationPhoto struct {
|
|||
Filesize int64
|
||||
Status CertificationPhotoStatus
|
||||
AdminComment string
|
||||
IPAddress string // the IP they uploaded the photo from
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ abbr {
|
|||
/* Bulma hack: smaller tag size inside of tab buttons. The default tag height
|
||||
is set to 2em which makes the boxed tabs too tall and the bottom line doesn't
|
||||
draw correctly. Seen on e.g. Profile Pages for the tag # of photos. */
|
||||
.tabs.is-boxed > ul > li > a .tag {
|
||||
.tabs.is-boxed>ul>li>a .tag {
|
||||
height: 1.5em !important;
|
||||
}
|
||||
|
||||
|
@ -131,15 +131,28 @@ abbr {
|
|||
white-space: nowrap;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.nonshy-navbar-notification-tag.is-warning {
|
||||
background-color: #ffd324;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.nonshy-navbar-notification-tag.is-info {
|
||||
background-color: #0f81cc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nonshy-navbar-notification-tag.is-danger {
|
||||
background-color: #ff0537;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nonshy-navbar-notification-tag.is-success {
|
||||
background-color: #3ec487;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nonshy-navbar-notification-tag.is-mixed {
|
||||
background: linear-gradient(141deg, #ff0537 0, #3ec487 100%);
|
||||
color: #fff;
|
||||
}
|
|
@ -121,6 +121,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<strong><i class="fa fa-location-dot mr-1"></i> GeoIP Insights:</strong>
|
||||
<div>
|
||||
{{$Insights := $Root.InsightsMap.Get .IPAddress}}
|
||||
{{if $Insights.IsZero}}
|
||||
<span class="has-text-danger">No GeoIP insights available for this IP address!</span>
|
||||
{{else}}
|
||||
{{$Insights.Medium}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div>
|
||||
IP: {{.IPAddress}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<textarea class="textarea" name="comment"
|
||||
cols="60" rows="2"
|
||||
|
@ -134,7 +149,12 @@
|
|||
</option>
|
||||
<option value="The sheet of paper must also include the website name: nonshy">Website name not visible</option>
|
||||
<option value="Please take a clearer picture that shows your arm and hand holding onto the sheet of paper">Unclear picture (hand not visible enough)</option>
|
||||
<option value="This photo has been digitally altered, please take a new certification picture and upload it as it comes off your camera">Photoshopped or digitally altered</option>
|
||||
<option value="You had a previous account on nonshy which was suspended and you are not welcome with a new account.">User was previously banned from nonshy</option>
|
||||
<option value="This is not an acceptable certification photo.">Not acceptable</option>
|
||||
<optgroup label="Other Actions">
|
||||
<option value="(ignore)">(Silently reject this photo without sending an e-mail)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -140,7 +140,15 @@
|
|||
<div class="column">
|
||||
{{.CurrentUser.Username}}
|
||||
{{if .NavUnreadNotifications}}<span class="nonshy-navbar-notification-tag is-warning ml-1">{{.NavUnreadNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}
|
||||
{{if and (.NavCertificationPhotos) (not .NavAdminFeedback)}}
|
||||
<span class="nonshy-navbar-notification-tag is-success ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{else if and .NavCertificationPhotos .NavAdminFeedback}}
|
||||
<span class="nonshy-navbar-notification-tag is-mixed ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{else}}
|
||||
<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -186,7 +194,16 @@
|
|||
<a class="navbar-item has-text-danger" href="/admin">
|
||||
<span class="icon"><i class="fa fa-peace"></i></span>
|
||||
<span>Admin</span>
|
||||
{{if .NavAdminNotifications}}<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}
|
||||
<!-- Color code them by the type -->
|
||||
{{if and (.NavCertificationPhotos) (not .NavAdminFeedback)}}
|
||||
<span class="nonshy-navbar-notification-tag is-success ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{else if and .NavCertificationPhotos .NavAdminFeedback}}
|
||||
<span class="nonshy-navbar-notification-tag is-mixed ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{else}}
|
||||
<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .SessionImpersonated}}
|
||||
|
|
Loading…
Reference in New Issue
Block a user