rabbit

package module
v0.0.0-...-34eb98d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2023 License: MIT Imports: 33 Imported by: 1

README

rabbit

Rabbit is a golang library for simplifying backend develop.

⭐ Open source project using Rabbit, which can be a reference for an example project:

Features

All features have corresponding unit tests, which is convenient for developers to learn and use.

Dynamic Handlers & Dynamic Gorm Functions

Reference: ./handler_common_test.go

func HandleGet[T any](c *gin.Context, db *gorm.DB, onRender onRenderFunc[T])
func HandleDelete[T any](c *gin.Context, db *gorm.DB, onDelete onDeleteFunc[T]) 
func HandleCreate[T any](c *gin.Context, db *gorm.DB, onCreate onCreateFunc[T])
func HandleEdit[T any](c *gin.Context, db *gorm.DB, editables []string, onUpdate onUpdateFunc[T])
func HandleQuery[T any](c *gin.Context, db *gorm.DB, ctx *QueryOption)
func ExecuteGet[T any, V Key](db *gorm.DB, key V) (*T, error)
func ExecuteEdit[T any, V Key](db *gorm.DB, key V, vals map[string]any) (*T, error)
func ExecuteQuery[T any](db *gorm.DB, form QueryForm) (items []T, count int, err error)

About how to use: Please refer to the corresponding unit tests.

Integration Web Objects - Generate RESTful API

Reference https://github.com/restsend/gormpher

Env Config

Load environment variables

Functions:

func GetEnv(key string) string
func LookupEnv(key string) (string, bool)

Examples:

# .env
xx
EXIST_ENV=100	
# run with env 
EXIST_ENV=100 go run .
rabbit.GetEnv("EXIST_ENV") // 100
rabbit.LookupEnv("EXIST_ENV") // 100, true
Load config from DB

Functions:

func CheckValue(db *gorm.DB, key, default_value string)
func SetValue(db *gorm.DB, key, value string)
func GetValue(db *gorm.DB, key string) string
func GetIntValue(db *gorm.DB, key string, default_value int) int
func GetBoolValue(db *gorm.DB, key string) bool

Examples:

db, _ := gorm.Open(sqlite.Open("file::memory:"), nil)
db.AutoMigrate(&rabbit.Config{})

rabbit.SetValue(db, "test_key", "test_value")
value := rabbit.GetValue(db, "test_key") // test_value

rabbit.CheckValue(db, "check_key", "default_value")
value = rabbit.GetValue(db, "check_key") // default_value

rabbit.SetValue(db, "int_key", "42")
intValue := rabbit.GetIntValue(db, "int_key", -1) // 42

rabbit.SetValue(db, "bool_key", "true")
boolValue := rabbit.GetBoolValue(db, "bool_key") // true

Built-in Handlers

Permission models
User <-UserRole-> Role
Role <-RolePermission-> Permission
User <-GroupMember-> Group

User
- ID
- Email
- Password
- ...

// for association
UserRole
- UserID
- RoleID

Role
- Name
- Label

// for association 
RolePermission
- RoleID
- PermissionID

Permission
- Name
- Uri
- Method
- Anonymous
- ParentID  // for tree struct
- Children  // for tree struct

Group
- Name

// for association
GroupMember
- UserID
- GroupID
Authentication handlers
RegisterAuthenticationHandlers("/auth", db, r)
GET    /auth/info
POST   /auth/login
POST   /auth/register
GET    /auth/logout
POST   /auth/change_password
Authorization handlers
rabbit.RegisterAuthorizationHandlers(db, r.Group("/api"))
PUT    /api/role
PATCH  /api/role/:key
DELETE /api/role/:key
PUT    /api/permission
PATCH  /api/permission/:key
DELETE /api/permission/:key
Middleware
ar := r.Group("/api").Use(
  rabbit.WithAuthentication(), 
  rabbit.WithAuthorization("/api"),
)

rabbit.RegisterAuthorizationHandlers(db, ar)

Unit Tests Utils

Reference: tests_test.go

Example:

type user struct {
  ID   uint   `json:"id" gorm:"primarykey"`
  Name string `json:"name"`
  Age  int    `json:"age"`
}

r := gin.Default()

r.GET("/ping", func(ctx *gin.Context) {
  ctx.JSON(http.StatusOK, true)
})
r.POST("/pong", func(ctx *gin.Context) {
  var form user
  ctx.BindJSON(&form)
  ctx.JSON(http.StatusOK, gin.H{
    "name": form.Name,
    "age":  form.Age,
  })
})

Example Test:

c := rabbit.NewTestClient(r)
// CallGet
{
  var result bool
  err := c.CallGet("/ping", nil, &result)
  assert.Nil(t, err)
  assert.True(t, result)
}
// Get
{
  w := c.Get("/ping")
  assert.Equal(t, http.StatusOK, w.Code)
  assert.Equal(t, "true", w.Body.String())
}
// Native
{
  req := httptest.NewRequest(http.MethodGet, "/ping", nil)
  w := httptest.NewRecorder()
  r.ServeHTTP(w, req)

  assert.Equal(t, http.StatusOK, w.Code)
  assert.Equal(t, "true", w.Body.String())
}
// CallPost
{
  var result map[string]any
  err := c.CallPost("/pong", user{Name: "test", Age: 11}, &result)

  assert.Nil(t, err)
  assert.Equal(t, "test", result["name"])
  assert.Equal(t, float64(11), result["age"])
}
// Post
{
  b, _ := json.Marshal(user{Name: "test", Age: 11})
  w := c.Post("/pong", b)

  assert.Equal(t, http.StatusOK, w.Code)
  assert.Equal(t, `{"age":11,"name":"test"}`, w.Body.String())
}
// Native
{
  b, _ := json.Marshal(user{Name: "test", Age: 11})
  req := httptest.NewRequest(http.MethodPost, "/pong", bytes.NewReader(b))
  w := httptest.NewRecorder()
  r.ServeHTTP(w, req)

  assert.Equal(t, http.StatusOK, w.Code)
  assert.Equal(t, `{"age":11,"name":"test"}`, w.Body.String())
}

Acknowledgement Project:

Documentation

Index

Constants

View Source
const (
	DbField    = "_rabbit_db"
	TzField    = "_rabbit_tz"
	UserField  = "_rabbit_uid" // for session: uid, for context: *User
	GroupField = "_rabbit_gid" // for session: gid, for context: *Group
)
View Source
const (
	LevelDebug = iota
	LevelInfo
	LevelWarning
	LevelError
)
View Source
const (
	// SigUserLogin: user *User, c *gin.Context
	SigUserLogin = "user.login"
	// SigUserLogout: user *User, c *gin.Context
	SigUserLogout = "user.logout"
	//SigUserCreate: user *User, c *gin.Context
	SigUserCreate = "user.create"
)
View Source
const CORS_ALLOW_ALL = "*"
View Source
const CORS_ALLOW_CREDENTIALS = "true"
View Source
const CORS_ALLOW_HEADERS = "" /* 137-byte string literal not displayed */
View Source
const CORS_ALLOW_METHODS = "POST, OPTIONS, GET, PUT, PATCH, DELETE"
View Source
const ENV_AUTH_PREFIX = "AUTH_PREFIX"
View Source
const ENV_DB_DRIVER = "DB_DRIVER"

DB

View Source
const ENV_DSN = "DSN"
View Source
const ENV_PASSWORD_SALT = "PASSWORD_SALT" // User Password salt
View Source
const ENV_SESSION_SECRET = "SESSION_SECRET"
View Source
const KEY_API_NEED_AUTH = "API_NEED_AUTH"
View Source
const KEY_USER_NEED_ACTIVATE = "USER_NEED_ACTIVATE"
View Source
const SessionField = "rabbit"

Gin session field

View Source
const XAuthTokenHeader = "X-Auth-Token"

Variables

View Source
var EnabledConsoleColor = false
View Source
var LogLevel = LevelDebug

Functions

func AddRoleForUser

func AddRoleForUser(db *gorm.DB, uid uint, rid uint) error

func CORSEnabled

func CORSEnabled() gin.HandlerFunc

1. set CORS header 2. if method is OPTIONS, return 204

func CheckGroupInUse

func CheckGroupInUse(db *gorm.DB, gid uint) (bool, error)

func CheckPassword

func CheckPassword(dbPassword, password string) bool

password

func CheckPermissionInUse

func CheckPermissionInUse(db *gorm.DB, pid uint) (bool, error)

func CheckPermissionNameExist

func CheckPermissionNameExist(db *gorm.DB, name string) (bool, error)

func CheckRoleInUse

func CheckRoleInUse(db *gorm.DB, rid uint) (bool, error)

func CheckRoleNameExist

func CheckRoleNameExist(db *gorm.DB, name string) (bool, error)

func CheckRolePermission

func CheckRolePermission(db *gorm.DB, rid uint, uri, method string) (bool, error)

check

func CheckUserPermission

func CheckUserPermission(db *gorm.DB, uid uint, uri, method string) (bool, error)

func CheckValue

func CheckValue(db *gorm.DB, key, default_value string)

CheckValue check if key exists, if not, set default_value

func Count

func Count[T any](db *gorm.DB, where ...any) (int, error)

func CreateDatabaseInstance

func CreateDatabaseInstance(driver, dsn string, cfg *gorm.Config) (*gorm.DB, error)

func CurrentTimezone

func CurrentTimezone(c *gin.Context) *time.Location

1. try get cache from context 2. try get from session 3. set context cache

func Debugf

func Debugf(format string, v ...any)

func Debugln

func Debugln(v ...any)

func DeletePermission

func DeletePermission(db *gorm.DB, pid uint) error

if the permission is a parent permission, delete all its children

func DeleteRole

func DeleteRole(db *gorm.DB, rid uint) error

func EncodeHashToken

func EncodeHashToken(user *User, timestamp int64, useLastLogin bool) (hash string)

timestamp-uid-token base64(email$timestamp) + "-" + sha256(salt + logintimestamp + password + email$timestamp)

func Errorf

func Errorf(format string, v ...any)

func Errorln

func Errorln(v ...any)

func GenUniqueKey

func GenUniqueKey(tx *gorm.DB, field string, size int) (key string)

GenUniqueKey generate unique key for field

func Get

func Get[T any](db *gorm.DB, val *T, where ...any) (*T, error)

func GetBoolValue

func GetBoolValue(db *gorm.DB, key string) bool

func GetByID

func GetByID[T any, E ~uint | ~int | ~string](db *gorm.DB, id E, where ...any) (*T, error)

func GetEnv

func GetEnv(key string) string

func GetIntValue

func GetIntValue(db *gorm.DB, key string, default_value int) int

func GetPkColumnName

func GetPkColumnName[T any]() string

func GetValue

func GetValue(db *gorm.DB, key string) string

func HandleError

func HandleError(c *gin.Context, code int, err error)

func HandleErrorMessage

func HandleErrorMessage(c *gin.Context, code int, msg string)

func HashPassword

func HashPassword(password string) string

func InTimezone

func InTimezone(c *gin.Context, timezone string)

1. set *time.Location to gin context, for cache 2. set [timezone string] to session

func Infof

func Infof(format string, v ...any)

func Infoln

func Infoln(v ...any)

func InitDatabase

func InitDatabase(driver, dsn string, logWrite io.Writer) *gorm.DB

func InitMigrate

func InitMigrate(db *gorm.DB) error

func InitRabbit

func InitRabbit(db *gorm.DB, r *gin.Engine)

InitRabbit start with default middleware and auth handler 1. migrate models 2. gin middleware 3. setup env 4. setup config 5. auth handler

func IsExistByEmail

func IsExistByEmail(db *gorm.DB, email string) bool

func Login

func Login(c *gin.Context, user *User)

set session

func Logout

func Logout(c *gin.Context, user *User)

1. remove context 2. remove session

func LookupEnv

func LookupEnv(key string) (string, bool)

1. Check .env file 2. Check environment variables

func MakeMigrates

func MakeMigrates(db *gorm.DB, insts ...any) error

func RandNumberText

func RandNumberText(n int) string

func RandText

func RandText(n int) string

func RegisterAuthenticationHandlers

func RegisterAuthenticationHandlers(prefix string, db *gorm.DB, r *gin.Engine)

func RegisterAuthorizationHandlers

func RegisterAuthorizationHandlers(db *gorm.DB, r gin.IRoutes)

func SetLastLogin

func SetLastLogin(db *gorm.DB, user *User, lastIp string) error

func SetLogLevel

func SetLogLevel(level int)

func SetPassword

func SetPassword(db *gorm.DB, user *User, password string) (err error)

func SetValue

func SetValue(db *gorm.DB, key, value string)

func StructAsMap

func StructAsMap(form any, fields []string) (vals map[string]any)

func SwitchGroup

func SwitchGroup(c *gin.Context, gid uint)

func UpdateFields

func UpdateFields[T any](db *gorm.DB, model *T, vals map[string]any) error

func Warningf

func Warningf(format string, v ...any)

func Warningln

func Warningln(v ...any)

func WithAuthentication

func WithAuthentication() gin.HandlerFunc

1. auth from session 2. auth from token

func WithAuthorization

func WithAuthorization(prefix string) gin.HandlerFunc

check if the user has permission to access the url superuser no need to check

func WithCookieSession

func WithCookieSession(secret string) gin.HandlerFunc

func WithGormDB

func WithGormDB(db *gorm.DB) gin.HandlerFunc

func WithMemSession

func WithMemSession(secret string) gin.HandlerFunc

Types

type ChangePasswordForm

type ChangePasswordForm struct {
	Password string `json:"password" binding:"required"`
}

type Config

type Config struct {
	ID    uint   `json:"id" gorm:"primaryKey"`
	Key   string `json:"key" gorm:"size:128,uniqueIndex"`
	Value string `json:"value"`
	Desc  string `json:"desc" gorm:"size: 200"`
}

type Group

type Group struct {
	ID        uint      `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`

	Name  string `json:"name" gorm:"size:200;uniqueIndex"`
	Extra string `json:"extra"`

	// for association
	Users []*User `json:"users" gorm:"many2many:group_members;"`
}

TODO:

func CreateGroupByUser

func CreateGroupByUser(db *gorm.DB, uid uint, name string) (*Group, error)

func CurrentGroup

func CurrentGroup(c *gin.Context) *Group

func GetFirstGroupByUser

func GetFirstGroupByUser(db *gorm.DB, uid uint) (*Group, error)

func GetGroupByID

func GetGroupByID(db *gorm.DB, gid uint) (*Group, error)

group

func GetGroupByName

func GetGroupByName(db *gorm.DB, name string) (*Group, error)

func GetGroupsByUser

func GetGroupsByUser(db *gorm.DB, uid uint) ([]*Group, error)

type GroupMember

type GroupMember struct {
	UserID  uint `json:"-" gorm:"primarykey"`
	GroupID uint `json:"-" gorm:"primarykey"`

	// for association
	User  User  `json:"user"`
	Group Group `json:"group"`
}

TODO:

type LoginForm

type LoginForm struct {
	Email     string `json:"email"`
	Password  string `json:"password,omitempty"`
	Timezone  string `json:"timezone,omitempty"`
	Remember  bool   `json:"remember,omitempty"`
	AuthToken string `json:"token,omitempty"`
}

type Permission

type Permission struct {
	ID        uint      `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`

	ParentID  uint   `json:"parentId"`
	Name      string `json:"name" gorm:"size:200;uniqueIndex"`
	Uri       string `json:"uri" gorm:"size:200"`
	Method    string `json:"method" gorm:"size:200"`
	Anonymous bool   `json:"anonymous"` // any role can access

	// for association
	Groups []*Group `json:"groups" gorm:"many2many:group_permissions;"`
	Roles  []*Role  `json:"roles" gorm:"many2many:role_permissions;"`

	// for tree
	Children []*Permission `json:"children,omitempty" gorm:"-"`
}

func GetPermission

func GetPermission(db *gorm.DB, uri, method string) (*Permission, error)

func GetPermissionByID

func GetPermissionByID(db *gorm.DB, pid uint) (*Permission, error)

func GetPermissionByName

func GetPermissionByName(db *gorm.DB, name string) (*Permission, error)

func GetPermissionChildren

func GetPermissionChildren(db *gorm.DB, pid uint) ([]*Permission, error)

func GetPermissionsByRole

func GetPermissionsByRole(db *gorm.DB, rid uint) ([]*Permission, error)

func SavePermission

func SavePermission(db *gorm.DB, id, pid uint, name, uri, method string, anonymous bool) (*Permission, error)

permission

type Profile

type Profile struct {
	Avatar  string         `json:"avatar,omitempty"`
	Gender  string         `json:"gender,omitempty"`
	City    string         `json:"city,omitempty"`
	Region  string         `json:"region,omitempty"`
	Country string         `json:"country,omitempty"`
	Extra   map[string]any `json:"extra,omitempty"`
}

func (*Profile) Scan

func (p *Profile) Scan(value any) error

func (Profile) Value

func (p Profile) Value() (driver.Value, error)

type RegisterUserForm

type RegisterUserForm struct {
	Email       string `json:"email" binding:"required"`
	Password    string `json:"password" binding:"required"`
	DisplayName string `json:"displayName"`
	FirstName   string `json:"firstName"`
	LastName    string `json:"lastName"`
	Locale      string `json:"locale"`
	Timezone    string `json:"timezone"`
	Source      string `json:"source"`
}

type Role

type Role struct {
	ID        uint      `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`

	Name  string `json:"name" gorm:"size:50;uniqueIndex"`
	Label string `json:"label" gorm:"size:200"`

	// for association
	Users       []*User       `json:"users" gorm:"many2many:user_roles;"`
	Permissions []*Permission `json:"permissions" gorm:"many2many:role_permissions;"`
}

func AddRoleWithPermissions

func AddRoleWithPermissions(db *gorm.DB, name, label string, ps []uint) (*Role, error)

func CreateRole

func CreateRole(db *gorm.DB, name, label string) (*Role, error)

func CreateRoleWithPermissions

func CreateRoleWithPermissions(db *gorm.DB, name, label string, ps []*Permission) (*Role, error)

for test

func GetRoleByID

func GetRoleByID(db *gorm.DB, rid uint) (*Role, error)

role

func GetRoleByName

func GetRoleByName(db *gorm.DB, name string) (*Role, error)

func GetRolesByUser

func GetRolesByUser(db *gorm.DB, uid uint) ([]*Role, error)

func UpdateRoleWithPermissions

func UpdateRoleWithPermissions(db *gorm.DB, rid uint, name, label string, ps []uint) (*Role, error)

type RoleForm

type RoleForm struct {
	ID            uint   `json:"id"`
	Name          string `json:"name"`
	Label         string `json:"label"`
	PermissionIds []uint `json:"permission_ids"`
}

type RolePermission

type RolePermission struct {
	RoleID       uint `json:"-" gorm:"primarykey"`
	PermissionID uint `json:"-" gorm:"primarykey"`

	// for association
	Role       Role       `json:"role"`
	Permission Permission `json:"permission"`
}

type SigHandler

type SigHandler func(sender any, params ...any)

type Signals

type Signals struct {
	// contains filtered or unexported fields
}

func NewSignals

func NewSignals() *Signals

func Sig

func Sig() *Signals

func (*Signals) Connect

func (s *Signals) Connect(event string, handler SigHandler)

func (*Signals) DisConnect

func (s *Signals) DisConnect(event string)

func (*Signals) Emit

func (s *Signals) Emit(event string, sender any, params ...any)

type TestClient

type TestClient struct {
	CookieJar http.CookieJar
	Scheme    string
	Host      string
	// contains filtered or unexported fields
}

func NewTestClient

func NewTestClient(r http.Handler) (c *TestClient)

func (*TestClient) Call

func (c *TestClient) Call(method, path string, form any, result any) error

func (*TestClient) CallDelete

func (c *TestClient) CallDelete(path string, form, result any) error

func (*TestClient) CallGet

func (c *TestClient) CallGet(path string, form, result any) error

func (*TestClient) CallPatch

func (c *TestClient) CallPatch(path string, form, result any) error

func (*TestClient) CallPost

func (c *TestClient) CallPost(path string, form any, result any) error

func (*TestClient) CallPut

func (c *TestClient) CallPut(path string, form, result any) error

func (*TestClient) Get

Get return *httptest.ResponseRecorder

func (*TestClient) Post

func (c *TestClient) Post(path string, body []byte) *httptest.ResponseRecorder

Post return *httptest.ResponseRecorder

func (*TestClient) SendReq

func (c *TestClient) SendReq(path string, req *http.Request) *httptest.ResponseRecorder

type User

type User struct {
	ID        uint      `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`

	Email       string     `json:"email" gorm:"size:128;uniqueIndex"`
	Password    string     `json:"-" gorm:"size:128"`
	FirstName   string     `json:"firstName,omitempty" gorm:"size:128"`
	LastName    string     `json:"lastName,omitempty" gorm:"size:128"`
	DisplayName string     `json:"displayName,omitempty" gorm:"size:128"`
	IsSuperUser bool       `json:"isSuper"`
	Enabled     bool       `json:"enabled"`
	Activated   bool       `json:"activated"`
	LastLogin   *time.Time `json:"lastLogin,omitempty"`
	LastLoginIP string     `json:"lastLoginIP" gorm:"size:128"`

	Source    string   `json:"-" gorm:"size:64;index"`
	Locale    string   `json:"locale,omitempty" gorm:"size:20"`
	Timezone  string   `json:"timezone,omitempty" gorm:"size:200"`
	Profile   *Profile `json:"profile,omitempty"`
	AuthToken string   `json:"token,omitempty" gorm:"-"`

	// for association
	Groups []*Group `json:"groups" gorm:"many2many:group_members;"`
	Roles  []*Role  `json:"roles" gorm:"many2many:user_roles;"`
}

func CreateUser

func CreateUser(db *gorm.DB, email, password string) (*User, error)

func CurrentUser

func CurrentUser(c *gin.Context) *User

1. try get cache from context 2. try get user from token/session 3. set context cache

func DecodeHashToken

func DecodeHashToken(db *gorm.DB, hash string, useLastLogin bool) (user *User, err error)

base64(email$timestamp) + "-" + sha256(salt + logintimestamp + password + email$timestamp)

func GetUserByEmail

func GetUserByEmail(db *gorm.DB, email string) (user *User, err error)

func GetUserByID

func GetUserByID(db *gorm.DB, userID uint) (*User, error)

user

func GetUsersByGroup

func GetUsersByGroup(db *gorm.DB, gid uint) ([]*User, error)

user

func GetUsersByRole

func GetUsersByRole(db *gorm.DB, rid uint) ([]*User, error)

func UpdateRolesForUser

func UpdateRolesForUser(db *gorm.DB, uid uint, rids []uint) (*User, error)

func (*User) GetProfile

func (u *User) GetProfile() Profile

func (*User) GetVisibleName

func (u *User) GetVisibleName() string

type UserRole

type UserRole struct {
	UserID uint `json:"-" gorm:"primarykey"`
	RoleID uint `json:"-" gorm:"primarykey"`

	// for association
	User User `json:"user"`
	Role Role `json:"role"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL