fix: issues

This commit is contained in:
Rogee
2025-01-10 11:42:12 +08:00
parent 1c7b603769
commit 0d35aa15de
46 changed files with 1822 additions and 113 deletions

View File

@@ -0,0 +1,174 @@
// Code generated by go-enum DO NOT EDIT.
// Version: -
// Revision: -
// Build Date: -
// Built By: -
package consts
import (
"database/sql/driver"
"errors"
"fmt"
"strings"
)
const (
// TokenTypeUser is a TokenType of type User.
TokenTypeUser TokenType = "__tu"
// TokenTypeTenant is a TokenType of type Tenant.
TokenTypeTenant TokenType = "__tt"
)
var ErrInvalidTokenType = fmt.Errorf("not a valid TokenType, try [%s]", strings.Join(_TokenTypeNames, ", "))
var _TokenTypeNames = []string{
string(TokenTypeUser),
string(TokenTypeTenant),
}
// TokenTypeNames returns a list of possible string values of TokenType.
func TokenTypeNames() []string {
tmp := make([]string, len(_TokenTypeNames))
copy(tmp, _TokenTypeNames)
return tmp
}
// TokenTypeValues returns a list of the values for TokenType
func TokenTypeValues() []TokenType {
return []TokenType{
TokenTypeUser,
TokenTypeTenant,
}
}
// String implements the Stringer interface.
func (x TokenType) 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 TokenType) IsValid() bool {
_, err := ParseTokenType(string(x))
return err == nil
}
var _TokenTypeValue = map[string]TokenType{
"__tu": TokenTypeUser,
"__tt": TokenTypeTenant,
}
// ParseTokenType attempts to convert a string to a TokenType.
func ParseTokenType(name string) (TokenType, error) {
if x, ok := _TokenTypeValue[name]; ok {
return x, nil
}
return TokenType(""), fmt.Errorf("%s is %w", name, ErrInvalidTokenType)
}
var errTokenTypeNilPtr = errors.New("value pointer is nil") // one per type for package clashes
// Scan implements the Scanner interface.
func (x *TokenType) Scan(value interface{}) (err error) {
if value == nil {
*x = TokenType("")
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 = ParseTokenType(v)
case []byte:
*x, err = ParseTokenType(string(v))
case TokenType:
*x = v
case *TokenType:
if v == nil {
return errTokenTypeNilPtr
}
*x = *v
case *string:
if v == nil {
return errTokenTypeNilPtr
}
*x, err = ParseTokenType(*v)
default:
return errors.New("invalid type for TokenType")
}
return
}
// Value implements the driver Valuer interface.
func (x TokenType) Value() (driver.Value, error) {
return x.String(), nil
}
// Set implements the Golang flag.Value interface func.
func (x *TokenType) Set(val string) error {
v, err := ParseTokenType(val)
*x = v
return err
}
// Get implements the Golang flag.Getter interface func.
func (x *TokenType) Get() interface{} {
return *x
}
// Type implements the github.com/spf13/pFlag Value interface.
func (x *TokenType) Type() string {
return "TokenType"
}
type NullTokenType struct {
TokenType TokenType
Valid bool
}
func NewNullTokenType(val interface{}) (x NullTokenType) {
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 *NullTokenType) Scan(value interface{}) (err error) {
if value == nil {
x.TokenType, x.Valid = TokenType(""), false
return
}
err = x.TokenType.Scan(value)
x.Valid = (err == nil)
return
}
// Value implements the driver Valuer interface.
func (x NullTokenType) Value() (driver.Value, error) {
if !x.Valid {
return nil, nil
}
// driver.Value accepts int64 for int values.
return string(x.TokenType), nil
}
type NullTokenTypeStr struct {
NullTokenType
}
func NewNullTokenTypeStr(val interface{}) (x NullTokenTypeStr) {
x.Scan(val) // yes, we ignore this error, it will just be an invalid value.
return
}
// Value implements the driver Valuer interface.
func (x NullTokenTypeStr) Value() (driver.Value, error) {
if !x.Valid {
return nil, nil
}
return x.TokenType.String(), nil
}

View File

@@ -0,0 +1,7 @@
package consts
// swagger:enum TokenType
// ENUM(
// User = "__tu", Tenant = "__tt"
// )
type TokenType string

View File

@@ -1,71 +0,0 @@
package events
import (
"encoding/json"
"fmt"
"time"
"git.ipao.vip/rogeecn/atom/contracts"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/sirupsen/logrus"
)
var _ contracts.EventHandler = (*UserRegister)(nil)
type Event struct {
ID int `json:"id"`
}
type ProcessedEvent struct {
ProcessedID int `json:"processed_id"`
Time time.Time `json:"time"`
}
// @provider(event)
type UserRegister struct {
log *logrus.Entry `inject:"false"`
}
func (u *UserRegister) Prepare() error {
return nil
}
// Handler implements contracts.EventHandler.
func (u *UserRegister) Handler(msg *message.Message) ([]*message.Message, error) {
consumedPayload := Event{}
err := json.Unmarshal(msg.Payload, &consumedPayload)
if err != nil {
// When a handler returns an error, the default behavior is to send a Nack (negative-acknowledgement).
// The message will be processed again.
//
// You can change the default behaviour by using middlewares, like Retry or PoisonQueue.
// You can also implement your own middleware.
return nil, err
}
fmt.Printf("received event %+v\n", consumedPayload)
newPayload, err := json.Marshal(ProcessedEvent{
ProcessedID: consumedPayload.ID,
Time: time.Now(),
})
if err != nil {
return nil, err
}
newMessage := message.NewMessage(watermill.NewUUID(), newPayload)
return nil, nil
return []*message.Message{newMessage}, nil
}
// PublishToTopic implements contracts.EventHandler.
func (u *UserRegister) PublishToTopic() string {
return "event:processed"
}
// Topic implements contracts.EventHandler.
func (u *UserRegister) Topic() string {
return "event:user-register"
}

View File

@@ -1,7 +1,7 @@
package events
import (
"backend/providers/events"
"backend/providers/event"
"git.ipao.vip/rogeecn/atom"
"git.ipao.vip/rogeecn/atom/container"
@@ -11,7 +11,7 @@ import (
func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func(
__event *events.PubSub,
__event *event.PubSub,
) (contracts.Initial, error) {
obj := &UserRegister{}
if err := obj.Prepare(); err != nil {

View File

@@ -0,0 +1,174 @@
// 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"
)
var ErrInvalidTopic = fmt.Errorf("not a valid Topic, try [%s]", strings.Join(_TopicNames, ", "))
var _TopicNames = []string{
string(TopicProcessed),
string(TopicUserRegister),
}
// 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,
}
}
// 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,
}
// 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
}

View File

@@ -0,0 +1,10 @@
package events
// swagger:enum Topic
// ENUM(
//
// Processed = "event:processed"
// UserRegister = "user:register"
//
// )
type Topic string

View File

@@ -0,0 +1,52 @@
package events
import (
"encoding/json"
"git.ipao.vip/rogeecn/atom/contracts"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/sirupsen/logrus"
)
var (
_ contracts.EventHandler = (*UserRegister)(nil)
_ contracts.EventPublisher = (*UserRegister)(nil)
)
// @provider(event)
type UserRegister struct {
log *logrus.Entry `inject:"false" json:"-"`
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)
}
// PublishToTopic implements contracts.EventHandler.
func (e *UserRegister) PublishToTopic() string {
return TopicProcessed.String()
}
// Topic implements contracts.EventHandler.
func (e *UserRegister) Topic() string {
return TopicUserRegister.String()
}
// Handler implements contracts.EventHandler.
func (e *UserRegister) Handler(msg *message.Message) ([]*message.Message, error) {
var payload UserRegister
err := json.Unmarshal(msg.Payload, &payload)
if err != nil {
return nil, err
}
e.log.Infof("received event %+v\n", payload)
return nil, nil
}

View File

@@ -0,0 +1,120 @@
package auth
import (
"fmt"
"net/url"
"strings"
"time"
"backend/app/consts"
"backend/app/http/users"
"backend/providers/jwt"
"backend/providers/otel"
"backend/providers/wechat"
"github.com/gofiber/fiber/v3"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
const StatePrefix = "sns_basic_auth"
// @provider
type Controller struct {
svc *Service
userSvc *users.Service
jwt *jwt.JWT
wechat *wechat.Client
log *log.Entry `inject:"false"`
}
func (ctl *Controller) Prepare() error {
ctl.log = log.WithField("module", "auth.Controller")
return nil
}
// @Router /v1/auth/wechat/jump/:tenant [get]
// @Bind tenant path
// @Bind redirectUri query
func (ctl *Controller) JumpToAuth(ctx fiber.Ctx, tenant, redirectUri string) error {
_, span := otel.Start(ctx.Context(), "auth.controller.wechat")
defer span.End()
ctl.log.Debugf("%s, query: %v", ctx.OriginalURL(), ctx.Queries())
paramRedirect := ctx.Query("redirect")
// 添加 redirect 参数
u, err := url.Parse(string(ctx.Request().URI().FullURI()))
if err != nil {
return err
}
query := u.Query()
query.Set("redirect", paramRedirect)
u.RawQuery = query.Encode()
u.Path = "/v1/auth/wechat/login/" + tenant
fullUrl := u.String()
ctl.log.WithField("module", "middleware.SilentAuth").Debug("redirect_uri: ", fullUrl)
to, err := ctl.wechat.ScopeAuthorizeURL(
wechat.ScopeAuthorizeURLWithRedirectURI(fullUrl),
wechat.ScopeAuthorizeURLWithState(fmt.Sprintf("%s-%d", StatePrefix, time.Now().UnixNano())),
)
if err != nil {
return errors.Wrap(err, "failed to get wechat auth url")
}
return ctx.Redirect().To(to.String())
}
// @Router /v1/auth/login/:tenant [get]
// @Bind tenant path
// @Bind code query
// @Bind state query
// @Bind redirectUri query
func (ctl *Controller) Login(ctx fiber.Ctx, code, state, tenant, redirectUri string) error {
ctl.log.Debugf("code: %s, state: %s", code, state)
ctx.Cookie(&fiber.Cookie{
Name: consts.TokenTypeUser.String(),
Value: "",
Expires: time.Now().Add(12 * time.Hour),
HTTPOnly: true,
})
// get the openid
token, err := ctl.wechat.AuthorizeCode2Token(code)
if err != nil {
return errors.Wrap(err, "failed to get openid")
}
ctl.log.Debugf("tokenInfo %+v", token)
user, err := ctl.userSvc.GetOrNewFromChannel(ctx.Context(), consts.AuthChannelWeChat, token.OpenID, tenant)
if err != nil {
return errors.Wrap(err, "failed to get user")
}
claim := c.jwt.CreateClaims(jwt.BaseClaims{
OpenID: user.OpenID,
Tenant: tenantSlug,
UserID: user.ID,
TenantID: tenant.ID,
})
jwtToken, err = c.jwt.CreateToken(claim)
if err != nil {
return errors.Wrap(err, "failed to create token")
}
ctx.Cookie(&fiber.Cookie{
Name: "token",
Value: jwtToken,
Expires: time.Now().Add(6 * time.Hour),
HTTPOnly: true,
})
html := strings.ReplaceAll(string(b), "{{JWT}}", jwtToken)
return ctx.SendString(html)
return ctx.Redirect().To(paramRedirect)
}

View File

@@ -0,0 +1 @@
package auth

View File

@@ -0,0 +1,50 @@
package auth
import (
"database/sql"
"backend/app/http/users"
"backend/providers/jwt"
"backend/providers/wechat"
"git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/utils/opt"
)
func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func(
jwt *jwt.JWT,
svc *Service,
userSvc *users.Service,
wechat *wechat.Client,
) (*Controller, error) {
obj := &Controller{
jwt: jwt,
svc: svc,
userSvc: userSvc,
wechat: wechat,
}
if err := obj.Prepare(); err != nil {
return nil, err
}
return obj, nil
}); 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
}

View File

@@ -0,0 +1,20 @@
package auth
import (
"database/sql"
. "github.com/go-jet/jet/v2/postgres"
log "github.com/sirupsen/logrus"
)
// @provider:except
type Service struct {
db *sql.DB
log *log.Entry `inject:"false"`
}
func (svc *Service) Prepare() error {
svc.log = log.WithField("module", "auth.service")
_ = Int(1)
return nil
}

View File

@@ -0,0 +1,37 @@
package auth
import (
"testing"
"backend/app/service/testx"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/suite"
"go.uber.org/dig"
)
type ServiceInjectParams struct {
dig.In
Svc *Service
}
type ServiceTestSuite struct {
suite.Suite
ServiceInjectParams
}
func Test_DiscoverMedias(t *testing.T) {
providers := testx.Default().With(
Provide,
)
testx.Serve(providers, t, func(params ServiceInjectParams) {
suite.Run(t, &ServiceTestSuite{ServiceInjectParams: params})
})
}
func (s *ServiceTestSuite) Test_Service() {
Convey("Test Service", s.T(), func() {
So(s.Svc, ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,30 @@
package tenants
import (
log "github.com/sirupsen/logrus"
)
// @provider
type Controller struct {
svc *Service
log *log.Entry `inject:"false"`
}
func (c *Controller) Prepare() error {
c.log = log.WithField("module", "tenants.Controller")
return nil
}
// Test godoc
//
// @Summary Test
// @Description Test
// @Tags Test
// @Accept json
// @Produce json
// @Param id path int true "AccountID"
// @Param queryFilter query dto.AlarmListQuery true "AlarmListQueryFilter"
// @Param pageFilter query common.PageQueryFilter true "PageQueryFilter"
// @Param sortFilter query common.SortQueryFilter true "SortQueryFilter"
// @Success 200 {object} common.PageDataResponse{list=dto.AlarmItem}
// @Router /v1/test/:id<int> [get]

View File

@@ -0,0 +1 @@
package tenants

View File

@@ -0,0 +1,40 @@
package tenants
import (
"database/sql"
"git.ipao.vip/rogeecn/atom/container"
"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,
}
if err := obj.Prepare(); err != nil {
return nil, err
}
return obj, nil
}); 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
}

View File

@@ -0,0 +1,20 @@
package tenants
import (
"database/sql"
. "github.com/go-jet/jet/v2/postgres"
log "github.com/sirupsen/logrus"
)
// @provider:except
type Service struct {
db *sql.DB
log *log.Entry `inject:"false"`
}
func (svc *Service) Prepare() error {
svc.log = log.WithField("module", "tenants.service")
_ = Int(1)
return nil
}

View File

@@ -0,0 +1,37 @@
package tenants
import (
"testing"
"backend/app/service/testx"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/suite"
"go.uber.org/dig"
)
type ServiceInjectParams struct {
dig.In
Svc *Service
}
type ServiceTestSuite struct {
suite.Suite
ServiceInjectParams
}
func Test_DiscoverMedias(t *testing.T) {
providers := testx.Default().With(
Provide,
)
testx.Serve(providers, t, func(params ServiceInjectParams) {
suite.Run(t, &ServiceTestSuite{ServiceInjectParams: params})
})
}
func (s *ServiceTestSuite) Test_Service() {
Convey("Test Service", s.T(), func() {
So(s.Svc, ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,30 @@
package users
import (
log "github.com/sirupsen/logrus"
)
// @provider
type Controller struct {
svc *Service
log *log.Entry `inject:"false"`
}
func (c *Controller) Prepare() error {
c.log = log.WithField("module", "users.Controller")
return nil
}
// Test godoc
//
// @Summary Test
// @Description Test
// @Tags Test
// @Accept json
// @Produce json
// @Param id path int true "AccountID"
// @Param queryFilter query dto.AlarmListQuery true "AlarmListQueryFilter"
// @Param pageFilter query common.PageQueryFilter true "PageQueryFilter"
// @Param sortFilter query common.SortQueryFilter true "SortQueryFilter"
// @Success 200 {object} common.PageDataResponse{list=dto.AlarmItem}
// @Router /v1/test/:id<int> [get]

View File

@@ -0,0 +1 @@
package users

View File

@@ -0,0 +1,40 @@
package users
import (
"database/sql"
"git.ipao.vip/rogeecn/atom/container"
"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,
}
if err := obj.Prepare(); err != nil {
return nil, err
}
return obj, nil
}); 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
}

View File

@@ -0,0 +1,201 @@
package users
import (
"context"
"database/sql"
"time"
"backend/app/events"
"backend/database/fields"
"backend/database/models/qvyun_v2/public/model"
"backend/database/models/qvyun_v2/public/table"
"backend/pkg/oauth"
"backend/providers/event"
"backend/providers/otel"
. "github.com/go-jet/jet/v2/postgres"
"github.com/samber/lo"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.15.0"
"golang.org/x/crypto/bcrypt"
)
// @provider:except
type Service struct {
db *sql.DB
event *event.PubSub
log *log.Entry `inject:"false"`
}
func (svc *Service) Prepare() error {
svc.log = log.WithField("module", "users.service")
_ = Int(1)
return nil
}
// GetUsersByOpenID Get user by open id
func (svc *Service) GetUserByOpenID(ctx context.Context, channel fields.AuthChannel, openID string) (*model.Users, error) {
_, span := otel.Start(ctx, "users.service.GetUsersByOpenID")
defer span.End()
userId, err := svc.GetUserIDByOpenID(ctx, channel, openID)
if err != nil {
// span 添加用户不存在事件
span.AddEvent("user not found")
return nil, err
}
tbl := table.Users
stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(userId)))
span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql()))
var user model.Users
if err := stmt.QueryContext(ctx, svc.db, &user); err != nil {
return nil, err
}
return &user, nil
}
func (svc *Service) GetUserIDByOpenID(ctx context.Context, channel fields.AuthChannel, openID string) (int64, error) {
_, span := otel.Start(ctx, "users.service.GetUserIDByOpenID")
defer span.End()
span.SetAttributes(
attribute.String("channel", channel.String()),
attribute.String("openID", openID),
)
tbl := table.UserOauths
stmt := tbl.
SELECT(tbl.UserID.AS("user_id")).
WHERE(
tbl.Channel.EQ(Int16(int16(channel))).
AND(tbl.OpenID.EQ(String(openID))),
)
span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql()))
var result struct {
UserID int64
}
if err := stmt.QueryContext(ctx, svc.db, &result); err != nil {
return 0, err
}
return result.UserID, nil
}
// CreateUser
func (svc *Service) CreateUser(ctx context.Context, user *model.Users) (*model.Users, error) {
_, span := otel.Start(ctx, "users.service.CreateUser")
defer span.End()
span.SetAttributes(
attribute.String("user.username", user.Username),
attribute.String("user.email", user.Email),
attribute.String("user.phone", user.Phone),
)
if user.CreatedAt.IsZero() {
user.CreatedAt = time.Now()
}
if user.UpdatedAt.IsZero() {
user.UpdatedAt = time.Now()
}
user.Status = int16(fields.UserStatusPending)
// use bcrypt to hash password
pwd, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user.Password = string(pwd)
tbl := table.Users
stmt := tbl.INSERT(tbl.MutableColumns).MODEL(user).ON_CONFLICT(tbl.Email, tbl.Phone, tbl.Username).DO_NOTHING().RETURNING(tbl.AllColumns)
span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql()))
var m model.Users
if err = stmt.QueryContext(ctx, svc.db, &m); err != nil {
return nil, err
}
// if user created successfully, trigger event
span.AddEvent("user created")
svc.event.Publish(&events.UserRegister{ID: m.ID})
return &m, err
}
// GetUserByID
func (svc *Service) GetUserByID(ctx context.Context, userID int64) (*model.Users, error) {
_, span := otel.Start(ctx, "users.service.GetUserByID")
defer span.End()
span.SetAttributes(
attribute.Int64("user.id", userID),
)
tbl := table.Users
stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(userID)))
span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql()))
var user model.Users
if err := stmt.QueryContext(ctx, svc.db, &user); err != nil {
return nil, err
}
return &user, nil
}
// AttachUserOAuth
func (svc *Service) AttachUserOAuth(ctx context.Context, user *model.Users, channel fields.AuthChannel, oauthInfo oauth.OAuthInfo) error {
_, span := otel.Start(ctx, "users.service.AttachUserOAuth")
defer span.End()
span.SetAttributes(
attribute.Int64("user.id", user.ID),
attribute.String("channel", channel.String()),
attribute.String("openID", oauthInfo.GetOpenID()),
)
m := &model.UserOauths{
ID: 0,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: nil,
Channel: channel,
UserID: user.ID,
UnionID: lo.ToPtr(oauthInfo.GetUnionID()),
OpenID: oauthInfo.GetOpenID(),
AccessToken: oauthInfo.GetAccessToken(),
RefreshToken: oauthInfo.GetRefreshToken(),
ExpireAt: oauthInfo.GetExpiredAt(),
Meta: new(string),
}
tbl := table.UserOauths
stmt := tbl.
INSERT(tbl.MutableColumns).
MODEL(m).
ON_CONFLICT(tbl.Channel, tbl.UserID).
DO_UPDATE(
SET(
tbl.UnionID.SET(String(oauthInfo.GetUnionID())),
tbl.OpenID.SET(String(oauthInfo.GetOpenID())),
tbl.AccessToken.SET(String(oauthInfo.GetAccessToken())),
tbl.RefreshToken.SET(String(oauthInfo.GetRefreshToken())),
tbl.ExpireAt.SET(TimestampT(oauthInfo.GetExpiredAt())),
),
)
span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql()))
if _, err := stmt.ExecContext(ctx, svc.db); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,109 @@
package users
import (
"context"
"testing"
"time"
"backend/app/service/testx"
"backend/database"
"backend/database/fields"
"backend/database/models/qvyun_v2/public/model"
"backend/database/models/qvyun_v2/public/table"
"git.ipao.vip/rogeecn/atom/contracts"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/suite"
"go.uber.org/dig"
)
type ServiceInjectParams struct {
dig.In
Initials []contracts.Initial `group:"initials"`
Svc *Service
}
type ServiceTestSuite struct {
suite.Suite
ServiceInjectParams
}
func Test_DiscoverMedias(t *testing.T) {
providers := testx.Default().With(
Provide,
)
testx.Serve(providers, t, func(params ServiceInjectParams) {
suite.Run(t, &ServiceTestSuite{ServiceInjectParams: params})
})
}
func (s *ServiceTestSuite) Test_GetUserIDByOpenID() {
Convey("Test GetUserIDByOpenID", s.T(), func() {
v, err := fields.AuthChannelWeChat.Value()
So(err, ShouldBeNil)
So(v, ShouldEqual, 1)
Convey("truncate tables", func() {
err := database.Truncate(context.Background(), s.Svc.db, table.UserOauths.TableName())
So(err, ShouldBeNil)
})
Convey("insert data", func() {
m := model.UserOauths{
Channel: fields.AuthChannelWeChat,
UserID: 1,
OpenID: "test_open_id",
AccessKey: "test_access_key",
AccessToken: "test_access_token",
RefreshToken: "test_refresh_token",
ExpireAt: time.Now().Add(time.Hour),
}
tbl := table.UserOauths
stmt := tbl.INSERT(
tbl.MutableColumns.Except(
tbl.CreatedAt,
tbl.UpdatedAt,
tbl.DeletedAt,
tbl.UnionID,
tbl.Meta,
),
).MODEL(m)
s.T().Log(stmt.Sql())
_, err := stmt.ExecContext(context.Background(), s.Svc.db)
So(err, ShouldBeNil)
})
Convey("get user id by open id", func() {
userID, err := s.Svc.GetUserIDByOpenID(context.Background(), fields.AuthChannelWeChat, "test_open_id")
So(err, ShouldBeNil)
So(userID, ShouldEqual, 1)
})
})
}
// CreateUser
func (s *ServiceTestSuite) Test_CreateUser() {
Convey("Test CreateUser", s.T(), func() {
Convey("truncate tables", func() {
err := database.Truncate(context.Background(), s.Svc.db, table.Users.TableName())
So(err, ShouldBeNil)
})
Convey("create user", func() {
user := &model.Users{
Email: "test@qq.com",
Phone: "12345678901",
Username: "test",
Password: "test",
Age: 18,
}
_, err := s.Svc.CreateUser(context.Background(), user)
So(err, ShouldBeNil)
})
})
}

View File

@@ -1,13 +1,28 @@
package middlewares
import (
"backend/providers/app"
"backend/providers/jwt"
"backend/providers/storage"
"backend/providers/wechat"
"git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/utils/opt"
)
func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func() (*Middlewares, error) {
obj := &Middlewares{}
if err := container.Container.Provide(func(
app *app.Config,
client *wechat.Client,
jwt *jwt.JWT,
storagePath *storage.Config,
) (*Middlewares, error) {
obj := &Middlewares{
app: app,
client: client,
jwt: jwt,
storagePath: storagePath,
}
if err := obj.Prepare(); err != nil {
return nil, err
}

View File

@@ -6,7 +6,7 @@ import (
"backend/app/events"
"backend/app/service"
"backend/providers/app"
providerEvents "backend/providers/events"
"backend/providers/event"
"backend/providers/postgres"
"git.ipao.vip/rogeecn/atom"
@@ -41,7 +41,7 @@ type Service struct {
dig.In
App *app.Config
PubSub *providerEvents.PubSub
PubSub *event.PubSub
Initials []contracts.Initial `group:"initials"`
}

View File

@@ -17,6 +17,7 @@ import (
"git.ipao.vip/rogeecn/atom"
"git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/contracts"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/favicon"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -69,6 +70,10 @@ func Serve(cmd *cobra.Command, args []string) error {
svc.Http.Engine.Get("/swagger/*", swagger.HandlerDefault)
}
svc.Http.Engine.Get("MP_verify_dEF9kn8rJlBsuLKk.txt", func(c fiber.Ctx) error {
return c.SendString("dEF9kn8rJlBsuLKk")
})
svc.Http.Engine.Use(svc.Middlewares.WeChatVerify)
svc.Http.Engine.Use(errorx.Middleware)
svc.Http.Engine.Use(favicon.New(favicon.Config{

View File

@@ -2,7 +2,7 @@ package service
import (
"backend/providers/app"
"backend/providers/events"
"backend/providers/event"
"git.ipao.vip/rogeecn/atom/container"
)
@@ -10,6 +10,6 @@ import (
func Default(providers ...container.ProviderContainer) container.Providers {
return append(container.Providers{
app.DefaultProvider(),
events.DefaultProvider(),
event.DefaultProvider(),
}, providers...)
}

View File

@@ -4,18 +4,27 @@ import (
"os"
"testing"
"backend/providers/otel"
"backend/providers/postgres"
"git.ipao.vip/rogeecn/atom"
"git.ipao.vip/rogeecn/atom/container"
"github.com/rogeecn/fabfile"
log "github.com/sirupsen/logrus"
. "github.com/smartystreets/goconvey/convey"
)
func Default(providers ...container.ProviderContainer) container.Providers {
return append(container.Providers{}, providers...)
return append(container.Providers{
otel.DefaultProvider(),
postgres.DefaultProvider(),
}, providers...)
}
func Serve(providers container.Providers, t *testing.T, invoke any) {
Convey("tests boot up", t, func() {
log.SetLevel(log.DebugLevel)
file := fabfile.MustFind("config.toml")
localEnv := os.Getenv("ENV_LOCAL")

View File

@@ -0,0 +1,277 @@
// 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 (
// AuthChannelWeChat is a AuthChannel of type WeChat.
AuthChannelWeChat AuthChannel = iota + 1
// AuthChannelWeiBo is a AuthChannel of type WeiBo.
AuthChannelWeiBo
// AuthChannelQQ is a AuthChannel of type QQ.
AuthChannelQQ
// AuthChannelGithub is a AuthChannel of type Github.
AuthChannelGithub
// AuthChannelGoogle is a AuthChannel of type Google.
AuthChannelGoogle
// AuthChannelFacebook is a AuthChannel of type Facebook.
AuthChannelFacebook
// AuthChannelTwitter is a AuthChannel of type Twitter.
AuthChannelTwitter
// AuthChannelLinkedIn is a AuthChannel of type LinkedIn.
AuthChannelLinkedIn
// AuthChannelApple is a AuthChannel of type Apple.
AuthChannelApple
)
var ErrInvalidAuthChannel = fmt.Errorf("not a valid AuthChannel, try [%s]", strings.Join(_AuthChannelNames, ", "))
const _AuthChannelName = "WeChatWeiBoQQGithubGoogleFacebookTwitterLinkedInApple"
var _AuthChannelNames = []string{
_AuthChannelName[0:6],
_AuthChannelName[6:11],
_AuthChannelName[11:13],
_AuthChannelName[13:19],
_AuthChannelName[19:25],
_AuthChannelName[25:33],
_AuthChannelName[33:40],
_AuthChannelName[40:48],
_AuthChannelName[48:53],
}
// AuthChannelNames returns a list of possible string values of AuthChannel.
func AuthChannelNames() []string {
tmp := make([]string, len(_AuthChannelNames))
copy(tmp, _AuthChannelNames)
return tmp
}
// AuthChannelValues returns a list of the values for AuthChannel
func AuthChannelValues() []AuthChannel {
return []AuthChannel{
AuthChannelWeChat,
AuthChannelWeiBo,
AuthChannelQQ,
AuthChannelGithub,
AuthChannelGoogle,
AuthChannelFacebook,
AuthChannelTwitter,
AuthChannelLinkedIn,
AuthChannelApple,
}
}
var _AuthChannelMap = map[AuthChannel]string{
AuthChannelWeChat: _AuthChannelName[0:6],
AuthChannelWeiBo: _AuthChannelName[6:11],
AuthChannelQQ: _AuthChannelName[11:13],
AuthChannelGithub: _AuthChannelName[13:19],
AuthChannelGoogle: _AuthChannelName[19:25],
AuthChannelFacebook: _AuthChannelName[25:33],
AuthChannelTwitter: _AuthChannelName[33:40],
AuthChannelLinkedIn: _AuthChannelName[40:48],
AuthChannelApple: _AuthChannelName[48:53],
}
// String implements the Stringer interface.
func (x AuthChannel) String() string {
if str, ok := _AuthChannelMap[x]; ok {
return str
}
return fmt.Sprintf("AuthChannel(%d)", x)
}
// IsValid provides a quick way to determine if the typed value is
// part of the allowed enumerated values
func (x AuthChannel) IsValid() bool {
_, ok := _AuthChannelMap[x]
return ok
}
var _AuthChannelValue = map[string]AuthChannel{
_AuthChannelName[0:6]: AuthChannelWeChat,
_AuthChannelName[6:11]: AuthChannelWeiBo,
_AuthChannelName[11:13]: AuthChannelQQ,
_AuthChannelName[13:19]: AuthChannelGithub,
_AuthChannelName[19:25]: AuthChannelGoogle,
_AuthChannelName[25:33]: AuthChannelFacebook,
_AuthChannelName[33:40]: AuthChannelTwitter,
_AuthChannelName[40:48]: AuthChannelLinkedIn,
_AuthChannelName[48:53]: AuthChannelApple,
}
// ParseAuthChannel attempts to convert a string to a AuthChannel.
func ParseAuthChannel(name string) (AuthChannel, error) {
if x, ok := _AuthChannelValue[name]; ok {
return x, nil
}
return AuthChannel(0), fmt.Errorf("%s is %w", name, ErrInvalidAuthChannel)
}
var errAuthChannelNilPtr = errors.New("value pointer is nil") // one per type for package clashes
// Scan implements the Scanner interface.
func (x *AuthChannel) Scan(value interface{}) (err error) {
if value == nil {
*x = AuthChannel(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 = AuthChannel(v)
case string:
*x, err = ParseAuthChannel(v)
if err != nil {
// try parsing the integer value as a string
if val, verr := strconv.Atoi(v); verr == nil {
*x, err = AuthChannel(val), nil
}
}
case []byte:
*x, err = ParseAuthChannel(string(v))
if err != nil {
// try parsing the integer value as a string
if val, verr := strconv.Atoi(string(v)); verr == nil {
*x, err = AuthChannel(val), nil
}
}
case AuthChannel:
*x = v
case int:
*x = AuthChannel(v)
case *AuthChannel:
if v == nil {
return errAuthChannelNilPtr
}
*x = *v
case uint:
*x = AuthChannel(v)
case uint64:
*x = AuthChannel(v)
case *int:
if v == nil {
return errAuthChannelNilPtr
}
*x = AuthChannel(*v)
case *int64:
if v == nil {
return errAuthChannelNilPtr
}
*x = AuthChannel(*v)
case float64: // json marshals everything as a float64 if it's a number
*x = AuthChannel(v)
case *float64: // json marshals everything as a float64 if it's a number
if v == nil {
return errAuthChannelNilPtr
}
*x = AuthChannel(*v)
case *uint:
if v == nil {
return errAuthChannelNilPtr
}
*x = AuthChannel(*v)
case *uint64:
if v == nil {
return errAuthChannelNilPtr
}
*x = AuthChannel(*v)
case *string:
if v == nil {
return errAuthChannelNilPtr
}
*x, err = ParseAuthChannel(*v)
if err != nil {
// try parsing the integer value as a string
if val, verr := strconv.Atoi(*v); verr == nil {
*x, err = AuthChannel(val), nil
}
}
}
return
}
// Value implements the driver Valuer interface.
func (x AuthChannel) Value() (driver.Value, error) {
return int64(x), nil
}
// Set implements the Golang flag.Value interface func.
func (x *AuthChannel) Set(val string) error {
v, err := ParseAuthChannel(val)
*x = v
return err
}
// Get implements the Golang flag.Getter interface func.
func (x *AuthChannel) Get() interface{} {
return *x
}
// Type implements the github.com/spf13/pFlag Value interface.
func (x *AuthChannel) Type() string {
return "AuthChannel"
}
type NullAuthChannel struct {
AuthChannel AuthChannel
Valid bool
}
func NewNullAuthChannel(val interface{}) (x NullAuthChannel) {
x.Scan(val) // yes, we ignore this error, it will just be an invalid value.
return
}
// Scan implements the Scanner interface.
func (x *NullAuthChannel) Scan(value interface{}) (err error) {
if value == nil {
x.AuthChannel, x.Valid = AuthChannel(0), false
return
}
err = x.AuthChannel.Scan(value)
x.Valid = (err == nil)
return
}
// Value implements the driver Valuer interface.
func (x NullAuthChannel) Value() (driver.Value, error) {
if !x.Valid {
return nil, nil
}
// driver.Value accepts int64 for int values.
return int64(x.AuthChannel), nil
}
type NullAuthChannelStr struct {
NullAuthChannel
}
func NewNullAuthChannelStr(val interface{}) (x NullAuthChannelStr) {
x.Scan(val) // yes, we ignore this error, it will just be an invalid value.
return
}
// Value implements the driver Valuer interface.
func (x NullAuthChannelStr) Value() (driver.Value, error) {
if !x.Valid {
return nil, nil
}
return x.AuthChannel.String(), nil
}

View File

@@ -0,0 +1,5 @@
package fields
// swagger:enum AuthChannel
// ENUM( WeChat = 1 , WeiBo, QQ, Github, Google, Facebook, Twitter, LinkedIn, Apple)
type AuthChannel int16

View File

@@ -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
}

View File

@@ -0,0 +1,5 @@
package fields
// swagger:enum UserStatus
// ENUM( Pending, Verified, Blocked)
type UserStatus int16

View File

@@ -33,7 +33,6 @@ CREATE TABLE
user_id INT8 NOT NULL,
union_id VARCHAR(128),
open_id VARCHAR(128) NOT NULL UNIQUE,
access_key VARCHAR(256) NOT NULL default '',
access_token VARCHAR(256) NOT NULL default '',
refresh_token VARCHAR(256) NOT NULL default '',
expire_at timestamp NOT NULL,

View File

@@ -8,6 +8,7 @@
package model
import (
"backend/database/fields"
"time"
)
@@ -16,7 +17,7 @@ type UserOauths struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at"`
Channel int16 `json:"channel"`
Channel fields.AuthChannel `json:"channel"`
UserID int64 `json:"user_id"`
UnionID *string `json:"union_id"`
OpenID string `json:"open_id"`

View File

@@ -5,7 +5,9 @@ ignores:
- river_job
- river_leader
- river_queue
# types:
# users: # table name
# meta: UserMeta
# meta: Json[UserMeta]
types:
users: # table name
status: UserStatus
user_oauths:
channel: AuthChannel

View File

@@ -1,8 +0,0 @@
package consts
// Format
//
// // swagger:enum CacheKey
// // ENUM(
// // VerifyCode = "code:__CHANNEL__:%s",
// // )

View File

@@ -0,0 +1,11 @@
package oauth
import "time"
type OAuthInfo interface {
GetOpenID() string
GetUnionID() string
GetAccessToken() string
GetRefreshToken() string
GetExpiredAt() time.Time
}

View File

@@ -0,0 +1,39 @@
package oauth
import "time"
var _ OAuthInfo = (*WechatOAuthInfo)(nil)
type WechatOAuthInfo struct {
Scope string `json:"scope,omitempty"`
OpenID string `json:"openid,omitempty"`
UnionID string `json:"unionid,omitempty"`
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int64 `json:"expires_in,omitempty"`
}
// GetAccessToken implements OAuthInfo.
func (w *WechatOAuthInfo) GetAccessToken() string {
return w.AccessToken
}
// GetExpiredAt implements OAuthInfo.
func (w *WechatOAuthInfo) GetExpiredAt() time.Time {
return time.Now().Add(time.Duration(w.ExpiresIn) * time.Second)
}
// GetOpenID implements OAuthInfo.
func (w *WechatOAuthInfo) GetOpenID() string {
return w.OpenID
}
// GetRefreshToken implements OAuthInfo.
func (w *WechatOAuthInfo) GetRefreshToken() string {
return w.RefreshToken
}
// GetUnionID implements OAuthInfo.
func (w *WechatOAuthInfo) GetUnionID() string {
return w.UnionID
}

View File

@@ -1,10 +1,12 @@
package events
package event
import (
"context"
"git.ipao.vip/rogeecn/atom/container"
"git.ipao.vip/rogeecn/atom/contracts"
"git.ipao.vip/rogeecn/atom/utils/opt"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
)
@@ -46,3 +48,18 @@ func (ps *PubSub) Handle(
) {
ps.Router.AddHandler(handlerName, consumerTopic, ps.Subscriber, publisherTopic, ps.Publisher, handler)
}
// publish
func (ps *PubSub) Publish(e contracts.EventPublisher) error {
if e == nil {
return nil
}
payload, err := e.Marshal()
if err != nil {
return err
}
msg := message.NewMessage(watermill.NewUUID(), payload)
return ps.Publisher.Publish(e.Topic(), msg)
}

View File

@@ -1,4 +1,4 @@
package events
package event
import (
"github.com/ThreeDotsLabs/watermill"

View File

@@ -1,4 +1,4 @@
package events
package event
import (
"git.ipao.vip/rogeecn/atom/container"

View File

@@ -1,4 +1,4 @@
package events
package event
import (
"git.ipao.vip/rogeecn/atom/container"

View File

@@ -1,4 +1,4 @@
package events
package event
import (
"git.ipao.vip/rogeecn/atom/container"

View File

@@ -1,4 +1,4 @@
package events
package event
import (
sqlDB "database/sql"

View File

@@ -51,4 +51,11 @@ func (c *Config) format() {
c.Env = "unknown"
}
}
if c.EndpointGRPC == "" {
c.EndpointGRPC = os.Getenv("OTEL_ENDPOINT_GRPC")
if c.EndpointGRPC == "" {
c.EndpointGRPC = "localhost:4317"
}
}
}

View File

@@ -13,7 +13,7 @@ var (
)
func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
return tracer.Start(ctx, spanName, opts...)
return tracer.Start(ctx, spanName)
}
func Int64Counter(name string, options ...metric.Int64CounterOption) (metric.Int64Counter, error) {

1
readme.md Normal file
View File

@@ -0,0 +1 @@
# QvYun