feat: migrate api to v1 package

This commit is contained in:
Steven
2023-06-20 18:02:53 +08:00
parent 44ef82fb4a
commit 92d50eabf3
17 changed files with 244 additions and 799 deletions

View File

@ -1,21 +0,0 @@
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 ""
}

View File

@ -1,12 +0,0 @@
package api
type Signup struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
Password string `json:"password"`
}
type Signin struct {
Email string `json:"email"`
Password string `json:"password"`
}

View File

@ -1,91 +0,0 @@
package api
// Visibility is the type of a visibility.
type Visibility string
const (
// VisibilityPublic is the PUBLIC visibility.
VisibilityPublic Visibility = "PUBLIC"
// VisibilityWorkspace is the WORKSPACE visibility.
VisibilityWorkspace Visibility = "WORKSPACE"
// VisibilityPrivite is the PRIVATE visibility.
VisibilityPrivite Visibility = "PRIVATE"
)
func (e Visibility) String() string {
switch e {
case VisibilityPublic:
return "PUBLIC"
case VisibilityWorkspace:
return "WORKSPACE"
case VisibilityPrivite:
return "PRIVATE"
}
return "PRIVATE"
}
type Shortcut struct {
ID int `json:"id"`
// Standard fields
CreatorID int `json:"creatorId"`
Creator *User `json:"creator"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
WorkspaceID int `json:"workspaceId"`
RowStatus RowStatus `json:"rowStatus"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type ShortcutCreate struct {
// Standard fields
CreatorID int
WorkspaceID int `json:"workspaceId"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type ShortcutPatch struct {
ID int
// Standard fields
RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields
Name *string `json:"name"`
Link *string `json:"link"`
Description *string `json:"description"`
Visibility *Visibility `json:"visibility"`
}
type ShortcutFind struct {
ID *int
// Standard fields
CreatorID *int `json:"creatorId"`
WorkspaceID *int `json:"workspaceId"`
// Domain specific fields
Name *string
Link *string
Description *string
MemberID *int
VisibilityList []Visibility
}
type ShortcutDelete struct {
ID *int
// Standard fields
CreatorID *int
WorkspaceID *int
}

View File

@ -1,46 +0,0 @@
package api
// Role is the type of a role.
type Role string
const (
// RoleAdmin is the ADMIN role.
RoleAdmin Role = "ADMIN"
// RoleUser is the USER role.
RoleUser Role = "USER"
)
func (e Role) String() string {
switch e {
case RoleAdmin:
return "ADMIN"
case RoleUser:
return "USER"
}
return "USER"
}
type User struct {
ID int `json:"id"`
// Standard fields
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
RowStatus RowStatus `json:"rowStatus"`
// Domain specific fields
Email string `json:"email"`
DisplayName string `json:"displayName"`
PasswordHash string `json:"-"`
OpenID string `json:"openId"`
Role Role `json:"role"`
}
type UserCreate struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
Password string `json:"password"`
PasswordHash string `json:"-"`
OpenID string `json:"-"`
Role Role `json:"-"`
}

View File

@ -1,69 +0,0 @@
package api
import (
"encoding/json"
"fmt"
)
type UserSettingKey string
const (
// UserSettingLocaleKey is the key type for user locale.
UserSettingLocaleKey UserSettingKey = "locale"
)
// String returns the string format of UserSettingKey type.
func (key UserSettingKey) String() string {
if key == UserSettingLocaleKey {
return "locale"
}
return ""
}
var (
UserSettingLocaleValue = []string{"en", "zh"}
)
type UserSetting struct {
UserID int
Key UserSettingKey `json:"key"`
// Value is a JSON string with basic value
Value string `json:"value"`
}
type UserSettingUpsert struct {
UserID int
Key UserSettingKey `json:"key"`
Value string `json:"value"`
}
func (upsert UserSettingUpsert) Validate() error {
if upsert.Key == UserSettingLocaleKey {
localeValue := "en"
err := json.Unmarshal([]byte(upsert.Value), &localeValue)
if err != nil {
return fmt.Errorf("failed to unmarshal user setting locale value")
}
invalid := true
for _, value := range UserSettingLocaleValue {
if localeValue == value {
invalid = false
break
}
}
if invalid {
return fmt.Errorf("invalid user setting locale value")
}
} else {
return fmt.Errorf("invalid user setting key")
}
return nil
}
type UserSettingFind struct {
UserID int
Key *UserSettingKey `json:"key"`
}

227
api/v1/shortcut.go Normal file
View File

@ -0,0 +1,227 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/boojack/shortify/store"
"github.com/labstack/echo/v4"
)
// Visibility is the type of a visibility.
type Visibility string
const (
// VisibilityPublic is the PUBLIC visibility.
VisibilityPublic Visibility = "PUBLIC"
// VisibilityWorkspace is the WORKSPACE visibility.
VisibilityWorkspace Visibility = "WORKSPACE"
// VisibilityPrivate is the PRIVATE visibility.
VisibilityPrivate Visibility = "PRIVATE"
)
func (e Visibility) String() string {
switch e {
case VisibilityPublic:
return "PUBLIC"
case VisibilityWorkspace:
return "WORKSPACE"
case VisibilityPrivate:
return "PRIVATE"
}
return "PRIVATE"
}
type Shortcut struct {
ID int `json:"id"`
// Standard fields
CreatorID int `json:"creatorId"`
Creator *User `json:"creator"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
WorkspaceID int `json:"workspaceId"`
RowStatus RowStatus `json:"rowStatus"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type CreateShortcutRequest struct {
WorkspaceID int `json:"workspaceId"`
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type PatchShortcutRequest struct {
RowStatus *RowStatus `json:"rowStatus"`
Name *string `json:"name"`
Link *string `json:"link"`
Description *string `json:"description"`
Visibility *Visibility `json:"visibility"`
}
func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.POST("/shortcut", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
create := &CreateShortcutRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
}
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
CreatorID: userID,
WorkspaceID: create.WorkspaceID,
Name: create.Name,
Link: create.Link,
Description: create.Description,
Visibility: convertVisibilityToStore(create.Visibility),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
ID: &shortcutID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
}
workspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{
UserID: &userID,
WorkspaceID: &shortcut.WorkspaceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err)
}
if shortcut.CreatorID != userID && workspaceUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut")
}
patch := &PatchShortcutRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(patch); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
}
shortcut, err = s.Store.UpdateShortcut(ctx, &store.UpdateShortcut{
ID: shortcutID,
RowStatus: (*store.RowStatus)(patch.RowStatus),
Name: patch.Name,
Link: patch.Link,
Visibility: (*store.Visibility)(patch.Visibility),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.GET("/shortcut", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
find := &store.FindShortcut{}
if workspaceID, err := strconv.Atoi(c.QueryParam("workspaceId")); err == nil {
find.WorkspaceID = &workspaceID
}
if name := c.QueryParam("name"); name != "" {
find.Name = &name
}
list := []*store.Shortcut{}
find.VisibilityList = []store.Visibility{store.VisibilityWorkspace, store.VisibilityPublic}
visibleShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
}
list = append(list, visibleShortcutList...)
find.VisibilityList = []store.Visibility{store.VisibilityPrivate}
find.CreatorID = &userID
privateShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch private shortcut list").SetInternal(err)
}
list = append(list, privateShortcutList...)
return c.JSON(http.StatusOK, list)
})
g.GET("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
}
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
ID: &shortcutID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to fetch shortcut by ID %d", shortcutID)).SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.DELETE("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
}
if err := s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
ID: shortcutID,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
func convertVisibilityToStore(visibility Visibility) store.Visibility {
switch visibility {
case VisibilityPrivate:
return store.VisibilityPrivate
case VisibilityWorkspace:
return store.VisibilityWorkspace
case VisibilityPublic:
return store.VisibilityPublic
default:
return store.VisibilityPrivate
}
}

View File

@ -25,4 +25,5 @@ func (s *APIV1Service) Start(apiV1Group *echo.Group, secret string) {
s.registerUserRoutes(apiV1Group)
s.registerWorkspaceRoutes(apiV1Group)
s.registerWorkspaceUserRoutes(apiV1Group)
s.registerShortcutRoutes(apiV1Group)
}

View File

@ -81,7 +81,7 @@ func (s *APIV1Service) registerWorkspaceRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace").SetInternal(err)
}
_, err = s.Store.UpsertWorkspaceUserV1(ctx, &store.WorkspaceUser{
_, err = s.Store.UpsertWorkspaceUser(ctx, &store.WorkspaceUser{
WorkspaceID: workspace.ID,
UserID: userID,
Role: store.RoleAdmin,

View File

@ -59,7 +59,7 @@ func (s *APIV1Service) registerWorkspaceUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace user request").SetInternal(err)
}
workspaceUser, err := s.Store.UpsertWorkspaceUserV1(ctx, &store.WorkspaceUser{
workspaceUser, err := s.Store.UpsertWorkspaceUser(ctx, &store.WorkspaceUser{
WorkspaceID: upsert.WorkspaceID,
UserID: upsert.UserID,
Role: convertRoleToStore(upsert.Role),
@ -157,7 +157,7 @@ func (s *APIV1Service) registerWorkspaceUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to delete workspace user").SetInternal(err)
}
if err := s.Store.DeleteWorkspaceUserV1(ctx, &store.DeleteWorkspaceUser{
if err := s.Store.DeleteWorkspaceUser(ctx, &store.DeleteWorkspaceUser{
WorkspaceID: workspaceID,
UserID: userID,
}); err != nil {