From 7d6f99b3b32e08a473e333594b23721a3db208aa Mon Sep 17 00:00:00 2001 From: Hein Date: Fri, 20 Feb 2026 16:39:43 +0200 Subject: [PATCH] feat(serverembed): add server embed functionality and readme --- pkg/serverembed/frontend.go | 8 ++ pkg/serverembed/readme | 1 + pkg/whatshooked/whatshooked.go | 140 +++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 pkg/serverembed/frontend.go create mode 100644 pkg/serverembed/readme diff --git a/pkg/serverembed/frontend.go b/pkg/serverembed/frontend.go new file mode 100644 index 0000000..7474b2e --- /dev/null +++ b/pkg/serverembed/frontend.go @@ -0,0 +1,8 @@ +package serverembed + +import ( + "embed" +) + +//go:embed dist/** readme +var RootEmbedFS embed.FS diff --git a/pkg/serverembed/readme b/pkg/serverembed/readme new file mode 100644 index 0000000..3ac4151 --- /dev/null +++ b/pkg/serverembed/readme @@ -0,0 +1 @@ +# Server Embed File \ No newline at end of file diff --git a/pkg/whatshooked/whatshooked.go b/pkg/whatshooked/whatshooked.go index e3a61bb..0dd809c 100644 --- a/pkg/whatshooked/whatshooked.go +++ b/pkg/whatshooked/whatshooked.go @@ -13,9 +13,11 @@ import ( "git.warky.dev/wdevs/whatshooked/pkg/handlers" "git.warky.dev/wdevs/whatshooked/pkg/hooks" "git.warky.dev/wdevs/whatshooked/pkg/logging" + "git.warky.dev/wdevs/whatshooked/pkg/models" "git.warky.dev/wdevs/whatshooked/pkg/storage" "git.warky.dev/wdevs/whatshooked/pkg/utils" "git.warky.dev/wdevs/whatshooked/pkg/whatsapp" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" "go.mau.fi/whatsmeow/types" ) @@ -258,6 +260,137 @@ func (wh *WhatsHooked) loadHooksFromDatabase(ctx context.Context) error { return nil } +// syncConfigToDatabase upserts hooks and WhatsApp accounts from config.json into the +// database so that config-file entries are never silently ignored at startup. Existing +// rows (matched by ID) are updated to reflect any config changes; new rows are inserted. +// The admin user is used as the owner for all config-seeded rows. +func (wh *WhatsHooked) syncConfigToDatabase(ctx context.Context) error { + db := storage.GetDB() + if db == nil { + return nil + } + + // Resolve the admin user ID to assign as owner of config-seeded rows. + userRepo := storage.NewUserRepository(db) + adminUser, err := userRepo.GetByUsername(ctx, "admin") + if err != nil || adminUser == nil { + logging.Warn("Admin user not found, skipping config sync", "error", err) + return nil + } + adminID := adminUser.ID.String() + + now := time.Now() + + // --- Sync hooks --- + if len(wh.config.Hooks) > 0 { + for _, h := range wh.config.Hooks { + if h.ID == "" { + logging.Warn("Skipping config hook with no ID", "name", h.Name) + continue + } + + method := h.Method + if method == "" { + method = "POST" + } + + // Serialize headers and events as JSON blobs. + headersJSON := "" + if len(h.Headers) > 0 { + if b, err := json.Marshal(h.Headers); err == nil { + headersJSON = string(b) + } + } + eventsJSON := "" + if len(h.Events) > 0 { + if b, err := json.Marshal(h.Events); err == nil { + eventsJSON = string(b) + } + } + + row := models.ModelPublicHook{ + ID: resolvespec_common.NewSqlString(h.ID), + Name: resolvespec_common.NewSqlString(h.Name), + URL: resolvespec_common.NewSqlString(h.URL), + Method: resolvespec_common.NewSqlString(method), + Description: resolvespec_common.NewSqlString(h.Description), + Headers: resolvespec_common.NewSqlString(headersJSON), + Events: resolvespec_common.NewSqlString(eventsJSON), + Active: h.Active, + AllowInsecure: h.AllowInsecure, + UserID: resolvespec_common.NewSqlString(adminID), + CreatedAt: resolvespec_common.NewSqlTimeStamp(now), + UpdatedAt: resolvespec_common.NewSqlTimeStamp(now), + } + + _, err := db.NewInsert(). + Model(&row). + On("CONFLICT (id) DO UPDATE"). + Set("name = EXCLUDED.name"). + Set("url = EXCLUDED.url"). + Set("method = EXCLUDED.method"). + Set("description = EXCLUDED.description"). + Set("headers = EXCLUDED.headers"). + Set("events = EXCLUDED.events"). + Set("active = EXCLUDED.active"). + Set("allow_insecure = EXCLUDED.allow_insecure"). + Set("updated_at = EXCLUDED.updated_at"). + Exec(ctx) + if err != nil { + logging.Error("Failed to sync hook from config", "hook_id", h.ID, "error", err) + } else { + logging.Info("Synced hook from config to database", "hook_id", h.ID, "name", h.Name) + } + } + } + + // --- Sync WhatsApp accounts --- + if len(wh.config.WhatsApp) > 0 { + accountRepo := storage.NewWhatsAppAccountRepository(db) + _ = accountRepo // used via db directly for upsert + + for _, wa := range wh.config.WhatsApp { + if wa.ID == "" { + logging.Warn("Skipping config WhatsApp account with no ID", "phone", wa.PhoneNumber) + continue + } + + accountType := wa.Type + if accountType == "" { + accountType = "whatsmeow" + } + + row := models.ModelPublicWhatsappAccount{ + ID: resolvespec_common.NewSqlString(wa.ID), + AccountType: resolvespec_common.NewSqlString(accountType), + PhoneNumber: resolvespec_common.NewSqlString(wa.PhoneNumber), + SessionPath: resolvespec_common.NewSqlString(wa.SessionPath), + Active: !wa.Disabled, + UserID: resolvespec_common.NewSqlString(adminID), + CreatedAt: resolvespec_common.NewSqlTimeStamp(now), + UpdatedAt: resolvespec_common.NewSqlTimeStamp(now), + } + + _, err := db.NewInsert(). + Model(&row). + On("CONFLICT (id) DO UPDATE"). + Set("account_type = EXCLUDED.account_type"). + Set("phone_number = EXCLUDED.phone_number"). + Set("session_path = EXCLUDED.session_path"). + Set("active = EXCLUDED.active"). + Set("updated_at = EXCLUDED.updated_at"). + Exec(ctx) + if err != nil { + logging.Error("Failed to sync WhatsApp account from config", "account_id", wa.ID, "error", err) + } else { + logging.Info("Synced WhatsApp account from config to database", "account_id", wa.ID, "phone", wa.PhoneNumber) + } + } + } + + return nil +} + // Handlers returns the HTTP handlers instance func (wh *WhatsHooked) Handlers() *handlers.Handlers { return wh.handlers @@ -330,6 +463,13 @@ func (wh *WhatsHooked) StartAPIServer(ctx context.Context) error { // Mark database as ready for account/hook loading wh.dbReady = true + // Sync config.json hooks and accounts into the database so they are never silently ignored + logging.Info("Syncing config to database") + if err := wh.syncConfigToDatabase(ctx); err != nil { + logging.Error("Failed to sync config to database", "error", err) + // Non-fatal: continue with whatever is already in the database + } + // Load hooks from database if err := wh.loadHooksFromDatabase(ctx); err != nil { logging.Error("Failed to load hooks from database", "error", err)