From dbe1e19be2068fd871bbcb996c8099b21c04b2f4 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 15 Jan 2025 20:31:21 +0800 Subject: [PATCH] remove: tenantslug --- backend/app/events/publishers/post_created.go | 2 +- backend/app/events/publishers/post_deleted.go | 23 +++ backend/app/events/publishers/post_updated.go | 23 +++ .../app/events/publishers/user_register.go | 2 +- .../app/events/subscribers/post_created.go | 8 +- .../app/events/subscribers/post_deleted.go | 60 ++++++ .../app/events/subscribers/post_updated.go | 48 +++++ .../app/events/subscribers/provider.gen.go | 51 +++++ .../app/events/subscribers/user_register.go | 4 +- backend/app/events/topics.gen.go | 179 ------------------ backend/app/events/topics.go | 16 +- backend/app/events/user_register.go | 27 --- backend/app/http/medias/controller.go | 10 +- backend/app/http/medias/routes.gen.go | 3 +- backend/app/http/orders/controller_order.go | 15 +- backend/app/http/orders/routes.gen.go | 3 +- backend/app/http/posts/controller.go | 123 ++++++++---- backend/app/http/posts/provider.gen.go | 14 +- backend/app/http/posts/routes.gen.go | 23 ++- backend/app/http/posts/service.go | 97 +++++++++- backend/app/jobs/provider.gen.go | 24 +++ backend/database/fields/posts.go | 4 + backend/database/transform.yaml | 1 + backend/providers/hashids/hashids.go | 62 +++++- 24 files changed, 521 insertions(+), 301 deletions(-) create mode 100644 backend/app/events/publishers/post_deleted.go create mode 100644 backend/app/events/publishers/post_updated.go create mode 100644 backend/app/events/subscribers/post_deleted.go create mode 100644 backend/app/events/subscribers/post_updated.go delete mode 100644 backend/app/events/topics.gen.go delete mode 100644 backend/app/events/user_register.go diff --git a/backend/app/events/publishers/post_created.go b/backend/app/events/publishers/post_created.go index 6077a47..600958a 100644 --- a/backend/app/events/publishers/post_created.go +++ b/backend/app/events/publishers/post_created.go @@ -19,5 +19,5 @@ func (e *PostCreated) Marshal() ([]byte, error) { } func (e *PostCreated) Topic() string { - return events.TopicPostCreated.String() + return events.TopicPostCreated } diff --git a/backend/app/events/publishers/post_deleted.go b/backend/app/events/publishers/post_deleted.go new file mode 100644 index 0000000..3ceeba1 --- /dev/null +++ b/backend/app/events/publishers/post_deleted.go @@ -0,0 +1,23 @@ +package publishers + +import ( + "encoding/json" + + "backend/app/events" + + "git.ipao.vip/rogeecn/atom/contracts" +) + +var _ contracts.EventPublisher = (*UserRegister)(nil) + +type PostDeleted struct { + ID int64 `json:"id"` +} + +func (e *PostDeleted) Marshal() ([]byte, error) { + return json.Marshal(e) +} + +func (e *PostDeleted) Topic() string { + return events.TopicPostDeleted +} diff --git a/backend/app/events/publishers/post_updated.go b/backend/app/events/publishers/post_updated.go new file mode 100644 index 0000000..2fb375a --- /dev/null +++ b/backend/app/events/publishers/post_updated.go @@ -0,0 +1,23 @@ +package publishers + +import ( + "encoding/json" + + "backend/app/events" + + "git.ipao.vip/rogeecn/atom/contracts" +) + +var _ contracts.EventPublisher = (*PostUpdatedEvent)(nil) + +type PostUpdatedEvent struct { + ID int64 `json:"id"` +} + +func (e *PostUpdatedEvent) Marshal() ([]byte, error) { + return json.Marshal(e) +} + +func (e *PostUpdatedEvent) Topic() string { + return events.TopicPostUpdated +} diff --git a/backend/app/events/publishers/user_register.go b/backend/app/events/publishers/user_register.go index b0f487d..77d7d78 100644 --- a/backend/app/events/publishers/user_register.go +++ b/backend/app/events/publishers/user_register.go @@ -19,5 +19,5 @@ func (e *UserRegister) Marshal() ([]byte, error) { } func (e *UserRegister) Topic() string { - return events.TopicUserRegister.String() + return events.TopicUserRegister } diff --git a/backend/app/events/subscribers/post_created.go b/backend/app/events/subscribers/post_created.go index 06cfe2b..0f885cc 100644 --- a/backend/app/events/subscribers/post_created.go +++ b/backend/app/events/subscribers/post_created.go @@ -29,18 +29,18 @@ type PostCreated struct { } func (e *PostCreated) Prepare() error { - e.log = logrus.WithField("module", "events.subscribers.user_register") + e.log = logrus.WithField("module", "events.subscribers.post_created") return nil } // PublishToTopic implements contracts.EventHandler. func (e *PostCreated) PublishToTopic() string { - return events.TopicProcessed.String() + return events.TopicProcessed } // Topic implements contracts.EventHandler. func (e *PostCreated) Topic() string { - return events.TopicPostCreated.String() + return events.TopicPostCreated } // Handler implements contracts.EventHandler. @@ -66,8 +66,6 @@ func (e *PostCreated) Handler(msg *message.Message) ([]*message.Message, error) return nil, nil } - _ = video - job, err := e.job.Client() if err != nil { return nil, err diff --git a/backend/app/events/subscribers/post_deleted.go b/backend/app/events/subscribers/post_deleted.go new file mode 100644 index 0000000..b1c8ce6 --- /dev/null +++ b/backend/app/events/subscribers/post_deleted.go @@ -0,0 +1,60 @@ +package subscribers + +import ( + "context" + "encoding/json" + + "backend/app/events" + "backend/app/events/publishers" + "backend/app/http/posts" + "backend/providers/job" + + "git.ipao.vip/rogeecn/atom/contracts" + "github.com/ThreeDotsLabs/watermill/message" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var _ contracts.EventHandler = (*PostDeleted)(nil) + +// @provider(event) +type PostDeleted struct { + log *logrus.Entry `inject:"false"` + + postSvc *posts.Service + job *job.Job +} + +func (e *PostDeleted) Prepare() error { + e.log = logrus.WithField("module", "events.subscribers.post_deleted") + return nil +} + +// PublishToTopic implements contracts.EventHandler. +func (e *PostDeleted) PublishToTopic() string { + return events.TopicProcessed +} + +// Topic implements contracts.EventHandler. +func (e *PostDeleted) Topic() string { + return events.TopicPostDeleted +} + +// Handler implements contracts.EventHandler. +func (e *PostDeleted) Handler(msg *message.Message) ([]*message.Message, error) { + var payload publishers.PostDeleted + err := json.Unmarshal(msg.Payload, &payload) + if err != nil { + return nil, err + } + e.log.Infof("received event %s", msg.Payload) + + post, err := e.postSvc.ForceGetPostByID(context.Background(), payload.ID) + if err != nil { + return nil, errors.Wrapf(err, "failed to get item by id: %d", payload.ID) + } + _ = post + // TODO: handle post deletion + + return nil, nil +} diff --git a/backend/app/events/subscribers/post_updated.go b/backend/app/events/subscribers/post_updated.go new file mode 100644 index 0000000..3f43d9a --- /dev/null +++ b/backend/app/events/subscribers/post_updated.go @@ -0,0 +1,48 @@ +package subscribers + +import ( + "encoding/json" + + "backend/app/events" + "backend/app/events/publishers" + + "git.ipao.vip/rogeecn/atom/contracts" + "github.com/ThreeDotsLabs/watermill/message" + "github.com/sirupsen/logrus" +) + +var _ contracts.EventHandler = (*PostUpdatedSubscriber)(nil) + +// @provider(event) +type PostUpdatedSubscriber struct { + log *logrus.Entry `inject:"false"` +} + +func (e *PostUpdatedSubscriber) Prepare() error { + e.log = logrus.WithField("module", "events.subscribers.PostUpdatedSubscriber") + return nil +} + +// PublishToTopic implements contracts.EventHandler. +func (e *PostUpdatedSubscriber) PublishToTopic() string { + return events.TopicProcessed +} + +// Topic implements contracts.EventHandler. +func (e *PostUpdatedSubscriber) Topic() string { + return events.TopicPostUpdated +} + +// Handler implements contracts.EventHandler. +func (e *PostUpdatedSubscriber) Handler(msg *message.Message) ([]*message.Message, error) { + var payload publishers.PostUpdatedEvent + err := json.Unmarshal(msg.Payload, &payload) + if err != nil { + return nil, err + } + e.log.Infof("received event %s", msg.Payload) + + // TODO: handle post deletion + + return nil, nil +} diff --git a/backend/app/events/subscribers/provider.gen.go b/backend/app/events/subscribers/provider.gen.go index 59e5f12..f9899d6 100755 --- a/backend/app/events/subscribers/provider.gen.go +++ b/backend/app/events/subscribers/provider.gen.go @@ -1,8 +1,10 @@ package subscribers import ( + "backend/app/http/posts" "backend/app/http/users" "backend/providers/event" + "backend/providers/job" "git.ipao.vip/rogeecn/atom" "git.ipao.vip/rogeecn/atom/container" @@ -11,6 +13,55 @@ import ( ) func Provide(opts ...opt.Option) error { + if err := container.Container.Provide(func( + __event *event.PubSub, + job *job.Job, + postSvc *posts.Service, + ) (contracts.Initial, error) { + obj := &PostCreated{ + job: job, + postSvc: postSvc, + } + if err := obj.Prepare(); err != nil { + return nil, err + } + __event.Handle("handler:PostCreated", obj.Topic(), obj.PublishToTopic(), obj.Handler) + + return obj, nil + }, atom.GroupInitial); err != nil { + return err + } + if err := container.Container.Provide(func( + __event *event.PubSub, + job *job.Job, + postSvc *posts.Service, + ) (contracts.Initial, error) { + obj := &PostDeleted{ + job: job, + postSvc: postSvc, + } + if err := obj.Prepare(); err != nil { + return nil, err + } + __event.Handle("handler:PostDeleted", obj.Topic(), obj.PublishToTopic(), obj.Handler) + + return obj, nil + }, atom.GroupInitial); err != nil { + return err + } + if err := container.Container.Provide(func( + __event *event.PubSub, + ) (contracts.Initial, error) { + obj := &PostUpdatedSubscriber{} + if err := obj.Prepare(); err != nil { + return nil, err + } + __event.Handle("handler:PostUpdatedSubscriber", obj.Topic(), obj.PublishToTopic(), obj.Handler) + + return obj, nil + }, atom.GroupInitial); err != nil { + return err + } if err := container.Container.Provide(func( __event *event.PubSub, userSvc *users.Service, diff --git a/backend/app/events/subscribers/user_register.go b/backend/app/events/subscribers/user_register.go index 8addc3d..0d7a1aa 100644 --- a/backend/app/events/subscribers/user_register.go +++ b/backend/app/events/subscribers/user_register.go @@ -31,12 +31,12 @@ func (e *UserRegister) Prepare() error { // PublishToTopic implements contracts.EventHandler. func (e *UserRegister) PublishToTopic() string { - return events.TopicProcessed.String() + return events.TopicProcessed } // Topic implements contracts.EventHandler. func (e *UserRegister) Topic() string { - return events.TopicUserRegister.String() + return events.TopicUserRegister } // Handler implements contracts.EventHandler. diff --git a/backend/app/events/topics.gen.go b/backend/app/events/topics.gen.go deleted file mode 100644 index f03af50..0000000 --- a/backend/app/events/topics.gen.go +++ /dev/null @@ -1,179 +0,0 @@ -// Code generated by go-enum DO NOT EDIT. -// Version: - -// Revision: - -// Build Date: - -// Built By: - - -package events - -import ( - "database/sql/driver" - "errors" - "fmt" - "strings" -) - -const ( - // TopicProcessed is a Topic of type Processed. - TopicProcessed Topic = "event:processed" - // TopicUserRegister is a Topic of type UserRegister. - TopicUserRegister Topic = "user:register" - // TopicPostCreated is a Topic of type PostCreated. - TopicPostCreated Topic = "post:created" -) - -var ErrInvalidTopic = fmt.Errorf("not a valid Topic, try [%s]", strings.Join(_TopicNames, ", ")) - -var _TopicNames = []string{ - string(TopicProcessed), - string(TopicUserRegister), - string(TopicPostCreated), -} - -// TopicNames returns a list of possible string values of Topic. -func TopicNames() []string { - tmp := make([]string, len(_TopicNames)) - copy(tmp, _TopicNames) - return tmp -} - -// TopicValues returns a list of the values for Topic -func TopicValues() []Topic { - return []Topic{ - TopicProcessed, - TopicUserRegister, - TopicPostCreated, - } -} - -// String implements the Stringer interface. -func (x Topic) String() string { - return string(x) -} - -// IsValid provides a quick way to determine if the typed value is -// part of the allowed enumerated values -func (x Topic) IsValid() bool { - _, err := ParseTopic(string(x)) - return err == nil -} - -var _TopicValue = map[string]Topic{ - "event:processed": TopicProcessed, - "user:register": TopicUserRegister, - "post:created": TopicPostCreated, -} - -// ParseTopic attempts to convert a string to a Topic. -func ParseTopic(name string) (Topic, error) { - if x, ok := _TopicValue[name]; ok { - return x, nil - } - return Topic(""), fmt.Errorf("%s is %w", name, ErrInvalidTopic) -} - -var errTopicNilPtr = errors.New("value pointer is nil") // one per type for package clashes - -// Scan implements the Scanner interface. -func (x *Topic) Scan(value interface{}) (err error) { - if value == nil { - *x = Topic("") - return - } - - // A wider range of scannable types. - // driver.Value values at the top of the list for expediency - switch v := value.(type) { - case string: - *x, err = ParseTopic(v) - case []byte: - *x, err = ParseTopic(string(v)) - case Topic: - *x = v - case *Topic: - if v == nil { - return errTopicNilPtr - } - *x = *v - case *string: - if v == nil { - return errTopicNilPtr - } - *x, err = ParseTopic(*v) - default: - return errors.New("invalid type for Topic") - } - - return -} - -// Value implements the driver Valuer interface. -func (x Topic) Value() (driver.Value, error) { - return x.String(), nil -} - -// Set implements the Golang flag.Value interface func. -func (x *Topic) Set(val string) error { - v, err := ParseTopic(val) - *x = v - return err -} - -// Get implements the Golang flag.Getter interface func. -func (x *Topic) Get() interface{} { - return *x -} - -// Type implements the github.com/spf13/pFlag Value interface. -func (x *Topic) Type() string { - return "Topic" -} - -type NullTopic struct { - Topic Topic - Valid bool -} - -func NewNullTopic(val interface{}) (x NullTopic) { - err := x.Scan(val) // yes, we ignore this error, it will just be an invalid value. - _ = err // make any errcheck linters happy - return -} - -// Scan implements the Scanner interface. -func (x *NullTopic) Scan(value interface{}) (err error) { - if value == nil { - x.Topic, x.Valid = Topic(""), false - return - } - - err = x.Topic.Scan(value) - x.Valid = (err == nil) - return -} - -// Value implements the driver Valuer interface. -func (x NullTopic) Value() (driver.Value, error) { - if !x.Valid { - return nil, nil - } - // driver.Value accepts int64 for int values. - return string(x.Topic), nil -} - -type NullTopicStr struct { - NullTopic -} - -func NewNullTopicStr(val interface{}) (x NullTopicStr) { - x.Scan(val) // yes, we ignore this error, it will just be an invalid value. - return -} - -// Value implements the driver Valuer interface. -func (x NullTopicStr) Value() (driver.Value, error) { - if !x.Valid { - return nil, nil - } - return x.Topic.String(), nil -} diff --git a/backend/app/events/topics.go b/backend/app/events/topics.go index 84f71a7..cfd1f9c 100644 --- a/backend/app/events/topics.go +++ b/backend/app/events/topics.go @@ -1,11 +1,9 @@ package events -// swagger:enum Topic -// ENUM( -// -// Processed = "event:processed" -// UserRegister = "user:register" -// PostCreated = "post:created" -// -// ) -type Topic string +const ( + TopicProcessed = "event:processed" + TopicUserRegister = "user:register" + TopicPostCreated = "post:created" + TopicPostDeleted = "post:deleted" +) +const TopicPostUpdated = "post_updated" diff --git a/backend/app/events/user_register.go b/backend/app/events/user_register.go deleted file mode 100644 index 21698fd..0000000 --- a/backend/app/events/user_register.go +++ /dev/null @@ -1,27 +0,0 @@ -package events - -import ( - "encoding/json" - - "git.ipao.vip/rogeecn/atom/contracts" -) - -var _ contracts.EventPublisher = (*UserRegister)(nil) - -type UserRegister struct { - ID int64 `json:"id"` -} - -func (e *UserRegister) Prepare() error { - return nil -} - -// Marshal implements contracts.EventPublisher. -func (e *UserRegister) Marshal() ([]byte, error) { - return json.Marshal(e) -} - -// Topic implements contracts.EventHandler. -func (e *UserRegister) Topic() string { - return TopicUserRegister.String() -} diff --git a/backend/app/http/medias/controller.go b/backend/app/http/medias/controller.go index d33ea80..a112842 100644 --- a/backend/app/http/medias/controller.go +++ b/backend/app/http/medias/controller.go @@ -29,16 +29,10 @@ func (ctl *Controller) Prepare() error { // Upload // @Router /api/v1/medias/:tenant/upload [post] -// @Bind tenantSlug path // @Bind req body // @Bind file file // @Bind claim local -func (ctl *Controller) Upload(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug string, file *multipart.FileHeader, req *UploadReq) (*storage.UploadedFile, error) { - tenant, err := ctl.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) - if err != nil { - return nil, err - } - +func (ctl *Controller) Upload(ctx fiber.Ctx, claim *jwt.Claims, file *multipart.FileHeader, req *UploadReq) (*storage.UploadedFile, error) { defaultStorage, err := ctl.storageSvc.GetDefault(ctx.Context()) if err != nil { return nil, err @@ -67,7 +61,7 @@ func (ctl *Controller) Upload(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug strin _, err = ctl.svc.Create(ctx.Context(), &model.Medias{ CreatedAt: time.Now(), UpdatedAt: time.Now(), - TenantID: tenant.ID, + TenantID: *claim.TenantID, UserID: claim.UserID, StorageID: defaultStorage.ID, Hash: uploadedFile.Hash, diff --git a/backend/app/http/medias/routes.gen.go b/backend/app/http/medias/routes.gen.go index 8289c82..86f9ade 100644 --- a/backend/app/http/medias/routes.gen.go +++ b/backend/app/http/medias/routes.gen.go @@ -30,10 +30,9 @@ func (r *Routes) Name() string { func (r *Routes) Register(router fiber.Router) { // 注册路由组: Controller - router.Post("/api/v1/medias/:tenant/upload", DataFunc4( + router.Post("/api/v1/medias/:tenant/upload", DataFunc3( r.controller.Upload, Local[*jwt.Claims]("claim"), - PathParam[string]("tenantSlug"), File[multipart.FileHeader]("file"), Body[UploadReq]("req"), )) diff --git a/backend/app/http/orders/controller_order.go b/backend/app/http/orders/controller_order.go index 58e83f6..90f9899 100644 --- a/backend/app/http/orders/controller_order.go +++ b/backend/app/http/orders/controller_order.go @@ -1,7 +1,6 @@ package orders import ( - "backend/app/errorx" "backend/app/http/posts" "backend/app/http/tenants" "backend/app/http/users" @@ -62,27 +61,17 @@ func (c *OrderController) List(ctx fiber.Ctx, claim *jwt.Claims, pagination *req // @Router /api/v1/orders [post] // @Bind claim local // @Bind hash path -// @Bind tenantSlug cookie key(tenant) -func (c *OrderController) Create(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash string) (*UserOrder, error) { +func (c *OrderController) Create(ctx fiber.Ctx, claim *jwt.Claims, hash string) (*UserOrder, error) { user, err := c.userSvc.GetUserByID(ctx.Context(), claim.UserID) if err != nil { return nil, err } - tenant, err := c.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) + post, err := c.postSvc.GetPostByHash(ctx.Context(), *claim.TenantID, hash) if err != nil { return nil, err } - post, err := c.postSvc.GetPostByHash(ctx.Context(), tenant.ID, hash) - if err != nil { - return nil, err - } - - if tenant.ID != post.TenantID { - return nil, errorx.BadRequest - } - order, err := c.svc.Create(ctx.Context(), user, post) if err != nil { return nil, err diff --git a/backend/app/http/orders/routes.gen.go b/backend/app/http/orders/routes.gen.go index 316760a..2071ed9 100644 --- a/backend/app/http/orders/routes.gen.go +++ b/backend/app/http/orders/routes.gen.go @@ -38,10 +38,9 @@ func (r *Routes) Register(router fiber.Router) { Query[UserOrderFilter]("filter"), )) - router.Post("/api/v1/orders", DataFunc3( + router.Post("/api/v1/orders", DataFunc2( r.orderController.Create, Local[*jwt.Claims]("claim"), - CookieParam("tenant"), PathParam[string]("hash"), )) diff --git a/backend/app/http/posts/controller.go b/backend/app/http/posts/controller.go index 11ebcb8..06fae2e 100644 --- a/backend/app/http/posts/controller.go +++ b/backend/app/http/posts/controller.go @@ -4,25 +4,29 @@ import ( "time" "backend/app/errorx" + "backend/app/events/publishers" "backend/app/http/medias" "backend/app/http/tenants" "backend/app/http/users" "backend/app/requests" "backend/database/fields" "backend/database/models/qvyun_v2/public/model" + "backend/providers/event" + "backend/providers/hashids" "backend/providers/jwt" "github.com/gofiber/fiber/v3" "github.com/jinzhu/copier" "github.com/samber/lo" log "github.com/sirupsen/logrus" - "github.com/speps/go-hashids/v2" ) // @provider type Controller struct { + event *event.PubSub + svc *Service - hashIds *hashids.HashID + hashId *hashids.Hasher userSvc *users.Service tenantSvc *tenants.Service mediaSvc *medias.Service @@ -36,22 +40,16 @@ func (c *Controller) Prepare() error { // List show posts list // @Router /api/v1/posts [get] -// @Bind tenantSlug cookie key(tenant) // @Bind claim local // @Bind pagination query // @Bind filter query -func (c *Controller) List(ctx fiber.Ctx, tenantSlug string, claim *jwt.Claims, pagination *requests.Pagination, filter *UserPostFilter) (*requests.Pager, error) { - tenant, err := c.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) - if err != nil { - return nil, err - } - +func (c *Controller) List(ctx fiber.Ctx, claim *jwt.Claims, pagination *requests.Pagination, filter *UserPostFilter) (*requests.Pager, error) { pagination.Format() pager := &requests.Pager{ Pagination: *pagination, } - filter.TenantID = tenant.ID + filter.TenantID = *claim.TenantID filter.UserID = claim.UserID orders, total, err := c.svc.GetPosts(ctx.Context(), pagination, filter) if err != nil { @@ -72,22 +70,16 @@ func (c *Controller) List(ctx fiber.Ctx, tenantSlug string, claim *jwt.Claims, p // ListBought show user bought posts list // @Router /api/v1/bought-posts [get] -// @Bind tenantSlug cookie key(tenant) // @Bind claim local // @Bind pagination query // @Bind filter query -func (c *Controller) ListBought(ctx fiber.Ctx, tenantSlug string, claim *jwt.Claims, pagination *requests.Pagination, filter *UserPostFilter) (*requests.Pager, error) { - tenant, err := c.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) - if err != nil { - return nil, err - } - +func (c *Controller) ListBought(ctx fiber.Ctx, claim *jwt.Claims, pagination *requests.Pagination, filter *UserPostFilter) (*requests.Pager, error) { pagination.Format() pager := &requests.Pager{ Pagination: *pagination, } - filter.TenantID = tenant.ID + filter.TenantID = *claim.TenantID filter.UserID = claim.UserID orders, total, err := c.svc.GetBoughtPosts(ctx.Context(), pagination, filter) if err != nil { @@ -109,26 +101,16 @@ func (c *Controller) ListBought(ctx fiber.Ctx, tenantSlug string, claim *jwt.Cla // Show show posts detail // @Router /api/v1/show/:hash [get] // @Bind claim local -// @Bind tenantSlug cookie key(tenant) // @Bind hash path -func (c *Controller) Show(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash string) (*UserPost, error) { +func (c *Controller) Show(ctx fiber.Ctx, claim *jwt.Claims, hash string) (*UserPost, error) { userPost := &UserPost{} - tenant, err := c.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) - if err != nil { - return nil, err - } - - postIds, err := c.hashIds.DecodeInt64WithError(hash) + postId, err := c.hashId.DecodeOnlyInt64(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]) + post, err := c.svc.GetPostByID(ctx.Context(), postId) if err != nil { return nil, err } @@ -186,7 +168,84 @@ func (ctl *Controller) Create(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug strin if err := ctl.svc.Create(ctx.Context(), tenant, user, post); err != nil { return err } - // TODO: trigger event && jobs + + _ = ctl.event.Publish(&publishers.PostCreated{ID: post.ID}) return nil } + +// Delete +// @Router /api/v1/posts/:hash [delete] +// @Bind claim local +// @Bind tenantSlug cookie key(tenant) +// @Bind hash path +func (c *Controller) Delete(ctx fiber.Ctx, claim *jwt.Claims, tenantSlug, hash string) error { + tenant, err := c.tenantSvc.GetTenantBySlug(ctx.Context(), tenantSlug) + if err != nil { + return err + } + + postId, err := c.hashId.DecodeOnlyInt64(hash) + if err != nil { + return errorx.RecordNotExists + } + + if err := c.svc.Delete(ctx.Context(), tenant.ID, *&claim.UserID, postId); err != nil { + return err + } + + // trigger event + _ = c.event.Publish(&publishers.PostDeleted{ID: postId}) + + return nil +} + +// Update +// @Router /api/v1/posts/:hash [put] +// @Bind claim local +// @Bind tenantSlug cookie key(tenant) +// @Bind hash path +// @Bind body body +func (ctl *Controller) Update(ctx fiber.Ctx, claim *jwt.Claims, hash string, body *PostBody) error { + postId, err := ctl.hashId.DecodeOnlyInt64(hash) + if err != nil { + return errorx.RecordNotExists + } + + post, err := ctl.svc.GetPostByID(ctx.Context(), postId) + if err != nil { + return err + } + + // check media assets exists + hashes := lo.Map(body.Assets.Data, func(item fields.MediaAsset, _ int) string { return item.Hash }) + medias, err := ctl.mediaSvc.GetMediasByHash(ctx.Context(), *claim.TenantID, post.UserID, hashes) + if err != nil { + return err + } + + if len(medias) != len(lo.Uniq(hashes)) { + return errorx.BadRequest + } + + _ = ctl.event.Publish(&publishers.PostUpdatedEvent{ID: postId}) + + m := &model.Posts{ + UpdatedAt: time.Now(), + Title: body.Title, + Description: body.Description, + Content: body.Content, + Price: body.Price, + Discount: body.Discount, + Assets: body.Assets, + Tags: body.Tags, + + Stage: fields.PostStagePending, + Status: fields.PostStatusPending, + } + + if err := ctl.svc.Update(ctx.Context(), post.TenantID, post.UserID, post.ID, m); err != nil { + return err + } + return nil +} diff --git a/backend/app/http/posts/provider.gen.go b/backend/app/http/posts/provider.gen.go index 5300158..8a83e3e 100755 --- a/backend/app/http/posts/provider.gen.go +++ b/backend/app/http/posts/provider.gen.go @@ -3,25 +3,31 @@ package posts import ( "database/sql" + "backend/app/http/medias" "backend/app/http/tenants" "backend/app/http/users" + "backend/providers/event" + "backend/providers/hashids" "git.ipao.vip/rogeecn/atom" "git.ipao.vip/rogeecn/atom/container" "git.ipao.vip/rogeecn/atom/contracts" "git.ipao.vip/rogeecn/atom/utils/opt" - "github.com/speps/go-hashids/v2" ) func Provide(opts ...opt.Option) error { if err := container.Container.Provide(func( - hashIds *hashids.HashID, + event *event.PubSub, + hashId *hashids.Hasher, + mediaSvc *medias.Service, svc *Service, tenantSvc *tenants.Service, userSvc *users.Service, ) (*Controller, error) { obj := &Controller{ - hashIds: hashIds, + event: event, + hashId: hashId, + mediaSvc: mediaSvc, svc: svc, tenantSvc: tenantSvc, userSvc: userSvc, @@ -50,7 +56,7 @@ func Provide(opts ...opt.Option) error { } if err := container.Container.Provide(func( db *sql.DB, - hashIds *hashids.HashID, + hashIds *hashids.Hasher, ) (*Service, error) { obj := &Service{ db: db, diff --git a/backend/app/http/posts/routes.gen.go b/backend/app/http/posts/routes.gen.go index 68d53b7..a09c7ef 100644 --- a/backend/app/http/posts/routes.gen.go +++ b/backend/app/http/posts/routes.gen.go @@ -30,26 +30,23 @@ func (r *Routes) Name() string { func (r *Routes) Register(router fiber.Router) { // 注册路由组: Controller - router.Get("/api/v1/posts", DataFunc4( + router.Get("/api/v1/posts", DataFunc3( r.controller.List, - CookieParam("tenant"), Local[*jwt.Claims]("claim"), Query[requests.Pagination]("pagination"), Query[UserPostFilter]("filter"), )) - router.Get("/api/v1/bought-posts", DataFunc4( + router.Get("/api/v1/bought-posts", DataFunc3( r.controller.ListBought, - CookieParam("tenant"), Local[*jwt.Claims]("claim"), Query[requests.Pagination]("pagination"), Query[UserPostFilter]("filter"), )) - router.Get("/api/v1/show/:hash", DataFunc3( + router.Get("/api/v1/show/:hash", DataFunc2( r.controller.Show, Local[*jwt.Claims]("claim"), - CookieParam("tenant"), PathParam[string]("hash"), )) @@ -60,4 +57,18 @@ func (r *Routes) Register(router fiber.Router) { Body[PostBody]("body"), )) + router.Delete("/api/v1/posts/:hash", Func3( + r.controller.Delete, + Local[*jwt.Claims]("claim"), + CookieParam("tenant"), + PathParam[string]("hash"), + )) + + router.Put("/api/v1/posts/:hash", Func3( + r.controller.Update, + Local[*jwt.Claims]("claim"), + PathParam[string]("hash"), + Body[PostBody]("body"), + )) + } diff --git a/backend/app/http/posts/service.go b/backend/app/http/posts/service.go index 404502d..1ed63c9 100644 --- a/backend/app/http/posts/service.go +++ b/backend/app/http/posts/service.go @@ -3,17 +3,18 @@ package posts import ( "context" "database/sql" + "time" "backend/app/requests" "backend/database" "backend/database/models/qvyun_v2/public/model" "backend/database/models/qvyun_v2/public/table" + "backend/providers/hashids" "backend/providers/otel" . "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" ) @@ -21,7 +22,7 @@ import ( // @provider:except type Service struct { db *sql.DB - hashIds *hashids.HashID + hashIds *hashids.Hasher log *log.Entry `inject:"false"` } @@ -114,7 +115,7 @@ func (svc *Service) GetPosts(ctx context.Context, pagination *requests.Paginatio ) tbl := table.Posts - cond := Bool(true) + cond := tbl.DeletedAt.IS_NULL() if filter.ID != nil { cond = cond.AND(tbl.ID.EQ(Int64(*filter.ID))) } @@ -165,6 +166,24 @@ func (svc *Service) GetPosts(ctx context.Context, pagination *requests.Paginatio return posts, count.Cnt, nil } +// ForceGetPostByID +func (svc *Service) ForceGetPostByID(ctx context.Context, id int64) (*model.Posts, error) { + _, span := otel.Start(ctx, "users.service.ForceGetPostByID") + defer span.End() + span.SetAttributes(attribute.Int64("post.id", id)) + tbl := table.Posts + + stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id))) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + var post model.Posts + if err := stmt.QueryContext(ctx, svc.db, &post); err != nil { + return nil, err + } + + return &post, nil +} + // GetPostByID func (svc *Service) GetPostByID(ctx context.Context, id int64) (*model.Posts, error) { _, span := otel.Start(ctx, "users.service.GetPostByID") @@ -172,7 +191,7 @@ func (svc *Service) GetPostByID(ctx context.Context, id int64) (*model.Posts, er span.SetAttributes(attribute.Int64("post.id", id)) tbl := table.Posts - stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id))) + stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id)).AND(tbl.DeletedAt.IS_NULL())) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var post model.Posts @@ -240,14 +259,76 @@ func (svc *Service) GetPostByHash(ctx context.Context, tenantID int64, hash stri attribute.String("hash", hash), ) - postIDs, err := svc.hashIds.DecodeInt64WithError(hash) + postId, err := svc.hashIds.DecodeOnlyInt64(hash) if err != nil { return nil, err } - if tenantID != postIDs[0] { - return nil, nil + return svc.GetPostByID(ctx, postId) +} + +// Delete +func (svc *Service) Delete(ctx context.Context, tenantID, userID, postID int64) error { + _, span := otel.Start(ctx, "users.service.Delete") + defer span.End() + span.SetAttributes( + attribute.Int64("tenant.id", tenantID), + attribute.Int64("user.id", userID), + attribute.Int64("post.id", postID), + ) + tbl := table.Posts + + stmt := tbl. + UPDATE(). + SET( + tbl.DeletedAt.SET(TimestampT(time.Now())), + ). + WHERE( + tbl.ID.EQ(Int64(postID)).AND( + tbl.TenantID.EQ(Int64(tenantID)).AND( + tbl.UserID.EQ(Int64(userID)), + ), + ), + ) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + if _, err := stmt.ExecContext(ctx, svc.db); err != nil { + return err } - return svc.GetPostByID(ctx, postIDs[1]) + return nil +} + +// Update +func (svc *Service) Update(ctx context.Context, tenantID, userID, postID int64, post *model.Posts) error { + _, span := otel.Start(ctx, "users.service.Update") + defer span.End() + span.SetAttributes( + attribute.Int64("tenant.id", tenantID), + attribute.Int64("user.id", userID), + attribute.Int64("post.id", postID), + ) + tbl := table.Posts + + stmt := tbl. + UPDATE( + tbl.MutableColumns.Except( + tbl.TenantID, tbl.UserID, tbl.CreatedAt, tbl.DeletedAt, + ), + ). + MODEL(post). + WHERE( + tbl.ID.EQ(Int64(postID)).AND( + tbl.TenantID.EQ(Int64(tenantID)).AND( + tbl.UserID.EQ(Int64(userID)), + ), + ), + ) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + if _, err := stmt.ExecContext(ctx, svc.db); err != nil { + return err + } + + return nil } diff --git a/backend/app/jobs/provider.gen.go b/backend/app/jobs/provider.gen.go index 5f41b9b..43440c4 100755 --- a/backend/app/jobs/provider.gen.go +++ b/backend/app/jobs/provider.gen.go @@ -11,6 +11,30 @@ import ( ) func Provide(opts ...opt.Option) error { + if err := container.Container.Provide(func( + __job *job.Job, + ) (contracts.Initial, error) { + obj := &VideoCutWorker{} + if err := river.AddWorkerSafely(__job.Workers, obj); err != nil { + return nil, err + } + + return obj, nil + }, atom.GroupInitial); err != nil { + return err + } + if err := container.Container.Provide(func( + __job *job.Job, + ) (contracts.Initial, error) { + obj := &VideoExtractAudioWorker{} + if err := river.AddWorkerSafely(__job.Workers, obj); err != nil { + return nil, err + } + + return obj, nil + }, atom.GroupInitial); err != nil { + return err + } if err := container.Container.Provide(func() (contracts.CronJob, error) { obj := &CronJob{} if err := obj.Prepare(); err != nil { diff --git a/backend/database/fields/posts.go b/backend/database/fields/posts.go index c8005f0..c5073f2 100644 --- a/backend/database/fields/posts.go +++ b/backend/database/fields/posts.go @@ -29,3 +29,7 @@ type PostStatus int16 // swagger:enum PostType // ENUM( Article, Picture, Video, Audio) type PostType int16 + +type PostMeta struct { + WorkerMark int64 `json:"worker_mark,omitempty"` +} diff --git a/backend/database/transform.yaml b/backend/database/transform.yaml index a51d62d..3a282ee 100644 --- a/backend/database/transform.yaml +++ b/backend/database/transform.yaml @@ -22,6 +22,7 @@ types: type: PostType assets: Json[[]MediaAsset] tags: Json[[]string] + metas: PostMeta orders: type: OrderType diff --git a/backend/providers/hashids/hashids.go b/backend/providers/hashids/hashids.go index c296ad4..938bf6a 100644 --- a/backend/providers/hashids/hashids.go +++ b/backend/providers/hashids/hashids.go @@ -13,7 +13,7 @@ func Provide(opts ...opt.Option) error { if err := o.UnmarshalConfig(&config); err != nil { return err } - return container.Container.Provide(func() (*hashids.HashID, error) { + return container.Container.Provide(func() (*Hasher, error) { data := hashids.NewData() data.MinLength = int(config.MinLength) if data.MinLength == 0 { @@ -30,6 +30,64 @@ func Provide(opts ...opt.Option) error { data.Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" } - return hashids.NewWithData(data) + hasher, err := hashids.NewWithData(data) + if err != nil { + return nil, err + } + + return &Hasher{ + hasher: hasher, + }, nil }, o.DiOptions()...) } + +type Hasher struct { + hasher *hashids.HashID +} + +func (h *Hasher) EncodeInt64(id ...int64) (string, error) { + return h.hasher.EncodeInt64(id) +} + +func (h *Hasher) MustEncodeInt64(id ...int64) string { + s, err := h.hasher.EncodeInt64(id) + if err != nil { + panic(err) + } + return s +} + +func (h *Hasher) DecodeInt64(hash string) ([]int64, error) { + return h.hasher.DecodeInt64WithError(hash) +} + +func (h *Hasher) DecodeOnlyInt64(hash string) (int64, error) { + ints, err := h.hasher.DecodeInt64WithError(hash) + if err != nil { + return 0, err + } + + if len(ints) == 0 { + return 0, nil + } + + return ints[0], nil +} + +func (h *Hasher) MustDecodeInt64(hash string) []int64 { + id, err := h.DecodeInt64(hash) + if err != nil { + panic(err) + } + + return id +} + +func (h *Hasher) MustDecodeOnlyInt64(hash string) int64 { + id, err := h.DecodeInt64(hash) + if err != nil { + panic(err) + } + + return id[0] +}