Skip to content

Commit 629471f

Browse files
committed
copy cmd + user already exists
1 parent 70c5b5f commit 629471f

9 files changed

Lines changed: 100 additions & 6 deletions

File tree

console/src/components/settings/WorkspaceMembers.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ export function WorkspaceMembers({
179179

180180
// Refresh the members list
181181
onMembersChange()
182-
} catch (error) {
182+
} catch (error: any) {
183183
console.error('Failed to create API key', error)
184-
message.error('Failed to create API key')
184+
message.error(error.message || 'Failed to create API key')
185185
} finally {
186186
setCreatingApiKey(false)
187187
}

console/src/pages/TransactionalNotificationsPage.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
faPaperPlane,
2626
faEye
2727
} from '@fortawesome/free-regular-svg-icons'
28-
import { faTerminal } from '@fortawesome/free-solid-svg-icons'
28+
import { faTerminal, faCopy } from '@fortawesome/free-solid-svg-icons'
2929
import UpsertTransactionalNotificationDrawer from '../components/transactional/UpsertTransactionalNotificationDrawer'
3030
import React, { useState } from 'react'
3131
import dayjs from '../lib/dayjs'
@@ -151,6 +151,43 @@ export function TransactionalNotificationsPage() {
151151
setApiModalOpen(true)
152152
}
153153

154+
const handleCopyCommand = () => {
155+
if (!currentApiNotification) return
156+
157+
const curlCommand = `curl -X POST \\
158+
"${window.location.origin}/api/transactional.send" \\
159+
-H "Content-Type: application/json" \\
160+
-H "Authorization: Bearer YOUR_API_KEY" \\
161+
-d '{
162+
"workspace_id": "${workspaceId}",
163+
"notification": {
164+
"id": "${currentApiNotification.id}",
165+
"channels": ["email"],
166+
"contact": {
167+
"email": "recipient@example.com"
168+
// other optional contact fields here
169+
},
170+
"data": {
171+
// Your template variables here
172+
},
173+
"email_options": {
174+
"reply_to": "reply@example.com",
175+
"cc": ["cc@example.com"],
176+
"bcc": ["bcc@example.com"]
177+
}
178+
}
179+
}'`
180+
181+
navigator.clipboard
182+
.writeText(curlCommand)
183+
.then(() => {
184+
message.success('Curl command copied to clipboard!')
185+
})
186+
.catch(() => {
187+
message.error('Failed to copy to clipboard')
188+
})
189+
}
190+
154191
if (notificationsError) {
155192
return (
156193
<div>
@@ -300,7 +337,20 @@ export function TransactionalNotificationsPage() {
300337
title="API Command"
301338
open={apiModalOpen}
302339
onCancel={() => setApiModalOpen(false)}
303-
footer={null}
340+
footer={[
341+
<Button
342+
key="copy"
343+
type="primary"
344+
ghost
345+
icon={<FontAwesomeIcon icon={faCopy} />}
346+
onClick={handleCopyCommand}
347+
>
348+
Copy
349+
</Button>,
350+
<Button key="close" onClick={() => setApiModalOpen(false)}>
351+
Close
352+
</Button>
353+
]}
304354
width={800}
305355
>
306356
{currentApiNotification && (

internal/domain/user.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ func (e *ErrUserNotFound) Error() string {
111111
return e.Message
112112
}
113113

114+
// ErrUserExists is returned when trying to create a user that already exists
115+
type ErrUserExists struct {
116+
Message string
117+
}
118+
119+
func (e *ErrUserExists) Error() string {
120+
return e.Message
121+
}
122+
114123
// ErrSessionNotFound is returned when a session is not found
115124
type ErrSessionNotFound struct {
116125
Message string

internal/domain/user_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ func TestErrUserNotFound_Error(t *testing.T) {
6363
assert.Equal(t, "test error", err.Error())
6464
}
6565

66+
func TestErrUserExists_Error(t *testing.T) {
67+
err := &ErrUserExists{Message: "user already exists"}
68+
assert.Equal(t, "user already exists", err.Error())
69+
}
70+
6671
func TestErrSessionNotFound_Error(t *testing.T) {
6772
err := &ErrSessionNotFound{Message: "test error"}
6873
assert.Equal(t, "test error", err.Error())

internal/http/workspace_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ func (h *WorkspaceHandler) handleCreateAPIKey(w http.ResponseWriter, r *http.Req
338338
return
339339
}
340340

341-
WriteJSONError(w, "Failed to create API key", http.StatusInternalServerError)
341+
WriteJSONError(w, err.Error(), http.StatusInternalServerError)
342342
return
343343
}
344344

internal/http/workspace_handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1312,7 +1312,7 @@ func TestWorkspaceHandler_HandleCreateAPIKey_ServiceError(t *testing.T) {
13121312
var response map[string]string
13131313
err = json.NewDecoder(w.Body).Decode(&response)
13141314
require.NoError(t, err)
1315-
assert.Equal(t, "Failed to create API key", response["error"])
1315+
assert.Equal(t, "service error", response["error"])
13161316
}
13171317

13181318
func TestWorkspaceHandler_HandleRemoveMember(t *testing.T) {

internal/repository/user_postgres.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"fmt"
7+
"strings"
78
"time"
89

910
"github.com/google/uuid"
@@ -46,6 +47,11 @@ func (r *userRepository) CreateUser(ctx context.Context, user *domain.User) erro
4647
user.UpdatedAt,
4748
)
4849
if err != nil {
50+
// Check for duplicate key constraint violation (PostgreSQL error code 23505)
51+
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") ||
52+
strings.Contains(err.Error(), "UNIQUE constraint failed") {
53+
return &domain.ErrUserExists{Message: "user already exists"}
54+
}
4955
return fmt.Errorf("failed to create user: %w", err)
5056
}
5157
return nil

internal/repository/user_postgres_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ func TestCreateUser(t *testing.T) {
5252
err = repo.CreateUser(context.Background(), userWithError)
5353
require.Error(t, err)
5454
assert.Contains(t, err.Error(), "failed to create user")
55+
56+
// Test case 3: Duplicate key constraint violation
57+
duplicateUser := &domain.User{
58+
ID: uuid.New().String(),
59+
Email: "duplicate@example.com",
60+
Name: "Duplicate User",
61+
Type: domain.UserTypeUser,
62+
}
63+
64+
mock.ExpectExec(`INSERT INTO users \(id, email, name, type, created_at, updated_at\) VALUES \(\$1, \$2, \$3, \$4, \$5, \$6\)`).
65+
WithArgs(duplicateUser.ID, duplicateUser.Email, duplicateUser.Name, duplicateUser.Type, sqlmock.AnyArg(), sqlmock.AnyArg()).
66+
WillReturnError(errors.New("pq: duplicate key value violates unique constraint \"users_email_key\""))
67+
68+
err = repo.CreateUser(context.Background(), duplicateUser)
69+
require.Error(t, err)
70+
assert.IsType(t, &domain.ErrUserExists{}, err)
71+
assert.Equal(t, "user already exists", err.Error())
5572
}
5673

5774
func TestGetUserByEmail(t *testing.T) {

internal/service/workspace_service.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/hex"
7+
"errors"
78
"fmt"
89
"strings"
910
"time"
@@ -678,6 +679,12 @@ func (s *WorkspaceService) CreateAPIKey(ctx context.Context, workspaceID string,
678679

679680
err = s.userRepo.CreateUser(ctx, apiUser)
680681
if err != nil {
682+
// Check if this is a duplicate user error
683+
var userExistsErr *domain.ErrUserExists
684+
if errors.As(err, &userExistsErr) {
685+
s.logger.WithField("workspace_id", workspaceID).WithField("user_email", apiUser.Email).Error("API user already exists")
686+
return "", "", fmt.Errorf("this user already exists")
687+
}
681688
s.logger.WithField("workspace_id", workspaceID).WithField("user_id", apiUser.ID).WithField("error", err.Error()).Error("Failed to create API user")
682689
return "", "", err
683690
}

0 commit comments

Comments
 (0)