feat: update finding memo with visibility

This commit is contained in:
boojack 2022-07-26 21:12:20 +08:00
parent 58e68f8f80
commit b7339e00ba
8 changed files with 136 additions and 82 deletions

View File

@ -1,5 +1,9 @@
package api package api
var (
UNKNOWN_ID = 0
)
type Signin struct { type Signin struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`

View File

@ -6,6 +6,8 @@ type Visibility string
const ( const (
// Public is the PUBLIC visibility. // Public is the PUBLIC visibility.
Public Visibility = "PUBLIC" Public Visibility = "PUBLIC"
// Protected is the PROTECTED visibility.
Protected Visibility = "PROTECTED"
// Privite is the PRIVATE visibility. // Privite is the PRIVATE visibility.
Privite Visibility = "PRIVATE" Privite Visibility = "PRIVATE"
) )
@ -14,6 +16,8 @@ func (e Visibility) String() string {
switch e { switch e {
case Public: case Public:
return "PUBLIC" return "PUBLIC"
case Protected:
return "PROTECTED"
case Privite: case Privite:
return "PRIVATE" return "PRIVATE"
} }
@ -65,9 +69,9 @@ type MemoFind struct {
CreatorID *int `json:"creatorId"` CreatorID *int `json:"creatorId"`
// Domain specific fields // Domain specific fields
Pinned *bool Pinned *bool
ContentSearch *string ContentSearch *string
Visibility *Visibility VisibilityList []Visibility
// Pagination // Pagination
Limit int Limit int

View File

@ -21,30 +21,30 @@ func getUserIDContextKey() string {
return userIDContextKey return userIDContextKey
} }
func setUserSession(c echo.Context, user *api.User) error { func setUserSession(ctx echo.Context, user *api.User) error {
sess, _ := session.Get("session", c) sess, _ := session.Get("session", ctx)
sess.Options = &sessions.Options{ sess.Options = &sessions.Options{
Path: "/", Path: "/",
MaxAge: 1000 * 3600 * 24 * 30, MaxAge: 1000 * 3600 * 24 * 30,
HttpOnly: true, HttpOnly: true,
} }
sess.Values[userIDContextKey] = user.ID sess.Values[userIDContextKey] = user.ID
err := sess.Save(c.Request(), c.Response()) err := sess.Save(ctx.Request(), ctx.Response())
if err != nil { if err != nil {
return fmt.Errorf("failed to set session, err: %w", err) return fmt.Errorf("failed to set session, err: %w", err)
} }
return nil return nil
} }
func removeUserSession(c echo.Context) error { func removeUserSession(ctx echo.Context) error {
sess, _ := session.Get("session", c) sess, _ := session.Get("session", ctx)
sess.Options = &sessions.Options{ sess.Options = &sessions.Options{
Path: "/", Path: "/",
MaxAge: 0, MaxAge: 0,
HttpOnly: true, HttpOnly: true,
} }
sess.Values[userIDContextKey] = nil sess.Values[userIDContextKey] = nil
err := sess.Save(c.Request(), c.Response()) err := sess.Save(ctx.Request(), ctx.Response())
if err != nil { if err != nil {
return fmt.Errorf("failed to set session, err: %w", err) return fmt.Errorf("failed to set session, err: %w", err)
} }
@ -53,14 +53,14 @@ func removeUserSession(c echo.Context) error {
// Use session to store user.id. // Use session to store user.id.
func BasicAuthMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc { func BasicAuthMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(ctx echo.Context) error {
// Skip auth for some paths. // Skip auth for some paths.
if common.HasPrefixes(c.Path(), "/api/auth", "/api/ping", "/api/status", "/api/user/:userId") { if common.HasPrefixes(ctx.Path(), "/api/auth", "/api/ping", "/api/status", "/api/user/:userId") {
return next(c) return next(ctx)
} }
// If there is openId in query string and related user is found, then skip auth. // If there is openId in query string and related user is found, then skip auth.
openID := c.QueryParam("openId") openID := ctx.QueryParam("openId")
if openID != "" { if openID != "" {
userFind := &api.UserFind{ userFind := &api.UserFind{
OpenID: &openID, OpenID: &openID,
@ -71,49 +71,49 @@ func BasicAuthMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc {
} }
if user != nil { if user != nil {
// Stores userID into context. // Stores userID into context.
c.Set(getUserIDContextKey(), user.ID) ctx.Set(getUserIDContextKey(), user.ID)
return next(c) return next(ctx)
} }
} }
if common.HasPrefixes(c.Path(), "/api/memo", "/api/tag", "/api/shortcut") && c.Request().Method == http.MethodGet { needAuth := true
if _, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil { if common.HasPrefixes(ctx.Path(), "/api/memo", "/api/tag", "/api/shortcut") && ctx.Request().Method == http.MethodGet {
return next(c) if _, err := strconv.Atoi(ctx.QueryParam("creatorId")); err == nil {
needAuth = false
} }
} }
sess, err := session.Get("session", c) {
if err != nil { sess, _ := session.Get("session", ctx)
return echo.NewHTTPError(http.StatusUnauthorized, "Missing session").SetInternal(err) userIDValue := sess.Values[userIDContextKey]
if userIDValue == nil && needAuth {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing userID in session")
}
userID, err := strconv.Atoi(fmt.Sprintf("%v", userIDValue))
if err != nil && needAuth {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to malformatted user id in the session.").SetInternal(err)
}
userFind := &api.UserFind{
ID: &userID,
}
user, err := s.Store.FindUser(userFind)
if err != nil && needAuth {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by ID: %d", userID)).SetInternal(err)
}
if needAuth {
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Not found user ID: %d", userID))
} else if user.RowStatus == api.Archived {
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", user.Email))
}
}
// Save userID into context.
ctx.Set(getUserIDContextKey(), userID)
} }
userIDValue := sess.Values[userIDContextKey] return next(ctx)
if userIDValue == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing userID in session")
}
userID, err := strconv.Atoi(fmt.Sprintf("%v", userIDValue))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to malformatted user id in the session.").SetInternal(err)
}
// Even if there is no error, we still need to make sure the user still exists.
userFind := &api.UserFind{
ID: &userID,
}
user, err := s.Store.FindUser(userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by ID: %d", userID)).SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Not found user ID: %d", userID))
} else if user.RowStatus == api.Archived {
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", user.Email))
}
// Stores userID into context.
c.Set(getUserIDContextKey(), userID)
return next(c)
} }
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/usememos/memos/api" "github.com/usememos/memos/api"
"github.com/usememos/memos/common" "github.com/usememos/memos/common"
@ -68,21 +69,21 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
memoFind := &api.MemoFind{} memoFind := &api.MemoFind{}
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil { if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
memoFind.CreatorID = &userID
} else {
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
}
memoFind.CreatorID = &userID memoFind.CreatorID = &userID
} }
// Only can get PUBLIC memos in visitor mode currentUserID := c.Get(getUserIDContextKey()).(int)
_, ok := c.Get(getUserIDContextKey()).(int) if currentUserID == api.UNKNOWN_ID {
if !ok { if memoFind.CreatorID == nil {
publicVisibility := api.Public return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
memoFind.Visibility = &publicVisibility }
memoFind.VisibilityList = []api.Visibility{api.Public}
} else {
if memoFind.CreatorID == nil {
memoFind.CreatorID = &currentUserID
} else {
memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
}
} }
rowStatus := api.RowStatus(c.QueryParam("rowStatus")) rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
@ -99,6 +100,14 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
contentSearch := "#" + tag + " " contentSearch := "#" + tag + " "
memoFind.ContentSearch = &contentSearch memoFind.ContentSearch = &contentSearch
} }
visibilitListStr := c.QueryParam("visibility")
if visibilitListStr != "" {
visibilityList := []api.Visibility{}
for _, visibility := range strings.Split(visibilitListStr, ",") {
visibilityList = append(visibilityList, api.Visibility(visibility))
}
memoFind.VisibilityList = visibilityList
}
if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil { if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
memoFind.Limit = limit memoFind.Limit = limit
} }
@ -190,9 +199,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
memoDelete := &api.MemoDelete{ memoDelete := &api.MemoDelete{
ID: memoID, ID: memoID,
} }
if err := s.Store.DeleteMemo(memoDelete); err != nil {
err = s.Store.DeleteMemo(memoDelete)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete memo ID: %v", memoID)).SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete memo ID: %v", memoID)).SetInternal(err)
} }

View File

@ -22,21 +22,21 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
} }
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil { if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
memoFind.CreatorID = &userID
} else {
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find tag")
}
memoFind.CreatorID = &userID memoFind.CreatorID = &userID
} }
// Only can get PUBLIC memos in visitor mode currentUserID := c.Get(getUserIDContextKey()).(int)
_, ok := c.Get(getUserIDContextKey()).(int) if currentUserID == api.UNKNOWN_ID {
if !ok { if memoFind.CreatorID == nil {
publicVisibility := api.Public return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
memoFind.Visibility = &publicVisibility }
memoFind.VisibilityList = []api.Visibility{api.Public}
} else {
if memoFind.CreatorID == nil {
memoFind.CreatorID = &currentUserID
} else {
memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
}
} }
memoList, err := s.Store.FindMemoList(&memoFind) memoList, err := s.Store.FindMemoList(&memoFind)

View File

@ -182,4 +182,34 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
} }
return nil return nil
}) })
g.DELETE("/user/:userId", func(c echo.Context) error {
currentUserID := c.Get(getUserIDContextKey()).(int)
currentUser, err := s.Store.FindUser(&api.UserFind{
ID: &currentUserID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
} else if currentUser.Role != api.Host {
return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
}
userID, err := strconv.Atoi(c.Param("userId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("userId"))).SetInternal(err)
}
userDelete := &api.UserDelete{
ID: userID,
}
if err := s.Store.DeleteUser(userDelete); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
} }

View File

@ -16,7 +16,8 @@ INSERT INTO
memo ( memo (
`id`, `id`,
`content`, `content`,
`creator_id` `creator_id`,
`visibility`
) )
VALUES VALUES
( (
@ -26,7 +27,8 @@ VALUES
- [x] Clean the room; - [x] Clean the room;
- [x] Read *📖 The Little Prince*; - [x] Read *📖 The Little Prince*;
(👆 click to toggle status)', (👆 click to toggle status)',
101 101,
'PROTECTED'
); );
INSERT INTO INSERT INTO
@ -48,7 +50,8 @@ INSERT INTO
memo ( memo (
`id`, `id`,
`content`, `content`,
`creator_id` `creator_id`,
`visibility`
) )
VALUES VALUES
( (
@ -59,7 +62,8 @@ VALUES
- [ ] Watch *👦 The Boys*; - [ ] Watch *👦 The Boys*;
(👆 click to toggle status) (👆 click to toggle status)
', ',
102 102,
'PROTECTED'
); );
INSERT INTO INSERT INTO

View File

@ -222,8 +222,13 @@ func findMemoRawList(db *sql.DB, find *api.MemoFind) ([]*memoRaw, error) {
if v := find.ContentSearch; v != nil { if v := find.ContentSearch; v != nil {
where, args = append(where, "content LIKE ?"), append(args, "%"+*v+"%") where, args = append(where, "content LIKE ?"), append(args, "%"+*v+"%")
} }
if v := find.Visibility; v != nil { if v := find.VisibilityList; len(v) != 0 {
where, args = append(where, "visibility = ?"), append(args, *v) list := []string{}
for _, visibility := range v {
list = append(list, fmt.Sprintf("$%d", len(args)+1))
args = append(args, visibility)
}
where = append(where, fmt.Sprintf("visibility in (%s)", strings.Join(list, ",")))
} }
pagination := "" pagination := ""