2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

feat: 数据库用户接口

This commit is contained in:
耗子
2024-11-25 03:01:02 +08:00
parent 7f61793b36
commit 0df2a9107d
17 changed files with 593 additions and 160 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)
}
}

View File

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

View File

@@ -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"`

View File

@@ -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"`

View File

@@ -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) {

View File

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

View File

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

View 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)
}

View File

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

View File

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

View File

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

View File

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