first commit

This commit is contained in:
2026-03-28 15:43:18 +08:00
commit e5611df24e
54 changed files with 11065 additions and 0 deletions

Binary file not shown.

148
backend/cmd/server/main.go Normal file
View File

@@ -0,0 +1,148 @@
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"filefast/backend/internal/config"
"filefast/backend/internal/handler"
"filefast/backend/internal/scheduler"
"filefast/backend/internal/service"
"filefast/backend/internal/storage"
"filefast/backend/internal/store"
"filefast/backend/internal/ws"
)
func main() {
cfg := config.Load()
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel}))
memStore := store.NewMemoryStore(cfg.Runtime)
var redisBackplane *storage.RedisClient
storageConnected := false
redisConnected := false
sqliteClient, err := storage.NewSQLiteClient(cfg.SQLite)
if err != nil {
logger.Error("failed to initialize sqlite client", "path", cfg.SQLite.Path, "error", err)
os.Exit(1)
}
defer sqliteClient.Close()
storagePingCtx, storagePingCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer storagePingCancel()
if err := sqliteClient.Ping(storagePingCtx); err != nil {
logger.Warn("sqlite connection failed", "path", cfg.SQLite.Path, "error", err)
} else {
storageConnected = true
logger.Info("sqlite connected", "path", cfg.SQLite.Path)
bootstrapCtx, bootstrapCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer bootstrapCancel()
if err := sqliteClient.EnsureSchema(bootstrapCtx); err != nil {
logger.Warn("failed to ensure sqlite schema", "error", err)
} else {
if err := sqliteClient.EnsureAdminUser(bootstrapCtx, cfg.Admin.Username, cfg.Admin.Password); err != nil {
logger.Warn("failed to ensure admin user", "username", cfg.Admin.Username, "error", err)
}
if err := sqliteClient.ResetOnlineDevices(bootstrapCtx); err != nil {
logger.Warn("failed to reset device online states", "error", err)
}
snapshot, err := sqliteClient.LoadSnapshot(bootstrapCtx, cfg.Runtime)
if err != nil {
logger.Warn("failed to restore state from sqlite", "error", err)
} else {
memStore.LoadSnapshot(snapshot)
logger.Info(
"restored state from sqlite",
"devices", len(snapshot.Devices),
"rooms", len(snapshot.Rooms),
"transfers", len(snapshot.Transfers),
"fallback_objects", len(snapshot.FallbackObjects),
)
}
}
memStore.SetPersistence(sqliteClient, 5*time.Second, func(kind, id string, err error) {
logger.Warn("failed to persist state", "kind", kind, "id", id, "error", err)
})
}
redisClient := storage.NewRedisClient(cfg.Redis)
defer redisClient.Close()
redisPingCtx, redisPingCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer redisPingCancel()
if err := redisClient.Ping(redisPingCtx); err != nil {
logger.Warn("redis connection failed", "addr", cfg.Redis.Addr, "db", cfg.Redis.DB, "error", err)
} else {
redisConnected = true
logger.Info("redis connected", "addr", cfg.Redis.Addr, "db", cfg.Redis.DB)
redisBackplane = redisClient
}
deviceService := service.NewDeviceService(memStore, redisBackplane, redisBackplane)
roomService := service.NewRoomService(memStore, cfg.RoomTTL)
transferService := service.NewTransferService(memStore)
adminService := service.NewAdminService(memStore, cfg.Admin, redisBackplane, sqliteClient)
minioClient, err := storage.NewMinIOClient(cfg.MinIO)
if err != nil {
logger.Error("failed to initialize minio client", "error", err)
}
hub := ws.NewHub(logger, deviceService, redisBackplane)
go hub.Run()
cleanupScheduler := scheduler.NewCleanupScheduler(logger, memStore, minioClient)
cleanupScheduler.Start()
defer cleanupScheduler.Stop()
httpHandler := handler.NewHTTPHandler(handler.Dependencies{
Config: cfg,
Logger: logger,
Store: memStore,
DeviceService: deviceService,
RoomService: roomService,
TransferService: transferService,
AdminService: adminService,
MinIOClient: minioClient,
Hub: hub,
StorageReady: storageConnected,
RedisReady: redisConnected,
})
server := &http.Server{
Addr: cfg.HTTPAddress,
Handler: httpHandler.Router(),
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
logger.Info("server listening", "addr", cfg.HTTPAddress)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("server crashed", "error", err)
os.Exit(1)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Error("server shutdown failed", "error", err)
}
logger.Info("server stopped")
}