Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: Add update profile endpoint#916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 16 commits intomainfrombq/users-crud
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
16 commits
Select commitHold shift + click to select a range
2bdcaa8
feat: Add UpdateUser to users.sql
BrunoQuaresmaApr 7, 2022
a23c561
feat: Add UpdateUser
BrunoQuaresmaApr 7, 2022
4418d74
feat: add patchUser api endpoint
BrunoQuaresmaApr 7, 2022
27adb09
tests: Add tests
BrunoQuaresmaApr 7, 2022
25a1a7c
fix: format
BrunoQuaresmaApr 7, 2022
dcb9179
refactor: rename user operations to userProfile
BrunoQuaresmaApr 8, 2022
5fe89ba
refactor: use database.Now() instead of CURRENT_TIMESTAMP
BrunoQuaresmaApr 8, 2022
edd8c1f
feat: check if there is a user with the email or username
BrunoQuaresmaApr 8, 2022
d7e749a
refactor: improve invalid error message
BrunoQuaresmaApr 8, 2022
b228896
refactor: improve error
BrunoQuaresmaApr 8, 2022
ae22b4b
fix: rename errors to responseErrors
BrunoQuaresmaApr 8, 2022
1e1fb85
fix: empty name
BrunoQuaresmaApr 8, 2022
c223fcf
refactor: User /profile and make it Update/PUT
BrunoQuaresmaApr 12, 2022
b6eb9f7
fix: lint errors
BrunoQuaresmaApr 12, 2022
7803e8f
style: rename update to put
BrunoQuaresmaApr 12, 2022
4531e0d
fix: Manually update protoc version
BrunoQuaresmaApr 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionscoderd/coderd.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@ func New(options *Options) (http.Handler, func()) {
r.Route("/{user}", func(r chi.Router) {
r.Use(httpmw.ExtractUserParam(options.Database))
r.Get("/", api.userByName)
r.Put("/profile", api.putUserProfile)
r.Get("/organizations", api.organizationsByUser)
r.Post("/organizations", api.postOrganizationsByUser)
r.Post("/keys", api.postAPIKey)
Expand Down
17 changes: 17 additions & 0 deletionscoderd/database/databasefake/databasefake.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1027,6 +1027,23 @@ func (q *fakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
return user, nil
}

func (q *fakeQuerier) UpdateUserProfile(_ context.Context, arg database.UpdateUserProfileParams) (database.User, error) {
q.mutex.Lock()
defer q.mutex.Unlock()

for index, user := range q.users {
if user.ID != arg.ID {
continue
}
user.Name = arg.Name
user.Email = arg.Email
user.Username = arg.Username
q.users[index] = user
return user, nil
}
return database.User{}, sql.ErrNoRows
}

func (q *fakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
Expand Down
1 change: 1 addition & 0 deletionscoderd/database/querier.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

43 changes: 43 additions & 0 deletionscoderd/database/queries.sql.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

11 changes: 11 additions & 0 deletionscoderd/database/queries/users.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,3 +40,14 @@ INSERT INTO
)
VALUES
($1, $2, $3, $4, FALSE, $5, $6, $7, $8) RETURNING *;

-- name: UpdateUserProfile :one
UPDATE
users
SET
email = $2,
"name" = $3,
username = $4,
updated_at = $5
WHERE
id = $1 RETURNING *;
65 changes: 65 additions & 0 deletionscoderd/users.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -270,6 +270,70 @@ func (*api) userByName(rw http.ResponseWriter, r *http.Request) {
render.JSON(rw, r, convertUser(user))
}

func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) {
user := httpmw.UserParam(r)

var params codersdk.UpdateUserProfileRequest
if !httpapi.Read(rw, r, &params) {
return
}

if params.Name == nil {
params.Name = &user.Name
}

existentUser, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
Email: params.Email,
Username: params.Username,
})
isDifferentUser := existentUser.ID != user.ID

if err == nil && isDifferentUser {
responseErrors := []httpapi.Error{}
if existentUser.Email == params.Email {
responseErrors = append(responseErrors, httpapi.Error{
Field: "email",
Code: "exists",
})
}
if existentUser.Username == params.Username {
responseErrors = append(responseErrors, httpapi.Error{
Field: "username",
Code: "exists",
})
}
httpapi.Write(rw, http.StatusConflict, httpapi.Response{
Message: fmt.Sprintf("user already exists"),
Errors: responseErrors,
})
return
}
if !errors.Is(err, sql.ErrNoRows) && isDifferentUser {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user: %s", err),
})
return
}

updatedUserProfile, err := api.Database.UpdateUserProfile(r.Context(), database.UpdateUserProfileParams{
ID: user.ID,
Name: *params.Name,
Email: params.Email,
Username: params.Username,
UpdatedAt: database.Now(),
})

if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("patch user: %s", err.Error()),
})
return
}

render.Status(r, http.StatusOK)
render.JSON(rw, r, convertUser(updatedUserProfile))
}

// Returns organizations the parameterized user has access to.
func (api *api) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
user := httpmw.UserParam(r)
Expand DownExpand Up@@ -872,5 +936,6 @@ func convertUser(user database.User) codersdk.User {
Email: user.Email,
CreatedAt: user.CreatedAt,
Username: user.Username,
Name: user.Name,
}
}
105 changes: 105 additions & 0 deletionscoderd/users_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -200,6 +200,111 @@ func TestPostUsers(t *testing.T) {
})
}

func TestUpdateUserProfile(t *testing.T) {
t.Parallel()
t.Run("UserNotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
_, err := client.UpdateUserProfile(context.Background(), uuid.New(), codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: "newemail@coder.com",
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
// Right now, we are raising a BAD request error because we don't support a
// user accessing other users info
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})

t.Run("ConflictingEmail", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
existentUser, _ := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
Email: "bruno@coder.com",
Username: "bruno",
Password: "password",
OrganizationID: user.OrganizationID,
})
_, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: existentUser.Email,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
})

t.Run("ConflictingUsername", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
existentUser, _ := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
Email: "bruno@coder.com",
Username: "bruno",
Password: "password",
OrganizationID: user.OrganizationID,
})
_, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: existentUser.Username,
Email: "newemail@coder.com",
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
})

t.Run("UpdateUsernameAndEmail", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: "newemail@coder.com",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, "newusername")
require.Equal(t, userProfile.Email, "newemail@coder.com")
})

t.Run("UpdateUsername", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
me, _ := client.User(context.Background(), codersdk.Me)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: me.Username,
Email: "newemail@coder.com",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, me.Username)
require.Equal(t, userProfile.Email, "newemail@coder.com")
})

t.Run("KeepUserName", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
me, _ := client.User(context.Background(), codersdk.Me)
newName := "New Name"
firstProfile, _ := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: me.Username,
Email: me.Email,
Name: &newName,
})
t.Log(firstProfile)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: "newemail@coder.com",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, "newusername")
require.Equal(t, userProfile.Email, "newemail@coder.com")
require.Equal(t, userProfile.Name, newName)
})
}

func TestUserByName(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
Expand Down
21 changes: 21 additions & 0 deletionscodersdk/users.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ type User struct {
Email string `json:"email" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
Username string `json:"username" validate:"required"`
Name string `json:"name"`
}

type CreateFirstUserRequest struct {
Expand All@@ -41,6 +42,12 @@ type CreateUserRequest struct {
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
}

type UpdateUserProfileRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,username"`
Name *string `json:"name"`
}

// LoginWithPasswordRequest enables callers to authenticate with email and password.
type LoginWithPasswordRequest struct {
Email string `json:"email" validate:"required,email"`
Expand DownExpand Up@@ -115,6 +122,20 @@ func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (User, e
return user, json.NewDecoder(res.Body).Decode(&user)
}

// UpdateUserProfile enables callers to update profile information
func (c *Client) UpdateUserProfile(ctx context.Context, userID uuid.UUID, req UpdateUserProfileRequest) (User, error) {
res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/profile", uuidOrMe(userID)), req)
if err != nil {
return User{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return User{}, readBodyAsError(res)
}
var user User
return user, json.NewDecoder(res.Body).Decode(&user)
}

// CreateAPIKey generates an API key for the user ID provided.
func (c *Client) CreateAPIKey(ctx context.Context, userID uuid.UUID) (*GenerateAPIKeyResponse, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", uuidOrMe(userID)), nil)
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp