diff --git a/backend/Makefile b/backend/Makefile index f229c0f..93a6f7f 100755 --- a/backend/Makefile +++ b/backend/Makefile @@ -32,6 +32,11 @@ lint: proto: @buf generate +.PHONY: fresh +fresh: + @go run . migrate down + @go run . migrate up + .PHONY: mup mup: @go run . migrate up diff --git a/backend/common/errorx/error.go b/backend/common/errorx/error.go new file mode 100644 index 0000000..f318711 --- /dev/null +++ b/backend/common/errorx/error.go @@ -0,0 +1,19 @@ +package errorx + +import ( + "fmt" +) + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (r Response) Error() string { + return fmt.Sprintf("%d: %s", r.Code, r.Message) +} + +var ( + RequestParseError = Response{400, "请求解析错误"} + InternalError = Response{500, "内部错误"} +) diff --git a/backend/database/.transform.yaml b/backend/database/.transform.yaml index f16420c..5c40142 100755 --- a/backend/database/.transform.yaml +++ b/backend/database/.transform.yaml @@ -2,3 +2,6 @@ ignores: [] # ignore tables types: users: # table name oauth: backend/pkg/pg.UserOAuth + + media_resources: # table name + type: backend/pkg/pg.MediaType diff --git a/backend/database/migrations/20241128075611_init.sql b/backend/database/migrations/20241128075611_init.sql index 6770d2c..0838966 100644 --- a/backend/database/migrations/20241128075611_init.sql +++ b/backend/database/migrations/20241128075611_init.sql @@ -72,7 +72,7 @@ CREATE TABLE title VARCHAR(198) NOT NULL, description VARCHAR(198) NOT NULL, price INT8 NOT NULL, - discount INT8 NOT NULL, + discount INT8 NOT NULL default 100, publish BOOL NOT NULL, created_at timestamp NOT NULL default now(), updated_at timestamp NOT NULL default now() diff --git a/backend/database/models/qvyun/public/model/media_resources.go b/backend/database/models/qvyun/public/model/media_resources.go index 5854005..39360a3 100644 --- a/backend/database/models/qvyun/public/model/media_resources.go +++ b/backend/database/models/qvyun/public/model/media_resources.go @@ -8,16 +8,17 @@ package model import ( + "backend/pkg/pg" "time" ) type MediaResources struct { - ID int64 `sql:"primary_key" json:"id"` - MediaID int64 `json:"media_id"` - Type string `json:"type"` - Source *string `json:"source"` - Size int64 `json:"size"` - Publish bool `json:"publish"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int64 `sql:"primary_key" json:"id"` + MediaID int64 `json:"media_id"` + Type pg.MediaType `json:"type"` + Source *string `json:"source"` + Size int64 `json:"size"` + Publish bool `json:"publish"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } diff --git a/backend/go.mod b/backend/go.mod index 3682028..192a121 100755 --- a/backend/go.mod +++ b/backend/go.mod @@ -18,6 +18,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/smartystreets/goconvey v1.6.4 github.com/speps/go-hashids/v2 v2.0.1 + github.com/spf13/cast v1.5.1 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.17.0 go.uber.org/dig v1.18.0 @@ -68,7 +69,6 @@ require ( github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect - github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect diff --git a/backend/modules/medias/__debug_bin790297978 b/backend/modules/medias/__debug_bin790297978 new file mode 100755 index 0000000..1e4d73c Binary files /dev/null and b/backend/modules/medias/__debug_bin790297978 differ diff --git a/backend/modules/medias/controller.go b/backend/modules/medias/controller.go new file mode 100644 index 0000000..b6de9c4 --- /dev/null +++ b/backend/modules/medias/controller.go @@ -0,0 +1,30 @@ +package medias + +import ( + "backend/common/errorx" + + "github.com/gofiber/fiber/v3" + . "github.com/spf13/cast" +) + +// @provider +type Controller struct { + svc *Service +} + +// List +func (c *Controller) List(ctx fiber.Ctx) error { + filter := ListFilter{} + if err := ctx.Bind().Body(&filter); err != nil { + return ctx.Status(fiber.StatusBadRequest).JSON(errorx.RequestParseError) + } + + tenantId, userId := ToInt64(ctx.Locals("tenantId")), ToInt64(ctx.Locals("userId")) + + items, err := c.svc.List(ctx.Context(), tenantId, userId, &filter) + if err != nil { + return ctx.Status(fiber.StatusInternalServerError).JSON(errorx.InternalError) + } + + return ctx.JSON(items) +} diff --git a/backend/modules/medias/dto.go b/backend/modules/medias/dto.go new file mode 100644 index 0000000..b51f13f --- /dev/null +++ b/backend/modules/medias/dto.go @@ -0,0 +1,18 @@ +package medias + +import ( + "backend/database/models/qvyun/public/model" + "backend/pkg/db" +) + +type ListFilter struct { + db.Pagination + Title *string `json:"title"` + Bought *bool `json:"bought"` +} + +type ListItem struct { + model.Medias + Bought bool `json:"bought"` + MediaResources []model.MediaResources `json:"media_resources"` +} diff --git a/backend/modules/medias/provider.gen.go b/backend/modules/medias/provider.gen.go new file mode 100755 index 0000000..f6bb8f3 --- /dev/null +++ b/backend/modules/medias/provider.gen.go @@ -0,0 +1,54 @@ +package medias + +import ( + "database/sql" + + "backend/providers/http" + + "git.ipao.vip/rogeecn/atom" + "git.ipao.vip/rogeecn/atom/container" + "git.ipao.vip/rogeecn/atom/contracts" + "git.ipao.vip/rogeecn/atom/utils/opt" +) + +func Provide(opts ...opt.Option) error { + if err := container.Container.Provide(func( + svc *Service, + ) (*Controller, error) { + obj := &Controller{ + svc: svc, + } + return obj, nil + }); err != nil { + return err + } + + if err := container.Container.Provide(func( + controller *Controller, + http *http.Service, + ) (contracts.HttpRoute, error) { + obj := &Router{ + controller: controller, + http: http, + } + return obj, nil + }, atom.GroupRoutes); err != nil { + return err + } + + if err := container.Container.Provide(func( + db *sql.DB, + ) (*Service, error) { + obj := &Service{ + db: db, + } + if err := obj.Prepare(); err != nil { + return nil, err + } + return obj, nil + }); err != nil { + return err + } + + return nil +} diff --git a/backend/modules/medias/router.go b/backend/modules/medias/router.go new file mode 100755 index 0000000..a2a3f60 --- /dev/null +++ b/backend/modules/medias/router.go @@ -0,0 +1,26 @@ +package medias + +import ( + "backend/providers/http" + + _ "git.ipao.vip/rogeecn/atom" + _ "git.ipao.vip/rogeecn/atom/contracts" + "github.com/gofiber/fiber/v3" + log "github.com/sirupsen/logrus" +) + +// @provider:except contracts.HttpRoute atom.GroupRoutes +type Router struct { + http *http.Service + + controller *Controller +} + +func (r *Router) Register() error { + group := r.http.Engine.Group("medias") + log.Infof("register route group: %s", group.(*fiber.Group).Prefix) + group.Get("", r.controller.List) + group.Get("{id}", r.controller.List) + + return nil +} diff --git a/backend/modules/medias/service.go b/backend/modules/medias/service.go new file mode 100644 index 0000000..dcbb6dc --- /dev/null +++ b/backend/modules/medias/service.go @@ -0,0 +1,164 @@ +package medias + +import ( + "context" + "database/sql" + + "backend/database/models/qvyun/public/model" + "backend/database/models/qvyun/public/table" + + . "github.com/go-jet/jet/v2/postgres" + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/sirupsen/logrus" +) + +// @provider:except +type Service struct { + db *sql.DB + log *logrus.Entry `inject:"false"` +} + +func (svc *Service) Prepare() error { + svc.log = logrus.WithField("module", "medias.service") + return nil +} + +// GetByID +func (svc *Service) GetByID(ctx context.Context, id int64) (*ListItem, error) { + log := svc.log.WithField("method", "GetByID") + + tbl := table.Medias + stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int(id))) + log.Debug(stmt.Sql()) + + var media ListItem + if err := stmt.QueryContext(ctx, svc.db, &media); err != nil { + return nil, errors.Wrap(err, "query media by id") + } + + return &media, nil +} + +// Decorate List Resources +func (svc *Service) DecorateListResources(ctx context.Context, tenantId, userId int64, items []ListItem) ([]ListItem, error) { + log := svc.log.WithField("method", "DecorateListResources") + + mediaIDs := make([]int64, len(items)) + for i, item := range items { + mediaIDs[i] = item.ID + } + + tbl := table.MediaResources + stmt := tbl. + SELECT( + tbl.MediaID, + tbl.Type, + tbl.Size, + ). + WHERE(tbl.MediaID.IN(lo.Map(mediaIDs, func(item int64, _ int) Expression { + return Int(item) + })...)) + log.Debug(stmt.DebugSql()) + + var resources []model.MediaResources + if err := stmt.QueryContext(ctx, svc.db, &resources); err != nil { + return nil, errors.Wrap(err, "query media resources") + } + + if len(resources) == 0 { + return nil, nil + } + + // group resources by media id + resourcesMap := make(map[int64][]model.MediaResources) + for _, resource := range resources { + if _, ok := resourcesMap[resource.MediaID]; !ok { + resourcesMap[resource.MediaID] = make([]model.MediaResources, 0) + } + resourcesMap[resource.MediaID] = append(resourcesMap[resource.MediaID], resource) + } + + // set resources to items + for i, item := range items { + if resources, ok := resourcesMap[item.ID]; ok { + items[i].MediaResources = resources + } + } + + return items, nil +} + +// List +func (svc *Service) List(ctx context.Context, tenantId, userId int64, filter *ListFilter) ([]ListItem, error) { + log := svc.log.WithField("method", "List") + + tbl := table.Medias + stmt := tbl. + SELECT(tbl.AllColumns). + WHERE(tbl.TenantID.EQ(Int(tenantId))). + ORDER_BY(tbl.ID.DESC()) + + if filter.Title != nil && *filter.Title != "" { + stmt = stmt.WHERE(tbl.Title.LIKE(String("%" + *filter.Title + "%"))) + } + + if filter.Bought != nil && *filter.Bought { + boughtIDs, err := svc.GetUserBoughtMedias(ctx, tenantId, userId) + if err != nil { + return nil, errors.Wrap(err, "get user bought medias") + } + + if len(boughtIDs) > 0 { + stmt = stmt. + WHERE(tbl.ID.IN(lo.Map(boughtIDs, func(item int64, _ int) Expression { + return Int(item) + })...)) + } + } else { + stmt = stmt.WHERE(tbl.Publish.EQ(Bool(true))) + } + + if filter.OffsetID > 0 { + if filter.Action == 0 { + stmt = stmt.WHERE(tbl.ID.GT(Int(filter.OffsetID))) + } + + if filter.Action == 1 { + stmt = stmt.WHERE(tbl.ID.LT(Int(filter.OffsetID))) + stmt = stmt.LIMIT(10) + } + } else { + stmt = stmt.LIMIT(10) + } + log.Debug(stmt.DebugSql()) + + var dest []ListItem + if err := stmt.QueryContext(ctx, svc.db, &dest); err != nil { + return nil, errors.Wrap(err, "query medias") + } + + return dest, nil +} + +// GetUserBoughtMedias +func (svc *Service) GetUserBoughtMedias(ctx context.Context, tenant int64, userID int64) ([]int64, error) { + log := svc.log.WithField("method", "GetUserBoughtMedias") + + tbl := table.UserMedias + stmt := tbl. + SELECT(tbl.MediaID). + WHERE( + tbl.TenantID.EQ(Int(tenant)).AND( + tbl.UserID.EQ(Int(userID)), + ), + ) + log.Debug(stmt.Sql()) + + var mediaIDs []int64 + if err := stmt.QueryContext(ctx, svc.db, &mediaIDs); err != nil { + return nil, errors.Wrap(err, "query user bought medias") + } + + return mediaIDs, nil +} diff --git a/backend/modules/medias/service_test.go b/backend/modules/medias/service_test.go new file mode 100644 index 0000000..846da15 --- /dev/null +++ b/backend/modules/medias/service_test.go @@ -0,0 +1,280 @@ +package medias + +import ( + "context" + "testing" + + "backend/database/models/qvyun/public/model" + "backend/database/models/qvyun/public/table" + "backend/fixtures" + dbUtil "backend/pkg/db" + "backend/pkg/pg" + + . "github.com/go-jet/jet/v2/postgres" + "github.com/samber/lo" + . "github.com/smartystreets/goconvey/convey" +) + +func TestService_GetUserBoughtMedias(t *testing.T) { + Convey("TestService_GetUserBoughtMedias", t, func() { + db, err := fixtures.GetDB() + So(err, ShouldBeNil) + defer db.Close() + + So(dbUtil.TruncateAllTables(context.TODO(), db, "user_medias"), ShouldBeNil) + + Convey("insert some data", func() { + items := []model.UserMedias{ + {UserID: 1, TenantID: 1, MediaID: 1, Price: 10}, + {UserID: 1, TenantID: 1, MediaID: 2, Price: 10}, + {UserID: 1, TenantID: 1, MediaID: 3, Price: 10}, + } + + tbl := table.UserMedias + stmt := tbl.INSERT(tbl.UserID, tbl.TenantID, tbl.MediaID, tbl.Price).MODELS(items) + t.Log(stmt.DebugSql()) + + _, err := stmt.Exec(db) + So(err, ShouldBeNil) + + Convey("get user bought medias", func() { + svc := &Service{db: db} + So(svc.Prepare(), ShouldBeNil) + + ids, err := svc.GetUserBoughtMedias(context.TODO(), 1, 1) + So(err, ShouldBeNil) + + for _, id := range ids { + So(lo.Contains([]int64{1, 2, 3}, id), ShouldBeTrue) + } + }) + }) + }) +} + +func TestService_List(t *testing.T) { + Convey("TestService_list", t, func() { + db, err := fixtures.GetDB() + So(err, ShouldBeNil) + defer db.Close() + + Convey("truncate all tables", func() { + So(dbUtil.TruncateAllTables(context.TODO(), db, "medias", "media_resources", "user_medias"), ShouldBeNil) + }) + + Convey("insert user_medias data", func() { + items := []model.UserMedias{ + {UserID: 1, TenantID: 1, MediaID: 1, Price: 10}, + } + + tbl := table.UserMedias + stmt := tbl.INSERT(tbl.UserID, tbl.TenantID, tbl.MediaID, tbl.Price).MODELS(items) + + _, err := stmt.Exec(db) + So(err, ShouldBeNil) + }) + + Convey("insert medias data", func() { + items := []model.Medias{ + { + TenantID: 1, + Title: "title1", + Description: "hello", + Price: 100, + Publish: true, + }, + { + TenantID: 1, + Title: "title2", + Description: "hello", + Price: 100, + Publish: true, + }, + { + TenantID: 1, + Title: "title3", + Description: "hello", + Price: 100, + Publish: false, + }, + } + + tbl := table.Medias + stmt := tbl.INSERT( + tbl.TenantID, + tbl.Title, + tbl.Description, + tbl.Price, + tbl.Publish, + ).MODELS(items) + t.Log(stmt.DebugSql()) + + _, err := stmt.Exec(db) + So(err, ShouldBeNil) + }) + + Convey("create media's resources", func() { + items := []model.MediaResources{ + { + MediaID: 1, + Type: pg.MediaTypeVideo, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 1, + Type: pg.MediaTypeAudio, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 2, + Type: pg.MediaTypeVideo, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 2, + Type: pg.MediaTypeAudio, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 3, + Type: pg.MediaTypeVideo, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 3, + Type: pg.MediaTypeAudio, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + } + + tbl := table.MediaResources + stmt := tbl.INSERT( + tbl.MediaID, + tbl.Type, + tbl.Size, + tbl.Publish, + ).MODELS(items) + t.Log(stmt.DebugSql()) + + _, err := stmt.Exec(db) + So(err, ShouldBeNil) + }) + + Convey("get list", func() { + svc := &Service{db: db} + So(svc.Prepare(), ShouldBeNil) + + items, err := svc.List(context.TODO(), 1, 1, &ListFilter{ + Bought: lo.ToPtr(true), + }) + So(err, ShouldBeNil) + + t.Logf("items: %+v", items) + So(items, ShouldHaveLength, 1) + }) + }) +} + +func TestService_List1(t *testing.T) { + Convey("TestService_list", t, func() { + db, err := fixtures.GetDB() + So(err, ShouldBeNil) + defer db.Close() + + tbl := table.Medias + stmt := tbl. + SELECT(tbl.AllColumns). + WHERE( + tbl.Publish.EQ(Bool(true)).AND( + tbl.TenantID.EQ(Int(1)), + ), + ). + WHERE(tbl.ID.EQ(Int(1))). + ORDER_BY(tbl.ID.DESC()) + + t.Log(stmt.DebugSql()) + + type list struct { + Media model.Medias `alias:"ListItem.*"` + } + + var dest []ListItem + err = stmt.QueryContext(context.TODO(), db, &dest) + So(err, ShouldBeNil) + + t.Logf("dest: %+v", dest) + }) +} + +func TestService_DecorateListResources(t *testing.T) { + Convey("TestService_DecorateListResources", t, func() { + db, err := fixtures.GetDB() + So(err, ShouldBeNil) + + defer db.Close() + + Convey("truncate all tables", func() { + So(dbUtil.TruncateAllTables(context.TODO(), db, "medias", "media_resources"), ShouldBeNil) + }) + + Convey("create media's resources", func() { + items := []model.MediaResources{ + { + MediaID: 1, + Type: pg.MediaTypeVideo, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + { + MediaID: 2, + Type: pg.MediaTypeAudio, + Source: lo.ToPtr("http://www.baidu.com"), + Size: 100, + Publish: true, + }, + } + + tbl := table.MediaResources + stmt := tbl.INSERT( + tbl.MediaID, + tbl.Type, + tbl.Size, + tbl.Publish, + ).MODELS(items) + t.Log(stmt.DebugSql()) + + _, err := stmt.Exec(db) + So(err, ShouldBeNil) + }) + + Convey("decorate list resources", func() { + items := []ListItem{ + {Medias: model.Medias{ID: 1}}, + {Medias: model.Medias{ID: 2}}, + } + + svc := &Service{db: db} + So(svc.Prepare(), ShouldBeNil) + + items, err := svc.DecorateListResources(context.TODO(), 1, 1, items) + So(err, ShouldBeNil) + + for _, item := range items { + So(item.MediaResources, ShouldHaveLength, 1) + } + }) + }) +} diff --git a/backend/pkg/db/db.go b/backend/pkg/db/db.go index 7774f2e..106aa2f 100644 --- a/backend/pkg/db/db.go +++ b/backend/pkg/db/db.go @@ -19,7 +19,7 @@ func FromContext(ctx context.Context, db *sql.DB) qrm.DB { func TruncateAllTables(ctx context.Context, db *sql.DB, tableName ...string) error { for _, name := range tableName { - sql := fmt.Sprintf("TRUNCATE TABLE %s CASCADE", name) + sql := fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY", name) if _, err := db.ExecContext(ctx, sql); err != nil { return err } diff --git a/backend/pkg/db/pagination.go b/backend/pkg/db/pagination.go new file mode 100644 index 0000000..3a00cd6 --- /dev/null +++ b/backend/pkg/db/pagination.go @@ -0,0 +1,7 @@ +package db + +type Pagination struct { + Offset string `json:"offset"` + OffsetID int64 `json:"-"` + Action int `json:"action"` // action: 0 :加载更多 1:刷新 +} diff --git a/backend/pkg/pg/media.gen.go b/backend/pkg/pg/media.gen.go new file mode 100644 index 0000000..1532fe1 --- /dev/null +++ b/backend/pkg/pg/media.gen.go @@ -0,0 +1,179 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: - +// Revision: - +// Build Date: - +// Built By: - + +package pg + +import ( + "database/sql/driver" + "errors" + "fmt" + "strings" +) + +const ( + // MediaTypeVideo is a MediaType of type Video. + MediaTypeVideo MediaType = "video" + // MediaTypeAudio is a MediaType of type Audio. + MediaTypeAudio MediaType = "audio" + // MediaTypePdf is a MediaType of type Pdf. + MediaTypePdf MediaType = "pdf" +) + +var ErrInvalidMediaType = fmt.Errorf("not a valid MediaType, try [%s]", strings.Join(_MediaTypeNames, ", ")) + +var _MediaTypeNames = []string{ + string(MediaTypeVideo), + string(MediaTypeAudio), + string(MediaTypePdf), +} + +// MediaTypeNames returns a list of possible string values of MediaType. +func MediaTypeNames() []string { + tmp := make([]string, len(_MediaTypeNames)) + copy(tmp, _MediaTypeNames) + return tmp +} + +// MediaTypeValues returns a list of the values for MediaType +func MediaTypeValues() []MediaType { + return []MediaType{ + MediaTypeVideo, + MediaTypeAudio, + MediaTypePdf, + } +} + +// String implements the Stringer interface. +func (x MediaType) 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 MediaType) IsValid() bool { + _, err := ParseMediaType(string(x)) + return err == nil +} + +var _MediaTypeValue = map[string]MediaType{ + "video": MediaTypeVideo, + "audio": MediaTypeAudio, + "pdf": MediaTypePdf, +} + +// ParseMediaType attempts to convert a string to a MediaType. +func ParseMediaType(name string) (MediaType, error) { + if x, ok := _MediaTypeValue[name]; ok { + return x, nil + } + return MediaType(""), fmt.Errorf("%s is %w", name, ErrInvalidMediaType) +} + +var errMediaTypeNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *MediaType) Scan(value interface{}) (err error) { + if value == nil { + *x = MediaType("") + 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 = ParseMediaType(v) + case []byte: + *x, err = ParseMediaType(string(v)) + case MediaType: + *x = v + case *MediaType: + if v == nil { + return errMediaTypeNilPtr + } + *x = *v + case *string: + if v == nil { + return errMediaTypeNilPtr + } + *x, err = ParseMediaType(*v) + default: + return errors.New("invalid type for MediaType") + } + + return +} + +// Value implements the driver Valuer interface. +func (x MediaType) Value() (driver.Value, error) { + return x.String(), nil +} + +// Set implements the Golang flag.Value interface func. +func (x *MediaType) Set(val string) error { + v, err := ParseMediaType(val) + *x = v + return err +} + +// Get implements the Golang flag.Getter interface func. +func (x *MediaType) Get() interface{} { + return *x +} + +// Type implements the github.com/spf13/pFlag Value interface. +func (x *MediaType) Type() string { + return "MediaType" +} + +type NullMediaType struct { + MediaType MediaType + Valid bool +} + +func NewNullMediaType(val interface{}) (x NullMediaType) { + 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 *NullMediaType) Scan(value interface{}) (err error) { + if value == nil { + x.MediaType, x.Valid = MediaType(""), false + return + } + + err = x.MediaType.Scan(value) + x.Valid = (err == nil) + return +} + +// Value implements the driver Valuer interface. +func (x NullMediaType) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + // driver.Value accepts int64 for int values. + return string(x.MediaType), nil +} + +type NullMediaTypeStr struct { + NullMediaType +} + +func NewNullMediaTypeStr(val interface{}) (x NullMediaTypeStr) { + x.Scan(val) // yes, we ignore this error, it will just be an invalid value. + return +} + +// Value implements the driver Valuer interface. +func (x NullMediaTypeStr) Value() (driver.Value, error) { + if !x.Valid { + return nil, nil + } + return x.MediaType.String(), nil +} diff --git a/backend/pkg/pg/media.go b/backend/pkg/pg/media.go new file mode 100644 index 0000000..d4e03e3 --- /dev/null +++ b/backend/pkg/pg/media.go @@ -0,0 +1,9 @@ +package pg + +// swagger:enum MediaType +// ENUM( +// Video = "video", +// Audio = "audio", +// Pdf = "pdf", +// ) +type MediaType string