chore: Update dependencies and configuration files

This commit is contained in:
divyam234 2024-02-12 23:52:52 +05:30
parent ddbc88f770
commit 82e4f4afe2
21 changed files with 109 additions and 303 deletions

View file

@ -12,7 +12,10 @@ builds:
main: cmd/teldrive/main.go
flags: -trimpath
ldflags: "-extldflags -static -s -w"
ldflags:
- -extldflags=-static
- -s -w
- -X {{ .ModulePath }}/internal/config.Version={{ .Version }}
mod_timestamp: "{{ .CommitTimestamp }}"
goos:
- linux

2
.vscode/launch.json vendored
View file

@ -10,7 +10,7 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/teldrive",
"args": ["run"]
"args": ["run","--config","C:\\Users\\divya\\Documents\\Github\\teldrive\\bin\\config.toml"]
}
]
}

View file

@ -7,6 +7,7 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD)
GIT_LINK := $(shell git remote get-url origin)
GIT_DEV_TAG := $(shell git describe --tags --abbrev=0 --match='*-dev')
ENV_FILE := $(FRONTEND_DIR)/.env
MODULE_PATH := $(shell go list -m)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
@ -36,7 +37,7 @@ endif
backend:
@echo "Building backend for $(GOOS)/$(GOARCH)..."
go build -trimpath -ldflags "-s -w -extldflags=-static" -o $(BUILD_DIR)/$(APP_NAME)$(BINARY_EXTENSION) ./cmd/teldrive
go build -trimpath -ldflags "-s -w -X $(MODULE_PATH)/internal/config.Version=$(GIT_VERSION) -extldflags=-static" -o $(BUILD_DIR)/$(APP_NAME)$(BINARY_EXTENSION)
build: frontend backend
@echo "Building complete."

17
cmd/root.go Normal file
View file

@ -0,0 +1,17 @@
package cmd
import (
"github.com/spf13/cobra"
)
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "teldrive",
Short: "Teldrive",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(NewRun(), NewVersion())
return cmd
}

View file

@ -1,4 +1,4 @@
package main
package cmd
import (
"context"
@ -52,7 +52,7 @@ func NewRun() *cobra.Command {
runCmd.Flags().DurationVar(&config.Server.GracefulShutdown, "server-graceful-shutdown", 15*time.Second, "Server graceful shutdown timeout")
runCmd.Flags().IntVarP(&config.Log.Level, "log-level", "", -1, "Logging level")
runCmd.Flags().StringVar(&config.Log.Encoding, "log-encoding", "console", "Logging encoding")
runCmd.Flags().StringVar(&config.Log.File, "log-file", "", "Logging file path")
runCmd.Flags().BoolVar(&config.Log.Development, "log-development", false, "Enable development mode")
runCmd.Flags().StringVar(&config.JWT.Secret, "jwt-secret", "", "JWT secret key")
@ -62,8 +62,8 @@ func NewRun() *cobra.Command {
runCmd.Flags().StringVar(&config.DB.DataSource, "db-data-source", "", "Database connection string")
runCmd.Flags().IntVar(&config.DB.LogLevel, "db-log-level", 1, "Database log level")
runCmd.Flags().BoolVar(&config.DB.Migrate.Enable, "db-migrate-enable", true, "Enable database migration")
runCmd.Flags().IntVar(&config.DB.Pool.MaxOpen, "db-pool-max-open", 25, "Database max open connections")
runCmd.Flags().IntVar(&config.DB.Pool.MaxIdle, "db-pool-max-idle", 25, "Database max idle connections")
runCmd.Flags().IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-open-connections", 25, "Database max open connections")
runCmd.Flags().IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-idle-connections", 25, "Database max idle connections")
runCmd.Flags().DurationVar(&config.DB.Pool.MaxLifetime, "db-pool-max-lifetime", 10*time.Minute, "Database max connection lifetime")
runCmd.Flags().IntVar(&config.TG.AppId, "tg-app-id", 0, "Telegram app ID")
@ -93,9 +93,9 @@ func NewRun() *cobra.Command {
func runApplication(conf *config.Config) {
logging.SetConfig(&logging.Config{
Encoding: conf.Log.Encoding,
Level: zapcore.Level(conf.Log.Level),
Development: conf.Log.Development,
FilePath: conf.Log.File,
})
defer logging.DefaultLogger().Sync()

View file

@ -1,29 +0,0 @@
package main
import (
"log"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "teldrive [command]",
Short: "Teldrive",
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func init() {
runCmd := NewRun()
rootCmd.AddCommand(runCmd, NewVersion())
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Printf("failed to execute command. err: %v", err)
os.Exit(1)
}
}

View file

@ -1,21 +1,18 @@
package main
package cmd
import (
"runtime"
"github.com/divyam234/teldrive/internal/config"
"github.com/spf13/cobra"
)
var (
AppVersion = "dev"
)
func NewVersion() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Check the version info",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Printf("teldrive %s\n", AppVersion)
cmd.Printf("teldrive %s\n", config.Version)
cmd.Printf("- os/type: %s\n", runtime.GOOS)
cmd.Printf("- os/arch: %s\n", runtime.GOARCH)
cmd.Printf("- go/version: %s\n", runtime.Version())

View file

@ -6,18 +6,17 @@
enable = true
[db.pool]
max-idle = 25
max-idle-connections = 25
max-lifetime = "10m"
max-open = 25
max-open-connections = 25
[jwt]
allowed-users = [""]
secret = ""
session-time = "30d"
session-time = "720h"
[log]
development = false
encoding = "console"
development = true
level = -1
[server]
@ -43,5 +42,5 @@
[tg.uploads]
encryption-key = ""
retention = "15d"
retention = "360h"
threads = 16

1
go.mod
View file

@ -23,6 +23,7 @@ require (
go.uber.org/fx v1.20.1
go.uber.org/zap v1.26.0
golang.org/x/time v0.5.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/postgres v1.5.6
gorm.io/gorm v1.25.7
)

2
go.sum
View file

@ -401,6 +401,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View file

@ -42,8 +42,8 @@ type TGConfig struct {
type LoggingConfig struct {
Level int
Encoding string
Development bool
File string
}
type JWTConfig struct {
@ -59,8 +59,8 @@ type DBConfig struct {
Enable bool
}
Pool struct {
MaxOpen int
MaxIdle int
MaxLifetime time.Duration
MaxOpenConnections int
MaxIdleConnections int
MaxLifetime time.Duration
}
}

View file

@ -0,0 +1,3 @@
package config
var Version = "dev"

View file

@ -45,8 +45,8 @@ func NewDatabase(cfg *config.Config) (*gorm.DB, error) {
if err != nil {
return nil, err
}
rawDB.SetMaxOpenConns(cfg.DB.Pool.MaxOpen)
rawDB.SetMaxIdleConns(cfg.DB.Pool.MaxIdle)
rawDB.SetMaxOpenConns(cfg.DB.Pool.MaxOpenConnections)
rawDB.SetMaxIdleConns(cfg.DB.Pool.MaxIdleConnections)
rawDB.SetConnMaxLifetime(cfg.DB.Pool.MaxLifetime)
if cfg.DB.Migrate.Enable {

View file

@ -25,7 +25,7 @@ func NewTestDatabase(tb testing.TB, migration bool) *gorm.DB {
TablePrefix: "teldrive.",
SingularTable: false,
},
PrepareStmt: false,
PrepareStmt: true,
NowFunc: func() time.Time {
return time.Now().UTC()
},

View file

@ -1,49 +0,0 @@
package handler
import (
"encoding/json"
"fmt"
)
type ErrorCode string
const (
// 400 bad request
InvalidQueryValue = ErrorCode("InvalidQueryValue")
InvalidUriValue = ErrorCode("InvalidUriValue")
InvalidBodyValue = ErrorCode("InvalidBodyValue")
// 404 not found
NotFoundEntity = ErrorCode("NotFoundEntity")
// 409 duplicate
DuplicateEntry = ErrorCode("DuplicateEntry")
// 500
InternalServerError = ErrorCode("InternalServerError")
)
type ErrorResponse struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Errors interface{} `json:"-"`
}
func (e *ErrorResponse) MarshalJSON() ([]byte, error) {
message := fmt.Sprintf("[%s]", e.Code)
if e.Message != "" {
message += " " + e.Message
}
m := map[string]interface{}{
"code": e.Code,
"message": message,
}
if e.Errors != nil {
m["errors"] = e.Errors
}
return json.Marshal(&m)
}
func (e *ErrorResponse) Error() string {
return fmt.Sprintf("ErrorResponse{Code:%s, Message:%s, Errors:%v}", e.Code, e.Message, e.Errors)
}

View file

@ -1,47 +0,0 @@
package handler
import (
"github.com/gin-gonic/gin"
"net/http"
)
func HandleRequest(c *gin.Context, f func(c *gin.Context) *Response) {
ctx := c.Request.Context()
if _, ok := ctx.Deadline(); !ok {
handleRequestReal(c, f(c))
return
}
doneChan := make(chan *Response)
go func() {
doneChan <- f(c)
}()
select {
case <-ctx.Done():
// Nothing to do because err handled from timeout middleware
case res := <-doneChan:
handleRequestReal(c, res)
}
}
func handleRequestReal(c *gin.Context, res *Response) {
if res.Err == nil {
statusCode := res.StatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
if res.Data != nil {
c.JSON(res.StatusCode, res.Data)
} else {
c.Status(res.StatusCode)
}
return
}
var err *ErrorResponse
err, ok := res.Err.(*ErrorResponse)
if !ok {
res.StatusCode = http.StatusInternalServerError
err = &ErrorResponse{Code: InternalServerError, Message: "An error has occurred, please try again later"}
}
c.AbortWithStatusJSON(res.StatusCode, err)
}

View file

@ -1,101 +0,0 @@
package handler
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/divyam234/teldrive/internal/middleware"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestHandleRequest(t *testing.T) {
cases := []struct {
Name string
Func func(c *gin.Context) *Response
// expected
Code int
Body string
}{
{
Name: "Success with data",
Func: func(c *gin.Context) *Response {
return NewSuccessResponse(http.StatusOK, map[string]interface{}{
"data": "ok",
})
},
Code: http.StatusOK,
Body: `
{
"data": "ok"
}
`,
}, {
Name: "Fail with ErrorResponse",
Func: func(c *gin.Context) *Response {
return NewErrorResponse(http.StatusBadRequest, InvalidQueryValue, "invalid query", nil)
},
Code: http.StatusBadRequest,
Body: `
{
"code": "InvalidQueryValue",
"message": "[InvalidQueryValue] invalid query"
}
`,
}, {
Name: "Fail with any error",
Func: func(c *gin.Context) *Response {
return &Response{
Err: errors.New("any error"),
}
},
Code: http.StatusInternalServerError,
Body: `
{
"code": "InternalServerError",
"message": "[InternalServerError] An error has occurred, please try again later"
}
`,
}, {
Name: "Timeout with ErrorResponse",
Func: func(c *gin.Context) *Response {
time.Sleep(250 * time.Millisecond)
return nil
},
Code: http.StatusGatewayTimeout,
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
s := setupRouterWithHandler(func(c *gin.Engine) {
c.Use(middleware.TimeoutMiddleware(200 * time.Millisecond))
}, func(c *gin.Context) {
HandleRequest(c, tc.Func)
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://localhost/foo", nil)
// when
s.ServeHTTP(res, req)
// then
assert.Equal(t, tc.Code, res.Code)
if tc.Body != "" {
assert.JSONEq(t, tc.Body, res.Body.String())
}
})
}
}
func setupRouterWithHandler(middlewareFunc func(c *gin.Engine), handler func(c *gin.Context)) *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.Default()
middlewareFunc(r)
r.GET("/foo", handler)
return r
}

View file

@ -1,31 +0,0 @@
package handler
type Response struct {
StatusCode int
Data interface{}
Err error
}
func NewSuccessResponse(statusCode int, data interface{}) *Response {
return &Response{
StatusCode: statusCode,
Data: data,
}
}
func NewErrorResponse(statusCode int, code ErrorCode, message string, details interface{}) *Response {
return &Response{
StatusCode: statusCode,
Err: &ErrorResponse{
Code: code,
Message: message,
Errors: details,
},
}
}
func NewInternalErrorResponse(err error) *Response {
return &Response{
Err: err,
}
}

18
main.go Normal file
View file

@ -0,0 +1,18 @@
package main
import (
"context"
"os"
"os/signal"
"github.com/divyam234/teldrive/cmd"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := cmd.New().ExecuteContext(ctx); err != nil {
panic(err)
}
}

View file

@ -2,11 +2,14 @@ package logging
import (
"context"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
type contextKey = string
@ -19,22 +22,21 @@ var (
)
var conf = &Config{
Encoding: "console",
Level: zapcore.InfoLevel,
Development: true,
}
type Config struct {
Encoding string
Level zapcore.Level
Development bool
FilePath string
}
func SetConfig(c *Config) {
conf = &Config{
Encoding: c.Encoding,
Level: c.Level,
Development: c.Development,
FilePath: c.FilePath,
}
}
@ -43,21 +45,38 @@ func SetLevel(l zapcore.Level) {
}
func NewLogger(conf *Config) *zap.SugaredLogger {
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.ISO8601TimeEncoder
cfg := zap.Config{
Encoding: conf.Encoding,
EncoderConfig: ec,
Level: zap.NewAtomicLevelAt(conf.Level),
Development: conf.Development,
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
ec.EncodeLevel = zapcore.CapitalColorLevelEncoder
ec.CallerKey = ""
ec.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("02/01/2006 03:04 PM"))
}
logger, err := cfg.Build()
if err != nil {
logger = zap.NewNop()
var cores []zapcore.Core
cores = append(cores, zapcore.NewCore(zapcore.NewConsoleEncoder(ec),
zapcore.AddSync(os.Stdout), zap.NewAtomicLevelAt(conf.Level)))
if conf.FilePath != "" {
lumberjackLogger := &lumberjack.Logger{
Filename: conf.FilePath,
MaxSize: 10,
MaxBackups: 3,
MaxAge: 15,
Compress: true,
}
cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(lumberjackLogger), zap.NewAtomicLevelAt(conf.Level)))
}
return logger.Sugar()
options := []zap.Option{}
if conf.Development {
options = append(options, zap.Development())
}
return zap.New(zapcore.NewTee(cores...), options...).Sugar()
}
func DefaultLogger() *zap.SugaredLogger {

View file

@ -165,30 +165,33 @@ func (fs *FileService) ListFiles(userId int64, fquery *schemas.FileQuery) (*sche
if fquery.Op == "list" {
query.Order("type DESC").Order(getOrder(fquery)).Where("parent_id = ?", pathId).
Model(filter)
Model(filter).Where(&filter)
} else if fquery.Op == "find" {
filter.Name = fquery.Name
filter.Type = fquery.Type
filter.ParentID = fquery.ParentID
filter.Starred = *fquery.Starred
filter.Path = fquery.Path
filter.Type = fquery.Type
if fquery.Starred != nil {
filter.Starred = *fquery.Starred
}
if fquery.Path != "" && fquery.Name != "" {
filter.ParentID = pathId
filter.Path = ""
}
query.Order("type DESC").Order(getOrder(fquery)).Where(fquery).
Model(&filter)
query.Order("type DESC").Order(getOrder(fquery)).
Model(&filter).Where(&filter)
} else if fquery.Op == "search" {
query.Where("teldrive.get_tsquery(?) @@ teldrive.get_tsvector(name)", fquery.Search)
query.Order(getOrder(fquery)).
Model(filter)
Model(&filter).Where(&filter)
}
if fquery.Path == "" {