- 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 statusesT661-disable-accounts
@@ -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) | |||
@@ -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 { | |||
@@ -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)} | |||
} | |||
@@ -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 | |||
} | |||
@@ -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 | |||
} | |||
@@ -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 | |||
} | |||
@@ -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 | |||
@@ -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 | |||
@@ -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) | |||
@@ -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) | |||
@@ -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") | |||
@@ -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; | |||
@@ -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 | |||
); | |||
-- -------------------------------------------------------- | |||
@@ -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"), | |||
)) | |||
} | |||
@@ -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 }} | |||
@@ -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}} | |||
@@ -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 }} | |||
@@ -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" .}} | |||
@@ -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/*}} | |||
@@ -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> | |||
@@ -36,6 +36,9 @@ | |||
</head> | |||
<body id="post"> | |||
{{if .Suspended}} | |||
{{template "user-suspended"}} | |||
{{end}} | |||
<header> | |||
<h1 dir="{{.Direction}}"><a href="/">{{.SiteName}}</a></h1> | |||
<nav> | |||
@@ -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> | |||
@@ -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"> | |||
@@ -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> | |||
@@ -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"> | |||
@@ -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> | |||
@@ -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}} |
@@ -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> | |||
@@ -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> | |||
@@ -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"` | |||
} | |||