mirror of
https://github.com/writefreely/writefreely
synced 2024-09-20 21:51:54 +00:00
Merge pull request #123 from writeas/private-instance
Private instances Resolves T576
This commit is contained in:
commit
7aaff778da
9 changed files with 249 additions and 81 deletions
1
app.go
1
app.go
|
@ -312,6 +312,7 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
|
|||
p.Username = u.Username
|
||||
}
|
||||
}
|
||||
p.CanViewReader = !app.cfg.App.Private || u != nil
|
||||
|
||||
return p
|
||||
}
|
||||
|
|
|
@ -76,7 +76,9 @@ type (
|
|||
// Federation
|
||||
Federation bool `ini:"federation"`
|
||||
PublicStats bool `ini:"public_stats"`
|
||||
Private bool `ini:"private"`
|
||||
|
||||
// Access
|
||||
Private bool `ini:"private"`
|
||||
|
||||
// Additional functions
|
||||
LocalTimeline bool `ini:"local_timeline"`
|
||||
|
|
229
handle.go
229
handle.go
|
@ -23,24 +23,52 @@ import (
|
|||
"github.com/gorilla/sessions"
|
||||
"github.com/writeas/impart"
|
||||
"github.com/writeas/web-core/log"
|
||||
"github.com/writeas/writefreely/config"
|
||||
"github.com/writeas/writefreely/page"
|
||||
)
|
||||
|
||||
// UserLevel represents the required user level for accessing an endpoint
|
||||
type UserLevel int
|
||||
|
||||
const (
|
||||
UserLevelNone UserLevel = iota // user or not -- ignored
|
||||
UserLevelOptional // user or not -- object fetched if user
|
||||
UserLevelNoneRequired // non-user (required)
|
||||
UserLevelUser // user (required)
|
||||
UserLevelNoneType UserLevel = iota // user or not -- ignored
|
||||
UserLevelOptionalType // user or not -- object fetched if user
|
||||
UserLevelNoneRequiredType // non-user (required)
|
||||
UserLevelUserType // user (required)
|
||||
)
|
||||
|
||||
func UserLevelNone(cfg *config.Config) UserLevel {
|
||||
return UserLevelNoneType
|
||||
}
|
||||
|
||||
func UserLevelOptional(cfg *config.Config) UserLevel {
|
||||
return UserLevelOptionalType
|
||||
}
|
||||
|
||||
func UserLevelNoneRequired(cfg *config.Config) UserLevel {
|
||||
return UserLevelNoneRequiredType
|
||||
}
|
||||
|
||||
func UserLevelUser(cfg *config.Config) UserLevel {
|
||||
return UserLevelUserType
|
||||
}
|
||||
|
||||
// UserLevelReader returns the permission level required for any route where
|
||||
// users can read published content.
|
||||
func UserLevelReader(cfg *config.Config) UserLevel {
|
||||
if cfg.App.Private {
|
||||
return UserLevelUserType
|
||||
}
|
||||
return UserLevelOptionalType
|
||||
}
|
||||
|
||||
type (
|
||||
handlerFunc func(app *App, w http.ResponseWriter, r *http.Request) error
|
||||
userHandlerFunc func(app *App, u *User, w http.ResponseWriter, r *http.Request) error
|
||||
userApperHandlerFunc func(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error
|
||||
dataHandlerFunc func(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
|
||||
authFunc func(app *App, r *http.Request) (*User, error)
|
||||
UserLevelFunc func(cfg *config.Config) UserLevel
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
|
@ -209,22 +237,49 @@ func (h *Handler) AdminApper(f userApperHandlerFunc) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func apiAuth(app *App, r *http.Request) (*User, error) {
|
||||
// Authorize user from Authorization header
|
||||
t := r.Header.Get("Authorization")
|
||||
if t == "" {
|
||||
return nil, ErrNoAccessToken
|
||||
}
|
||||
u := &User{ID: app.db.GetUserID(t)}
|
||||
if u.ID == -1 {
|
||||
return nil, ErrBadAccessToken
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// optionaAPIAuth is used for endpoints that accept authenticated requests via
|
||||
// Authorization header or cookie, unlike apiAuth. It returns a different err
|
||||
// in the case where no Authorization header is present.
|
||||
func optionalAPIAuth(app *App, r *http.Request) (*User, error) {
|
||||
// Authorize user from Authorization header
|
||||
t := r.Header.Get("Authorization")
|
||||
if t == "" {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
u := &User{ID: app.db.GetUserID(t)}
|
||||
if u.ID == -1 {
|
||||
return nil, ErrBadAccessToken
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func webAuth(app *App, r *http.Request) (*User, error) {
|
||||
u := getUserSession(app, r)
|
||||
if u == nil {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// UserAPI handles requests made in the API by the authenticated user.
|
||||
// This provides user-friendly HTML pages and actions that work in the browser.
|
||||
func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
|
||||
return h.UserAll(false, f, func(app *App, r *http.Request) (*User, error) {
|
||||
// Authorize user from Authorization header
|
||||
t := r.Header.Get("Authorization")
|
||||
if t == "" {
|
||||
return nil, ErrNoAccessToken
|
||||
}
|
||||
u := &User{ID: app.db.GetUserID(t)}
|
||||
if u.ID == -1 {
|
||||
return nil, ErrBadAccessToken
|
||||
}
|
||||
|
||||
return u, nil
|
||||
})
|
||||
return h.UserAll(false, f, apiAuth)
|
||||
}
|
||||
|
||||
func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc {
|
||||
|
@ -307,7 +362,7 @@ func (h *Handler) Page(n string) http.HandlerFunc {
|
|||
}, UserLevelOptional)
|
||||
}
|
||||
|
||||
func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) WebErrors(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: factor out this logic shared with Web()
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
|
@ -331,21 +386,21 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
|
||||
var session *sessions.Session
|
||||
var err error
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err = h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
@ -380,9 +435,25 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CollectionPostOrStatic(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.Contains(r.URL.Path, ".") && !isRaw(r) {
|
||||
start := time.Now()
|
||||
status := 200
|
||||
defer func() {
|
||||
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
|
||||
}()
|
||||
|
||||
// Serve static file
|
||||
h.app.App().shttp.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
h.Web(viewCollectionPost, UserLevelReader)(w, r)
|
||||
}
|
||||
|
||||
// Web handles requests made in the web application. This provides user-
|
||||
// friendly HTML pages and actions that work in the browser.
|
||||
func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) Web(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
var status int
|
||||
|
@ -404,21 +475,21 @@ func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
|
||||
}()
|
||||
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err := h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
@ -478,7 +549,65 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) AllReader(f handlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleError(w, r, func() error {
|
||||
status := 200
|
||||
start := time.Now()
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Error("%s:\n%s", e, debug.Stack())
|
||||
impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
|
||||
status = 500
|
||||
}
|
||||
|
||||
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
|
||||
}()
|
||||
|
||||
if h.app.App().cfg.App.Private {
|
||||
// This instance is private, so ensure it's being accessed by a valid user
|
||||
// Check if authenticated with an access token
|
||||
_, apiErr := optionalAPIAuth(h.app.App(), r)
|
||||
if apiErr != nil {
|
||||
if err, ok := apiErr.(impart.HTTPError); ok {
|
||||
status = err.Status
|
||||
} else {
|
||||
status = 500
|
||||
}
|
||||
|
||||
if apiErr == ErrNotLoggedIn {
|
||||
// Fall back to web auth since there was no access token given
|
||||
_, err := webAuth(h.app.App(), r)
|
||||
if err != nil {
|
||||
if err, ok := apiErr.(impart.HTTPError); ok {
|
||||
status = err.Status
|
||||
} else {
|
||||
status = 500
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return apiErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := f(h.app.App(), w, r)
|
||||
if err != nil {
|
||||
if err, ok := err.(impart.HTTPError); ok {
|
||||
status = err.Status
|
||||
} else {
|
||||
status = 500
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Download(f dataHandlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
var status int
|
||||
|
@ -523,27 +652,27 @@ func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) Redirect(url string, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
start := time.Now()
|
||||
|
||||
var status int
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err := h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
@ -673,6 +802,34 @@ func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
|
|||
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
|
||||
}()
|
||||
|
||||
if h.app.App().cfg.App.Private {
|
||||
// This instance is private, so ensure it's being accessed by a valid user
|
||||
// Check if authenticated with an access token
|
||||
_, apiErr := optionalAPIAuth(h.app.App(), r)
|
||||
if apiErr != nil {
|
||||
if err, ok := apiErr.(impart.HTTPError); ok {
|
||||
status = err.Status
|
||||
} else {
|
||||
status = 500
|
||||
}
|
||||
|
||||
if apiErr == ErrNotLoggedIn {
|
||||
// Fall back to web auth since there was no access token given
|
||||
_, err := webAuth(h.app.App(), r)
|
||||
if err != nil {
|
||||
if err, ok := apiErr.(impart.HTTPError); ok {
|
||||
status = err.Status
|
||||
} else {
|
||||
status = 500
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return apiErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f(w, r)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -23,10 +23,11 @@ type StaticPage struct {
|
|||
HeaderNav bool
|
||||
|
||||
// Request values
|
||||
Path string
|
||||
Username string
|
||||
Values map[string]string
|
||||
Flashes []string
|
||||
Path string
|
||||
Username string
|
||||
Values map[string]string
|
||||
Flashes []string
|
||||
CanViewReader bool
|
||||
}
|
||||
|
||||
// SanitizeHost alters the StaticPage to contain a real hostname. This is
|
||||
|
|
30
posts.go
30
posts.go
|
@ -266,6 +266,8 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
vars := mux.Vars(r)
|
||||
friendlyID := vars["post"]
|
||||
|
||||
// NOTE: until this is done better, be sure to keep this in parity with
|
||||
// isRaw() and viewCollectionPost()
|
||||
isJSON := strings.HasSuffix(friendlyID, ".json")
|
||||
isXML := strings.HasSuffix(friendlyID, ".xml")
|
||||
isCSS := strings.HasSuffix(friendlyID, ".css")
|
||||
|
@ -587,7 +589,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
// Write success now
|
||||
response := impart.WriteSuccess(w, newPost, http.StatusCreated)
|
||||
|
||||
if newPost.Collection != nil && app.cfg.App.Federation && !newPost.Created.After(time.Now()) {
|
||||
if newPost.Collection != nil && !app.cfg.App.Private && app.cfg.App.Federation && !newPost.Created.After(time.Now()) {
|
||||
go federatePost(app, newPost, newPost.Collection.ID, false)
|
||||
}
|
||||
|
||||
|
@ -687,7 +689,7 @@ func existingPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
if pRes.CollectionID.Valid {
|
||||
coll, err := app.db.GetCollectionBy("id = ?", pRes.CollectionID.Int64)
|
||||
if err == nil && app.cfg.App.Federation {
|
||||
if err == nil && !app.cfg.App.Private && app.cfg.App.Federation {
|
||||
coll.hostName = app.cfg.App.Host
|
||||
pRes.Collection = &CollectionObj{Collection: *coll}
|
||||
go federatePost(app, pRes, pRes.Collection.ID, true)
|
||||
|
@ -828,7 +830,7 @@ func deletePost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
if t != nil {
|
||||
t.Commit()
|
||||
}
|
||||
if coll != nil && app.cfg.App.Federation {
|
||||
if coll != nil && !app.cfg.App.Private && app.cfg.App.Federation {
|
||||
go deleteFederatedPost(app, pp, collID.Int64)
|
||||
}
|
||||
|
||||
|
@ -872,7 +874,7 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if app.cfg.App.Federation {
|
||||
if !app.cfg.App.Private && app.cfg.App.Federation {
|
||||
for _, pRes := range *res {
|
||||
if pRes.Code != http.StatusOK {
|
||||
continue
|
||||
|
@ -1193,21 +1195,29 @@ func getRawCollectionPost(app *App, slug, collAlias string) *RawPost {
|
|||
}
|
||||
}
|
||||
|
||||
func isRaw(r *http.Request) bool {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
|
||||
// NOTE: until this is done better, be sure to keep this in parity with
|
||||
// isRaw in viewCollectionPost() and handleViewPost()
|
||||
isJSON := strings.HasSuffix(slug, ".json")
|
||||
isXML := strings.HasSuffix(slug, ".xml")
|
||||
isMarkdown := strings.HasSuffix(slug, ".md")
|
||||
return strings.HasSuffix(slug, ".txt") || isJSON || isXML || isMarkdown
|
||||
}
|
||||
|
||||
func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
|
||||
// NOTE: until this is done better, be sure to keep this in parity with
|
||||
// isRaw() and handleViewPost()
|
||||
isJSON := strings.HasSuffix(slug, ".json")
|
||||
isXML := strings.HasSuffix(slug, ".xml")
|
||||
isMarkdown := strings.HasSuffix(slug, ".md")
|
||||
isRaw := strings.HasSuffix(slug, ".txt") || isJSON || isXML || isMarkdown
|
||||
|
||||
if strings.Contains(r.URL.Path, ".") && !isRaw {
|
||||
// Serve static file
|
||||
app.shttp.ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
cr := &collectionReq{}
|
||||
err := processCollectionRequest(cr, vars, w, r)
|
||||
if err != nil {
|
||||
|
|
49
routes.go
49
routes.go
|
@ -60,7 +60,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
|
||||
// Federation endpoints
|
||||
// host-meta
|
||||
write.HandleFunc("/.well-known/host-meta", handler.Web(handleViewHostMeta, UserLevelOptional))
|
||||
write.HandleFunc("/.well-known/host-meta", handler.Web(handleViewHostMeta, UserLevelReader))
|
||||
// webfinger
|
||||
write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger)))
|
||||
// nodeinfo
|
||||
|
@ -112,28 +112,28 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
// Handle collections
|
||||
write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST")
|
||||
apiColls := write.PathPrefix("/api/collections/").Subrouter()
|
||||
apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(fetchCollection)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.AllReader(fetchCollection)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE")
|
||||
apiColls.HandleFunc("/{alias}/posts", handler.All(fetchCollectionPosts)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/posts", handler.All(newPost)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/posts/{post}", handler.All(fetchPost)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/posts/{post}", handler.AllReader(fetchPost)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/posts/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/posts/{post}/{property}", handler.All(fetchPostProperty)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/posts/{post}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/collect", handler.All(addPost)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/pin", handler.All(pinPost)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/unpin", handler.All(pinPost)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/inbox", handler.All(handleFetchCollectionInbox)).Methods("POST")
|
||||
apiColls.HandleFunc("/{alias}/outbox", handler.All(handleFetchCollectionOutbox)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/following", handler.All(handleFetchCollectionFollowing)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/followers", handler.All(handleFetchCollectionFollowers)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/outbox", handler.AllReader(handleFetchCollectionOutbox)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/following", handler.AllReader(handleFetchCollectionFollowing)).Methods("GET")
|
||||
apiColls.HandleFunc("/{alias}/followers", handler.AllReader(handleFetchCollectionFollowers)).Methods("GET")
|
||||
|
||||
// Handle posts
|
||||
write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST")
|
||||
posts := write.PathPrefix("/api/posts/").Subrouter()
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(fetchPost)).Methods("GET")
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.AllReader(fetchPost)).Methods("GET")
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT")
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE")
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.All(fetchPostProperty)).Methods("GET")
|
||||
posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET")
|
||||
posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST")
|
||||
posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST")
|
||||
|
||||
|
@ -152,11 +152,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
|
||||
write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelNoneRequired)).Methods("GET")
|
||||
// TODO: show a reader-specific 404 page if the function is disabled
|
||||
// TODO: change this based on configuration for either public or private-to-this-instance
|
||||
readPerm := UserLevelOptional
|
||||
|
||||
write.HandleFunc("/read", handler.Web(viewLocalTimeline, readPerm))
|
||||
RouteRead(handler, readPerm, write.PathPrefix("/read").Subrouter())
|
||||
write.HandleFunc("/read", handler.Web(viewLocalTimeline, UserLevelReader))
|
||||
RouteRead(handler, UserLevelReader, write.PathPrefix("/read").Subrouter())
|
||||
|
||||
draftEditPrefix := ""
|
||||
if apper.App().cfg.App.SingleUser {
|
||||
|
@ -173,8 +170,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
if apper.App().cfg.App.SingleUser {
|
||||
RouteCollections(handler, write.PathPrefix("/").Subrouter())
|
||||
} else {
|
||||
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelReader))
|
||||
write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelReader))
|
||||
RouteCollections(handler, write.PathPrefix("/{prefix:[@~$!\\-+]?}{collection}").Subrouter())
|
||||
// Posts
|
||||
}
|
||||
|
@ -184,19 +181,19 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
}
|
||||
|
||||
func RouteCollections(handler *Handler, r *mux.Router) {
|
||||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelOptional))
|
||||
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelOptional))
|
||||
r.HandleFunc("/tags/{tag}", handler.Web(handleViewCollectionTag, UserLevelOptional))
|
||||
r.HandleFunc("/sitemap.xml", handler.All(handleViewSitemap))
|
||||
r.HandleFunc("/feed/", handler.All(ViewFeed))
|
||||
r.HandleFunc("/{slug}", handler.Web(viewCollectionPost, UserLevelOptional))
|
||||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
|
||||
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
|
||||
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader))
|
||||
r.HandleFunc("/tags/{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
|
||||
r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap))
|
||||
r.HandleFunc("/feed/", handler.AllReader(ViewFeed))
|
||||
r.HandleFunc("/{slug}", handler.CollectionPostOrStatic)
|
||||
r.HandleFunc("/{slug}/edit", handler.Web(handleViewPad, UserLevelUser))
|
||||
r.HandleFunc("/{slug}/edit/meta", handler.Web(handleViewMeta, UserLevelUser))
|
||||
r.HandleFunc("/{slug}/", handler.Web(handleCollectionPostRedirect, UserLevelOptional)).Methods("GET")
|
||||
r.HandleFunc("/{slug}/", handler.Web(handleCollectionPostRedirect, UserLevelReader)).Methods("GET")
|
||||
}
|
||||
|
||||
func RouteRead(handler *Handler, readPerm UserLevel, r *mux.Router) {
|
||||
func RouteRead(handler *Handler, readPerm UserLevelFunc, r *mux.Router) {
|
||||
r.HandleFunc("/api/posts", handler.Web(viewLocalTimelineAPI, readPerm))
|
||||
r.HandleFunc("/p/{page}", handler.Web(viewLocalTimeline, readPerm))
|
||||
r.HandleFunc("/feed/", handler.Web(viewLocalTimelineFeed, readPerm))
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<nav id="user-nav">
|
||||
<nav class="tabs">
|
||||
<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a>
|
||||
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>Reader</a>{{end}}
|
||||
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>Reader</a>{{end}}
|
||||
{{if and (not .SingleUser) (not .Username)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a>{{end}}
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<h3><a class="home" href="/">{{.SiteName}}</a></h3>
|
||||
<ul>
|
||||
<li><a href="/about">about</a></li>
|
||||
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">reader</a>{{end}}
|
||||
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read">reader</a>{{end}}
|
||||
<li><a href="/privacy">privacy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<label><input type="radio" name="visibility" id="visibility-unlisted" value="0" {{if .IsUnlisted}}checked="checked"{{end}} />
|
||||
Unlisted
|
||||
</label>
|
||||
<p>This blog is visible to anyone with its link.</p>
|
||||
<p>This blog is visible to {{if .Private}}any registered user on this instance{{else}}anyone with its link{{end}}.</p>
|
||||
</li>
|
||||
<li>
|
||||
<label class="option-text"><input type="radio" name="visibility" id="visibility-private" value="2" {{if .IsPrivate}}checked="checked"{{end}} />
|
||||
|
@ -62,7 +62,7 @@
|
|||
<label class="option-text{{if not .LocalTimeline}} disabled{{end}}"><input type="radio" name="visibility" id="visibility-public" value="1" {{if .IsPublic}}checked="checked"{{end}} {{if not .LocalTimeline}}disabled="disabled"{{end}} />
|
||||
Public
|
||||
</label>
|
||||
{{if .LocalTimeline}}<p>This blog is displayed on the public <a href="/read">reader</a>, and to anyone with its link.</p>
|
||||
{{if .LocalTimeline}}<p>This blog is displayed on the public <a href="/read">reader</a>, and is visible to {{if .Private}}any registered user on this instance{{else}}anyone with its link{{end}}.</p>
|
||||
{{else}}<p>The public reader is currently turned off for this community.</p>{{end}}
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in a new issue