Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10455828
admin.go
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Subscribers
None
admin.go
View Options
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package
writefreely
import
(
"database/sql"
"fmt"
"net/http"
"runtime"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/appstats"
"github.com/writeas/writefreely/config"
)
var
(
appStartTime
=
time
.
Now
()
sysStatus
systemStatus
)
const
adminUsersPerPage
=
30
type
systemStatus
struct
{
Uptime
string
NumGoroutine
int
// General statistics.
MemAllocated
string
// bytes allocated and still in use
MemTotal
string
// bytes allocated (even if freed)
MemSys
string
// bytes obtained from system (sum of XxxSys below)
Lookups
uint64
// number of pointer lookups
MemMallocs
uint64
// number of mallocs
MemFrees
uint64
// number of frees
// Main allocation heap statistics.
HeapAlloc
string
// bytes allocated and still in use
HeapSys
string
// bytes obtained from system
HeapIdle
string
// bytes in idle spans
HeapInuse
string
// bytes in non-idle span
HeapReleased
string
// bytes released to the OS
HeapObjects
uint64
// total number of allocated objects
// Low-level fixed-size structure allocator statistics.
// Inuse is bytes used now.
// Sys is bytes obtained from system.
StackInuse
string
// bootstrap stacks
StackSys
string
MSpanInuse
string
// mspan structures
MSpanSys
string
MCacheInuse
string
// mcache structures
MCacheSys
string
BuckHashSys
string
// profiling bucket hash table
GCSys
string
// GC metadata
OtherSys
string
// other system allocations
// Garbage collector statistics.
NextGC
string
// next run in HeapAlloc time (bytes)
LastGC
string
// last run in absolute time (ns)
PauseTotalNs
string
PauseNs
string
// circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC
uint32
}
type
inspectedCollection
struct
{
CollectionObj
Followers
int
LastPost
string
}
type
instanceContent
struct
{
ID
string
Type
string
Title
sql
.
NullString
Content
string
Updated
time
.
Time
}
func
(
c
instanceContent
)
UpdatedFriendly
()
string
{
/*
// TODO: accept a locale in this method and use that for the format
var loc monday.Locale = monday.LocaleEnUS
return monday.Format(u.Created, monday.DateTimeFormatsByLocale[loc], loc)
*/
return
c
.
Updated
.
Format
(
"January 2, 2006, 3:04 PM"
)
}
func
handleViewAdminDash
(
app
*
App
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
updateAppStats
()
p
:=
struct
{
*
UserPage
SysStatus
systemStatus
Config
config
.
AppCfg
Message
,
ConfigMessage
string
}{
UserPage
:
NewUserPage
(
app
,
r
,
u
,
"Admin"
,
nil
),
SysStatus
:
sysStatus
,
Config
:
app
.
cfg
.
App
,
Message
:
r
.
FormValue
(
"m"
),
ConfigMessage
:
r
.
FormValue
(
"cm"
),
}
showUserPage
(
w
,
"admin"
,
p
)
return
nil
}
func
handleViewAdminUsers
(
app
*
App
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
p
:=
struct
{
*
UserPage
Config
config
.
AppCfg
Message
string
Users
*
[]
User
CurPage
int
TotalUsers
int64
TotalPages
[]
int
}{
UserPage
:
NewUserPage
(
app
,
r
,
u
,
"Users"
,
nil
),
Config
:
app
.
cfg
.
App
,
Message
:
r
.
FormValue
(
"m"
),
}
p
.
TotalUsers
=
app
.
db
.
GetAllUsersCount
()
ttlPages
:=
p
.
TotalUsers
/
adminUsersPerPage
p
.
TotalPages
=
[]
int
{}
for
i
:=
1
;
i
<=
int
(
ttlPages
);
i
++
{
p
.
TotalPages
=
append
(
p
.
TotalPages
,
i
)
}
var
err
error
p
.
CurPage
,
err
=
strconv
.
Atoi
(
r
.
FormValue
(
"p"
))
if
err
!=
nil
||
p
.
CurPage
<
1
{
p
.
CurPage
=
1
}
else
if
p
.
CurPage
>
int
(
ttlPages
)
{
p
.
CurPage
=
int
(
ttlPages
)
}
p
.
Users
,
err
=
app
.
db
.
GetAllUsers
(
uint
(
p
.
CurPage
))
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get users: %v"
,
err
)}
}
showUserPage
(
w
,
"users"
,
p
)
return
nil
}
func
handleViewAdminUser
(
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"
}
}
p
:=
struct
{
*
UserPage
Config
config
.
AppCfg
Message
string
User
*
User
Colls
[]
inspectedCollection
LastPost
string
TotalPosts
int64
}{
Config
:
app
.
cfg
.
App
,
Message
:
r
.
FormValue
(
"m"
),
Colls
:
[]
inspectedCollection
{},
}
var
err
error
p
.
User
,
err
=
app
.
db
.
GetUserForAuth
(
username
)
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get user: %v"
,
err
)}
}
p
.
UserPage
=
NewUserPage
(
app
,
r
,
u
,
p
.
User
.
Username
,
nil
)
p
.
TotalPosts
=
app
.
db
.
GetUserPostsCount
(
p
.
User
.
ID
)
lp
,
err
:=
app
.
db
.
GetUserLastPostTime
(
p
.
User
.
ID
)
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get user's last post time: %v"
,
err
)}
}
if
lp
!=
nil
{
p
.
LastPost
=
lp
.
Format
(
"January 2, 2006, 3:04 PM"
)
}
colls
,
err
:=
app
.
db
.
GetCollections
(
p
.
User
,
app
.
cfg
.
App
.
Host
)
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get user's collections: %v"
,
err
)}
}
for
_
,
c
:=
range
*
colls
{
ic
:=
inspectedCollection
{
CollectionObj
:
CollectionObj
{
Collection
:
c
},
}
if
app
.
cfg
.
App
.
Federation
{
folls
,
err
:=
app
.
db
.
GetAPFollowers
(
&
c
)
if
err
==
nil
{
// TODO: handle error here (at least log it)
ic
.
Followers
=
len
(
*
folls
)
}
}
app
.
db
.
GetPostsCount
(
&
ic
.
CollectionObj
,
true
)
lp
,
err
:=
app
.
db
.
GetCollectionLastPostTime
(
c
.
ID
)
if
err
!=
nil
{
log
.
Error
(
"Didn't get last post time for collection %d: %v"
,
c
.
ID
,
err
)
}
if
lp
!=
nil
{
ic
.
LastPost
=
lp
.
Format
(
"January 2, 2006, 3:04 PM"
)
}
p
.
Colls
=
append
(
p
.
Colls
,
ic
)
}
showUserPage
(
w
,
"view-user"
,
p
)
return
nil
}
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"
}
}
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
user
.
IsSilenced
()
{
err
=
app
.
db
.
SetUserStatus
(
user
.
ID
,
UserActive
)
}
else
{
err
=
app
.
db
.
SetUserStatus
(
user
.
ID
,
UserSilenced
)
}
if
err
!=
nil
{
log
.
Error
(
"toggle user suspended: %v"
,
err
)
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not toggle user status: %v"
)}
}
return
impart
.
HTTPError
{
http
.
StatusFound
,
fmt
.
Sprintf
(
"/admin/user/%s#status"
,
username
)}
}
func
handleViewAdminPages
(
app
*
App
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
p
:=
struct
{
*
UserPage
Config
config
.
AppCfg
Message
string
Pages
[]
*
instanceContent
}{
UserPage
:
NewUserPage
(
app
,
r
,
u
,
"Pages"
,
nil
),
Config
:
app
.
cfg
.
App
,
Message
:
r
.
FormValue
(
"m"
),
}
var
err
error
p
.
Pages
,
err
=
app
.
db
.
GetInstancePages
()
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get pages: %v"
,
err
)}
}
// Add in default pages
var
hasAbout
,
hasPrivacy
bool
for
i
,
c
:=
range
p
.
Pages
{
if
hasAbout
&&
hasPrivacy
{
break
}
if
c
.
ID
==
"about"
{
hasAbout
=
true
if
!
c
.
Title
.
Valid
{
p
.
Pages
[
i
].
Title
=
defaultAboutTitle
(
app
.
cfg
)
}
}
else
if
c
.
ID
==
"privacy"
{
hasPrivacy
=
true
if
!
c
.
Title
.
Valid
{
p
.
Pages
[
i
].
Title
=
defaultPrivacyTitle
()
}
}
}
if
!
hasAbout
{
p
.
Pages
=
append
(
p
.
Pages
,
&
instanceContent
{
ID
:
"about"
,
Title
:
defaultAboutTitle
(
app
.
cfg
),
Content
:
defaultAboutPage
(
app
.
cfg
),
Updated
:
defaultPageUpdatedTime
,
})
}
if
!
hasPrivacy
{
p
.
Pages
=
append
(
p
.
Pages
,
&
instanceContent
{
ID
:
"privacy"
,
Title
:
defaultPrivacyTitle
(),
Content
:
defaultPrivacyPolicy
(
app
.
cfg
),
Updated
:
defaultPageUpdatedTime
,
})
}
showUserPage
(
w
,
"pages"
,
p
)
return
nil
}
func
handleViewAdminPage
(
app
*
App
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
vars
:=
mux
.
Vars
(
r
)
slug
:=
vars
[
"slug"
]
if
slug
==
""
{
return
impart
.
HTTPError
{
http
.
StatusFound
,
"/admin/pages"
}
}
p
:=
struct
{
*
UserPage
Config
config
.
AppCfg
Message
string
Banner
*
instanceContent
Content
*
instanceContent
}{
Config
:
app
.
cfg
.
App
,
Message
:
r
.
FormValue
(
"m"
),
}
var
err
error
// Get pre-defined pages, or select slug
if
slug
==
"about"
{
p
.
Content
,
err
=
getAboutPage
(
app
)
}
else
if
slug
==
"privacy"
{
p
.
Content
,
err
=
getPrivacyPage
(
app
)
}
else
if
slug
==
"landing"
{
p
.
Banner
,
err
=
getLandingBanner
(
app
)
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get banner: %v"
,
err
)}
}
p
.
Content
,
err
=
getLandingBody
(
app
)
p
.
Content
.
ID
=
"landing"
}
else
if
slug
==
"reader"
{
p
.
Content
,
err
=
getReaderSection
(
app
)
}
else
{
p
.
Content
,
err
=
app
.
db
.
GetDynamicContent
(
slug
)
}
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not get page: %v"
,
err
)}
}
title
:=
"New page"
if
p
.
Content
!=
nil
{
title
=
"Edit "
+
p
.
Content
.
ID
}
else
{
p
.
Content
=
&
instanceContent
{}
}
p
.
UserPage
=
NewUserPage
(
app
,
r
,
u
,
title
,
nil
)
showUserPage
(
w
,
"view-page"
,
p
)
return
nil
}
func
handleAdminUpdateSite
(
app
*
App
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
vars
:=
mux
.
Vars
(
r
)
id
:=
vars
[
"page"
]
// Validate
if
id
!=
"about"
&&
id
!=
"privacy"
&&
id
!=
"landing"
&&
id
!=
"reader"
{
return
impart
.
HTTPError
{
http
.
StatusNotFound
,
"No such page."
}
}
var
err
error
m
:=
""
if
id
==
"landing"
{
// Handle special landing page
err
=
app
.
db
.
UpdateDynamicContent
(
"landing-banner"
,
""
,
r
.
FormValue
(
"banner"
),
"section"
)
if
err
!=
nil
{
m
=
"?m="
+
err
.
Error
()
return
impart
.
HTTPError
{
http
.
StatusFound
,
"/admin/page/"
+
id
+
m
}
}
err
=
app
.
db
.
UpdateDynamicContent
(
"landing-body"
,
""
,
r
.
FormValue
(
"content"
),
"section"
)
}
else
if
id
==
"reader"
{
// Update sections with titles
err
=
app
.
db
.
UpdateDynamicContent
(
id
,
r
.
FormValue
(
"title"
),
r
.
FormValue
(
"content"
),
"section"
)
}
else
{
// Update page
err
=
app
.
db
.
UpdateDynamicContent
(
id
,
r
.
FormValue
(
"title"
),
r
.
FormValue
(
"content"
),
"page"
)
}
if
err
!=
nil
{
m
=
"?m="
+
err
.
Error
()
}
return
impart
.
HTTPError
{
http
.
StatusFound
,
"/admin/page/"
+
id
+
m
}
}
func
handleAdminUpdateConfig
(
apper
Apper
,
u
*
User
,
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
error
{
apper
.
App
().
cfg
.
App
.
SiteName
=
r
.
FormValue
(
"site_name"
)
apper
.
App
().
cfg
.
App
.
SiteDesc
=
r
.
FormValue
(
"site_desc"
)
apper
.
App
().
cfg
.
App
.
Landing
=
r
.
FormValue
(
"landing"
)
apper
.
App
().
cfg
.
App
.
OpenRegistration
=
r
.
FormValue
(
"open_registration"
)
==
"on"
mul
,
err
:=
strconv
.
Atoi
(
r
.
FormValue
(
"min_username_len"
))
if
err
==
nil
{
apper
.
App
().
cfg
.
App
.
MinUsernameLen
=
mul
}
mb
,
err
:=
strconv
.
Atoi
(
r
.
FormValue
(
"max_blogs"
))
if
err
==
nil
{
apper
.
App
().
cfg
.
App
.
MaxBlogs
=
mb
}
apper
.
App
().
cfg
.
App
.
Federation
=
r
.
FormValue
(
"federation"
)
==
"on"
apper
.
App
().
cfg
.
App
.
PublicStats
=
r
.
FormValue
(
"public_stats"
)
==
"on"
apper
.
App
().
cfg
.
App
.
Private
=
r
.
FormValue
(
"private"
)
==
"on"
apper
.
App
().
cfg
.
App
.
LocalTimeline
=
r
.
FormValue
(
"local_timeline"
)
==
"on"
if
apper
.
App
().
cfg
.
App
.
LocalTimeline
&&
apper
.
App
().
timeline
==
nil
{
log
.
Info
(
"Initializing local timeline..."
)
initLocalTimeline
(
apper
.
App
())
}
apper
.
App
().
cfg
.
App
.
UserInvites
=
r
.
FormValue
(
"user_invites"
)
if
apper
.
App
().
cfg
.
App
.
UserInvites
==
"none"
{
apper
.
App
().
cfg
.
App
.
UserInvites
=
""
}
apper
.
App
().
cfg
.
App
.
DefaultVisibility
=
r
.
FormValue
(
"default_visibility"
)
m
:=
"?cm=Configuration+saved."
err
=
apper
.
SaveConfig
(
apper
.
App
().
cfg
)
if
err
!=
nil
{
m
=
"?cm="
+
err
.
Error
()
}
return
impart
.
HTTPError
{
http
.
StatusFound
,
"/admin"
+
m
+
"#config"
}
}
func
updateAppStats
()
{
sysStatus
.
Uptime
=
appstats
.
TimeSincePro
(
appStartTime
)
m
:=
new
(
runtime
.
MemStats
)
runtime
.
ReadMemStats
(
m
)
sysStatus
.
NumGoroutine
=
runtime
.
NumGoroutine
()
sysStatus
.
MemAllocated
=
appstats
.
FileSize
(
int64
(
m
.
Alloc
))
sysStatus
.
MemTotal
=
appstats
.
FileSize
(
int64
(
m
.
TotalAlloc
))
sysStatus
.
MemSys
=
appstats
.
FileSize
(
int64
(
m
.
Sys
))
sysStatus
.
Lookups
=
m
.
Lookups
sysStatus
.
MemMallocs
=
m
.
Mallocs
sysStatus
.
MemFrees
=
m
.
Frees
sysStatus
.
HeapAlloc
=
appstats
.
FileSize
(
int64
(
m
.
HeapAlloc
))
sysStatus
.
HeapSys
=
appstats
.
FileSize
(
int64
(
m
.
HeapSys
))
sysStatus
.
HeapIdle
=
appstats
.
FileSize
(
int64
(
m
.
HeapIdle
))
sysStatus
.
HeapInuse
=
appstats
.
FileSize
(
int64
(
m
.
HeapInuse
))
sysStatus
.
HeapReleased
=
appstats
.
FileSize
(
int64
(
m
.
HeapReleased
))
sysStatus
.
HeapObjects
=
m
.
HeapObjects
sysStatus
.
StackInuse
=
appstats
.
FileSize
(
int64
(
m
.
StackInuse
))
sysStatus
.
StackSys
=
appstats
.
FileSize
(
int64
(
m
.
StackSys
))
sysStatus
.
MSpanInuse
=
appstats
.
FileSize
(
int64
(
m
.
MSpanInuse
))
sysStatus
.
MSpanSys
=
appstats
.
FileSize
(
int64
(
m
.
MSpanSys
))
sysStatus
.
MCacheInuse
=
appstats
.
FileSize
(
int64
(
m
.
MCacheInuse
))
sysStatus
.
MCacheSys
=
appstats
.
FileSize
(
int64
(
m
.
MCacheSys
))
sysStatus
.
BuckHashSys
=
appstats
.
FileSize
(
int64
(
m
.
BuckHashSys
))
sysStatus
.
GCSys
=
appstats
.
FileSize
(
int64
(
m
.
GCSys
))
sysStatus
.
OtherSys
=
appstats
.
FileSize
(
int64
(
m
.
OtherSys
))
sysStatus
.
NextGC
=
appstats
.
FileSize
(
int64
(
m
.
NextGC
))
sysStatus
.
LastGC
=
fmt
.
Sprintf
(
"%.1fs"
,
float64
(
time
.
Now
().
UnixNano
()
-
int64
(
m
.
LastGC
))
/
1000
/
1000
/
1000
)
sysStatus
.
PauseTotalNs
=
fmt
.
Sprintf
(
"%.1fs"
,
float64
(
m
.
PauseTotalNs
)
/
1000
/
1000
/
1000
)
sysStatus
.
PauseNs
=
fmt
.
Sprintf
(
"%.3fs"
,
float64
(
m
.
PauseNs
[(
m
.
NumGC
+
255
)
%
256
])
/
1000
/
1000
/
1000
)
sysStatus
.
NumGC
=
m
.
NumGC
}
func
adminResetPassword
(
app
*
App
,
u
*
User
,
newPass
string
)
error
{
hashedPass
,
err
:=
auth
.
HashPass
([]
byte
(
newPass
))
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not create password hash: %v"
,
err
)}
}
err
=
app
.
db
.
ChangePassphrase
(
u
.
ID
,
true
,
""
,
hashedPass
)
if
err
!=
nil
{
return
impart
.
HTTPError
{
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Could not update passphrase: %v"
,
err
)}
}
return
nil
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 31, 11:58 PM (19 h, 10 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3146012
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment