refactor: raw struct for store

pull/61/head
boojack 3 years ago
parent 0b50122aac
commit bc22f69ac5

@ -0,0 +1,21 @@
package api
// RowStatus is the status for a row.
type RowStatus string
const (
// Normal is the status for a normal row.
Normal RowStatus = "NORMAL"
// Archived is the status for an archived row.
Archived RowStatus = "ARCHIVED"
)
func (e RowStatus) String() string {
switch e {
case Normal:
return "NORMAL"
case Archived:
return "ARCHIVED"
}
return ""
}

@ -4,13 +4,14 @@ type Memo struct {
ID int `json:"id"` ID int `json:"id"`
// Standard fields // Standard fields
CreatedTs int64 `json:"createdTs"` RowStatus RowStatus `json:"rowStatus"`
UpdatedTs int64 `json:"updatedTs"` CreatorID int `json:"creatorId"`
RowStatus string `json:"rowStatus"` CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields // Domain specific fields
Content string `json:"content"` Content string `json:"content"`
CreatorID int `json:"creatorId"` Pinned bool `json:"pinned"`
} }
type MemoCreate struct { type MemoCreate struct {
@ -27,7 +28,7 @@ type MemoPatch struct {
ID int ID int
// Standard fields // Standard fields
RowStatus *string `json:"rowStatus"` RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields // Domain specific fields
Content *string `json:"content"` Content *string `json:"content"`
@ -37,8 +38,11 @@ type MemoFind struct {
ID *int `json:"id"` ID *int `json:"id"`
// Standard fields // Standard fields
CreatorID *int `json:"creatorId"` RowStatus *RowStatus `json:"rowStatus"`
RowStatus *string `json:"rowStatus"` CreatorID *int `json:"creatorId"`
// Domain specific fields
Pinned *bool
} }
type MemoDelete struct { type MemoDelete struct {

@ -0,0 +1,21 @@
package api
type MemoOrganizer struct {
ID int
// Domain specific fields
MemoID int
UserID int
Pinned bool
}
type MemoOrganizerFind struct {
MemoID int
UserID int
}
type MemoOrganizerUpsert struct {
MemoID int
UserID int
Pinned bool `json:"pinned"`
}

@ -4,10 +4,10 @@ type Shortcut struct {
ID int `json:"id"` ID int `json:"id"`
// Standard fields // Standard fields
CreatorID int RowStatus RowStatus `json:"rowStatus"`
CreatedTs int64 `json:"createdTs"` CreatorID int `json:"creatorId"`
UpdatedTs int64 `json:"updatedTs"` CreatedTs int64 `json:"createdTs"`
RowStatus string `json:"rowStatus"` UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields // Domain specific fields
Title string `json:"title"` Title string `json:"title"`
@ -27,7 +27,7 @@ type ShortcutPatch struct {
ID int ID int
// Standard fields // Standard fields
RowStatus *string `json:"rowStatus"` RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields // Domain specific fields
Title *string `json:"title"` Title *string `json:"title"`

@ -10,12 +10,23 @@ const (
NormalUser Role = "USER" NormalUser Role = "USER"
) )
func (e Role) String() string {
switch e {
case Owner:
return "OWNER"
case NormalUser:
return "USER"
}
return "USER"
}
type User struct { type User struct {
ID int `json:"id"` ID int `json:"id"`
// Standard fields // Standard fields
CreatedTs int64 `json:"createdTs"` RowStatus RowStatus `json:"rowStatus"`
UpdatedTs int64 `json:"updatedTs"` CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields // Domain specific fields
Email string `json:"email"` Email string `json:"email"`
@ -38,6 +49,9 @@ type UserCreate struct {
type UserPatch struct { type UserPatch struct {
ID int ID int
// Standard fields
RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields // Domain specific fields
Email *string `json:"email"` Email *string `json:"email"`
Name *string `json:"name"` Name *string `json:"name"`
@ -50,6 +64,9 @@ type UserPatch struct {
type UserFind struct { type UserFind struct {
ID *int `json:"id"` ID *int `json:"id"`
// Standard fields
RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields // Domain specific fields
Email *string `json:"email"` Email *string `json:"email"`
Role *Role Role *Role

@ -27,6 +27,8 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
} }
if user == nil { if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", login.Email)) return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", login.Email))
} else if user.RowStatus == api.Archived {
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", login.Email))
} }
// Compare the stored hashed password, with the hashed version of the password that was received. // Compare the stored hashed password, with the hashed version of the password that was received.

@ -85,6 +85,8 @@ func BasicAuthMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc {
} }
if user == nil { if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Not found user ID: %d", userID)) 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. // Stores userID into context.

@ -65,10 +65,16 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
memoFind := &api.MemoFind{ memoFind := &api.MemoFind{
CreatorID: &userID, CreatorID: &userID,
} }
rowStatus := c.QueryParam("rowStatus")
rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
if rowStatus != "" { if rowStatus != "" {
memoFind.RowStatus = &rowStatus memoFind.RowStatus = &rowStatus
} }
pinnedStr := c.QueryParam("pinned")
if pinnedStr != "" {
pinned := pinnedStr == "true"
memoFind.Pinned = &pinned
}
list, err := s.Store.FindMemoList(memoFind) list, err := s.Store.FindMemoList(memoFind)
if err != nil { if err != nil {
@ -83,6 +89,45 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return nil return nil
}) })
g.POST("/memo/:memoId/organizer", func(c echo.Context) error {
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
userID := c.Get(getUserIDContextKey()).(int)
memoOrganizerUpsert := &api.MemoOrganizerUpsert{
MemoID: memoID,
UserID: userID,
}
if err := json.NewDecoder(c.Request().Body).Decode(memoOrganizerUpsert); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo organizer request").SetInternal(err)
}
err = s.Store.UpsertMemoOrganizer(memoOrganizerUpsert)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo organizer").SetInternal(err)
}
memo, err := s.Store.FindMemo(&api.MemoFind{
ID: &memoID,
})
if err != nil {
if common.ErrorCode(err) == common.NotFound {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo ID not found: %d", memoID)).SetInternal(err)
}
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", memoID)).SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(memo)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo response").SetInternal(err)
}
return nil
})
g.GET("/memo/:memoId", func(c echo.Context) error { g.GET("/memo/:memoId", func(c echo.Context) error {
memoID, err := strconv.Atoi(c.Param("memoId")) memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil { if err != nil {

@ -6,6 +6,7 @@ import (
"memos/api" "memos/api"
"memos/common" "memos/common"
"net/http" "net/http"
"strconv"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -84,19 +85,6 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
} }
if userPatch.Email != nil {
userFind := api.UserFind{
Email: userPatch.Email,
}
user, err := s.Store.FindUser(&userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", *userPatch.Email)).SetInternal(err)
}
if user != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("User with email %s existed", *userPatch.Email)).SetInternal(err)
}
}
if userPatch.Password != nil && *userPatch.Password != "" { if userPatch.Password != nil && *userPatch.Password != "" {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost) passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
@ -124,4 +112,53 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
return nil return nil
}) })
g.PATCH("/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.Owner {
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)
}
userPatch := &api.UserPatch{
ID: userID,
}
if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
}
if userPatch.Password != nil && *userPatch.Password != "" {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
passwordHashStr := string(passwordHash)
userPatch.PasswordHash = &passwordHashStr
}
user, err := s.Store.PatchUser(userPatch)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
}
return nil
})
} }

@ -67,7 +67,7 @@ func (s *Server) registerWebhookRoutes(g *echo.Group) {
memoFind := &api.MemoFind{ memoFind := &api.MemoFind{
CreatorID: &user.ID, CreatorID: &user.ID,
} }
rowStatus := c.QueryParam("rowStatus") rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
if rowStatus != "" { if rowStatus != "" {
memoFind.RowStatus = &rowStatus memoFind.RowStatus = &rowStatus
} }

@ -7,8 +7,45 @@ import (
"strings" "strings"
) )
// memoRaw is the store model for an Memo.
// Fields have exactly the same meanings as Memo.
type memoRaw struct {
ID int
// Standard fields
RowStatus api.RowStatus
CreatorID int
CreatedTs int64
UpdatedTs int64
// Domain specific fields
Content string
}
// toMemo creates an instance of Memo based on the memoRaw.
// This is intended to be called when we need to compose an Memo relationship.
func (raw *memoRaw) toMemo() *api.Memo {
return &api.Memo{
ID: raw.ID,
// Standard fields
RowStatus: raw.RowStatus,
CreatorID: raw.CreatorID,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
// Domain specific fields
Content: raw.Content,
}
}
func (s *Store) CreateMemo(create *api.MemoCreate) (*api.Memo, error) { func (s *Store) CreateMemo(create *api.MemoCreate) (*api.Memo, error) {
memo, err := createMemo(s.db, create) memoRaw, err := createMemoRaw(s.db, create)
if err != nil {
return nil, err
}
memo, err := s.composeMemo(memoRaw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -17,7 +54,12 @@ func (s *Store) CreateMemo(create *api.MemoCreate) (*api.Memo, error) {
} }
func (s *Store) PatchMemo(patch *api.MemoPatch) (*api.Memo, error) { func (s *Store) PatchMemo(patch *api.MemoPatch) (*api.Memo, error) {
memo, err := patchMemo(s.db, patch) memoRaw, err := patchMemoRaw(s.db, patch)
if err != nil {
return nil, err
}
memo, err := s.composeMemo(memoRaw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -26,16 +68,26 @@ func (s *Store) PatchMemo(patch *api.MemoPatch) (*api.Memo, error) {
} }
func (s *Store) FindMemoList(find *api.MemoFind) ([]*api.Memo, error) { func (s *Store) FindMemoList(find *api.MemoFind) ([]*api.Memo, error) {
list, err := findMemoList(s.db, find) memoRawList, err := findMemoRawList(s.db, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []*api.Memo{}
for _, raw := range memoRawList {
memo, err := s.composeMemo(raw)
if err != nil {
return nil, err
}
list = append(list, memo)
}
return list, nil return list, nil
} }
func (s *Store) FindMemo(find *api.MemoFind) (*api.Memo, error) { func (s *Store) FindMemo(find *api.MemoFind) (*api.Memo, error) {
list, err := findMemoList(s.db, find) list, err := findMemoRawList(s.db, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -44,7 +96,12 @@ func (s *Store) FindMemo(find *api.MemoFind) (*api.Memo, error) {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
} }
return list[0], nil memo, err := s.composeMemo(list[0])
if err != nil {
return nil, err
}
return memo, nil
} }
func (s *Store) DeleteMemo(delete *api.MemoDelete) error { func (s *Store) DeleteMemo(delete *api.MemoDelete) error {
@ -56,7 +113,7 @@ func (s *Store) DeleteMemo(delete *api.MemoDelete) error {
return nil return nil
} }
func createMemo(db *DB, create *api.MemoCreate) (*api.Memo, error) { func createMemoRaw(db *DB, create *api.MemoCreate) (*memoRaw, error) {
set := []string{"creator_id", "content"} set := []string{"creator_id", "content"}
placeholder := []string{"?", "?"} placeholder := []string{"?", "?"}
args := []interface{}{create.CreatorID, create.Content} args := []interface{}{create.CreatorID, create.Content}
@ -79,26 +136,23 @@ func createMemo(db *DB, create *api.MemoCreate) (*api.Memo, error) {
} }
defer row.Close() defer row.Close()
if !row.Next() { row.Next()
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} var memoRaw memoRaw
}
var memo api.Memo
if err := row.Scan( if err := row.Scan(
&memo.ID, &memoRaw.ID,
&memo.CreatorID, &memoRaw.CreatorID,
&memo.CreatedTs, &memoRaw.CreatedTs,
&memo.UpdatedTs, &memoRaw.UpdatedTs,
&memo.Content, &memoRaw.Content,
&memo.RowStatus, &memoRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &memo, nil return &memoRaw, nil
} }
func patchMemo(db *DB, patch *api.MemoPatch) (*api.Memo, error) { func patchMemoRaw(db *DB, patch *api.MemoPatch) (*memoRaw, error) {
set, args := []string{}, []interface{}{} set, args := []string{}, []interface{}{}
if v := patch.Content; v != nil { if v := patch.Content; v != nil {
@ -125,21 +179,21 @@ func patchMemo(db *DB, patch *api.MemoPatch) (*api.Memo, error) {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
} }
var memo api.Memo var memoRaw memoRaw
if err := row.Scan( if err := row.Scan(
&memo.ID, &memoRaw.ID,
&memo.CreatedTs, &memoRaw.CreatedTs,
&memo.UpdatedTs, &memoRaw.UpdatedTs,
&memo.Content, &memoRaw.Content,
&memo.RowStatus, &memoRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &memo, nil return &memoRaw, nil
} }
func findMemoList(db *DB, find *api.MemoFind) ([]*api.Memo, error) { func findMemoRawList(db *DB, find *api.MemoFind) ([]*memoRaw, error) {
where, args := []string{"1 = 1"}, []interface{}{} where, args := []string{"1 = 1"}, []interface{}{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
@ -151,6 +205,9 @@ func findMemoList(db *DB, find *api.MemoFind) ([]*api.Memo, error) {
if v := find.RowStatus; v != nil { if v := find.RowStatus; v != nil {
where, args = append(where, "row_status = ?"), append(args, *v) where, args = append(where, "row_status = ?"), append(args, *v)
} }
if v := find.Pinned; v != nil {
where = append(where, "id in (SELECT memo_id FROM memo_organizer WHERE pinned = 1 AND user_id = memo.creator_id )")
}
rows, err := db.Db.Query(` rows, err := db.Db.Query(`
SELECT SELECT
@ -169,28 +226,28 @@ func findMemoList(db *DB, find *api.MemoFind) ([]*api.Memo, error) {
} }
defer rows.Close() defer rows.Close()
list := make([]*api.Memo, 0) memoRawList := make([]*memoRaw, 0)
for rows.Next() { for rows.Next() {
var memo api.Memo var memoRaw memoRaw
if err := rows.Scan( if err := rows.Scan(
&memo.ID, &memoRaw.ID,
&memo.CreatorID, &memoRaw.CreatorID,
&memo.CreatedTs, &memoRaw.CreatedTs,
&memo.UpdatedTs, &memoRaw.UpdatedTs,
&memo.Content, &memoRaw.Content,
&memo.RowStatus, &memoRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
list = append(list, &memo) memoRawList = append(memoRawList, &memoRaw)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return list, nil return memoRawList, nil
} }
func deleteMemo(db *DB, delete *api.MemoDelete) error { func deleteMemo(db *DB, delete *api.MemoDelete) error {
@ -206,3 +263,19 @@ func deleteMemo(db *DB, delete *api.MemoDelete) error {
return nil return nil
} }
func (s *Store) composeMemo(raw *memoRaw) (*api.Memo, error) {
memo := raw.toMemo()
memoOrganizer, err := s.FindMemoOrganizer(&api.MemoOrganizerFind{
MemoID: memo.ID,
UserID: memo.CreatorID,
})
if err != nil && common.ErrorCode(err) != common.NotFound {
return nil, err
} else if memoOrganizer != nil {
memo.Pinned = memoOrganizer.Pinned
}
return memo, nil
}

@ -0,0 +1,117 @@
package store
import (
"fmt"
"memos/api"
"memos/common"
)
// memoOrganizerRaw is the store model for an MemoOrganizer.
// Fields have exactly the same meanings as MemoOrganizer.
type memoOrganizerRaw struct {
ID int
// Domain specific fields
MemoID int
UserID int
Pinned bool
}
func (raw *memoOrganizerRaw) toMemoOrganizer() *api.MemoOrganizer {
return &api.MemoOrganizer{
ID: raw.ID,
MemoID: raw.MemoID,
UserID: raw.UserID,
Pinned: raw.Pinned,
}
}
func (s *Store) FindMemoOrganizer(find *api.MemoOrganizerFind) (*api.MemoOrganizer, error) {
memoOrganizerRaw, err := findMemoOrganizer(s.db, find)
if err != nil {
return nil, err
}
memoOrganizer := memoOrganizerRaw.toMemoOrganizer()
return memoOrganizer, nil
}
func (s *Store) UpsertMemoOrganizer(upsert *api.MemoOrganizerUpsert) error {
err := upsertMemoOrganizer(s.db, upsert)
if err != nil {
return err
}
return nil
}
func findMemoOrganizer(db *DB, find *api.MemoOrganizerFind) (*memoOrganizerRaw, error) {
row, err := db.Db.Query(`
SELECT
id,
memo_id,
user_id,
pinned
FROM memo_organizer
WHERE memo_id = ? AND user_id = ?
`, find.MemoID, find.UserID)
if err != nil {
return nil, FormatError(err)
}
defer row.Close()
if !row.Next() {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
}
var memoOrganizerRaw memoOrganizerRaw
if err := row.Scan(
&memoOrganizerRaw.ID,
&memoOrganizerRaw.MemoID,
&memoOrganizerRaw.UserID,
&memoOrganizerRaw.Pinned,
); err != nil {
return nil, FormatError(err)
}
return &memoOrganizerRaw, nil
}
func upsertMemoOrganizer(db *DB, upsert *api.MemoOrganizerUpsert) error {
row, err := db.Db.Query(`
INSERT INTO memo_organizer (
memo_id,
user_id,
pinned
)
VALUES (?, ?, ?)
ON CONFLICT(memo_id, user_id) DO UPDATE
SET
pinned = EXCLUDED.pinned
RETURNING id, memo_id, user_id, pinned
`,
upsert.MemoID,
upsert.UserID,
upsert.Pinned,
)
if err != nil {
return FormatError(err)
}
defer row.Close()
row.Next()
var memoOrganizer api.MemoOrganizer
if err := row.Scan(
&memoOrganizer.ID,
&memoOrganizer.MemoID,
&memoOrganizer.UserID,
&memoOrganizer.Pinned,
); err != nil {
return FormatError(err)
}
return nil
}

@ -1,3 +1,4 @@
DROP TABLE IF EXISTS `memo_organizer`;
DROP TABLE IF EXISTS `memo`; DROP TABLE IF EXISTS `memo`;
DROP TABLE IF EXISTS `shortcut`; DROP TABLE IF EXISTS `shortcut`;
DROP TABLE IF EXISTS `resource`; DROP TABLE IF EXISTS `resource`;

@ -3,6 +3,8 @@ CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
-- allowed row status are 'NORMAL', 'ARCHIVED'.
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
email TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE,
role TEXT NOT NULL CHECK (role IN ('OWNER', 'USER')) DEFAULT 'USER', role TEXT NOT NULL CHECK (role IN ('OWNER', 'USER')) DEFAULT 'USER',
name TEXT NOT NULL, name TEXT NOT NULL,
@ -33,10 +35,9 @@ CREATE TABLE memo (
creator_id INTEGER NOT NULL, creator_id INTEGER NOT NULL,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
-- allowed row status are 'NORMAL', 'ARCHIVED', 'HIDDEN'. row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
row_status TEXT NOT NULL DEFAULT 'NORMAL',
content TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '',
FOREIGN KEY(creator_id) REFERENCES users(id) FOREIGN KEY(creator_id) REFERENCES user(id)
); );
INSERT INTO INSERT INTO
@ -56,17 +57,32 @@ WHERE
rowid = old.rowid; rowid = old.rowid;
END; END;
-- memo_organizer
CREATE TABLE memo_organizer (
id INTEGER PRIMARY KEY AUTOINCREMENT,
memo_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
FOREIGN KEY(memo_id) REFERENCES memo(id),
FOREIGN KEY(user_id) REFERENCES user(id),
UNIQUE(memo_id, user_id)
);
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('memo_organizer', 100);
-- shortcut -- shortcut
CREATE TABLE shortcut ( CREATE TABLE shortcut (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
creator_id INTEGER NOT NULL, creator_id INTEGER NOT NULL,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
title TEXT NOT NULL DEFAULT '', title TEXT NOT NULL DEFAULT '',
payload TEXT NOT NULL DEFAULT '{}', payload TEXT NOT NULL DEFAULT '{}',
-- allowed row status are 'NORMAL', 'ARCHIVED'. FOREIGN KEY(creator_id) REFERENCES user(id)
row_status TEXT NOT NULL DEFAULT 'NORMAL',
FOREIGN KEY(creator_id) REFERENCES users(id)
); );
INSERT INTO INSERT INTO
@ -96,7 +112,7 @@ CREATE TABLE resource (
blob BLOB NOT NULL, blob BLOB NOT NULL,
type TEXT NOT NULL DEFAULT '', type TEXT NOT NULL DEFAULT '',
size INTEGER NOT NULL DEFAULT 0, size INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(creator_id) REFERENCES users(id) FOREIGN KEY(creator_id) REFERENCES user(id)
); );
INSERT INTO INSERT INTO

@ -7,22 +7,63 @@ import (
"strings" "strings"
) )
// resourceRaw is the store model for an Resource.
// Fields have exactly the same meanings as Resource.
type resourceRaw struct {
ID int
// Standard fields
CreatorID int
CreatedTs int64
UpdatedTs int64
// Domain specific fields
Filename string
Blob []byte
Type string
Size int64
}
func (raw *resourceRaw) toResource() *api.Resource {
return &api.Resource{
ID: raw.ID,
// Standard fields
CreatorID: raw.CreatorID,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
// Domain specific fields
Filename: raw.Filename,
Blob: raw.Blob,
Type: raw.Type,
Size: raw.Size,
}
}
func (s *Store) CreateResource(create *api.ResourceCreate) (*api.Resource, error) { func (s *Store) CreateResource(create *api.ResourceCreate) (*api.Resource, error) {
resource, err := createResource(s.db, create) resourceRaw, err := createResource(s.db, create)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resource := resourceRaw.toResource()
return resource, nil return resource, nil
} }
func (s *Store) FindResourceList(find *api.ResourceFind) ([]*api.Resource, error) { func (s *Store) FindResourceList(find *api.ResourceFind) ([]*api.Resource, error) {
list, err := findResourceList(s.db, find) resourceRawList, err := findResourceList(s.db, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return list, nil resourceList := []*api.Resource{}
for _, raw := range resourceRawList {
resourceList = append(resourceList, raw.toResource())
}
return resourceList, nil
} }
func (s *Store) FindResource(find *api.ResourceFind) (*api.Resource, error) { func (s *Store) FindResource(find *api.ResourceFind) (*api.Resource, error) {
@ -35,7 +76,9 @@ func (s *Store) FindResource(find *api.ResourceFind) (*api.Resource, error) {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
} }
return list[0], nil resource := list[0].toResource()
return resource, nil
} }
func (s *Store) DeleteResource(delete *api.ResourceDelete) error { func (s *Store) DeleteResource(delete *api.ResourceDelete) error {
@ -47,7 +90,7 @@ func (s *Store) DeleteResource(delete *api.ResourceDelete) error {
return nil return nil
} }
func createResource(db *DB, create *api.ResourceCreate) (*api.Resource, error) { func createResource(db *DB, create *api.ResourceCreate) (*resourceRaw, error) {
row, err := db.Db.Query(` row, err := db.Db.Query(`
INSERT INTO resource ( INSERT INTO resource (
filename, filename,
@ -70,27 +113,24 @@ func createResource(db *DB, create *api.ResourceCreate) (*api.Resource, error) {
} }
defer row.Close() defer row.Close()
if !row.Next() { row.Next()
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} var resourceRaw resourceRaw
}
var resource api.Resource
if err := row.Scan( if err := row.Scan(
&resource.ID, &resourceRaw.ID,
&resource.Filename, &resourceRaw.Filename,
&resource.Blob, &resourceRaw.Blob,
&resource.Type, &resourceRaw.Type,
&resource.Size, &resourceRaw.Size,
&resource.CreatedTs, &resourceRaw.CreatedTs,
&resource.UpdatedTs, &resourceRaw.UpdatedTs,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &resource, nil return &resourceRaw, nil
} }
func findResourceList(db *DB, find *api.ResourceFind) ([]*api.Resource, error) { func findResourceList(db *DB, find *api.ResourceFind) ([]*resourceRaw, error) {
where, args := []string{"1 = 1"}, []interface{}{} where, args := []string{"1 = 1"}, []interface{}{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
@ -121,29 +161,29 @@ func findResourceList(db *DB, find *api.ResourceFind) ([]*api.Resource, error) {
} }
defer rows.Close() defer rows.Close()
list := make([]*api.Resource, 0) resourceRawList := make([]*resourceRaw, 0)
for rows.Next() { for rows.Next() {
var resource api.Resource var resourceRaw resourceRaw
if err := rows.Scan( if err := rows.Scan(
&resource.ID, &resourceRaw.ID,
&resource.Filename, &resourceRaw.Filename,
&resource.Blob, &resourceRaw.Blob,
&resource.Type, &resourceRaw.Type,
&resource.Size, &resourceRaw.Size,
&resource.CreatedTs, &resourceRaw.CreatedTs,
&resource.UpdatedTs, &resourceRaw.UpdatedTs,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
list = append(list, &resource) resourceRawList = append(resourceRawList, &resourceRaw)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return list, nil return resourceRawList, nil
} }
func deleteResource(db *DB, delete *api.ResourceDelete) error { func deleteResource(db *DB, delete *api.ResourceDelete) error {

@ -1,3 +1,4 @@
DELETE FROM memo_organizer;
DELETE FROM resource; DELETE FROM resource;
DELETE FROM shortcut; DELETE FROM shortcut;
DELETE FROM memo; DELETE FROM memo;

@ -12,6 +12,6 @@ VALUES
'guest', 'guest',
'guest@example.com', 'guest@example.com',
'guest_open_id', 'guest_open_id',
-- "secret" -- raw password: secret
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'
); );

@ -1,23 +1,25 @@
INSERT INTO INSERT INTO
memo ( memo (
`id`,
`content`, `content`,
`creator_id` `creator_id`
) )
VALUES VALUES
( (
101,
'#memos 👋 Welcome to memos', '#memos 👋 Welcome to memos',
101 101
); );
INSERT INTO INSERT INTO
memo ( memo (
`id`,
`content`, `content`,
`creator_id`, `creator_id`
`row_status`
) )
VALUES VALUES
( (
102,
'好好学习,天天向上。', '好好学习,天天向上。',
101, 101
'ARCHIVED'
); );

@ -0,0 +1,12 @@
INSERT INTO
memo_organizer (
`memo_id`,
`user_id`,
`pinned`
)
VALUES
(
102,
101,
1
);

@ -7,30 +7,69 @@ import (
"strings" "strings"
) )
// shortcutRaw is the store model for an Shortcut.
// Fields have exactly the same meanings as Shortcut.
type shortcutRaw struct {
ID int
// Standard fields
RowStatus api.RowStatus
CreatorID int
CreatedTs int64
UpdatedTs int64
// Domain specific fields
Title string
Payload string
}
func (raw *shortcutRaw) toShortcut() *api.Shortcut {
return &api.Shortcut{
ID: raw.ID,
RowStatus: raw.RowStatus,
CreatorID: raw.CreatorID,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
Title: raw.Title,
Payload: raw.Payload,
}
}
func (s *Store) CreateShortcut(create *api.ShortcutCreate) (*api.Shortcut, error) { func (s *Store) CreateShortcut(create *api.ShortcutCreate) (*api.Shortcut, error) {
shortcut, err := createShortcut(s.db, create) shortcutRaw, err := createShortcut(s.db, create)
if err != nil { if err != nil {
return nil, err return nil, err
} }
shortcut := shortcutRaw.toShortcut()
return shortcut, nil return shortcut, nil
} }
func (s *Store) PatchShortcut(patch *api.ShortcutPatch) (*api.Shortcut, error) { func (s *Store) PatchShortcut(patch *api.ShortcutPatch) (*api.Shortcut, error) {
shortcut, err := patchShortcut(s.db, patch) shortcutRaw, err := patchShortcut(s.db, patch)
if err != nil { if err != nil {
return nil, err return nil, err
} }
shortcut := shortcutRaw.toShortcut()
return shortcut, nil return shortcut, nil
} }
func (s *Store) FindShortcutList(find *api.ShortcutFind) ([]*api.Shortcut, error) { func (s *Store) FindShortcutList(find *api.ShortcutFind) ([]*api.Shortcut, error) {
list, err := findShortcutList(s.db, find) shortcutRawList, err := findShortcutList(s.db, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []*api.Shortcut{}
for _, raw := range shortcutRawList {
list = append(list, raw.toShortcut())
}
return list, nil return list, nil
} }
@ -44,7 +83,9 @@ func (s *Store) FindShortcut(find *api.ShortcutFind) (*api.Shortcut, error) {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
} }
return list[0], nil shortcut := list[0].toShortcut()
return shortcut, nil
} }
func (s *Store) DeleteShortcut(delete *api.ShortcutDelete) error { func (s *Store) DeleteShortcut(delete *api.ShortcutDelete) error {
@ -56,7 +97,7 @@ func (s *Store) DeleteShortcut(delete *api.ShortcutDelete) error {
return nil return nil
} }
func createShortcut(db *DB, create *api.ShortcutCreate) (*api.Shortcut, error) { func createShortcut(db *DB, create *api.ShortcutCreate) (*shortcutRaw, error) {
row, err := db.Db.Query(` row, err := db.Db.Query(`
INSERT INTO shortcut ( INSERT INTO shortcut (
title, title,
@ -70,30 +111,29 @@ func createShortcut(db *DB, create *api.ShortcutCreate) (*api.Shortcut, error) {
create.Payload, create.Payload,
create.CreatorID, create.CreatorID,
) )
if err != nil { if err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
defer row.Close() defer row.Close()
row.Next() row.Next()
var shortcut api.Shortcut var shortcutRaw shortcutRaw
if err := row.Scan( if err := row.Scan(
&shortcut.ID, &shortcutRaw.ID,
&shortcut.Title, &shortcutRaw.Title,
&shortcut.Payload, &shortcutRaw.Payload,
&shortcut.CreatorID, &shortcutRaw.CreatorID,
&shortcut.CreatedTs, &shortcutRaw.CreatedTs,
&shortcut.UpdatedTs, &shortcutRaw.UpdatedTs,
&shortcut.RowStatus, &shortcutRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &shortcut, nil return &shortcutRaw, nil
} }
func patchShortcut(db *DB, patch *api.ShortcutPatch) (*api.Shortcut, error) { func patchShortcut(db *DB, patch *api.ShortcutPatch) (*shortcutRaw, error) {
set, args := []string{}, []interface{}{} set, args := []string{}, []interface{}{}
if v := patch.Title; v != nil { if v := patch.Title; v != nil {
@ -123,22 +163,22 @@ func patchShortcut(db *DB, patch *api.ShortcutPatch) (*api.Shortcut, error) {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
} }
var shortcut api.Shortcut var shortcutRaw shortcutRaw
if err := row.Scan( if err := row.Scan(
&shortcut.ID, &shortcutRaw.ID,
&shortcut.Title, &shortcutRaw.Title,
&shortcut.Payload, &shortcutRaw.Payload,
&shortcut.CreatedTs, &shortcutRaw.CreatedTs,
&shortcut.UpdatedTs, &shortcutRaw.UpdatedTs,
&shortcut.RowStatus, &shortcutRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &shortcut, nil return &shortcutRaw, nil
} }
func findShortcutList(db *DB, find *api.ShortcutFind) ([]*api.Shortcut, error) { func findShortcutList(db *DB, find *api.ShortcutFind) ([]*shortcutRaw, error) {
where, args := []string{"1 = 1"}, []interface{}{} where, args := []string{"1 = 1"}, []interface{}{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
@ -169,29 +209,29 @@ func findShortcutList(db *DB, find *api.ShortcutFind) ([]*api.Shortcut, error) {
} }
defer rows.Close() defer rows.Close()
list := make([]*api.Shortcut, 0) shortcutRawList := make([]*shortcutRaw, 0)
for rows.Next() { for rows.Next() {
var shortcut api.Shortcut var shortcutRaw shortcutRaw
if err := rows.Scan( if err := rows.Scan(
&shortcut.ID, &shortcutRaw.ID,
&shortcut.Title, &shortcutRaw.Title,
&shortcut.Payload, &shortcutRaw.Payload,
&shortcut.CreatorID, &shortcutRaw.CreatorID,
&shortcut.CreatedTs, &shortcutRaw.CreatedTs,
&shortcut.UpdatedTs, &shortcutRaw.UpdatedTs,
&shortcut.RowStatus, &shortcutRaw.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
list = append(list, &shortcut) shortcutRawList = append(shortcutRawList, &shortcutRaw)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return list, nil return shortcutRawList, nil
} }
func deleteShortcut(db *DB, delete *api.ShortcutDelete) error { func deleteShortcut(db *DB, delete *api.ShortcutDelete) error {

@ -7,30 +7,73 @@ import (
"strings" "strings"
) )
// userRaw is the store model for an User.
// Fields have exactly the same meanings as User.
type userRaw struct {
ID int
// Standard fields
RowStatus api.RowStatus
CreatedTs int64
UpdatedTs int64
// Domain specific fields
Email string
Role api.Role
Name string
PasswordHash string
OpenID string
}
func (raw *userRaw) toUser() *api.User {
return &api.User{
ID: raw.ID,
RowStatus: raw.RowStatus,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
Email: raw.Email,
Role: raw.Role,
Name: raw.Name,
PasswordHash: raw.PasswordHash,
OpenID: raw.OpenID,
}
}
func (s *Store) CreateUser(create *api.UserCreate) (*api.User, error) { func (s *Store) CreateUser(create *api.UserCreate) (*api.User, error) {
user, err := createUser(s.db, create) userRaw, err := createUser(s.db, create)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user := userRaw.toUser()
return user, nil return user, nil
} }
func (s *Store) PatchUser(patch *api.UserPatch) (*api.User, error) { func (s *Store) PatchUser(patch *api.UserPatch) (*api.User, error) {
user, err := patchUser(s.db, patch) userRaw, err := patchUser(s.db, patch)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user := userRaw.toUser()
return user, nil return user, nil
} }
func (s *Store) FindUserList(find *api.UserFind) ([]*api.User, error) { func (s *Store) FindUserList(find *api.UserFind) ([]*api.User, error) {
list, err := findUserList(s.db, find) userRawList, err := findUserList(s.db, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []*api.User{}
for _, raw := range userRawList {
list = append(list, raw.toUser())
}
return list, nil return list, nil
} }
@ -46,10 +89,12 @@ func (s *Store) FindUser(find *api.UserFind) (*api.User, error) {
return nil, &common.Error{Code: common.Conflict, Err: fmt.Errorf("found %d users with filter %+v, expect 1. ", len(list), find)} return nil, &common.Error{Code: common.Conflict, Err: fmt.Errorf("found %d users with filter %+v, expect 1. ", len(list), find)}
} }
return list[0], nil user := list[0].toUser()
return user, nil
} }
func createUser(db *DB, create *api.UserCreate) (*api.User, error) { func createUser(db *DB, create *api.UserCreate) (*userRaw, error) {
row, err := db.Db.Query(` row, err := db.Db.Query(`
INSERT INTO user ( INSERT INTO user (
email, email,
@ -73,37 +118,40 @@ func createUser(db *DB, create *api.UserCreate) (*api.User, error) {
defer row.Close() defer row.Close()
row.Next() row.Next()
var user api.User var userRaw userRaw
if err := row.Scan( if err := row.Scan(
&user.ID, &userRaw.ID,
&user.Email, &userRaw.Email,
&user.Role, &userRaw.Role,
&user.Name, &userRaw.Name,
&user.PasswordHash, &userRaw.PasswordHash,
&user.OpenID, &userRaw.OpenID,
&user.CreatedTs, &userRaw.CreatedTs,
&user.UpdatedTs, &userRaw.UpdatedTs,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &user, nil return &userRaw, nil
} }
func patchUser(db *DB, patch *api.UserPatch) (*api.User, error) { func patchUser(db *DB, patch *api.UserPatch) (*userRaw, error) {
set, args := []string{}, []interface{}{} set, args := []string{}, []interface{}{}
if v := patch.RowStatus; v != nil {
set, args = append(set, "row_status = ?"), append(args, *v)
}
if v := patch.Email; v != nil { if v := patch.Email; v != nil {
set, args = append(set, "email = ?"), append(args, v) set, args = append(set, "email = ?"), append(args, *v)
} }
if v := patch.Name; v != nil { if v := patch.Name; v != nil {
set, args = append(set, "name = ?"), append(args, v) set, args = append(set, "name = ?"), append(args, *v)
} }
if v := patch.PasswordHash; v != nil { if v := patch.PasswordHash; v != nil {
set, args = append(set, "password_hash = ?"), append(args, v) set, args = append(set, "password_hash = ?"), append(args, *v)
} }
if v := patch.OpenID; v != nil { if v := patch.OpenID; v != nil {
set, args = append(set, "open_id = ?"), append(args, v) set, args = append(set, "open_id = ?"), append(args, *v)
} }
args = append(args, patch.ID) args = append(args, patch.ID)
@ -120,27 +168,27 @@ func patchUser(db *DB, patch *api.UserPatch) (*api.User, error) {
defer row.Close() defer row.Close()
if row.Next() { if row.Next() {
var user api.User var userRaw userRaw
if err := row.Scan( if err := row.Scan(
&user.ID, &userRaw.ID,
&user.Email, &userRaw.Email,
&user.Role, &userRaw.Role,
&user.Name, &userRaw.Name,
&user.PasswordHash, &userRaw.PasswordHash,
&user.OpenID, &userRaw.OpenID,
&user.CreatedTs, &userRaw.CreatedTs,
&user.UpdatedTs, &userRaw.UpdatedTs,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return &user, nil return &userRaw, nil
} }
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("user ID not found: %d", patch.ID)} return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("user ID not found: %d", patch.ID)}
} }
func findUserList(db *DB, find *api.UserFind) ([]*api.User, error) { func findUserList(db *DB, find *api.UserFind) ([]*userRaw, error) {
where, args := []string{"1 = 1"}, []interface{}{} where, args := []string{"1 = 1"}, []interface{}{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
@ -178,29 +226,29 @@ func findUserList(db *DB, find *api.UserFind) ([]*api.User, error) {
} }
defer rows.Close() defer rows.Close()
list := make([]*api.User, 0) userRawList := make([]*userRaw, 0)
for rows.Next() { for rows.Next() {
var user api.User var userRaw userRaw
if err := rows.Scan( if err := rows.Scan(
&user.ID, &userRaw.ID,
&user.Email, &userRaw.Email,
&user.Role, &userRaw.Role,
&user.Name, &userRaw.Name,
&user.PasswordHash, &userRaw.PasswordHash,
&user.OpenID, &userRaw.OpenID,
&user.CreatedTs, &userRaw.CreatedTs,
&user.UpdatedTs, &userRaw.UpdatedTs,
); err != nil { ); err != nil {
fmt.Println(err) fmt.Println(err)
return nil, FormatError(err) return nil, FormatError(err)
} }
list = append(list, &user) userRawList = append(userRawList, &userRaw)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
return list, nil return userRawList, nil
} }

Loading…
Cancel
Save