78b57273e2
- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
268 lines
6.9 KiB
Go
268 lines
6.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// SocialMediaAccount representerar ett kopplat socialt media-konto
|
|
type SocialMediaAccount struct {
|
|
ID string `json:"id"`
|
|
Platform string `json:"platform"`
|
|
AccountName string `json:"account_name"`
|
|
DisplayName string `json:"display_name"`
|
|
Followers int `json:"followers"`
|
|
Following int `json:"following"`
|
|
Posts int `json:"posts"`
|
|
ProfileURL string `json:"profile_url"`
|
|
AvatarURL string `json:"avatar_url"`
|
|
IsConnected bool `json:"is_connected"`
|
|
LastSynced time.Time `json:"last_synced"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// SocialMediaPost representerar ett inlägg
|
|
type SocialMediaPost struct {
|
|
ID string `json:"id"`
|
|
AccountID string `json:"account_id"`
|
|
Platform string `json:"platform"`
|
|
Content string `json:"content"`
|
|
MediaURL string `json:"media_url,omitempty"`
|
|
Likes int `json:"likes"`
|
|
Comments int `json:"comments"`
|
|
Shares int `json:"shares"`
|
|
Reach int `json:"reach"`
|
|
PostedAt time.Time `json:"posted_at"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
// SocialMediaStats representerar aggregerad statistik
|
|
type SocialMediaStats struct {
|
|
TotalFollowers int `json:"total_followers"`
|
|
TotalPosts int `json:"total_posts"`
|
|
TotalEngagement int `json:"total_engagement"`
|
|
Accounts int `json:"accounts"`
|
|
}
|
|
|
|
// SocialMediaHandler hanterar sociala media-konton
|
|
type SocialMediaHandler struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewSocialMediaHandler skapar en ny handler
|
|
func NewSocialMediaHandler(db *sql.DB) *SocialMediaHandler {
|
|
return &SocialMediaHandler{db: db}
|
|
}
|
|
|
|
// InitDB skapar tabeller för sociala media
|
|
func (h *SocialMediaHandler) InitDB() error {
|
|
_, err := h.db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS social_media_accounts (
|
|
id TEXT PRIMARY KEY,
|
|
platform TEXT NOT NULL,
|
|
account_name TEXT NOT NULL,
|
|
display_name TEXT,
|
|
followers INTEGER DEFAULT 0,
|
|
following INTEGER DEFAULT 0,
|
|
posts INTEGER DEFAULT 0,
|
|
profile_url TEXT,
|
|
avatar_url TEXT,
|
|
is_connected BOOLEAN DEFAULT false,
|
|
last_synced TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = h.db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS social_media_posts (
|
|
id TEXT PRIMARY KEY,
|
|
account_id TEXT NOT NULL,
|
|
platform TEXT NOT NULL,
|
|
content TEXT,
|
|
media_url TEXT,
|
|
likes INTEGER DEFAULT 0,
|
|
comments INTEGER DEFAULT 0,
|
|
shares INTEGER DEFAULT 0,
|
|
reach INTEGER DEFAULT 0,
|
|
posted_at TIMESTAMP,
|
|
status TEXT DEFAULT 'published',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (account_id) REFERENCES social_media_accounts(id)
|
|
)
|
|
`)
|
|
return err
|
|
}
|
|
|
|
// ListAccounts listar alla kopplade konton
|
|
func (h *SocialMediaHandler) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.db.Query(`
|
|
SELECT id, platform, account_name, display_name, followers, following, posts,
|
|
profile_url, avatar_url, is_connected, last_synced, created_at
|
|
FROM social_media_accounts
|
|
ORDER BY platform, account_name
|
|
`)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]interface{}{
|
|
"ok": false,
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var accounts []SocialMediaAccount
|
|
for rows.Next() {
|
|
var a SocialMediaAccount
|
|
var lastSynced sql.NullTime
|
|
err := rows.Scan(
|
|
&a.ID, &a.Platform, &a.AccountName, &a.DisplayName,
|
|
&a.Followers, &a.Following, &a.Posts,
|
|
&a.ProfileURL, &a.AvatarURL, &a.IsConnected,
|
|
&lastSynced, &a.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if lastSynced.Valid {
|
|
a.LastSynced = lastSynced.Time
|
|
}
|
|
accounts = append(accounts, a)
|
|
}
|
|
|
|
if accounts == nil {
|
|
accounts = []SocialMediaAccount{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"accounts": accounts,
|
|
})
|
|
}
|
|
|
|
// GetStats returnerar aggregerad statistik
|
|
func (h *SocialMediaHandler) GetStats(w http.ResponseWriter, r *http.Request) {
|
|
var stats SocialMediaStats
|
|
err := h.db.QueryRow(`
|
|
SELECT
|
|
COALESCE(SUM(followers), 0),
|
|
COALESCE(SUM(posts), 0),
|
|
COUNT(*)
|
|
FROM social_media_accounts
|
|
WHERE is_connected = true
|
|
`).Scan(&stats.TotalFollowers, &stats.TotalPosts, &stats.Accounts)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]interface{}{
|
|
"ok": false,
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"stats": stats,
|
|
})
|
|
}
|
|
|
|
// AddAccount lägger till ett nytt konto
|
|
func (h *SocialMediaHandler) AddAccount(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Platform string `json:"platform"`
|
|
AccountName string `json:"account_name"`
|
|
DisplayName string `json:"display_name"`
|
|
ProfileURL string `json:"profile_url"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
|
|
"ok": false,
|
|
"error": "invalid request",
|
|
})
|
|
return
|
|
}
|
|
|
|
id := fmt.Sprintf("%s_%d", req.Platform, time.Now().Unix())
|
|
_, err := h.db.Exec(`
|
|
INSERT INTO social_media_accounts (id, platform, account_name, display_name, profile_url, is_connected)
|
|
VALUES (?, ?, ?, ?, ?, true)
|
|
`, id, req.Platform, req.AccountName, req.DisplayName, req.ProfileURL)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]interface{}{
|
|
"ok": false,
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"account": map[string]interface{}{
|
|
"id": id,
|
|
"platform": req.Platform,
|
|
"name": req.AccountName,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ListPosts listar inlägg
|
|
func (h *SocialMediaHandler) ListPosts(w http.ResponseWriter, r *http.Request) {
|
|
accountID := r.URL.Query().Get("account_id")
|
|
platform := r.URL.Query().Get("platform")
|
|
|
|
query := `
|
|
SELECT id, account_id, platform, content, media_url, likes, comments, shares, reach, posted_at, status
|
|
FROM social_media_posts
|
|
WHERE 1=1
|
|
`
|
|
var args []interface{}
|
|
|
|
if accountID != "" {
|
|
query += " AND account_id = ?"
|
|
args = append(args, accountID)
|
|
}
|
|
if platform != "" {
|
|
query += " AND platform = ?"
|
|
args = append(args, platform)
|
|
}
|
|
query += " ORDER BY posted_at DESC LIMIT 50"
|
|
|
|
rows, err := h.db.Query(query, args...)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]interface{}{
|
|
"ok": false,
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var posts []SocialMediaPost
|
|
for rows.Next() {
|
|
var p SocialMediaPost
|
|
var postedAt sql.NullTime
|
|
err := rows.Scan(
|
|
&p.ID, &p.AccountID, &p.Platform, &p.Content, &p.MediaURL,
|
|
&p.Likes, &p.Comments, &p.Shares, &p.Reach, &postedAt, &p.Status,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if postedAt.Valid {
|
|
p.PostedAt = postedAt.Time
|
|
}
|
|
posts = append(posts, p)
|
|
}
|
|
|
|
if posts == nil {
|
|
posts = []SocialMediaPost{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"posts": posts,
|
|
})
|
|
}
|