Files
oneterm/backend/internal/api/controller/file.go

338 lines
10 KiB
Go

package controller
import (
"fmt"
"io"
"io/fs"
"net/http"
"path/filepath"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/spf13/cast"
"go.uber.org/zap"
"github.com/veops/oneterm/internal/acl"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/service"
gsession "github.com/veops/oneterm/internal/session"
"github.com/veops/oneterm/pkg/errors"
"github.com/veops/oneterm/pkg/logger"
)
// GetFileHistory godoc
//
// @Tags file
// @Param page_index query int true "page_index"
// @Param page_size query int true "page_size"
// @Param search query string false "search"
// @Param action query int false "saction"
// @Param start query string false "start, RFC3339"
// @Param end query string false "end, RFC3339"
// @Param uid query int false "uid"
// @Param asset_id query int false "asset id"
// @Param accout_id query int false "account id"
// @Param client_ip query string false "client_ip"
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.Session}}
// @Router /file/history [get]
func (c *Controller) GetFileHistory(ctx *gin.Context) {
// Create filter conditions
filters := make(map[string]interface{})
// Get user permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
filters["uid"] = currentUser.Uid
}
// Add other filter conditions
if search := ctx.Query("search"); search != "" {
filters["user_name LIKE ?"] = "%" + search + "%"
}
if status := ctx.Query("status"); status != "" {
filters["status"] = cast.ToInt(status)
}
if uid := ctx.Query("uid"); uid != "" {
filters["uid"] = cast.ToInt(uid)
}
if assetId := ctx.Query("asset_id"); assetId != "" {
filters["asset_id"] = cast.ToInt(assetId)
}
if accountId := ctx.Query("account_id"); accountId != "" {
filters["account_id"] = cast.ToInt(accountId)
}
if clientIp := ctx.Query("client_ip"); clientIp != "" {
filters["client_ip"] = clientIp
}
if action := ctx.Query("action"); action != "" {
filters["action"] = cast.ToInt(action)
}
// Process time range
if start := ctx.Query("start"); start != "" {
filters["created_at >= ?"] = start
}
if end := ctx.Query("end"); end != "" {
filters["created_at <= ?"] = end
}
// Use global file service
histories, count, err := service.DefaultFileService.GetFileHistory(ctx, filters)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Convert to slice of any type
historiesAny := make([]any, 0, len(histories))
for _, h := range histories {
historiesAny = append(historiesAny, h)
}
result := &ListData{
Count: count,
List: historiesAny,
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: result,
})
}
// FileLS godoc
//
// @Tags file
// @Param asset_id path int true "asset_id"
// @Param account_id path int true "account_id"
// @Param dir query string true "dir"
// @Success 200 {object} HttpResponse
// @Router /file/ls/:asset_id/:account_id [post]
func (c *Controller) FileLS(ctx *gin.Context) {
sess := &gsession.Session{
Session: &model.Session{
AssetId: cast.ToInt(ctx.Param("asset_id")),
AccountId: cast.ToInt(ctx.Param("account_id")),
},
}
if ok, err := hasAuthorization(ctx, sess); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
} else if !ok {
ctx.AbortWithError(http.StatusForbidden, &errors.ApiError{Code: errors.ErrNoPerm, Data: map[string]any{}})
return
}
// Use global file service
info, err := service.DefaultFileService.ReadDir(ctx, sess.Session.AssetId, sess.Session.AccountId, ctx.Query("dir"))
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
res := &ListData{
Count: int64(len(info)),
List: lo.Map(info, func(f fs.FileInfo, _ int) any {
return &service.FileInfo{
Name: f.Name(),
IsDir: f.IsDir(),
Size: f.Size(),
Mode: f.Mode().String(),
}
}),
}
ctx.JSON(http.StatusOK, NewHttpResponseWithData(res))
}
// FileMkdir file
//
// @Tags account
// @Param asset_id path int true "asset_id"
// @Param account_id path int true "account_id"
// @Param dir query string true "dir "
// @Success 200 {object} HttpResponse
// @Router /file/mkdir/:asset_id/:account_id [post]
func (c *Controller) FileMkdir(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
sess := &gsession.Session{
Session: &model.Session{
AssetId: cast.ToInt(ctx.Param("asset_id")),
AccountId: cast.ToInt(ctx.Param("account_id")),
},
}
if ok, err := hasAuthorization(ctx, sess); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
} else if !ok {
ctx.AbortWithError(http.StatusForbidden, &errors.ApiError{Code: errors.ErrNoPerm, Data: map[string]any{}})
return
}
// Use global file service
if err := service.DefaultFileService.MkdirAll(ctx, sess.Session.AssetId, sess.Session.AccountId, ctx.Query("dir")); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
// Create history record
h := &model.FileHistory{
Uid: currentUser.GetUid(),
UserName: currentUser.GetUserName(),
AssetId: sess.Session.AssetId,
AccountId: sess.Session.AccountId,
ClientIp: ctx.ClientIP(),
Action: model.FILE_ACTION_MKDIR,
Dir: ctx.Query("dir"),
}
if err := service.DefaultFileService.AddFileHistory(ctx, h); err != nil {
logger.L().Error("record mkdir failed", zap.Error(err), zap.Any("history", h))
}
ctx.JSON(http.StatusOK, defaultHttpResponse)
}
// FileUpload godoc
//
// @Tags file
// @Param asset_id path int true "asset_id"
// @Param account_id path int true "account_id"
// @Param path query string true "path"
// @Success 200 {object} HttpResponse
// @Router /file/upload/:asset_id/:account_id [post]
func (c *Controller) FileUpload(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
sess := &gsession.Session{
Session: &model.Session{
AssetId: cast.ToInt(ctx.Param("asset_id")),
AccountId: cast.ToInt(ctx.Param("account_id")),
},
}
if ok, err := hasAuthorization(ctx, sess); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
} else if !ok {
ctx.AbortWithError(http.StatusForbidden, &errors.ApiError{Code: errors.ErrNoPerm, Data: map[string]any{}})
return
}
f, fh, err := ctx.Request.FormFile("file")
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
content, err := io.ReadAll(f)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
// Use global file service
rf, err := service.DefaultFileService.Create(ctx, sess.Session.AssetId, sess.Session.AccountId, filepath.Join(ctx.Query("dir"), fh.Filename))
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
if _, err = rf.Write(content); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Create history record
h := &model.FileHistory{
Uid: currentUser.GetUid(),
UserName: currentUser.GetUserName(),
AssetId: sess.Session.AssetId,
AccountId: sess.Session.AccountId,
ClientIp: ctx.ClientIP(),
Action: model.FILE_ACTION_UPLOAD,
Dir: ctx.Query("dir"),
Filename: fh.Filename,
}
if err = service.DefaultFileService.AddFileHistory(ctx, h); err != nil {
logger.L().Error("record upload failed", zap.Error(err), zap.Any("history", h))
}
ctx.JSON(http.StatusOK, defaultHttpResponse)
}
// FileDownload godoc
//
// @Tags file
// @Param asset_id path int true "asset_id"
// @Param account_id path int true "account_id"
// @Param dir query string true "dir"
// @Param filename query string true "filename"
// @Param file formData string true "file field name"
// @Success 200 {object} HttpResponse
// @Router /file/download/:asset_id/:account_id [get]
func (c *Controller) FileDownload(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
sess := &gsession.Session{
Session: &model.Session{
AssetId: cast.ToInt(ctx.Param("asset_id")),
AccountId: cast.ToInt(ctx.Param("account_id")),
},
}
if ok, err := hasAuthorization(ctx, sess); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
} else if !ok {
ctx.AbortWithError(http.StatusForbidden, &errors.ApiError{Code: errors.ErrNoPerm, Data: map[string]any{}})
return
}
// Use global file service
rf, err := service.DefaultFileService.Open(ctx, sess.Session.AssetId, sess.Session.AccountId, filepath.Join(ctx.Query("dir"), ctx.Query("filename")))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
defer rf.Close()
content, err := io.ReadAll(rf)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Create history record
h := &model.FileHistory{
Uid: currentUser.GetUid(),
UserName: currentUser.GetUserName(),
AssetId: sess.Session.AssetId,
AccountId: sess.Session.AccountId,
ClientIp: ctx.ClientIP(),
Action: model.FILE_ACTION_DOWNLOAD,
Dir: ctx.Query("dir"),
Filename: ctx.Query("filename"),
}
if err = service.DefaultFileService.AddFileHistory(ctx, h); err != nil {
logger.L().Error("record download failed", zap.Error(err), zap.Any("history", h))
}
rw := ctx.Writer
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", ctx.Query("filename")))
rw.Header().Set("Content-Type", "application/octet-stream")
rw.Header().Set("Content-Length", fmt.Sprintf("%d", len(content)))
rw.Write(content)
}