refactor(UI): 🏗️ Ui changes and API changes
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/handlers"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
@@ -397,6 +399,60 @@ func handleQueryCreate(w http.ResponseWriter, r *http.Request, db *bun.DB, req Q
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize data map if needed
|
||||
if req.Data == nil {
|
||||
req.Data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Auto-generate UUID for id field if not provided
|
||||
generatedID := ""
|
||||
if _, exists := req.Data["id"]; !exists {
|
||||
generatedID = uuid.New().String()
|
||||
req.Data["id"] = generatedID
|
||||
}
|
||||
|
||||
// Auto-inject user_id for tables that need it
|
||||
if userCtx != nil && userCtx.Claims != nil {
|
||||
// Get user_id from claims (it's stored as UUID string)
|
||||
if userIDClaim, ok := userCtx.Claims["user_id"]; ok {
|
||||
if userID, ok := userIDClaim.(string); ok && userID != "" {
|
||||
// Add user_id to data if the table requires it and it's not already set
|
||||
tablesWithUserID := map[string]bool{
|
||||
"hooks": true,
|
||||
"whatsapp_accounts": true,
|
||||
"api_keys": true,
|
||||
"event_logs": true,
|
||||
}
|
||||
|
||||
if tablesWithUserID[req.Table] {
|
||||
// Only set user_id if not already provided
|
||||
if _, exists := req.Data["user_id"]; !exists {
|
||||
req.Data["user_id"] = userID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-generate session_path for WhatsApp accounts if not provided
|
||||
if req.Table == "whatsapp_accounts" {
|
||||
// Set session_path if not already provided
|
||||
if _, exists := req.Data["session_path"]; !exists {
|
||||
// Use account_id if provided, otherwise use generated id
|
||||
sessionID := ""
|
||||
if accountID, ok := req.Data["account_id"].(string); ok && accountID != "" {
|
||||
sessionID = accountID
|
||||
} else if generatedID != "" {
|
||||
sessionID = generatedID
|
||||
} else if id, ok := req.Data["id"].(string); ok && id != "" {
|
||||
sessionID = id
|
||||
}
|
||||
if sessionID != "" {
|
||||
req.Data["session_path"] = fmt.Sprintf("./sessions/%s", sessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert data map to model using JSON marshaling
|
||||
dataJSON, err := json.Marshal(req.Data)
|
||||
if err != nil {
|
||||
@@ -409,6 +465,22 @@ func handleQueryCreate(w http.ResponseWriter, r *http.Request, db *bun.DB, req Q
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure ID is set after unmarshaling by using model-specific handling
|
||||
if generatedID != "" {
|
||||
switch m := model.(type) {
|
||||
case *models.ModelPublicWhatsappAccount:
|
||||
m.ID.FromString(generatedID)
|
||||
case *models.ModelPublicHook:
|
||||
m.ID.FromString(generatedID)
|
||||
case *models.ModelPublicAPIKey:
|
||||
m.ID.FromString(generatedID)
|
||||
case *models.ModelPublicEventLog:
|
||||
m.ID.FromString(generatedID)
|
||||
case *models.ModelPublicUser:
|
||||
m.ID.FromString(generatedID)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into database
|
||||
_, err = db.NewInsert().Model(model).Exec(r.Context())
|
||||
if err != nil {
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
|
||||
type ModelPublicWhatsappAccount struct {
|
||||
bun.BaseModel `bun:"table:whatsapp_accounts,alias:whatsapp_accounts"`
|
||||
ID resolvespec_common.SqlString `bun:"id,type:varchar(36),pk," json:"id"` // UUID
|
||||
AccountType resolvespec_common.SqlString `bun:"account_type,type:varchar(50),notnull," json:"account_type"` // whatsmeow or business-api
|
||||
ID resolvespec_common.SqlString `bun:"id,type:varchar(36),pk," json:"id"` // UUID
|
||||
AccountID resolvespec_common.SqlString `bun:"account_id,type:varchar(100),unique,nullzero," json:"account_id"` // User-friendly unique identifier
|
||||
AccountType resolvespec_common.SqlString `bun:"account_type,type:varchar(50),notnull," json:"account_type"` // whatsmeow or business-api
|
||||
Active bool `bun:"active,type:boolean,default:true,notnull," json:"active"`
|
||||
Config resolvespec_common.SqlString `bun:"config,type:text,nullzero," json:"config"` // JSON encoded additional config
|
||||
CreatedAt resolvespec_common.SqlTime `bun:"created_at,type:timestamp,default:now(),notnull," json:"created_at"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package whatshooked
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/api"
|
||||
@@ -29,6 +30,7 @@ type WhatsHooked struct {
|
||||
messageCache *cache.MessageCache
|
||||
handlers *handlers.Handlers
|
||||
apiServer *api.Server // ResolveSpec unified server
|
||||
dbReady bool // Flag to indicate if database is ready
|
||||
}
|
||||
|
||||
// NewFromFile creates a WhatsHooked instance from a config file
|
||||
@@ -109,7 +111,7 @@ func newWithConfig(cfg *config.Config, configPath string) (*WhatsHooked, error)
|
||||
|
||||
// Initialize hook manager
|
||||
wh.hookMgr = hooks.NewManager(wh.eventBus, wh.messageCache)
|
||||
wh.hookMgr.LoadHooks(cfg.Hooks)
|
||||
// Don't load hooks here - will be loaded from database after it's initialized
|
||||
wh.hookMgr.Start()
|
||||
|
||||
// Initialize event logger if enabled
|
||||
@@ -134,6 +136,59 @@ func newWithConfig(cfg *config.Config, configPath string) (*WhatsHooked, error)
|
||||
|
||||
// ConnectAll connects to all configured WhatsApp accounts
|
||||
func (wh *WhatsHooked) ConnectAll(ctx context.Context) error {
|
||||
// If database is ready, load accounts from database
|
||||
if wh.dbReady {
|
||||
return wh.connectFromDatabase(ctx)
|
||||
}
|
||||
|
||||
// Otherwise, fall back to config file (legacy)
|
||||
return wh.connectFromConfig(ctx)
|
||||
}
|
||||
|
||||
// connectFromDatabase loads and connects WhatsApp accounts from the database
|
||||
func (wh *WhatsHooked) connectFromDatabase(ctx context.Context) error {
|
||||
db := storage.GetDB()
|
||||
if db == nil {
|
||||
logging.Warn("Database not available, skipping account connections")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load active WhatsApp accounts from database
|
||||
accountRepo := storage.NewWhatsAppAccountRepository(db)
|
||||
accounts, err := accountRepo.List(ctx, map[string]interface{}{"active": true})
|
||||
if err != nil {
|
||||
logging.Error("Failed to load WhatsApp accounts from database", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logging.Info("Loading WhatsApp accounts from database", "count", len(accounts))
|
||||
|
||||
for _, account := range accounts {
|
||||
// Skip if account_id is not set
|
||||
accountID := account.AccountID.String()
|
||||
if accountID == "" {
|
||||
accountID = account.ID.String() // Fall back to UUID if account_id not set
|
||||
}
|
||||
|
||||
// Convert database model to config format
|
||||
waCfg := config.WhatsAppConfig{
|
||||
ID: accountID,
|
||||
PhoneNumber: account.PhoneNumber.String(),
|
||||
Type: account.AccountType.String(),
|
||||
SessionPath: account.SessionPath.String(),
|
||||
Disabled: !account.Active,
|
||||
}
|
||||
|
||||
if err := wh.whatsappMgr.Connect(ctx, waCfg); err != nil {
|
||||
logging.Error("Failed to connect to WhatsApp", "account_id", waCfg.ID, "error", err)
|
||||
// Continue connecting to other accounts even if one fails
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectFromConfig loads and connects WhatsApp accounts from config file (legacy)
|
||||
func (wh *WhatsHooked) connectFromConfig(ctx context.Context) error {
|
||||
for _, waCfg := range wh.config.WhatsApp {
|
||||
// Skip disabled accounts
|
||||
if waCfg.Disabled {
|
||||
@@ -149,6 +204,59 @@ func (wh *WhatsHooked) ConnectAll(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadHooksFromDatabase loads webhooks from the database
|
||||
func (wh *WhatsHooked) loadHooksFromDatabase(ctx context.Context) error {
|
||||
db := storage.GetDB()
|
||||
if db == nil {
|
||||
logging.Warn("Database not available, skipping hook loading")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load active hooks from database
|
||||
hookRepo := storage.NewHookRepository(db)
|
||||
dbHooks, err := hookRepo.List(ctx, map[string]interface{}{"active": true})
|
||||
if err != nil {
|
||||
logging.Error("Failed to load hooks from database", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logging.Info("Loading hooks from database", "count", len(dbHooks))
|
||||
|
||||
// Convert database models to config format
|
||||
configHooks := make([]config.Hook, 0, len(dbHooks))
|
||||
for _, dbHook := range dbHooks {
|
||||
hook := config.Hook{
|
||||
ID: dbHook.ID.String(),
|
||||
Name: dbHook.Name.String(),
|
||||
URL: dbHook.URL.String(),
|
||||
Method: dbHook.Method.String(),
|
||||
Description: dbHook.Description.String(),
|
||||
Active: dbHook.Active,
|
||||
}
|
||||
|
||||
// Parse headers JSON if present
|
||||
if headersStr := dbHook.Headers.String(); headersStr != "" {
|
||||
hook.Headers = make(map[string]string)
|
||||
if err := json.Unmarshal([]byte(headersStr), &hook.Headers); err != nil {
|
||||
logging.Warn("Failed to parse hook headers", "hook_id", hook.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse events JSON if present
|
||||
if eventsStr := dbHook.Events.String(); eventsStr != "" {
|
||||
if err := json.Unmarshal([]byte(eventsStr), &hook.Events); err != nil {
|
||||
logging.Warn("Failed to parse hook events", "hook_id", hook.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
configHooks = append(configHooks, hook)
|
||||
}
|
||||
|
||||
// Load hooks into the hook manager
|
||||
wh.hookMgr.LoadHooks(configHooks)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handlers returns the HTTP handlers instance
|
||||
func (wh *WhatsHooked) Handlers() *handlers.Handlers {
|
||||
return wh.handlers
|
||||
@@ -218,6 +326,15 @@ func (wh *WhatsHooked) StartAPIServer(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark database as ready for account/hook loading
|
||||
wh.dbReady = true
|
||||
|
||||
// Load hooks from database
|
||||
if err := wh.loadHooksFromDatabase(ctx); err != nil {
|
||||
logging.Error("Failed to load hooks from database", "error", err)
|
||||
// Continue anyway, hooks can be added later
|
||||
}
|
||||
|
||||
// Create unified server
|
||||
logging.Info("Creating ResolveSpec server", "host", wh.config.Server.Host, "port", wh.config.Server.Port)
|
||||
apiServer, err := api.NewServer(wh.config, db, wh)
|
||||
|
||||
Reference in New Issue
Block a user