From e06bf2f9c44fcb40c0c00c39f6b76ef83c384604 Mon Sep 17 00:00:00 2001
From: Noah Petherbridge
Date: Mon, 22 Aug 2022 20:58:35 -0700
Subject: [PATCH] Spit and Polish
* Friends: can now see your sent requests awaiting approval too.
* Site Gallery: you may see Friends-only photos on the Gallery if you are
friends with the owner and the pic is opted-in for the Gallery.
* Site Gallery: show color coded visibility icons in card headers.
* Improve appearance of Upload Photo page.
* Update FAQ
---
pkg/controller/friend/friends.go | 9 +-
pkg/models/friend.go | 40 +++++++--
pkg/models/photo.go | 38 ++++++--
web/static/css/theme.css | 12 +++
web/templates/faq.html | 63 ++++++++++++++
web/templates/friend/friends.html | 18 +++-
web/templates/photo/gallery.html | 30 ++++++-
web/templates/photo/upload.html | 140 ++++++++++++++++++------------
8 files changed, 269 insertions(+), 81 deletions(-)
diff --git a/pkg/controller/friend/friends.go b/pkg/controller/friend/friends.go
index fce3b0e..e3a7e2c 100644
--- a/pkg/controller/friend/friends.go
+++ b/pkg/controller/friend/friends.go
@@ -13,7 +13,11 @@ import (
func Friends() http.HandlerFunc {
tmpl := templates.Must("friend/friends.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- isRequests := r.FormValue("view") == "requests"
+ var (
+ view = r.FormValue("view")
+ isRequests = view == "requests"
+ isPending = view == "pending"
+ )
currentUser, err := session.CurrentUser(r)
if err != nil {
@@ -28,7 +32,7 @@ func Friends() http.HandlerFunc {
Sort: "updated_at desc",
}
pager.ParsePage(r)
- friends, err := models.PaginateFriends(currentUser.ID, isRequests, pager)
+ friends, err := models.PaginateFriends(currentUser.ID, isRequests, isPending, pager)
if err != nil {
session.FlashError(w, r, "Couldn't paginate friends: %s", err)
templates.Redirect(w, "/")
@@ -37,6 +41,7 @@ func Friends() http.HandlerFunc {
var vars = map[string]interface{}{
"IsRequests": isRequests,
+ "IsPending": isPending,
"Friends": friends,
"Pager": pager,
}
diff --git a/pkg/models/friend.go b/pkg/models/friend.go
index d171254..01fb470 100644
--- a/pkg/models/friend.go
+++ b/pkg/models/friend.go
@@ -91,6 +91,19 @@ func FriendStatus(sourceUserID, targetUserID uint64) string {
return "none"
}
+// FriendIDs returns all user IDs with approved friendship to the user.
+func FriendIDs(userId uint64) []uint64 {
+ var (
+ fs = []*Friend{}
+ userIDs = []uint64{}
+ )
+ DB.Where("source_user_id = ? AND approved = ?", userId, true).Find(&fs)
+ for _, row := range fs {
+ userIDs = append(userIDs, row.TargetUserID)
+ }
+ return userIDs
+}
+
// CountFriendRequests gets a count of pending requests for the user.
func CountFriendRequests(userID uint64) (int64, error) {
var count int64
@@ -102,9 +115,15 @@ func CountFriendRequests(userID uint64) (int64, error) {
return count, result.Error
}
-// PaginateFriends gets a page of friends (or pending friend requests) as User objects ordered
-// by friendship date.
-func PaginateFriends(userID uint64, requests bool, pager *Pagination) ([]*User, error) {
+/*
+PaginateFriends gets a page of friends (or pending friend requests) as User objects ordered
+by friendship date.
+
+The `requests` and `sent` bools are mutually exclusive (use only one, or neither). `requests`
+asks for unanswered friend requests to you, and `sent` returns the friend requests that you
+have sent and have not been answered.
+*/
+func PaginateFriends(userID uint64, requests bool, sent bool, pager *Pagination) ([]*User, error) {
// We paginate over the Friend table.
var (
fs = []*Friend{}
@@ -112,17 +131,24 @@ func PaginateFriends(userID uint64, requests bool, pager *Pagination) ([]*User,
query *gorm.DB
)
+ if requests && sent {
+ return nil, errors.New("requests and sent are mutually exclusive options, use one or neither")
+ }
+
if requests {
query = DB.Where(
"target_user_id = ? AND approved = ?",
- userID,
- false,
+ userID, false,
+ )
+ } else if sent {
+ query = DB.Where(
+ "source_user_id = ? AND approved = ?",
+ userID, false,
)
} else {
query = DB.Where(
"source_user_id = ? AND approved = ?",
- userID,
- true,
+ userID, true,
)
}
diff --git a/pkg/models/photo.go b/pkg/models/photo.go
index 5ca3460..576d841 100644
--- a/pkg/models/photo.go
+++ b/pkg/models/photo.go
@@ -33,11 +33,21 @@ const (
PhotoPrivate = "private" // private
)
-var PhotoVisibilityAll = []PhotoVisibility{
- PhotoPublic,
- PhotoFriends,
- PhotoPrivate,
-}
+// PhotoVisibility preset settings.
+var (
+ PhotoVisibilityAll = []PhotoVisibility{
+ PhotoPublic,
+ PhotoFriends,
+ PhotoPrivate,
+ }
+
+ // Site Gallery visibility for when your friends show up in the gallery.
+ // Or: "Friends + Gallery" photos can appear to your friends in the Site Gallery.
+ PhotoVisibilityFriends = []string{
+ string(PhotoPublic),
+ string(PhotoFriends),
+ }
+)
// CreatePhoto with most of the settings you want (not ID or timestamps) in the database.
func CreatePhoto(tmpl Photo) (*Photo, error) {
@@ -127,13 +137,25 @@ func PaginateGalleryPhotos(userID uint64, adminView bool, explicitOK bool, pager
p = []*Photo{}
query *gorm.DB
blocklist = BlockedUserIDs(userID)
+ friendIDs = FriendIDs(userID)
wheres = []string{}
placeholders = []interface{}{}
)
- // Universal filters: public + gallery photos only.
- wheres = append(wheres, "visibility = ?", "gallery = ?")
- placeholders = append(placeholders, PhotoPublic, true)
+ // Include ourself in our friend IDs.
+ friendIDs = append(friendIDs, userID)
+
+ // You can see friends' Friend photos but only public for non-friends.
+ wheres = append(wheres,
+ "(user_id IN ? AND visibility IN ?) OR (user_id NOT IN ? AND visibility = ?)",
+ )
+ placeholders = append(placeholders,
+ friendIDs, PhotoVisibilityFriends, friendIDs, PhotoPublic,
+ )
+
+ // Gallery photos only.
+ wheres = append(wheres, "gallery = ?")
+ placeholders = append(placeholders, true)
// Filter blocked users.
if len(blocklist) > 0 {
diff --git a/web/static/css/theme.css b/web/static/css/theme.css
index 82a67ca..6364721 100644
--- a/web/static/css/theme.css
+++ b/web/static/css/theme.css
@@ -33,6 +33,13 @@
background-color: #FFEEFF;
}
+.has-text-private {
+ color: #CC00CC;
+}
+.has-text-private-light {
+ color: #FF99FF;
+}
+
/* Mobile: notification badge near the hamburger menu */
.nonshy-mobile-notification {
position: absolute;
@@ -44,4 +51,9 @@
.nonshy-mobile-notification {
display: none;
}
+}
+
+/* Bulma hack: full-width columns in photo card headers */
+.nonshy-fullwidth {
+ width: 100%;
}
\ No newline at end of file
diff --git a/web/templates/faq.html b/web/templates/faq.html
index a43f04e..f07f58d 100644
--- a/web/templates/faq.html
+++ b/web/templates/faq.html
@@ -69,6 +69,27 @@
want to see just dick pics everywhere. And don't set those as your default profile pic!
+ What appears on the Site Gallery?
+
+
+ The " Gallery" link on the site nav bar goes to the Site-wide
+ Photo Gallery page. Here is shown all of the public photos uploaded by
+ all (certified) users, if those pictures are also opted-in to appear on the Gallery in
+ their settings.
+
+
+
+ If you have friends on here, you may also see their "Friends-only" photos on the Site
+ Gallery. This way, you don't miss any updates if your friends add a new picture (so
+ long as they allow their picture to appear on the Gallery).
+
+
+
+ When you upload a picture you may opt it in or out of the Gallery by checking a box on
+ its settings page. For example, you can upload a Public photo but opt it out of
+ the Gallery -- it will then only appear on your profile page.
+
+
What is considered "explicit" in photos?
@@ -96,6 +117,48 @@
You can enable a setting on your profile if you are comfortable with seeing explicit
content from other users -- by default this site is "normal nudes" friendly!
+
+ Technical FAQs
+
+ Why did you build a custom website?
+
+
+ Other variants on this question might be: why not just run a
+ Mastodon instance? Or why
+ this website and not a Discord server or MeWe group or insert off-the-shelf
+ free software or hosted web service here?
+
+
+
+ It certainly would've been simpler to just use an off-the-shelf open source app
+ such as Mastodon (a decentralized, Twitter-like app) or similar. These apps though
+ have a scalability problem: users with their infinitely long timelines will upload
+ infinite photos until your server runs out of disk space and not enough of them may
+ donate to cover the costs. And the Fediverse feature (Mastodon is like e-mail and
+ users from all servers can like, follow and comment on one another across the entire
+ network) is a double edged sword too: all my members would need to tag even their
+ "normal nudes" as NSFW or else other servers would ban ours (meaning we have to follow
+ rules imposed by the wider Internet community), and conversely it is difficult to
+ moderate incoming content from other servers showing up on my users' timelines.
+ It's not a good fit for the vision I had in mind.
+
+
+
+ And on just using a service like Discord or MeWe to host my community: that's still
+ putting us in the hands of a corporation which can one day decide to ban all NSFW
+ users. Many people run nudist Discords and MeWe groups, but I needed something whose
+ fate is kept in my own hands.
+
+
+ Is this website open source?
+
+
+ Yes! The source code is currently hosted on the author's personal Git server. It
+ will eventually have a GitHub mirror and accept pull requests from the community.
+ In the mean time, contact the site owner to get a link to the public git repo.
+ This site is programmed in the Go language and released under the GNU General Public
+ License.
+
{{end}}
\ No newline at end of file
diff --git a/web/templates/friend/friends.html b/web/templates/friend/friends.html
index ec24bb6..23b1f34 100644
--- a/web/templates/friend/friends.html
+++ b/web/templates/friend/friends.html
@@ -15,7 +15,7 @@
- -
+
-
My Friends
-
@@ -24,6 +24,11 @@
{{if .NavFriendRequests}}{{.NavFriendRequests}}{{end}}
+ -
+
+ Sent
+
+
@@ -33,8 +38,13 @@
- You have {{.Pager.Total}} friend{{if .IsRequests}} request{{end}}{{Pluralize64 .Pager.Total}}
- (page {{.Pager.Page}} of {{.Pager.Pages}}).
+ {{if .IsPending}}
+ You have sent {{.Pager.Total}} friend request{{Pluralize64 .Pager.Total}} which
+ {{Pluralize64 .Pager.Total "has" "have"}} not been approved yet.
+ {{else}}
+ You have {{.Pager.Total}} friend{{if .IsRequests}} request{{end}}{{Pluralize64 .Pager.Total}}
+ (page {{.Pager.Page}} of {{.Pager.Pages}}).
+ {{end}}
@@ -121,7 +131,7 @@
{{end}}
diff --git a/web/templates/photo/gallery.html b/web/templates/photo/gallery.html
index 0f7d8ce..7b85a81 100644
--- a/web/templates/photo/gallery.html
+++ b/web/templates/photo/gallery.html
@@ -35,14 +35,14 @@
{{else if eq .Visibility "friends"}}
-
+
Friends
{{else}}
-
+
Private
@@ -203,7 +203,7 @@