feat(backend): implement task creation and admin web

This commit is contained in:
QiuSW
2026-07-26 14:03:32 +08:00
parent 2b265c92fc
commit c5d3b215ff
58 changed files with 8773 additions and 83 deletions
+39 -4
View File
@@ -10,11 +10,13 @@ import (
)
const (
HTTPAddressEnvironment = "CMROUBAO_HTTP_ADDR"
DatabasePathEnvironment = "CMROUBAO_DATABASE_PATH"
HTTPAddressEnvironment = "CMROUBAO_HTTP_ADDR"
DatabasePathEnvironment = "CMROUBAO_DATABASE_PATH"
AssetDirectoryEnvironment = "CMROUBAO_ASSET_DIR"
defaultHTTPAddress = "127.0.0.1:8080"
defaultDatabasePath = "var/cmroubao.db"
defaultHTTPAddress = "127.0.0.1:8080"
defaultDatabasePath = "var/cmroubao.db"
defaultAssetDirectory = "var/assets"
)
type LookupEnvironment func(string) (string, bool)
@@ -22,6 +24,7 @@ type LookupEnvironment func(string) (string, bool)
type Config struct {
HTTPAddress string
DatabasePath string
AssetDirectory string
ReadHeaderTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
@@ -53,9 +56,23 @@ func Load(lookup LookupEnvironment) (Config, error) {
return Config{}, err
}
assetDirectory, err := environmentValue(
lookup,
AssetDirectoryEnvironment,
defaultAssetDirectory,
)
if err != nil {
return Config{}, err
}
assetDirectory, err = validatedAssetDirectory(assetDirectory)
if err != nil {
return Config{}, err
}
return Config{
HTTPAddress: httpAddress,
DatabasePath: filepath.Clean(databasePath),
AssetDirectory: assetDirectory,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
@@ -65,6 +82,24 @@ func Load(lookup LookupEnvironment) (Config, error) {
}, nil
}
func validatedAssetDirectory(path string) (string, error) {
if strings.ContainsRune(path, '\x00') {
return "", errors.New(
AssetDirectoryEnvironment + " contains an invalid character",
)
}
cleanPath := filepath.Clean(path)
volumeRoot := filepath.VolumeName(cleanPath) + string(filepath.Separator)
if cleanPath == "." ||
cleanPath == string(filepath.Separator) ||
cleanPath == volumeRoot {
return "", errors.New(
AssetDirectoryEnvironment + " must be a dedicated directory",
)
}
return cleanPath, nil
}
func LoadDatabasePath(lookup LookupEnvironment) (string, error) {
databasePath, err := environmentValue(
lookup,
+21 -2
View File
@@ -18,6 +18,9 @@ func TestLoadUsesSafeDefaults(t *testing.T) {
if cfg.DatabasePath != filepath.FromSlash("var/cmroubao.db") {
t.Fatalf("DatabasePath = %q", cfg.DatabasePath)
}
if cfg.AssetDirectory != filepath.FromSlash("var/assets") {
t.Fatalf("AssetDirectory = %q", cfg.AssetDirectory)
}
if cfg.ReadHeaderTimeout <= 0 ||
cfg.ReadTimeout <= 0 ||
cfg.WriteTimeout <= 0 ||
@@ -33,8 +36,9 @@ func TestLoadUsesSafeDefaults(t *testing.T) {
func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
values := map[string]string{
HTTPAddressEnvironment: "192.0.2.10:9090",
DatabasePathEnvironment: "tmp/test.db",
HTTPAddressEnvironment: "192.0.2.10:9090",
DatabasePathEnvironment: "tmp/test.db",
AssetDirectoryEnvironment: "tmp/assets",
}
cfg, err := Load(mapEnvironment(values))
@@ -48,6 +52,9 @@ func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
if cfg.DatabasePath != filepath.Clean(values[DatabasePathEnvironment]) {
t.Fatalf("DatabasePath = %q", cfg.DatabasePath)
}
if cfg.AssetDirectory != filepath.Clean(values[AssetDirectoryEnvironment]) {
t.Fatalf("AssetDirectory = %q", cfg.AssetDirectory)
}
}
func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
@@ -97,6 +104,18 @@ func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
DatabasePathEnvironment: "var/database.txt",
},
},
{
name: "asset root directory",
values: map[string]string{
AssetDirectoryEnvironment: string(filepath.Separator),
},
},
{
name: "asset current directory",
values: map[string]string{
AssetDirectoryEnvironment: ".",
},
},
}
for _, test := range tests {