Private Profiles & Misc Improvements
* Add setting to mark profile as "private" * If a profile is private you can't see their profile page or user photo gallery unless you are friends (or admin) * The Site Gallery never shows pictures from private profiles. * Add HTML5 drag/drop upload support for photo gallery. * Suppress SQL logging except in debug mode. * Clean up extra logs.
This commit is contained in:
parent
09d61aa5c7
commit
7f96edf95d
|
@ -145,8 +145,11 @@ func initdb(c *cli.Context) {
|
|||
// Load the settings.json
|
||||
config.LoadSettings()
|
||||
|
||||
var gormcfg = &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
var gormcfg = &gorm.Config{}
|
||||
if c.Bool("debug") {
|
||||
gormcfg = &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the database.
|
||||
|
|
|
@ -3,7 +3,6 @@ package account
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"git.kirsle.net/apps/gosocial/pkg/log"
|
||||
"git.kirsle.net/apps/gosocial/pkg/templates"
|
||||
)
|
||||
|
||||
|
@ -11,7 +10,6 @@ import (
|
|||
func Dashboard() http.HandlerFunc {
|
||||
tmpl := templates.Must("account/dashboard.html")
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Error("Dashboard called")
|
||||
if err := tmpl.Execute(w, r, nil); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -37,6 +37,8 @@ func Profile() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
var isSelf = currentUser.ID == user.ID
|
||||
|
||||
// Banned or disabled? Only admin can view then.
|
||||
if user.Status != models.UserStatusActive && !currentUser.IsAdmin {
|
||||
templates.NotFoundPage(w, r)
|
||||
|
@ -49,9 +51,16 @@ func Profile() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Are they friends? And/or is this user private?
|
||||
var (
|
||||
isFriend = models.FriendStatus(currentUser.ID, user.ID)
|
||||
isPrivate = !currentUser.IsAdmin && !isSelf && user.Visibility == models.UserVisibilityPrivate && isFriend != "approved"
|
||||
)
|
||||
|
||||
vars := map[string]interface{}{
|
||||
"User": user,
|
||||
"IsFriend": models.FriendStatus(currentUser.ID, user.ID),
|
||||
"User": user,
|
||||
"IsFriend": isFriend,
|
||||
"IsPrivate": isPrivate,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
|
|
|
@ -93,9 +93,15 @@ func Settings() http.HandlerFunc {
|
|||
case "preferences":
|
||||
var (
|
||||
explicit = r.PostFormValue("explicit") == "true"
|
||||
private = r.PostFormValue("private") == "true"
|
||||
)
|
||||
|
||||
user.Explicit = explicit
|
||||
if private {
|
||||
user.Visibility = models.UserVisibilityPrivate
|
||||
} else {
|
||||
user.Visibility = models.UserVisibilityPublic
|
||||
}
|
||||
|
||||
if err := user.Save(); err != nil {
|
||||
session.FlashError(w, r, "Failed to save user to database: %s", err)
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"regexp"
|
||||
|
||||
"git.kirsle.net/apps/gosocial/pkg/config"
|
||||
"git.kirsle.net/apps/gosocial/pkg/log"
|
||||
"git.kirsle.net/apps/gosocial/pkg/models"
|
||||
"git.kirsle.net/apps/gosocial/pkg/session"
|
||||
"git.kirsle.net/apps/gosocial/pkg/templates"
|
||||
|
@ -52,6 +51,17 @@ func UserPhotos() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Is this user private and we're not friends?
|
||||
var (
|
||||
areFriends = models.AreFriends(user.ID, currentUser.ID)
|
||||
isPrivate = user.Visibility == models.UserVisibilityPrivate && !areFriends
|
||||
)
|
||||
if isPrivate && !currentUser.IsAdmin && !isOwnPhotos {
|
||||
session.FlashError(w, r, "This user's profile page and photo gallery are private.")
|
||||
templates.Redirect(w, "/u/"+user.Username)
|
||||
return
|
||||
}
|
||||
|
||||
// What set of visibilities to query?
|
||||
visibility := []models.PhotoVisibility{models.PhotoPublic}
|
||||
if isOwnPhotos || currentUser.IsAdmin {
|
||||
|
@ -73,7 +83,6 @@ func UserPhotos() http.HandlerFunc {
|
|||
Sort: "created_at desc",
|
||||
}
|
||||
pager.ParsePage(r)
|
||||
log.Error("Pager: %+v", pager)
|
||||
photos, err := models.PaginateUserPhotos(user.ID, visibility, explicit, pager)
|
||||
|
||||
// Get the count of explicit photos if we are not viewing explicit photos.
|
||||
|
|
|
@ -16,7 +16,7 @@ func init() {
|
|||
Theme: golog.DarkTheme,
|
||||
})
|
||||
|
||||
log.Config.Level = golog.DebugLevel
|
||||
log.Config.Level = golog.InfoLevel
|
||||
}
|
||||
|
||||
// SetDebug toggles debug level logging.
|
||||
|
|
|
@ -152,6 +152,11 @@ func PaginateGalleryPhotos(userID uint64, adminView bool, explicitOK bool, pager
|
|||
"EXISTS (SELECT 1 FROM users WHERE id = photos.user_id AND certified = true)",
|
||||
)
|
||||
|
||||
// Exclude private users' photos.
|
||||
wheres = append(wheres,
|
||||
"NOT EXISTS (SELECT 1 FROM users WHERE id = photos.user_id AND visibility = 'private')",
|
||||
)
|
||||
|
||||
// Admin view: get ALL PHOTOS on the site, period.
|
||||
if adminView {
|
||||
query = DB
|
||||
|
|
|
@ -19,9 +19,9 @@ type User struct {
|
|||
Username string `gorm:"uniqueIndex"`
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
HashedPassword string
|
||||
IsAdmin bool `gorm:"index"`
|
||||
Status UserStatus `gorm:"index"` // active, disabled
|
||||
Visibility string `gorm:"index"` // public, private
|
||||
IsAdmin bool `gorm:"index"`
|
||||
Status UserStatus `gorm:"index"` // active, disabled
|
||||
Visibility UserVisibility `gorm:"index"` // public, private
|
||||
Name *string
|
||||
Birthdate time.Time
|
||||
Certified bool
|
||||
|
@ -36,6 +36,13 @@ type User struct {
|
|||
ProfilePhoto Photo `gorm:"foreignKey:profile_photo_id"`
|
||||
}
|
||||
|
||||
type UserVisibility string
|
||||
|
||||
const (
|
||||
UserVisibilityPublic UserVisibility = "public"
|
||||
UserVisibilityPrivate = "private"
|
||||
)
|
||||
|
||||
// Preload related tables for the user (classmethod).
|
||||
func (u *User) Preload() *gorm.DB {
|
||||
return DB.Preload("ProfileField").Preload("ProfilePhoto")
|
||||
|
@ -60,9 +67,10 @@ func CreateUser(username, email, password string) (*User, error) {
|
|||
}
|
||||
|
||||
u := &User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Status: UserStatusActive,
|
||||
Username: username,
|
||||
Email: email,
|
||||
Status: UserStatusActive,
|
||||
Visibility: UserVisibilityPublic,
|
||||
}
|
||||
|
||||
if err := u.HashPassword(password); err != nil {
|
||||
|
|
|
@ -56,7 +56,7 @@ func LoadOrNew(r *http.Request) *Session {
|
|||
key := fmt.Sprintf(config.SessionRedisKeyFormat, sess.UUID)
|
||||
|
||||
err = redis.Get(key, sess)
|
||||
log.Error("LoadOrNew: raw from Redis: %+v", sess)
|
||||
// log.Error("LoadOrNew: raw from Redis: %+v", sess)
|
||||
if err != nil {
|
||||
log.Error("session.LoadOrNew: didn't find %s in Redis: %s", err)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<div class="column">
|
||||
<h1 class="title">
|
||||
{{.User.NameOrUsername}}
|
||||
{{if eq .User.Visibility "private"}}<sup class="fa fa-mask ml-2 is-size-6" title="Private Profile"></sup>{{end}}
|
||||
</h1>
|
||||
{{if ne .User.Status "active"}}
|
||||
<h2 class="subtitle">
|
||||
|
@ -164,6 +165,14 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{{if .IsPrivate}}
|
||||
<div class="block p-4">
|
||||
<div class="notification block is-warning">
|
||||
<i class="fa fa-mask"></i> This member's profile page is <strong>private.</strong> You may send them
|
||||
a friend request, and only if approved, you may then view their profile page and photo gallery.
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="block p-4">
|
||||
<div class="tabs is-boxed">
|
||||
<ul>
|
||||
|
@ -352,7 +361,8 @@
|
|||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /columns-->
|
||||
</div>
|
||||
{{end}}<!-- not IsPrivate -->
|
||||
</div>
|
||||
{{end}}
|
|
@ -183,7 +183,12 @@
|
|||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">
|
||||
<a href="/u/{{.Username}}" class="has-text-dark">{{.NameOrUsername}}</a>
|
||||
<a href="/u/{{.Username}}" class="has-text-dark">
|
||||
{{.NameOrUsername}}
|
||||
</a>
|
||||
{{if eq .Visibility "private"}}
|
||||
<sup class="fa fa-mask is-size-7" title="Private Profile"></sup>
|
||||
{{end}}
|
||||
</p>
|
||||
<p class="subtitle is-6 mb-2">
|
||||
<span class="icon"><i class="fa fa-user"></i></span>
|
||||
|
|
|
@ -219,6 +219,22 @@
|
|||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">Private Profile</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="private"
|
||||
value="true"
|
||||
{{if eq .CurrentUser.Visibility "private"}}checked{{end}}>
|
||||
Mark my profile page as "private"
|
||||
</label>
|
||||
<p class="help">
|
||||
If you check this box then only friends who you have approved are able to
|
||||
see your profile page and gallery. Your gallery photos also will NOT appear
|
||||
on the Site Gallery page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Explicit Content Filter</label>
|
||||
<label class="checkbox">
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<ul>
|
||||
<li>
|
||||
For <strong>nudists:</strong> a default setting on your profile will hide 'explicit content'
|
||||
from users' profile pages and you will not see the explicit web forums either.
|
||||
from users' profile pages and you will not see the explicit web forum threads either.
|
||||
</li>
|
||||
<li>
|
||||
For <strong>exhibitionists:</strong> you can toggle that setting to view explicit content
|
||||
|
@ -44,6 +44,19 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>Open Beta</h1>
|
||||
|
||||
<p>
|
||||
This website is currently open for beta testing. It's not 100% complete yet, but is basically
|
||||
functional. It currently supports profile pages, photo galleries, friend requests, Direct
|
||||
Messages, a member directory to discover new users, and basic account management features.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Before the "1.0" launch it will include web forums which will provide a community space to
|
||||
chat with and meet other members.
|
||||
</p>
|
||||
|
||||
<h1>Site Rules</h1>
|
||||
|
||||
<ul>
|
||||
|
@ -72,25 +85,11 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>Site Features</h1>
|
||||
|
||||
<p>
|
||||
This website is still a work in progress, but <em>eventually</em> it will have at least
|
||||
the following features and functions:
|
||||
For more details, please check out the <a href="/tos">Terms of Service</a> as well as
|
||||
the <a href="/privacy">Privacy Policy</a>.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Web forums</strong> where you can write posts and meet your fellow members in the comments.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Profile pages</strong> where you can write a bit about yourself and upload some of your
|
||||
nudist or exhibitionist pictures.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Direct messages</strong> where you can chat with other members on the site.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="column is-one-quarter">
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
When User Gallery: .User is defined, .IsOwnPhotos may be.
|
||||
-->
|
||||
{{define "title"}}
|
||||
{{if .IsSiteGallery}}Member Gallery{{else}}Photos of {{.User.Username}}{{end}}
|
||||
{{if .IsSiteGallery}}
|
||||
Member Gallery
|
||||
{{else}}
|
||||
Photos of {{.User.Username}}
|
||||
{{if eq .User.Visibility "private"}}<sup class="fa fa-mask ml-2 is-size-6" title="Private Profile"></sup>{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<!-- Reusable card body -->
|
||||
|
|
|
@ -19,6 +19,16 @@
|
|||
|
||||
{{ $User := .CurrentUser }}
|
||||
|
||||
<!-- Drag/Drop Modal -->
|
||||
<div class="modal" id="drop-modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box content has-text-centered">
|
||||
<h1><i class="fa fa-upload mr-2"></i> Drop image to select it for upload</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .EditPhoto}}
|
||||
<form action="/photo/edit" method="POST">
|
||||
<input type="hidden" name="id" value="{{.EditPhoto.ID}}">
|
||||
|
@ -347,13 +357,13 @@
|
|||
$fileName = document.querySelector("#fileName"),
|
||||
$hiddenPreview = document.querySelector("#imagePreview"),
|
||||
$previewBox = document.querySelector("#previewBox"),
|
||||
$cropField = document.querySelector("#cropCoords");
|
||||
$cropField = document.querySelector("#cropCoords"),
|
||||
$dropArea = document.querySelector("#drop-modal")
|
||||
$body = document.querySelector("body");
|
||||
|
||||
// Clear the answer in case of page reload.
|
||||
$cropField.value = "";
|
||||
|
||||
$file.addEventListener("change", function() {
|
||||
let file = this.files[0];
|
||||
// Common handler for file selection, either via input
|
||||
// field or drag/drop onto the page.
|
||||
let onFile = (file) => {
|
||||
$fileName.innerHTML = file.name;
|
||||
|
||||
// Read the image to show the preview on-page.
|
||||
|
@ -411,7 +421,45 @@
|
|||
}
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
// Set up drag/drop file upload events.
|
||||
$body.addEventListener("dragenter", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$dropArea.classList.add("is-active");
|
||||
});
|
||||
$body.addEventListener("dragover", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$dropArea.classList.add("is-active");
|
||||
});
|
||||
$body.addEventListener("dragleave", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$dropArea.classList.remove("is-active");
|
||||
});
|
||||
$body.addEventListener("drop", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$dropArea.classList.remove("is-active");
|
||||
|
||||
// Grab the file.
|
||||
let dt = e.dataTransfer;
|
||||
let file = dt.files[0];
|
||||
|
||||
// Set the file on the input field too.
|
||||
$file.files = dt.files;
|
||||
|
||||
onFile(file);
|
||||
});
|
||||
|
||||
// Clear the answer in case of page reload.
|
||||
$cropField.value = "";
|
||||
|
||||
$file.addEventListener("change", function() {
|
||||
let file = this.files[0];
|
||||
onFile(file);
|
||||
});
|
||||
});
|
||||
{{end}}
|
||||
|
|
Reference in New Issue
Block a user