security: Add proper authentication, RBAC, and tenant isolation
- 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
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user