mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
feat: 数据库用户接口
This commit is contained in:
@@ -2,12 +2,24 @@ package biz
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type DatabaseStatus string
|
||||
|
||||
const (
|
||||
DatabaseStatusValid DatabaseStatus = "valid"
|
||||
DatabaseStatusInvalid DatabaseStatus = "invalid"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
Name string `json:"name"`
|
||||
ServerID uint `json:"server_id"`
|
||||
Status DatabaseStatus `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type DatabaseRepo interface {
|
||||
Count() (int64, error)
|
||||
List(page, limit uint) ([]types.Database, int64, error)
|
||||
List(page, limit uint) ([]*Database, int64, error)
|
||||
Create(req *request.DatabaseCreate) error
|
||||
Delete(serverID uint, name string) error
|
||||
}
|
||||
|
||||
@@ -14,20 +14,30 @@ type DatabaseType string
|
||||
const (
|
||||
DatabaseTypeMysql DatabaseType = "mysql"
|
||||
DatabaseTypePostgresql DatabaseType = "postgresql"
|
||||
DatabaseTypeMongoDB DatabaseType = "mongodb"
|
||||
DatabaseSQLite DatabaseType = "sqlite"
|
||||
DatabaseTypeRedis DatabaseType = "redis"
|
||||
)
|
||||
|
||||
type DatabaseServerStatus string
|
||||
|
||||
const (
|
||||
DatabaseServerStatusValid DatabaseServerStatus = "valid"
|
||||
DatabaseServerStatusInvalid DatabaseServerStatus = "invalid"
|
||||
)
|
||||
|
||||
type DatabaseServer struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type DatabaseType `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port uint `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type DatabaseType `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port uint `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Status DatabaseServerStatus `gorm:"-:all" json:"status"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *DatabaseServer) BeforeSave(tx *gorm.DB) error {
|
||||
@@ -57,4 +67,5 @@ type DatabaseServerRepo interface {
|
||||
Create(req *request.DatabaseServerCreate) error
|
||||
Update(req *request.DatabaseServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Sync(id uint) error
|
||||
}
|
||||
|
||||
@@ -9,14 +9,24 @@ import (
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseUserStatus string
|
||||
|
||||
const (
|
||||
DatabaseUserStatusValid DatabaseUserStatus = "valid"
|
||||
DatabaseUserStatusInvalid DatabaseUserStatus = "invalid"
|
||||
)
|
||||
|
||||
type DatabaseUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ServerID uint `gorm:"not null" json:"server_id"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ServerID uint `gorm:"not null" json:"server_id"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Host string `gorm:"not null" json:"host"` // 仅 mysql
|
||||
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
|
||||
Privileges map[string][]string `gorm:"-:all" json:"privileges"` // 仅显示
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *DatabaseUser) BeforeSave(tx *gorm.DB) error {
|
||||
@@ -46,5 +56,5 @@ type DatabaseUserRepo interface {
|
||||
Create(req *request.DatabaseUserCreate) error
|
||||
Update(req *request.DatabaseUserUpdate) error
|
||||
Delete(id uint) error
|
||||
Sync(id uint) error
|
||||
DeleteByServerID(serverID uint) error
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type databaseRepo struct{}
|
||||
@@ -18,22 +17,13 @@ func NewDatabaseRepo() biz.DatabaseRepo {
|
||||
return do.MustInvoke[biz.DatabaseRepo](injector)
|
||||
}
|
||||
|
||||
func (r databaseRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&types.Database{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r databaseRepo) List(page, limit uint) ([]types.Database, int64, error) {
|
||||
func (r databaseRepo) List(page, limit uint) ([]*biz.Database, int64, error) {
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
if err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Find(&databaseServer).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
database := make([]types.Database, 0)
|
||||
database := make([]*biz.Database, 0)
|
||||
for _, server := range databaseServer {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
@@ -41,10 +31,10 @@ func (r databaseRepo) List(page, limit uint) ([]types.Database, int64, error) {
|
||||
if err == nil {
|
||||
if databases, err := mysql.Databases(); err == nil {
|
||||
for _, name := range databases {
|
||||
database = append(database, types.Database{
|
||||
database = append(database, &biz.Database{
|
||||
Name: name,
|
||||
ServerID: server.ID,
|
||||
Status: types.DatabaseStatusValid,
|
||||
Status: biz.DatabaseStatusValid,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,10 +44,10 @@ func (r databaseRepo) List(page, limit uint) ([]types.Database, int64, error) {
|
||||
if err == nil {
|
||||
if databases, err := postgres.Databases(); err == nil {
|
||||
for _, item := range databases {
|
||||
database = append(database, types.Database{
|
||||
database = append(database, &biz.Database{
|
||||
Name: item.Name,
|
||||
ServerID: server.ID,
|
||||
Status: types.DatabaseStatusValid,
|
||||
Status: biz.DatabaseStatusValid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
@@ -30,6 +31,11 @@ func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
|
||||
|
||||
for server := range slices.Values(databaseServer) {
|
||||
r.checkServer(server)
|
||||
}
|
||||
|
||||
return databaseServer, total, err
|
||||
}
|
||||
|
||||
@@ -39,26 +45,12 @@ func (r databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.checkServer(databaseServer)
|
||||
|
||||
return databaseServer, nil
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
switch biz.DatabaseType(req.Type) {
|
||||
case biz.DatabaseTypeMysql:
|
||||
if _, err := db.NewMySQL(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
if _, err := db.NewRedis(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
databaseServer := &biz.DatabaseServer{
|
||||
Name: req.Name,
|
||||
Type: biz.DatabaseType(req.Type),
|
||||
@@ -69,42 +61,125 @@ func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if !r.checkServer(databaseServer) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
}
|
||||
|
||||
return app.Orm.Create(databaseServer).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
switch biz.DatabaseType(req.Type) {
|
||||
case biz.DatabaseTypeMysql:
|
||||
if _, err := db.NewMySQL(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
if _, err := db.NewRedis(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return app.Orm.Model(&biz.DatabaseServer{}).Where("id = ?", req.ID).Select("*").Updates(&biz.DatabaseServer{
|
||||
Name: req.Name,
|
||||
Type: biz.DatabaseType(req.Type),
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Remark: req.Remark,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Delete(id uint) error {
|
||||
ds := new(biz.DatabaseServer)
|
||||
if err := app.Orm.Where("id = ?", id).First(ds).Error; err != nil {
|
||||
server, err := r.Get(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Orm.Delete(&biz.DatabaseServer{}, id).Error
|
||||
server.Name = req.Name
|
||||
server.Host = req.Host
|
||||
server.Port = req.Port
|
||||
server.Username = req.Username
|
||||
server.Password = req.Password
|
||||
server.Remark = req.Remark
|
||||
|
||||
if !r.checkServer(server) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
}
|
||||
|
||||
return app.Orm.Save(server).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Delete(id uint) error {
|
||||
// 删除服务器下的所有用户
|
||||
if err := NewDatabaseUserRepo().DeleteByServerID(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseServer{}).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Sync(id uint) error {
|
||||
server, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := make([]*biz.DatabaseUser, 0)
|
||||
if err = app.Orm.Where("server_id = ?", id).Find(&users).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allUsers, err := mysql.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for user := range slices.Values(allUsers) {
|
||||
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
|
||||
return a.Username == user.User && a.Host == user.Host
|
||||
}) {
|
||||
newUser := &biz.DatabaseUser{
|
||||
ServerID: id,
|
||||
Username: user.User,
|
||||
Host: user.Host,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
}
|
||||
app.Orm.Create(newUser)
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allUsers, err := postgres.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for user := range slices.Values(allUsers) {
|
||||
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
|
||||
return a.Username == user.Role
|
||||
}) {
|
||||
newUser := &biz.DatabaseUser{
|
||||
ServerID: id,
|
||||
Username: user.Role,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
}
|
||||
app.Orm.Create(newUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkServer 检查服务器连接
|
||||
func (r databaseServerRepo) checkServer(server *biz.DatabaseServer) bool {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
_, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
_, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
_, err := db.NewRedis(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
server.Status = biz.DatabaseServerStatusValid
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
server.Status = biz.DatabaseServerStatusInvalid
|
||||
return false
|
||||
}
|
||||
|
||||
213
internal/data/database_user.go
Normal file
213
internal/data/database_user.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/do/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
)
|
||||
|
||||
type databaseUserRepo struct{}
|
||||
|
||||
func NewDatabaseUserRepo() biz.DatabaseUserRepo {
|
||||
return do.MustInvoke[biz.DatabaseUserRepo](injector)
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.DatabaseUser{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) List(page, limit uint) ([]*biz.DatabaseUser, int64, error) {
|
||||
var user []*biz.DatabaseUser
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.DatabaseUser{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&user).Error
|
||||
|
||||
for u := range slices.Values(user) {
|
||||
r.fillUser(u)
|
||||
}
|
||||
|
||||
return user, total, err
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Get(id uint) (*biz.DatabaseUser, error) {
|
||||
user := new(biz.DatabaseUser)
|
||||
if err := app.Orm.Where("id = ?", id).First(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.fillUser(user)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
server, err := NewDatabaseServerRepo().Get(req.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.PrivilegesGrant(req.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.PrivilegesGrant(req.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user := &biz.DatabaseUser{
|
||||
ServerID: req.ServerID,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
return app.Orm.Create(user).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
user, err := r.Get(req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Password != "" {
|
||||
if err = mysql.UserPassword(user.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.PrivilegesGrant(user.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Password != "" {
|
||||
if err = postgres.UserPassword(user.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.PrivilegesGrant(user.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.Password = req.Password
|
||||
user.Remark = req.Remark
|
||||
|
||||
return app.Orm.Save(user).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) Delete(id uint) error {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mysql.UserDrop(user.Username)
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = postgres.DatabaseDrop(user.Username)
|
||||
}
|
||||
|
||||
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) DeleteByServerID(serverID uint) error {
|
||||
return app.Orm.Where("server_id = ?", serverID).Delete(&biz.DatabaseUser{}).Error
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) fillUser(user *biz.DatabaseUser) {
|
||||
server, err := NewDatabaseServerRepo().Get(user.ServerID)
|
||||
if err == nil {
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
privileges, _ := mysql.UserPrivileges(user.Username, user.Host)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
if _, err := db.NewMySQL(user.Username, user.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)); err == nil {
|
||||
user.Status = biz.DatabaseUserStatusValid
|
||||
} else {
|
||||
user.Status = biz.DatabaseUserStatusInvalid
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
privileges, _ := postgres.UserPrivileges(user.Username)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
if _, err := db.NewPostgres(user.Username, user.Password, server.Host, server.Port); err == nil {
|
||||
user.Status = biz.DatabaseUserStatusValid
|
||||
} else {
|
||||
user.Status = biz.DatabaseUserStatusInvalid
|
||||
}
|
||||
}
|
||||
}
|
||||
// 初始化,防止 nil
|
||||
if user.Privileges == nil {
|
||||
user.Privileges = make(map[string][]string)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func init() {
|
||||
return &databaseServerRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.DatabaseUserRepo, error) {
|
||||
return nil, nil // TODO
|
||||
return &databaseUserRepo{}, nil
|
||||
})
|
||||
do.Provide(injector, func(i do.Injector) (biz.DatabaseRepo, error) {
|
||||
return &databaseRepo{}, nil
|
||||
|
||||
@@ -13,7 +13,6 @@ type DatabaseServerCreate struct {
|
||||
type DatabaseServerUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=database_servers id"`
|
||||
Name string `form:"name" json:"name" validate:"required,not_exists=database_servers name"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=mysql postgresql redis"`
|
||||
Host string `form:"host" json:"host" validate:"required"`
|
||||
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
|
||||
Username string `form:"username" json:"username"`
|
||||
|
||||
@@ -9,8 +9,7 @@ type DatabaseUserCreate struct {
|
||||
}
|
||||
|
||||
type DatabaseUserUpdate struct {
|
||||
ID string `form:"id" json:"id" validate:"required,exists=database_users id"`
|
||||
Username string `form:"username" json:"username"`
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=database_users id"`
|
||||
Password string `form:"password" json:"password"`
|
||||
Privileges []string `form:"privileges" json:"privileges"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
|
||||
@@ -70,11 +70,20 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/databaseServer", func(r chi.Router) {
|
||||
database := service.NewDatabaseServerService()
|
||||
r.Get("/", database.List)
|
||||
r.Post("/", database.Create)
|
||||
r.Put("/{id}", database.Update)
|
||||
r.Delete("/{id}", database.Delete)
|
||||
server := service.NewDatabaseServerService()
|
||||
r.Get("/", server.List)
|
||||
r.Post("/", server.Create)
|
||||
r.Put("/{id}", server.Update)
|
||||
r.Delete("/{id}", server.Delete)
|
||||
r.Delete("/{id}/sync", server.Sync)
|
||||
})
|
||||
|
||||
r.Route("/databaseUser", func(r chi.Router) {
|
||||
user := service.NewDatabaseUserService()
|
||||
r.Get("/", user.List)
|
||||
r.Post("/", user.Create)
|
||||
r.Put("/{id}", user.Update)
|
||||
r.Delete("/{id}", user.Delete)
|
||||
})
|
||||
|
||||
r.Route("/backup", func(r chi.Router) {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *Database) List(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
certs, total, err := s.databaseRepo.List(req.Page, req.Limit)
|
||||
databases, total, err := s.databaseRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -35,7 +35,7 @@ func (s *Database) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": certs,
|
||||
"items": databases,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *DatabaseServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
certs, total, err := s.databaseServerRepo.List(req.Page, req.Limit)
|
||||
servers, total, err := s.databaseServerRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -35,7 +35,7 @@ func (s *DatabaseServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": certs,
|
||||
"items": servers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,3 +83,18 @@ func (s *DatabaseServer) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Sync(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Sync(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
85
internal/service/database_user.go
Normal file
85
internal/service/database_user.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type DatabaseUser struct {
|
||||
databaseUserRepo biz.DatabaseUserRepo
|
||||
}
|
||||
|
||||
func NewDatabaseUserService() *DatabaseUser {
|
||||
return &DatabaseUser{
|
||||
databaseUserRepo: data.NewDatabaseUserRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) List(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.Paginate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
users, total, err := s.databaseUserRepo.List(req.Page, req.Limit)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": users,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseUserCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Create(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DatabaseUserUpdate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Update(req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseUser) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseUserRepo.Delete(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
@@ -132,15 +133,33 @@ func (m *MySQL) UserPrivileges(user, host string) (map[string][]string, error) {
|
||||
privileges := make(map[string][]string)
|
||||
for rows.Next() {
|
||||
var grant string
|
||||
if err := rows.Scan(&grant); err != nil {
|
||||
if err = rows.Scan(&grant); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan grant: %w", err)
|
||||
}
|
||||
if !strings.HasPrefix(grant, "GRANT ") {
|
||||
continue
|
||||
}
|
||||
|
||||
var db string
|
||||
var privs []string
|
||||
if _, err := fmt.Sscanf(grant, "GRANT %s ON %s TO", &privs, &db); err == nil {
|
||||
privileges[db] = append(privileges[db], privs...)
|
||||
parts := strings.Split(grant, " ON ")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
privList := strings.TrimPrefix(parts[0], "GRANT ")
|
||||
privs := strings.Split(privList, ", ")
|
||||
|
||||
dbPart := strings.Split(parts[1], " TO")[0]
|
||||
// *.* 表示全局权限
|
||||
if dbPart == "*.*" {
|
||||
dbPart = "*"
|
||||
}
|
||||
|
||||
dbPart = strings.Trim(dbPart, "`")
|
||||
privileges[dbPart] = append(privileges[dbPart], privs...)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privileges, nil
|
||||
|
||||
@@ -43,59 +43,59 @@ func NewPostgres(username, password, address string, port uint) (*Postgres, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Postgres) Close() error {
|
||||
return m.db.Close()
|
||||
func (r *Postgres) Close() error {
|
||||
return r.db.Close()
|
||||
}
|
||||
|
||||
func (m *Postgres) Ping() error {
|
||||
return m.db.Ping()
|
||||
func (r *Postgres) Ping() error {
|
||||
return r.db.Ping()
|
||||
}
|
||||
|
||||
func (m *Postgres) Query(query string, args ...any) (*sql.Rows, error) {
|
||||
return m.db.Query(query, args...)
|
||||
func (r *Postgres) Query(query string, args ...any) (*sql.Rows, error) {
|
||||
return r.db.Query(query, args...)
|
||||
}
|
||||
|
||||
func (m *Postgres) QueryRow(query string, args ...any) *sql.Row {
|
||||
return m.db.QueryRow(query, args...)
|
||||
func (r *Postgres) QueryRow(query string, args ...any) *sql.Row {
|
||||
return r.db.QueryRow(query, args...)
|
||||
}
|
||||
|
||||
func (m *Postgres) Exec(query string, args ...any) (sql.Result, error) {
|
||||
return m.db.Exec(query, args...)
|
||||
func (r *Postgres) Exec(query string, args ...any) (sql.Result, error) {
|
||||
return r.db.Exec(query, args...)
|
||||
}
|
||||
|
||||
func (m *Postgres) Prepare(query string) (*sql.Stmt, error) {
|
||||
return m.db.Prepare(query)
|
||||
func (r *Postgres) Prepare(query string) (*sql.Stmt, error) {
|
||||
return r.db.Prepare(query)
|
||||
}
|
||||
|
||||
func (m *Postgres) DatabaseCreate(name string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("CREATE DATABASE %s", name))
|
||||
func (r *Postgres) DatabaseCreate(name string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("CREATE DATABASE %s", name))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Postgres) DatabaseDrop(name string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name))
|
||||
func (r *Postgres) DatabaseDrop(name string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Postgres) DatabaseExist(name string) (bool, error) {
|
||||
func (r *Postgres) DatabaseExist(name string) (bool, error) {
|
||||
var count int
|
||||
if err := m.QueryRow("SELECT COUNT(*) FROM pg_database WHERE datname = $1", name).Scan(&count); err != nil {
|
||||
if err := r.QueryRow("SELECT COUNT(*) FROM pg_database WHERE datname = $1", name).Scan(&count); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (m *Postgres) DatabaseSize(name string) (int64, error) {
|
||||
func (r *Postgres) DatabaseSize(name string) (int64, error) {
|
||||
query := fmt.Sprintf("SELECT pg_database_size('%s')", name)
|
||||
var size int64
|
||||
if err := m.QueryRow(query).Scan(&size); err != nil {
|
||||
if err := r.QueryRow(query).Scan(&size); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (m *Postgres) UserCreate(user, password string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("CREATE USER %s WITH PASSWORD '%s'", user, password))
|
||||
func (r *Postgres) UserCreate(user, password string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("CREATE USER %s WITH PASSWORD '%s'", user, password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,8 +103,8 @@ func (m *Postgres) UserCreate(user, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Postgres) UserDrop(user string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("DROP USER IF EXISTS %s", user))
|
||||
func (r *Postgres) UserDrop(user string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("DROP USER IF EXISTS %s", user))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,12 +112,12 @@ func (m *Postgres) UserDrop(user string) error {
|
||||
return systemctl.Reload("postgresql")
|
||||
}
|
||||
|
||||
func (m *Postgres) UserPassword(user, password string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("ALTER USER %s WITH PASSWORD '%s'", user, password))
|
||||
func (r *Postgres) UserPassword(user, password string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("ALTER USER %s WITH PASSWORD '%s'", user, password))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Postgres) UserPrivileges(user string) (map[string][]string, error) {
|
||||
func (r *Postgres) UserPrivileges(user string) (map[string][]string, error) {
|
||||
query := `
|
||||
SELECT
|
||||
table_catalog as database_name,
|
||||
@@ -126,7 +126,7 @@ func (p *Postgres) UserPrivileges(user string) (map[string][]string, error) {
|
||||
WHERE grantee = $1
|
||||
GROUP BY table_catalog`
|
||||
|
||||
rows, err := p.Query(query, user)
|
||||
rows, err := r.Query(query, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query database privileges: %w", err)
|
||||
}
|
||||
@@ -135,39 +135,38 @@ func (p *Postgres) UserPrivileges(user string) (map[string][]string, error) {
|
||||
privileges := make(map[string][]string)
|
||||
|
||||
for rows.Next() {
|
||||
var dbName, privilegeStr string
|
||||
if err := rows.Scan(&dbName, &privilegeStr); err != nil {
|
||||
var db, privilegeStr string
|
||||
if err := rows.Scan(&db, &privilegeStr); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.*", dbName)
|
||||
privileges[key] = strings.Split(privilegeStr, ",")
|
||||
privileges[db] = strings.Split(privilegeStr, ",")
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating rows: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privileges, nil
|
||||
}
|
||||
|
||||
func (m *Postgres) PrivilegesGrant(user, database string) error {
|
||||
if _, err := m.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", database, user)); err != nil {
|
||||
func (r *Postgres) PrivilegesGrant(user, database string) error {
|
||||
if _, err := r.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", database, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := m.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", database, user)); err != nil {
|
||||
if _, err := r.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", database, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Postgres) PrivilegesRevoke(user, database string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s", database, user))
|
||||
func (r *Postgres) PrivilegesRevoke(user, database string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s", database, user))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Postgres) Users() ([]types.PostgresUser, error) {
|
||||
func (r *Postgres) Users() ([]types.PostgresUser, error) {
|
||||
query := `
|
||||
SELECT rolname,
|
||||
rolsuper,
|
||||
@@ -178,7 +177,7 @@ func (m *Postgres) Users() ([]types.PostgresUser, error) {
|
||||
FROM pg_roles
|
||||
WHERE rolcanlogin = true;
|
||||
`
|
||||
rows, err := m.Query(query)
|
||||
rows, err := r.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -212,16 +211,20 @@ func (m *Postgres) Users() ([]types.PostgresUser, error) {
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (m *Postgres) Databases() ([]types.PostgresDatabase, error) {
|
||||
func (r *Postgres) Databases() ([]types.PostgresDatabase, error) {
|
||||
query := `
|
||||
SELECT d.datname, pg_catalog.pg_get_userbyid(d.datdba), pg_catalog.pg_encoding_to_char(d.encoding)
|
||||
FROM pg_catalog.pg_database d
|
||||
WHERE datistemplate = false;
|
||||
`
|
||||
rows, err := m.Query(query)
|
||||
rows, err := r.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package types
|
||||
|
||||
type DatabaseStatus string
|
||||
|
||||
const (
|
||||
DatabaseStatusValid DatabaseStatus = "valid"
|
||||
DatabaseStatusInvalid DatabaseStatus = "invalid"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
Name string `json:"name"`
|
||||
ServerID uint `json:"server_id"`
|
||||
Status DatabaseStatus `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
@@ -19,5 +19,13 @@ export default {
|
||||
// 删除数据库服务器
|
||||
serverDelete: (id: number) => http.Delete(`/databaseServer/${id}`),
|
||||
// 同步数据库
|
||||
serverSync: (id: number) => http.Post(`/databaseServer/${id}/sync`)
|
||||
serverSync: (id: number) => http.Post(`/databaseServer/${id}/sync`),
|
||||
// 获取数据库用户列表
|
||||
userList: (page: number, limit: number) => http.Get('/databaseUser', { params: { page, limit } }),
|
||||
// 创建数据库用户
|
||||
userCreate: (data: any) => http.Post('/databaseUser', data),
|
||||
// 更新数据库用户
|
||||
userUpdate: (id: number, data: any) => http.Put(`/databaseUser/${id}`, data),
|
||||
// 删除数据库用户
|
||||
userDelete: (id: number) => http.Delete(`/databaseUser/${id}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user