feat: integrate Grafana dashboards into BOC DashboardPage
- Add Infrastructure Health section with CPU/Memory/Disk panels - Add Service Status section with PM2/Docker panels - Create GrafanaPanel component for iframe embedding - Build passes successfully
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// CustomerRepository hanterar alla customer-relaterade databasoperationer
|
||||
type CustomerRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewCustomerRepository(db *sql.DB) *CustomerRepository {
|
||||
return &CustomerRepository{db: db}
|
||||
}
|
||||
|
||||
// Customer representerar en kund
|
||||
type Customer struct {
|
||||
ID string `json:"id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Company string `json:"company"`
|
||||
OrgNumber string `json:"org_number"`
|
||||
Status string `json:"status"`
|
||||
Source string `json:"source"`
|
||||
Tags []string `json:"tags"`
|
||||
AssignedTo *string `json:"assigned_to"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// CreateCustomer skapar en ny kund
|
||||
func (r *CustomerRepository) CreateCustomer(ctx context.Context, c *Customer) error {
|
||||
query := `
|
||||
INSERT INTO boc_customers (tenant_id, name, email, phone, company, org_number, status, source, tags, assigned_to)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query,
|
||||
c.TenantID, c.Name, c.Email, c.Phone, c.Company, c.OrgNumber,
|
||||
c.Status, c.Source, pq.Array(c.Tags), c.AssignedTo,
|
||||
).Scan(&c.ID, &c.CreatedAt, &c.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("create customer: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCustomer hämtar en kund med ID
|
||||
func (r *CustomerRepository) GetCustomer(ctx context.Context, id string) (*Customer, error) {
|
||||
query := `
|
||||
SELECT id, tenant_id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
|
||||
FROM boc_customers
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var c Customer
|
||||
var tags pq.StringArray
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||
&c.ID, &c.TenantID, &c.Name, &c.Email, &c.Phone, &c.Company,
|
||||
&c.OrgNumber, &c.Status, &c.Source, &tags, &c.AssignedTo,
|
||||
&c.CreatedAt, &c.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("customer not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get customer: %w", err)
|
||||
}
|
||||
|
||||
c.Tags = []string(tags)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// ListCustomers hämtar kunder med pagination
|
||||
func (r *CustomerRepository) ListCustomers(ctx context.Context, tenantID, status string, limit, offset int) ([]Customer, int, error) {
|
||||
// Hämta total count
|
||||
var total int
|
||||
countQuery := `SELECT COUNT(*) FROM boc_customers WHERE tenant_id = $1 AND status = $2`
|
||||
if err := r.db.QueryRowContext(ctx, countQuery, tenantID, status).Scan(&total); err != nil {
|
||||
return nil, 0, fmt.Errorf("count customers: %w", err)
|
||||
}
|
||||
|
||||
// Hämta kunder
|
||||
query := `
|
||||
SELECT id, tenant_id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
|
||||
FROM boc_customers
|
||||
WHERE tenant_id = $1 AND status = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $3 OFFSET $4
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, tenantID, status, limit, offset)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("list customers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var customers []Customer
|
||||
for rows.Next() {
|
||||
var c Customer
|
||||
var tags pq.StringArray
|
||||
|
||||
if err := rows.Scan(
|
||||
&c.ID, &c.TenantID, &c.Name, &c.Email, &c.Phone, &c.Company,
|
||||
&c.OrgNumber, &c.Status, &c.Source, &tags, &c.AssignedTo,
|
||||
&c.CreatedAt, &c.UpdatedAt,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c.Tags = []string(tags)
|
||||
customers = append(customers, c)
|
||||
}
|
||||
|
||||
return customers, total, rows.Err()
|
||||
}
|
||||
|
||||
// UpdateCustomer uppdaterar en kund
|
||||
func (r *CustomerRepository) UpdateCustomer(ctx context.Context, c *Customer) error {
|
||||
query := `
|
||||
UPDATE boc_customers
|
||||
SET name = $1, email = $2, phone = $3, company = $4, org_number = $5,
|
||||
status = $6, source = $7, tags = $8, assigned_to = $9, updated_at = NOW()
|
||||
WHERE id = $10
|
||||
RETURNING updated_at
|
||||
`
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query,
|
||||
c.Name, c.Email, c.Phone, c.Company, c.OrgNumber,
|
||||
c.Status, c.Source, pq.Array(c.Tags), c.AssignedTo, c.ID,
|
||||
).Scan(&c.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return fmt.Errorf("customer not found")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("update customer: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCustomer markerar en kund som borttagen (soft delete)
|
||||
func (r *CustomerRepository) DeleteCustomer(ctx context.Context, id string) error {
|
||||
query := `
|
||||
UPDATE boc_customers
|
||||
SET status = 'deleted', updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
result, err := r.db.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete customer: %w", err)
|
||||
}
|
||||
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("customer not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchCustomers söker efter kunder
|
||||
func (r *CustomerRepository) SearchCustomers(ctx context.Context, tenantID, query string, limit int) ([]Customer, error) {
|
||||
sql := `
|
||||
SELECT id, tenant_id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at
|
||||
FROM boc_customers
|
||||
WHERE tenant_id = $1
|
||||
AND status != 'deleted'
|
||||
AND (name ILIKE $2 OR email ILIKE $2 OR company ILIKE $2)
|
||||
ORDER BY name
|
||||
LIMIT $3
|
||||
`
|
||||
|
||||
searchTerm := "%" + query + "%"
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, sql, tenantID, searchTerm, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search customers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var customers []Customer
|
||||
for rows.Next() {
|
||||
var c Customer
|
||||
var tags pq.StringArray
|
||||
|
||||
if err := rows.Scan(
|
||||
&c.ID, &c.TenantID, &c.Name, &c.Email, &c.Phone, &c.Company,
|
||||
&c.OrgNumber, &c.Status, &c.Source, &tags, &c.AssignedTo,
|
||||
&c.CreatedAt, &c.UpdatedAt,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c.Tags = []string(tags)
|
||||
customers = append(customers, c)
|
||||
}
|
||||
|
||||
return customers, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user