diff --git a/backend/app/http/orders/controller_order.go b/backend/app/http/orders/controller_order.go index 58e83f6..489cc53 100644 --- a/backend/app/http/orders/controller_order.go +++ b/backend/app/http/orders/controller_order.go @@ -61,7 +61,7 @@ func (c *OrderController) List(ctx fiber.Ctx, claim *jwt.Claims, pagination *req // Create order // @Router /api/v1/orders [post] // @Bind claim local -// @Bind hash path +// @Bind hash // @Bind tenantSlug cookie key(tenant) func (c *OrderController) Create(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash string) (*UserOrder, error) { user, err := c.userSvc.GetUserByID(ctx.Context(), claim.UserID) diff --git a/backend/app/http/orders/controller_pay.go b/backend/app/http/orders/controller_pay.go index 69f41d7..5d0f300 100644 --- a/backend/app/http/orders/controller_pay.go +++ b/backend/app/http/orders/controller_pay.go @@ -1,6 +1,8 @@ package orders import ( + "fmt" + "backend/app/errorx" "backend/app/http/posts" "backend/app/http/tenants" @@ -10,7 +12,6 @@ import ( "backend/providers/jwt" "backend/providers/pay" - "github.com/go-pay/gopay/wechat/v3" "github.com/gofiber/fiber/v3" "github.com/samber/lo" log "github.com/sirupsen/logrus" @@ -32,10 +33,11 @@ func (c *PayController) Prepare() error { } // JSPay -// @Router /api/v1/orders/pay/:orderID/js [get] +// @Router /api/v1/orders/pay/:orderID/:channel [get] // @Bind claim local // @Bind orderID path -func (ctl *PayController) JSPay(ctx fiber.Ctx, claim *jwt.Claims, orderID string) (*wechat.JSAPIPayParams, error) { +// @Bind channel path +func (ctl *PayController) Pay(ctx fiber.Ctx, claim *jwt.Claims, channel, orderID string) (any, error) { order, err := ctl.svc.GetUserOrderByOrderID(ctx.Context(), orderID, claim.UserID) if err != nil { return nil, err @@ -58,6 +60,15 @@ func (ctl *PayController) JSPay(ctx fiber.Ctx, claim *jwt.Claims, orderID string return nil, errorx.BadRequest.WithMsg("未绑定微信") } + switch channel { + case "wechat-js": + return ctl.payWechatJS(ctx, order, &oauth) + } + + return nil, errorx.BadRequest.WithMsg("支付渠道错误") +} + +func (ctl *PayController) payWechatJS(ctx fiber.Ctx, order *model.Orders, oauth *model.UserOauths) (any, error) { params, err := ctl.pay.WeChat_JSApiPayRequest( ctx.Context(), oauth.OpenID, @@ -65,7 +76,7 @@ func (ctl *PayController) JSPay(ctx fiber.Ctx, claim *jwt.Claims, orderID string order.Title, order.Amount, 1, - "/v1/orders/pay/wechat/notify", + ctl.getNotifyURL(ctx, "wechat"), ) if err != nil { return nil, err @@ -73,3 +84,13 @@ func (ctl *PayController) JSPay(ctx fiber.Ctx, claim *jwt.Claims, orderID string return params, nil } + +func (ctl *PayController) getNotifyURL(ctx fiber.Ctx, channel string) string { + return fmt.Sprintf("%s://%s/v1/orders/pay/notify/%s", ctx.Request().URI().Scheme(), ctx.Host(), channel) +} + +// @Router /v1/orders/pay/notify/:channel [post] +// @Bind channel path +func (c *PayController) Notify(ctx fiber.Ctx, channel string) (any, error) { + return nil, nil +} diff --git a/backend/app/http/orders/routes.gen.go b/backend/app/http/orders/routes.gen.go index f4a26bf..30a6367 100644 --- a/backend/app/http/orders/routes.gen.go +++ b/backend/app/http/orders/routes.gen.go @@ -46,9 +46,10 @@ func (r *Routes) Register(router fiber.Router) { )) // 注册路由组: PayController - router.Get("/api/v1/orders/pay/:orderID/js", DataFunc2( - r.payController.JSPay, + router.Get("/api/v1/orders/pay/:orderID/:channel", DataFunc3( + r.payController.Pay, Local[*jwt.Claims]("claim"), + PathParam[string]("channel"), PathParam[string]("orderID"), )) diff --git a/backend/app/http/posts/controller.go b/backend/app/http/posts/controller.go index 99f9ab7..23cb597 100644 --- a/backend/app/http/posts/controller.go +++ b/backend/app/http/posts/controller.go @@ -1,8 +1,13 @@ package posts import ( + "time" + + "backend/app/errorx" "backend/app/http/tenants" + "backend/app/http/users" "backend/app/requests" + "backend/database/fields" "backend/database/models/qvyun_v2/public/model" "backend/providers/jwt" @@ -10,12 +15,15 @@ import ( "github.com/jinzhu/copier" "github.com/samber/lo" log "github.com/sirupsen/logrus" + "github.com/speps/go-hashids/v2" ) // @provider type Controller struct { - tenantSvc *tenants.Service svc *Service + hashIds *hashids.HashID + userSvc *users.Service + tenantSvc *tenants.Service log *log.Entry `inject:"false"` } @@ -109,7 +117,16 @@ func (c *Controller) Show(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash str return nil, err } - post, err := c.svc.GetPostByHash(ctx.Context(), tenant.ID, hash) + postIds, err := c.hashIds.DecodeInt64WithError(hash) + if err != nil { + return nil, errorx.RecordNotExists + } + + if tenant.ID != postIds[0] { + return nil, errorx.RecordNotExists + } + + post, err := c.svc.GetPostByID(ctx.Context(), postIds[1]) if err != nil { return nil, err } @@ -120,3 +137,40 @@ func (c *Controller) Show(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash str return userPost, nil } + +// @Router /api/v1/posts [post] +// @Bind claim local +// @Bind tenantSlug cookie key(tenant) +// @Bind body body +func (ctl *Controller) Create(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug string, body *PostBody) error { + user, err := ctl.userSvc.GetUserByID(ctx.Context(), claim.UserID) + if err != nil { + return err + } + + tenant, err := ctl.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) + if err != nil { + return err + } + + post := &model.Posts{ + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + TenantID: tenant.ID, + UserID: user.ID, + Title: body.Title, + Description: body.Description, + Content: body.Content, + PosterAssetID: 0, + Stage: fields.PostStagePending, + Status: fields.PostStatusPending, + Price: body.Price, + Discount: body.Discount, + } + + if err := ctl.svc.Create(ctx.Context(), tenant, user, post); err != nil { + return err + } + + return nil +} diff --git a/backend/app/http/posts/dto.go b/backend/app/http/posts/dto.go index 981ff0e..5c933d0 100644 --- a/backend/app/http/posts/dto.go +++ b/backend/app/http/posts/dto.go @@ -30,3 +30,11 @@ type UserPostFilter struct { CreatedAt *time.Time `query:"created_at"` Keyword *string `json:"title"` } + +type PostBody struct { + Title string + Description string + Content string + Price int64 + Discount int16 +} diff --git a/backend/app/http/posts/service.go b/backend/app/http/posts/service.go index 44eea9f..404502d 100644 --- a/backend/app/http/posts/service.go +++ b/backend/app/http/posts/service.go @@ -13,14 +13,16 @@ import ( . "github.com/go-jet/jet/v2/postgres" "github.com/samber/lo" log "github.com/sirupsen/logrus" + "github.com/speps/go-hashids/v2" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) // @provider:except type Service struct { - db *sql.DB - log *log.Entry `inject:"false"` + db *sql.DB + hashIds *hashids.HashID + log *log.Entry `inject:"false"` } func (svc *Service) Prepare() error { @@ -163,22 +165,14 @@ func (svc *Service) GetPosts(ctx context.Context, pagination *requests.Paginatio return posts, count.Cnt, nil } -// GetPostByHash -func (svc *Service) GetPostByHash(ctx context.Context, tenantID int64, hash string) (*model.Posts, error) { - _, span := otel.Start(ctx, "users.service.GetPostByHash") +// GetPostByID +func (svc *Service) GetPostByID(ctx context.Context, id int64) (*model.Posts, error) { + _, span := otel.Start(ctx, "users.service.GetPostByID") defer span.End() - span.SetAttributes( - attribute.String("hash", hash), - ) + span.SetAttributes(attribute.Int64("post.id", id)) tbl := table.Posts - stmt := tbl. - SELECT(tbl.AllColumns). - WHERE( - tbl.Hash.EQ(String(hash)).AND( - tbl.TenantID.EQ(Int64(tenantID)), - ), - ) + stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id))) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var post model.Posts @@ -220,3 +214,40 @@ func (svc *Service) GetUserBoughtIDs(ctx context.Context, tenantID, userID int64 return item.PostID }), nil } + +// Create +func (svc *Service) Create(ctx context.Context, tenant *model.Tenants, user *model.Users, post *model.Posts) error { + _, span := otel.Start(ctx, "users.service.Create") + defer span.End() + + tbl := table.Posts + stmt := tbl.INSERT(tbl.MutableColumns).MODEL(post) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + if _, err := stmt.ExecContext(ctx, svc.db); err != nil { + return err + } + + return nil +} + +// GetPostByHash +func (svc *Service) GetPostByHash(ctx context.Context, tenantID int64, hash string) (*model.Posts, error) { + _, span := otel.Start(ctx, "users.service.GetPostByHash") + defer span.End() + span.SetAttributes( + attribute.Int64("tenant.id", tenantID), + attribute.String("hash", hash), + ) + + postIDs, err := svc.hashIds.DecodeInt64WithError(hash) + if err != nil { + return nil, err + } + + if tenantID != postIDs[0] { + return nil, nil + } + + return svc.GetPostByID(ctx, postIDs[1]) +} diff --git a/backend/database/fields/posts.gen.go b/backend/database/fields/posts.gen.go new file mode 100644 index 0000000..79eec95 --- /dev/null +++ b/backend/database/fields/posts.gen.go @@ -0,0 +1,241 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: - +// Revision: - +// Build Date: - +// Built By: - + +package fields + +import ( + "database/sql/driver" + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + // UserStatusPending is a UserStatus of type Pending. + UserStatusPending UserStatus = iota + // UserStatusVerified is a UserStatus of type Verified. + UserStatusVerified + // UserStatusBlocked is a UserStatus of type Blocked. + UserStatusBlocked +) + +var ErrInvalidUserStatus = fmt.Errorf("not a valid UserStatus, try [%s]", strings.Join(_UserStatusNames, ", ")) + +const _UserStatusName = "PendingVerifiedBlocked" + +var _UserStatusNames = []string{ + _UserStatusName[0:7], + _UserStatusName[7:15], + _UserStatusName[15:22], +} + +// UserStatusNames returns a list of possible string values of UserStatus. +func UserStatusNames() []string { + tmp := make([]string, len(_UserStatusNames)) + copy(tmp, _UserStatusNames) + return tmp +} + +// UserStatusValues returns a list of the values for UserStatus +func UserStatusValues() []UserStatus { + return []UserStatus{ + UserStatusPending, + UserStatusVerified, + UserStatusBlocked, + } +} + +var _UserStatusMap = map[UserStatus]string{ + UserStatusPending: _UserStatusName[0:7], + UserStatusVerified: _UserStatusName[7:15], + UserStatusBlocked: _UserStatusName[15:22], +} + +// String implements the Stringer interface. +func (x UserStatus) String() string { + if str, ok := _UserStatusMap[x]; ok { + return str + } + return fmt.Sprintf("UserStatus(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x UserStatus) IsValid() bool { + _, ok := _UserStatusMap[x] + return ok +} + +var _UserStatusValue = map[string]UserStatus{ + _UserStatusName[0:7]: UserStatusPending, + _UserStatusName[7:15]: UserStatusVerified, + _UserStatusName[15:22]: UserStatusBlocked, +} + +// ParseUserStatus attempts to convert a string to a UserStatus. +func ParseUserStatus(name string) (UserStatus, error) { + if x, ok := _UserStatusValue[name]; ok { + return x, nil + } + return UserStatus(0), fmt.Errorf("%s is %w", name, ErrInvalidUserStatus) +} + +var errUserStatusNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *UserStatus) Scan(value interface{}) (err error) { + if value == nil { + *x = UserStatus(0) + return + } + + // A wider range of scannable types. + // driver.Value values at the top of the list for expediency + switch v := value.(type) { + case int64: + *x = UserStatus(v) + case string: + *x, err = ParseUserStatus(v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(v); verr == nil { + *x, err = UserStatus(val), nil + } + } + case []byte: + *x, err = ParseUserStatus(string(v)) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(string(v)); verr == nil { + *x, err = UserStatus(val), nil + } + } + case UserStatus: + *x = v + case int: + *x = UserStatus(v) + case *UserStatus: + if v == nil { + return errUserStatusNilPtr + } + *x = *v + case uint: + *x = UserStatus(v) + case uint64: + *x = UserStatus(v) + case *int: + if v == nil { + return errUserStatusNilPtr + } + *x = UserStatus(*v) + case *int64: + if v == nil { + return errUserStatusNilPtr + } + *x = UserStatus(*v) + case float64: // json marshals everything as a float64 if it's a number + *x = UserStatus(v) + case *float64: // json marshals everything as a float64 if it's a number + if v == nil { + return errUserStatusNilPtr + } + *x = UserStatus(*v) + case *uint: + if v == nil { + return errUserStatusNilPtr + } + *x = UserStatus(*v) + case *uint64: + if v == nil { + return errUserStatusNilPtr + } + *x = UserStatus(*v) + case *string: + if v == nil { + return errUserStatusNilPtr + } + *x, err = ParseUserStatus(*v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(*v); verr == nil { + *x, err = UserStatus(val), nil + } + } + } + + return +} + +// Value implements the driver Valuer interface. +func (x UserStatus) Value() (driver.Value, error) { + return int64(x), nil +} + +// Set implements the Golang flag.Value interface func. +func (x *UserStatus) Set(val string) error { + v, err := ParseUserStatus(val) + *x = v + return err +} + +// Get implements the Golang flag.Getter interface func. +func (x *UserStatus) Get() interface{} { + return *x +} + +// Type implements the github.com/spf13/pFlag Value interface. +func (x *UserStatus) Type() string { + return "UserStatus" +} + +type NullUserStatus struct { + UserStatus UserStatus + Valid bool +} + +func NewNullUserStatus(val interface{}) (x NullUserStatus) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Scan implements the Scanner interface. +func (x *NullUserStatus) Scan(value interface{}) (err error) { + if value == nil { + x.UserStatus, x.Valid = UserStatus(0), false + return + } + + err = x.UserStatus.Scan(value) + x.Valid = (err == nil) + return +} + +// Value implements the driver Valuer interface. +func (x NullUserStatus) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + // driver.Value accepts int64 for int values. + return int64(x.UserStatus), nil +} + +type NullUserStatusStr struct { + NullUserStatus +} + +func NewNullUserStatusStr(val interface{}) (x NullUserStatusStr) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Value implements the driver Valuer interface. +func (x NullUserStatusStr) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + return x.UserStatus.String(), nil +} diff --git a/backend/database/fields/posts.go b/backend/database/fields/posts.go new file mode 100644 index 0000000..5c4cb28 --- /dev/null +++ b/backend/database/fields/posts.go @@ -0,0 +1,5 @@ +package fields + +// swagger:enum UserStatus +// ENUM( Pending, Verified, Blocked) +type UserStatus int16 diff --git a/backend/database/fields/users.gen.go b/backend/database/fields/users.gen.go index 79eec95..b69622d 100644 --- a/backend/database/fields/users.gen.go +++ b/backend/database/fields/users.gen.go @@ -15,81 +15,87 @@ import ( ) const ( - // UserStatusPending is a UserStatus of type Pending. - UserStatusPending UserStatus = iota - // UserStatusVerified is a UserStatus of type Verified. - UserStatusVerified - // UserStatusBlocked is a UserStatus of type Blocked. - UserStatusBlocked + // PostStagePending is a PostStage of type Pending. + PostStagePending PostStage = iota + // PostStageProcessing is a PostStage of type Processing. + PostStageProcessing + // PostStageCompleted is a PostStage of type Completed. + PostStageCompleted + // PostStageDeleted is a PostStage of type Deleted. + PostStageDeleted ) -var ErrInvalidUserStatus = fmt.Errorf("not a valid UserStatus, try [%s]", strings.Join(_UserStatusNames, ", ")) +var ErrInvalidPostStage = fmt.Errorf("not a valid PostStage, try [%s]", strings.Join(_PostStageNames, ", ")) -const _UserStatusName = "PendingVerifiedBlocked" +const _PostStageName = "PendingProcessingCompletedDeleted" -var _UserStatusNames = []string{ - _UserStatusName[0:7], - _UserStatusName[7:15], - _UserStatusName[15:22], +var _PostStageNames = []string{ + _PostStageName[0:7], + _PostStageName[7:17], + _PostStageName[17:26], + _PostStageName[26:33], } -// UserStatusNames returns a list of possible string values of UserStatus. -func UserStatusNames() []string { - tmp := make([]string, len(_UserStatusNames)) - copy(tmp, _UserStatusNames) +// PostStageNames returns a list of possible string values of PostStage. +func PostStageNames() []string { + tmp := make([]string, len(_PostStageNames)) + copy(tmp, _PostStageNames) return tmp } -// UserStatusValues returns a list of the values for UserStatus -func UserStatusValues() []UserStatus { - return []UserStatus{ - UserStatusPending, - UserStatusVerified, - UserStatusBlocked, +// PostStageValues returns a list of the values for PostStage +func PostStageValues() []PostStage { + return []PostStage{ + PostStagePending, + PostStageProcessing, + PostStageCompleted, + PostStageDeleted, } } -var _UserStatusMap = map[UserStatus]string{ - UserStatusPending: _UserStatusName[0:7], - UserStatusVerified: _UserStatusName[7:15], - UserStatusBlocked: _UserStatusName[15:22], +var _PostStageMap = map[PostStage]string{ + PostStagePending: _PostStageName[0:7], + PostStageProcessing: _PostStageName[7:17], + PostStageCompleted: _PostStageName[17:26], + PostStageDeleted: _PostStageName[26:33], } // String implements the Stringer interface. -func (x UserStatus) String() string { - if str, ok := _UserStatusMap[x]; ok { +func (x PostStage) String() string { + if str, ok := _PostStageMap[x]; ok { return str } - return fmt.Sprintf("UserStatus(%d)", x) + return fmt.Sprintf("PostStage(%d)", x) } // IsValid provides a quick way to determine if the typed value is // part of the allowed enumerated values -func (x UserStatus) IsValid() bool { - _, ok := _UserStatusMap[x] +func (x PostStage) IsValid() bool { + _, ok := _PostStageMap[x] return ok } -var _UserStatusValue = map[string]UserStatus{ - _UserStatusName[0:7]: UserStatusPending, - _UserStatusName[7:15]: UserStatusVerified, - _UserStatusName[15:22]: UserStatusBlocked, +var _PostStageValue = map[string]PostStage{ + _PostStageName[0:7]: PostStagePending, + _PostStageName[7:17]: PostStageProcessing, + _PostStageName[17:26]: PostStageCompleted, + _PostStageName[26:33]: PostStageDeleted, } -// ParseUserStatus attempts to convert a string to a UserStatus. -func ParseUserStatus(name string) (UserStatus, error) { - if x, ok := _UserStatusValue[name]; ok { +// ParsePostStage attempts to convert a string to a PostStage. +func ParsePostStage(name string) (PostStage, error) { + if x, ok := _PostStageValue[name]; ok { return x, nil } - return UserStatus(0), fmt.Errorf("%s is %w", name, ErrInvalidUserStatus) + return PostStage(0), fmt.Errorf("%s is %w", name, ErrInvalidPostStage) } -var errUserStatusNilPtr = errors.New("value pointer is nil") // one per type for package clashes +var errPostStageNilPtr = errors.New("value pointer is nil") // one per type for package clashes // Scan implements the Scanner interface. -func (x *UserStatus) Scan(value interface{}) (err error) { +func (x *PostStage) Scan(value interface{}) (err error) { if value == nil { - *x = UserStatus(0) + *x = PostStage(0) return } @@ -97,72 +103,72 @@ func (x *UserStatus) Scan(value interface{}) (err error) { // driver.Value values at the top of the list for expediency switch v := value.(type) { case int64: - *x = UserStatus(v) + *x = PostStage(v) case string: - *x, err = ParseUserStatus(v) + *x, err = ParsePostStage(v) if err != nil { // try parsing the integer value as a string if val, verr := strconv.Atoi(v); verr == nil { - *x, err = UserStatus(val), nil + *x, err = PostStage(val), nil } } case []byte: - *x, err = ParseUserStatus(string(v)) + *x, err = ParsePostStage(string(v)) if err != nil { // try parsing the integer value as a string if val, verr := strconv.Atoi(string(v)); verr == nil { - *x, err = UserStatus(val), nil + *x, err = PostStage(val), nil } } - case UserStatus: + case PostStage: *x = v case int: - *x = UserStatus(v) - case *UserStatus: + *x = PostStage(v) + case *PostStage: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } *x = *v case uint: - *x = UserStatus(v) + *x = PostStage(v) case uint64: - *x = UserStatus(v) + *x = PostStage(v) case *int: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x = UserStatus(*v) + *x = PostStage(*v) case *int64: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x = UserStatus(*v) + *x = PostStage(*v) case float64: // json marshals everything as a float64 if it's a number - *x = UserStatus(v) + *x = PostStage(v) case *float64: // json marshals everything as a float64 if it's a number if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x = UserStatus(*v) + *x = PostStage(*v) case *uint: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x = UserStatus(*v) + *x = PostStage(*v) case *uint64: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x = UserStatus(*v) + *x = PostStage(*v) case *string: if v == nil { - return errUserStatusNilPtr + return errPostStageNilPtr } - *x, err = ParseUserStatus(*v) + *x, err = ParsePostStage(*v) if err != nil { // try parsing the integer value as a string if val, verr := strconv.Atoi(*v); verr == nil { - *x, err = UserStatus(val), nil + *x, err = PostStage(val), nil } } } @@ -171,71 +177,529 @@ func (x *UserStatus) Scan(value interface{}) (err error) { } // Value implements the driver Valuer interface. -func (x UserStatus) Value() (driver.Value, error) { +func (x PostStage) Value() (driver.Value, error) { return int64(x), nil } // Set implements the Golang flag.Value interface func. -func (x *UserStatus) Set(val string) error { - v, err := ParseUserStatus(val) +func (x *PostStage) Set(val string) error { + v, err := ParsePostStage(val) *x = v return err } // Get implements the Golang flag.Getter interface func. -func (x *UserStatus) Get() interface{} { +func (x *PostStage) Get() interface{} { return *x } // Type implements the github.com/spf13/pFlag Value interface. -func (x *UserStatus) Type() string { - return "UserStatus" +func (x *PostStage) Type() string { + return "PostStage" } -type NullUserStatus struct { - UserStatus UserStatus - Valid bool +type NullPostStage struct { + PostStage PostStage + Valid bool } -func NewNullUserStatus(val interface{}) (x NullUserStatus) { +func NewNullPostStage(val interface{}) (x NullPostStage) { x.Scan(val) // yes, we ignore this error, it will just be an invalid value. return } // Scan implements the Scanner interface. -func (x *NullUserStatus) Scan(value interface{}) (err error) { +func (x *NullPostStage) Scan(value interface{}) (err error) { if value == nil { - x.UserStatus, x.Valid = UserStatus(0), false + x.PostStage, x.Valid = PostStage(0), false return } - err = x.UserStatus.Scan(value) + err = x.PostStage.Scan(value) x.Valid = (err == nil) return } // Value implements the driver Valuer interface. -func (x NullUserStatus) Value() (driver.Value, error) { +func (x NullPostStage) Value() (driver.Value, error) { if !x.Valid { return nil, nil } // driver.Value accepts int64 for int values. - return int64(x.UserStatus), nil + return int64(x.PostStage), nil } -type NullUserStatusStr struct { - NullUserStatus +type NullPostStageStr struct { + NullPostStage } -func NewNullUserStatusStr(val interface{}) (x NullUserStatusStr) { +func NewNullPostStageStr(val interface{}) (x NullPostStageStr) { x.Scan(val) // yes, we ignore this error, it will just be an invalid value. return } // Value implements the driver Valuer interface. -func (x NullUserStatusStr) Value() (driver.Value, error) { +func (x NullPostStageStr) Value() (driver.Value, error) { if !x.Valid { return nil, nil } - return x.UserStatus.String(), nil + return x.PostStage.String(), nil +} + +const ( + // PostStatusPending is a PostStatus of type Pending. + PostStatusPending PostStatus = iota + // PostStatusVerified is a PostStatus of type Verified. + PostStatusVerified + // PostStatusBlocked is a PostStatus of type Blocked. + PostStatusBlocked +) + +var ErrInvalidPostStatus = fmt.Errorf("not a valid PostStatus, try [%s]", strings.Join(_PostStatusNames, ", ")) + +const _PostStatusName = "PendingVerifiedBlocked" + +var _PostStatusNames = []string{ + _PostStatusName[0:7], + _PostStatusName[7:15], + _PostStatusName[15:22], +} + +// PostStatusNames returns a list of possible string values of PostStatus. +func PostStatusNames() []string { + tmp := make([]string, len(_PostStatusNames)) + copy(tmp, _PostStatusNames) + return tmp +} + +// PostStatusValues returns a list of the values for PostStatus +func PostStatusValues() []PostStatus { + return []PostStatus{ + PostStatusPending, + PostStatusVerified, + PostStatusBlocked, + } +} + +var _PostStatusMap = map[PostStatus]string{ + PostStatusPending: _PostStatusName[0:7], + PostStatusVerified: _PostStatusName[7:15], + PostStatusBlocked: _PostStatusName[15:22], +} + +// String implements the Stringer interface. +func (x PostStatus) String() string { + if str, ok := _PostStatusMap[x]; ok { + return str + } + return fmt.Sprintf("PostStatus(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x PostStatus) IsValid() bool { + _, ok := _PostStatusMap[x] + return ok +} + +var _PostStatusValue = map[string]PostStatus{ + _PostStatusName[0:7]: PostStatusPending, + _PostStatusName[7:15]: PostStatusVerified, + _PostStatusName[15:22]: PostStatusBlocked, +} + +// ParsePostStatus attempts to convert a string to a PostStatus. +func ParsePostStatus(name string) (PostStatus, error) { + if x, ok := _PostStatusValue[name]; ok { + return x, nil + } + return PostStatus(0), fmt.Errorf("%s is %w", name, ErrInvalidPostStatus) +} + +var errPostStatusNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *PostStatus) Scan(value interface{}) (err error) { + if value == nil { + *x = PostStatus(0) + return + } + + // A wider range of scannable types. + // driver.Value values at the top of the list for expediency + switch v := value.(type) { + case int64: + *x = PostStatus(v) + case string: + *x, err = ParsePostStatus(v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(v); verr == nil { + *x, err = PostStatus(val), nil + } + } + case []byte: + *x, err = ParsePostStatus(string(v)) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(string(v)); verr == nil { + *x, err = PostStatus(val), nil + } + } + case PostStatus: + *x = v + case int: + *x = PostStatus(v) + case *PostStatus: + if v == nil { + return errPostStatusNilPtr + } + *x = *v + case uint: + *x = PostStatus(v) + case uint64: + *x = PostStatus(v) + case *int: + if v == nil { + return errPostStatusNilPtr + } + *x = PostStatus(*v) + case *int64: + if v == nil { + return errPostStatusNilPtr + } + *x = PostStatus(*v) + case float64: // json marshals everything as a float64 if it's a number + *x = PostStatus(v) + case *float64: // json marshals everything as a float64 if it's a number + if v == nil { + return errPostStatusNilPtr + } + *x = PostStatus(*v) + case *uint: + if v == nil { + return errPostStatusNilPtr + } + *x = PostStatus(*v) + case *uint64: + if v == nil { + return errPostStatusNilPtr + } + *x = PostStatus(*v) + case *string: + if v == nil { + return errPostStatusNilPtr + } + *x, err = ParsePostStatus(*v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(*v); verr == nil { + *x, err = PostStatus(val), nil + } + } + } + + return +} + +// Value implements the driver Valuer interface. +func (x PostStatus) Value() (driver.Value, error) { + return int64(x), nil +} + +// Set implements the Golang flag.Value interface func. +func (x *PostStatus) Set(val string) error { + v, err := ParsePostStatus(val) + *x = v + return err +} + +// Get implements the Golang flag.Getter interface func. +func (x *PostStatus) Get() interface{} { + return *x +} + +// Type implements the github.com/spf13/pFlag Value interface. +func (x *PostStatus) Type() string { + return "PostStatus" +} + +type NullPostStatus struct { + PostStatus PostStatus + Valid bool +} + +func NewNullPostStatus(val interface{}) (x NullPostStatus) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Scan implements the Scanner interface. +func (x *NullPostStatus) Scan(value interface{}) (err error) { + if value == nil { + x.PostStatus, x.Valid = PostStatus(0), false + return + } + + err = x.PostStatus.Scan(value) + x.Valid = (err == nil) + return +} + +// Value implements the driver Valuer interface. +func (x NullPostStatus) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + // driver.Value accepts int64 for int values. + return int64(x.PostStatus), nil +} + +type NullPostStatusStr struct { + NullPostStatus +} + +func NewNullPostStatusStr(val interface{}) (x NullPostStatusStr) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Value implements the driver Valuer interface. +func (x NullPostStatusStr) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + return x.PostStatus.String(), nil +} + +const ( + // PostTypeArticle is a PostType of type Article. + PostTypeArticle PostType = iota + // PostTypePicture is a PostType of type Picture. + PostTypePicture + // PostTypeVideo is a PostType of type Video. + PostTypeVideo + // PostTypeAudio is a PostType of type Audio. + PostTypeAudio +) + +var ErrInvalidPostType = fmt.Errorf("not a valid PostType, try [%s]", strings.Join(_PostTypeNames, ", ")) + +const _PostTypeName = "ArticlePictureVideoAudio" + +var _PostTypeNames = []string{ + _PostTypeName[0:7], + _PostTypeName[7:14], + _PostTypeName[14:19], + _PostTypeName[19:24], +} + +// PostTypeNames returns a list of possible string values of PostType. +func PostTypeNames() []string { + tmp := make([]string, len(_PostTypeNames)) + copy(tmp, _PostTypeNames) + return tmp +} + +// PostTypeValues returns a list of the values for PostType +func PostTypeValues() []PostType { + return []PostType{ + PostTypeArticle, + PostTypePicture, + PostTypeVideo, + PostTypeAudio, + } +} + +var _PostTypeMap = map[PostType]string{ + PostTypeArticle: _PostTypeName[0:7], + PostTypePicture: _PostTypeName[7:14], + PostTypeVideo: _PostTypeName[14:19], + PostTypeAudio: _PostTypeName[19:24], +} + +// String implements the Stringer interface. +func (x PostType) String() string { + if str, ok := _PostTypeMap[x]; ok { + return str + } + return fmt.Sprintf("PostType(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x PostType) IsValid() bool { + _, ok := _PostTypeMap[x] + return ok +} + +var _PostTypeValue = map[string]PostType{ + _PostTypeName[0:7]: PostTypeArticle, + _PostTypeName[7:14]: PostTypePicture, + _PostTypeName[14:19]: PostTypeVideo, + _PostTypeName[19:24]: PostTypeAudio, +} + +// ParsePostType attempts to convert a string to a PostType. +func ParsePostType(name string) (PostType, error) { + if x, ok := _PostTypeValue[name]; ok { + return x, nil + } + return PostType(0), fmt.Errorf("%s is %w", name, ErrInvalidPostType) +} + +var errPostTypeNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *PostType) Scan(value interface{}) (err error) { + if value == nil { + *x = PostType(0) + return + } + + // A wider range of scannable types. + // driver.Value values at the top of the list for expediency + switch v := value.(type) { + case int64: + *x = PostType(v) + case string: + *x, err = ParsePostType(v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(v); verr == nil { + *x, err = PostType(val), nil + } + } + case []byte: + *x, err = ParsePostType(string(v)) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(string(v)); verr == nil { + *x, err = PostType(val), nil + } + } + case PostType: + *x = v + case int: + *x = PostType(v) + case *PostType: + if v == nil { + return errPostTypeNilPtr + } + *x = *v + case uint: + *x = PostType(v) + case uint64: + *x = PostType(v) + case *int: + if v == nil { + return errPostTypeNilPtr + } + *x = PostType(*v) + case *int64: + if v == nil { + return errPostTypeNilPtr + } + *x = PostType(*v) + case float64: // json marshals everything as a float64 if it's a number + *x = PostType(v) + case *float64: // json marshals everything as a float64 if it's a number + if v == nil { + return errPostTypeNilPtr + } + *x = PostType(*v) + case *uint: + if v == nil { + return errPostTypeNilPtr + } + *x = PostType(*v) + case *uint64: + if v == nil { + return errPostTypeNilPtr + } + *x = PostType(*v) + case *string: + if v == nil { + return errPostTypeNilPtr + } + *x, err = ParsePostType(*v) + if err != nil { + // try parsing the integer value as a string + if val, verr := strconv.Atoi(*v); verr == nil { + *x, err = PostType(val), nil + } + } + } + + return +} + +// Value implements the driver Valuer interface. +func (x PostType) Value() (driver.Value, error) { + return int64(x), nil +} + +// Set implements the Golang flag.Value interface func. +func (x *PostType) Set(val string) error { + v, err := ParsePostType(val) + *x = v + return err +} + +// Get implements the Golang flag.Getter interface func. +func (x *PostType) Get() interface{} { + return *x +} + +// Type implements the github.com/spf13/pFlag Value interface. +func (x *PostType) Type() string { + return "PostType" +} + +type NullPostType struct { + PostType PostType + Valid bool +} + +func NewNullPostType(val interface{}) (x NullPostType) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Scan implements the Scanner interface. +func (x *NullPostType) Scan(value interface{}) (err error) { + if value == nil { + x.PostType, x.Valid = PostType(0), false + return + } + + err = x.PostType.Scan(value) + x.Valid = (err == nil) + return +} + +// Value implements the driver Valuer interface. +func (x NullPostType) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + // driver.Value accepts int64 for int values. + return int64(x.PostType), nil +} + +type NullPostTypeStr struct { + NullPostType +} + +func NewNullPostTypeStr(val interface{}) (x NullPostTypeStr) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Value implements the driver Valuer interface. +func (x NullPostTypeStr) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + return x.PostType.String(), nil } diff --git a/backend/database/fields/users.go b/backend/database/fields/users.go index 5c4cb28..990705a 100644 --- a/backend/database/fields/users.go +++ b/backend/database/fields/users.go @@ -1,5 +1,13 @@ package fields -// swagger:enum UserStatus +// swagger:enum PostStage +// ENUM( Pending, Processing, Completed, Deleted) +type PostStage int16 + +// swagger:enum PostStatus // ENUM( Pending, Verified, Blocked) -type UserStatus int16 +type PostStatus int16 + +// swagger:enum PostType +// ENUM( Article, Picture, Video, Audio) +type PostType int16 diff --git a/backend/database/migrations/20250109095933_create_post.sql b/backend/database/migrations/20250109095933_create_post.sql index f396abc..c6e2e61 100644 --- a/backend/database/migrations/20250109095933_create_post.sql +++ b/backend/database/migrations/20250109095933_create_post.sql @@ -8,16 +8,17 @@ CREATE TABLE updated_at timestamp NOT NULL default now(), deleted_at timestamp, + type INT2 NOT NULL default 0, + stage INT2 NOT NULL default 0, + status INT2 NOT NULL default 0, + tenant_id INT8 NOT NULL, user_id INT8 NOT NULL, - hash VARCHAR(128) NOT NULL UNIQUE, title VARCHAR(128) NOT NULL, description VARCHAR(256) NOT NULL, - poster VARCHAR(128) NOT NULL, + poster_asset_id INT8 NOT NULL, content TEXT NOT NULL, - stage INT2 NOT NULL default 0, - status INT2 NOT NULL default 0, price INT8 NOT NULL default 0, discount INT2 NOT NULL default 100, views INT8 NOT NULL default 0, @@ -26,6 +27,7 @@ CREATE TABLE assets jsonb default '{}'::jsonb ); -- create indexes +CREATE INDEX posts_type_index ON posts (type); CREATE INDEX posts_tenant_id_index ON posts (tenant_id); CREATE INDEX posts_user_id_index ON posts (user_id); CREATE INDEX posts_title_index ON posts (title); @@ -55,4 +57,5 @@ CREATE INDEX user_bought_posts_post_id_index ON user_bought_posts (post_id); -- +goose Down -- +goose StatementBegin DROP TABLE posts; +DROP TABLE user_bought_posts; -- +goose StatementEnd diff --git a/backend/database/models/qvyun_v2/public/model/posts.go b/backend/database/models/qvyun_v2/public/model/posts.go index 80cc76c..2e09b23 100644 --- a/backend/database/models/qvyun_v2/public/model/posts.go +++ b/backend/database/models/qvyun_v2/public/model/posts.go @@ -8,27 +8,28 @@ package model import ( + "backend/database/fields" "time" ) type Posts struct { - ID int64 `sql:"primary_key" json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt *time.Time `json:"deleted_at"` - TenantID int64 `json:"tenant_id"` - UserID int64 `json:"user_id"` - Hash string `json:"hash"` - Title string `json:"title"` - Description string `json:"description"` - Poster string `json:"poster"` - Content string `json:"content"` - Stage int16 `json:"stage"` - Status int16 `json:"status"` - Price int64 `json:"price"` - Discount int16 `json:"discount"` - Views int64 `json:"views"` - Likes int64 `json:"likes"` - Meta *string `json:"meta"` - Assets *string `json:"assets"` + ID int64 `sql:"primary_key" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at"` + Type fields.PostType `json:"type"` + Stage fields.PostStage `json:"stage"` + Status fields.PostStatus `json:"status"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Title string `json:"title"` + Description string `json:"description"` + PosterAssetID int64 `json:"poster_asset_id"` + Content string `json:"content"` + Price int64 `json:"price"` + Discount int16 `json:"discount"` + Views int64 `json:"views"` + Likes int64 `json:"likes"` + Meta *string `json:"meta"` + Assets *string `json:"assets"` } diff --git a/backend/database/models/qvyun_v2/public/table/posts.go b/backend/database/models/qvyun_v2/public/table/posts.go index e59f31f..1b3f722 100644 --- a/backend/database/models/qvyun_v2/public/table/posts.go +++ b/backend/database/models/qvyun_v2/public/table/posts.go @@ -17,25 +17,25 @@ type postsTable struct { postgres.Table // Columns - ID postgres.ColumnInteger - CreatedAt postgres.ColumnTimestamp - UpdatedAt postgres.ColumnTimestamp - DeletedAt postgres.ColumnTimestamp - TenantID postgres.ColumnInteger - UserID postgres.ColumnInteger - Hash postgres.ColumnString - Title postgres.ColumnString - Description postgres.ColumnString - Poster postgres.ColumnString - Content postgres.ColumnString - Stage postgres.ColumnInteger - Status postgres.ColumnInteger - Price postgres.ColumnInteger - Discount postgres.ColumnInteger - Views postgres.ColumnInteger - Likes postgres.ColumnInteger - Meta postgres.ColumnString - Assets postgres.ColumnString + ID postgres.ColumnInteger + CreatedAt postgres.ColumnTimestamp + UpdatedAt postgres.ColumnTimestamp + DeletedAt postgres.ColumnTimestamp + Type postgres.ColumnInteger + Stage postgres.ColumnInteger + Status postgres.ColumnInteger + TenantID postgres.ColumnInteger + UserID postgres.ColumnInteger + Title postgres.ColumnString + Description postgres.ColumnString + PosterAssetID postgres.ColumnInteger + Content postgres.ColumnString + Price postgres.ColumnInteger + Discount postgres.ColumnInteger + Views postgres.ColumnInteger + Likes postgres.ColumnInteger + Meta postgres.ColumnString + Assets postgres.ColumnString AllColumns postgres.ColumnList MutableColumns postgres.ColumnList @@ -76,52 +76,52 @@ func newPostsTable(schemaName, tableName, alias string) *PostsTable { func newPostsTableImpl(schemaName, tableName, alias string) postsTable { var ( - IDColumn = postgres.IntegerColumn("id") - CreatedAtColumn = postgres.TimestampColumn("created_at") - UpdatedAtColumn = postgres.TimestampColumn("updated_at") - DeletedAtColumn = postgres.TimestampColumn("deleted_at") - TenantIDColumn = postgres.IntegerColumn("tenant_id") - UserIDColumn = postgres.IntegerColumn("user_id") - HashColumn = postgres.StringColumn("hash") - TitleColumn = postgres.StringColumn("title") - DescriptionColumn = postgres.StringColumn("description") - PosterColumn = postgres.StringColumn("poster") - ContentColumn = postgres.StringColumn("content") - StageColumn = postgres.IntegerColumn("stage") - StatusColumn = postgres.IntegerColumn("status") - PriceColumn = postgres.IntegerColumn("price") - DiscountColumn = postgres.IntegerColumn("discount") - ViewsColumn = postgres.IntegerColumn("views") - LikesColumn = postgres.IntegerColumn("likes") - MetaColumn = postgres.StringColumn("meta") - AssetsColumn = postgres.StringColumn("assets") - allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, UpdatedAtColumn, DeletedAtColumn, TenantIDColumn, UserIDColumn, HashColumn, TitleColumn, DescriptionColumn, PosterColumn, ContentColumn, StageColumn, StatusColumn, PriceColumn, DiscountColumn, ViewsColumn, LikesColumn, MetaColumn, AssetsColumn} - mutableColumns = postgres.ColumnList{CreatedAtColumn, UpdatedAtColumn, DeletedAtColumn, TenantIDColumn, UserIDColumn, HashColumn, TitleColumn, DescriptionColumn, PosterColumn, ContentColumn, StageColumn, StatusColumn, PriceColumn, DiscountColumn, ViewsColumn, LikesColumn, MetaColumn, AssetsColumn} + IDColumn = postgres.IntegerColumn("id") + CreatedAtColumn = postgres.TimestampColumn("created_at") + UpdatedAtColumn = postgres.TimestampColumn("updated_at") + DeletedAtColumn = postgres.TimestampColumn("deleted_at") + TypeColumn = postgres.IntegerColumn("type") + StageColumn = postgres.IntegerColumn("stage") + StatusColumn = postgres.IntegerColumn("status") + TenantIDColumn = postgres.IntegerColumn("tenant_id") + UserIDColumn = postgres.IntegerColumn("user_id") + TitleColumn = postgres.StringColumn("title") + DescriptionColumn = postgres.StringColumn("description") + PosterAssetIDColumn = postgres.IntegerColumn("poster_asset_id") + ContentColumn = postgres.StringColumn("content") + PriceColumn = postgres.IntegerColumn("price") + DiscountColumn = postgres.IntegerColumn("discount") + ViewsColumn = postgres.IntegerColumn("views") + LikesColumn = postgres.IntegerColumn("likes") + MetaColumn = postgres.StringColumn("meta") + AssetsColumn = postgres.StringColumn("assets") + allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, UpdatedAtColumn, DeletedAtColumn, TypeColumn, StageColumn, StatusColumn, TenantIDColumn, UserIDColumn, TitleColumn, DescriptionColumn, PosterAssetIDColumn, ContentColumn, PriceColumn, DiscountColumn, ViewsColumn, LikesColumn, MetaColumn, AssetsColumn} + mutableColumns = postgres.ColumnList{CreatedAtColumn, UpdatedAtColumn, DeletedAtColumn, TypeColumn, StageColumn, StatusColumn, TenantIDColumn, UserIDColumn, TitleColumn, DescriptionColumn, PosterAssetIDColumn, ContentColumn, PriceColumn, DiscountColumn, ViewsColumn, LikesColumn, MetaColumn, AssetsColumn} ) return postsTable{ Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), //Columns - ID: IDColumn, - CreatedAt: CreatedAtColumn, - UpdatedAt: UpdatedAtColumn, - DeletedAt: DeletedAtColumn, - TenantID: TenantIDColumn, - UserID: UserIDColumn, - Hash: HashColumn, - Title: TitleColumn, - Description: DescriptionColumn, - Poster: PosterColumn, - Content: ContentColumn, - Stage: StageColumn, - Status: StatusColumn, - Price: PriceColumn, - Discount: DiscountColumn, - Views: ViewsColumn, - Likes: LikesColumn, - Meta: MetaColumn, - Assets: AssetsColumn, + ID: IDColumn, + CreatedAt: CreatedAtColumn, + UpdatedAt: UpdatedAtColumn, + DeletedAt: DeletedAtColumn, + Type: TypeColumn, + Stage: StageColumn, + Status: StatusColumn, + TenantID: TenantIDColumn, + UserID: UserIDColumn, + Title: TitleColumn, + Description: DescriptionColumn, + PosterAssetID: PosterAssetIDColumn, + Content: ContentColumn, + Price: PriceColumn, + Discount: DiscountColumn, + Views: ViewsColumn, + Likes: LikesColumn, + Meta: MetaColumn, + Assets: AssetsColumn, AllColumns: allColumns, MutableColumns: mutableColumns, diff --git a/backend/database/transform.yaml b/backend/database/transform.yaml index 38a5fa1..4c49bbd 100644 --- a/backend/database/transform.yaml +++ b/backend/database/transform.yaml @@ -15,6 +15,11 @@ types: storages: type: StorageType + posts: + stage: PostStage + status: PostStatus + type: PostType + orders: type: OrderType status: OrderStatus