diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..cfb8fb6 --- /dev/null +++ b/.air.toml @@ -0,0 +1,42 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./bin/octopus" + cmd = "go build -o ./bin/octopus ./cmd/octopus" + delay = 0 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/Makefile b/Makefile index 7bd560e..acb7993 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,10 @@ gen: @ rm -rf ./internal/dal/query @ go run ./cmd/gen/main.go +.PHONY: dev +dev: + air run + lint: golines -m 180 -w --reformat-tags . diff --git a/cmd/octopus/server/cmd.go b/cmd/octopus/server/cmd.go index 9b036b3..8447432 100644 --- a/cmd/octopus/server/cmd.go +++ b/cmd/octopus/server/cmd.go @@ -3,7 +3,6 @@ package server import ( "context" "fmt" - "net/http" "os" "os/signal" "sync" @@ -12,7 +11,6 @@ import ( "octopus/internal/config" "octopus/pkg/logger" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -40,28 +38,6 @@ var CmdRun = &cobra.Command{ } }() - prometheusAddr := fmt.Sprintf(":%d", config.Get().PrometheusPort) - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - server := &http.Server{Addr: prometheusAddr, Handler: mux} - - wg.Add(1) - go func() { - defer wg.Done() - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatal().Err(err).Msg("failed to start prometheus server") - } - }() - - wg.Add(1) - go func() { - defer wg.Done() - <-ctx.Done() - if err := server.Shutdown(ctx); err != nil { - log.Fatal().Err(err).Msg("failed to shutdown prometheus server") - } - }() - sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) diff --git a/internal/config/config.go b/internal/config/config.go index f6e1bff..12ac28a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,10 +14,9 @@ import ( ) type Config struct { - IsLocal bool - Debug bool `mapstructure:"debug"` - HTTPPort int `mapstructure:"http_port" validate:"required"` - PrometheusPort int `mapstructure:"prometheus_port" validate:"required"` + IsLocal bool + Debug bool `mapstructure:"debug"` + HTTPPort int `mapstructure:"http_port" validate:"required"` Databases struct { OSS string `mapstructure:"oss" validate:"required"` diff --git a/internal/dal/model/docs.go b/internal/dal/model/docs.go index 9a828e6..f36fff4 100644 --- a/internal/dal/model/docs.go +++ b/internal/dal/model/docs.go @@ -4,6 +4,7 @@ import ( "context" "net/url" "octopus/internal/dal" + "octopus/internal/schema" "time" "github.com/rs/xid" @@ -25,31 +26,58 @@ type DocFolder struct { func (*DocFolder) TableName() string { return TableNameDocFolder } -func (df *DocFolder) BeforeCreate(*gorm.DB) error { - df.ID = xid.New().String() + +func (df *DocFolder) ToSchema() *schema.DocFolder { + return &schema.DocFolder{ + ID: df.ID, + IsDeletable: df.IsDeletable, + IsEditable: df.IsEditable, + CreatedAt: df.CreatedAt, + UpdatedAt: df.UpdatedAt, + } +} + +func (d *DocFolder) BeforeCreate(*gorm.DB) error { + d.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 - ObjectName string `gorm:"column:object_name;type:varchar"` // 对象存储中对应的object_name - CreatedBy string `gorm:"column:created_by;type:varchar;not null"` // 创建人 + 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 + ObjectName string `gorm:"column:object_name;type:varchar"` // 对象存储中对应的object_name + UploadedAt time.Time `gorm:"column:uploaded_at;type:datetime"` // 上传时间 + 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) { - return dal.GetStorage().PresignedGetObject(ctx, df.ObjectName, time.Hour) +func (d *Doc) ToSchema(ctx context.Context) *schema.Doc { + url, _ := d.PresignedURL(ctx) + + return &schema.Doc{ + ID: d.ID, + Folder: d.Folder.ToSchema(), + PresignedURL: url, + IsDeletable: d.IsDeletable, + IsEditable: d.IsEditable, + UploadedAt: d.UploadedAt, + CreatedAt: d.CreatedAt, + UpdatedAt: d.UpdatedAt, + } +} + +func (d *Doc) PresignedURL(ctx context.Context) (*url.URL, error) { + return dal.GetStorage().PresignedGetObject(ctx, d.ObjectName, time.Hour) } func (*Doc) TableName() string { return TableNameDocFolder } -func (df *Doc) BeforeCreate(*gorm.DB) error { - df.ID = xid.New().String() +func (d *Doc) BeforeCreate(*gorm.DB) error { + d.ID = xid.New().String() return nil } diff --git a/internal/router/doc.go b/internal/router/doc.go index 9cae4fd..91bb55a 100644 --- a/internal/router/doc.go +++ b/internal/router/doc.go @@ -2,76 +2,31 @@ package router import ( "octopus/internal/schema" + "octopus/internal/service" + "github.com/casdoor/casdoor-go-sdk/casdoorsdk" + "github.com/gofiber/fiber/v2" "github.com/neo-f/soda" ) func RegisterDocRouter(app *soda.Soda) { - app.Get("/docs", nil). - AddTags("文档管理"). - SetSummary("获取文档列表"). - AddJWTSecurity(JWTRequired). - SetParameters(schema.ListDocQuery{}). - AddJSONResponse(200, schema.DocList{}).OK() - - app.Post("/docs", nil). - AddTags("文档管理"). - SetSummary("新建文档"). - AddJWTSecurity(JWTRequired). - SetJSONRequestBody(schema.CreateDoc{}). - AddJSONResponse(200, schema.Doc{}).OK() - - app.Put("/docs/:id", nil). - AddTags("文档管理"). - SetSummary("更新文档"). - AddJWTSecurity(JWTRequired). - SetParameters(schema.DocID{}). - SetJSONRequestBody(schema.UpdateDoc{}). - AddJSONResponse(200, schema.Doc{}).OK() - - // get presigned url for tmp file upload - app.Get("/docs/upload-url", nil). - AddTags("文档管理"). - SetSummary("获取临时上传文件用的预签名URL"). - AddJWTSecurity(JWTRequired). - SetParameters(schema.CreateUploadURL{}). - AddJSONResponse(200, schema.UploadURL{}).OK() - - app.Delete("/docs/:id", nil). - AddTags("文档管理"). - SetSummary("获取文档列表"). - AddJWTSecurity(JWTRequired). - SetParameters(schema.DocID{}). - AddJSONResponse(200, nil).OK() - - app.Post("/docs/batch/delete", nil). - AddTags("文档管理"). - SetSummary("批量-文件删除"). - AddJWTSecurity(JWTRequired). - SetJSONRequestBody(schema.DocsBatchDelete{}). - AddJSONResponse(200, schema.DocsBatchResults{}).OK() - - app.Post("/docs/batch/update", nil). - AddTags("文档管理"). - SetSummary("批量-文件更新"). - AddJWTSecurity(JWTRequired). - SetJSONRequestBody(schema.DocsBatchUpdate{}). - AddJSONResponse(200, schema.DocsBatchResults{}).OK() + registerDocs(app) + registerDocFolders(app) +} +func registerDocFolders(app *soda.Soda) { app.Get("/doc-folders", nil). AddTags("文件夹管理"). SetSummary("获取文件夹树"). AddJWTSecurity(JWTRequired). SetParameters(schema.GetDocFolderTree{}). AddJSONResponse(200, schema.DocFolderWithChildren{}).OK() - app.Post("/doc-folders", nil). AddTags("文件夹管理"). SetSummary("新建文件夹"). AddJWTSecurity(JWTRequired). SetJSONRequestBody(schema.CreateDocFolder{}). AddJSONResponse(200, schema.DocFolder{}).OK() - app.Put("/doc-folders/:id", nil). AddTags("文件夹管理"). SetSummary("更新文件夹"). @@ -79,7 +34,6 @@ func RegisterDocRouter(app *soda.Soda) { SetParameters(schema.DocFolderID{}). SetJSONRequestBody(schema.UpdateDocFolder{}). AddJSONResponse(200, schema.DocFolder{}).OK() - app.Delete("/doc-folders/:id", nil). AddTags("文件夹管理"). SetSummary("删除文件夹"). @@ -87,3 +41,138 @@ func RegisterDocRouter(app *soda.Soda) { SetParameters(schema.DocFolderID{}). AddJSONResponse(200, nil).OK() } + +func registerDocs(app *soda.Soda) { + app.Get("/docs", nil). + AddTags("文档管理"). + SetSummary("获取文档列表"). + AddJWTSecurity(JWTRequired). + SetParameters(schema.ListDocQuery{}). + AddJSONResponse(200, schema.DocList{}).OK() + app.Post("/docs", nil). + AddTags("文档管理"). + SetSummary("新建文档"). + AddJWTSecurity(JWTRequired). + SetJSONRequestBody(schema.CreateDoc{}). + AddJSONResponse(200, schema.Doc{}).OK() + app.Put("/docs/:id", nil). + AddTags("文档管理"). + SetSummary("更新文档"). + AddJWTSecurity(JWTRequired). + SetParameters(schema.DocID{}). + SetJSONRequestBody(schema.UpdateDoc{}). + AddJSONResponse(200, schema.Doc{}).OK() + app.Delete("/docs/:id", nil). + AddTags("文档管理"). + SetSummary("获取文档列表"). + AddJWTSecurity(JWTRequired). + SetParameters(schema.DocID{}). + AddJSONResponse(200, nil).OK() + app.Post("/docs/batch/delete", nil). + AddTags("文档管理"). + SetSummary("批量-文件删除"). + AddJWTSecurity(JWTRequired). + SetJSONRequestBody(schema.DocsBatchDelete{}). + AddJSONResponse(200, schema.DocsBatchResults{}).OK() + app.Post("/docs/batch/update", nil). + AddTags("文档管理"). + SetSummary("批量-文件更新"). + AddJWTSecurity(JWTRequired). + SetJSONRequestBody(schema.DocsBatchUpdate{}). + AddJSONResponse(200, schema.DocsBatchResults{}).OK() + // get presigned url for tmp file upload + app.Get("/docs/upload-url", nil). + AddTags("文档管理"). + SetSummary("获取临时上传文件用的预签名URL"). + AddJWTSecurity(JWTRequired). + SetParameters(schema.CreateUploadURL{}). + AddJSONResponse(200, schema.UploadURL{}).OK() +} + +func ListDocs(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + params := c.Locals(soda.KeyParameter).(*schema.ListDocQuery) + docs, total, err := service.ListDocs(c.UserContext(), auth, params) + if err != nil { + return err + } + docSchemas := make([]*schema.Doc, 0, len(docs)) + for _, doc := range docs { + docSchemas = append(docSchemas, doc.ToSchema(c.UserContext())) + } + return c.JSON(schema.DocList{Items: docSchemas, Total: total}) +} + +func CreateDoc(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + body := c.Locals(soda.KeyRequestBody).(*schema.CreateDoc) + + doc, err := service.CreateDoc(c.UserContext(), auth, body) + if err != nil { + return err + } + return c.JSON(doc.ToSchema(c.UserContext())) +} + +func UpdateDoc(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + params := c.Locals(soda.KeyParameter).(*schema.DocID) + body := c.Locals(soda.KeyRequestBody).(*schema.UpdateDoc) + + ctx := c.UserContext() + doc, err := service.UpdateDoc(ctx, auth, params.ID, body) + if err != nil { + return err + } + return c.JSON(doc.ToSchema(ctx)) +} +func DeleteDoc(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + params := c.Locals(soda.KeyParameter).(*schema.DocID) + ctx := c.UserContext() + + if err := service.DeleteDoc(ctx, auth, params.ID); err != nil { + return err + } + return c.JSON(nil) +} + +func DeleteDocBatch(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + body := c.Locals(soda.KeyRequestBody).(*schema.DocsBatchDelete) + ctx := c.UserContext() + + resp, err := service.DeleteDocBatch(ctx, auth, body) + if err != nil { + return err + } + return c.JSON(resp) +} + +func UpdateDocBatch(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + body := c.Locals(soda.KeyRequestBody).(*schema.DocsBatchUpdate) + ctx := c.UserContext() + + resp, err := service.UpdateDocBatch(ctx, auth, body) + if err != nil { + return err + } + return c.JSON(resp) +} + +func CreateUploadURL(c *fiber.Ctx) error { + auth := c.Locals(userClaimsKey).(*casdoorsdk.Claims) + params := c.Locals(soda.KeyParameter).(*schema.CreateUploadURL) + ctx := c.UserContext() + + u, objectName, err := service.CreateUploadURL(ctx, auth, params) + if err != nil { + return err + } + + return c.JSON(schema.UploadURL{ + URL: *u, + ObjectName: objectName, + }) +} diff --git a/internal/schema/docs.go b/internal/schema/docs.go index 403f311..fe47761 100644 --- a/internal/schema/docs.go +++ b/internal/schema/docs.go @@ -44,14 +44,14 @@ type DocFolderWithChildren struct { } type Doc struct { - ID string `json:"id" oai:"description=文档ID"` - Folder DocFolder `json:"folder" oai:"description=归属文件夹信息"` - PresignedURL string `json:"presigned_url" oai:"description=文档预签名下载URL(临时下载URL)"` - IsDeletable bool `json:"is_deletable" oai:"description=文件夹是否允许被删除"` - IsEditable bool `json:"is_editable" oai:"description=文件夹是否允许被修改"` - UploadedAt time.Time `json:"uploaded_at" oai:"description=上传时间"` - CreatedAt time.Time `json:"created_at" oai:"description=创建时间"` - UpdatedAt time.Time `json:"updated_at" oai:"description=更新时间"` + ID string `json:"id" oai:"description=文档ID"` + Folder *DocFolder `json:"folder" oai:"description=归属文件夹信息"` + PresignedURL *url.URL `json:"presigned_url" oai:"description=文档预签名下载URL(临时下载URL)"` + IsDeletable bool `json:"is_deletable" oai:"description=文件夹是否允许被删除"` + IsEditable bool `json:"is_editable" oai:"description=文件夹是否允许被修改"` + UploadedAt time.Time `json:"uploaded_at" oai:"description=上传时间"` + CreatedAt time.Time `json:"created_at" oai:"description=创建时间"` + UpdatedAt time.Time `json:"updated_at" oai:"description=更新时间"` } type DocList struct { diff --git a/internal/service/doc.go b/internal/service/doc.go new file mode 100644 index 0000000..f779434 --- /dev/null +++ b/internal/service/doc.go @@ -0,0 +1,38 @@ +package service + +import ( + "context" + "net/url" + "octopus/internal/dal/model" + "octopus/internal/schema" + + "github.com/casdoor/casdoor-go-sdk/casdoorsdk" +) + +func ListDocs(ctx context.Context, auth *casdoorsdk.Claims, query *schema.ListDocQuery) ([]*model.Doc, int64, error) { + panic("implement me") +} + +func CreateDoc(ctx context.Context, auth *casdoorsdk.Claims, body *schema.CreateDoc) (*model.Doc, error) { + panic("implement me") +} + +func UpdateDoc(ctx context.Context, auth *casdoorsdk.Claims, id string, body *schema.UpdateDoc) (*model.Doc, error) { + panic("implement me") + +} +func DeleteDoc(ctx context.Context, auth *casdoorsdk.Claims, id string) error { + panic("implement me") + +} +func DeleteDocBatch(ctx context.Context, auth *casdoorsdk.Claims, param *schema.DocsBatchDelete) (*schema.DocsBatchResults, error) { + panic("implement me") + +} +func UpdateDocBatch(ctx context.Context, auth *casdoorsdk.Claims, param *schema.DocsBatchUpdate) (*schema.DocsBatchResults, error) { + panic("implement me") +} + +func CreateUploadURL(ctx context.Context, auth *casdoorsdk.Claims, param *schema.CreateUploadURL) (u *url.URL, objectName string, err error) { + panic("implement me") +}