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:
t
2026-08-20 21:46:55 +08:00
parent 954a3fe8d9
commit 3d47227fbd
612 changed files with 515 additions and 97334 deletions
+20 -11
View File
@@ -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 == "" {
+39
View File
@@ -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()
+8 -14
View File
@@ -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
+2 -2
View File
@@ -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 {