328 lines
20 KiB
Go
328 lines
20 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
"github.com/jmoiron/sqlx"
|
|
httpSwagger "github.com/swaggo/http-swagger"
|
|
|
|
"github.com/saveinmed/backend-go/internal/config"
|
|
"github.com/saveinmed/backend-go/internal/domain"
|
|
"github.com/saveinmed/backend-go/internal/http/handler"
|
|
"github.com/saveinmed/backend-go/internal/http/middleware"
|
|
"github.com/saveinmed/backend-go/internal/infrastructure/mapbox"
|
|
"github.com/saveinmed/backend-go/internal/infrastructure/notifications"
|
|
"github.com/saveinmed/backend-go/internal/infrastructure/payments"
|
|
"github.com/saveinmed/backend-go/internal/repository/postgres"
|
|
"github.com/saveinmed/backend-go/internal/usecase"
|
|
)
|
|
|
|
const (
|
|
bootstrapAdminName = "SaveInMed Admin"
|
|
bootstrapAdminUsername = "admin"
|
|
)
|
|
|
|
// Server wires the infrastructure and exposes HTTP handlers.
|
|
type Server struct {
|
|
cfg config.Config
|
|
db *sqlx.DB
|
|
mux *http.ServeMux
|
|
svc *usecase.Service
|
|
}
|
|
|
|
func New(cfg config.Config) (*Server, error) {
|
|
db, err := sqlx.Open("pgx", cfg.DatabaseURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoInstance := postgres.New(db)
|
|
|
|
var paymentGateway domain.PaymentGateway
|
|
switch cfg.PaymentGatewayProvider {
|
|
case "asaas":
|
|
paymentGateway = payments.NewAsaasGateway(cfg.AsaasAPIKey, cfg.AsaasWalletID, cfg.AsaasEnvironment, cfg.MarketplaceCommission)
|
|
case "stripe":
|
|
paymentGateway = payments.NewStripeGateway(cfg.StripeAPIKey, cfg.MarketplaceCommission)
|
|
default:
|
|
paymentGateway = payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MercadoPagoAccessToken, cfg.BackendHost, cfg.MarketplaceCommission)
|
|
}
|
|
|
|
mapboxClient := mapbox.New(cfg.MapboxAccessToken)
|
|
|
|
// Services
|
|
notifySvc := notifications.NewLoggerNotificationService()
|
|
// MarketplaceCommission: 12% total (Split 6% buyer / 6% seller)
|
|
// BuyerFeeRate: 6% (This is the inflation added to search prices)
|
|
svc := usecase.NewService(repoInstance, paymentGateway, mapboxClient, notifySvc, 12.0, 0.06, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
|
|
h := handler.New(svc, 0.06)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
// Root endpoint
|
|
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
response := `{"message":"💊 SaveInMed API is running!","docs":"/docs","health":"/health","version":"1.0.0"}`
|
|
_, _ = w.Write([]byte(response))
|
|
})
|
|
|
|
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("ok"))
|
|
})
|
|
|
|
auth := middleware.RequireAuth([]byte(cfg.JWTSecret))
|
|
adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), domain.RoleAdmin)
|
|
productManagers := middleware.RequireAuth([]byte(cfg.JWTSecret), domain.RoleAdmin, domain.RoleOwner)
|
|
|
|
// Companies (Empresas)
|
|
mux.Handle("POST /api/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/empresas", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip)) // Alias for frontend
|
|
mux.Handle("GET /api/v1/companies", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/empresas", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip)) // Alias for frontend
|
|
mux.Handle("GET /api/v1/companies/{id}", chain(http.HandlerFunc(h.GetCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/empresas/{id}", chain(http.HandlerFunc(h.GetCompany), middleware.Logger, middleware.Gzip)) // Alias for frontend
|
|
mux.Handle("PATCH /api/v1/companies/{id}", chain(http.HandlerFunc(h.UpdateCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("PATCH /api/v1/empresas/{id}", chain(http.HandlerFunc(h.UpdateCompany), middleware.Logger, middleware.Gzip)) // Alias for frontend
|
|
mux.Handle("DELETE /api/v1/companies/{id}", chain(http.HandlerFunc(h.DeleteCompany), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("DELETE /api/v1/empresas/{id}", chain(http.HandlerFunc(h.DeleteCompany), middleware.Logger, middleware.Gzip)) // Alias for frontend
|
|
mux.Handle("PATCH /api/v1/companies/{id}/verify", chain(http.HandlerFunc(h.VerifyCompany), middleware.Logger, middleware.Gzip, adminOnly))
|
|
mux.Handle("PATCH /api/v1/empresas/{id}/verify", chain(http.HandlerFunc(h.VerifyCompany), middleware.Logger, middleware.Gzip, adminOnly)) // Alias for frontend
|
|
mux.Handle("GET /api/v1/companies/me", chain(http.HandlerFunc(h.GetMyCompany), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/companies/{id}/rating", chain(http.HandlerFunc(h.GetCompanyRating), middleware.Logger, middleware.Gzip))
|
|
|
|
// KYC
|
|
mux.Handle("POST /api/v1/companies/documents", chain(http.HandlerFunc(h.UploadDocument), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/companies/documents", chain(http.HandlerFunc(h.GetDocuments), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
// Financials
|
|
mux.Handle("GET /api/v1/finance/ledger", chain(http.HandlerFunc(h.GetLedger), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/finance/balance", chain(http.HandlerFunc(h.GetBalance), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/finance/withdrawals", chain(http.HandlerFunc(h.RequestWithdrawal), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/finance/withdrawals", chain(http.HandlerFunc(h.ListWithdrawals), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
// Team
|
|
mux.Handle("GET /api/v1/team", chain(http.HandlerFunc(h.ListTeam), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/team", chain(http.HandlerFunc(h.InviteMember), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
// Product Management (Admin + Vendors)
|
|
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, productManagers))
|
|
mux.Handle("POST /api/v1/products/import", chain(http.HandlerFunc(h.ImportProducts), middleware.Logger, middleware.Gzip, productManagers))
|
|
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip, productManagers))
|
|
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip, productManagers))
|
|
|
|
// Public/Shared Product Access
|
|
mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip, auth)) // List might remain open or logged-in only
|
|
mux.Handle("GET /api/v1/products/search", chain(http.HandlerFunc(h.SearchProducts), middleware.Logger, middleware.Gzip, middleware.OptionalAuth([]byte(cfg.JWTSecret))))
|
|
mux.Handle("GET /api/v1/products/{id}", chain(http.HandlerFunc(h.GetProduct), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/marketplace/records", chain(http.HandlerFunc(h.ListMarketplaceRecords), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/laboratorios", chain(http.HandlerFunc(h.ListManufacturers), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/categorias", chain(http.HandlerFunc(h.ListCategories), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/produtos-catalogo", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip)) // Alias
|
|
mux.Handle("POST /api/v1/produtos-catalogo", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, productManagers)) // Alias for frontend
|
|
mux.Handle("GET /api/v1/produtos-catalogo/codigo-ean/{ean}", chain(http.HandlerFunc(h.GetProductByEAN), middleware.Logger, middleware.Gzip))
|
|
|
|
mux.Handle("GET /api/v1/inventory", chain(http.HandlerFunc(h.ListInventory), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/inventory", chain(http.HandlerFunc(h.CreateInventoryItem), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/inventory/reserve", chain(http.HandlerFunc(h.ReserveStock), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/produtos-venda", chain(http.HandlerFunc(h.CreateInventoryItem), middleware.Logger, middleware.Gzip, auth)) // Alias
|
|
mux.Handle("GET /api/v1/produtos-venda", chain(http.HandlerFunc(h.ListInventory), middleware.Logger, middleware.Gzip, auth)) // Alias for list
|
|
mux.Handle("PUT /api/v1/produtos-venda/{id}", chain(http.HandlerFunc(h.UpdateInventoryItem), middleware.Logger, middleware.Gzip, auth)) // Update inventory
|
|
mux.Handle("POST /api/v1/inventory/adjust", chain(http.HandlerFunc(h.AdjustInventory), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
mux.Handle("POST /api/v1/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/orders/{id}/reorder", chain(http.HandlerFunc(h.Reorder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/orders", chain(http.HandlerFunc(h.ListOrders), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/orders/{id}", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/orders/{id}", chain(http.HandlerFunc(h.UpdateOrder), middleware.Logger, middleware.Gzip, auth)) // Add PUT support
|
|
mux.Handle("PATCH /api/v1/orders/{id}/status", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("DELETE /api/v1/orders/{id}", chain(http.HandlerFunc(h.DeleteOrder), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/orders/{id}/payment", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/orders/{id}/pay", chain(http.HandlerFunc(h.ProcessOrderPayment), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/orders/{id}/pix", chain(http.HandlerFunc(h.CreatePixPayment), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
mux.Handle("POST /api/v1/shipments", chain(http.HandlerFunc(h.CreateShipment), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/shipments", chain(http.HandlerFunc(h.ListShipments), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/shipments/", chain(http.HandlerFunc(h.GetShipmentByOrderID), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
mux.Handle("POST /api/v1/payments/webhook", chain(http.HandlerFunc(h.HandlePaymentWebhook), middleware.Logger, middleware.Gzip)) // Generic/Mercado Pago
|
|
mux.Handle("POST /api/v1/payments/webhook/stripe", chain(http.HandlerFunc(h.HandleStripeWebhook), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/payments/webhook/asaas", chain(http.HandlerFunc(h.HandleAsaasWebhook), middleware.Logger, middleware.Gzip))
|
|
|
|
mux.Handle("POST /api/v1/reviews", chain(http.HandlerFunc(h.CreateReview), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/reviews", chain(http.HandlerFunc(h.ListReviews), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/dashboard", chain(http.HandlerFunc(h.GetDashboard), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/dashboard/seller", chain(http.HandlerFunc(h.GetDashboard), middleware.Logger, middleware.Gzip, auth)) // Alias for frontend
|
|
|
|
// Payment Config (Admin)
|
|
mux.Handle("GET /api/v1/admin/payment-gateways/{provider}", chain(http.HandlerFunc(h.GetPaymentGatewayConfig), middleware.Logger, middleware.Gzip, adminOnly))
|
|
mux.Handle("PUT /api/v1/admin/payment-gateways/{provider}", chain(http.HandlerFunc(h.UpdatePaymentGatewayConfig), middleware.Logger, middleware.Gzip, adminOnly))
|
|
mux.Handle("POST /api/v1/admin/payment-gateways/{provider}/test", chain(http.HandlerFunc(h.TestPaymentGateway), middleware.Logger, middleware.Gzip, adminOnly))
|
|
mux.Handle("POST /api/v1/admin/geocode-sync", chain(http.HandlerFunc(h.GeocodeSync), middleware.Logger, middleware.Gzip, adminOnly))
|
|
|
|
// Payment Config (Seller)
|
|
mux.Handle("GET /api/v1/sellers/{id}/payment-config", chain(http.HandlerFunc(h.GetSellerPaymentConfig), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/sellers/{id}/onboarding", chain(http.HandlerFunc(h.OnboardSeller), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
// Credit Lines (Boleto a Prazo)
|
|
mux.Handle("POST /api/v1/companies/{company_id}/credit/check", chain(http.HandlerFunc(h.CheckCreditLine), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/companies/{company_id}/credit/limit", chain(http.HandlerFunc(h.SetCreditLimit), middleware.Logger, middleware.Gzip, adminOnly))
|
|
|
|
mux.Handle("POST /api/v1/auth/register", chain(http.HandlerFunc(h.Register), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/register/customer", chain(http.HandlerFunc(h.RegisterCustomer), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/register/tenant", chain(http.HandlerFunc(h.RegisterTenant), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/login", chain(http.HandlerFunc(h.Login), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("GET /api/v1/auth/me", chain(http.HandlerFunc(h.GetMe), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/auth/logout", chain(http.HandlerFunc(h.Logout), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/password/forgot", chain(http.HandlerFunc(h.ForgotPassword), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/password/reset", chain(http.HandlerFunc(h.ResetPassword), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/refresh", chain(http.HandlerFunc(h.Refresh), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/refresh-token", chain(http.HandlerFunc(h.RefreshToken), middleware.Logger, middleware.Gzip))
|
|
mux.Handle("POST /api/v1/auth/verify-email", chain(http.HandlerFunc(h.VerifyEmail), middleware.Logger, middleware.Gzip))
|
|
// Address
|
|
mux.Handle("POST /api/v1/enderecos", chain(http.HandlerFunc(h.CreateAddress), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/enderecos", chain(http.HandlerFunc(h.ListAddresses), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/enderecos/{id}", chain(http.HandlerFunc(h.UpdateAddress), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("DELETE /api/v1/enderecos/{id}", chain(http.HandlerFunc(h.DeleteAddress), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/push/register", chain(http.HandlerFunc(h.RegisterPushToken), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("DELETE /api/v1/push/unregister", chain(http.HandlerFunc(h.UnregisterPushToken), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/push/test", chain(http.HandlerFunc(h.TestPushNotification), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
mux.Handle("POST /api/v1/users", chain(http.HandlerFunc(h.CreateUser), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/users", chain(http.HandlerFunc(h.ListUsers), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("GET /api/v1/users/", chain(http.HandlerFunc(h.GetUser), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/users/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PATCH /api/v1/users/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip, auth)) // Add PATCH support
|
|
mux.Handle("PATCH /api/v1/usuarios/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip, auth)) // Alias for frontend
|
|
mux.Handle("DELETE /api/v1/users/", chain(http.HandlerFunc(h.DeleteUser), middleware.Logger, middleware.Gzip, auth))
|
|
|
|
mux.Handle("POST /api/v1/cart", chain(http.HandlerFunc(h.AddToCart), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/cart", chain(http.HandlerFunc(h.UpdateCart), middleware.Logger, middleware.Gzip, auth)) // Add PUT support
|
|
mux.Handle("GET /api/v1/cart", chain(http.HandlerFunc(h.GetCart), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("DELETE /api/v1/cart", chain(http.HandlerFunc(h.DeleteCartItem), middleware.Logger, middleware.Gzip, auth)) // Clear all
|
|
mux.Handle("DELETE /api/v1/cart/", chain(http.HandlerFunc(h.DeleteCartItem), middleware.Logger, middleware.Gzip, auth)) // Clear item
|
|
mux.Handle("GET /api/v1/shipping/settings/{vendor_id}", chain(http.HandlerFunc(h.GetShippingSettings), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("PUT /api/v1/shipping/settings/{vendor_id}", chain(http.HandlerFunc(h.UpsertShippingSettings), middleware.Logger, middleware.Gzip, auth))
|
|
mux.Handle("POST /api/v1/shipping/calculate", chain(http.HandlerFunc(h.CalculateShipping), middleware.Logger, middleware.Gzip))
|
|
|
|
mux.Handle("GET /api/v1/files/", chain(http.HandlerFunc(h.ServeFile), middleware.Logger))
|
|
|
|
mux.Handle("GET /docs/", httpSwagger.Handler(httpSwagger.URL("/docs/doc.json")))
|
|
|
|
return &Server{cfg: cfg, db: db, mux: mux, svc: svc}, nil
|
|
}
|
|
|
|
// Start runs the HTTP server and ensures the database is reachable.
|
|
func (s *Server) Start(ctx context.Context) error {
|
|
if err := s.db.PingContext(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
repo := postgres.New(s.db)
|
|
// Migrações já aplicadas manualmente
|
|
// if err := repo.ApplyMigrations(ctx); err != nil {
|
|
// return err
|
|
// }
|
|
|
|
// Seed platform admin automatically
|
|
{
|
|
adminEmail := s.cfg.BootstrapAdminEmail
|
|
adminPassword := s.cfg.BootstrapAdminPassword
|
|
|
|
existingUser, err := repo.GetUserByEmail(ctx, adminEmail)
|
|
if err != nil {
|
|
log.Printf("Seeding admin user: %s", adminEmail)
|
|
|
|
// Reuse existing platform company if CNPJ already exists
|
|
var platformCompanyID uuid.UUID
|
|
if dbErr := s.db.QueryRowContext(ctx, `SELECT id FROM companies WHERE cnpj = $1 LIMIT 1`, "00000000000000").Scan(&platformCompanyID); dbErr != nil {
|
|
// Company doesn't exist — create it via RegisterAccount
|
|
company := &domain.Company{
|
|
ID: uuid.Nil,
|
|
CNPJ: "00000000000000",
|
|
CorporateName: "SaveInMed Platform",
|
|
Category: "platform",
|
|
LicenseNumber: "ADMIN",
|
|
IsVerified: true,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
if err := s.svc.RegisterAccount(ctx, company, &domain.User{
|
|
Role: domain.RoleAdmin,
|
|
Name: bootstrapAdminName,
|
|
Username: bootstrapAdminUsername,
|
|
Email: adminEmail,
|
|
Superadmin: false,
|
|
}, adminPassword); err != nil {
|
|
log.Printf("Failed to seed admin: %v", err)
|
|
} else {
|
|
if _, err := s.svc.VerifyCompany(ctx, company.ID); err != nil {
|
|
log.Printf("Failed to verify platform company: %v", err)
|
|
}
|
|
log.Printf("Admin user created: email=%s", adminEmail)
|
|
}
|
|
} else {
|
|
// Company exists — create user bound to it directly
|
|
if err := s.svc.CreateUser(ctx, &domain.User{
|
|
CompanyID: platformCompanyID,
|
|
Role: domain.RoleAdmin,
|
|
Name: bootstrapAdminName,
|
|
Username: bootstrapAdminUsername,
|
|
Email: adminEmail,
|
|
Superadmin: false,
|
|
}, adminPassword); err != nil {
|
|
log.Printf("Failed to seed admin (existing company): %v", err)
|
|
} else {
|
|
log.Printf("Admin user created: email=%s", adminEmail)
|
|
}
|
|
}
|
|
} else {
|
|
existingUser.Role = domain.RoleAdmin
|
|
existingUser.Superadmin = false
|
|
if existingUser.Name == "" {
|
|
existingUser.Name = bootstrapAdminName
|
|
}
|
|
if existingUser.Username == "" {
|
|
existingUser.Username = bootstrapAdminUsername
|
|
}
|
|
|
|
if err := s.svc.UpdateUser(ctx, existingUser, adminPassword); err != nil {
|
|
log.Printf("Failed to reconcile existing user %s as admin: %v", adminEmail, err)
|
|
} else {
|
|
log.Printf("Admin user reconciled: email=%s", adminEmail)
|
|
}
|
|
}
|
|
}
|
|
|
|
corsConfig := middleware.CORSConfig{AllowedOrigins: s.cfg.CORSOrigins}
|
|
srv := &http.Server{
|
|
Addr: s.cfg.Addr(),
|
|
Handler: middleware.SecurityHeaders(middleware.CORSWithConfig(corsConfig)(s.mux)),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
// Start Stock Reservation Cleanup Worker
|
|
s.svc.StartStockCleanupWorker(ctx)
|
|
|
|
log.Printf("starting %s on %s", s.cfg.AppName, s.cfg.Addr())
|
|
return srv.ListenAndServe()
|
|
}
|
|
|
|
func chain(h http.Handler, middlewareFns ...func(http.Handler) http.Handler) http.Handler {
|
|
for i := len(middlewareFns) - 1; i >= 0; i-- {
|
|
h = middlewareFns[i](h)
|
|
}
|
|
return h
|
|
}
|
|
|
|
|