refactor: split backend into standalone server project
Remove embedded frontend and workflow editor assets, add standalone API deployment configuration, and retain the current backend service updates.
This commit is contained in:
+20
-11
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
|
||||
// "gorm.io/driver/sqlite" // Sqlite driver based on CGO
|
||||
"github.com/glebarez/sqlite" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
|
||||
@@ -23,17 +24,9 @@ import (
|
||||
)
|
||||
|
||||
func InitDB(cfg config.DBConfig) (*gorm.DB, error) {
|
||||
var dialector gorm.Dialector
|
||||
switch cfg.Type {
|
||||
case "sqlite":
|
||||
if err := ensureSQLiteDir(cfg.DSN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dialector = sqlite.Open(cfg.DSN)
|
||||
case "mysql":
|
||||
dialector = mysql.Open(cfg.DSN)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported db type: %s", cfg.Type)
|
||||
dialector, err := newDialector(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, &gorm.Config{
|
||||
@@ -76,6 +69,22 @@ func InitDB(cfg config.DBConfig) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func newDialector(cfg config.DBConfig) (gorm.Dialector, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(cfg.Type)) {
|
||||
case "sqlite":
|
||||
if err := ensureSQLiteDir(cfg.DSN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sqlite.Open(cfg.DSN), nil
|
||||
case "mysql":
|
||||
return mysql.Open(cfg.DSN), nil
|
||||
case "postgres", "postgresql":
|
||||
return postgres.Open(cfg.DSN), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported db type: %s", cfg.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSQLiteDir(dsn string) error {
|
||||
dbPath := sqliteFilePath(dsn)
|
||||
if dbPath == "" {
|
||||
|
||||
@@ -4,8 +4,47 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"agent-desk/internal/pkg/config"
|
||||
)
|
||||
|
||||
func TestNewDialector(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
dbType string
|
||||
want string
|
||||
}{
|
||||
{dbType: "sqlite", want: "sqlite"},
|
||||
{dbType: "mysql", want: "mysql"},
|
||||
{dbType: "postgres", want: "postgres"},
|
||||
{dbType: "postgresql", want: "postgres"},
|
||||
{dbType: " PostgreSQL ", want: "postgres"},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.dbType, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dialector, err := newDialector(config.DBConfig{Type: tt.dbType, DSN: ":memory:"})
|
||||
if err != nil {
|
||||
t.Fatalf("newDialector() error = %v", err)
|
||||
}
|
||||
if got := dialector.Name(); got != tt.want {
|
||||
t.Fatalf("dialector.Name() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDialectorRejectsUnsupportedType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := newDialector(config.DBConfig{Type: "oracle"}); err == nil {
|
||||
t.Fatal("newDialector() error = nil, want unsupported type error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLiteFilePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/tracex"
|
||||
"agent-desk/internal/services"
|
||||
webspa "agent-desk/web"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
@@ -46,19 +45,14 @@ func NewServer() (*gin.Engine, error) {
|
||||
notFoundPrefixes = append(notFoundPrefixes, baseURL+"/")
|
||||
}
|
||||
app.StaticFS(cfg.Storage.Local.BaseURL, ginx.StaticFiles(cfg.Storage.Local.Root))
|
||||
ginx.HandleSPA(app, ginx.SPAOptions{
|
||||
Root: "./web/out",
|
||||
EmbeddedFS: webspa.SPA,
|
||||
EmbeddedRoot: "out",
|
||||
DirOptions: ginx.DirOptions{
|
||||
ShowList: false,
|
||||
SPA: true,
|
||||
IndexName: "index.html",
|
||||
},
|
||||
NotFoundPrefixes: notFoundPrefixes,
|
||||
NotFoundHandler: func(ctx *gin.Context) {
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusNotFound, web.JsonErrorCode(http.StatusNotFound, i18nx.T(ctx, "error.notFound")))
|
||||
},
|
||||
app.NoRoute(func(ctx *gin.Context) {
|
||||
for _, prefix := range notFoundPrefixes {
|
||||
if strings.HasPrefix(ctx.Request.URL.Path, prefix) {
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusNotFound, web.JsonErrorCode(http.StatusNotFound, i18nx.T(ctx, "error.notFound")))
|
||||
return
|
||||
}
|
||||
}
|
||||
httpx.WriteHttpStatusJSON(ctx, http.StatusNotFound, web.JsonErrorCode(http.StatusNotFound, i18nx.T(ctx, "error.notFound")))
|
||||
})
|
||||
|
||||
return app, nil
|
||||
|
||||
@@ -183,7 +183,7 @@ func TestNewServerDoesNotExposeLegacyAuthOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerSeparatesAPIStaticAndSPA(t *testing.T) {
|
||||
func TestNewServerReturnsJSONNotFoundWithoutFrontendSPA(t *testing.T) {
|
||||
config.SetCurrent(&config.Config{
|
||||
Storage: config.StorageConfig{
|
||||
Local: config.LocalStorageConfig{
|
||||
@@ -204,7 +204,7 @@ func TestNewServerSeparatesAPIStaticAndSPA(t *testing.T) {
|
||||
contentType string
|
||||
}{
|
||||
{path: "/api/not-exists", wantStatus: http.StatusNotFound, contentType: "application/json"},
|
||||
{path: "/dashboard/not-exists", wantStatus: http.StatusOK, contentType: "text/html"},
|
||||
{path: "/dashboard/not-exists", wantStatus: http.StatusNotFound, contentType: "application/json"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user