mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-05 14:35:18 +00:00
Compare commits
31 Commits
feat/api-u
...
v0.7.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b971619ea6 | ||
|
|
69accaebef | ||
|
|
27de73536e | ||
|
|
df108a3363 | ||
|
|
266c3dab72 | ||
|
|
bf2c1fff6f | ||
|
|
2930af0c4f | ||
|
|
389c4e3dd3 | ||
|
|
9a119e6dc3 | ||
|
|
ee178d383d | ||
|
|
fc4db676d9 | ||
|
|
70cb3d0f80 | ||
|
|
c9920c3377 | ||
|
|
6d62c3a4ba | ||
|
|
d9b5fb8f0f | ||
|
|
3de320f1fb | ||
|
|
be977dcff2 | ||
|
|
5e19f13e18 | ||
|
|
ccc5940dd9 | ||
|
|
4203b82e90 | ||
|
|
ba07e224c2 | ||
|
|
3fff65150f | ||
|
|
c4fcf6bd91 | ||
|
|
5ea1b9e84c | ||
|
|
5b522888bc | ||
|
|
dc2250ce50 | ||
|
|
839a06f0d2 | ||
|
|
d2e5d85e3a | ||
|
|
0737d22374 | ||
|
|
d6af9d10ea | ||
|
|
6381fc23c2 |
16
.github/ISSUE_TEMPLATE/confirmed-bug.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/confirmed-bug.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Confirmed Bug Report
|
||||||
|
about: Report a confirmed bug in Libredesk
|
||||||
|
title: "[Bug] <brief summary>"
|
||||||
|
labels: bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:**
|
||||||
|
- libredesk: [eg: v0.7.0]
|
||||||
|
|
||||||
|
**Description of the bug and steps to reproduce:**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Logs / Screenshots:**
|
||||||
|
Attach any relevant logs or screenshots to help diagnose the issue.
|
||||||
16
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Possible Bug Report
|
||||||
|
about: Something in Libredesk might be broken but needs confirmation
|
||||||
|
title: "[Possible Bug] <brief summary>"
|
||||||
|
labels: bug, needs-investigation
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:**
|
||||||
|
- libredesk: [eg: v0.7.0]
|
||||||
|
|
||||||
|
**Description of the bug and steps to reproduce:**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Logs / Screenshots:**
|
||||||
|
Attach any relevant logs or screenshots to help diagnose the issue.
|
||||||
2
.github/workflows/crowdin.yml
vendored
2
.github/workflows/crowdin.yml
vendored
@@ -12,6 +12,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
crowdin:
|
crowdin:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Only run on the original repository, not forks
|
||||||
|
if: github.event.repository.fork == false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Visit [libredesk.io](https://libredesk.io) for more info. Check out the [**Live
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Multi Shared Inbox**
|
- **Multi Shared Inbox**
|
||||||
Libredesk supports multiple shares inboxes, letting you manage conversations across teams effortlessly.
|
Libredesk supports multiple shared inboxes, letting you manage conversations across teams effortlessly.
|
||||||
- **Granular Permissions**
|
- **Granular Permissions**
|
||||||
Create custom roles with granular permissions for teams and individual agents.
|
Create custom roles with granular permissions for teams and individual agents.
|
||||||
- **Smart Automation**
|
- **Smart Automation**
|
||||||
@@ -85,6 +85,11 @@ __________________
|
|||||||
## Developers
|
## Developers
|
||||||
If you are interested in contributing, refer to the [developer setup](https://libredesk.io/docs/developer-setup/). The backend is written in Go and the frontend is Vue js 3 with Shadcn for UI components.
|
If you are interested in contributing, refer to the [developer setup](https://libredesk.io/docs/developer-setup/). The backend is written in Go and the frontend is Vue js 3 with Shadcn for UI components.
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
|
||||||
|
Libredesk is under active development.
|
||||||
|
Track roadmap and progress on the GitHub Project Board: [https://github.com/users/abhinavxd/projects/1](https://github.com/users/abhinavxd/projects/1)
|
||||||
|
|
||||||
|
|
||||||
## Translators
|
## Translators
|
||||||
You can help translate Libredesk into your language on [Crowdin](https://crowdin.com/project/libredesk).
|
You can help translate Libredesk into your language on [Crowdin](https://crowdin.com/project/libredesk).
|
||||||
|
|||||||
@@ -45,10 +45,11 @@ func handleToggleAutomationRule(r *fastglue.Request) error {
|
|||||||
app = r.Context.(*App)
|
app = r.Context.(*App)
|
||||||
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
||||||
)
|
)
|
||||||
if err := app.automation.ToggleRule(id); err != nil {
|
toggledRule, err := app.automation.ToggleRule(id)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(toggledRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateAutomationRule updates an automation rule
|
// handleUpdateAutomationRule updates an automation rule
|
||||||
@@ -66,10 +67,11 @@ func handleUpdateAutomationRule(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = app.automation.UpdateRule(id, rule); err != nil {
|
updatedRule, err := app.automation.UpdateRule(id, rule)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleCreateAutomationRule creates a new automation rule
|
// handleCreateAutomationRule creates a new automation rule
|
||||||
@@ -81,10 +83,11 @@ func handleCreateAutomationRule(r *fastglue.Request) error {
|
|||||||
if err := r.Decode(&rule, "json"); err != nil {
|
if err := r.Decode(&rule, "json"); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.automation.CreateRule(rule); err != nil {
|
createdRule, err := app.automation.CreateRule(rule)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteAutomationRule deletes an automation rule
|
// handleDeleteAutomationRule deletes an automation rule
|
||||||
|
|||||||
@@ -55,11 +55,12 @@ func handleCreateBusinessHours(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.businessHours.Create(businessHours.Name, businessHours.Description, businessHours.IsAlwaysOpen, businessHours.Hours, businessHours.Holidays); err != nil {
|
createdBusinessHours, err := app.businessHours.Create(businessHours.Name, businessHours.Description, businessHours.IsAlwaysOpen, businessHours.Hours, businessHours.Holidays)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdBusinessHours)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteBusinessHour deletes the business hour with the given id.
|
// handleDeleteBusinessHour deletes the business hour with the given id.
|
||||||
@@ -93,8 +94,9 @@ func handleUpdateBusinessHours(r *fastglue.Request) error {
|
|||||||
if businessHours.Name == "" {
|
if businessHours.Name == "" {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.businessHours.Update(id, businessHours.Name, businessHours.Description, businessHours.IsAlwaysOpen, businessHours.Hours, businessHours.Holidays); err != nil {
|
updatedBusinessHours, err := app.businessHours.Update(id, businessHours.Name, businessHours.Description, businessHours.IsAlwaysOpen, businessHours.Hours, businessHours.Holidays)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedBusinessHours)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ func handleCreateConversation(r *fastglue.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send reply to the created conversation.
|
// Send reply to the created conversation.
|
||||||
if err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil {
|
if _, err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil {
|
||||||
// Delete the conversation if reply fails.
|
// Delete the conversation if reply fails.
|
||||||
if err := app.conversation.DeleteConversation(conversationUUID); err != nil {
|
if err := app.conversation.DeleteConversation(conversationUUID); err != nil {
|
||||||
app.lo.Error("error deleting conversation", "error", err)
|
app.lo.Error("error deleting conversation", "error", err)
|
||||||
|
|||||||
@@ -70,10 +70,11 @@ func handleCreateCustomAttribute(r *fastglue.Request) error {
|
|||||||
if err := validateCustomAttribute(app, attribute); err != nil {
|
if err := validateCustomAttribute(app, attribute); err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
if err := app.customAttribute.Create(attribute); err != nil {
|
createdAttr, err := app.customAttribute.Create(attribute)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdAttr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateCustomAttribute updates an existing custom attribute in the database.
|
// handleUpdateCustomAttribute updates an existing custom attribute in the database.
|
||||||
@@ -92,10 +93,11 @@ func handleUpdateCustomAttribute(r *fastglue.Request) error {
|
|||||||
if err := validateCustomAttribute(app, attribute); err != nil {
|
if err := validateCustomAttribute(app, attribute); err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
if err = app.customAttribute.Update(id, attribute); err != nil {
|
updatedAttr, err := app.customAttribute.Update(id, attribute)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedAttr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteCustomAttribute deletes a custom attribute from the database.
|
// handleDeleteCustomAttribute deletes a custom attribute from the database.
|
||||||
|
|||||||
@@ -47,11 +47,12 @@ func handleCreateInbox(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), err.Error(), envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), err.Error(), envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.inbox.Create(inbox); err != nil {
|
createdInbox, err := app.inbox.Create(inbox)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateInbox(app, inbox); err != nil {
|
if err := validateInbox(app, createdInbox); err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +60,13 @@ func handleCreateInbox(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
// Clear passwords before returning.
|
||||||
|
if err := createdInbox.ClearPasswords(); err != nil {
|
||||||
|
app.lo.Error("error clearing inbox passwords from response", "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.inbox}"), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.SendEnvelope(createdInbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateInbox updates an inbox
|
// handleUpdateInbox updates an inbox
|
||||||
@@ -82,7 +89,7 @@ func handleUpdateInbox(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.inbox.Update(id, inbox)
|
updatedInbox, err := app.inbox.Update(id, inbox)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
@@ -91,7 +98,13 @@ func handleUpdateInbox(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(inbox)
|
// Clear passwords before returning.
|
||||||
|
if err := updatedInbox.ClearPasswords(); err != nil {
|
||||||
|
app.lo.Error("error clearing inbox passwords from response", "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.inbox}"), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.SendEnvelope(updatedInbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleToggleInbox toggles an inbox
|
// handleToggleInbox toggles an inbox
|
||||||
@@ -105,7 +118,8 @@ func handleToggleInbox(r *fastglue.Request) error {
|
|||||||
app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = app.inbox.Toggle(id); err != nil {
|
toggledInbox, err := app.inbox.Toggle(id)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +127,13 @@ func handleToggleInbox(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "{globals.terms.inbox}"), nil, envelope.GeneralError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
// Clear passwords before returning
|
||||||
|
if err := toggledInbox.ClearPasswords(); err != nil {
|
||||||
|
app.lo.Error("error clearing inbox passwords from response", "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.inbox}"), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.SendEnvelope(toggledInbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteInbox deletes an inbox
|
// handleDeleteInbox deletes an inbox
|
||||||
|
|||||||
10
cmd/macro.go
10
cmd/macro.go
@@ -81,11 +81,12 @@ func handleCreateMacro(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.macro.Create(macro.Name, macro.MessageContent, macro.UserID, macro.TeamID, macro.Visibility, macro.VisibleWhen, macro.Actions); err != nil {
|
createdMacro, err := app.macro.Create(macro.Name, macro.MessageContent, macro.UserID, macro.TeamID, macro.Visibility, macro.VisibleWhen, macro.Actions)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(macro)
|
return r.SendEnvelope(createdMacro)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateMacro updates a macro.
|
// handleUpdateMacro updates a macro.
|
||||||
@@ -109,11 +110,12 @@ func handleUpdateMacro(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = app.macro.Update(id, macro.Name, macro.MessageContent, macro.UserID, macro.TeamID, macro.Visibility, macro.VisibleWhen, macro.Actions); err != nil {
|
updatedMacro, err := app.macro.Update(id, macro.Name, macro.MessageContent, macro.UserID, macro.TeamID, macro.Visibility, macro.VisibleWhen, macro.Actions)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(macro)
|
return r.SendEnvelope(updatedMacro)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteMacro deletes macro.
|
// handleDeleteMacro deletes macro.
|
||||||
|
|||||||
@@ -162,13 +162,15 @@ func handleSendMessage(r *fastglue.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Private {
|
if req.Private {
|
||||||
if err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message); err != nil {
|
message, err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
} else {
|
return r.SendEnvelope(message)
|
||||||
if err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/); err != nil {
|
}
|
||||||
|
message, err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
}
|
return r.SendEnvelope(message)
|
||||||
return r.SendEnvelope(true)
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
cmd/oidc.go
18
cmd/oidc.go
@@ -65,7 +65,8 @@ func handleCreateOIDC(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.oidc.Create(req); err != nil {
|
createdOIDC, err := app.oidc.Create(req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +74,11 @@ func handleCreateOIDC(r *fastglue.Request) error {
|
|||||||
if err := reloadAuth(app); err != nil {
|
if err := reloadAuth(app); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "OIDC"), nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "OIDC"), nil, envelope.GeneralError)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope("OIDC created successfully")
|
|
||||||
|
// Clear client secret before returning
|
||||||
|
createdOIDC.ClientSecret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
return r.SendEnvelope(createdOIDC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateOIDC updates an OIDC record.
|
// handleUpdateOIDC updates an OIDC record.
|
||||||
@@ -96,7 +101,8 @@ func handleUpdateOIDC(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = app.oidc.Update(id, req); err != nil {
|
updatedOIDC, err := app.oidc.Update(id, req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +110,11 @@ func handleUpdateOIDC(r *fastglue.Request) error {
|
|||||||
if err := reloadAuth(app); err != nil {
|
if err := reloadAuth(app); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "OIDC"), nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.couldNotReload", "name", "OIDC"), nil, envelope.GeneralError)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
|
||||||
|
// Clear client secret before returning
|
||||||
|
updatedOIDC.ClientSecret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
return r.SendEnvelope(updatedOIDC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteOIDC deletes an OIDC record.
|
// handleDeleteOIDC deletes an OIDC record.
|
||||||
|
|||||||
10
cmd/roles.go
10
cmd/roles.go
@@ -55,10 +55,11 @@ func handleCreateRole(r *fastglue.Request) error {
|
|||||||
if err := r.Decode(&req, "json"); err != nil {
|
if err := r.Decode(&req, "json"); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.role.Create(req); err != nil {
|
createdRole, err := app.role.Create(req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateRole updates a role
|
// handleUpdateRole updates a role
|
||||||
@@ -71,8 +72,9 @@ func handleUpdateRole(r *fastglue.Request) error {
|
|||||||
if err := r.Decode(&req, "json"); err != nil {
|
if err := r.Decode(&req, "json"); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.role.Update(id, req); err != nil {
|
updatedRole, err := app.role.Update(id, req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedRole)
|
||||||
}
|
}
|
||||||
|
|||||||
10
cmd/sla.go
10
cmd/sla.go
@@ -54,11 +54,12 @@ func handleCreateSLA(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.sla.Create(sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications); err != nil {
|
createdSLA, err := app.sla.Create(sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope("SLA created successfully.")
|
return r.SendEnvelope(createdSLA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateSLA updates the SLA with the given ID.
|
// handleUpdateSLA updates the SLA with the given ID.
|
||||||
@@ -81,11 +82,12 @@ func handleUpdateSLA(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.sla.Update(id, sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications); err != nil {
|
updatedSLA, err := app.sla.Update(id, sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedSLA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteSLA deletes the SLA with the given ID.
|
// handleDeleteSLA deletes the SLA with the given ID.
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ func handleCreateStatus(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.status.Create(status.Name)
|
createdStatus, err := app.status.Create(status.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteStatus(r *fastglue.Request) error {
|
func handleDeleteStatus(r *fastglue.Request) error {
|
||||||
@@ -74,10 +74,10 @@ func handleUpdateStatus(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.status.Update(id, status.Name)
|
updatedStatus, err := app.status.Update(id, status.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedStatus)
|
||||||
}
|
}
|
||||||
|
|||||||
10
cmd/tags.go
10
cmd/tags.go
@@ -35,11 +35,12 @@ func handleCreateTag(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.tag.Create(tag.Name); err != nil {
|
createdTag, err := app.tag.Create(tag.Name)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteTag deletes a tag from the database.
|
// handleDeleteTag deletes a tag from the database.
|
||||||
@@ -78,9 +79,10 @@ func handleUpdateTag(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = app.tag.Update(id, tag.Name); err != nil {
|
updatedTag, err := app.tag.Update(id, tag.Name)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedTag)
|
||||||
}
|
}
|
||||||
|
|||||||
10
cmd/teams.go
10
cmd/teams.go
@@ -60,10 +60,11 @@ func handleCreateTeam(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil))
|
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.team.Create(req.Name, req.Timezone, req.ConversationAssignmentType, req.BusinessHoursID, req.SLAPolicyID, req.Emoji.String, req.MaxAutoAssignedConversations); err != nil {
|
createdTeam, err := app.team.Create(req.Name, req.Timezone, req.ConversationAssignmentType, req.BusinessHoursID, req.SLAPolicyID, req.Emoji.String, req.MaxAutoAssignedConversations)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdTeam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateTeam updates an existing team.
|
// handleUpdateTeam updates an existing team.
|
||||||
@@ -82,10 +83,11 @@ func handleUpdateTeam(r *fastglue.Request) error {
|
|||||||
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil))
|
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.team.Update(id, req.Name, req.Timezone, req.ConversationAssignmentType, req.BusinessHoursID, req.SLAPolicyID, req.Emoji.String, req.MaxAutoAssignedConversations); err != nil {
|
updatedTeam, err := app.team.Update(id, req.Name, req.Timezone, req.ConversationAssignmentType, req.BusinessHoursID, req.SLAPolicyID, req.Emoji.String, req.MaxAutoAssignedConversations);
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedTeam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteTeam deletes a team
|
// handleDeleteTeam deletes a team
|
||||||
|
|||||||
@@ -53,10 +53,11 @@ func handleCreateTemplate(r *fastglue.Request) error {
|
|||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.tmpl.Create(req); err != nil {
|
template, err := app.tmpl.Create(req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateTemplate updates a template.
|
// handleUpdateTemplate updates a template.
|
||||||
@@ -76,10 +77,11 @@ func handleUpdateTemplate(r *fastglue.Request) error {
|
|||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err = app.tmpl.Update(id, req); err != nil {
|
updatedTemplate, err := app.tmpl.Update(id, req)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteTemplate deletes a template.
|
// handleDeleteTemplate deletes a template.
|
||||||
|
|||||||
10
cmd/views.go
10
cmd/views.go
@@ -47,10 +47,11 @@ func handleCreateUserView(r *fastglue.Request) error {
|
|||||||
if string(view.Filters) == "" {
|
if string(view.Filters) == "" {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`Filters`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`Filters`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
if err := app.view.Create(view.Name, view.Filters, user.ID); err != nil {
|
createdView, err := app.view.Create(view.Name, view.Filters, user.ID)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(createdView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteUserView deletes a view for a user.
|
// handleDeleteUserView deletes a view for a user.
|
||||||
@@ -111,8 +112,9 @@ func handleUpdateUserView(r *fastglue.Request) error {
|
|||||||
if v.UserID != user.ID {
|
if v.UserID != user.ID {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.Ts("globals.messages.denied", "name", "{globals.terms.permission}"), nil, envelope.PermissionError)
|
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.Ts("globals.messages.denied", "name", "{globals.terms.permission}"), nil, envelope.PermissionError)
|
||||||
}
|
}
|
||||||
if err = app.view.Update(id, view.Name, view.Filters); err != nil {
|
updatedView, err := app.view.Update(id, view.Name, view.Filters)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(true)
|
return r.SendEnvelope(updatedView)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,12 +67,15 @@ func handleCreateWebhook(r *fastglue.Request) error {
|
|||||||
return r.SendEnvelope(err)
|
return r.SendEnvelope(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := app.webhook.Create(webhook)
|
webhook, err := app.webhook.Create(webhook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
// Clear secret before returning
|
||||||
|
webhook.Secret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
return r.SendEnvelope(webhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateWebhook updates an existing webhook in the database.
|
// handleUpdateWebhook updates an existing webhook in the database.
|
||||||
@@ -105,11 +108,15 @@ func handleUpdateWebhook(r *fastglue.Request) error {
|
|||||||
webhook.Secret = existingWebhook.Secret
|
webhook.Secret = existingWebhook.Secret
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.webhook.Update(id, webhook); err != nil {
|
updatedWebhook, err := app.webhook.Update(id, webhook)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
// Clear secret before returning
|
||||||
|
updatedWebhook.Secret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
return r.SendEnvelope(updatedWebhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDeleteWebhook deletes a webhook from the database.
|
// handleDeleteWebhook deletes a webhook from the database.
|
||||||
@@ -140,11 +147,15 @@ func handleToggleWebhook(r *fastglue.Request) error {
|
|||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.webhook.Toggle(id); err != nil {
|
toggledWebhook, err := app.webhook.Toggle(id)
|
||||||
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.SendEnvelope(true)
|
// Clear secret before returning
|
||||||
|
toggledWebhook.Secret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
return r.SendEnvelope(toggledWebhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleTestWebhook sends a test payload to a webhook.
|
// handleTestWebhook sends a test payload to a webhook.
|
||||||
|
|||||||
30
docs/docs/api-getting-started.md
Normal file
30
docs/docs/api-getting-started.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# API getting started
|
||||||
|
|
||||||
|
You can access the Libredesk API to interact with your instance programmatically.
|
||||||
|
|
||||||
|
## Generating API keys
|
||||||
|
|
||||||
|
1. **Edit agent**: Go to Admin → Teammate → Agent → Edit
|
||||||
|
2. **Generate new API key**: An API Key and API Secret will be generated for the agent
|
||||||
|
3. **Save the credentials**: Keep both the API Key and API Secret secure
|
||||||
|
4. **Key management**: You can revoke / regenerate API keys at any time from the same page
|
||||||
|
|
||||||
|
## Using the API
|
||||||
|
|
||||||
|
LibreDesk supports two authentication schemes:
|
||||||
|
|
||||||
|
### Basic authentication
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://your-libredesk-instance.com/api/endpoint" \
|
||||||
|
-H "Authorization: Basic <base64_encoded_key:secret>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token authentication
|
||||||
|
```bash
|
||||||
|
curl -X GET "https://your-libredesk-instance.com/api/endpoint" \
|
||||||
|
-H "Authorization: token your_api_key:your_api_secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
Complete API documentation with available endpoints and examples coming soon.
|
||||||
@@ -210,7 +210,7 @@ Triggered when an existing message is updated.
|
|||||||
|
|
||||||
## Delivery and Retries
|
## Delivery and Retries
|
||||||
|
|
||||||
- Webhooks are delivered with a 10-second timeout
|
- Webhooks requests timeout can be configured in the `config.toml` file
|
||||||
- Failed deliveries are not automatically retried
|
- Failed deliveries are not automatically retried
|
||||||
- Webhook delivery runs in a background worker pool for better performance
|
- Webhook delivery runs in a background worker pool for better performance
|
||||||
- If the webhook queue is full (configurable in config.toml file), new events may be dropped
|
- If the webhook queue is full (configurable in config.toml file), new events may be dropped
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ nav:
|
|||||||
- Email Templates: templating.md
|
- Email Templates: templating.md
|
||||||
- SSO Setup: sso.md
|
- SSO Setup: sso.md
|
||||||
- Webhooks: webhooks.md
|
- Webhooks: webhooks.md
|
||||||
|
- API Getting Started: api-getting-started.md
|
||||||
- Contributions:
|
- Contributions:
|
||||||
- Developer Setup: developer-setup.md
|
- Developer Setup: developer-setup.md
|
||||||
- Translate Libredesk: translations.md
|
- Translate Libredesk: translations.md
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@internationalized/date": "^3.5.5",
|
"@internationalized/date": "^3.5.5",
|
||||||
"@radix-icons/vue": "^1.0.0",
|
"@radix-icons/vue": "^1.0.0",
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"codeflask": "^1.4.1",
|
"codemirror": "^6.0.2",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"lucide-vue-next": "^0.378.0",
|
"lucide-vue-next": "^0.378.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
|
|||||||
210
frontend/pnpm-lock.yaml
generated
210
frontend/pnpm-lock.yaml
generated
@@ -8,6 +8,12 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@codemirror/lang-html':
|
||||||
|
specifier: ^6.4.9
|
||||||
|
version: 6.4.9
|
||||||
|
'@codemirror/theme-one-dark':
|
||||||
|
specifier: ^6.1.3
|
||||||
|
version: 6.1.3
|
||||||
'@formkit/auto-animate':
|
'@formkit/auto-animate':
|
||||||
specifier: ^0.8.2
|
specifier: ^0.8.2
|
||||||
version: 0.8.2
|
version: 0.8.2
|
||||||
@@ -74,9 +80,9 @@ importers:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
codeflask:
|
codemirror:
|
||||||
specifier: ^1.4.1
|
specifier: ^6.0.2
|
||||||
version: 1.4.1
|
version: 6.0.2
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
@@ -234,6 +240,39 @@ packages:
|
|||||||
'@bassist/utils@0.4.0':
|
'@bassist/utils@0.4.0':
|
||||||
resolution: {integrity: sha512-aoFTl0jUjm8/tDZodP41wnEkvB+C5O9NFCuYN/ztL6jSUSsuBkXq90/1ifBm1XhV/zySHgLYlU1+tgo3XtQ+nA==}
|
resolution: {integrity: sha512-aoFTl0jUjm8/tDZodP41wnEkvB+C5O9NFCuYN/ztL6jSUSsuBkXq90/1ifBm1XhV/zySHgLYlU1+tgo3XtQ+nA==}
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.18.6':
|
||||||
|
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
||||||
|
|
||||||
|
'@codemirror/commands@6.8.1':
|
||||||
|
resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
|
||||||
|
|
||||||
|
'@codemirror/lang-css@6.3.1':
|
||||||
|
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
||||||
|
|
||||||
|
'@codemirror/lang-html@6.4.9':
|
||||||
|
resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
|
||||||
|
|
||||||
|
'@codemirror/lang-javascript@6.2.4':
|
||||||
|
resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
|
||||||
|
|
||||||
|
'@codemirror/language@6.11.1':
|
||||||
|
resolution: {integrity: sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==}
|
||||||
|
|
||||||
|
'@codemirror/lint@6.8.5':
|
||||||
|
resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==}
|
||||||
|
|
||||||
|
'@codemirror/search@6.5.11':
|
||||||
|
resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==}
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.2':
|
||||||
|
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
|
||||||
|
|
||||||
|
'@codemirror/view@6.37.2':
|
||||||
|
resolution: {integrity: sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==}
|
||||||
|
|
||||||
'@colors/colors@1.5.0':
|
'@colors/colors@1.5.0':
|
||||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||||
engines: {node: '>=0.1.90'}
|
engines: {node: '>=0.1.90'}
|
||||||
@@ -508,6 +547,24 @@ packages:
|
|||||||
'@juggle/resize-observer@3.4.0':
|
'@juggle/resize-observer@3.4.0':
|
||||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||||
|
|
||||||
|
'@lezer/common@1.2.3':
|
||||||
|
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||||
|
|
||||||
|
'@lezer/css@1.2.1':
|
||||||
|
resolution: {integrity: sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==}
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.1':
|
||||||
|
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
|
||||||
|
|
||||||
|
'@lezer/html@1.3.10':
|
||||||
|
resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==}
|
||||||
|
|
||||||
|
'@lezer/javascript@1.5.1':
|
||||||
|
resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==}
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.2':
|
||||||
|
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
|
||||||
|
|
||||||
'@mapbox/geojson-rewind@0.5.2':
|
'@mapbox/geojson-rewind@0.5.2':
|
||||||
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
|
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -535,6 +592,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
|
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2':
|
||||||
|
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1109,9 +1169,6 @@ packages:
|
|||||||
'@types/pbf@3.0.5':
|
'@types/pbf@3.0.5':
|
||||||
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
||||||
|
|
||||||
'@types/prismjs@1.26.5':
|
|
||||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
|
||||||
|
|
||||||
'@types/sinonjs__fake-timers@8.1.1':
|
'@types/sinonjs__fake-timers@8.1.1':
|
||||||
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
|
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
|
||||||
|
|
||||||
@@ -1532,8 +1589,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
codeflask@1.4.1:
|
codemirror@6.0.2:
|
||||||
resolution: {integrity: sha512-4vb2IbE/iwvP0Uubhd2ixVeysm3KNC2pl7SoDaisxq1juhZzvap3qbaX7B2CtpQVvv5V9sjcQK8hO0eTcY0V9Q==}
|
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
@@ -2780,10 +2837,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
prismjs@1.29.0:
|
|
||||||
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
process@0.11.10:
|
process@0.11.10:
|
||||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
@@ -3093,6 +3146,9 @@ packages:
|
|||||||
striptags@3.2.0:
|
striptags@3.2.0:
|
||||||
resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==}
|
resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==}
|
||||||
|
|
||||||
|
style-mod@4.1.2:
|
||||||
|
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
|
||||||
|
|
||||||
stylis@4.2.0:
|
stylis@4.2.0:
|
||||||
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||||
|
|
||||||
@@ -3550,6 +3606,89 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@withtypes/mime': 0.1.2
|
'@withtypes/mime': 0.1.2
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.18.6':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
|
||||||
|
'@codemirror/commands@6.8.1':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
|
||||||
|
'@codemirror/lang-css@6.3.1':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.18.6
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/css': 1.2.1
|
||||||
|
|
||||||
|
'@codemirror/lang-html@6.4.9':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.18.6
|
||||||
|
'@codemirror/lang-css': 6.3.1
|
||||||
|
'@codemirror/lang-javascript': 6.2.4
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/css': 1.2.1
|
||||||
|
'@lezer/html': 1.3.10
|
||||||
|
|
||||||
|
'@codemirror/lang-javascript@6.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.18.6
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/lint': 6.8.5
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/javascript': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/language@6.11.1':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
'@lezer/lr': 1.4.2
|
||||||
|
style-mod: 4.1.2
|
||||||
|
|
||||||
|
'@codemirror/lint@6.8.5':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/search@6.5.11':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.2':
|
||||||
|
dependencies:
|
||||||
|
'@marijn/find-cluster-break': 1.0.2
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
|
||||||
|
'@codemirror/view@6.37.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
crelt: 1.0.6
|
||||||
|
style-mod: 4.1.2
|
||||||
|
w3c-keyname: 2.2.8
|
||||||
|
|
||||||
'@colors/colors@1.5.0':
|
'@colors/colors@1.5.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3815,6 +3954,34 @@ snapshots:
|
|||||||
|
|
||||||
'@juggle/resize-observer@3.4.0': {}
|
'@juggle/resize-observer@3.4.0': {}
|
||||||
|
|
||||||
|
'@lezer/common@1.2.3': {}
|
||||||
|
|
||||||
|
'@lezer/css@1.2.1':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
'@lezer/lr': 1.4.2
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.1':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
|
||||||
|
'@lezer/html@1.3.10':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
'@lezer/lr': 1.4.2
|
||||||
|
|
||||||
|
'@lezer/javascript@1.5.1':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
'@lezer/lr': 1.4.2
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.2':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
|
||||||
'@mapbox/geojson-rewind@0.5.2':
|
'@mapbox/geojson-rewind@0.5.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stream: 6.0.1
|
get-stream: 6.0.1
|
||||||
@@ -3836,6 +4003,8 @@ snapshots:
|
|||||||
|
|
||||||
'@mapbox/whoots-js@3.1.0': {}
|
'@mapbox/whoots-js@3.1.0': {}
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2': {}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@@ -4378,8 +4547,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/pbf@3.0.5': {}
|
'@types/pbf@3.0.5': {}
|
||||||
|
|
||||||
'@types/prismjs@1.26.5': {}
|
|
||||||
|
|
||||||
'@types/sinonjs__fake-timers@8.1.1': {}
|
'@types/sinonjs__fake-timers@8.1.1': {}
|
||||||
|
|
||||||
'@types/sizzle@2.3.9': {}
|
'@types/sizzle@2.3.9': {}
|
||||||
@@ -4906,10 +5073,15 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
codeflask@1.4.1:
|
codemirror@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prismjs': 1.26.5
|
'@codemirror/autocomplete': 6.18.6
|
||||||
prismjs: 1.29.0
|
'@codemirror/commands': 6.8.1
|
||||||
|
'@codemirror/language': 6.11.1
|
||||||
|
'@codemirror/lint': 6.8.5
|
||||||
|
'@codemirror/search': 6.5.11
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.37.2
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6200,8 +6372,6 @@ snapshots:
|
|||||||
|
|
||||||
pretty-bytes@5.6.0: {}
|
pretty-bytes@5.6.0: {}
|
||||||
|
|
||||||
prismjs@1.29.0: {}
|
|
||||||
|
|
||||||
process@0.11.10: {}
|
process@0.11.10: {}
|
||||||
|
|
||||||
prosemirror-changeset@2.2.1:
|
prosemirror-changeset@2.2.1:
|
||||||
@@ -6601,6 +6771,8 @@ snapshots:
|
|||||||
|
|
||||||
striptags@3.2.0: {}
|
striptags@3.2.0: {}
|
||||||
|
|
||||||
|
style-mod@4.1.2: {}
|
||||||
|
|
||||||
stylis@4.2.0: {}
|
stylis@4.2.0: {}
|
||||||
|
|
||||||
stylus@0.57.0:
|
stylus@0.57.0:
|
||||||
|
|||||||
@@ -207,10 +207,6 @@
|
|||||||
}
|
}
|
||||||
// End Scrollbar
|
// End Scrollbar
|
||||||
|
|
||||||
.code-editor {
|
|
||||||
@apply rounded border shadow h-[65vh] min-h-[250px] w-full relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-quoted-text {
|
.show-quoted-text {
|
||||||
blockquote {
|
blockquote {
|
||||||
@apply block;
|
@apply block;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="codeEditor" id="code-editor" class="code-editor" />
|
<div ref="codeEditor" @click="editorView?.focus()" class="w-full h-[28rem] border rounded-md" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
import { ref, onMounted, watch, nextTick, useTemplateRef } from 'vue'
|
||||||
import CodeFlask from 'codeflask'
|
import { EditorView, basicSetup } from 'codemirror'
|
||||||
|
import { html } from '@codemirror/lang-html'
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark'
|
||||||
|
import { useColorMode } from '@vueuse/core'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: '' },
|
modelValue: { type: String, default: '' },
|
||||||
@@ -13,45 +16,38 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const codeEditor = ref(null)
|
|
||||||
const data = ref('')
|
const data = ref('')
|
||||||
const flask = ref(null)
|
let editorView = null
|
||||||
|
const codeEditor = useTemplateRef('codeEditor')
|
||||||
|
|
||||||
const initCodeEditor = (body) => {
|
const initCodeEditor = (body) => {
|
||||||
const el = document.createElement('code-flask')
|
const isDark = useColorMode().value === 'dark'
|
||||||
el.attachShadow({ mode: 'open' })
|
|
||||||
el.shadowRoot.innerHTML = `
|
|
||||||
<style>
|
|
||||||
.codeflask .codeflask__flatten {
|
|
||||||
font-size: 15px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.codeflask .codeflask__lines { background: #fafafa; z-index: 10; }
|
|
||||||
.codeflask .token.tag { font-weight: bold; }
|
|
||||||
.codeflask .token.attr-name { color: #111; }
|
|
||||||
.codeflask .token.attr-value { color: #000 !important; }
|
|
||||||
</style>
|
|
||||||
<div id="area"></div>
|
|
||||||
`
|
|
||||||
codeEditor.value.appendChild(el)
|
|
||||||
|
|
||||||
flask.value = new CodeFlask(el.shadowRoot.getElementById('area'), {
|
editorView = new EditorView({
|
||||||
language: props.language,
|
doc: body,
|
||||||
lineNumbers: false,
|
extensions: [
|
||||||
styleParent: el.shadowRoot,
|
basicSetup,
|
||||||
readonly: props.disabled
|
html(),
|
||||||
})
|
...(isDark ? [oneDark] : []),
|
||||||
|
EditorView.editable.of(!props.disabled),
|
||||||
flask.value.onUpdate((v) => {
|
EditorView.theme({
|
||||||
|
'&': { height: '100%' },
|
||||||
|
'.cm-editor': { height: '100%' },
|
||||||
|
'.cm-scroller': { overflow: 'auto' }
|
||||||
|
}),
|
||||||
|
EditorView.updateListener.of((update) => {
|
||||||
|
if (!update.docChanged) return
|
||||||
|
const v = update.state.doc.toString()
|
||||||
emit('update:modelValue', v)
|
emit('update:modelValue', v)
|
||||||
data.value = v
|
data.value = v
|
||||||
|
|
||||||
|
})
|
||||||
|
],
|
||||||
|
parent: codeEditor.value
|
||||||
})
|
})
|
||||||
|
|
||||||
flask.value.updateCode(body)
|
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
document.querySelector('code-flask').shadowRoot.querySelector('textarea').focus()
|
editorView?.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +57,9 @@ onMounted(() => {
|
|||||||
|
|
||||||
watch(() => props.modelValue, (newVal) => {
|
watch(() => props.modelValue, (newVal) => {
|
||||||
if (newVal !== data.value) {
|
if (newVal !== data.value) {
|
||||||
flask.value.updateCode(newVal)
|
editorView?.dispatch({
|
||||||
|
changes: { from: 0, to: editorView.state.doc.length, insert: newVal }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
accountNavItems,
|
accountNavItems,
|
||||||
contactNavItems
|
contactNavItems
|
||||||
} from '@/constants/navigation'
|
} from '@/constants/navigation'
|
||||||
import { RouterLink, useRoute } from 'vue-router'
|
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
@@ -43,14 +43,17 @@ import { useStorage } from '@vueuse/core'
|
|||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useConversationStore } from '@/stores/conversation'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
userTeams: { type: Array, default: () => [] },
|
userTeams: { type: Array, default: () => [] },
|
||||||
userViews: { type: Array, default: () => [] }
|
userViews: { type: Array, default: () => [] }
|
||||||
})
|
})
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const conversationStore = useConversationStore()
|
||||||
const settingsStore = useAppSettingsStore()
|
const settingsStore = useAppSettingsStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const emit = defineEmits(['createView', 'editView', 'deleteView', 'createConversation'])
|
const emit = defineEmits(['createView', 'editView', 'deleteView', 'createConversation'])
|
||||||
|
|
||||||
@@ -74,6 +77,58 @@ const deleteView = (view) => {
|
|||||||
emit('deleteView', view)
|
emit('deleteView', view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigation methods with conversation retention
|
||||||
|
const navigateToInbox = (type) => {
|
||||||
|
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||||
|
router.push({
|
||||||
|
name: 'inbox-conversation',
|
||||||
|
params: {
|
||||||
|
type,
|
||||||
|
uuid: conversationStore.conversation.data.uuid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'inbox',
|
||||||
|
params: { type }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateToTeamInbox = (teamID) => {
|
||||||
|
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||||
|
router.push({
|
||||||
|
name: 'team-inbox-conversation',
|
||||||
|
params: {
|
||||||
|
teamID,
|
||||||
|
uuid: conversationStore.conversation.data.uuid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'team-inbox',
|
||||||
|
params: { teamID }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateToViewInbox = (viewID) => {
|
||||||
|
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||||
|
router.push({
|
||||||
|
name: 'view-inbox-conversation',
|
||||||
|
params: {
|
||||||
|
viewID,
|
||||||
|
uuid: conversationStore.conversation.data.uuid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'view-inbox',
|
||||||
|
params: { viewID }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filteredAdminNavItems = computed(() => filterNavItems(adminNavItems, userStore.can))
|
const filteredAdminNavItems = computed(() => filterNavItems(adminNavItems, userStore.can))
|
||||||
const filteredReportsNavItems = computed(() => filterNavItems(reportsNavItems, userStore.can))
|
const filteredReportsNavItems = computed(() => filterNavItems(reportsNavItems, userStore.can))
|
||||||
const filteredContactsNavItems = computed(() => filterNavItems(contactNavItems, userStore.can))
|
const filteredContactsNavItems = computed(() => filterNavItems(contactNavItems, userStore.can))
|
||||||
@@ -322,32 +377,32 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
|||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/assigned')">
|
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/assigned')">
|
||||||
<router-link :to="{ name: 'inbox', params: { type: 'assigned' } }">
|
<a href="#" @click.prevent="navigateToInbox('assigned')">
|
||||||
<User />
|
<User />
|
||||||
<span>{{ t('globals.terms.myInbox') }}</span>
|
<span>{{ t('globals.terms.myInbox') }}</span>
|
||||||
</router-link>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/unassigned')">
|
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/unassigned')">
|
||||||
<router-link :to="{ name: 'inbox', params: { type: 'unassigned' } }">
|
<a href="#" @click.prevent="navigateToInbox('unassigned')">
|
||||||
<CircleDashed />
|
<CircleDashed />
|
||||||
<span>
|
<span>
|
||||||
{{ t('globals.terms.unassigned') }}
|
{{ t('globals.terms.unassigned') }}
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/all')">
|
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/all')">
|
||||||
<router-link :to="{ name: 'inbox', params: { type: 'all' } }">
|
<a href="#" @click.prevent="navigateToInbox('all')">
|
||||||
<List />
|
<List />
|
||||||
<span>
|
<span>
|
||||||
{{ t('globals.messages.all') }}
|
{{ t('globals.messages.all') }}
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|
||||||
@@ -380,9 +435,9 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
|||||||
:is-active="route.params.teamID == team.id"
|
:is-active="route.params.teamID == team.id"
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<router-link :to="{ name: 'team-inbox', params: { teamID: team.id } }">
|
<a href="#" @click.prevent="navigateToTeamInbox(team.id)">
|
||||||
{{ team.emoji }}<span>{{ team.name }}</span>
|
{{ team.emoji }}<span>{{ team.name }}</span>
|
||||||
</router-link>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
@@ -423,7 +478,7 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
|||||||
:isActive="route.params.viewID == view.id"
|
:isActive="route.params.viewID == view.id"
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<router-link :to="{ name: 'view-inbox', params: { viewID: view.id } }">
|
<a href="#" @click.prevent="navigateToViewInbox(view.id)">
|
||||||
<span class="break-words w-32 truncate">{{ view.name }}</span>
|
<span class="break-words w-32 truncate">{{ view.name }}</span>
|
||||||
<SidebarMenuAction :showOnHover="true" class="mr-3">
|
<SidebarMenuAction :showOnHover="true" class="mr-3">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -440,7 +495,7 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuAction>
|
</SidebarMenuAction>
|
||||||
</router-link>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
:checked="!!selectedDays[day]"
|
:checked="!!selectedDays[day]"
|
||||||
@update:checked="handleDayToggle(day, $event)"
|
@update:checked="handleDayToggle(day, $event)"
|
||||||
/>
|
/>
|
||||||
<Label :for="day" class="font-medium text-gray-800">{{ day }}</Label>
|
<Label :for="day" class="font-medium">{{ day }}</Label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-2 items-center">
|
||||||
<div class="flex flex-col items-start">
|
<div class="flex flex-col items-start">
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button :disabled="!holidayName || !holidayDate" @click="saveHoliday">
|
<Button :disabled="!holidayName || !holidayDate" @click="saveHoliday">
|
||||||
{{ t('globals.messages.saveChanges') }}
|
{{ t('globals.messages.add') }}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -231,9 +231,16 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
validationSchema: toTypedSchema(createFormSchema(t)),
|
validationSchema: toTypedSchema(createFormSchema(t)),
|
||||||
initialValues: props.initialValues
|
initialValues: {
|
||||||
|
is_always_open: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Sync form field with local state
|
||||||
|
const syncHoursToForm = () => {
|
||||||
|
form.setFieldValue('hours', { ...hours.value })
|
||||||
|
}
|
||||||
|
|
||||||
const saveHoliday = () => {
|
const saveHoliday = () => {
|
||||||
holidays.push({
|
holidays.push({
|
||||||
name: holidayName.value,
|
name: holidayName.value,
|
||||||
@@ -252,21 +259,15 @@ const deleteHoliday = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDayToggle = (day, checked) => {
|
const handleDayToggle = (day, checked) => {
|
||||||
selectedDays.value = {
|
selectedDays.value[day] = checked
|
||||||
...selectedDays.value,
|
|
||||||
[day]: checked
|
if (checked) {
|
||||||
|
hours.value[day] = hours.value[day] || { open: '09:00', close: '17:00' }
|
||||||
|
} else {
|
||||||
|
delete hours.value[day]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checked && !hours.value[day]) {
|
syncHoursToForm()
|
||||||
hours.value[day] = { open: '09:00', close: '17:00' }
|
|
||||||
} else if (!checked) {
|
|
||||||
const newHours = { ...hours.value }
|
|
||||||
delete newHours[day]
|
|
||||||
hours.value = newHours
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync with form values
|
|
||||||
form.setFieldValue('hours', { ...hours.value })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateHours = (day, type, value) => {
|
const updateHours = (day, type, value) => {
|
||||||
@@ -274,50 +275,48 @@ const updateHours = (day, type, value) => {
|
|||||||
hours.value[day] = { open: '09:00', close: '17:00' }
|
hours.value[day] = { open: '09:00', close: '17:00' }
|
||||||
}
|
}
|
||||||
hours.value[day][type] = value
|
hours.value[day][type] = value
|
||||||
|
syncHoursToForm()
|
||||||
// Sync with form values
|
|
||||||
form.setFieldValue('hours', { ...hours.value })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((values) => {
|
const onSubmit = form.handleSubmit((values) => {
|
||||||
const businessHours =
|
const businessHours = values.is_always_open === true ? {} : { ...hours.value }
|
||||||
values.is_always_open === true
|
|
||||||
? {}
|
|
||||||
: Object.keys(selectedDays.value)
|
|
||||||
.filter((day) => selectedDays.value[day])
|
|
||||||
.reduce((acc, day) => {
|
|
||||||
acc[day] = hours.value[day]
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
const finalValues = {
|
const finalValues = {
|
||||||
...values,
|
...values,
|
||||||
is_always_open: values.is_always_open,
|
|
||||||
hours: businessHours,
|
hours: businessHours,
|
||||||
holidays: holidays
|
holidays: [...holidays]
|
||||||
}
|
}
|
||||||
props.submitForm(finalValues)
|
props.submitForm(finalValues)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for initial values
|
// Initialize state from props
|
||||||
watch(
|
const initializeFromValues = (values) => {
|
||||||
() => props.initialValues,
|
if (!values) return
|
||||||
(newValues) => {
|
|
||||||
if (!newValues || Object.keys(newValues).length === 0) {
|
// Reset state
|
||||||
return
|
hours.value = {}
|
||||||
}
|
selectedDays.value = {}
|
||||||
// Set business hours if provided
|
holidays.length = 0
|
||||||
if (newValues.is_always_open === false) {
|
|
||||||
hours.value = newValues.hours || {}
|
// Set hours and selected days
|
||||||
selectedDays.value = Object.keys(hours.value).reduce((acc, day) => {
|
if (values.hours && typeof values.hours === 'object') {
|
||||||
|
hours.value = { ...values.hours }
|
||||||
|
selectedDays.value = Object.keys(values.hours).reduce((acc, day) => {
|
||||||
acc[day] = true
|
acc[day] = true
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
// Set other form values
|
|
||||||
form.setValues(newValues)
|
// Set holidays
|
||||||
holidays.length = 0
|
if (values.holidays) {
|
||||||
holidays.push(...(newValues.holidays || []))
|
holidays.push(...values.holidays)
|
||||||
},
|
}
|
||||||
{ deep: true }
|
|
||||||
)
|
// Update form
|
||||||
|
form.setValues(values)
|
||||||
|
syncHoursToForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for initial values
|
||||||
|
watch(() => props.initialValues, initializeFromValues, { immediate: true, deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const timeRegex = /^([01]\d|2[0-3]):([0-5]\d)$/
|
|||||||
export const createFormSchema = (t) => z.object({
|
export const createFormSchema = (t) => z.object({
|
||||||
name: z.string().min(1, t('globals.messages.required')),
|
name: z.string().min(1, t('globals.messages.required')),
|
||||||
description: z.string().min(1, t('globals.messages.required')),
|
description: z.string().min(1, t('globals.messages.required')),
|
||||||
is_always_open: z.boolean().default(true),
|
is_always_open: z.boolean(),
|
||||||
hours: z.record(
|
hours: z.record(
|
||||||
z.object({
|
z.object({
|
||||||
open: z.string().regex(timeRegex, t('form.error.time.invalid')),
|
open: z.string().regex(timeRegex, t('form.error.time.invalid')),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const createColumns = (t) => [
|
|||||||
},
|
},
|
||||||
cell: function ({ row }) {
|
cell: function ({ row }) {
|
||||||
const url = row.getValue('url')
|
const url = row.getValue('url')
|
||||||
return h('div', { class: 'text-center font-mono text-sm max-w-sm truncate' }, url)
|
return h('div', { class: 'text-center font-mono mt-1 max-w-sm truncate' }, url)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,11 +66,11 @@
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<span class="text-muted-foreground text-xs mt-1">
|
<span class="text-muted-foreground text-xs mt-1">
|
||||||
{{ format(message.updated_at, 'h:mm a') }}
|
{{ formatMessageTimestamp(message.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{{ format(message.updated_at, "MMMM dd, yyyy 'at' HH:mm") }}
|
{{ formatFullTimestamp(message.created_at) }}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,12 +79,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { useConversationStore } from '@/stores/conversation'
|
import { useConversationStore } from '@/stores/conversation'
|
||||||
import { Lock, RotateCcw, Check } from 'lucide-vue-next'
|
import { Lock, RotateCcw, Check } from 'lucide-vue-next'
|
||||||
import { revertCIDToImageSrc } from '@/utils/strings'
|
import { revertCIDToImageSrc } from '@/utils/strings'
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { Spinner } from '@/components/ui/spinner'
|
import { Spinner } from '@/components/ui/spinner'
|
||||||
|
import { formatMessageTimestamp, formatFullTimestamp } from '@/utils/datetime'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
||||||
import MessageEnvelope from './MessageEnvelope.vue'
|
import MessageEnvelope from './MessageEnvelope.vue'
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<Letter
|
<Letter
|
||||||
:html="sanitizedMessageContent"
|
:html="sanitizedMessageContent"
|
||||||
:allowedSchemas="['cid', 'https', 'http', 'mailto']"
|
:allowedSchemas="['cid', 'https', 'http', 'mailto']"
|
||||||
class="mb-1 native-html"
|
class="mb-1 native-html break-all"
|
||||||
:class="{ 'mb-3': message.attachments.length > 0 }"
|
:class="{ 'mb-3': message.attachments.length > 0 }"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -60,12 +60,12 @@
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<span class="text-muted-foreground text-xs mt-1">
|
<span class="text-muted-foreground text-xs mt-1">
|
||||||
{{ format(message.updated_at, 'h:mm a') }}
|
{{ formatMessageTimestamp(message.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>
|
||||||
{{ format(message.updated_at, "MMMM dd, yyyy 'at' HH:mm") }}
|
{{ formatFullTimestamp(message.created_at) }}
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -75,11 +75,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { useConversationStore } from '@/stores/conversation'
|
import { useConversationStore } from '@/stores/conversation'
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
import { Letter } from 'vue-letter'
|
import { Letter } from 'vue-letter'
|
||||||
|
import { formatMessageTimestamp, formatFullTimestamp } from '@/utils/datetime'
|
||||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="max-w-5xl mx-auto p-6 min-h-screen">
|
<div class="max-w-5xl mx-auto p-6 min-h-screen">
|
||||||
<div class="space-y-8">
|
<Tabs :default-value="defaultTab" v-model="activeTab">
|
||||||
<div
|
<TabsList class="grid w-full mb-6" :class="tabsGridClass">
|
||||||
v-for="(items, type) in results"
|
<TabsTrigger v-for="(items, type) in results" :key="type" :value="type" class="capitalize">
|
||||||
:key="type"
|
{{ type }} ({{ items.length }})
|
||||||
class="bg-card rounded shadow overflow-hidden"
|
</TabsTrigger>
|
||||||
>
|
</TabsList>
|
||||||
<!-- Header for each section -->
|
|
||||||
<h2
|
|
||||||
class="bg-primary dark:bg-primary text-lg font-bold text-white dark:text-primary-foreground py-2 px-6 capitalize"
|
|
||||||
>
|
|
||||||
{{ type }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
<TabsContent v-for="(items, type) in results" :key="type" :value="type" class="mt-0">
|
||||||
|
<div class="bg-background rounded border overflow-hidden">
|
||||||
<!-- No results message -->
|
<!-- No results message -->
|
||||||
<div v-if="items.length === 0" class="p-6 text-gray-500 dark:text-muted-foreground">
|
<div v-if="items.length === 0" class="p-8 text-center text-muted-foreground">
|
||||||
|
<div class="text-lg font-medium mb-2">
|
||||||
{{
|
{{
|
||||||
$t('globals.messages.noResults', {
|
$t('globals.messages.noResults', {
|
||||||
name: type
|
name: type
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-sm">{{ $t('search.adjustSearchTerms') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Results list -->
|
<!-- Results list -->
|
||||||
<div class="divide-y divide-gray-200 dark:divide-border">
|
<div v-else class="divide-y divide-border">
|
||||||
<div
|
<div
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.id || item.uuid"
|
:key="item.id || item.uuid"
|
||||||
class="p-6 hover:bg-gray-100 dark:hover:bg-accent transition duration-300 ease-in-out group"
|
class="p-6 hover:bg-accent/50 transition duration-200 ease-in-out group"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<!-- Reference number -->
|
<!-- Reference number -->
|
||||||
<div
|
<div
|
||||||
class="text-sm font-semibold mb-2 group-hover:text-primary dark:group-hover:text-primary transition duration-300"
|
class="text-sm font-semibold mb-2 text-muted-foreground group-hover:text-primary transition duration-200"
|
||||||
>
|
>
|
||||||
#{{
|
#{{
|
||||||
type === 'conversations'
|
type === 'conversations'
|
||||||
@@ -54,15 +53,18 @@
|
|||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div
|
<div
|
||||||
class="text-gray-900 dark:text-card-foreground font-medium mb-2 text-lg group-hover:text-gray-950 dark:group-hover:text-foreground transition duration-300"
|
class="text-foreground font-medium mb-2 text-lg group-hover:text-primary transition duration-200"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
truncateText(type === 'conversations' ? item.subject : item.text_content, 100)
|
truncateText(
|
||||||
|
type === 'conversations' ? item.subject : item.text_content,
|
||||||
|
100
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Timestamp -->
|
<!-- Timestamp -->
|
||||||
<div class="text-sm text-gray-500 dark:text-muted-foreground flex items-center">
|
<div class="text-sm text-muted-foreground flex items-center">
|
||||||
<ClockIcon class="h-4 w-4 mr-1" />
|
<ClockIcon class="h-4 w-4 mr-1" />
|
||||||
{{
|
{{
|
||||||
formatDate(
|
formatDate(
|
||||||
@@ -74,10 +76,10 @@
|
|||||||
|
|
||||||
<!-- Right arrow icon -->
|
<!-- Right arrow icon -->
|
||||||
<div
|
<div
|
||||||
class="bg-gray-200 dark:bg-secondary rounded-full p-2 group-hover:bg-primary dark:group-hover:bg-primary transition duration-300"
|
class="bg-secondary rounded-full p-2 group-hover:bg-primary transition duration-200"
|
||||||
>
|
>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
class="h-5 w-5 text-gray-700 dark:text-secondary-foreground group-hover:text-white dark:group-hover:text-primary-foreground"
|
class="h-5 w-5 text-secondary-foreground group-hover:text-primary-foreground"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,20 +88,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
import { ChevronRightIcon, ClockIcon } from 'lucide-vue-next'
|
import { ChevronRightIcon, ClockIcon } from 'lucide-vue-next'
|
||||||
import { format, parseISO } from 'date-fns'
|
import { format, parseISO } from 'date-fns'
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
results: {
|
results: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get the first available tab as default
|
||||||
|
const defaultTab = computed(() => {
|
||||||
|
const types = Object.keys(props.results)
|
||||||
|
return types.length > 0 ? types[0] : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeTab = ref('')
|
||||||
|
|
||||||
|
// Watch for changes in results and set the first tab as active
|
||||||
|
watch(
|
||||||
|
() => props.results,
|
||||||
|
(newResults) => {
|
||||||
|
const types = Object.keys(newResults)
|
||||||
|
if (types.length > 0 && !activeTab.value) {
|
||||||
|
activeTab.value = types[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dynamic grid class based on number of tabs
|
||||||
|
const tabsGridClass = computed(() => {
|
||||||
|
const tabCount = Object.keys(props.results).length
|
||||||
|
if (tabCount <= 2) return 'grid-cols-2'
|
||||||
|
if (tabCount <= 3) return 'grid-cols-3'
|
||||||
|
if (tabCount <= 4) return 'grid-cols-4'
|
||||||
|
return 'grid-cols-5'
|
||||||
|
})
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
const date = parseISO(dateString)
|
const date = parseISO(dateString)
|
||||||
return format(date, 'MMM d, yyyy HH:mm')
|
return format(date, 'MMM d, yyyy HH:mm')
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ const routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
name: 'team-inbox',
|
name: 'team-inbox',
|
||||||
component: () => import('@/views/inbox/InboxView.vue'),
|
component: () => import('@/views/inbox/InboxView.vue'),
|
||||||
meta: { title: 'Team inbox' }
|
meta: { title: 'Team inbox' },
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: 'conversation/:uuid',
|
path: 'conversation/:uuid',
|
||||||
name: 'team-inbox-conversation',
|
name: 'team-inbox-conversation',
|
||||||
@@ -81,6 +81,8 @@ const routes = [
|
|||||||
meta: { title: 'Team inbox', hidePageHeader: true }
|
meta: { title: 'Team inbox', hidePageHeader: true }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/inboxes/views/:viewID',
|
path: '/inboxes/views/:viewID',
|
||||||
@@ -93,8 +95,8 @@ const routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
name: 'view-inbox',
|
name: 'view-inbox',
|
||||||
component: () => import('@/views/inbox/InboxView.vue'),
|
component: () => import('@/views/inbox/InboxView.vue'),
|
||||||
meta: { title: 'View inbox' }
|
meta: { title: 'View inbox' },
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: 'conversation/:uuid',
|
path: 'conversation/:uuid',
|
||||||
name: 'view-inbox-conversation',
|
name: 'view-inbox-conversation',
|
||||||
@@ -103,6 +105,8 @@ const routes = [
|
|||||||
meta: { title: 'View inbox', hidePageHeader: true }
|
meta: { title: 'View inbox', hidePageHeader: true }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'inboxes/search',
|
path: 'inboxes/search',
|
||||||
|
|||||||
@@ -378,9 +378,6 @@ export const useConversationStore = defineStore('conversation', () => {
|
|||||||
if (conversations.listType !== listType || conversations.teamID !== teamID || conversations.viewID !== viewID) {
|
if (conversations.listType !== listType || conversations.teamID !== teamID || conversations.viewID !== viewID) {
|
||||||
resetConversations()
|
resetConversations()
|
||||||
}
|
}
|
||||||
if (conversations.listType !== listType) {
|
|
||||||
resetCurrentConversation()
|
|
||||||
}
|
|
||||||
if (listType) conversations.listType = listType
|
if (listType) conversations.listType = listType
|
||||||
if (teamID) conversations.teamID = teamID
|
if (teamID) conversations.teamID = teamID
|
||||||
if (viewID) conversations.viewID = viewID
|
if (viewID) conversations.viewID = viewID
|
||||||
|
|||||||
@@ -26,3 +26,11 @@ export const formatDuration = (seconds, showSeconds = true) => {
|
|||||||
const secs = totalSeconds % 60
|
const secs = totalSeconds % 60
|
||||||
return `${hours}h ${mins}m ${showSeconds ? `${secs}s` : ''}`
|
return `${hours}h ${mins}m ${showSeconds ? `${secs}s` : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatMessageTimestamp = (time) => {
|
||||||
|
return format(time, 'd MMM, hh:mm a')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatFullTimestamp = (time) => {
|
||||||
|
return format(time, 'd MMM yyyy, hh:mm a')
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { watch, onMounted, onUnmounted } from 'vue'
|
import { watch, onMounted } from 'vue'
|
||||||
import { useConversationStore } from '@/stores/conversation'
|
import { useConversationStore } from '@/stores/conversation'
|
||||||
import Conversation from '@/features/conversation/Conversation.vue'
|
import Conversation from '@/features/conversation/Conversation.vue'
|
||||||
import ConversationSideBarWrapper from '@/features/conversation/sidebar/ConversationSideBarWrapper.vue'
|
import ConversationSideBarWrapper from '@/features/conversation/sidebar/ConversationSideBarWrapper.vue'
|
||||||
@@ -37,10 +37,6 @@ onMounted(() => {
|
|||||||
if (props.uuid) fetchConversation(props.uuid)
|
if (props.uuid) fetchConversation(props.uuid)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
conversationStore.resetCurrentConversation()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Watcher for UUID changes
|
// Watcher for UUID changes
|
||||||
watch(
|
watch(
|
||||||
() => props.uuid,
|
() => props.uuid,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ onMounted(() => {
|
|||||||
if (!conversationStore.getListStatus) {
|
if (!conversationStore.getListStatus) {
|
||||||
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
||||||
}
|
}
|
||||||
conversationStore.resetCurrentConversation()
|
|
||||||
conversationStore.fetchConversationsList(true, type.value)
|
conversationStore.fetchConversationsList(true, type.value)
|
||||||
}
|
}
|
||||||
// Fetch team list.
|
// Fetch team list.
|
||||||
@@ -34,7 +33,6 @@ onMounted(() => {
|
|||||||
if (!conversationStore.getListStatus) {
|
if (!conversationStore.getListStatus) {
|
||||||
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
||||||
}
|
}
|
||||||
conversationStore.resetCurrentConversation()
|
|
||||||
conversationStore.fetchConversationsList(
|
conversationStore.fetchConversationsList(
|
||||||
true,
|
true,
|
||||||
CONVERSATION_LIST_TYPE.TEAM_UNASSIGNED,
|
CONVERSATION_LIST_TYPE.TEAM_UNASSIGNED,
|
||||||
@@ -45,7 +43,6 @@ onMounted(() => {
|
|||||||
if (viewID.value) {
|
if (viewID.value) {
|
||||||
// Empty out list status as views are already filtered.
|
// Empty out list status as views are already filtered.
|
||||||
conversationStore.setListStatus('', false)
|
conversationStore.setListStatus('', false)
|
||||||
conversationStore.resetCurrentConversation()
|
|
||||||
conversationStore.fetchConversationsList(true, CONVERSATION_LIST_TYPE.VIEW, 0, [], viewID.value)
|
conversationStore.fetchConversationsList(true, CONVERSATION_LIST_TYPE.VIEW, 0, [], viewID.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
chunkSizeWarningLimit: 600,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -38,7 +38,7 @@ require (
|
|||||||
github.com/zerodha/simplesessions/v3 v3.0.0
|
github.com/zerodha/simplesessions/v3 v3.0.0
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/mod v0.17.0
|
golang.org/x/mod v0.17.0
|
||||||
golang.org/x/oauth2 v0.21.0
|
golang.org/x/oauth2 v0.27.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -140,8 +140,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.5.5 h1:51VEyMF8eOO+NUHFm8fpg+IOc1xFuFOhxs3R+kPu1FM=
|
github.com/redis/go-redis/v9 v9.5.5 h1:51VEyMF8eOO+NUHFm8fpg+IOc1xFuFOhxs3R+kPu1FM=
|
||||||
github.com/redis/go-redis/v9 v9.5.5/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
github.com/redis/go-redis/v9 v9.5.5/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/rhnvrm/simples3 v0.9.0 h1:It6/glyqRTRooRzXcYOuqpKwjGg3lsXgNmeGgxpBtjA=
|
|
||||||
github.com/rhnvrm/simples3 v0.9.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
|
||||||
github.com/rhnvrm/simples3 v0.9.1 h1:pYfEe2wTjx8B2zFzUdy4kZn3I3Otd9ZvzIhHkFR85kE=
|
github.com/rhnvrm/simples3 v0.9.1 h1:pYfEe2wTjx8B2zFzUdy4kZn3I3Otd9ZvzIhHkFR85kE=
|
||||||
github.com/rhnvrm/simples3 v0.9.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
github.com/rhnvrm/simples3 v0.9.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@@ -211,8 +209,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"globals.terms.setting": "Setting | Settings",
|
"globals.terms.setting": "Setting | Settings",
|
||||||
"globals.terms.template": "Template | Templates",
|
"globals.terms.template": "Template | Templates",
|
||||||
"globals.terms.rule": "Rule | Rules",
|
"globals.terms.rule": "Rule | Rules",
|
||||||
"globals.terms.businessHour": "Business Hour | Business Hours",
|
"globals.terms.businessHour": "Business hour | Business hours",
|
||||||
"globals.terms.priority": "Priority | Priorities",
|
"globals.terms.priority": "Priority | Priorities",
|
||||||
"globals.terms.status": "Status | Statuses",
|
"globals.terms.status": "Status | Statuses",
|
||||||
"globals.terms.secret": "Secret | Secrets",
|
"globals.terms.secret": "Secret | Secrets",
|
||||||
@@ -398,7 +398,7 @@
|
|||||||
"admin.general.allowedFileUploadExtensions.description": "Allowed file upload extensions. Use `*` to allow all file types.",
|
"admin.general.allowedFileUploadExtensions.description": "Allowed file upload extensions. Use `*` to allow all file types.",
|
||||||
"admin.businessHours.unauthorized": "You do not have permission to view business hours.",
|
"admin.businessHours.unauthorized": "You do not have permission to view business hours.",
|
||||||
"admin.businessHours.setBusinessHours": "Set business hours",
|
"admin.businessHours.setBusinessHours": "Set business hours",
|
||||||
"admin.businessHours.alwaysOpen24x7": "Always open 24/7",
|
"admin.businessHours.alwaysOpen24x7": "Always open (24/7)",
|
||||||
"admin.businessHours.customBusinessHours": "Custom business hours",
|
"admin.businessHours.customBusinessHours": "Custom business hours",
|
||||||
"admin.businessHours.hours.required": "Business hours are required",
|
"admin.businessHours.hours.required": "Business hours are required",
|
||||||
"admin.businessHours.openClose.required": "Open and close time are required",
|
"admin.businessHours.openClose.required": "Open and close time are required",
|
||||||
@@ -533,7 +533,7 @@
|
|||||||
"admin.automation.event.message.incoming": "Incoming message",
|
"admin.automation.event.message.incoming": "Incoming message",
|
||||||
"admin.automation.invalid": "Make sure you have atleast one action and one rule and their values are not empty.",
|
"admin.automation.invalid": "Make sure you have atleast one action and one rule and their values are not empty.",
|
||||||
"admin.notification.restartApp": "Settings updated successfully, Please restart the app for changes to take effect.",
|
"admin.notification.restartApp": "Settings updated successfully, Please restart the app for changes to take effect.",
|
||||||
"admin.template.outgoingEmailTemplates": "Outgoing Email Templates",
|
"admin.template.outgoingEmailTemplates": "Outgoing email templates",
|
||||||
"admin.template.emailNotificationTemplates": "Email notification templates",
|
"admin.template.emailNotificationTemplates": "Email notification templates",
|
||||||
"admin.template.makeSureTemplateHasContent": "Make sure the template has {content} only once.",
|
"admin.template.makeSureTemplateHasContent": "Make sure the template has {content} only once.",
|
||||||
"admin.template.onlyOneDefaultOutgoingTemplate": "You can have only one default outgoing email template.",
|
"admin.template.onlyOneDefaultOutgoingTemplate": "You can have only one default outgoing email template.",
|
||||||
@@ -568,6 +568,7 @@
|
|||||||
"search.noResultsForQuery": "No results found for query `{query}`. Try a different search term.",
|
"search.noResultsForQuery": "No results found for query `{query}`. Try a different search term.",
|
||||||
"search.minQueryLength": " Please enter at least {length} characters to search.",
|
"search.minQueryLength": " Please enter at least {length} characters to search.",
|
||||||
"search.searchBy": "Search by reference number, contact email address or messages in conversations.",
|
"search.searchBy": "Search by reference number, contact email address or messages in conversations.",
|
||||||
|
"search.adjustSearchTerms": "Try adjusting your search terms or filters.",
|
||||||
"sla.overdueBy": "Overdue by",
|
"sla.overdueBy": "Overdue by",
|
||||||
"sla.met": "SLA met",
|
"sla.met": "SLA met",
|
||||||
"view.form.description": "Create and save custom filter views for quick access to your conversations.",
|
"view.form.description": "Create and save custom filter views for quick access to your conversations.",
|
||||||
|
|||||||
@@ -198,42 +198,45 @@ func (e *Engine) GetRule(id int) (models.RuleRecord, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleRule toggles the active status of a rule by ID.
|
// ToggleRule toggles the active status of a rule by ID.
|
||||||
func (e *Engine) ToggleRule(id int) error {
|
func (e *Engine) ToggleRule(id int) (models.RuleRecord, error) {
|
||||||
if _, err := e.q.ToggleRule.Exec(id); err != nil {
|
var result models.RuleRecord
|
||||||
|
if err := e.q.ToggleRule.Get(&result, id); err != nil {
|
||||||
e.lo.Error("error toggling rule", "error", err)
|
e.lo.Error("error toggling rule", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorUpdating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
return models.RuleRecord{}, envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorUpdating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
||||||
}
|
}
|
||||||
// Reload rules.
|
// Reload rules.
|
||||||
e.ReloadRules()
|
e.ReloadRules()
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRule updates an existing rule.
|
// UpdateRule updates an existing rule.
|
||||||
func (e *Engine) UpdateRule(id int, rule models.RuleRecord) error {
|
func (e *Engine) UpdateRule(id int, rule models.RuleRecord) (models.RuleRecord, error) {
|
||||||
if rule.Events == nil {
|
if rule.Events == nil {
|
||||||
rule.Events = pq.StringArray{}
|
rule.Events = pq.StringArray{}
|
||||||
}
|
}
|
||||||
if _, err := e.q.UpdateRule.Exec(id, rule.Name, rule.Description, rule.Type, rule.Events, rule.Rules, rule.Enabled); err != nil {
|
var result models.RuleRecord
|
||||||
|
if err := e.q.UpdateRule.Get(&result, id, rule.Name, rule.Description, rule.Type, rule.Events, rule.Rules, rule.Enabled); err != nil {
|
||||||
e.lo.Error("error updating rule", "error", err)
|
e.lo.Error("error updating rule", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorUpdating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
return models.RuleRecord{}, envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorUpdating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
||||||
}
|
}
|
||||||
// Reload rules.
|
// Reload rules.
|
||||||
e.ReloadRules()
|
e.ReloadRules()
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRule creates a new rule.
|
// CreateRule creates a new rule.
|
||||||
func (e *Engine) CreateRule(rule models.RuleRecord) error {
|
func (e *Engine) CreateRule(rule models.RuleRecord) (models.RuleRecord, error) {
|
||||||
if rule.Events == nil {
|
if rule.Events == nil {
|
||||||
rule.Events = pq.StringArray{}
|
rule.Events = pq.StringArray{}
|
||||||
}
|
}
|
||||||
if _, err := e.q.InsertRule.Exec(rule.Name, rule.Description, rule.Type, rule.Events, rule.Rules); err != nil {
|
var result models.RuleRecord
|
||||||
|
if err := e.q.InsertRule.Get(&result, rule.Name, rule.Description, rule.Type, rule.Events, rule.Rules); err != nil {
|
||||||
e.lo.Error("error creating rule", "error", err)
|
e.lo.Error("error creating rule", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorCreating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
return models.RuleRecord{}, envelope.NewError(envelope.GeneralError, e.i18n.Ts("globals.messages.errorCreating", "name", e.i18n.Ts("globals.terms.rule")), nil)
|
||||||
}
|
}
|
||||||
// Reload rules.
|
// Reload rules.
|
||||||
e.ReloadRules()
|
e.ReloadRules()
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRule deletes a rule by ID.
|
// DeleteRule deletes a rule by ID.
|
||||||
@@ -328,7 +331,7 @@ func (e *Engine) handleNewConversation(conversation cmodels.Conversation) {
|
|||||||
e.lo.Debug("handling new conversation for automation rule evaluation", "uuid", conversation.UUID)
|
e.lo.Debug("handling new conversation for automation rule evaluation", "uuid", conversation.UUID)
|
||||||
rules := e.filterRulesByType(models.RuleTypeNewConversation, "")
|
rules := e.filterRulesByType(models.RuleTypeNewConversation, "")
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
e.lo.Warn("no rules to evaluate for new conversation rule evaluation", "uuid", conversation.UUID)
|
e.lo.Info("no rules to evaluate for new conversation rule evaluation", "uuid", conversation.UUID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.evalConversationRules(rules, conversation)
|
e.evalConversationRules(rules, conversation)
|
||||||
@@ -339,7 +342,7 @@ func (e *Engine) handleUpdateConversation(conversation cmodels.Conversation, eve
|
|||||||
e.lo.Debug("handling update conversation for automation rule evaluation", "uuid", conversation.UUID, "event_type", eventType)
|
e.lo.Debug("handling update conversation for automation rule evaluation", "uuid", conversation.UUID, "event_type", eventType)
|
||||||
rules := e.filterRulesByType(models.RuleTypeConversationUpdate, eventType)
|
rules := e.filterRulesByType(models.RuleTypeConversationUpdate, eventType)
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
e.lo.Warn("no rules to evaluate for conversation update", "uuid", conversation.UUID, "event_type", eventType)
|
e.lo.Info("no rules to evaluate for conversation update", "uuid", conversation.UUID, "event_type", eventType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.evalConversationRules(rules, conversation)
|
e.evalConversationRules(rules, conversation)
|
||||||
@@ -356,7 +359,7 @@ func (e *Engine) handleTimeTrigger() {
|
|||||||
}
|
}
|
||||||
rules := e.filterRulesByType(models.RuleTypeTimeTrigger, "")
|
rules := e.filterRulesByType(models.RuleTypeTimeTrigger, "")
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
e.lo.Warn("no rules to evaluate for time trigger")
|
e.lo.Info("no rules to evaluate for time trigger")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.lo.Info("fetched conversations for evaluating time triggers", "conversations_count", len(conversations), "rules_count", len(rules))
|
e.lo.Info("fetched conversations for evaluating time triggers", "conversations_count", len(conversations), "rules_count", len(rules))
|
||||||
|
|||||||
@@ -24,10 +24,13 @@ DO UPDATE SET
|
|||||||
rules = EXCLUDED.rules,
|
rules = EXCLUDED.rules,
|
||||||
enabled = EXCLUDED.enabled,
|
enabled = EXCLUDED.enabled,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE $1 > 0;
|
WHERE $1 > 0
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: insert-rule
|
-- name: insert-rule
|
||||||
INSERT into automation_rules (name, description, type, events, rules) values ($1, $2, $3, $4, $5);
|
INSERT into automation_rules (name, description, type, events, rules)
|
||||||
|
values ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-rule
|
-- name: delete-rule
|
||||||
delete from automation_rules where id = $1;
|
delete from automation_rules where id = $1;
|
||||||
@@ -35,7 +38,8 @@ delete from automation_rules where id = $1;
|
|||||||
-- name: toggle-rule
|
-- name: toggle-rule
|
||||||
UPDATE automation_rules
|
UPDATE automation_rules
|
||||||
SET enabled = NOT enabled, updated_at = NOW()
|
SET enabled = NOT enabled, updated_at = NOW()
|
||||||
WHERE id = $1;
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update-rule-weight
|
-- name: update-rule-weight
|
||||||
UPDATE automation_rules
|
UPDATE automation_rules
|
||||||
|
|||||||
@@ -80,12 +80,13 @@ func (m *Manager) GetAll() ([]models.BusinessHours, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates new business hours.
|
// Create creates new business hours.
|
||||||
func (m *Manager) Create(name string, description null.String, isAlwaysOpen bool, workingHrs, holidays types.JSONText) error {
|
func (m *Manager) Create(name string, description null.String, isAlwaysOpen bool, workingHrs, holidays types.JSONText) (models.BusinessHours, error) {
|
||||||
if _, err := m.q.InsertBusinessHours.Exec(name, description, isAlwaysOpen, workingHrs, holidays); err != nil {
|
var result models.BusinessHours
|
||||||
|
if err := m.q.InsertBusinessHours.Get(&result, name, description, isAlwaysOpen, workingHrs, holidays); err != nil {
|
||||||
m.lo.Error("error inserting business hours", "error", err)
|
m.lo.Error("error inserting business hours", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.businessHour}"), nil)
|
return models.BusinessHours{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.businessHour}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes business hours by ID.
|
// Delete deletes business hours by ID.
|
||||||
@@ -101,10 +102,11 @@ func (m *Manager) Delete(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates business hours by ID.
|
// Update updates business hours by ID.
|
||||||
func (m *Manager) Update(id int, name string, description null.String, isAlwaysOpen bool, workingHrs, holidays types.JSONText) error {
|
func (m *Manager) Update(id int, name string, description null.String, isAlwaysOpen bool, workingHrs, holidays types.JSONText) (models.BusinessHours, error) {
|
||||||
if _, err := m.q.UpdateBusinessHours.Exec(id, name, description, isAlwaysOpen, workingHrs, holidays); err != nil {
|
var result models.BusinessHours
|
||||||
|
if err := m.q.UpdateBusinessHours.Get(&result, id, name, description, isAlwaysOpen, workingHrs, holidays); err != nil {
|
||||||
m.lo.Error("error updating business hours", "error", err)
|
m.lo.Error("error updating business hours", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.businessHour}"), nil)
|
return models.BusinessHours{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.businessHour}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ INSERT INTO business_hours (
|
|||||||
hours,
|
hours,
|
||||||
holidays
|
holidays
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5);
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-business-hours
|
-- name: delete-business-hours
|
||||||
DELETE FROM business_hours
|
DELETE FROM business_hours
|
||||||
@@ -41,4 +42,5 @@ SET "name" = $2,
|
|||||||
hours = $5,
|
hours = $5,
|
||||||
holidays = $6,
|
holidays = $6,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1;
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
@@ -920,14 +920,17 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
|||||||
}
|
}
|
||||||
return m.UpdateConversationStatus(conv.UUID, statusID, "", "", user)
|
return m.UpdateConversationStatus(conv.UUID, statusID, "", "", user)
|
||||||
case amodels.ActionSendPrivateNote:
|
case amodels.ActionSendPrivateNote:
|
||||||
return m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0])
|
_, err := m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending private note: %w", err)
|
||||||
|
}
|
||||||
case amodels.ActionReply:
|
case amodels.ActionReply:
|
||||||
// Make recipient list.
|
// Make recipient list.
|
||||||
to, cc, bcc, err := m.makeRecipients(conv.ID, conv.Contact.Email.String, conv.InboxMail)
|
to, cc, bcc, err := m.makeRecipients(conv.ID, conv.Contact.Email.String, conv.InboxMail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making recipients for reply action: %w", err)
|
return fmt.Errorf("making recipients for reply action: %w", err)
|
||||||
}
|
}
|
||||||
return m.SendReply(
|
_, err = m.SendReply(
|
||||||
[]mmodels.Media{},
|
[]mmodels.Media{},
|
||||||
conv.InboxID,
|
conv.InboxID,
|
||||||
user.ID,
|
user.ID,
|
||||||
@@ -938,6 +941,9 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
|||||||
bcc,
|
bcc,
|
||||||
map[string]any{}, /**meta**/
|
map[string]any{}, /**meta**/
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending reply: %w", err)
|
||||||
|
}
|
||||||
case amodels.ActionSetSLA:
|
case amodels.ActionSetSLA:
|
||||||
slaID, err := strconv.Atoi(action.Value[0])
|
slaID, err := strconv.Atoi(action.Value[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -951,6 +957,7 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown action: %s", action.Type)
|
return fmt.Errorf("unknown action: %s", action.Type)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveConversationAssignee removes the assignee from the conversation.
|
// RemoveConversationAssignee removes the assignee from the conversation.
|
||||||
@@ -991,10 +998,16 @@ func (m *Manager) SendCSATReply(actorUserID int, conversation models.Conversatio
|
|||||||
// Make recipient list.
|
// Make recipient list.
|
||||||
to, cc, bcc, err := m.makeRecipients(conversation.ID, conversation.Contact.Email.String, conversation.InboxMail)
|
to, cc, bcc, err := m.makeRecipients(conversation.ID, conversation.Contact.Email.String, conversation.InboxMail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making recipients for CSAT reply: %w", err)
|
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta)
|
// Send CSAT reply.
|
||||||
|
_, err = m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta)
|
||||||
|
if err != nil {
|
||||||
|
m.lo.Error("error sending CSAT reply", "conversation_uuid", conversation.UUID, "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteConversation deletes a conversation.
|
// DeleteConversation deletes a conversation.
|
||||||
|
|||||||
@@ -356,8 +356,7 @@ func (m *Manager) MarkMessageAsPending(uuid string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendPrivateNote inserts a private message in a conversation.
|
// SendPrivateNote inserts a private message in a conversation.
|
||||||
func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) error {
|
func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) (models.Message, error) {
|
||||||
// Insert Message.
|
|
||||||
message := models.Message{
|
message := models.Message{
|
||||||
ConversationUUID: conversationUUID,
|
ConversationUUID: conversationUUID,
|
||||||
SenderID: senderID,
|
SenderID: senderID,
|
||||||
@@ -369,18 +368,25 @@ func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversat
|
|||||||
Private: true,
|
Private: true,
|
||||||
Media: media,
|
Media: media,
|
||||||
}
|
}
|
||||||
return m.InsertMessage(&message)
|
if err := m.InsertMessage(&message); err != nil {
|
||||||
|
return models.Message{}, err
|
||||||
|
}
|
||||||
|
return message, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendReply inserts a reply message in a conversation.
|
// SendReply inserts a reply message in a conversation.
|
||||||
func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) error {
|
func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) (models.Message, error) {
|
||||||
// Save to, cc and bcc in meta.
|
var (
|
||||||
|
message = models.Message{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clear empty fields in to, cc, bcc.
|
||||||
to = stringutil.RemoveEmpty(to)
|
to = stringutil.RemoveEmpty(to)
|
||||||
cc = stringutil.RemoveEmpty(cc)
|
cc = stringutil.RemoveEmpty(cc)
|
||||||
bcc = stringutil.RemoveEmpty(bcc)
|
bcc = stringutil.RemoveEmpty(bcc)
|
||||||
|
|
||||||
if len(to) == 0 {
|
if len(to) == 0 {
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil)
|
return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil)
|
||||||
}
|
}
|
||||||
meta["to"] = to
|
meta["to"] = to
|
||||||
|
|
||||||
@@ -393,22 +399,22 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
|
|||||||
|
|
||||||
metaJSON, err := json.Marshal(meta)
|
metaJSON, err := json.Marshal(meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil)
|
return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generage unique source ID i.e. message-id for email.
|
// Generage unique source ID i.e. message-id for email.
|
||||||
inbox, err := m.inboxStore.GetDBRecord(inboxID)
|
inbox, err := m.inboxStore.GetDBRecord(inboxID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return message, err
|
||||||
}
|
}
|
||||||
sourceID, err := stringutil.GenerateEmailMessageID(conversationUUID, inbox.From)
|
sourceID, err := stringutil.GenerateEmailMessageID(conversationUUID, inbox.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error generating source message id", "error", err)
|
m.lo.Error("error generating source message id", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil)
|
return message, envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert Message.
|
// Insert Message.
|
||||||
message := models.Message{
|
message = models.Message{
|
||||||
ConversationUUID: conversationUUID,
|
ConversationUUID: conversationUUID,
|
||||||
SenderID: senderID,
|
SenderID: senderID,
|
||||||
Type: models.MessageOutgoing,
|
Type: models.MessageOutgoing,
|
||||||
@@ -421,16 +427,17 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
|
|||||||
Meta: metaJSON,
|
Meta: metaJSON,
|
||||||
SourceID: null.StringFrom(sourceID),
|
SourceID: null.StringFrom(sourceID),
|
||||||
}
|
}
|
||||||
return m.InsertMessage(&message)
|
if err := m.InsertMessage(&message); err != nil {
|
||||||
|
return models.Message{}, err
|
||||||
|
}
|
||||||
|
return message, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertMessage inserts a message and attaches the media to the message.
|
// InsertMessage inserts a message and attaches the media to the message.
|
||||||
func (m *Manager) InsertMessage(message *models.Message) error {
|
func (m *Manager) InsertMessage(message *models.Message) error {
|
||||||
// Private message is always sent.
|
|
||||||
if message.Private {
|
if message.Private {
|
||||||
message.Status = models.MessageStatusSent
|
message.Status = models.MessageStatusSent
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(message.Meta) == 0 || string(message.Meta) == "null" {
|
if len(message.Meta) == 0 || string(message.Meta) == "null" {
|
||||||
message.Meta = json.RawMessage(`{}`)
|
message.Meta = json.RawMessage(`{}`)
|
||||||
}
|
}
|
||||||
@@ -438,14 +445,16 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
|||||||
// Convert HTML content to text for search.
|
// Convert HTML content to text for search.
|
||||||
message.TextContent = stringutil.HTML2Text(message.Content)
|
message.TextContent = stringutil.HTML2Text(message.Content)
|
||||||
|
|
||||||
// Insert Message.
|
// Insert and scan the message into the struct.
|
||||||
if err := m.q.InsertMessage.QueryRow(message.Type, message.Status, message.ConversationID, message.ConversationUUID, message.Content, message.TextContent, message.SenderID, message.SenderType,
|
if err := m.q.InsertMessage.Get(message,
|
||||||
message.Private, message.ContentType, message.SourceID, message.Meta).Scan(&message.ID, &message.UUID, &message.CreatedAt); err != nil {
|
message.Type, message.Status, message.ConversationID, message.ConversationUUID,
|
||||||
|
message.Content, message.TextContent, message.SenderID, message.SenderType,
|
||||||
|
message.Private, message.ContentType, message.SourceID, message.Meta); err != nil {
|
||||||
m.lo.Error("error inserting message in db", "error", err)
|
m.lo.Error("error inserting message in db", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorInserting", "name", "{globals.terms.message}"), nil)
|
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorInserting", "name", "{globals.terms.message}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach message to the media.
|
// Attach just inserted message to the media.
|
||||||
for _, media := range message.Media {
|
for _, media := range message.Media {
|
||||||
m.mediaStore.Attach(media.ID, mmodels.ModelMessages, message.ID)
|
m.mediaStore.Attach(media.ID, mmodels.ModelMessages, message.ID)
|
||||||
}
|
}
|
||||||
@@ -465,13 +474,19 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
|||||||
// Broadcast new message.
|
// Broadcast new message.
|
||||||
m.BroadcastNewMessage(message)
|
m.BroadcastNewMessage(message)
|
||||||
|
|
||||||
// Refetch message and send webhook event for message created.
|
// Refetch message if this message has media attachments, as media gets linked after inserting the message.
|
||||||
updatedMessage, err := m.GetMessage(message.UUID)
|
if len(message.Media) > 0 {
|
||||||
|
refetchedMessage, err := m.GetMessage(message.UUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error fetching updated message for webhook event", "uuid", message.UUID, "error", err)
|
m.lo.Error("error fetching message after insert", "error", err)
|
||||||
} else {
|
} else {
|
||||||
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, updatedMessage)
|
// Replace the message in the struct with the refetched message.
|
||||||
|
*message = refetchedMessage
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger webhook for new message created.
|
||||||
|
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, message)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -530,7 +545,7 @@ func (m *Manager) InsertConversationActivity(activityType, conversationUUID, new
|
|||||||
content, err := m.getMessageActivityContent(activityType, newValue, actor.FullName())
|
content, err := m.getMessageActivityContent(activityType, newValue, actor.FullName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error could not generate activity content", "error", err)
|
m.lo.Error("error could not generate activity content", "error", err)
|
||||||
return err
|
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorGenerating", "name", "{globals.terms.activityMessage}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
message := models.Message{
|
message := models.Message{
|
||||||
|
|||||||
@@ -438,6 +438,7 @@ SELECT
|
|||||||
m.sender_type,
|
m.sender_type,
|
||||||
m.sender_id,
|
m.sender_id,
|
||||||
m.meta,
|
m.meta,
|
||||||
|
c.uuid as conversation_uuid,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
json_agg(
|
json_agg(
|
||||||
json_build_object(
|
json_build_object(
|
||||||
@@ -452,10 +453,11 @@ SELECT
|
|||||||
'[]'::json
|
'[]'::json
|
||||||
) AS attachments
|
) AS attachments
|
||||||
FROM conversation_messages m
|
FROM conversation_messages m
|
||||||
|
INNER JOIN conversations c ON c.id = m.conversation_id
|
||||||
LEFT JOIN media ON media.model_type = 'messages' AND media.model_id = m.id
|
LEFT JOIN media ON media.model_type = 'messages' AND media.model_id = m.id
|
||||||
WHERE m.uuid = $1
|
WHERE m.uuid = $1
|
||||||
GROUP BY
|
GROUP BY
|
||||||
m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.uuid, m.private, m.sender_type
|
m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.uuid, m.private, m.sender_type, c.uuid
|
||||||
ORDER BY m.created_at;
|
ORDER BY m.created_at;
|
||||||
|
|
||||||
-- name: get-messages
|
-- name: get-messages
|
||||||
@@ -509,9 +511,9 @@ inserted_msg AS (
|
|||||||
$1, $2, (SELECT id FROM conversation_id),
|
$1, $2, (SELECT id FROM conversation_id),
|
||||||
$5, $6, $7, $8, $9, $10, $11, $12
|
$5, $6, $7, $8, $9, $10, $11, $12
|
||||||
)
|
)
|
||||||
RETURNING id, uuid, created_at, conversation_id
|
RETURNING *
|
||||||
)
|
)
|
||||||
SELECT id, uuid, created_at FROM inserted_msg;
|
SELECT * FROM inserted_msg;
|
||||||
|
|
||||||
-- name: message-exists-by-source-id
|
-- name: message-exists-by-source-id
|
||||||
SELECT conversation_id
|
SELECT conversation_id
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ select id,
|
|||||||
from conversation_statuses;
|
from conversation_statuses;
|
||||||
|
|
||||||
-- name: insert-status
|
-- name: insert-status
|
||||||
INSERT into conversation_statuses(name) values ($1);
|
INSERT into conversation_statuses(name) values ($1) RETURNING *;
|
||||||
|
|
||||||
-- name: delete-status
|
-- name: delete-status
|
||||||
DELETE from conversation_statuses where id = $1;
|
DELETE from conversation_statuses where id = $1;
|
||||||
|
|
||||||
-- name: update-status
|
-- name: update-status
|
||||||
UPDATE conversation_statuses set name = $2 where id = $1;
|
UPDATE conversation_statuses set name = $2 where id = $1 RETURNING *;
|
||||||
@@ -70,15 +70,16 @@ func (m *Manager) GetAll() ([]models.Status, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new status.
|
// Create creates a new status.
|
||||||
func (m *Manager) Create(name string) error {
|
func (m *Manager) Create(name string) (models.Status, error) {
|
||||||
|
var status models.Status
|
||||||
if err := m.validateStatusName(name); err != nil {
|
if err := m.validateStatusName(name); err != nil {
|
||||||
return err
|
return status, err
|
||||||
}
|
}
|
||||||
if _, err := m.q.InsertStatus.Exec(name); err != nil {
|
if err := m.q.InsertStatus.Get(&status, name); err != nil {
|
||||||
m.lo.Error("error inserting status", "error", err)
|
m.lo.Error("error inserting status", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", m.i18n.T("globals.terms.status")), nil)
|
return status, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", m.i18n.T("globals.terms.status")), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a status by ID.
|
// Delete deletes a status by ID.
|
||||||
@@ -104,25 +105,26 @@ func (m *Manager) Delete(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a status by id.
|
// Update updates a status by id.
|
||||||
func (m *Manager) Update(id int, name string) error {
|
func (m *Manager) Update(id int, name string) (models.Status, error) {
|
||||||
|
var updatedStatus models.Status
|
||||||
if err := m.validateStatusName(name); err != nil {
|
if err := m.validateStatusName(name); err != nil {
|
||||||
return err
|
return updatedStatus, err
|
||||||
}
|
}
|
||||||
// Disallow updating of default statuses.
|
// Disallow updating of default statuses.
|
||||||
status, err := m.Get(id)
|
status, err := m.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorFetching", "name", m.i18n.Ts("globals.terms.status")), nil)
|
return updatedStatus, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorFetching", "name", m.i18n.Ts("globals.terms.status")), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(models.DefaultStatuses, status.Name) {
|
if slices.Contains(models.DefaultStatuses, status.Name) {
|
||||||
return envelope.NewError(envelope.InputError, m.i18n.T("conversationStatus.cannotUpdateDefault"), nil)
|
return updatedStatus, envelope.NewError(envelope.InputError, m.i18n.T("conversationStatus.cannotUpdateDefault"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := m.q.UpdateStatus.Exec(id, name); err != nil {
|
if err := m.q.UpdateStatus.Get(&updatedStatus, id, name); err != nil {
|
||||||
m.lo.Error("error updating status", "error", err)
|
m.lo.Error("error updating status", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", m.i18n.Ts("globals.terms.status")), nil)
|
return updatedStatus, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", m.i18n.Ts("globals.terms.status")), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return updatedStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves a status by ID.
|
// Get retrieves a status by ID.
|
||||||
|
|||||||
@@ -78,24 +78,26 @@ func (m *Manager) GetAll(appliesTo string) ([]models.CustomAttribute, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new custom attribute.
|
// Create creates a new custom attribute.
|
||||||
func (m *Manager) Create(attr models.CustomAttribute) error {
|
func (m *Manager) Create(attr models.CustomAttribute) (models.CustomAttribute, error) {
|
||||||
if _, err := m.q.InsertCustomAttribute.Exec(attr.AppliesTo, attr.Name, attr.Description, attr.Key, pq.Array(attr.Values), attr.DataType, attr.Regex, attr.RegexHint); err != nil {
|
var createdAttr models.CustomAttribute
|
||||||
|
if err := m.q.InsertCustomAttribute.Get(&createdAttr, attr.AppliesTo, attr.Name, attr.Description, attr.Key, pq.Array(attr.Values), attr.DataType, attr.Regex, attr.RegexHint); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) {
|
if dbutil.IsUniqueViolationError(err) {
|
||||||
return envelope.NewError(envelope.InputError, m.i18n.Ts("globals.messages.errorAlreadyExists", "name", m.i18n.P("globals.terms.customAttribute")), nil)
|
return models.CustomAttribute{}, envelope.NewError(envelope.InputError, m.i18n.Ts("globals.messages.errorAlreadyExists", "name", m.i18n.P("globals.terms.customAttribute")), nil)
|
||||||
}
|
}
|
||||||
m.lo.Error("error inserting custom attribute", "error", err)
|
m.lo.Error("error inserting custom attribute", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.customAttribute}"), nil)
|
return models.CustomAttribute{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.customAttribute}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return createdAttr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a custom attribute by ID.
|
// Update updates a custom attribute by ID.
|
||||||
func (m *Manager) Update(id int, attr models.CustomAttribute) error {
|
func (m *Manager) Update(id int, attr models.CustomAttribute) (models.CustomAttribute, error) {
|
||||||
if _, err := m.q.UpdateCustomAttribute.Exec(id, attr.AppliesTo, attr.Name, attr.Description, pq.Array(attr.Values), attr.Regex, attr.RegexHint); err != nil {
|
var updatedAttr models.CustomAttribute
|
||||||
|
if err := m.q.UpdateCustomAttribute.Get(&updatedAttr, id, attr.AppliesTo, attr.Name, attr.Description, pq.Array(attr.Values), attr.Regex, attr.RegexHint); err != nil {
|
||||||
m.lo.Error("error updating custom attribute", "error", err)
|
m.lo.Error("error updating custom attribute", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.customAttribute}"), nil)
|
return models.CustomAttribute{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.customAttribute}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return updatedAttr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a custom attribute by ID.
|
// Delete deletes a custom attribute by ID.
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ INSERT INTO
|
|||||||
custom_attribute_definitions (applies_to, name, description, key, values, data_type, regex, regex_hint)
|
custom_attribute_definitions (applies_to, name, description, key, values, data_type, regex, regex_hint)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8)
|
($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING *
|
||||||
|
|
||||||
-- name: delete-custom-attribute
|
-- name: delete-custom-attribute
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
@@ -62,4 +63,5 @@ SET
|
|||||||
regex_hint = $7,
|
regex_hint = $7,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = $1;
|
id = $1
|
||||||
|
RETURNING *;
|
||||||
@@ -169,12 +169,13 @@ func (m *Manager) GetAll() ([]imodels.Inbox, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates an inbox in the DB.
|
// Create creates an inbox in the DB.
|
||||||
func (m *Manager) Create(inbox imodels.Inbox) error {
|
func (m *Manager) Create(inbox imodels.Inbox) (imodels.Inbox, error) {
|
||||||
if _, err := m.queries.InsertInbox.Exec(inbox.Channel, inbox.Config, inbox.Name, inbox.From, inbox.CSATEnabled); err != nil {
|
var createdInbox imodels.Inbox
|
||||||
|
if err := m.queries.InsertInbox.Get(&createdInbox, inbox.Channel, inbox.Config, inbox.Name, inbox.From, inbox.CSATEnabled); err != nil {
|
||||||
m.lo.Error("error creating inbox", "error", err)
|
m.lo.Error("error creating inbox", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.inbox}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.inbox}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return createdInbox, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitInboxes initializes and registers active inboxes with the manager.
|
// InitInboxes initializes and registers active inboxes with the manager.
|
||||||
@@ -254,10 +255,10 @@ func (m *Manager) Reload(ctx context.Context, initFn initFn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an inbox in the DB.
|
// Update updates an inbox in the DB.
|
||||||
func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
func (m *Manager) Update(id int, inbox imodels.Inbox) (imodels.Inbox, error) {
|
||||||
current, err := m.GetDBRecord(id)
|
current, err := m.GetDBRecord(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return imodels.Inbox{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve existing passwords if update has empty password
|
// Preserve existing passwords if update has empty password
|
||||||
@@ -274,22 +275,22 @@ func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
|||||||
|
|
||||||
if err := json.Unmarshal(current.Config, ¤tCfg); err != nil {
|
if err := json.Unmarshal(current.Config, ¤tCfg); err != nil {
|
||||||
m.lo.Error("error unmarshalling current config", "id", id, "error", err)
|
m.lo.Error("error unmarshalling current config", "id", id, "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.config}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.config}"), nil)
|
||||||
}
|
}
|
||||||
if len(inbox.Config) == 0 {
|
if len(inbox.Config) == 0 {
|
||||||
return envelope.NewError(envelope.InputError, m.i18n.Ts("globals.messages.empty", "name", "{globals.terms.config}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.InputError, m.i18n.Ts("globals.messages.empty", "name", "{globals.terms.config}"), nil)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(inbox.Config, &updateCfg); err != nil {
|
if err := json.Unmarshal(inbox.Config, &updateCfg); err != nil {
|
||||||
m.lo.Error("error unmarshalling update config", "id", id, "error", err)
|
m.lo.Error("error unmarshalling update config", "id", id, "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.config}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.config}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updateCfg.IMAP) == 0 {
|
if len(updateCfg.IMAP) == 0 {
|
||||||
return envelope.NewError(envelope.InputError, m.i18n.T("inbox.emptyIMAP"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.InputError, m.i18n.T("inbox.emptyIMAP"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updateCfg.SMTP) == 0 {
|
if len(updateCfg.SMTP) == 0 {
|
||||||
return envelope.NewError(envelope.InputError, m.i18n.T("inbox.emptySMTP"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.InputError, m.i18n.T("inbox.emptySMTP"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve existing IMAP passwords if update has empty password
|
// Preserve existing IMAP passwords if update has empty password
|
||||||
@@ -308,27 +309,29 @@ func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
|||||||
updatedConfig, err := json.Marshal(updateCfg)
|
updatedConfig, err := json.Marshal(updateCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error marshalling updated config", "id", id, "error", err)
|
m.lo.Error("error marshalling updated config", "id", id, "error", err)
|
||||||
return err
|
return imodels.Inbox{}, err
|
||||||
}
|
}
|
||||||
inbox.Config = updatedConfig
|
inbox.Config = updatedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the inbox in the DB.
|
// Update the inbox in the DB.
|
||||||
if _, err := m.queries.Update.Exec(id, inbox.Channel, inbox.Config, inbox.Name, inbox.From, inbox.CSATEnabled, inbox.Enabled); err != nil {
|
var updatedInbox imodels.Inbox
|
||||||
|
if err := m.queries.Update.Get(&updatedInbox, id, inbox.Channel, inbox.Config, inbox.Name, inbox.From, inbox.CSATEnabled, inbox.Enabled); err != nil {
|
||||||
m.lo.Error("error updating inbox", "error", err)
|
m.lo.Error("error updating inbox", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.inbox}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.inbox}"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return updatedInbox, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle toggles the status of an inbox in the DB.
|
// Toggle toggles the status of an inbox in the DB.
|
||||||
func (m *Manager) Toggle(id int) error {
|
func (m *Manager) Toggle(id int) (imodels.Inbox, error) {
|
||||||
if _, err := m.queries.Toggle.Exec(id); err != nil {
|
var updatedInbox imodels.Inbox
|
||||||
|
if err := m.queries.Toggle.Get(&updatedInbox, id); err != nil {
|
||||||
m.lo.Error("error toggling inbox", "error", err)
|
m.lo.Error("error toggling inbox", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.inbox}"), nil)
|
return imodels.Inbox{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.inbox}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return updatedInbox, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoftDelete soft deletes an inbox in the DB.
|
// SoftDelete soft deletes an inbox in the DB.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ SELECT id, created_at, updated_at, name, channel, enabled from inboxes where del
|
|||||||
INSERT INTO inboxes
|
INSERT INTO inboxes
|
||||||
(channel, config, "name", "from", csat_enabled)
|
(channel, config, "name", "from", csat_enabled)
|
||||||
VALUES($1, $2, $3, $4, $5)
|
VALUES($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *
|
||||||
|
|
||||||
-- name: get-inbox
|
-- name: get-inbox
|
||||||
SELECT * from inboxes where id = $1 and deleted_at is NULL;
|
SELECT * from inboxes where id = $1 and deleted_at is NULL;
|
||||||
@@ -15,7 +16,8 @@ SELECT * from inboxes where id = $1 and deleted_at is NULL;
|
|||||||
-- name: update
|
-- name: update
|
||||||
UPDATE inboxes
|
UPDATE inboxes
|
||||||
set channel = $2, config = $3, "name" = $4, "from" = $5, csat_enabled = $6, enabled = $7, updated_at = now()
|
set channel = $2, config = $3, "name" = $4, "from" = $5, csat_enabled = $6, enabled = $7, updated_at = now()
|
||||||
where id = $1 and deleted_at is NULL;
|
where id = $1 and deleted_at is NULL
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: soft-delete
|
-- name: soft-delete
|
||||||
UPDATE inboxes set deleted_at = now(), config = '{}' where id = $1 and deleted_at is NULL;
|
UPDATE inboxes set deleted_at = now(), config = '{}' where id = $1 and deleted_at is NULL;
|
||||||
@@ -23,4 +25,5 @@ UPDATE inboxes set deleted_at = now(), config = '{}' where id = $1 and deleted_a
|
|||||||
-- name: toggle
|
-- name: toggle
|
||||||
UPDATE inboxes
|
UPDATE inboxes
|
||||||
SET enabled = NOT enabled, updated_at = NOW()
|
SET enabled = NOT enabled, updated_at = NOW()
|
||||||
WHERE id = $1;
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
@@ -68,26 +68,25 @@ func (m *Manager) Get(id int) (models.Macro, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create adds a new macro.
|
// Create adds a new macro.
|
||||||
func (m *Manager) Create(name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) error {
|
func (m *Manager) Create(name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) (models.Macro, error) {
|
||||||
_, err := m.q.Create.Exec(name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions)
|
var createdMacro models.Macro
|
||||||
|
err := m.q.Create.Get(&createdMacro, name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error creating macro", "error", err)
|
m.lo.Error("error creating macro", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.macro}"), nil)
|
return models.Macro{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.macro}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return createdMacro, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update modifies an existing macro.
|
// Update modifies an existing macro.
|
||||||
func (m *Manager) Update(id int, name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) error {
|
func (m *Manager) Update(id int, name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) (models.Macro, error) {
|
||||||
result, err := m.q.Update.Exec(id, name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions)
|
var updatedMacro models.Macro
|
||||||
|
err := m.q.Update.Get(&updatedMacro, id, name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.lo.Error("error updating macro", "error", err)
|
m.lo.Error("error updating macro", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.macro}"), nil)
|
return models.Macro{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.macro}"), nil)
|
||||||
}
|
}
|
||||||
if rows, _ := result.RowsAffected(); rows == 0 {
|
return updatedMacro, nil
|
||||||
return envelope.NewError(envelope.NotFoundError, m.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.macro}"), nil)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns all macros.
|
// GetAll returns all macros.
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ ORDER BY
|
|||||||
INSERT INTO
|
INSERT INTO
|
||||||
macros (name, message_content, user_id, team_id, visibility, visible_when, actions)
|
macros (name, message_content, user_id, team_id, visibility, visible_when, actions)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6, $7);
|
($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update
|
-- name: update
|
||||||
UPDATE
|
UPDATE
|
||||||
@@ -53,7 +54,8 @@ SET
|
|||||||
actions = $8,
|
actions = $8,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = $1;
|
id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete
|
-- name: delete
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
|
|||||||
@@ -125,28 +125,30 @@ func (o *Manager) GetAllEnabled() ([]models.OIDC, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create adds a new oidc.
|
// Create adds a new oidc.
|
||||||
func (o *Manager) Create(oidc models.OIDC) error {
|
func (o *Manager) Create(oidc models.OIDC) (models.OIDC, error) {
|
||||||
if _, err := o.q.InsertOIDC.Exec(oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret); err != nil {
|
var createdOIDC models.OIDC
|
||||||
|
if err := o.q.InsertOIDC.Get(&createdOIDC, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret); err != nil {
|
||||||
o.lo.Error("error inserting oidc", "error", err)
|
o.lo.Error("error inserting oidc", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, o.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.oidcProvider}"), nil)
|
return models.OIDC{}, envelope.NewError(envelope.GeneralError, o.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.oidcProvider}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return createdOIDC, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a oidc by id.
|
// Update updates a oidc by id.
|
||||||
func (o *Manager) Update(id int, oidc models.OIDC) error {
|
func (o *Manager) Update(id int, oidc models.OIDC) (models.OIDC, error) {
|
||||||
current, err := o.Get(id, true)
|
current, err := o.Get(id, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return models.OIDC{}, err
|
||||||
}
|
}
|
||||||
if oidc.ClientSecret == "" {
|
if oidc.ClientSecret == "" {
|
||||||
oidc.ClientSecret = current.ClientSecret
|
oidc.ClientSecret = current.ClientSecret
|
||||||
}
|
}
|
||||||
if _, err := o.q.UpdateOIDC.Exec(id, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.Enabled); err != nil {
|
var updatedOIDC models.OIDC
|
||||||
|
if err := o.q.UpdateOIDC.Get(&updatedOIDC, id, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.Enabled); err != nil {
|
||||||
o.lo.Error("error updating oidc", "error", err)
|
o.lo.Error("error updating oidc", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, o.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.oidcProvider}"), nil)
|
return models.OIDC{}, envelope.NewError(envelope.GeneralError, o.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.oidcProvider}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return updatedOIDC, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a oidc by its id.
|
// Delete deletes a oidc by its id.
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ SELECT * FROM oidc WHERE id = $1;
|
|||||||
|
|
||||||
-- name: insert-oidc
|
-- name: insert-oidc
|
||||||
INSERT INTO oidc (name, provider, provider_url, client_id, client_secret)
|
INSERT INTO oidc (name, provider, provider_url, client_id, client_secret)
|
||||||
VALUES ($1, $2, $3, $4, $5);
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update-oidc
|
-- name: update-oidc
|
||||||
UPDATE oidc
|
UPDATE oidc
|
||||||
SET name = $2, provider = $3, provider_url = $4, client_id = $5, client_secret = $6, enabled = $7, updated_at = now()
|
SET name = $2, provider = $3, provider_url = $4, client_id = $5, client_secret = $6, enabled = $7, updated_at = now()
|
||||||
WHERE id = $1;
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-oidc
|
-- name: delete-oidc
|
||||||
DELETE FROM oidc WHERE id = $1;
|
DELETE FROM oidc WHERE id = $1;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SELECT * FROM roles where id = $1;
|
|||||||
DELETE FROM roles where id = $1;
|
DELETE FROM roles where id = $1;
|
||||||
|
|
||||||
-- name: insert-role
|
-- name: insert-role
|
||||||
INSERT INTO roles (name, description, permissions) VALUES ($1, $2, $3);
|
INSERT INTO roles (name, description, permissions) VALUES ($1, $2, $3) RETURNING *;
|
||||||
|
|
||||||
-- name: update-role
|
-- name: update-role
|
||||||
UPDATE roles SET name = $2, description = $3, permissions = $4 WHERE id = $1;
|
UPDATE roles SET name = $2, description = $3, permissions = $4 WHERE id = $1 RETURNING *;
|
||||||
@@ -101,47 +101,49 @@ func (u *Manager) Delete(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new role.
|
// Create creates a new role.
|
||||||
func (u *Manager) Create(r models.Role) error {
|
func (u *Manager) Create(r models.Role) (models.Role, error) {
|
||||||
validPermissions, err := u.filterValidPermissions(r.Permissions)
|
validPermissions, err := u.filterValidPermissions(r.Permissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return models.Role{}, err
|
||||||
}
|
}
|
||||||
if len(validPermissions) == 0 {
|
if len(validPermissions) == 0 {
|
||||||
return envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.empty", "name", u.i18n.P("globals.terms.permission")), nil)
|
return models.Role{}, envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.empty", "name", u.i18n.P("globals.terms.permission")), nil)
|
||||||
}
|
}
|
||||||
if _, err := u.q.Insert.Exec(r.Name, r.Description, pq.Array(validPermissions)); err != nil {
|
var result models.Role
|
||||||
|
if err := u.q.Insert.Get(&result, r.Name, r.Description, pq.Array(validPermissions)); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) {
|
if dbutil.IsUniqueViolationError(err) {
|
||||||
return envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.role}"), nil)
|
return models.Role{}, envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.role}"), nil)
|
||||||
}
|
}
|
||||||
u.lo.Error("error inserting role", "error", err)
|
u.lo.Error("error inserting role", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.role}"), nil)
|
return models.Role{}, envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.role}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an existing role.
|
// Update updates an existing role.
|
||||||
func (u *Manager) Update(id int, r models.Role) error {
|
func (u *Manager) Update(id int, r models.Role) (models.Role, error) {
|
||||||
validPermissions, err := u.filterValidPermissions(r.Permissions)
|
validPermissions, err := u.filterValidPermissions(r.Permissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return models.Role{}, err
|
||||||
}
|
}
|
||||||
if len(validPermissions) == 0 {
|
if len(validPermissions) == 0 {
|
||||||
return envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.empty", "name", u.i18n.P("globals.terms.permission")), nil)
|
return models.Role{}, envelope.NewError(envelope.InputError, u.i18n.Ts("globals.messages.empty", "name", u.i18n.P("globals.terms.permission")), nil)
|
||||||
}
|
}
|
||||||
// Disallow updating `Admin` role, as the main System login requires it.
|
// Disallow updating `Admin` role, as the main System login requires it.
|
||||||
role, err := u.Get(id)
|
role, err := u.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return models.Role{}, err
|
||||||
}
|
}
|
||||||
if role.Name == models.RoleAdmin {
|
if role.Name == models.RoleAdmin {
|
||||||
return envelope.NewError(envelope.InputError, u.i18n.T("admin.role.cannotModifyAdminRole"), nil)
|
return models.Role{}, envelope.NewError(envelope.InputError, u.i18n.T("admin.role.cannotModifyAdminRole"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := u.q.Update.Exec(id, r.Name, r.Description, pq.Array(validPermissions)); err != nil {
|
var result models.Role
|
||||||
|
if err := u.q.Update.Get(&result, id, r.Name, r.Description, pq.Array(validPermissions)); err != nil {
|
||||||
u.lo.Error("error updating role", "error", err)
|
u.lo.Error("error updating role", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.role}"), nil)
|
return models.Role{}, envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.role}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterValidPermissions filters out invalid permissions, logs warnings for unknown permissions.
|
// filterValidPermissions filters out invalid permissions, logs warnings for unknown permissions.
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ INSERT INTO sla_policies (
|
|||||||
resolution_time,
|
resolution_time,
|
||||||
next_response_time,
|
next_response_time,
|
||||||
notifications
|
notifications
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6);
|
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update-sla-policy
|
-- name: update-sla-policy
|
||||||
UPDATE sla_policies SET
|
UPDATE sla_policies SET
|
||||||
@@ -23,7 +24,8 @@ UPDATE sla_policies SET
|
|||||||
next_response_time = $6,
|
next_response_time = $6,
|
||||||
notifications = $7,
|
notifications = $7,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1;
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-sla-policy
|
-- name: delete-sla-policy
|
||||||
DELETE FROM sla_policies WHERE id = $1;
|
DELETE FROM sla_policies WHERE id = $1;
|
||||||
|
|||||||
@@ -186,21 +186,23 @@ func (m *Manager) GetAll() ([]models.SLAPolicy, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new SLA policy.
|
// Create creates a new SLA policy.
|
||||||
func (m *Manager) Create(name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) error {
|
func (m *Manager) Create(name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) (models.SLAPolicy, error) {
|
||||||
if _, err := m.q.InsertSLAPolicy.Exec(name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
|
var result models.SLAPolicy
|
||||||
|
if err := m.q.InsertSLAPolicy.Get(&result, name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
|
||||||
m.lo.Error("error inserting SLA", "error", err)
|
m.lo.Error("error inserting SLA", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.sla}"), nil)
|
return models.SLAPolicy{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.sla}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a SLA policy.
|
// Update updates a SLA policy.
|
||||||
func (m *Manager) Update(id int, name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) error {
|
func (m *Manager) Update(id int, name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) (models.SLAPolicy, error) {
|
||||||
if _, err := m.q.UpdateSLAPolicy.Exec(id, name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
|
var result models.SLAPolicy
|
||||||
|
if err := m.q.UpdateSLAPolicy.Get(&result, id, name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
|
||||||
m.lo.Error("error updating SLA", "error", err)
|
m.lo.Error("error updating SLA", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.sla}"), nil)
|
return models.SLAPolicy{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.sla}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes an SLA policy.
|
// Delete deletes an SLA policy.
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from
|
|||||||
INSERT into
|
INSERT into
|
||||||
tags (name)
|
tags (name)
|
||||||
values
|
values
|
||||||
($1);
|
($1)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-tag
|
-- name: delete-tag
|
||||||
DELETE from
|
DELETE from
|
||||||
@@ -26,4 +27,5 @@ set
|
|||||||
name = $2,
|
name = $2,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
where
|
where
|
||||||
id = $1;
|
id = $1
|
||||||
|
RETURNING *;
|
||||||
@@ -64,15 +64,16 @@ func (t *Manager) GetAll() ([]models.Tag, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new tag.
|
// Create creates a new tag.
|
||||||
func (t *Manager) Create(name string) error {
|
func (t *Manager) Create(name string) (models.Tag, error) {
|
||||||
if _, err := t.q.InsertTag.Exec(name); err != nil {
|
var tag models.Tag
|
||||||
|
if err := t.q.InsertTag.Get(&tag, name); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) {
|
if dbutil.IsUniqueViolationError(err) {
|
||||||
return envelope.NewError(envelope.ConflictError, t.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.tag}"), nil)
|
return tag, envelope.NewError(envelope.ConflictError, t.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.tag}"), nil)
|
||||||
}
|
}
|
||||||
t.lo.Error("error inserting tag", "error", err)
|
t.lo.Error("error inserting tag", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, t.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.tag}"), nil)
|
return tag, envelope.NewError(envelope.GeneralError, t.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.tag}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a tag by ID.
|
// Delete deletes a tag by ID.
|
||||||
@@ -85,10 +86,11 @@ func (t *Manager) Delete(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a tag by id.
|
// Update updates a tag by id.
|
||||||
func (t *Manager) Update(id int, name string) error {
|
func (t *Manager) Update(id int, name string) (models.Tag, error) {
|
||||||
if _, err := t.q.UpdateTag.Exec(id, name); err != nil {
|
var tag models.Tag
|
||||||
|
if err := t.q.UpdateTag.Get(&tag, id, name); err != nil {
|
||||||
t.lo.Error("error updating tag", "error", err)
|
t.lo.Error("error updating tag", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, t.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
|
return tag, envelope.NewError(envelope.GeneralError, t.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ JOIN teams t ON t.id = tm.team_id
|
|||||||
WHERE t.id = $1 AND u.deleted_at IS NULL AND u.type = 'agent' AND u.enabled = true;
|
WHERE t.id = $1 AND u.deleted_at IS NULL AND u.type = 'agent' AND u.enabled = true;
|
||||||
|
|
||||||
-- name: insert-team
|
-- name: insert-team
|
||||||
INSERT INTO teams (name, timezone, conversation_assignment_type, business_hours_id, sla_policy_id, emoji, max_auto_assigned_conversations) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id;
|
INSERT INTO teams (name, timezone, conversation_assignment_type, business_hours_id, sla_policy_id, emoji, max_auto_assigned_conversations) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;
|
||||||
|
|
||||||
-- name: update-team
|
-- name: update-team
|
||||||
UPDATE teams set name = $2, timezone = $3, conversation_assignment_type = $4, business_hours_id = $5, sla_policy_id = $6, emoji = $7, max_auto_assigned_conversations = $8, updated_at = now() where id = $1;
|
UPDATE teams set name = $2, timezone = $3, conversation_assignment_type = $4, business_hours_id = $5, sla_policy_id = $6, emoji = $7, max_auto_assigned_conversations = $8, updated_at = now() where id = $1 RETURNING *;
|
||||||
|
|
||||||
-- name: upsert-user-teams
|
-- name: upsert-user-teams
|
||||||
WITH delete_old_teams AS (
|
WITH delete_old_teams AS (
|
||||||
|
|||||||
@@ -105,24 +105,26 @@ func (u *Manager) Get(id int) (models.Team, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new team.
|
// Create creates a new team.
|
||||||
func (u *Manager) Create(name, timezone, conversationAssignmentType string, businessHrsID, slaPolicyID null.Int, emoji string, maxAutoAssignedConversations int) error {
|
func (u *Manager) Create(name, timezone, conversationAssignmentType string, businessHrsID, slaPolicyID null.Int, emoji string, maxAutoAssignedConversations int) (models.Team, error) {
|
||||||
if _, err := u.q.InsertTeam.Exec(name, timezone, conversationAssignmentType, businessHrsID, slaPolicyID, emoji, maxAutoAssignedConversations); err != nil {
|
var team models.Team
|
||||||
|
if err := u.q.InsertTeam.Get(&team, name, timezone, conversationAssignmentType, businessHrsID, slaPolicyID, emoji, maxAutoAssignedConversations); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) {
|
if dbutil.IsUniqueViolationError(err) {
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.team}"), nil)
|
return team, envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorAlreadyExists", "name", "{globals.terms.team}"), nil)
|
||||||
}
|
}
|
||||||
u.lo.Error("error inserting team", "error", err)
|
u.lo.Error("error inserting team", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.team}"), nil)
|
return team, envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.team}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return team, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an existing team.
|
// Update updates an existing team.
|
||||||
func (u *Manager) Update(id int, name, timezone, conversationAssignmentType string, businessHrsID, slaPolicyID null.Int, emoji string, maxAutoAssignedConversations int) error {
|
func (u *Manager) Update(id int, name, timezone, conversationAssignmentType string, businessHrsID, slaPolicyID null.Int, emoji string, maxAutoAssignedConversations int) (models.Team, error) {
|
||||||
if _, err := u.q.UpdateTeam.Exec(id, name, timezone, conversationAssignmentType, businessHrsID, slaPolicyID, emoji, maxAutoAssignedConversations); err != nil {
|
var team models.Team
|
||||||
|
if err := u.q.UpdateTeam.Get(&team, id, name, timezone, conversationAssignmentType, businessHrsID, slaPolicyID, emoji, maxAutoAssignedConversations); err != nil {
|
||||||
u.lo.Error("error updating team", "error", err)
|
u.lo.Error("error updating team", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.team}"), nil)
|
return team, envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.team}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return team, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a team by ID also deletes all the team members and unassigns all the conversations belonging to the team.
|
// Delete deletes a team by ID also deletes all the team members and unassigns all the conversations belonging to the team.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
-- name: insert
|
-- name: insert
|
||||||
INSERT INTO templates ("name", body, is_default, subject, type)
|
INSERT INTO templates ("name", body, is_default, subject, type)
|
||||||
VALUES ($1, $2, $3, $4, $5);
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update
|
-- name: update
|
||||||
WITH u AS (
|
WITH u AS (
|
||||||
@@ -13,11 +14,9 @@ WITH u AS (
|
|||||||
type = $6::template_type,
|
type = $6::template_type,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id
|
RETURNING *
|
||||||
)
|
)
|
||||||
UPDATE templates
|
SELECT * FROM u LIMIT 1;
|
||||||
SET is_default = FALSE
|
|
||||||
WHERE id != $1 AND $4 = TRUE;
|
|
||||||
|
|
||||||
-- name: get-default
|
-- name: get-default
|
||||||
SELECT id, type, name, body, subject FROM templates WHERE is_default is TRUE;
|
SELECT id, type, name, body, subject FROM templates WHERE is_default is TRUE;
|
||||||
|
|||||||
@@ -66,27 +66,29 @@ func New(lo *logf.Logger, db *sqlx.DB, webTpls *template.Template, tpls *templat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a new template with the given name, and body.
|
// Update updates a new template with the given name, and body.
|
||||||
func (m *Manager) Update(id int, t models.Template) error {
|
func (m *Manager) Update(id int, t models.Template) (models.Template, error) {
|
||||||
if _, err := m.q.UpdateTemplate.Exec(id, t.Name, t.Body, t.IsDefault, t.Subject, t.Type); err != nil {
|
var result models.Template
|
||||||
|
if err := m.q.UpdateTemplate.Get(&result, id, t.Name, t.Body, t.IsDefault, t.Subject, t.Type); err != nil {
|
||||||
m.lo.Error("error updating template", "error", err)
|
m.lo.Error("error updating template", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.template}"), nil)
|
return models.Template{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.template}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a template.
|
// Create creates a template.
|
||||||
func (m *Manager) Create(t models.Template) error {
|
func (m *Manager) Create(t models.Template) (models.Template, error) {
|
||||||
if t.IsDefault {
|
if t.IsDefault {
|
||||||
t.Type = TypeEmailOutgoing
|
t.Type = TypeEmailOutgoing
|
||||||
}
|
}
|
||||||
if _, err := m.q.InsertTemplate.Exec(t.Name, t.Body, t.IsDefault, t.Subject, t.Type); err != nil {
|
var result models.Template
|
||||||
|
if err := m.q.InsertTemplate.Get(&result, t.Name, t.Body, t.IsDefault, t.Subject, t.Type); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) && t.IsDefault {
|
if dbutil.IsUniqueViolationError(err) && t.IsDefault {
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.T("template.defaultTemplateAlreadyExists"), nil)
|
return models.Template{}, envelope.NewError(envelope.GeneralError, m.i18n.T("template.defaultTemplateAlreadyExists"), nil)
|
||||||
}
|
}
|
||||||
m.lo.Error("error inserting template", "error", err)
|
m.lo.Error("error inserting template", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.template}"), nil)
|
return models.Template{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.template}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns all templates by type.
|
// GetAll returns all templates by type.
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ FROM views WHERE user_id = $1;
|
|||||||
|
|
||||||
-- name: insert-view
|
-- name: insert-view
|
||||||
INSERT INTO views (name, filters, user_id)
|
INSERT INTO views (name, filters, user_id)
|
||||||
VALUES ($1, $2, $3);
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-view
|
-- name: delete-view
|
||||||
DELETE FROM views
|
DELETE FROM views
|
||||||
@@ -18,3 +19,4 @@ WHERE id = $1;
|
|||||||
UPDATE views
|
UPDATE views
|
||||||
SET name = $2, filters = $3, updated_at = NOW()
|
SET name = $2, filters = $3, updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
|
RETURNING *
|
||||||
|
|||||||
@@ -77,21 +77,23 @@ func (v *Manager) GetUsersViews(userID int) ([]models.View, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new view.
|
// Create creates a new view.
|
||||||
func (v *Manager) Create(name string, filter []byte, userID int) error {
|
func (v *Manager) Create(name string, filter []byte, userID int) (models.View, error) {
|
||||||
if _, err := v.q.InsertView.Exec(name, filter, userID); err != nil {
|
var createdView models.View
|
||||||
|
if err := v.q.InsertView.Get(&createdView, name, filter, userID); err != nil {
|
||||||
v.lo.Error("error inserting view", "error", err)
|
v.lo.Error("error inserting view", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, v.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.view}"), nil)
|
return models.View{}, envelope.NewError(envelope.GeneralError, v.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.view}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return createdView, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a view by id.
|
// Update updates a view by id.
|
||||||
func (v *Manager) Update(id int, name string, filter []byte) error {
|
func (v *Manager) Update(id int, name string, filter []byte) (models.View, error) {
|
||||||
if _, err := v.q.UpdateView.Exec(id, name, filter); err != nil {
|
var updatedView models.View
|
||||||
|
if err := v.q.UpdateView.Get(&updatedView, id, name, filter); err != nil {
|
||||||
v.lo.Error("error updating view", "error", err)
|
v.lo.Error("error updating view", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, v.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.view}"), nil)
|
return models.View{}, envelope.NewError(envelope.GeneralError, v.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.view}"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return updatedView, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a view by ID.
|
// Delete deletes a view by ID.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Webhook struct {
|
|||||||
Name string `db:"name" json:"name"`
|
Name string `db:"name" json:"name"`
|
||||||
URL string `db:"url" json:"url"`
|
URL string `db:"url" json:"url"`
|
||||||
Events pq.StringArray `db:"events" json:"events"`
|
Events pq.StringArray `db:"events" json:"events"`
|
||||||
Secret string `db:"secret" json:"secret,omitempty"`
|
Secret string `db:"secret" json:"secret"`
|
||||||
IsActive bool `db:"is_active" json:"is_active"`
|
IsActive bool `db:"is_active" json:"is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ INSERT INTO
|
|||||||
webhooks (name, url, events, secret, is_active)
|
webhooks (name, url, events, secret, is_active)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5)
|
($1, $2, $3, $4, $5)
|
||||||
RETURNING id;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: update-webhook
|
-- name: update-webhook
|
||||||
UPDATE
|
UPDATE
|
||||||
@@ -77,7 +77,8 @@ SET
|
|||||||
is_active = $6,
|
is_active = $6,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = $1;
|
id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
-- name: delete-webhook
|
-- name: delete-webhook
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
@@ -92,4 +93,5 @@ SET
|
|||||||
is_active = NOT is_active,
|
is_active = NOT is_active,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = $1;
|
id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func New(opts Opts) (*Manager, error) {
|
|||||||
db: opts.DB,
|
db: opts.DB,
|
||||||
deliveryQueue: make(chan DeliveryTask, opts.QueueSize),
|
deliveryQueue: make(chan DeliveryTask, opts.QueueSize),
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: opts.Timeout,
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
Timeout: 3 * time.Second,
|
Timeout: 3 * time.Second,
|
||||||
@@ -127,25 +127,26 @@ func (m *Manager) Get(id int) (models.Webhook, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new webhook.
|
// Create creates a new webhook.
|
||||||
func (m *Manager) Create(webhook models.Webhook) (int, error) {
|
func (m *Manager) Create(webhook models.Webhook) (models.Webhook, error) {
|
||||||
var id int
|
var result models.Webhook
|
||||||
if err := m.q.InsertWebhook.Get(&id, webhook.Name, webhook.URL, pq.Array(webhook.Events), webhook.Secret, webhook.IsActive); err != nil {
|
if err := m.q.InsertWebhook.Get(&result, webhook.Name, webhook.URL, pq.Array(webhook.Events), webhook.Secret, webhook.IsActive); err != nil {
|
||||||
if dbutil.IsUniqueViolationError(err) {
|
if dbutil.IsUniqueViolationError(err) {
|
||||||
return 0, envelope.NewError(envelope.ConflictError, m.i18n.Ts("globals.messages.errorAlreadyExists", "name", "webhook"), nil)
|
return models.Webhook{}, envelope.NewError(envelope.ConflictError, m.i18n.Ts("globals.messages.errorAlreadyExists", "name", "webhook"), nil)
|
||||||
}
|
}
|
||||||
m.lo.Error("error inserting webhook", "error", err)
|
m.lo.Error("error inserting webhook", "error", err)
|
||||||
return 0, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "webhook"), nil)
|
return models.Webhook{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "webhook"), nil)
|
||||||
}
|
}
|
||||||
return id, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a webhook by ID.
|
// Update updates a webhook by ID.
|
||||||
func (m *Manager) Update(id int, webhook models.Webhook) error {
|
func (m *Manager) Update(id int, webhook models.Webhook) (models.Webhook, error) {
|
||||||
if _, err := m.q.UpdateWebhook.Exec(id, webhook.Name, webhook.URL, pq.Array(webhook.Events), webhook.Secret, webhook.IsActive); err != nil {
|
var result models.Webhook
|
||||||
|
if err := m.q.UpdateWebhook.Get(&result, id, webhook.Name, webhook.URL, pq.Array(webhook.Events), webhook.Secret, webhook.IsActive); err != nil {
|
||||||
m.lo.Error("error updating webhook", "error", err)
|
m.lo.Error("error updating webhook", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "webhook"), nil)
|
return models.Webhook{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "webhook"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a webhook by ID.
|
// Delete deletes a webhook by ID.
|
||||||
@@ -158,12 +159,13 @@ func (m *Manager) Delete(id int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggle toggles the active status of a webhook by ID.
|
// Toggle toggles the active status of a webhook by ID.
|
||||||
func (m *Manager) Toggle(id int) error {
|
func (m *Manager) Toggle(id int) (models.Webhook, error) {
|
||||||
if _, err := m.q.ToggleWebhook.Exec(id); err != nil {
|
var result models.Webhook
|
||||||
|
if err := m.q.ToggleWebhook.Get(&result, id); err != nil {
|
||||||
m.lo.Error("error toggling webhook", "error", err)
|
m.lo.Error("error toggling webhook", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "webhook"), nil)
|
return models.Webhook{}, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "webhook"), nil)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTestWebhook sends a test webhook to the specified webhook ID.
|
// SendTestWebhook sends a test webhook to the specified webhook ID.
|
||||||
|
|||||||
Reference in New Issue
Block a user