diff --git a/cmd/server/http/handlers_users.go b/cmd/server/http/handlers_users.go index ae1684f..b6c039f 100644 --- a/cmd/server/http/handlers_users.go +++ b/cmd/server/http/handlers_users.go @@ -24,16 +24,7 @@ func (h *Handlers) CreateUser(w http.ResponseWriter, r *http.Request) error { return err } - b, err := json.Marshal(createdUser) - if err != nil { - return errors.InputBodyErr(err, "invalid input body provided") - } - - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(b) - if err != nil { - return errors.Wrap(err, "failed to respond") - } + webgo.R200(w, createdUser) return nil } @@ -49,5 +40,6 @@ func (h *Handlers) ReadUserByEmail(w http.ResponseWriter, r *http.Request) error } webgo.R200(w, out) + return nil } diff --git a/cmd/server/http/http.go b/cmd/server/http/http.go index 43513e1..bd2c057 100644 --- a/cmd/server/http/http.go +++ b/cmd/server/http/http.go @@ -67,22 +67,27 @@ func NewService(cfg *Config, apis api.Server) (*HTTP, error) { } router.Use(panicRecoverer) - // in this app, /-/ prefixed routes are used for healthchecks, readiness checks etc. - _ = otelhttp.WithFilter(func(req *http.Request) bool { - return !strings.HasPrefix(req.URL.Path, "/-/") - }) + otelopts := []otelhttp.Option{ + // in this app, /-/ prefixed routes are used for healthchecks, readiness checks etc. + otelhttp.WithFilter(func(req *http.Request) bool { + return !strings.HasPrefix(req.URL.Path, "/-/") + }), + // the span name formatter is used to reduce the cardinality of metrics generated + // when using URIs with variables in it + otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { + wctx := webgo.Context(r) + if wctx == nil { + return r.URL.Path + } + return wctx.Route.Pattern + }), + } - // the span name formatter is used to reduce the cardinality of metrics generated - // when using URIs with variables in it - otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { - wctx := webgo.Context(r) - if wctx == nil { - return r.URL.Path - } - return wctx.Route.Pattern + apmMw := apm.NewHTTPMiddleware(otelopts...) + router.Use(func(w http.ResponseWriter, r *http.Request, hf http.HandlerFunc) { + apmMw(hf).ServeHTTP(w, r) }) - _ = apm.NewHTTPMiddleware() return &HTTP{ server: router, listener: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), diff --git a/internal/users/store_postgres.go b/internal/users/store_postgres.go index dcbc6d5..16e00f5 100644 --- a/internal/users/store_postgres.go +++ b/internal/users/store_postgres.go @@ -3,6 +3,7 @@ package users import ( "context" "database/sql" + "strings" "github.com/Masterminds/squirrel" "github.com/bnkamalesh/errors" @@ -20,10 +21,10 @@ type pgstore struct { func (ps *pgstore) GetUserByEmail(ctx context.Context, email string) (*User, error) { query, args, err := ps.qbuilder.Select( "id", - "uname", + "full_name", "email", "phone", - "uaddress", + "contact_address", ).From( ps.tableName, ).Where( @@ -34,17 +35,20 @@ func (ps *pgstore) GetUserByEmail(ctx context.Context, email string) (*User, err } user := new(User) + uid := new(uuid.NullUUID) address := new(sql.NullString) phone := new(sql.NullString) + row := ps.pqdriver.QueryRow(ctx, query, args...) - err = row.Scan(user.ID, user.Name, phone, address) + err = row.Scan(uid, &user.FullName, &user.Email, phone, address) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, errors.NotFoundErr(ErrUserEmailNotFound, email) } return nil, errors.Wrap(err, "failed getting user info") } - user.Address = address.String + user.ID = uid.UUID.String() + user.ContactAddress = address.String user.Phone = phone.String return user, nil @@ -57,21 +61,21 @@ func (ps *pgstore) SaveUser(ctx context.Context, user *User) (string, error) { ps.tableName, ).Columns( "id", - "uname", + "full_name", "email", "phone", - "uaddress", + "contact_address", ).Values( user.ID, - user.Name, + user.FullName, user.Email, sql.NullString{ String: user.Phone, - Valid: len(user.Phone) == 0, + Valid: len(user.Phone) != 0, }, sql.NullString{ - String: user.Address, - Valid: len(user.Address) == 0, + String: user.ContactAddress, + Valid: len(user.ContactAddress) != 0, }, ).ToSql() if err != nil { @@ -79,6 +83,9 @@ func (ps *pgstore) SaveUser(ctx context.Context, user *User) (string, error) { } _, err = ps.pqdriver.Exec(ctx, query, args...) if err != nil { + if strings.Contains(err.Error(), "violates unique constraint \"users_email_key\"") { + return "", errors.DuplicateErr(ErrUserEmailAlreadyExists, user.Email) + } return "", errors.Wrap(err, "failed storing user info") } @@ -91,15 +98,15 @@ func (ps *pgstore) BulkSaveUser(ctx context.Context, users []User) error { for _, user := range users { rows = append(rows, []any{ user.ID, - user.Name, + user.FullName, user.Email, sql.NullString{ String: user.Phone, - Valid: len(user.Phone) == 0, + Valid: len(user.Phone) != 0, }, sql.NullString{ - String: user.Address, - Valid: len(user.Address) == 0, + String: user.ContactAddress, + Valid: len(user.ContactAddress) != 0, }, }) } @@ -107,7 +114,7 @@ func (ps *pgstore) BulkSaveUser(ctx context.Context, users []User) error { inserted, err := ps.pqdriver.CopyFrom( ctx, pgx.Identifier{ps.tableName}, - []string{"id", "uname", "email", "phone", "uaddress"}, + []string{"id", "full_name", "email", "phone", "contact_address"}, pgx.CopyFromRows(rows), ) if err != nil { diff --git a/internal/users/users.go b/internal/users/users.go index 4dcc577..037c41f 100644 --- a/internal/users/users.go +++ b/internal/users/users.go @@ -9,21 +9,22 @@ import ( ) var ( - ErrUserEmailNotFound = errors.New("user with the email not found") + ErrUserEmailNotFound = errors.New("user with the email not found") + ErrUserEmailAlreadyExists = errors.New("user with the email already exists") ) type User struct { - ID string - Name string - Email string - Phone string - Address string + ID string + FullName string + Email string + Phone string + ContactAddress string } // ValidateForCreate runs the validation required for when a user is being created. i.e. ID is not available func (us *User) ValidateForCreate() error { - if us.Name == "" { - return errors.Validation("name cannot be empty") + if us.FullName == "" { + return errors.Validation("full name cannot be empty") } if us.Email == "" { @@ -35,8 +36,8 @@ func (us *User) ValidateForCreate() error { func (us *User) Sanitize() { us.ID = strings.TrimSpace(us.ID) - us.Name = strings.TrimSpace(us.Name) - us.Address = strings.TrimSpace(us.Address) + us.FullName = strings.TrimSpace(us.FullName) + us.ContactAddress = strings.TrimSpace(us.ContactAddress) us.Phone = strings.TrimSpace(us.Phone) } diff --git a/schemas/users.sql b/schemas/users.sql index c42b924..b61d818 100644 --- a/schemas/users.sql +++ b/schemas/users.sql @@ -1,9 +1,9 @@ CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY, email TEXT UNIQUE, - uname TEXT, + full_name TEXT, phone TEXT, - uaddress TEXT, + contact_address TEXT, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now() );