Skip to content

Commit 8083ad9

Browse files
authored
Merge pull request #59 from abhinavxd/feat/manage-contacts
Feat - Manage contacts tab
2 parents 7957dbb + ad99dee commit 8083ad9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2159
-435
lines changed

cmd/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func handleOIDCCallback(r *fastglue.Request) error {
7777
}
7878

7979
// Lookup the user by email and set the session.
80-
user, err := app.user.GetAgentByEmail(claims.Email)
80+
user, err := app.user.GetAgent(0, claims.Email)
8181
if err != nil {
8282
return sendErrorEnvelope(r, err)
8383
}

cmd/contacts.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"path/filepath"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/abhinavxd/libredesk/internal/envelope"
9+
"github.com/abhinavxd/libredesk/internal/stringutil"
10+
"github.com/abhinavxd/libredesk/internal/user/models"
11+
"github.com/valyala/fasthttp"
12+
"github.com/volatiletech/null/v9"
13+
"github.com/zerodha/fastglue"
14+
)
15+
16+
// handleGetContacts returns a list of contacts from the database.
17+
func handleGetContacts(r *fastglue.Request) error {
18+
var (
19+
app = r.Context.(*App)
20+
order = string(r.RequestCtx.QueryArgs().Peek("order"))
21+
orderBy = string(r.RequestCtx.QueryArgs().Peek("order_by"))
22+
filters = string(r.RequestCtx.QueryArgs().Peek("filters"))
23+
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
24+
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
25+
total = 0
26+
)
27+
contacts, err := app.user.GetContacts(page, pageSize, order, orderBy, filters)
28+
if err != nil {
29+
return sendErrorEnvelope(r, err)
30+
}
31+
if len(contacts) > 0 {
32+
total = contacts[0].Total
33+
}
34+
return r.SendEnvelope(envelope.PageResults{
35+
Results: contacts,
36+
Total: total,
37+
PerPage: pageSize,
38+
TotalPages: (total + pageSize - 1) / pageSize,
39+
Page: page,
40+
})
41+
}
42+
43+
// handleGetTags returns a contact from the database.
44+
func handleGetContact(r *fastglue.Request) error {
45+
var (
46+
app = r.Context.(*App)
47+
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
48+
)
49+
if id <= 0 {
50+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
51+
}
52+
c, err := app.user.GetContact(id, "")
53+
if err != nil {
54+
return sendErrorEnvelope(r, err)
55+
}
56+
return r.SendEnvelope(c)
57+
}
58+
59+
// handleUpdateContact updates a contact in the database.
60+
func handleUpdateContact(r *fastglue.Request) error {
61+
var (
62+
app = r.Context.(*App)
63+
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
64+
)
65+
if id <= 0 {
66+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
67+
}
68+
69+
contact, err := app.user.GetContact(id, "")
70+
if err != nil {
71+
return sendErrorEnvelope(r, err)
72+
}
73+
74+
form, err := r.RequestCtx.MultipartForm()
75+
if err != nil {
76+
app.lo.Error("error parsing form data", "error", err)
77+
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.GeneralError)
78+
}
79+
80+
// Parse form data
81+
firstName := ""
82+
if v, ok := form.Value["first_name"]; ok && len(v) > 0 {
83+
firstName = string(v[0])
84+
}
85+
lastName := ""
86+
if v, ok := form.Value["last_name"]; ok && len(v) > 0 {
87+
lastName = string(v[0])
88+
}
89+
email := ""
90+
if v, ok := form.Value["email"]; ok && len(v) > 0 {
91+
email = strings.TrimSpace(string(v[0]))
92+
}
93+
phoneNumber := ""
94+
if v, ok := form.Value["phone_number"]; ok && len(v) > 0 {
95+
phoneNumber = string(v[0])
96+
}
97+
phoneNumberCallingCode := ""
98+
if v, ok := form.Value["phone_number_calling_code"]; ok && len(v) > 0 {
99+
phoneNumberCallingCode = string(v[0])
100+
}
101+
enabled := false
102+
if v, ok := form.Value["enabled"]; ok && len(v) > 0 {
103+
enabled = string(v[0]) == "true"
104+
}
105+
avatarURL := ""
106+
if v, ok := form.Value["avatar_url"]; ok && len(v) > 0 {
107+
avatarURL = string(v[0])
108+
}
109+
110+
// Validate mandatory fields.
111+
if email == "" {
112+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "email"), nil, envelope.InputError)
113+
}
114+
if !stringutil.ValidEmail(email) {
115+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "email"), nil, envelope.InputError)
116+
}
117+
if firstName == "" {
118+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "first_name"), nil, envelope.InputError)
119+
}
120+
121+
// Another contact with same new email?
122+
existingContact, _ := app.user.GetContact(0, email)
123+
if existingContact.ID > 0 && existingContact.ID != id {
124+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("contact.alreadyExistsWithEmail"), nil, envelope.InputError)
125+
}
126+
127+
contactToUpdate := models.User{
128+
FirstName: firstName,
129+
LastName: lastName,
130+
Email: null.StringFrom(email),
131+
AvatarURL: null.NewString(avatarURL, avatarURL != ""),
132+
PhoneNumber: null.NewString(phoneNumber, phoneNumber != ""),
133+
PhoneNumberCallingCode: null.NewString(phoneNumberCallingCode, phoneNumberCallingCode != ""),
134+
Enabled: enabled,
135+
}
136+
137+
if err := app.user.UpdateContact(id, contactToUpdate); err != nil {
138+
return sendErrorEnvelope(r, err)
139+
}
140+
141+
// Delete avatar?
142+
if avatarURL == "" && contact.AvatarURL.Valid {
143+
fileName := filepath.Base(contact.AvatarURL.String)
144+
app.media.Delete(fileName)
145+
contact.AvatarURL.Valid = false
146+
contact.AvatarURL.String = ""
147+
}
148+
149+
// Upload avatar?
150+
files, ok := form.File["files"]
151+
if ok && len(files) > 0 {
152+
if err := uploadUserAvatar(r, &contact, files); err != nil {
153+
return sendErrorEnvelope(r, err)
154+
}
155+
}
156+
return r.SendEnvelope(true)
157+
}

cmd/conversation.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func handleGetViewConversations(r *fastglue.Request) error {
130130
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.T("conversation.viewPermissionDenied"), nil, envelope.PermissionError)
131131
}
132132

133-
user, err := app.user.GetAgent(auser.ID)
133+
user, err := app.user.GetAgent(auser.ID, "")
134134
if err != nil {
135135
return sendErrorEnvelope(r, err)
136136
}
@@ -229,7 +229,7 @@ func handleGetConversation(r *fastglue.Request) error {
229229
auser = r.RequestCtx.UserValue("user").(amodels.User)
230230
)
231231

232-
user, err := app.user.GetAgent(auser.ID)
232+
user, err := app.user.GetAgent(auser.ID, "")
233233
if err != nil {
234234
return sendErrorEnvelope(r, err)
235235
}
@@ -251,7 +251,7 @@ func handleUpdateConversationAssigneeLastSeen(r *fastglue.Request) error {
251251
uuid = r.RequestCtx.UserValue("uuid").(string)
252252
auser = r.RequestCtx.UserValue("user").(amodels.User)
253253
)
254-
user, err := app.user.GetAgent(auser.ID)
254+
user, err := app.user.GetAgent(auser.ID, "")
255255
if err != nil {
256256
return sendErrorEnvelope(r, err)
257257
}
@@ -272,7 +272,7 @@ func handleGetConversationParticipants(r *fastglue.Request) error {
272272
uuid = r.RequestCtx.UserValue("uuid").(string)
273273
auser = r.RequestCtx.UserValue("user").(amodels.User)
274274
)
275-
user, err := app.user.GetAgent(auser.ID)
275+
user, err := app.user.GetAgent(auser.ID, "")
276276
if err != nil {
277277
return sendErrorEnvelope(r, err)
278278
}
@@ -299,7 +299,7 @@ func handleUpdateUserAssignee(r *fastglue.Request) error {
299299
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`assignee_id`"), nil, envelope.InputError)
300300
}
301301

302-
user, err := app.user.GetAgent(auser.ID)
302+
user, err := app.user.GetAgent(auser.ID, "")
303303
if err != nil {
304304
return sendErrorEnvelope(r, err)
305305
}
@@ -331,7 +331,7 @@ func handleUpdateTeamAssignee(r *fastglue.Request) error {
331331
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`assignee_id`"), nil, envelope.InputError)
332332
}
333333

334-
user, err := app.user.GetAgent(auser.ID)
334+
user, err := app.user.GetAgent(auser.ID, "")
335335
if err != nil {
336336
return sendErrorEnvelope(r, err)
337337
}
@@ -367,7 +367,7 @@ func handleUpdateConversationPriority(r *fastglue.Request) error {
367367
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`priority`"), nil, envelope.InputError)
368368
}
369369

370-
user, err := app.user.GetAgent(auser.ID)
370+
user, err := app.user.GetAgent(auser.ID, "")
371371
if err != nil {
372372
return sendErrorEnvelope(r, err)
373373
}
@@ -410,7 +410,7 @@ func handleUpdateConversationStatus(r *fastglue.Request) error {
410410
}
411411

412412
// Enforce conversation access.
413-
user, err := app.user.GetAgent(auser.ID)
413+
user, err := app.user.GetAgent(auser.ID, "")
414414
if err != nil {
415415
return sendErrorEnvelope(r, err)
416416
}
@@ -463,7 +463,7 @@ func handleUpdateConversationtags(r *fastglue.Request) error {
463463
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
464464
}
465465

466-
user, err := app.user.GetAgent(auser.ID)
466+
user, err := app.user.GetAgent(auser.ID, "")
467467
if err != nil {
468468
return sendErrorEnvelope(r, err)
469469
}
@@ -525,7 +525,7 @@ func handleRemoveUserAssignee(r *fastglue.Request) error {
525525
uuid = r.RequestCtx.UserValue("uuid").(string)
526526
auser = r.RequestCtx.UserValue("user").(amodels.User)
527527
)
528-
user, err := app.user.GetAgent(auser.ID)
528+
user, err := app.user.GetAgent(auser.ID, "")
529529
if err != nil {
530530
return sendErrorEnvelope(r, err)
531531
}
@@ -546,7 +546,7 @@ func handleRemoveTeamAssignee(r *fastglue.Request) error {
546546
uuid = r.RequestCtx.UserValue("uuid").(string)
547547
auser = r.RequestCtx.UserValue("user").(amodels.User)
548548
)
549-
user, err := app.user.GetAgent(auser.ID)
549+
user, err := app.user.GetAgent(auser.ID, "")
550550
if err != nil {
551551
return sendErrorEnvelope(r, err)
552552
}
@@ -601,7 +601,7 @@ func handleCreateConversation(r *fastglue.Request) error {
601601
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.fieldRequired", "name", "`first_name`"), nil, envelope.InputError)
602602
}
603603

604-
user, err := app.user.GetAgent(auser.ID)
604+
user, err := app.user.GetAgent(auser.ID, "")
605605
if err != nil {
606606
return sendErrorEnvelope(r, err)
607607
}

cmd/handlers.go

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
7979
g.DELETE("/api/v1/statuses/{id}", perm(handleDeleteStatus, "status:manage"))
8080
g.GET("/api/v1/priorities", auth(handleGetPriorities))
8181

82-
// Tag.
82+
// Tags.
8383
g.GET("/api/v1/tags", auth(handleGetTags))
8484
g.POST("/api/v1/tags", perm(handleCreateTag, "tags:manage"))
8585
g.PUT("/api/v1/tags/{id}", perm(handleUpdateTag, "tags:manage"))
@@ -93,22 +93,28 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
9393
g.DELETE("/api/v1/macros/{id}", perm(handleDeleteMacro, "macros:manage"))
9494
g.POST("/api/v1/conversations/{uuid}/macros/{id}/apply", auth(handleApplyMacro))
9595

96-
// User.
97-
g.GET("/api/v1/users/me", auth(handleGetCurrentUser))
98-
g.PUT("/api/v1/users/me", auth(handleUpdateCurrentUser))
99-
g.GET("/api/v1/users/me/teams", auth(handleGetCurrentUserTeams))
100-
g.PUT("/api/v1/users/me/availability", auth(handleUpdateUserAvailability))
101-
g.DELETE("/api/v1/users/me/avatar", auth(handleDeleteAvatar))
102-
g.GET("/api/v1/users/compact", auth(handleGetUsersCompact))
103-
g.GET("/api/v1/users", perm(handleGetUsers, "users:manage"))
104-
g.GET("/api/v1/users/{id}", perm(handleGetUser, "users:manage"))
105-
g.POST("/api/v1/users", perm(handleCreateUser, "users:manage"))
106-
g.PUT("/api/v1/users/{id}", perm(handleUpdateUser, "users:manage"))
107-
g.DELETE("/api/v1/users/{id}", perm(handleDeleteUser, "users:manage"))
108-
g.POST("/api/v1/users/reset-password", tryAuth(handleResetPassword))
109-
g.POST("/api/v1/users/set-password", tryAuth(handleSetPassword))
110-
111-
// Team.
96+
// Agents.
97+
g.GET("/api/v1/agents/me", auth(handleGetCurrentAgent))
98+
g.PUT("/api/v1/agents/me", auth(handleUpdateCurrentAgent))
99+
g.GET("/api/v1/agents/me/teams", auth(handleGetCurrentAgentTeams))
100+
g.PUT("/api/v1/agents/me/availability", auth(handleUpdateAgentAvailability))
101+
g.DELETE("/api/v1/agents/me/avatar", auth(handleDeleteCurrentAgentAvatar))
102+
103+
g.GET("/api/v1/agents/compact", auth(handleGetAgentsCompact))
104+
g.GET("/api/v1/agents", perm(handleGetAgents, "users:manage"))
105+
g.GET("/api/v1/agents/{id}", perm(handleGetAgent, "users:manage"))
106+
g.POST("/api/v1/agents", perm(handleCreateAgent, "users:manage"))
107+
g.PUT("/api/v1/agents/{id}", perm(handleUpdateAgent, "users:manage"))
108+
g.DELETE("/api/v1/agents/{id}", perm(handleDeleteAgent, "users:manage"))
109+
g.POST("/api/v1/agents/reset-password", tryAuth(handleResetPassword))
110+
g.POST("/api/v1/agents/set-password", tryAuth(handleSetPassword))
111+
112+
// Contacts.
113+
g.GET("/api/v1/contacts", perm(handleGetContacts, "contacts:manage"))
114+
g.GET("/api/v1/contacts/{id}", perm(handleGetContact, "contacts:manage"))
115+
g.PUT("/api/v1/contacts/{id}", perm(handleUpdateContact, "contacts:manage"))
116+
117+
// Teams.
112118
g.GET("/api/v1/teams/compact", auth(handleGetTeamsCompact))
113119
g.GET("/api/v1/teams", perm(handleGetTeams, "teams:manage"))
114120
g.GET("/api/v1/teams/{id}", perm(handleGetTeam, "teams:manage"))
@@ -119,36 +125,36 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
119125
// i18n.
120126
g.GET("/api/v1/lang/{lang}", handleGetI18nLang)
121127

122-
// Automation.
123-
g.GET("/api/v1/automation/rules", perm(handleGetAutomationRules, "automations:manage"))
124-
g.GET("/api/v1/automation/rules/{id}", perm(handleGetAutomationRule, "automations:manage"))
125-
g.POST("/api/v1/automation/rules", perm(handleCreateAutomationRule, "automations:manage"))
126-
g.PUT("/api/v1/automation/rules/{id}/toggle", perm(handleToggleAutomationRule, "automations:manage"))
127-
g.PUT("/api/v1/automation/rules/{id}", perm(handleUpdateAutomationRule, "automations:manage"))
128-
g.PUT("/api/v1/automation/rules/weights", perm(handleUpdateAutomationRuleWeights, "automations:manage"))
129-
g.PUT("/api/v1/automation/rules/execution-mode", perm(handleUpdateAutomationRuleExecutionMode, "automations:manage"))
130-
g.DELETE("/api/v1/automation/rules/{id}", perm(handleDeleteAutomationRule, "automations:manage"))
131-
132-
// Inbox.
128+
// Automations.
129+
g.GET("/api/v1/automations/rules", perm(handleGetAutomationRules, "automations:manage"))
130+
g.GET("/api/v1/automations/rules/{id}", perm(handleGetAutomationRule, "automations:manage"))
131+
g.POST("/api/v1/automations/rules", perm(handleCreateAutomationRule, "automations:manage"))
132+
g.PUT("/api/v1/automations/rules/{id}/toggle", perm(handleToggleAutomationRule, "automations:manage"))
133+
g.PUT("/api/v1/automations/rules/{id}", perm(handleUpdateAutomationRule, "automations:manage"))
134+
g.PUT("/api/v1/automations/rules/weights", perm(handleUpdateAutomationRuleWeights, "automations:manage"))
135+
g.PUT("/api/v1/automations/rules/execution-mode", perm(handleUpdateAutomationRuleExecutionMode, "automations:manage"))
136+
g.DELETE("/api/v1/automations/rules/{id}", perm(handleDeleteAutomationRule, "automations:manage"))
137+
138+
// Inboxes.
133139
g.GET("/api/v1/inboxes", auth(handleGetInboxes))
134140
g.GET("/api/v1/inboxes/{id}", perm(handleGetInbox, "inboxes:manage"))
135141
g.POST("/api/v1/inboxes", perm(handleCreateInbox, "inboxes:manage"))
136142
g.PUT("/api/v1/inboxes/{id}/toggle", perm(handleToggleInbox, "inboxes:manage"))
137143
g.PUT("/api/v1/inboxes/{id}", perm(handleUpdateInbox, "inboxes:manage"))
138144
g.DELETE("/api/v1/inboxes/{id}", perm(handleDeleteInbox, "inboxes:manage"))
139145

140-
// Role.
146+
// Roles.
141147
g.GET("/api/v1/roles", perm(handleGetRoles, "roles:manage"))
142148
g.GET("/api/v1/roles/{id}", perm(handleGetRole, "roles:manage"))
143149
g.POST("/api/v1/roles", perm(handleCreateRole, "roles:manage"))
144150
g.PUT("/api/v1/roles/{id}", perm(handleUpdateRole, "roles:manage"))
145151
g.DELETE("/api/v1/roles/{id}", perm(handleDeleteRole, "roles:manage"))
146152

147-
// Dashboard.
153+
// Reports.
148154
g.GET("/api/v1/reports/overview/counts", perm(handleDashboardCounts, "reports:manage"))
149155
g.GET("/api/v1/reports/overview/charts", perm(handleDashboardCharts, "reports:manage"))
150156

151-
// Template.
157+
// Templates.
152158
g.GET("/api/v1/templates", perm(handleGetTemplates, "templates:manage"))
153159
g.GET("/api/v1/templates/{id}", perm(handleGetTemplate, "templates:manage"))
154160
g.POST("/api/v1/templates", perm(handleCreateTemplate, "templates:manage"))
@@ -162,14 +168,14 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
162168
g.PUT("/api/v1/business-hours/{id}", perm(handleUpdateBusinessHours, "business_hours:manage"))
163169
g.DELETE("/api/v1/business-hours/{id}", perm(handleDeleteBusinessHour, "business_hours:manage"))
164170

165-
// SLA.
171+
// SLAs.
166172
g.GET("/api/v1/sla", perm(handleGetSLAs, "sla:manage"))
167173
g.GET("/api/v1/sla/{id}", perm(handleGetSLA, "sla:manage"))
168174
g.POST("/api/v1/sla", perm(handleCreateSLA, "sla:manage"))
169175
g.PUT("/api/v1/sla/{id}", perm(handleUpdateSLA, "sla:manage"))
170176
g.DELETE("/api/v1/sla/{id}", perm(handleDeleteSLA, "sla:manage"))
171177

172-
// AI completion.
178+
// AI completions.
173179
g.GET("/api/v1/ai/prompts", auth(handleGetAIPrompts))
174180
g.POST("/api/v1/ai/completion", auth(handleAICompletion))
175181
g.PUT("/api/v1/ai/provider", perm(handleUpdateAIProvider, "ai:manage"))

0 commit comments

Comments
 (0)