|
|
package biz
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
"fmt"
|
|
|
"runtime"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
pb "moredoc/api/v1"
|
|
|
"moredoc/middleware/auth"
|
|
|
"moredoc/model"
|
|
|
"moredoc/util"
|
|
|
"moredoc/util/device"
|
|
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
|
"go.uber.org/zap"
|
|
|
"google.golang.org/grpc/codes"
|
|
|
"google.golang.org/grpc/status"
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
|
)
|
|
|
|
|
|
type ConfigAPIService struct {
|
|
|
pb.UnimplementedConfigAPIServer
|
|
|
dbModel *model.DBModel
|
|
|
logger *zap.Logger
|
|
|
}
|
|
|
|
|
|
func NewConfigAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *ConfigAPIService) {
|
|
|
return &ConfigAPIService{dbModel: dbModel, logger: logger.Named("ConfigAPIService")}
|
|
|
}
|
|
|
|
|
|
func (s *ConfigAPIService) checkPermission(ctx context.Context) (userClaims *auth.UserClaims, err error) {
|
|
|
return checkGRPCPermission(s.dbModel, ctx)
|
|
|
}
|
|
|
|
|
|
// UpdateConfig 更新配置
|
|
|
func (s *ConfigAPIService) UpdateConfig(ctx context.Context, req *pb.Configs) (*emptypb.Empty, error) {
|
|
|
_, err := s.checkPermission(ctx)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
var cfgs []*model.Config
|
|
|
err = util.CopyStruct(req.Config, &cfgs)
|
|
|
if err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("req", req), zap.Any("cfgs", cfgs), zap.Error(err))
|
|
|
fmt.Println(err.Error())
|
|
|
}
|
|
|
|
|
|
doesUpdateSEO := false
|
|
|
isEmail := false
|
|
|
for idx, cfg := range cfgs {
|
|
|
if cfg.Value == "******" {
|
|
|
// 6个星号,不修改原值
|
|
|
exist, _ := s.dbModel.GetConfigByNameCategory(cfg.Name, cfg.Category)
|
|
|
cfgs[idx].Value = exist.Value
|
|
|
}
|
|
|
isEmail = isEmail || cfg.Category == model.ConfigCategoryEmail
|
|
|
if cfg.Category == model.ConfigCategorySystem {
|
|
|
doesUpdateSEO = true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
err = s.dbModel.UpdateConfigs(cfgs, "value")
|
|
|
if err != nil {
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
}
|
|
|
|
|
|
if isEmail {
|
|
|
cfgEmail := s.dbModel.GetConfigOfEmail(model.ConfigEmailEnable, model.ConfigEmailTestEmail)
|
|
|
if cfgEmail.Enable && cfgEmail.TestEmail != "" {
|
|
|
err = s.dbModel.SendMail("测试邮件", cfgEmail.TestEmail, "这是一封测试邮件")
|
|
|
if err != nil {
|
|
|
return nil, status.Error(codes.Internal, "邮件发送失败:"+err.Error())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if doesUpdateSEO {
|
|
|
s.dbModel.InitSEO()
|
|
|
}
|
|
|
|
|
|
return &emptypb.Empty{}, nil
|
|
|
}
|
|
|
|
|
|
// ListConfig 查询配置
|
|
|
func (s *ConfigAPIService) ListConfig(ctx context.Context, req *pb.ListConfigRequest) (*pb.Configs, error) {
|
|
|
_, err := s.checkPermission(ctx)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
opt := &model.OptionGetConfigList{
|
|
|
QueryIn: map[string][]interface{}{
|
|
|
"category": util.Slice2Interface(req.Category),
|
|
|
},
|
|
|
}
|
|
|
|
|
|
configs, err := s.dbModel.GetConfigList(opt)
|
|
|
if err != nil {
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
}
|
|
|
|
|
|
for idx, cfg := range configs {
|
|
|
if cfg.IsSecret && cfg.Value != "" {
|
|
|
configs[idx].Value = "******"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var pbConfigs []*pb.Config
|
|
|
util.CopyStruct(&configs, &pbConfigs)
|
|
|
|
|
|
return &pb.Configs{Config: pbConfigs}, nil
|
|
|
}
|
|
|
|
|
|
// GetSettings 获取公开配置
|
|
|
func (s *ConfigAPIService) GetSettings(ctx context.Context, req *emptypb.Empty) (*pb.Settings, error) {
|
|
|
res := &pb.Settings{
|
|
|
// Captcha: &pb.ConfigCaptcha{},
|
|
|
System: &pb.ConfigSystem{},
|
|
|
Footer: &pb.ConfigFooter{},
|
|
|
Security: &pb.ConfigSecurity{},
|
|
|
Display: &pb.ConfigDisplay{},
|
|
|
}
|
|
|
|
|
|
// captcha := s.dbModel.GetConfigOfCaptcha()
|
|
|
// util.CopyStruct(&captcha, res.Captcha)
|
|
|
|
|
|
system := s.dbModel.GetConfigOfSystem()
|
|
|
if err := util.CopyStruct(&system, res.System); err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("system", system), zap.Any("res.System", res.System), zap.Error(err))
|
|
|
}
|
|
|
system.Analytics = strings.TrimSpace(system.Analytics)
|
|
|
if system.Analytics != "" {
|
|
|
gq, errGQ := goquery.NewDocumentFromReader(strings.NewReader(system.Analytics))
|
|
|
if errGQ == nil {
|
|
|
var texts []string
|
|
|
gq.Find("script").Each(func(i int, selection *goquery.Selection) {
|
|
|
if text := strings.TrimSpace(selection.Text()); text != "" {
|
|
|
texts = append(texts, text)
|
|
|
}
|
|
|
})
|
|
|
if len(texts) > 0 {
|
|
|
res.System.Analytics = strings.Join(texts, "\n")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
res.System.Version = util.Version
|
|
|
res.System.CreditName = s.dbModel.GetConfigOfScore(model.ConfigScoreCreditName).CreditName
|
|
|
footer := s.dbModel.GetConfigOfFooter()
|
|
|
if err := util.CopyStruct(&footer, res.Footer); err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("footer", footer), zap.Any("res.Footer", res.Footer), zap.Error(err))
|
|
|
}
|
|
|
|
|
|
security := s.dbModel.GetConfigOfSecurity()
|
|
|
if err := util.CopyStruct(&security, res.Security); err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("security", security), zap.Any("res.Security", res.Security), zap.Error(err))
|
|
|
}
|
|
|
|
|
|
display := s.dbModel.GetConfigOfDisplay()
|
|
|
if err := util.CopyStruct(&display, res.Display); err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("display", display), zap.Any("res.Display", res.Display), zap.Error(err))
|
|
|
}
|
|
|
|
|
|
return res, nil
|
|
|
}
|
|
|
|
|
|
func (s *ConfigAPIService) GetStats(ctx context.Context, req *emptypb.Empty) (res *pb.Stats, err error) {
|
|
|
res = &pb.Stats{
|
|
|
UserCount: 0,
|
|
|
DocumentCount: 0,
|
|
|
CategoryCount: 0,
|
|
|
ArticleCount: 0,
|
|
|
CommentCount: 0,
|
|
|
BannerCount: 0,
|
|
|
FriendlinkCount: 0,
|
|
|
Os: runtime.GOOS,
|
|
|
Version: util.Version,
|
|
|
Hash: util.Hash,
|
|
|
BuildAt: util.BuildAt,
|
|
|
}
|
|
|
res.Os, _ = util.GetOSRelease()
|
|
|
res.UserCount, _ = s.dbModel.CountUser()
|
|
|
res.UserCount += s.dbModel.GetConfigOfDisplay(model.ConfigDisplayVirtualRegisterCount).VirtualRegisterCount
|
|
|
res.DocumentCount, _ = s.dbModel.CountDocument()
|
|
|
_, errPermission := s.checkPermission(ctx)
|
|
|
if errPermission == nil {
|
|
|
res.CategoryCount, _ = s.dbModel.CountCategory()
|
|
|
res.ArticleCount, _ = s.dbModel.CountArticle()
|
|
|
res.CommentCount, _ = s.dbModel.CountComment()
|
|
|
res.BannerCount, _ = s.dbModel.CountBanner()
|
|
|
res.FriendlinkCount, _ = s.dbModel.CountFriendlink()
|
|
|
res.ReportCount, _ = s.dbModel.CountReport()
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// UpdateSitemap 更新站点地图
|
|
|
func (s *ConfigAPIService) UpdateSitemap(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) {
|
|
|
_, err := s.checkPermission(ctx)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
err = s.dbModel.UpdateSitemap()
|
|
|
if err != nil {
|
|
|
s.logger.Error("UpdateSitemap", zap.Error(err))
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
}
|
|
|
|
|
|
return &emptypb.Empty{}, nil
|
|
|
}
|
|
|
|
|
|
func (s *ConfigAPIService) GetEnvs(ctx context.Context, req *emptypb.Empty) (res *pb.Envs, err error) {
|
|
|
res = &pb.Envs{}
|
|
|
_, errPermission := s.checkPermission(ctx)
|
|
|
if errPermission != nil {
|
|
|
return
|
|
|
}
|
|
|
envs := []*pb.EnvDependent{
|
|
|
{
|
|
|
Name: "LibreOffice",
|
|
|
Description: "LibreOffice是由文档基金会开发的自由及开放源代码的办公套件。魔豆文库用于将office等文档转为pdf。",
|
|
|
Cmd: "soffice",
|
|
|
IsRequired: true,
|
|
|
},
|
|
|
{
|
|
|
Name: "Calibre",
|
|
|
Description: "calibre是一个自由开源的电子书软件套装。魔豆文库用于将epub、mobi等电子书转为pdf。",
|
|
|
Cmd: "ebook-convert",
|
|
|
IsRequired: true,
|
|
|
},
|
|
|
{
|
|
|
// mupdf
|
|
|
Name: "MuPDF",
|
|
|
Description: "MuPDF是一款以C语言编写的自由及开放源代码软件库,是PDF和XPS解析和渲染引擎。魔豆文库用于将PDF转为svg、png等图片。",
|
|
|
Cmd: "mutool",
|
|
|
IsRequired: true,
|
|
|
},
|
|
|
{
|
|
|
// mupdf
|
|
|
Name: "ImageMagick",
|
|
|
Description: "ImageMagick是一个用于创建、编辑、合成和转换位图图像的自由软件套件。魔豆文库用于将PDF转为svg、png、jpg等图片。",
|
|
|
Cmd: "convert",
|
|
|
IsRequired: true,
|
|
|
},
|
|
|
{
|
|
|
// inkscape
|
|
|
Name: "Inkscape",
|
|
|
Description: "Inkscape是一个自由开源的矢量图形编辑器。在mupdf处理PDF出现兼容问题失败时,自动切换inkscape来处理。",
|
|
|
Cmd: "inkscape",
|
|
|
IsRequired: true,
|
|
|
},
|
|
|
{
|
|
|
Name: "SVGO",
|
|
|
Description: "SVGO 是一个基于 Node.js 的工具,用于优化 SVG 矢量图形文件。魔豆文库用于压缩svg图片大小。",
|
|
|
Cmd: "svgo",
|
|
|
IsRequired: false,
|
|
|
},
|
|
|
{
|
|
|
Name: "PM2",
|
|
|
Description: "PM2是JavaScript运行时Node.js的进程管理器。用于做魔豆文库的系统守护进程。Windows下建议使用PM2。",
|
|
|
Cmd: "pm2",
|
|
|
IsRequired: false,
|
|
|
}, {
|
|
|
Name: "Supervisor",
|
|
|
Description: "Supervisor是一个客户端/服务器系统,用于监视进程状态,当进程不再运行时自动重启它们。用于做魔豆文库的系统守护进程。Linux下建议使用Supervisor。",
|
|
|
Cmd: "supervisorctl",
|
|
|
IsRequired: false,
|
|
|
},
|
|
|
}
|
|
|
for i := 0; i < len(envs); i++ {
|
|
|
now := time.Now()
|
|
|
err := util.CheckCommandExists(envs[i].Cmd)
|
|
|
envs[i].IsInstalled = err == nil
|
|
|
envs[i].CheckedAt = &now
|
|
|
envs[i].Version = util.GetCommandVersion(envs[i].Cmd)
|
|
|
if err != nil {
|
|
|
envs[i].Error = err.Error()
|
|
|
}
|
|
|
}
|
|
|
res.Envs = envs
|
|
|
return
|
|
|
}
|
|
|
|
|
|
func (s *ConfigAPIService) GetDeviceInfo(ctx context.Context, req *emptypb.Empty) (res *pb.DeviceInfo, err error) {
|
|
|
res = &pb.DeviceInfo{
|
|
|
Cpu: &pb.CPUInfo{},
|
|
|
Memory: &pb.MemoryInfo{},
|
|
|
}
|
|
|
cpu := device.GetCPU()
|
|
|
|
|
|
err = util.CopyStruct(&cpu, res.Cpu)
|
|
|
if err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("cpu", cpu), zap.Any("res.Cpu", res.Cpu), zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
res.Cpu.Cores = int32(runtime.NumCPU())
|
|
|
|
|
|
mem := device.GetMemory()
|
|
|
err = util.CopyStruct(&mem, res.Memory)
|
|
|
if err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("mem", mem), zap.Any("res.Memory", res.Memory), zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
res.Memory.Free = res.Memory.Total - res.Memory.Used
|
|
|
|
|
|
disks := device.GetDisk()
|
|
|
if len(disks) > 0 {
|
|
|
for _, disk := range disks {
|
|
|
pbDisk := &pb.DiskInfo{}
|
|
|
err = util.CopyStruct(&disk, pbDisk)
|
|
|
if err != nil {
|
|
|
s.logger.Error("util.CopyStruct", zap.Any("disk", disk), zap.Any("res.Disk", res.Disk), zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
res.Disk = append(res.Disk, pbDisk)
|
|
|
}
|
|
|
}
|
|
|
return res, nil
|
|
|
}
|