address PR comments

- update error messages to be correct
- move suspended message into template and include for other pages
- check suspended status on all relevant pages and show message if
logged in user is suspended.
- fix possible nil pointer error
- remove changes to db schema files
- add version comment to migration
- add UserStatus type with UserActive and UserSuspended
- change database table to use status column instead of suspended
- update toggle suspended handler to be toggle status in prep for
possible future inclusion of further user statuses
This commit is contained in:
Rob Loranger 2019-10-25 12:04:24 -07:00
parent 9873fc443f
commit f85f0751a3
No known key found for this signature in database
GPG key ID: D6F1633A4F0903B8
30 changed files with 148 additions and 72 deletions

View file

@ -750,14 +750,20 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err
log.Error("unable to fetch collections: %v", err)
}
suspended, err := app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("view articles: %v", err)
}
d := struct {
*UserPage
AnonymousPosts *[]PublicPost
Collections *[]Collection
Suspended bool
}{
UserPage: NewUserPage(app, r, u, u.Username+"'s Posts", f),
AnonymousPosts: p,
Collections: c,
Suspended: suspended,
}
d.UserPage.SetMessaging(u)
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
@ -779,6 +785,11 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
uc, _ := app.db.GetUserCollectionCount(u.ID)
// TODO: handle any errors
suspended, err := app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("view collections %v", err)
return fmt.Errorf("view collections: %v", err)
}
d := struct {
*UserPage
Collections *[]Collection
@ -786,11 +797,13 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
UsedCollections, TotalCollections int
NewBlogsDisabled bool
Suspended bool
}{
UserPage: NewUserPage(app, r, u, u.Username+"'s Blogs", f),
Collections: c,
UsedCollections: int(uc),
NewBlogsDisabled: !app.cfg.App.CanCreateBlogs(uc),
Suspended: suspended,
}
d.UserPage.SetMessaging(u)
showUserPage(w, "collections", d)
@ -808,13 +821,20 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
return ErrCollectionNotFound
}
suspended, err := app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("view edit collection %v", err)
return fmt.Errorf("view edit collection: %v", err)
}
flashes, _ := getSessionFlashes(app, w, r, nil)
obj := struct {
*UserPage
*Collection
Suspended bool
}{
UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes),
Collection: c,
Suspended: suspended,
}
showUserPage(w, "collection", obj)
@ -976,17 +996,24 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
titleStats = c.DisplayTitle() + " "
}
suspended, err := app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("view stats: %v", err)
return err
}
obj := struct {
*UserPage
VisitsBlog string
Collection *Collection
TopPosts *[]PublicPost
APFollowers int
Suspended bool
}{
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes),
VisitsBlog: alias,
Collection: c,
TopPosts: topPosts,
Suspended: suspended,
}
if app.cfg.App.Federation {
folls, err := app.db.GetAPFollowers(c)
@ -1026,7 +1053,7 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
Email: fullUser.EmailClear(app.keys),
HasPass: passIsSet,
IsLogOut: r.FormValue("logout") == "1",
Suspended: fullUser.Suspended,
Suspended: fullUser.Status == UserSuspended,
}
showUserPage(w, "settings", obj)

View file

@ -82,7 +82,7 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re
}
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: get owner: %v", err)
log.Error("fetch collection activities: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -115,7 +115,7 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques
}
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: get owner: %v", err)
log.Error("fetch collection outbox: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -176,7 +176,7 @@ func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Req
}
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: get owner: %v", err)
log.Error("fetch collection followers: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -230,7 +230,7 @@ func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Req
}
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: get owner: %v", err)
log.Error("fetch collection following: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -272,7 +272,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
}
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: get owner: %v", err)
log.Error("fetch collection inbox: %v", err)
return ErrInternalGeneral
}
if suspended {

View file

@ -230,28 +230,27 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
return nil
}
func handleAdminToggleUserSuspended(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["username"]
if username == "" {
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
userToToggle, err := app.db.GetUserForAuth(username)
user, err := app.db.GetUserForAuth(username)
if err != nil {
log.Error("failed to get user: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user from username: %v", err)}
}
if userToToggle.Suspended {
err = app.db.SetUserSuspended(userToToggle.ID, false)
if user.Status == UserSuspended {
err = app.db.SetUserStatus(user.ID, UserActive)
} else {
err = app.db.SetUserSuspended(userToToggle.ID, true)
err = app.db.SetUserStatus(user.ID, UserSuspended)
}
if err != nil {
log.Error("toggle user suspended: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user suspended: %v")}
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user status: %v")}
}
// TODO: invalidate sessions
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s#status", username)}
}

View file

@ -71,6 +71,7 @@ type (
CurrentPage int
TotalPages int
Format *CollectionFormat
Suspended bool
}
SubmittedCollection struct {
// Data used for updating a given collection
@ -398,7 +399,7 @@ func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
}
suspended, err := app.db.IsUserSuspended(userID)
if err != nil {
log.Error("new collection: get user: %v", err)
log.Error("new collection: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -486,6 +487,7 @@ func fetchCollection(app *App, w http.ResponseWriter, r *http.Request) error {
res.Owner = u
}
}
// TODO: check suspended
app.db.GetPostsCount(res, isCollOwner)
// Strip non-public information
res.Collection.ForPublic()
@ -738,14 +740,10 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("view collection: get owner: %v", err)
log.Error("view collection: %v", err)
return ErrInternalGeneral
}
if suspended {
return ErrCollectionNotFound
}
// Serve ActivityStreams data now, if requested
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
ac := c.PersonObject()
@ -802,6 +800,10 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
log.Error("Error getting user for collection: %v", err)
}
}
if !isOwner && suspended {
return ErrCollectionNotFound
}
displayPage.Suspended = isOwner && suspended
displayPage.Owner = owner
coll.Owner = displayPage.Owner
@ -853,10 +855,6 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
return err
}
if u.Suspended {
return ErrCollectionNotFound
}
page := getCollectionPage(vars)
c, err := processCollectionPermissions(app, cr, u, w, r)
@ -908,6 +906,10 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
log.Error("Error getting user for collection: %v", err)
}
}
if !isOwner && u.Status == UserSuspended {
return ErrCollectionNotFound
}
displayPage.Suspended = u.Status == UserSuspended
displayPage.Owner = owner
coll.Owner = displayPage.Owner
// Add more data
@ -946,7 +948,7 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
collAlias := vars["alias"]
isWeb := r.FormValue("web") == "1"
var u *User
u := &User{}
if reqJSON && !isWeb {
// Ensure an access token was given
accessToken := r.Header.Get("Authorization")
@ -963,7 +965,7 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
suspended, err := app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("existing collection: get user suspended status: %v", err)
log.Error("existing collection: %v", err)
return ErrInternalGeneral
}

View file

@ -296,7 +296,7 @@ func (db *datastore) CreateCollection(cfg *config.Config, alias, title string, u
func (db *datastore) GetUserByID(id int64) (*User, error) {
u := &User{ID: id}
err := db.QueryRow("SELECT username, password, email, created, suspended FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
err := db.QueryRow("SELECT username, password, email, created, status FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created, &u.Status)
switch {
case err == sql.ErrNoRows:
return nil, ErrUserNotFound
@ -313,16 +313,16 @@ func (db *datastore) GetUserByID(id int64) (*User, error) {
func (db *datastore) IsUserSuspended(id int64) (bool, error) {
u := &User{ID: id}
err := db.QueryRow("SELECT suspended FROM users WHERE id = ?", id).Scan(&u.Suspended)
err := db.QueryRow("SELECT status FROM users WHERE id = ?", id).Scan(&u.Status)
switch {
case err == sql.ErrNoRows:
return false, ErrUserNotFound
return false, fmt.Errorf("is user suspended: %v", ErrUserNotFound)
case err != nil:
log.Error("Couldn't SELECT user password: %v", err)
return false, err
return false, fmt.Errorf("is user suspended: %v", err)
}
return u.Suspended, nil
return u.Status == UserSuspended, nil
}
// DoesUserNeedAuth returns true if the user hasn't provided any methods for
@ -364,7 +364,7 @@ func (db *datastore) IsUserPassSet(id int64) (bool, error) {
func (db *datastore) GetUserForAuth(username string) (*User, error) {
u := &User{Username: username}
err := db.QueryRow("SELECT id, password, email, created, suspended FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
err := db.QueryRow("SELECT id, password, email, created, status FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Status)
switch {
case err == sql.ErrNoRows:
// Check if they've entered the wrong, unnormalized username
@ -387,7 +387,7 @@ func (db *datastore) GetUserForAuth(username string) (*User, error) {
func (db *datastore) GetUserForAuthByID(userID int64) (*User, error) {
u := &User{ID: userID}
err := db.QueryRow("SELECT id, password, email, created, suspended FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
err := db.QueryRow("SELECT id, password, email, created, status FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Status)
switch {
case err == sql.ErrNoRows:
return nil, ErrUserNotFound
@ -1650,7 +1650,7 @@ func (db *datastore) GetTotalCollections() (collCount int64, err error) {
SELECT COUNT(*)
FROM collections c
LEFT JOIN users u ON u.id = c.owner_id
WHERE u.suspended = 0`).Scan(&collCount)
WHERE u.status = 0`).Scan(&collCount)
if err != nil {
log.Error("Unable to fetch collections count: %v", err)
}
@ -1662,7 +1662,7 @@ func (db *datastore) GetTotalPosts() (postCount int64, err error) {
SELECT COUNT(*)
FROM posts p
LEFT JOIN users u ON u.id = p.owner_id
WHERE u.Suspended = 0`).Scan(&postCount)
WHERE u.status = 0`).Scan(&postCount)
if err != nil {
log.Error("Unable to fetch posts count: %v", err)
}
@ -2384,7 +2384,7 @@ func (db *datastore) GetAllUsers(page uint) (*[]User, error) {
limitStr = fmt.Sprintf("%d, %d", (page-1)*adminUsersPerPage, adminUsersPerPage)
}
rows, err := db.Query("SELECT id, username, created, suspended FROM users ORDER BY created DESC LIMIT " + limitStr)
rows, err := db.Query("SELECT id, username, created, status FROM users ORDER BY created DESC LIMIT " + limitStr)
if err != nil {
log.Error("Failed selecting from users: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve all users."}
@ -2394,7 +2394,7 @@ func (db *datastore) GetAllUsers(page uint) (*[]User, error) {
users := []User{}
for rows.Next() {
u := User{}
err = rows.Scan(&u.ID, &u.Username, &u.Created, &u.Suspended)
err = rows.Scan(&u.ID, &u.Username, &u.Created, &u.Status)
if err != nil {
log.Error("Failed scanning GetAllUsers() row: %v", err)
break
@ -2431,10 +2431,11 @@ func (db *datastore) GetUserLastPostTime(id int64) (*time.Time, error) {
return &t, nil
}
func (db *datastore) SetUserSuspended(id int64, suspend bool) error {
_, err := db.Exec("UPDATE users SET suspended = ? WHERE id = ?", suspend, id)
// SetUserStatus changes a user's status in the database. see Users.UserStatus
func (db *datastore) SetUserStatus(id int64, status UserStatus) error {
_, err := db.Exec("UPDATE users SET status = ? WHERE id = ?", status, id)
if err != nil {
return fmt.Errorf("failed to update user suspended status: %v", err)
return fmt.Errorf("failed to update user status: %v", err)
}
return nil
}

View file

@ -78,7 +78,7 @@ func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Re
muVal := r.FormValue("uses")
expVal := r.FormValue("expires")
if u.Suspended {
if u.Status == UserSuspended {
return ErrUserSuspended
}

View file

@ -58,7 +58,7 @@ func (m *migration) Migrate(db *datastore) error {
var migrations = []Migration{
New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
New("support dynamic instance pages", supportInstancePages), // V1 -> V2 (v0.9.0)
New("support users suspension", supportUserSuspension), // V2 -> V3 ()
New("support users suspension", supportUserStatus), // V2 -> V3 (v0.11.0)
}
// CurrentVer returns the current migration version the application is on

View file

@ -10,10 +10,10 @@
package migrations
func supportUserSuspension(db *datastore) error {
func supportUserStatus(db *datastore) error {
t, err := db.Begin()
_, err = t.Exec(`ALTER TABLE users ADD COLUMN suspended ` + db.typeBool() + ` DEFAULT '0' NOT NULL`)
_, err = t.Exec(`ALTER TABLE users ADD COLUMN status ` + db.typeInt() + ` DEFAULT '0' NOT NULL`)
if err != nil {
t.Rollback()
return err

View file

@ -383,7 +383,7 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
suspended, err := app.db.IsUserSuspended(ownerID.Int64)
if err != nil {
log.Error("view post: get collection owner: %v", err)
log.Error("view post: %v", err)
return ErrInternalGeneral
}
@ -509,7 +509,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
suspended, err := app.db.IsUserSuspended(userID)
if err != nil {
log.Error("new post: get user: %v", err)
log.Error("new post: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -683,7 +683,7 @@ func existingPost(app *App, w http.ResponseWriter, r *http.Request) error {
suspended, err := app.db.IsUserSuspended(userID)
if err != nil {
log.Error("existing post: get user: %v", err)
log.Error("existing post: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -886,7 +886,7 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error {
suspended, err := app.db.IsUserSuspended(ownerID)
if err != nil {
log.Error("add post: get user: %v", err)
log.Error("add post: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -989,7 +989,7 @@ func pinPost(app *App, w http.ResponseWriter, r *http.Request) error {
suspended, err := app.db.IsUserSuspended(userID)
if err != nil {
log.Error("pin post: get user: %v", err)
log.Error("pin post: %v", err)
return ErrInternalGeneral
}
if suspended {
@ -1063,7 +1063,7 @@ func fetchPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
suspended, err := app.db.IsUserSuspended(ownerID)
if err != nil {
log.Error("fetch post: get owner: %v", err)
log.Error("fetch post: %v", err)
return ErrInternalGeneral
}
@ -1333,13 +1333,10 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
suspended, err := app.db.IsUserSuspended(c.OwnerID)
if err != nil {
log.Error("view collection post: get owner: %v", err)
log.Error("view collection post: %v", err)
return ErrInternalGeneral
}
if suspended {
return ErrPostNotFound
}
// Check collection permissions
if c.IsPrivate() && (u == nil || u.ID != c.OwnerID) {
return ErrPostNotFound
@ -1396,6 +1393,9 @@ Are you sure it was ever here?`,
p.Collection = coll
p.IsTopLevel = app.cfg.App.SingleUser
if !p.IsOwner && suspended {
return ErrPostNotFound
}
// Check if post has been unpublished
if p.Content == "" && p.Title.String == "" {
return impart.HTTPError{http.StatusGone, "Post was unpublished."}
@ -1445,12 +1445,14 @@ Are you sure it was ever here?`,
IsFound bool
IsAdmin bool
CanInvite bool
Suspended bool
}{
PublicPost: p,
StaticPage: pageForReq(app, r),
IsOwner: cr.isCollOwner,
IsCustomDomain: cr.isCustomDomain,
IsFound: postFound,
Suspended: suspended,
}
tp.IsAdmin = u != nil && u.IsAdmin()
tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin)

View file

@ -71,7 +71,7 @@ func (app *App) FetchPublicPosts() (interface{}, error) {
FROM collections c
LEFT JOIN posts p ON p.collection_id = c.id
LEFT JOIN users u ON u.id = p.owner_id
WHERE c.privacy = 1 AND (p.created >= ` + app.db.dateSub(3, "month") + ` AND p.created <= ` + app.db.now() + ` AND pinned_position IS NULL) AND u.suspended = 0
WHERE c.privacy = 1 AND (p.created >= ` + app.db.dateSub(3, "month") + ` AND p.created <= ` + app.db.now() + ` AND pinned_position IS NULL) AND u.status = 0
ORDER BY p.created DESC`)
if err != nil {
log.Error("Failed selecting from posts: %v", err)

View file

@ -144,7 +144,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET")
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
write.HandleFunc("/admin/user/{username}", handler.Admin(handleAdminToggleUserSuspended)).Methods("POST")
write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST")
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET")
write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST")

View file

@ -225,7 +225,6 @@ CREATE TABLE IF NOT EXISTS `users` (
`password` char(60) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`email` varbinary(255) DEFAULT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`suspended` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

View file

@ -214,8 +214,7 @@ CREATE TABLE IF NOT EXISTS `users` (
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT DEFAULT NULL,
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
suspended INTEGER NOT NULL DEFAULT 0
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- --------------------------------------------------------

View file

@ -11,10 +11,6 @@
package writefreely
import (
"github.com/dustin/go-humanize"
"github.com/writeas/web-core/l10n"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
"html/template"
"io"
"io/ioutil"
@ -22,6 +18,11 @@ import (
"os"
"path/filepath"
"strings"
"github.com/dustin/go-humanize"
"github.com/writeas/web-core/l10n"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
)
var (
@ -63,6 +64,7 @@ func initTemplate(parentDir, name string) {
filepath.Join(parentDir, templatesDir, name+".tmpl"),
filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"),
filepath.Join(parentDir, templatesDir, "base.tmpl"),
filepath.Join(parentDir, templatesDir, "user", "include", "suspended.tmpl"),
}
if name == "collection" || name == "collection-tags" || name == "chorus-collection" {
// These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
@ -86,6 +88,7 @@ func initPage(parentDir, path, key string) {
path,
filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"),
filepath.Join(parentDir, templatesDir, "base.tmpl"),
filepath.Join(parentDir, templatesDir, "user", "include", "suspended.tmpl"),
))
}
@ -98,6 +101,7 @@ func initUserPage(parentDir, path, key string) {
path,
filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"),
filepath.Join(parentDir, templatesDir, "user", "include", "footer.tmpl"),
filepath.Join(parentDir, templatesDir, "user", "include", "suspended.tmpl"),
))
}

View file

@ -65,6 +65,9 @@ article time.dt-published {
{{template "user-navigation" .}}
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<article id="post-body" class="{{.Font}} h-entry">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name">{{.FormattedDisplayTitle}}</h2>{{end}}{{/* TODO: check format: if .Collection.Format.ShowDates*/}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</time><div class="e-content">{{.HTMLContent}}</div></article>
{{ if .Collection.ShowFooterBranding }}

View file

@ -61,6 +61,9 @@ body#collection header nav.tabs a:first-child {
<body id="collection" itemscope itemtype="http://schema.org/WebPage">
{{template "user-navigation" .}}
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<header>
<h1 dir="{{.Direction}}" id="blog-title"><a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
{{if .Description}}<p class="description p-note">{{.Description}}</p>{{end}}

View file

@ -59,6 +59,9 @@
</nav>
</header>
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<article id="post-body" class="{{.Font}} h-entry {{if not .IsFound}}error-page{{end}}">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name">{{.FormattedDisplayTitle}}</h2>{{end}}<div class="e-content">{{.HTMLContent}}</div></article>
{{ if .Collection.ShowFooterBranding }}

View file

@ -53,6 +53,9 @@
</nav>
</header>
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
{{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}}
<h1>{{.Tag}}</h1>
{{template "posts" .}}

View file

@ -62,6 +62,9 @@
</ul></nav>{{end}}
<header>
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h1 dir="{{.Direction}}" id="blog-title">{{if .Posts}}{{else}}<span class="writeas-prefix"><a href="/">write.as</a></span> {{end}}<a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
{{if .Description}}<p class="description p-note">{{.Description}}</p>{{end}}
{{/*if not .Public/*}}

View file

@ -25,6 +25,9 @@
</head>
<body id="collection" itemscope itemtype="http://schema.org/WebPage">
{{if .Suspended}}
{{template "user-supsended"}}
{{end}}
<header>
<h1 dir="{{.Direction}}" id="blog-title"><a href="/{{.Alias}}/" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
</header>

View file

@ -36,6 +36,9 @@
</head>
<body id="post">
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<header>
<h1 dir="{{.Direction}}"><a href="/">{{.SiteName}}</a></h1>
<nav>

View file

@ -21,7 +21,7 @@
<td style="text-align:center">
<a
href="/admin/user/{{.Username}}#status"
title="View or change account status">{{if .Suspended}}suspended{{else}}active{{end}}</a></td>
title="View or change account status">{{if eq .Status 1}}suspended{{else}}active{{end}}</a></td>
</tr>
{{end}}
</table>

View file

@ -57,10 +57,10 @@ td.active-suspend > input[type="submit"] {
<td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td>
</tr>
<tr>
<form action="/admin/user/{{.User.Username}}" method="POST">
<form action="/admin/user/{{.User.Username}}/status" method="POST">
<a id="status"/>
<th>Status</th>
{{if .User.Suspended}}
{{if eq .User.Status 1}}
<td class="active-suspend"><p>User is currently Suspended</p><input type="submit" value="Activate"/></td>
{{else}}
<td class="active-suspend">

View file

@ -6,6 +6,9 @@
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h2 id="posts-header">drafts</h2>

View file

@ -8,6 +8,9 @@
<div class="content-container snug">
<div id="overlay"></div>
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h2>Customize {{.DisplayTitle}} <a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">view blog</a></h2>
{{if .Flashes}}<ul class="errors">

View file

@ -7,6 +7,9 @@
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h2>blogs</h2>
<ul class="atoms collections">
{{range $i, $el := .Collections}}<li class="collection"><h3>

View file

@ -0,0 +1,6 @@
{{define "user-suspended"}}
<div class="alert info">
<p>This account is currently suspended.</p>
<p>Please contact the instance administrator to discuss reactivation.</p>
</div>
{{end}}

View file

@ -7,17 +7,14 @@ h3 { font-weight: normal; }
.section > *:not(input) { font-size: 0.86em; }
</style>
<div class="content-container snug regular">
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h2>{{if .IsLogOut}}Before you go...{{else}}Account Settings {{if .IsAdmin}}<a href="/admin">admin settings</a>{{end}}{{end}}</h2>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{if .Suspended}}
<div class="alert info">
<p>This account is currently suspended.</p>
<p>Please contact the instance administrator to discuss reactivation.</p>
</div>
{{end}}
{{ if .IsLogOut }}
<div class="alert info">
<p class="introduction">Please add an <strong>email address</strong> and/or <strong>passphrase</strong> so you can log in again later.</p>

View file

@ -17,6 +17,9 @@ td.none {
</style>
<div class="content-container snug">
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h2 id="posts-header">{{if .Collection}}{{.Collection.DisplayTitle}} {{end}}Stats</h2>
<p>Stats for all time.</p>

View file

@ -19,6 +19,13 @@ import (
"github.com/writeas/writefreely/key"
)
type UserStatus int
const (
UserActive = iota
UserSuspended
)
type (
userCredentials struct {
Alias string `json:"alias" schema:"alias"`
@ -59,7 +66,7 @@ type (
HasPass bool `json:"has_pass"`
Email zero.String `json:"email"`
Created time.Time `json:"created"`
Suspended bool `json:"suspended"`
Status UserStatus `json:"status"`
clearEmail string `json:"email"`
}