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 @@
@@ -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 @@
{{if $Root.UserMap.Has .UserID}} {{$Owner := $Root.UserMap.Get .UserID}} -
+
{{if gt $Owner.ProfilePhoto.ID 0}} @@ -219,6 +219,17 @@
+
+ + {{if eq .Visibility "friends"}} + + {{else if eq .Visibility "private"}} + + {{else}} + + {{end}} + +
{{else}} @@ -278,7 +289,7 @@
{{if $Root.UserMap.Has .UserID}} {{$Owner := $Root.UserMap.Get .UserID}} -
+
{{if gt $Owner.ProfilePhoto.ID 0}} @@ -294,6 +305,17 @@
+
+ + {{if eq .Visibility "friends"}} + + {{else if eq .Visibility "private"}} + + {{else}} + + {{end}} + +
{{else}} diff --git a/web/templates/photo/upload.html b/web/templates/photo/upload.html index 20c4174..c3f741a 100644 --- a/web/templates/photo/upload.html +++ b/web/templates/photo/upload.html @@ -197,7 +197,7 @@
-
+
-
- +
+ +
+ +

+ This photo will appear on your profile page and can be seen by any + logged-in user account. It may also appear on the site-wide Photo + Gallery if that option is enabled, below. +

+
+
+ +

+ Only users you have accepted as a friend can see this photo on your + profile page and on the site-wide Photo Gallery if that option is + enabled, below. +

+
+
+ +

+ This photo is visible only to you and to users for whom you have + granted access (the latter feature is coming soon!) +

+
+
+ +
+ + +

+ Leave this box checked and your photo can appear in the site's Photo Gallery + page. Mainly your Public photos will appear + on the Gallery, and your approved friends may see your + Friends-only photos there as well. + Private photos may appear in + the gallery to users whom you have granted access. If this is undesirable, + un-check the Gallery box to skip the Site Gallery. +

+
+ +
+ {{if eq .Intent "profile_pic"}} Your default profile picture should @@ -238,59 +319,6 @@ {{end}}
-
- -
- -
-
- -
-
- -
-
- -
- - -

- Leave this box checked and your (public only) photo can appear in the site's - Photo Gallery page. If you uncheck this box, your (public) photo will still - appear on your profile page but not on the site photo gallery. Friends-only - or private photos never appear in the gallery even if this box is checked. -

-
- {{if not .EditPhoto}}