feat: docs api design
This commit is contained in:
77
internal/dal/gorm_logger.go
Normal file
77
internal/dal/gorm_logger.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
type GormLogger struct {
|
||||
SourceField string
|
||||
SlowThreshold time.Duration
|
||||
SkipErrRecordNotFound bool
|
||||
}
|
||||
|
||||
// Error implements logger.Interface
|
||||
func (l *GormLogger) Error(ctx context.Context, s string, args ...interface{}) {
|
||||
log.Ctx(ctx).Error().Msgf(s, args...)
|
||||
}
|
||||
|
||||
// Warn implements logger.Interface
|
||||
func (*GormLogger) Warn(ctx context.Context, s string, args ...interface{}) {
|
||||
log.Ctx(ctx).Warn().Msgf(s, args...)
|
||||
}
|
||||
|
||||
// Info implements logger.Interface
|
||||
func (*GormLogger) Info(ctx context.Context, s string, args ...interface{}) {
|
||||
log.Ctx(ctx).Info().Msgf(s, args...)
|
||||
}
|
||||
|
||||
// LogMode implements logger.Interface
|
||||
func (l *GormLogger) LogMode(logger.LogLevel) logger.Interface {
|
||||
return l
|
||||
}
|
||||
|
||||
// Trace implements logger.Interface
|
||||
func (l *GormLogger) Trace(
|
||||
ctx context.Context,
|
||||
begin time.Time,
|
||||
fc func() (string, int64),
|
||||
err error,
|
||||
) {
|
||||
elapsed := time.Since(begin)
|
||||
sql, rows := fc()
|
||||
log := log.Ctx(ctx).
|
||||
With().
|
||||
Str("sql", sql).
|
||||
Str("elapsed", elapsed.String()).
|
||||
Int64("rows", rows).
|
||||
Logger()
|
||||
|
||||
if l.SourceField != "" {
|
||||
log = log.With().Str(l.SourceField, utils.FileWithLineNum()).Logger()
|
||||
}
|
||||
if err != nil && (!errors.Is(err, gorm.ErrRecordNotFound) || !l.SkipErrRecordNotFound) {
|
||||
log.Error().Err(err).Msg("[GORM] query error")
|
||||
return
|
||||
}
|
||||
|
||||
if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
|
||||
log.Warn().Msg("[GORM] slow query")
|
||||
return
|
||||
}
|
||||
log.Debug().Msg("[GORM] query")
|
||||
}
|
||||
|
||||
var _ logger.Interface = &GormLogger{}
|
||||
|
||||
var DefaultGormLogger = &GormLogger{
|
||||
SlowThreshold: time.Second * 3,
|
||||
SourceField: "source",
|
||||
SkipErrRecordNotFound: true,
|
||||
}
|
||||
18
internal/dal/migrations/migrations.go
Normal file
18
internal/dal/migrations/migrations.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"octopus/internal/dal/model"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate(db *gorm.DB) {
|
||||
if err := db.AutoMigrate(
|
||||
new(model.Doc),
|
||||
new(model.DocFolder),
|
||||
); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to migrate database")
|
||||
}
|
||||
}
|
||||
10
internal/dal/model/base.go
Normal file
10
internal/dal/model/base.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Base struct {
|
||||
OrgID string `gorm:"column:org_id;type:varchar;primaryKey;comment:组织ID"` // 组织ID
|
||||
ID string `gorm:"column:id;type:varchar;primaryKey;comment:ID"` // ID
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:创建时间"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;comment:更新时间"`
|
||||
}
|
||||
57
internal/dal/model/docs.go
Normal file
57
internal/dal/model/docs.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"octopus/internal/config"
|
||||
"octopus/internal/dal"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameDocFolder = "doc_folders"
|
||||
const TableNameDoc = "docs"
|
||||
|
||||
type DocFolder struct {
|
||||
Base
|
||||
Name string `gorm:"column:name;type:varchar;not null"` // 文件夹名称
|
||||
IsDeletable bool `gorm:"column:is_deletable;type:boolean;not null;default:true"` // 是否允许被删除
|
||||
IsEditable bool `gorm:"column:is_editable;type:boolean;not null;default:true"` // 是否允许被修改
|
||||
Path string `gorm:"column:path;type:varchar;index:idx_path"` // 路径索引
|
||||
CreatedBy string `gorm:"column:created_by;type:varchar;not null"` // 创建人
|
||||
}
|
||||
|
||||
func (*DocFolder) TableName() string {
|
||||
return TableNameDocFolder
|
||||
}
|
||||
func (df *DocFolder) BeforeCreate(*gorm.DB) error {
|
||||
df.ID = xid.New().String()
|
||||
return nil
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Base
|
||||
Name string `gorm:"column:name;type:varchar;not null"` // 文件夹名称
|
||||
IsDeletable bool `gorm:"column:is_deletable;type:boolean;not null;default:true"` // 是否允许被删除
|
||||
IsEditable bool `gorm:"column:is_editable;type:boolean;not null;default:true"` // 是否允许编辑名称
|
||||
FolderID string `gorm:"column:folder_id;type:varchar;index:idx_folder_id"` // 文件夹ID
|
||||
OSSObjectID string `gorm:"column:oss_object_id;type:varchar"` // OSS Object ID
|
||||
CreatedBy string `gorm:"column:created_by;type:varchar;not null"` // 创建人
|
||||
|
||||
Folder *DocFolder `gorm:"foreignKey:FolderID;references:ID"`
|
||||
}
|
||||
|
||||
func (df *Doc) PresignedURL(ctx context.Context) (*url.URL, error) {
|
||||
bucket := config.Get().GetOSSConfig().Bucket
|
||||
return dal.GetMinio().PresignedGetObject(ctx, bucket, df.OSSObjectID, time.Hour, nil)
|
||||
}
|
||||
|
||||
func (*Doc) TableName() string {
|
||||
return TableNameDocFolder
|
||||
}
|
||||
func (df *Doc) BeforeCreate(*gorm.DB) error {
|
||||
df.ID = xid.New().String()
|
||||
return nil
|
||||
}
|
||||
50
internal/dal/oss.go
Normal file
50
internal/dal/oss.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"octopus/internal/config"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/rs/zerolog/log"
|
||||
// pb "github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
var (
|
||||
ossOnce sync.Once
|
||||
ossInstance *minio.Client
|
||||
)
|
||||
|
||||
func GetMinio() *minio.Client {
|
||||
ossOnce.Do(initMinio)
|
||||
return ossInstance
|
||||
}
|
||||
|
||||
func initMinio() {
|
||||
log.Info().Msg("loading minio configs")
|
||||
|
||||
ossConfig, err := url.Parse(config.Get().Databases.OSS)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("parse oss config error")
|
||||
}
|
||||
accessSecret, _ := ossConfig.User.Password()
|
||||
// defaultConfig := &model.StorageConfig{
|
||||
// Schema: ossConfig.Scheme,
|
||||
// Endpoint: ossConfig.Host,
|
||||
// AccessID: ossConfig.User.Username(),
|
||||
// AccessSecret: accessSecret,
|
||||
// Bucket: ossConfig.Query().Get("bucket"),
|
||||
// Region: ossConfig.Query().Get("region"),
|
||||
// Secure: cast.ToBool(ossConfig.Query().Get("secure")),
|
||||
// }
|
||||
minioClient, err := minio.New(ossConfig.Host, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(ossConfig.User.Username(), accessSecret, ""),
|
||||
Secure: false,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("minio client init error: %v", err)
|
||||
}
|
||||
|
||||
ossInstance = minioClient
|
||||
}
|
||||
75
internal/dal/postgres.go
Normal file
75
internal/dal/postgres.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
slog "log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"octopus/internal/config"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
sqlOnce sync.Once
|
||||
sqlInstance *gorm.DB
|
||||
)
|
||||
|
||||
func GetpostgresTest() *gorm.DB {
|
||||
l := logger.Default
|
||||
l = l.LogMode(logger.Info)
|
||||
db, _ := gorm.Open(sqlite.Open("file::memory:?parseTime=True&loc=Local"), &gorm.Config{Logger: l})
|
||||
return db
|
||||
}
|
||||
|
||||
func GetPostgres() *gorm.DB {
|
||||
sqlOnce.Do(initPostgres)
|
||||
return sqlInstance
|
||||
}
|
||||
|
||||
func initPostgres() {
|
||||
log.Info().Msg("loading postgres configs")
|
||||
dsn := config.Get().Databases.PostgreSQL
|
||||
db, err := gorm.Open(
|
||||
postgres.Open(dsn),
|
||||
&gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: logger.New(
|
||||
slog.New(os.Stdout, "\r\n", slog.LstdFlags), logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
LogLevel: logger.Info,
|
||||
IgnoreRecordNotFoundError: false,
|
||||
Colorful: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal().Str("dsn", dsn).Err(err).Msg("connected to postgres failed")
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Fatal().Str("dsn", dsn).Err(err).Msg("connected to postgres failed")
|
||||
}
|
||||
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
|
||||
sqlDB.SetMaxIdleConns(50)
|
||||
|
||||
// SetMaxOpenConns sets the maximum number of open connections to the database.
|
||||
sqlDB.SetMaxOpenConns(50)
|
||||
|
||||
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
|
||||
sqlDB.SetConnMaxLifetime(time.Minute * 2)
|
||||
if err != nil {
|
||||
log.Fatal().Str("dsn", dsn).Err(err).Msg("connected to postgres failed")
|
||||
}
|
||||
if config.Get().Debug {
|
||||
db = db.Debug()
|
||||
}
|
||||
sqlInstance = db
|
||||
log.Info().Msg("connected to postgres")
|
||||
}
|
||||
23
internal/dal/repo/docs.go
Normal file
23
internal/dal/repo/docs.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"octopus/internal/dal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocFolderRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDocFolderRepo(db *gorm.DB) *DocFolderRepo {
|
||||
return &DocFolderRepo{db}
|
||||
}
|
||||
|
||||
type DocFolderRepoInterface interface {
|
||||
CreateFolder(folder *model.DocFolder) error
|
||||
}
|
||||
|
||||
// func (r *DocFolderRepo) CreateFolder(folder *model.DocFolder) error {
|
||||
// return r.db.Create(folder).Error
|
||||
// }
|
||||
Reference in New Issue
Block a user