mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-10 14:15:42 +00:00
81847c7f4e
Not backported to main due to extensive i18n refactoring changes in this branch.
258 lines
7.1 KiB
Go
258 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/abhinavxd/libredesk/internal/envelope"
|
|
"github.com/abhinavxd/libredesk/internal/stringutil"
|
|
"github.com/valyala/fasthttp"
|
|
"github.com/zerodha/fastglue"
|
|
)
|
|
|
|
// handleImportAgents handles CSV upload and starts import job
|
|
func handleImportAgents(r *fastglue.Request) error {
|
|
var app = r.Context.(*App)
|
|
|
|
file, err := r.RequestCtx.FormFile("file")
|
|
if err != nil {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.required", "name", "{globals.terms.file}"), nil, envelope.InputError)
|
|
}
|
|
|
|
fileContent, err := file.Open()
|
|
if err != nil {
|
|
app.lo.Error("error opening uploaded file", "error", err)
|
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.GeneralError)
|
|
}
|
|
defer fileContent.Close()
|
|
|
|
reader := csv.NewReader(fileContent)
|
|
reader.TrimLeadingSpace = true
|
|
records, err := reader.ReadAll()
|
|
if err != nil {
|
|
app.lo.Error("error parsing CSV", "error", err)
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("validation.invalidCsvFile"), nil, envelope.InputError)
|
|
}
|
|
|
|
if len(records) < 2 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("importer.csvMustContainHeadersAndData"), nil, envelope.InputError)
|
|
}
|
|
|
|
err = app.importer.Submit("agents", func() error {
|
|
return processAgentImport(app, records)
|
|
})
|
|
|
|
if err != nil {
|
|
return r.SendErrorEnvelope(fasthttp.StatusConflict, app.i18n.T("importer.importAlreadyInProgress"), nil, envelope.GeneralError)
|
|
}
|
|
|
|
return r.SendEnvelope(true)
|
|
}
|
|
|
|
// handleGetAgentImportStatus returns current import status
|
|
func handleGetAgentImportStatus(r *fastglue.Request) error {
|
|
var app = r.Context.(*App)
|
|
status, err := app.importer.GetStatus("agents")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(status)
|
|
}
|
|
|
|
func processAgentImport(app *App, records [][]string) error {
|
|
// Parse headers
|
|
headerMap := make(map[string]int)
|
|
for i, h := range records[0] {
|
|
headerMap[strings.TrimSpace(strings.ToLower(h))] = i
|
|
}
|
|
|
|
// Validate required columns
|
|
required := []string{"first_name", "last_name", "email", "roles"}
|
|
for _, col := range required {
|
|
if _, ok := headerMap[col]; !ok {
|
|
return fmt.Errorf("missing required column: %s", col)
|
|
}
|
|
}
|
|
|
|
// Fetch valid teams and roles once
|
|
allTeams, err := app.team.GetAll()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch teams: %v", err)
|
|
}
|
|
|
|
allRoles, err := app.role.GetAll()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch roles: %v", err)
|
|
}
|
|
|
|
validTeams := make(map[string]bool)
|
|
for _, t := range allTeams {
|
|
validTeams[t.Name] = true
|
|
}
|
|
|
|
validRoles := make(map[string]bool)
|
|
for _, r := range allRoles {
|
|
validRoles[r.Name] = true
|
|
}
|
|
|
|
// Initialize import
|
|
total := len(records) - 1
|
|
app.importer.UpdateCounts("agents", total, 0, 0)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.startingImport",
|
|
"count", fmt.Sprintf("%d", total),
|
|
"type", "agents"))
|
|
|
|
// Process each row
|
|
for i, record := range records[1:] {
|
|
rowNum := i + 1
|
|
rowStr := fmt.Sprintf("%d", rowNum)
|
|
|
|
// Parse fields
|
|
firstName := getField(record, headerMap, "first_name")
|
|
lastName := getField(record, headerMap, "last_name")
|
|
email := strings.TrimSpace(strings.ToLower(getField(record, headerMap, "email")))
|
|
rolesStr := getField(record, headerMap, "roles")
|
|
teamsStr := getField(record, headerMap, "teams")
|
|
|
|
// Validate required fields
|
|
var missing []string
|
|
if firstName == "" {
|
|
missing = append(missing, "first_name")
|
|
}
|
|
if lastName == "" {
|
|
missing = append(missing, "last_name")
|
|
}
|
|
if email == "" {
|
|
missing = append(missing, "email")
|
|
}
|
|
if rolesStr == "" {
|
|
missing = append(missing, "roles")
|
|
}
|
|
if len(missing) > 0 {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.missingFields",
|
|
"row", rowStr,
|
|
"fields", strings.Join(missing, ", ")))
|
|
continue
|
|
}
|
|
|
|
// Validate email format
|
|
if !stringutil.ValidEmail(email) {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.invalidEmail",
|
|
"row", rowStr,
|
|
"email", email))
|
|
continue
|
|
}
|
|
|
|
// Parse and validate roles
|
|
roles := parseList(rolesStr)
|
|
if len(roles) == 0 {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.roleRequired",
|
|
"row", rowStr,
|
|
"email", email))
|
|
continue
|
|
}
|
|
|
|
invalidRoles := findInvalid(roles, validRoles)
|
|
if len(invalidRoles) > 0 {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.invalidRoles",
|
|
"row", rowStr,
|
|
"email", email,
|
|
"roles", strings.Join(invalidRoles, ", ")))
|
|
continue
|
|
}
|
|
|
|
// Parse and validate teams (optional)
|
|
teams := parseList(teamsStr)
|
|
if len(teams) > 0 {
|
|
invalidTeams := findInvalid(teams, validTeams)
|
|
if len(invalidTeams) > 0 {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.invalidTeams",
|
|
"row", rowStr,
|
|
"email", email,
|
|
"teams", strings.Join(invalidTeams, ", ")))
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Check if agent already exists
|
|
if _, err := app.user.GetAgent(0, email); err == nil {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.emailExists",
|
|
"row", rowStr,
|
|
"email", email))
|
|
continue
|
|
}
|
|
|
|
// Create agent
|
|
agent, err := app.user.CreateAgent(firstName, lastName, email, roles)
|
|
if err != nil {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.errorCreating",
|
|
"row", rowStr,
|
|
"email", email,
|
|
"error", err.Error()))
|
|
continue
|
|
}
|
|
|
|
// Assign teams (if provided)
|
|
if len(teams) > 0 {
|
|
if err := app.team.UpsertUserTeams(agent.ID, teams); err != nil {
|
|
app.importer.UpdateCounts("agents", 0, 0, 1)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.errorAssigningTeams",
|
|
"row", rowStr,
|
|
"email", email,
|
|
"error", err.Error()))
|
|
continue
|
|
}
|
|
}
|
|
|
|
app.importer.UpdateCounts("agents", 0, 1, 0)
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.createdAgent",
|
|
"row", rowStr,
|
|
"name", agent.FullName(),
|
|
"email", agent.Email.String))
|
|
}
|
|
|
|
// Final summary
|
|
status, _ := app.importer.GetStatus("agents")
|
|
app.importer.AddLog("agents", app.i18n.Ts("importer.importComplete",
|
|
"success", fmt.Sprintf("%d", status.Success),
|
|
"total", fmt.Sprintf("%d", status.Total),
|
|
"errors", fmt.Sprintf("%d", status.Errors)))
|
|
|
|
return nil
|
|
}
|
|
|
|
func getField(record []string, headerMap map[string]int, name string) string {
|
|
if idx, ok := headerMap[name]; ok && idx < len(record) {
|
|
return strings.TrimSpace(record[idx])
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseList(s string) []string {
|
|
var result []string
|
|
for part := range strings.SplitSeq(s, ",") {
|
|
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
|
result = append(result, trimmed)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func findInvalid(items []string, validMap map[string]bool) []string {
|
|
var invalid []string
|
|
for _, item := range items {
|
|
if !validMap[item] {
|
|
invalid = append(invalid, item)
|
|
}
|
|
}
|
|
return invalid
|
|
}
|