mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-10-23 05:11:57 +00:00
- Implemented MarkdownToHTML function using goldmark for converting markdown content to HTML. - Added CleanJSONResponse function to remove markdown code blocks from LLM responses. - Updated stringutil tests to remove unnecessary test cases for empty strings and special characters. refactor: Update SQL schema for knowledge base and help center - Introduced ai_knowledge_type enum for knowledge base categorization. - Added help_center_id reference in inboxes table. - Enhanced help_centers table with default_locale column. - Changed data types from INTEGER to INT for consistency across tables. - Renamed ai_custom_answers table to ai_knowledge_base and adjusted its schema. fix: Remove unnecessary CSS filter from default icon in widget - Cleaned up widget.js by removing the brightness filter from the default icon styling.
327 lines
11 KiB
Go
327 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
_ "time/tzdata"
|
|
|
|
_ "github.com/pgvector/pgvector-go"
|
|
|
|
activitylog "github.com/abhinavxd/libredesk/internal/activity_log"
|
|
"github.com/abhinavxd/libredesk/internal/ai"
|
|
auth_ "github.com/abhinavxd/libredesk/internal/auth"
|
|
"github.com/abhinavxd/libredesk/internal/authz"
|
|
businesshours "github.com/abhinavxd/libredesk/internal/business_hours"
|
|
"github.com/abhinavxd/libredesk/internal/colorlog"
|
|
"github.com/abhinavxd/libredesk/internal/csat"
|
|
customAttribute "github.com/abhinavxd/libredesk/internal/custom_attribute"
|
|
"github.com/abhinavxd/libredesk/internal/helpcenter"
|
|
"github.com/abhinavxd/libredesk/internal/macro"
|
|
notifier "github.com/abhinavxd/libredesk/internal/notification"
|
|
"github.com/abhinavxd/libredesk/internal/report"
|
|
"github.com/abhinavxd/libredesk/internal/search"
|
|
"github.com/abhinavxd/libredesk/internal/sla"
|
|
"github.com/abhinavxd/libredesk/internal/view"
|
|
|
|
"github.com/abhinavxd/libredesk/internal/automation"
|
|
"github.com/abhinavxd/libredesk/internal/conversation"
|
|
"github.com/abhinavxd/libredesk/internal/conversation/priority"
|
|
"github.com/abhinavxd/libredesk/internal/conversation/status"
|
|
"github.com/abhinavxd/libredesk/internal/inbox"
|
|
"github.com/abhinavxd/libredesk/internal/media"
|
|
"github.com/abhinavxd/libredesk/internal/oidc"
|
|
"github.com/abhinavxd/libredesk/internal/ratelimit"
|
|
"github.com/abhinavxd/libredesk/internal/role"
|
|
"github.com/abhinavxd/libredesk/internal/setting"
|
|
"github.com/abhinavxd/libredesk/internal/tag"
|
|
"github.com/abhinavxd/libredesk/internal/team"
|
|
"github.com/abhinavxd/libredesk/internal/template"
|
|
"github.com/abhinavxd/libredesk/internal/user"
|
|
"github.com/abhinavxd/libredesk/internal/webhook"
|
|
"github.com/knadh/go-i18n"
|
|
"github.com/knadh/koanf/v2"
|
|
"github.com/knadh/stuffbin"
|
|
"github.com/valyala/fasthttp"
|
|
"github.com/zerodha/fastglue"
|
|
"github.com/zerodha/logf"
|
|
)
|
|
|
|
var (
|
|
ko = koanf.New(".")
|
|
ctx = context.Background()
|
|
appName = "libredesk"
|
|
frontendDir = "frontend/dist/main"
|
|
widgetDir = "frontend/dist/widget"
|
|
|
|
// Injected at build time.
|
|
buildString string
|
|
versionString string
|
|
)
|
|
|
|
// App is the global app context which is passed and injected in the http handlers.
|
|
type App struct {
|
|
fs stuffbin.FileSystem
|
|
consts atomic.Value
|
|
auth *auth_.Auth
|
|
authz *authz.Enforcer
|
|
i18n *i18n.I18n
|
|
lo *logf.Logger
|
|
oidc *oidc.Manager
|
|
media *media.Manager
|
|
setting *setting.Manager
|
|
role *role.Manager
|
|
user *user.Manager
|
|
team *team.Manager
|
|
status *status.Manager
|
|
priority *priority.Manager
|
|
tag *tag.Manager
|
|
inbox *inbox.Manager
|
|
tmpl *template.Manager
|
|
macro *macro.Manager
|
|
conversation *conversation.Manager
|
|
automation *automation.Engine
|
|
businessHours *businesshours.Manager
|
|
sla *sla.Manager
|
|
csat *csat.Manager
|
|
view *view.Manager
|
|
ai *ai.Manager
|
|
search *search.Manager
|
|
activityLog *activitylog.Manager
|
|
notifier *notifier.Service
|
|
customAttribute *customAttribute.Manager
|
|
report *report.Manager
|
|
webhook *webhook.Manager
|
|
rateLimit *ratelimit.Limiter
|
|
helpcenter *helpcenter.Manager
|
|
|
|
// Global state that stores data on an available app update.
|
|
update *AppUpdate
|
|
sync.Mutex
|
|
}
|
|
|
|
func main() {
|
|
// Set up signal handler.
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
// Load command line flags into Koanf.
|
|
initFlags()
|
|
|
|
// Version flag.
|
|
if ko.Bool("version") {
|
|
fmt.Println(buildString)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Build string injected at build time.
|
|
colorlog.Green("Build: %s", buildString)
|
|
|
|
// Load the config files into Koanf.
|
|
initConfig(ko)
|
|
|
|
// Init stuffbin fs.
|
|
fs := initFS()
|
|
|
|
// Init DB.
|
|
db := initDB()
|
|
|
|
// Installer.
|
|
if ko.Bool("install") {
|
|
install(ctx, db, fs, ko.Bool("idempotent-install"), !ko.Bool("yes"))
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Set system user password.
|
|
if ko.Bool("set-system-user-password") {
|
|
setSystemUserPass(ctx, db)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Check if schema is installed.
|
|
installed, err := checkSchema(db)
|
|
if err != nil {
|
|
log.Fatalf("error checking db schema: %v", err)
|
|
}
|
|
if !installed {
|
|
log.Println("database tables are missing. Use the `--install` flag to set up the database schema.")
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Upgrade.
|
|
if ko.Bool("upgrade") {
|
|
upgrade(db, fs, !ko.Bool("yes"))
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Check for pending upgrade.
|
|
checkPendingUpgrade(db)
|
|
|
|
// Load app settings from DB into the Koanf instance.
|
|
settings := initSettings(db)
|
|
loadSettings(settings)
|
|
|
|
// Fallback for config typo. Logs a warning but continues to work with the incorrect key.
|
|
// Uses 'message.message_outgoing_scan_interval' (correct key) as default key, falls back to the common typo.
|
|
msgOutgoingScanIntervalKey := "message.message_outgoing_scan_interval"
|
|
if ko.String(msgOutgoingScanIntervalKey) == "" {
|
|
if ko.String("message.message_outoing_scan_interval") != "" {
|
|
colorlog.Red("WARNING: typo in config key 'message.message_outoing_scan_interval' detected. Use 'message.message_outgoing_scan_interval' instead in your config.toml file. Support for this incorrect key will be removed in a future release.")
|
|
msgOutgoingScanIntervalKey = "message.message_outoing_scan_interval"
|
|
}
|
|
}
|
|
|
|
var (
|
|
autoAssignInterval = ko.MustDuration("autoassigner.autoassign_interval")
|
|
unsnoozeInterval = ko.MustDuration("conversation.unsnooze_interval")
|
|
automationWorkers = ko.MustInt("automation.worker_count")
|
|
messageOutgoingQWorkers = ko.MustDuration("message.outgoing_queue_workers")
|
|
messageIncomingQWorkers = ko.MustDuration("message.incoming_queue_workers")
|
|
messageOutgoingScanInterval = ko.MustDuration(msgOutgoingScanIntervalKey)
|
|
slaEvaluationInterval = ko.MustDuration("sla.evaluation_interval")
|
|
lo = initLogger(appName)
|
|
rdb = initRedis()
|
|
constants = initConstants()
|
|
i18n = initI18n(fs)
|
|
csat = initCSAT(db, i18n)
|
|
oidc = initOIDC(db, settings, i18n)
|
|
status = initStatus(db, i18n)
|
|
priority = initPriority(db, i18n)
|
|
auth = initAuth(oidc, rdb, i18n)
|
|
template = initTemplate(db, fs, constants, i18n)
|
|
media = initMedia(db, i18n)
|
|
inbox = initInbox(db, i18n)
|
|
team = initTeam(db, i18n)
|
|
businessHours = initBusinessHours(db, i18n)
|
|
webhook = initWebhook(db, i18n)
|
|
user = initUser(i18n, db)
|
|
wsHub = initWS(user)
|
|
notifier = initNotifier()
|
|
automation = initAutomationEngine(db, i18n)
|
|
sla = initSLA(db, team, settings, businessHours, notifier, template, user, i18n)
|
|
conversation = initConversations(i18n, sla, status, priority, wsHub, notifier, db, inbox, user, team, media, settings, csat, automation, template, webhook)
|
|
autoassigner = initAutoAssigner(team, user, conversation)
|
|
rateLimiter = initRateLimit(rdb)
|
|
helpcenter = initHelpCenter(db, i18n)
|
|
ai = initAI(db, i18n, conversation, helpcenter)
|
|
)
|
|
|
|
wsHub.SetConversationStore(conversation)
|
|
automation.SetConversationStore(conversation)
|
|
conversation.SetAIStore(ai)
|
|
helpcenter.SetAIStore(ai)
|
|
|
|
// Start inboxes.
|
|
startInboxes(ctx, inbox, conversation, user)
|
|
|
|
go automation.Run(ctx, automationWorkers)
|
|
go autoassigner.Run(ctx, autoAssignInterval)
|
|
go conversation.Run(ctx, messageIncomingQWorkers, messageOutgoingQWorkers, messageOutgoingScanInterval)
|
|
go conversation.RunUnsnoozer(ctx, unsnoozeInterval)
|
|
go webhook.Run(ctx)
|
|
go notifier.Run(ctx)
|
|
go sla.Run(ctx, slaEvaluationInterval)
|
|
go sla.SendNotifications(ctx)
|
|
go media.DeleteUnlinkedMedia(ctx)
|
|
go user.MonitorAgentAvailability(ctx)
|
|
go ai.StartConversationCompletions()
|
|
|
|
var app = &App{
|
|
lo: lo,
|
|
fs: fs,
|
|
sla: sla,
|
|
oidc: oidc,
|
|
i18n: i18n,
|
|
auth: auth,
|
|
media: media,
|
|
setting: settings,
|
|
inbox: inbox,
|
|
user: user,
|
|
team: team,
|
|
status: status,
|
|
priority: priority,
|
|
tmpl: template,
|
|
notifier: notifier,
|
|
consts: atomic.Value{},
|
|
conversation: conversation,
|
|
automation: automation,
|
|
businessHours: businessHours,
|
|
activityLog: initActivityLog(db, i18n),
|
|
customAttribute: initCustomAttribute(db, i18n),
|
|
authz: initAuthz(i18n),
|
|
view: initView(db),
|
|
report: initReport(db, i18n),
|
|
csat: initCSAT(db, i18n),
|
|
search: initSearch(db, i18n),
|
|
role: initRole(db, i18n),
|
|
tag: initTag(db, i18n),
|
|
macro: initMacro(db, i18n),
|
|
ai: ai,
|
|
webhook: webhook,
|
|
rateLimit: rateLimiter,
|
|
helpcenter: helpcenter,
|
|
}
|
|
app.consts.Store(constants)
|
|
|
|
g := fastglue.NewGlue()
|
|
g.SetContext(app)
|
|
initHandlers(g, wsHub)
|
|
|
|
s := &fasthttp.Server{
|
|
Name: appName,
|
|
ReadTimeout: ko.MustDuration("app.server.read_timeout"),
|
|
WriteTimeout: ko.MustDuration("app.server.write_timeout"),
|
|
MaxRequestBodySize: ko.MustInt("app.server.max_body_size"),
|
|
MaxKeepaliveDuration: ko.MustDuration("app.server.keepalive_timeout"),
|
|
ReadBufferSize: ko.Int("app.server.read_buffer_size"),
|
|
}
|
|
|
|
go func() {
|
|
colorlog.Green("Server started at %s", ko.String("app.server.address"))
|
|
if ko.String("server.socket") != "" {
|
|
colorlog.Green("Unix socket created at %s", ko.String("server.socket"))
|
|
}
|
|
if err := g.ListenAndServe(ko.String("app.server.address"), ko.String("server.socket"), s); err != nil {
|
|
log.Fatalf("error starting server: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Start the app update checker.
|
|
if ko.Bool("app.check_updates") {
|
|
go checkUpdates(versionString, time.Hour*1, app)
|
|
}
|
|
|
|
// Wait for shutdown signal.
|
|
<-ctx.Done()
|
|
colorlog.Red("Shutting down HTTP server...")
|
|
s.Shutdown()
|
|
colorlog.Red("Shutting down inboxes...")
|
|
inbox.Close()
|
|
colorlog.Red("Shutting down automation...")
|
|
automation.Close()
|
|
colorlog.Red("Shutting down autoassigner...")
|
|
autoassigner.Close()
|
|
colorlog.Red("Shutting down notifier...")
|
|
notifier.Close()
|
|
colorlog.Red("Shutting down webhook...")
|
|
webhook.Close()
|
|
colorlog.Red("Shutting down conversation...")
|
|
conversation.Close()
|
|
colorlog.Red("Shutting down AI...")
|
|
app.ai.StopConversationCompletions()
|
|
colorlog.Red("Shutting down SLA...")
|
|
sla.Close()
|
|
colorlog.Red("Shutting down database...")
|
|
db.Close()
|
|
colorlog.Red("Shutting down redis...")
|
|
rdb.Close()
|
|
colorlog.Green("Shutdown complete.")
|
|
}
|