From cecbd2cef5be0282ac32ecc2a0417bf97efca944 Mon Sep 17 00:00:00 2001 From: Hein Date: Fri, 20 Feb 2026 17:56:02 +0200 Subject: [PATCH] feat(models): rename ModelPublicUser to ModelPublicUsers and update references * Update user-related models to use plural naming for consistency * Add relationships to ModelPublicUsers in related models * Adjust database migration and schema to reflect changes * Remove deprecated ModelPublicUser --- pkg/api/server.go | 30 ++++++---- pkg/models/sql_public_api_key.go | 1 + pkg/models/sql_public_event_log.go | 3 +- pkg/models/sql_public_hook.go | 1 + pkg/models/sql_public_session.go | 1 + pkg/models/sql_public_user.go | 67 --------------------- pkg/models/sql_public_users.go | 72 +++++++++++++++++++++++ pkg/models/sql_public_whatsapp_account.go | 1 + pkg/storage/db.go | 2 +- pkg/storage/repository.go | 12 ++-- pkg/storage/seed.go | 2 +- sql/migrate.sh | 2 + sql/schema.dbml | 2 +- 13 files changed, 107 insertions(+), 89 deletions(-) delete mode 100644 pkg/models/sql_public_user.go create mode 100644 pkg/models/sql_public_users.go create mode 100755 sql/migrate.sh diff --git a/pkg/api/server.go b/pkg/api/server.go index 9c2375b..684b8f5 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -73,8 +73,14 @@ func NewServer(cfg *config.Config, db *bun.DB, wh WhatsHookedInterface) (*Server apiV1Router.Use(security.NewAuthMiddleware(securityList)) apiV1Router.Use(security.SetSecurityMiddleware(securityList)) + // Create the embedded dist FS (built Vite output, includes web/public/ contents) + distFS, err := fs.Sub(serverembed.RootEmbedFS, "dist") + if err != nil { + return nil, fmt.Errorf("failed to sub embedded dist FS: %w", err) + } + // Setup WhatsApp API routes on main router (these use their own Auth middleware) - SetupWhatsAppRoutes(router, wh) + SetupWhatsAppRoutes(router, wh, distFS) // Setup ResolveSpec routes on the protected /api/v1 subrouter (auto-generated CRUD) restheadspec.SetupMuxRoutes(apiV1Router, handler, nil) @@ -83,10 +89,6 @@ func NewServer(cfg *config.Config, db *bun.DB, wh WhatsHookedInterface) (*Server SetupCustomRoutes(router, secProvider, db) // Serve React SPA from the embedded filesystem at /ui/ - distFS, err := fs.Sub(serverembed.RootEmbedFS, "dist") - if err != nil { - return nil, fmt.Errorf("failed to sub embedded dist FS: %w", err) - } spa := embeddedSPAHandler{fs: distFS, indexPath: "index.html"} router.PathPrefix("/ui/").Handler(http.StripPrefix("/ui", spa)) router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", spa)) @@ -131,7 +133,7 @@ func (s *Server) Start() error { // registerModelsToRegistry registers all BUN models with the model registry func registerModelsToRegistry(registry common.ModelRegistry) { // Register all models with their table names (without schema for SQLite compatibility) - registry.RegisterModel("users", &models.ModelPublicUser{}) + registry.RegisterModel("users", &models.ModelPublicUsers{}) registry.RegisterModel("api_keys", &models.ModelPublicAPIKey{}) registry.RegisterModel("hooks", &models.ModelPublicHook{}) registry.RegisterModel("whatsapp_accounts", &models.ModelPublicWhatsappAccount{}) @@ -141,7 +143,7 @@ func registerModelsToRegistry(registry common.ModelRegistry) { } // SetupWhatsAppRoutes adds all WhatsApp API routes -func SetupWhatsAppRoutes(router *mux.Router, wh WhatsHookedInterface) { +func SetupWhatsAppRoutes(router *mux.Router, wh WhatsHookedInterface, distFS fs.FS) { h := wh.Handlers() // Landing page (no auth required) @@ -151,8 +153,12 @@ func SetupWhatsAppRoutes(router *mux.Router, wh WhatsHookedInterface) { router.HandleFunc("/privacy-policy", h.ServePrivacyPolicy).Methods("GET") router.HandleFunc("/terms-of-service", h.ServeTermsOfService).Methods("GET") - // Static files (no auth required) - router.PathPrefix("/static/").HandlerFunc(h.ServeStatic) + // Logo files served from the embedded pkg/handlers/static/ (referenced by index.html) + router.HandleFunc("/static/logo.png", h.ServeStatic) + router.HandleFunc("/static/logo1024.png", h.ServeStatic) + + // Everything else under /static/ is served from the built web/public/ directory + router.PathPrefix("/static/").Handler(http.StripPrefix("/static", http.FileServer(http.FS(distFS)))) // Health check (no auth required) router.HandleFunc("/health", h.Health).Methods("GET") @@ -481,7 +487,7 @@ func handleQueryCreate(w http.ResponseWriter, r *http.Request, db *bun.DB, req Q m.ID.FromString(generatedID) case *models.ModelPublicEventLog: m.ID.FromString(generatedID) - case *models.ModelPublicUser: + case *models.ModelPublicUsers: m.ID.FromString(generatedID) } } @@ -547,7 +553,7 @@ func handleQueryDelete(w http.ResponseWriter, r *http.Request, db *bun.DB, req Q func getModelForTable(table string) func() interface{} { switch table { case "users": - return func() interface{} { return &[]models.ModelPublicUser{} } + return func() interface{} { return &[]models.ModelPublicUsers{} } case "hooks": return func() interface{} { return &[]models.ModelPublicHook{} } case "whatsapp_accounts": @@ -569,7 +575,7 @@ func getModelForTable(table string) func() interface{} { func getModelSingleForTable(table string) interface{} { switch table { case "users": - return &models.ModelPublicUser{} + return &models.ModelPublicUsers{} case "hooks": return &models.ModelPublicHook{} case "whatsapp_accounts": diff --git a/pkg/models/sql_public_api_key.go b/pkg/models/sql_public_api_key.go index 9e2ce4a..13bc715 100644 --- a/pkg/models/sql_public_api_key.go +++ b/pkg/models/sql_public_api_key.go @@ -21,6 +21,7 @@ type ModelPublicAPIKey struct { Permissions resolvespec_common.SqlString `bun:"permissions,type:text,nullzero," json:"permissions"` // JSON array of permissions UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),notnull," json:"user_id"` + RelUserID *ModelPublicUsers `bun:"rel:has-one,join:user_id=id" json:"reluserid,omitempty"` // Has one ModelPublicUsers } // TableName returns the table name for ModelPublicAPIKey diff --git a/pkg/models/sql_public_event_log.go b/pkg/models/sql_public_event_log.go index 035079f..ed5a955 100644 --- a/pkg/models/sql_public_event_log.go +++ b/pkg/models/sql_public_event_log.go @@ -20,7 +20,8 @@ type ModelPublicEventLog struct { IpAddress resolvespec_common.SqlString `bun:"ip_address,type:varchar(50),nullzero," json:"ip_address"` Success bool `bun:"success,type:boolean,default:true,notnull," json:"success"` UserAgent resolvespec_common.SqlString `bun:"user_agent,type:text,nullzero," json:"user_agent"` - UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),nullzero," json:"user_id"` // Optional user reference + UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),nullzero," json:"user_id"` // Optional user reference + RelUserID *ModelPublicUsers `bun:"rel:has-one,join:user_id=id" json:"reluserid,omitempty"` // Has one ModelPublicUsers } // TableName returns the table name for ModelPublicEventLog diff --git a/pkg/models/sql_public_hook.go b/pkg/models/sql_public_hook.go index 47f5b85..3e48e98 100644 --- a/pkg/models/sql_public_hook.go +++ b/pkg/models/sql_public_hook.go @@ -25,6 +25,7 @@ type ModelPublicHook struct { UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` URL resolvespec_common.SqlString `bun:"url,type:text,notnull," json:"url"` UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),notnull," json:"user_id"` + RelUserID *ModelPublicUsers `bun:"rel:has-one,join:user_id=id" json:"reluserid,omitempty"` // Has one ModelPublicUsers } // TableName returns the table name for ModelPublicHook diff --git a/pkg/models/sql_public_session.go b/pkg/models/sql_public_session.go index 5e24a91..861a0a6 100644 --- a/pkg/models/sql_public_session.go +++ b/pkg/models/sql_public_session.go @@ -17,6 +17,7 @@ type ModelPublicSession struct { UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` UserAgent resolvespec_common.SqlString `bun:"user_agent,type:text,nullzero," json:"user_agent"` UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),notnull," json:"user_id"` + RelUserID *ModelPublicUsers `bun:"rel:has-one,join:user_id=id" json:"reluserid,omitempty"` // Has one ModelPublicUsers } // TableName returns the table name for ModelPublicSession diff --git a/pkg/models/sql_public_user.go b/pkg/models/sql_public_user.go deleted file mode 100644 index 14c144e..0000000 --- a/pkg/models/sql_public_user.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by relspecgo. DO NOT EDIT. -package models - -import ( - "fmt" - resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" - "github.com/uptrace/bun" -) - -type ModelPublicUser struct { - bun.BaseModel `bun:"table:public.user,alias:user"` - ID resolvespec_common.SqlString `bun:"id,type:varchar(36),pk," json:"id"` // UUID - Active bool `bun:"active,type:boolean,default:true,notnull," json:"active"` - CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamp,default:now(),notnull," json:"created_at"` - DeletedAt resolvespec_common.SqlTimeStamp `bun:"deleted_at,type:timestamp,nullzero," json:"deleted_at"` // Soft delete - Email resolvespec_common.SqlString `bun:"email,type:varchar(255),notnull," json:"email"` - FullName resolvespec_common.SqlString `bun:"full_name,type:varchar(255),nullzero," json:"full_name"` - Password resolvespec_common.SqlString `bun:"password,type:varchar(255),notnull," json:"password"` // Bcrypt hashed password - Role resolvespec_common.SqlString `bun:"role,type:varchar(50),default:'user',notnull," json:"role"` // admin - UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` - Username resolvespec_common.SqlString `bun:"username,type:varchar(255),notnull," json:"username"` -} - -// TableName returns the table name for ModelPublicUser -func (m ModelPublicUser) TableName() string { - return "public.user" -} - -// TableNameOnly returns the table name without schema for ModelPublicUser -func (m ModelPublicUser) TableNameOnly() string { - return "user" -} - -// SchemaName returns the schema name for ModelPublicUser -func (m ModelPublicUser) SchemaName() string { - return "public" -} - -// GetID returns the primary key value -func (m ModelPublicUser) GetID() int64 { - return m.ID.Int64() -} - -// GetIDStr returns the primary key as a string -func (m ModelPublicUser) GetIDStr() string { - return fmt.Sprintf("%d", m.ID) -} - -// SetID sets the primary key value -func (m ModelPublicUser) SetID(newid int64) { - m.UpdateID(newid) -} - -// UpdateID updates the primary key value -func (m *ModelPublicUser) UpdateID(newid int64) { - m.ID.FromString(fmt.Sprintf("%d", newid)) -} - -// GetIDName returns the name of the primary key column -func (m ModelPublicUser) GetIDName() string { - return "id" -} - -// GetPrefix returns the table prefix -func (m ModelPublicUser) GetPrefix() string { - return "USE" -} diff --git a/pkg/models/sql_public_users.go b/pkg/models/sql_public_users.go new file mode 100644 index 0000000..b5b6204 --- /dev/null +++ b/pkg/models/sql_public_users.go @@ -0,0 +1,72 @@ +// Code generated by relspecgo. DO NOT EDIT. +package models + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicUsers struct { + bun.BaseModel `bun:"table:public.users,alias:users"` + ID resolvespec_common.SqlString `bun:"id,type:varchar(36),pk," json:"id"` // UUID + Active bool `bun:"active,type:boolean,default:true,notnull," json:"active"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamp,default:now(),notnull," json:"created_at"` + DeletedAt resolvespec_common.SqlTimeStamp `bun:"deleted_at,type:timestamp,nullzero," json:"deleted_at"` // Soft delete + Email resolvespec_common.SqlString `bun:"email,type:varchar(255),notnull," json:"email"` + FullName resolvespec_common.SqlString `bun:"full_name,type:varchar(255),nullzero," json:"full_name"` + Password resolvespec_common.SqlString `bun:"password,type:varchar(255),notnull," json:"password"` // Bcrypt hashed password + Role resolvespec_common.SqlString `bun:"role,type:varchar(50),default:'user',notnull," json:"role"` // admin + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` + Username resolvespec_common.SqlString `bun:"username,type:varchar(255),notnull," json:"username"` + RelUserIDPublicAPIKeys []*ModelPublicAPIKey `bun:"rel:has-many,join:id=user_id" json:"reluseridpublicapikeys,omitempty"` // Has many ModelPublicAPIKey + RelUserIDPublicHooks []*ModelPublicHook `bun:"rel:has-many,join:id=user_id" json:"reluseridpublichooks,omitempty"` // Has many ModelPublicHook + RelUserIDPublicWhatsappAccounts []*ModelPublicWhatsappAccount `bun:"rel:has-many,join:id=user_id" json:"reluseridpublicwhatsappaccounts,omitempty"` // Has many ModelPublicWhatsappAccount + RelUserIDPublicEventLogs []*ModelPublicEventLog `bun:"rel:has-many,join:id=user_id" json:"reluseridpubliceventlogs,omitempty"` // Has many ModelPublicEventLog + RelUserIDPublicSessions []*ModelPublicSession `bun:"rel:has-many,join:id=user_id" json:"reluseridpublicsessions,omitempty"` // Has many ModelPublicSession +} + +// TableName returns the table name for ModelPublicUsers +func (m ModelPublicUsers) TableName() string { + return "public.users" +} + +// TableNameOnly returns the table name without schema for ModelPublicUsers +func (m ModelPublicUsers) TableNameOnly() string { + return "users" +} + +// SchemaName returns the schema name for ModelPublicUsers +func (m ModelPublicUsers) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicUsers) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicUsers) GetIDStr() string { + return fmt.Sprintf("%d", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicUsers) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicUsers) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicUsers) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicUsers) GetPrefix() string { + return "USE" +} diff --git a/pkg/models/sql_public_whatsapp_account.go b/pkg/models/sql_public_whatsapp_account.go index 163b0d9..d8d1c45 100644 --- a/pkg/models/sql_public_whatsapp_account.go +++ b/pkg/models/sql_public_whatsapp_account.go @@ -22,6 +22,7 @@ type ModelPublicWhatsappAccount struct { Status resolvespec_common.SqlString `bun:"status,type:varchar(50),default:'disconnected',notnull," json:"status"` // connected UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamp,default:now(),notnull," json:"updated_at"` UserID resolvespec_common.SqlString `bun:"user_id,type:varchar(36),notnull," json:"user_id"` + RelUserID *ModelPublicUsers `bun:"rel:has-one,join:user_id=id" json:"reluserid,omitempty"` // Has one ModelPublicUsers } // TableName returns the table name for ModelPublicWhatsappAccount diff --git a/pkg/storage/db.go b/pkg/storage/db.go index 1507c0e..c493511 100644 --- a/pkg/storage/db.go +++ b/pkg/storage/db.go @@ -78,7 +78,7 @@ func CreateTables(ctx context.Context) error { // For PostgreSQL, use BUN's auto-generation models := []interface{}{ - (*models.ModelPublicUser)(nil), + (*models.ModelPublicUsers)(nil), (*models.ModelPublicAPIKey)(nil), (*models.ModelPublicHook)(nil), (*models.ModelPublicWhatsappAccount)(nil), diff --git a/pkg/storage/repository.go b/pkg/storage/repository.go index f65913a..035e1b6 100644 --- a/pkg/storage/repository.go +++ b/pkg/storage/repository.go @@ -74,19 +74,19 @@ func (r *Repository[T]) Count(ctx context.Context, filter map[string]interface{} // UserRepository provides user-specific operations type UserRepository struct { - *Repository[models.ModelPublicUser] + *Repository[models.ModelPublicUsers] } // NewUserRepository creates a new user repository func NewUserRepository(db *bun.DB) *UserRepository { return &UserRepository{ - Repository: NewRepository[models.ModelPublicUser](db), + Repository: NewRepository[models.ModelPublicUsers](db), } } // GetByUsername retrieves a user by username -func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*models.ModelPublicUser, error) { - var user models.ModelPublicUser +func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*models.ModelPublicUsers, error) { + var user models.ModelPublicUsers err := r.db.NewSelect().Model(&user).Where("username = ?", username).Scan(ctx) if err != nil { return nil, err @@ -95,8 +95,8 @@ func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*m } // GetByEmail retrieves a user by email -func (r *UserRepository) GetByEmail(ctx context.Context, email string) (*models.ModelPublicUser, error) { - var user models.ModelPublicUser +func (r *UserRepository) GetByEmail(ctx context.Context, email string) (*models.ModelPublicUsers, error) { + var user models.ModelPublicUsers err := r.db.NewSelect().Model(&user).Where("email = ?", email).Scan(ctx) if err != nil { return nil, err diff --git a/pkg/storage/seed.go b/pkg/storage/seed.go index a53e30b..34ae656 100644 --- a/pkg/storage/seed.go +++ b/pkg/storage/seed.go @@ -32,7 +32,7 @@ func SeedData(ctx context.Context) error { } now := time.Now() - adminUser := &models.ModelPublicUser{ + adminUser := &models.ModelPublicUsers{ ID: resolvespec_common.NewSqlString(uuid.New().String()), Username: resolvespec_common.NewSqlString("admin"), Email: resolvespec_common.NewSqlString("admin@whatshooked.local"), diff --git a/sql/migrate.sh b/sql/migrate.sh new file mode 100755 index 0000000..1ca5711 --- /dev/null +++ b/sql/migrate.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Migration script placeholder. Implement your migration logic here." diff --git a/sql/schema.dbml b/sql/schema.dbml index aaa911a..88f9777 100644 --- a/sql/schema.dbml +++ b/sql/schema.dbml @@ -1,7 +1,7 @@ // WhatsHooked Database Schema // This file defines the database schema for WhatsHooked Phase 2 -Table user { +Table users { id varchar(36) [primary key, note: 'UUID'] username varchar(255) [unique, not null] email varchar(255) [unique, not null]