diff --git a/cloudprint-backend/cmd/server/main.go b/cloudprint-backend/cmd/server/main.go index a0a0dc4..946db0b 100644 --- a/cloudprint-backend/cmd/server/main.go +++ b/cloudprint-backend/cmd/server/main.go @@ -1,19 +1,20 @@ package main import ( + "context" + "encoding/json" "flag" "fmt" "log" "net" + "net/http" "os" "os/signal" "syscall" "github.com/cloudprint/cloudprint-backend/config" - "github.com/cloudprint/cloudprint-backend/internal/grpc" "github.com/cloudprint/cloudprint-backend/internal/grpc/interceptor" - "github.com/cloudprint/cloudprint-backend/internal/grpc/service" - "github.com/cloudprint/cloudprint-backend/internal/model" + grpcService "github.com/cloudprint/cloudprint-backend/internal/grpc/service" "github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/cloudprint/cloudprint-backend/internal/service" "github.com/cloudprint/cloudprint-backend/pkg/jwt" @@ -22,7 +23,10 @@ import ( pb "github.com/cloudprint/cloudprint-backend/proto/generated" "go.uber.org/zap" "google.golang.org/grpc" - "gorm.io/driver/mysql" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) @@ -47,23 +51,6 @@ func main() { log.Fatalf("Failed to initialize database: %v", err) } - // Auto migrate - if err := db.AutoMigrate( - &model.User{}, - &model.Admin{}, - &model.Printer{}, - &model.Order{}, - &model.OrderItem{}, - &model.File{}, - &model.LibraryCategory{}, - &model.LibraryFile{}, - &model.PrintJob{}, - &model.PriceList{}, - &model.Payment{}, - ); err != nil { - log.Fatalf("Failed to migrate database: %v", err) - } - // Initialize repositories userRepo := repository.NewUserRepository(db) adminRepo := repository.NewAdminRepository(db) @@ -105,14 +92,17 @@ func main() { ) // Register gRPC services - pb.RegisterAuthServiceServer(grpcServer, service.NewAuthServiceServer(authService)) - pb.RegisterFileServiceServer(grpcServer, service.NewFileServiceServer(fileService)) - pb.RegisterOrderServiceServer(grpcServer, service.NewOrderServiceServer(orderService)) - pb.RegisterPrintServiceServer(grpcServer, service.NewPrintServiceServer(printService)) - pb.RegisterLibraryServiceServer(grpcServer, service.NewLibraryServiceServer(libraryRepo, fileRepo)) - pb.RegisterPriceServiceServer(grpcServer, service.NewPriceServiceServer(priceRepo)) - pb.RegisterPayServiceServer(grpcServer, service.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo)) - pb.RegisterAdminServiceServer(grpcServer, service.NewAdminServiceServer(authService, orderRepo, printerRepo)) + pb.RegisterAuthServiceServer(grpcServer, grpcService.NewAuthServiceServer(authService)) + pb.RegisterFileServiceServer(grpcServer, grpcService.NewFileServiceServer(fileService)) + pb.RegisterOrderServiceServer(grpcServer, grpcService.NewOrderServiceServer(orderService)) + pb.RegisterPrintServiceServer(grpcServer, grpcService.NewPrintServiceServer(printService)) + pb.RegisterLibraryServiceServer(grpcServer, grpcService.NewLibraryServiceServer(libraryRepo, fileRepo)) + pb.RegisterPriceServiceServer(grpcServer, grpcService.NewPriceServiceServer(priceRepo)) + pb.RegisterPayServiceServer(grpcServer, grpcService.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo)) + pb.RegisterAdminServiceServer(grpcServer, grpcService.NewAdminServiceServer(authService, orderRepo, printerRepo)) + + // Initialize HTTP gateway + gateway := newHTTPGateway(grpcServer, jwtManager) // Start server addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) @@ -125,14 +115,29 @@ func main() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + // HTTP server for gateway + httpServer := &http.Server{ + Handler: gateway, + } + + // Start HTTP server in goroutine go func() { - <-sigCh - grpcServer.GracefulStop() + zapLogger.Info("Starting HTTP gateway", zap.String("address", addr)) + if err := httpServer.Serve(lis); err != nil && err != http.ErrServerClosed { + log.Fatalf("Failed to serve HTTP: %v", err) + } }() - zapLogger.Info("Starting gRPC server", zap.String("address", addr)) - if err := grpcServer.Serve(lis); err != nil { - log.Fatalf("Failed to serve: %v", err) + // Wait for shutdown signal + <-sigCh + zapLogger.Info("Shutting down servers...") + + // Graceful stop gRPC server + grpcServer.GracefulStop() + + // Shutdown HTTP server + if err := httpServer.Close(); err != nil { + zapLogger.Error("Failed to close HTTP server", zap.Error(err)) } } @@ -144,7 +149,7 @@ func initDB(cfg *config.Config) (*gorm.DB, error) { }, ) - db, err := gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{ + db, err := gorm.Open(postgres.Open(cfg.Database.DSN()), &gorm.Config{ Logger: gormLogger, }) if err != nil { @@ -161,3 +166,301 @@ func initDB(cfg *config.Config) (*gorm.DB, error) { return db, nil } + +// HTTP Gateway + +type httpGateway struct { + grpcServer *grpc.Server + jwtManager *jwt.JWTManager + mux *http.ServeMux +} + +func newHTTPGateway(grpcServer *grpc.Server, jwtManager *jwt.JWTManager) *httpGateway { + g := &httpGateway{ + grpcServer: grpcServer, + jwtManager: jwtManager, + mux: http.NewServeMux(), + } + g.registerHandlers() + return g +} + +func (g *httpGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { + g.mux.ServeHTTP(w, r) +} + +func (g *httpGateway) registerHandlers() { + // Health check + g.mux.HandleFunc("/health", g.healthHandler) + + // Auth endpoints + g.mux.HandleFunc("/api/v1/auth/login", g.handleLogin) + g.mux.HandleFunc("/api/v1/auth/refresh", g.handleRefreshToken) + g.mux.HandleFunc("/api/v1/auth/userinfo", g.handleGetUserInfo) + + // File endpoints + g.mux.HandleFunc("/api/v1/file/upload", g.handleFileUpload) + g.mux.HandleFunc("/api/v1/file/", g.handleFileOps) + + // Order endpoints + g.mux.HandleFunc("/api/v1/order/create", g.handleCreateOrder) + g.mux.HandleFunc("/api/v1/order/", g.handleOrderOps) + + // Print endpoints + g.mux.HandleFunc("/api/v1/print/printers", g.handleGetPrinters) + g.mux.HandleFunc("/api/v1/print/submit", g.handleSubmitPrintJob) + + // Library endpoints + g.mux.HandleFunc("/api/v1/library/categories", g.handleGetCategories) + g.mux.HandleFunc("/api/v1/library/files", g.handleGetLibraryFiles) + + // Price endpoints + g.mux.HandleFunc("/api/v1/price/list", g.handleGetPriceList) + + // Pay endpoints + g.mux.HandleFunc("/api/v1/pay/wxpay", g.handleCreateWxPayOrder) + g.mux.HandleFunc("/api/v1/pay/callback", g.handlePayCallback) + g.mux.HandleFunc("/api/v1/pay/balance", g.handlePayByBalance) + + // Admin endpoints + g.mux.HandleFunc("/api/v1/admin/login", g.handleAdminLogin) + g.mux.HandleFunc("/api/v1/admin/stats", g.handleGetStatistics) +} + +func (g *httpGateway) healthHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) +} + +func (g *httpGateway) writeJSON(w http.ResponseWriter, statusCode int, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(data) +} + +func (g *httpGateway) handleError(w http.ResponseWriter, err error) { + st, ok := status.FromError(err) + if !ok { + g.writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + code := http.StatusInternalServerError + switch st.Code() { + case codes.OK: + code = http.StatusOK + case codes.InvalidArgument: + code = http.StatusBadRequest + case codes.NotFound: + code = http.StatusNotFound + case codes.Unauthenticated: + code = http.StatusUnauthorized + case codes.PermissionDenied: + code = http.StatusForbidden + } + g.writeJSON(w, code, map[string]string{"error": st.Message()}) +} + +func (g *httpGateway) parseToken(r *http.Request) (context.Context, error) { + token := r.Header.Get("Authorization") + if token == "" { + return nil, status.Error(codes.Unauthenticated, "missing token") + } + if len(token) > 7 && token[:7] == "Bearer " { + token = token[7:] + } + claims, err := g.jwtManager.ValidateToken(token) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "invalid token") + } + return metadata.NewOutgoingContext(r.Context(), metadata.Pairs( + "user_id", fmt.Sprintf("%d", claims.UserID), + "user_type", claims.UserType, + )), nil +} + +func (g *httpGateway) handleLogin(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + var req pb.LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + // gRPC call would go here - returning placeholder for now + g.writeJSON(w, http.StatusOK, map[string]string{"message": "Use gRPC directly for login"}) +} + +func (g *httpGateway) handleRefreshToken(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleGetUserInfo(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleFileUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleFileOps(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + switch r.Method { + case http.MethodGet: + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) + case http.MethodDelete: + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func (g *httpGateway) handleCreateOrder(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleOrderOps(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + switch r.Method { + case http.MethodGet: + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func (g *httpGateway) handleGetPrinters(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleSubmitPrintJob(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleGetCategories(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleGetLibraryFiles(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleGetPriceList(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleCreateWxPayOrder(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handlePayCallback(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handlePayByBalance(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleAdminLogin(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} + +func (g *httpGateway) handleGetStatistics(w http.ResponseWriter, r *http.Request) { + _, err := g.parseToken(r) + if err != nil { + g.handleError(w, err) + return + } + g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) +} diff --git a/cloudprint-backend/config/config.go b/cloudprint-backend/config/config.go index c167f90..776b16e 100644 --- a/cloudprint-backend/config/config.go +++ b/cloudprint-backend/config/config.go @@ -32,8 +32,8 @@ type DatabaseConfig struct { } func (d *DatabaseConfig) DSN() string { - return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", - d.Username, d.Password, d.Host, d.Port, d.Database) + return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai", + d.Host, d.Port, d.Username, d.Password, d.Database) } type RedisConfig struct { diff --git a/cloudprint-backend/go.mod b/cloudprint-backend/go.mod index 1a044c8..82df347 100644 --- a/cloudprint-backend/go.mod +++ b/cloudprint-backend/go.mod @@ -1,6 +1,6 @@ module github.com/cloudprint/cloudprint-backend -go 1.24.0 +go 1.25.0 require ( github.com/golang-jwt/jwt/v5 v5.2.0 @@ -8,18 +8,21 @@ require ( github.com/iGoogle-ink/gopay v1.5.14 github.com/spf13/viper v1.18.2 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.46.0 - google.golang.org/grpc v1.79.3 + golang.org/x/crypto v0.49.0 + google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 - gorm.io/driver/mysql v1.5.2 - gorm.io/gorm v1.25.5 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.10 ) require ( github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/iGoogle-ink/gotil v1.0.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -31,13 +34,16 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cloudprint-backend/go.sum b/cloudprint-backend/go.sum index 6259dfc..99230f1 100644 --- a/cloudprint-backend/go.sum +++ b/cloudprint-backend/go.sum @@ -56,8 +56,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-redis/redis/v8 v8.0.0-beta.5/go.mod h1:Mm9EH/5UMRx680UIryN6rd5XFn/L7zORPqLV+1D5thQ= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -95,6 +93,14 @@ github.com/iGoogle-ink/gopay v1.5.14 h1:vcwdqNeccx8FWrnqKTeZZSK5FcAHGUD07OFHwhMh github.com/iGoogle-ink/gopay v1.5.14/go.mod h1:JuI4rWXHSTEgFHD5C5xfpy3Do3b0pIcu/wdsRyw/Ds4= github.com/iGoogle-ink/gotil v1.0.2 h1:z58bvmhq31twxecMkyWU5Mh8H7T9SGPOTvUuAw9vB7M= github.com/iGoogle-ink/gotil v1.0.2/go.mod h1:HOeCIcJkJLAsQ+cAPdozZS3huVDT4YUKN/F7tmvvrbM= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -176,10 +182,12 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -190,16 +198,16 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= @@ -210,8 +218,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -233,8 +241,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -242,6 +250,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -252,13 +262,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -268,8 +278,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -279,23 +289,24 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -307,11 +318,10 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/cloudprint-backend/internal/grpc/service/file_service.go b/cloudprint-backend/internal/grpc/service/file_service.go index af3eaf3..4a4e82b 100644 --- a/cloudprint-backend/internal/grpc/service/file_service.go +++ b/cloudprint-backend/internal/grpc/service/file_service.go @@ -21,7 +21,7 @@ func NewFileServiceServer(fileService *service.FileService) *FileServiceServer { } } -func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.UploadResponse, error) { +func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) error { var fileName, fileType, uploaderType string var uploaderID int32 var chunks [][]byte @@ -32,7 +32,7 @@ func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.Uplo break } if err != nil { - return nil, status.Errorf(codes.Internal, "receive chunk failed: %v", err) + return status.Errorf(codes.Internal, "receive chunk failed: %v", err) } if fileName == "" { @@ -53,14 +53,14 @@ func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.Uplo file, err := s.fileService.Upload(stream.Context(), fileName, fileType, uploaderType, uploaderID, data) if err != nil { - return nil, status.Errorf(codes.Internal, "upload failed: %v", err) + return status.Errorf(codes.Internal, "upload failed: %v", err) } - return &pb.UploadResponse{ + return stream.SendAndClose(&pb.UploadResponse{ FileId: file.ID, FilePath: file.FilePath, FileUrl: file.FilePath, - }, nil + }) } func (s *FileServiceServer) GetFile(req *pb.GetFileRequest, stream pb.FileService_GetFileServer) error { diff --git a/cloudprint-backend/internal/repository/order_repository.go b/cloudprint-backend/internal/repository/order_repository.go index d3b8726..de06b30 100644 --- a/cloudprint-backend/internal/repository/order_repository.go +++ b/cloudprint-backend/internal/repository/order_repository.go @@ -116,14 +116,14 @@ func (r *OrderRepository) GetStatistics(ctx context.Context) (totalUsers, totalO // 今日订单数 today := time.Now().Format("2006-01-02") if err = r.db.WithContext(ctx).Model(&model.Order{}). - Where("DATE(created_at) = ? AND status = ?", today, model.OrderStatusPaid). + Where("created_at::date = ? AND status = ?", today, model.OrderStatusPaid). Count(&todayOrders).Error; err != nil { return 0, 0, 0, 0, 0, fmt.Errorf("count today orders failed: %w", err) } // 今日收入 if err = r.db.WithContext(ctx).Model(&model.Order{}). Select("COALESCE(SUM(total_amount), 0)"). - Where("DATE(created_at) = ? AND status = ?", today, model.OrderStatusPaid). + Where("created_at::date = ? AND status = ?", today, model.OrderStatusPaid). Scan(&todayRevenue).Error; err != nil { return 0, 0, 0, 0, 0, fmt.Errorf("sum today revenue failed: %w", err) } diff --git a/cloudprint-backend/scripts/db.sql b/cloudprint-backend/scripts/db.sql index 13c24db..ed0d7ad 100644 --- a/cloudprint-backend/scripts/db.sql +++ b/cloudprint-backend/scripts/db.sql @@ -1,182 +1,186 @@ --- 云印享 - 付费打印服务系统数据库初始化脚本 +-- 云印享 - 付费打印服务系统数据库初始化脚本 (PostgreSQL) -- 数据库: cloudprint -CREATE DATABASE IF NOT EXISTS `cloudprint` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -USE `cloudprint`; +-- 创建数据库(通常需要超级用户权限,也可以手动创建) +-- CREATE DATABASE cloudprint WITH ENCODING = 'UTF8'; +-- \c cloudprint -- 用户表 -CREATE TABLE IF NOT EXISTS `user` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `openid` VARCHAR(64) UNIQUE NOT NULL COMMENT '微信OpenID', - `nickname` VARCHAR(128) DEFAULT '' COMMENT '昵称', - `avatar` VARCHAR(512) DEFAULT '' COMMENT '头像URL', - `balance` DECIMAL(10,2) DEFAULT 0.00 COMMENT '余额', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX `idx_openid` (`openid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +CREATE TABLE IF NOT EXISTS "user" ( + "id" SERIAL PRIMARY KEY, + "openid" VARCHAR(64) UNIQUE NOT NULL, + "nickname" VARCHAR(128) DEFAULT '', + "avatar" VARCHAR(512) DEFAULT '', + "balance" DECIMAL(10,2) DEFAULT 0.00, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_user_openid" ON "user" ("openid"); -- 管理员表 -CREATE TABLE IF NOT EXISTS `admin` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `username` VARCHAR(64) UNIQUE NOT NULL COMMENT '用户名', - `password_hash` VARCHAR(256) NOT NULL COMMENT '密码哈希', - `nickname` VARCHAR(128) DEFAULT '' COMMENT '昵称', - `role` TINYINT DEFAULT 1 COMMENT '1:普通管理员 2:超级管理员', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_username` (`username`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; +CREATE TABLE IF NOT EXISTS "admin" ( + "id" SERIAL PRIMARY KEY, + "username" VARCHAR(64) UNIQUE NOT NULL, + "password_hash" VARCHAR(256) NOT NULL, + "nickname" VARCHAR(128) DEFAULT '', + "role" SMALLINT DEFAULT 1, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_admin_username" ON "admin" ("username"); -- 打印机表 -CREATE TABLE IF NOT EXISTS `printer` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `name` VARCHAR(128) UNIQUE NOT NULL COMMENT '系统打印机名', - `display_name` VARCHAR(128) NOT NULL COMMENT '显示名称', - `api_key` VARCHAR(64) UNIQUE NOT NULL COMMENT '客户端认证Key', - `is_active` TINYINT DEFAULT 1 COMMENT '是否启用', - `status` TINYINT DEFAULT 0 COMMENT '0:空闲 1:打印中 2:离线', - `last_heartbeat` DATETIME DEFAULT NULL COMMENT '最后心跳时间', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_name` (`name`), - INDEX `idx_api_key` (`api_key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印机表'; - --- 订单表 -CREATE TABLE IF NOT EXISTS `order` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `order_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '订单号', - `user_id` INT NOT NULL COMMENT '用户ID', - `status` TINYINT DEFAULT 0 COMMENT '0:待支付 1:已支付 2:打印中 3:已完成 4:已取消', - `total_amount` DECIMAL(10,2) NOT NULL COMMENT '总金额', - `remark` VARCHAR(512) DEFAULT '' COMMENT '备注', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - `paid_at` DATETIME DEFAULT NULL COMMENT '支付时间', - INDEX `idx_order_no` (`order_no`), - INDEX `idx_user_id` (`user_id`), - INDEX `idx_status` (`status`), - INDEX `idx_created_at` (`created_at`), - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; - --- 订单明细表 -CREATE TABLE IF NOT EXISTS `order_item` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `order_id` INT NOT NULL COMMENT '订单ID', - `file_id` INT NOT NULL COMMENT '文件ID', - `printer_id` INT DEFAULT NULL COMMENT '打印机ID', - `copies` INT DEFAULT 1 COMMENT '份数', - `color` TINYINT DEFAULT 0 COMMENT '是否彩色 0:黑白 1:彩色', - `duplex` TINYINT DEFAULT 0 COMMENT '是否双面 0:单面 1:双面', - `page_range` VARCHAR(64) DEFAULT 'all' COMMENT '页面范围', - `price` DECIMAL(10,2) NOT NULL COMMENT '价格', - INDEX `idx_order_id` (`order_id`), - INDEX `idx_file_id` (`file_id`), - FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE, - FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表'; +CREATE TABLE IF NOT EXISTS "printer" ( + "id" SERIAL PRIMARY KEY, + "name" VARCHAR(128) UNIQUE NOT NULL, + "display_name" VARCHAR(128) NOT NULL, + "api_key" VARCHAR(64) UNIQUE NOT NULL, + "is_active" BOOLEAN DEFAULT TRUE, + "status" SMALLINT DEFAULT 0, + "last_heartbeat" TIMESTAMP DEFAULT NULL, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_printer_name" ON "printer" ("name"); +CREATE INDEX IF NOT EXISTS "idx_printer_api_key" ON "printer" ("api_key"); -- 文件表 -CREATE TABLE IF NOT EXISTS `file` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `file_name` VARCHAR(256) NOT NULL COMMENT '文件名', - `file_path` VARCHAR(512) NOT NULL COMMENT '文件路径', - `file_type` VARCHAR(32) NOT NULL COMMENT '文件类型', - `file_size` BIGINT NOT NULL COMMENT '文件大小(字节)', - `uploader_type` VARCHAR(16) NOT NULL COMMENT '上传者类型: user/admin/system', - `uploader_id` INT NOT NULL COMMENT '上传者ID', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_uploader` (`uploader_type`, `uploader_id`), - INDEX `idx_file_type` (`file_type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; +CREATE TABLE IF NOT EXISTS "file" ( + "id" SERIAL PRIMARY KEY, + "file_name" VARCHAR(256) NOT NULL, + "file_path" VARCHAR(512) NOT NULL, + "file_type" VARCHAR(32) NOT NULL, + "file_size" BIGINT NOT NULL, + "uploader_type" VARCHAR(16) NOT NULL, + "uploader_id" INTEGER NOT NULL, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_file_uploader" ON "file" ("uploader_type", "uploader_id"); +CREATE INDEX IF NOT EXISTS "idx_file_type" ON "file" ("file_type"); + +-- 订单表 +CREATE TABLE IF NOT EXISTS "order" ( + "id" SERIAL PRIMARY KEY, + "order_no" VARCHAR(32) UNIQUE NOT NULL, + "user_id" INTEGER NOT NULL, + "status" SMALLINT DEFAULT 0, + "total_amount" DECIMAL(10,2) NOT NULL, + "remark" VARCHAR(512) DEFAULT '', + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "paid_at" TIMESTAMP DEFAULT NULL, + FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT +); +CREATE INDEX IF NOT EXISTS "idx_order_order_no" ON "order" ("order_no"); +CREATE INDEX IF NOT EXISTS "idx_order_user_id" ON "order" ("user_id"); +CREATE INDEX IF NOT EXISTS "idx_order_status" ON "order" ("status"); +CREATE INDEX IF NOT EXISTS "idx_order_created_at" ON "order" ("created_at"); + +-- 订单明细表 +CREATE TABLE IF NOT EXISTS "order_item" ( + "id" SERIAL PRIMARY KEY, + "order_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + "printer_id" INTEGER DEFAULT NULL, + "copies" INTEGER DEFAULT 1, + "color" BOOLEAN DEFAULT FALSE, + "duplex" BOOLEAN DEFAULT FALSE, + "page_range" VARCHAR(64) DEFAULT 'all', + "price" DECIMAL(10,2) NOT NULL, + FOREIGN KEY ("order_id") REFERENCES "order"("id") ON DELETE CASCADE, + FOREIGN KEY ("file_id") REFERENCES "file"("id") ON DELETE RESTRICT +); +CREATE INDEX IF NOT EXISTS "idx_order_item_order_id" ON "order_item" ("order_id"); +CREATE INDEX IF NOT EXISTS "idx_order_item_file_id" ON "order_item" ("file_id"); -- 文库分类表 -CREATE TABLE IF NOT EXISTS `library_category` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `name` VARCHAR(128) NOT NULL COMMENT '分类名称', - `icon` VARCHAR(128) DEFAULT '' COMMENT '图标', - `sort_order` INT DEFAULT 0 COMMENT '排序', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文库分类表'; +CREATE TABLE IF NOT EXISTS "library_category" ( + "id" SERIAL PRIMARY KEY, + "name" VARCHAR(128) NOT NULL, + "icon" VARCHAR(128) DEFAULT '', + "sort_order" INTEGER DEFAULT 0, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); -- 文库文件表 -CREATE TABLE IF NOT EXISTS `library_file` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `category_id` INT NOT NULL COMMENT '分类ID', - `file_id` INT NOT NULL COMMENT '文件ID', - `title` VARCHAR(256) NOT NULL COMMENT '标题', - `description` VARCHAR(512) DEFAULT '' COMMENT '描述', - `page_count` INT DEFAULT 0 COMMENT '页数', - `download_count` INT DEFAULT 0 COMMENT '下载次数', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_category_id` (`category_id`), - INDEX `idx_file_id` (`file_id`), - FOREIGN KEY (`category_id`) REFERENCES `library_category`(`id`) ON DELETE CASCADE, - FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文库文件表'; +CREATE TABLE IF NOT EXISTS "library_file" ( + "id" SERIAL PRIMARY KEY, + "category_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + "title" VARCHAR(256) NOT NULL, + "description" VARCHAR(512) DEFAULT '', + "page_count" INTEGER DEFAULT 0, + "download_count" INTEGER DEFAULT 0, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("category_id") REFERENCES "library_category"("id") ON DELETE CASCADE, + FOREIGN KEY ("file_id") REFERENCES "file"("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_library_file_category_id" ON "library_file" ("category_id"); +CREATE INDEX IF NOT EXISTS "idx_library_file_file_id" ON "library_file" ("file_id"); -- 打印任务表 -CREATE TABLE IF NOT EXISTS `print_job` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `job_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '任务号', - `printer_id` INT NOT NULL COMMENT '打印机ID', - `order_item_id` INT NOT NULL COMMENT '订单明细ID', - `status` VARCHAR(16) DEFAULT 'pending' COMMENT 'pending/printing/completed/failed/cancelled', - `progress` INT DEFAULT 0 COMMENT '进度(0-100)', - `error_msg` VARCHAR(512) DEFAULT '' COMMENT '错误信息', - `started_at` DATETIME DEFAULT NULL COMMENT '开始时间', - `completed_at` DATETIME DEFAULT NULL COMMENT '完成时间', - INDEX `idx_job_no` (`job_no`), - INDEX `idx_printer_id` (`printer_id`), - INDEX `idx_status` (`status`), - FOREIGN KEY (`printer_id`) REFERENCES `printer`(`id`) ON DELETE RESTRICT, - FOREIGN KEY (`order_item_id`) REFERENCES `order_item`(`id`) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印任务表'; +CREATE TABLE IF NOT EXISTS "print_job" ( + "id" SERIAL PRIMARY KEY, + "job_no" VARCHAR(32) UNIQUE NOT NULL, + "printer_id" INTEGER NOT NULL, + "order_item_id" INTEGER NOT NULL, + "status" VARCHAR(16) DEFAULT 'pending', + "progress" INTEGER DEFAULT 0, + "error_msg" VARCHAR(512) DEFAULT '', + "started_at" TIMESTAMP DEFAULT NULL, + "completed_at" TIMESTAMP DEFAULT NULL, + FOREIGN KEY ("printer_id") REFERENCES "printer"("id") ON DELETE RESTRICT, + FOREIGN KEY ("order_item_id") REFERENCES "order_item"("id") ON DELETE RESTRICT +); +CREATE INDEX IF NOT EXISTS "idx_print_job_job_no" ON "print_job" ("job_no"); +CREATE INDEX IF NOT EXISTS "idx_print_job_printer_id" ON "print_job" ("printer_id"); +CREATE INDEX IF NOT EXISTS "idx_print_job_status" ON "print_job" ("status"); -- 价目表 -CREATE TABLE IF NOT EXISTS `price_list` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `name` VARCHAR(128) NOT NULL COMMENT '项目名称', - `type` VARCHAR(32) NOT NULL COMMENT '纸张类型: A4/A3/照片纸', - `price` DECIMAL(10,2) NOT NULL COMMENT '单价', - `unit` VARCHAR(16) NOT NULL COMMENT '单位: 页/张/份', - `color_enabled` TINYINT DEFAULT 1 COMMENT '是否支持彩色', - `is_active` TINYINT DEFAULT 1 COMMENT '是否启用', - `sort_order` INT DEFAULT 0 COMMENT '排序', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_type` (`type`), - INDEX `idx_is_active` (`is_active`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='价目表'; +CREATE TABLE IF NOT EXISTS "price_list" ( + "id" SERIAL PRIMARY KEY, + "name" VARCHAR(128) NOT NULL, + "type" VARCHAR(32) NOT NULL, + "price" DECIMAL(10,2) NOT NULL, + "unit" VARCHAR(16) NOT NULL, + "color_enabled" BOOLEAN DEFAULT TRUE, + "is_active" BOOLEAN DEFAULT TRUE, + "sort_order" INTEGER DEFAULT 0, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_price_list_type" ON "price_list" ("type"); +CREATE INDEX IF NOT EXISTS "idx_price_list_is_active" ON "price_list" ("is_active"); -- 支付记录表 -CREATE TABLE IF NOT EXISTS `payment` ( - `id` INT PRIMARY KEY AUTO_INCREMENT, - `order_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '订单号', - `pay_type` VARCHAR(16) DEFAULT 'wechat' COMMENT '支付类型: wechat/balance', - `pay_status` TINYINT DEFAULT 0 COMMENT '0:待支付 1:支付中 2:成功 3:失败', - `transaction_id` VARCHAR(64) DEFAULT '' COMMENT '微信交易号', - `paid_at` DATETIME DEFAULT NULL COMMENT '支付时间', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_order_no` (`order_no`), - INDEX `idx_pay_status` (`pay_status`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录表'; +CREATE TABLE IF NOT EXISTS "payment" ( + "id" SERIAL PRIMARY KEY, + "order_no" VARCHAR(32) UNIQUE NOT NULL, + "pay_type" VARCHAR(16) DEFAULT 'wechat', + "pay_status" SMALLINT DEFAULT 0, + "transaction_id" VARCHAR(64) DEFAULT '', + "paid_at" TIMESTAMP DEFAULT NULL, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_payment_order_no" ON "payment" ("order_no"); +CREATE INDEX IF NOT EXISTS "idx_payment_pay_status" ON "payment" ("pay_status"); -- 初始化管理员账号 (密码: admin123) -- 密码哈希使用 bcrypt 生成 -INSERT INTO `admin` (`username`, `password_hash`, `nickname`, `role`) VALUES -('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMy.MqrqJlmZ2j3.N9qo8uLOickgx2ZMRZoG', '系统管理员', 2); +INSERT INTO "admin" ("username", "password_hash", "nickname", "role") VALUES +('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMy.MqrqJlmZ2j3.N9qo8uLOickgx2ZMRZoG', '系统管理员', 2) +ON CONFLICT ("username") DO NOTHING; -- 初始化默认价目表 -INSERT INTO `price_list` (`name`, `type`, `price`, `unit`, `color_enabled`, `is_active`, `sort_order`) VALUES -('A4黑白打印', 'A4', 0.10, '页', 0, 1, 1), -('A4彩色打印', 'A4', 0.30, '页', 1, 1, 2), -('A3黑白打印', 'A3', 0.20, '页', 0, 1, 3), -('A3彩色打印', 'A3', 0.60, '页', 1, 1, 4), -('照片打印', '照片纸', 2.00, '张', 1, 1, 5); +INSERT INTO "price_list" ("name", "type", "price", "unit", "color_enabled", "is_active", "sort_order") VALUES +('A4黑白打印', 'A4', 0.10, '页', FALSE, TRUE, 1), +('A4彩色打印', 'A4', 0.30, '页', TRUE, TRUE, 2), +('A3黑白打印', 'A3', 0.20, '页', FALSE, TRUE, 3), +('A3彩色打印', 'A3', 0.60, '页', TRUE, TRUE, 4), +('照片打印', '照片纸', 2.00, '张', TRUE, TRUE, 5) +ON CONFLICT DO NOTHING; -- 初始化默认文库分类 -INSERT INTO `library_category` (`name`, `icon`, `sort_order`) VALUES +INSERT INTO "library_category" ("name", "icon", "sort_order") VALUES ('学习资料', 'book', 1), ('考试真题', 'exam', 2), ('简历模板', 'resume', 3), ('合同协议', 'contract', 4), -('其他资料', 'other', 5); +('其他资料', 'other', 5) +ON CONFLICT DO NOTHING;