More ChatGPT Fun

My previous blog post was more of a snide joke, where I asked ChatGPT to write a blog post for me showing situations where it works well and where it doesn’t work well.

In this blog post, I’ll attempt to have it write a complete application for me, and document all the steps I needed to make the output work.

Hopefully in this exercise I will demonstrate why I’m not worried I’ll be out of a job - just yet. I do think there are engineers born today whose job will be dramatically different to the job I have today - I think software engineers will be spending a lot of time working with AIs to craft code-bases by nudging the AI in the directions it needs to go, and correcting errors it makes in it’s generation of code. I think we’re a long way from a product manager being able to do that.

Getting Started

The key to successful chatGPT interactions is providing clear constraints. We will provide some right at the beginning that should hopefully influence its decisions later on in the conversation.

NOTE: in this post I will use quotes for my comments in line with the responses from ChatGPT. Any additional questions asked will be in the header of the section.

Initial request

Today, we will develop a complete authentication API service in Go.

This API service will use github.com/labstack/echo/v4 for the HTTP framework, and we will need to support account registration, authentication, authorization, and user profile management (both by the authenticated user, and by admin users) using a REST API. The REST API should be documented in an OpenAPI 3 document, and the API should serve the documentation via the swagger-ui from /docs.

For storage, I’d like to use the github.com/jackc/pgx/v5 database library with a postgresql database.

Users should be stored with their email address as the primary key, the password should use a strong one-way hashing algorithm like argon2. Users should be able to be a member of a Role, and there should be at least two roles initially defined, one for administrative users and one for normal users.

Note that it did stop generating several times during this response. You can simply say “looks like you got cut off in step x” and it will continue from where it got cut off.

Initial response

Sure! Developing a complete authentication API service in Go using the specified libraries and tools you mentioned is definitely possible. It will require some initial setup and configuration, but with a step-by-step approach, we can build the desired API service.

Here’s a rough outline of the steps we’ll need to take:

Set up the project and install required dependencies Create the necessary database tables and schema Implement the account registration endpoint Implement the authentication endpoint Implement the authorization middleware Implement the user profile management endpoints Document the REST API using OpenAPI 3 Serve the documentation via the swagger-ui from /docs Let’s start with the first step.

Step 1: Set up the project and install required dependencies

To start, we’ll create a new Go module for the project and install the required dependencies, echo and pgx.

To create a new module, navigate to the directory where you’d like to create the project and run the following command:

go mod init <module-name>

Replace with a suitable name for your project.

Next, we’ll install the required dependencies using the following commands:

go get github.com/labstack/echo/v4
go get github.com/jackc/pgx/v5

Seems reasonable to me, if a bit thin.

Step 2: Create the necessary database tables and schema

Next, we’ll create the necessary database tables and schema. We’ll need a users table to store user information, and a roles table to store role information. Each user can have a single role assigned to them, and each role can have multiple users assigned to it. Here’s the schema for the tables:

CREATE TABLE users (
    email VARCHAR(320) PRIMARY KEY,
    password VARCHAR(255),
    role VARCHAR(255),
    name VARCHAR(255),
    bio TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE roles (
    name VARCHAR(255) PRIMARY KEY
);

We’ll also need to insert the initial roles into the roles table. Here’s some sample SQL to do that:

INSERT INTO roles (name) VALUES ('admin'), ('user');

bare minimum but I’ll take it.

Step 3: Implement the account registration endpoint

Now that we have our database schema set up, we can start implementing the endpoints. We’ll start with the account registration endpoint, which will allow users to create a new account by providing their email address and password.

First, we’ll need to define a struct to represent the user data that we’ll receive in the request body:

type CreateUserRequest struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}
func createUser(c echo.Context) error {
    req := new(CreateUserRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body"})
    }

    // Check if user already exists
    _, err := db.Exec("SELECT email FROM users WHERE email=$1", req.Email)
    if err == nil {
        return c.JSON(http.StatusConflict, map[string]string{"error": "user already exists"})
    }

    // Hash the password
    hash, err := argon2id.CreateHash(req.Password, argon2id.DefaultParams)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
    }

    // Insert the new user into the database
    _, err = db.Exec("INSERT INTO users (email, password, role) VALUES ($1, $2, 'user')", req.Email, hash)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
    }

    return c.JSON(http.StatusCreated, map[string]string{"message": "user created successfully"})
}

This function checks if the user already exists in the database, hashes the password using Argon2, and then inserts the new user into the database with the default role of “user”.

Mixing the database code in the handler isn’t exactly idiomatic but I can live with it for a POC.

Step 4: Implement the authentication endpoint

The next step is to implement the authentication endpoint, which will allow users to log in and obtain a token that can be used to authenticate subsequent requests.

We’ll start by defining a struct to represent the authentication request body:

type AuthRequest struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}

Next, we’ll create the endpoint function that will handle the authentication request:

func authenticate(c echo.Context) error {
    req := new(AuthRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body"})
    }

    // Get the user from the database
    row := db.QueryRow("SELECT password FROM users WHERE email=$1", req.Email)

    // Check if the user exists
    var hash string
    err := row.Scan(&hash)
    if err != nil {
        return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid email or password"})
    }

    // Verify the password
    err = argon2id.ComparePasswordAndHash(req.Password, hash)
    if err != nil {
        return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid email or password"})
    }

    // Generate a JWT token
    token := jwt.New(jwt.SigningMethodHS256)
    claims := token.Claims.(jwt.MapClaims)
    claims["email"] = req.Email
    claims["exp"] = time.Now().Add(time.Hour * 24).Unix()
    t, err := token.SignedString([]byte("secret"))
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
    }

    return c.JSON(http.StatusOK, map[string]string{"token": t})
}

This function retrieves the user’s password hash from the database, verifies the password, and then generates a JWT token that includes the user’s email address and an expiration time of 24 hours.

Thats actually pretty neat.

Step 5: Implement the authorization middleware

Before we can implement the user profile management endpoints, we need to implement an authorization middleware that will check the validity of the JWT token included in the request header.

First, we’ll define a function to extract the token from the header:

func getToken(c echo.Context) (string, error) {
    authHeader := c.Request().Header.Get("Authorization")
    if authHeader == "" {
        return "", errors.New("missing Authorization header")
    }

    parts := strings.Split(authHeader, " ")
    if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
        return "", errors.New("invalid Authorization header")
    }

    return parts[1], nil
}

This function checks that the Authorization header is present and in the correct format, and returns the token string.

Next, we’ll create the authorization middleware:

func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        token, err := getToken(c)
        if err != nil {
            return c.JSON(http.StatusUnauthorized, map[string]string{"error": err.Error()})
        }

        jwtToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, errors.New("invalid token")
            }

            return []byte("secret"), nil
        })

        if err != nil {
            return c.JSON(http.StatusUnauthorized, map[string]string{"error": err.Error()})
        }

        claims, ok := jwtToken.Claims.(jwt.MapClaims)
        if !ok || !jwtToken.Valid {
            return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid token"})
        }

        c.Set("email", claims["email"])

        return next(c)
    }
}

This middleware extracts the token from the request header, parses the JWT token, validates it, and sets the email address as a context value that can be accessed by the request handler.

We can now use this middleware to protect the user profile management endpoints in the next step.

it never sets the role in the context, which is used later in the other handlers, so this is a miss.

is it normal to put a JWT in a header, or would cookies be better?

Both methods are valid for sending authentication information between the client and server. Using headers to send the JWT token is a common practice and has the advantage of being easy to implement and test. It also allows for cross-origin requests (CORS) because cookies are subject to the same-origin policy.

On the other hand, using cookies for authentication can provide some benefits such as better security against cross-site scripting (XSS) attacks and the ability to set HTTP-only cookies that can’t be accessed by JavaScript. However, it also requires additional configuration to work properly with CORS and CSRF protection.

In general, it’s important to carefully consider the specific requirements of your application and choose the appropriate method of authentication.

Solid response. I’d agree with that.

Give me step 6

Step 6: Implement the user profile management endpoints

Now that we have implemented the authentication and authorization middleware, we can implement the user profile management endpoints.

We’ll start by defining the User struct that will be used to represent the user in the API:

type User struct {
    Email string `json:"email"`
    Name  string `json:"name"`
    Role  string `json:"role"`
}

Next, we’ll define the endpoint for retrieving the current user’s profile:

func getUserProfile(c echo.Context) error {
    email := c.Get("email").(string)

    user, err := getUserByEmail(email)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to retrieve user"})
    }

    return c.JSON(http.StatusOK, user)
}

This endpoint retrieves the email address of the current user from the context, uses the getUserByEmail function to retrieve the corresponding user from the database, and returns the user as a JSON response.

We’ll also define the endpoint for updating the current user’s profile:

func updateUserProfile(c echo.Context) error {
    email := c.Get("email").(string)

    user := new(User)
    if err := c.Bind(user); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
    }

    if user.Email != email {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "cannot change email"})
    }

    err := updateUser(user)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update user"})
    }

    return c.NoContent(http.StatusOK)
}

This endpoint retrieves the email address of the current user from the context, binds the request body to a new User struct, and checks that the email address in the request matches the current user’s email address. If the email address matches, it calls the updateUser function to update the user in the database.

We’ll also define the endpoint for retrieving all users, which will only be accessible by admin users:

func getUsers(c echo.Context) error {
    role := c.Get("role").(string)
    if role != "admin" {
        return c.JSON(http.StatusForbidden, map[string]string{"error": "not authorized"})
    }

    users, err := getAllUsers()
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to retrieve users"})
    }

    return c.JSON(http.StatusOK, users)
}

This endpoint retrieves the role of the current user from the context and checks that it is “admin”. If the role is “admin”, it calls the getAllUsers function to retrieve all users from the database and returns them as a JSON response.

We’ll also define the endpoint for retrieving a specific user by email address, which will only be accessible by admin users:

func getUserByEmailHandler(c echo.Context) error {
    role := c.Get("role").(string)
    if role != "admin" {
        return c.JSON(http.StatusForbidden, map[string]string{"error": "not authorized"})
    }

    email := c.Param("email")

    user, err := getUserByEmail(email)
    if err != nil {
        return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
    }

    return c.JSON(http.StatusOK, user)
}

this code is using a function getUserByEmail that we’ve not discussed, that operate on the database. So the generator is now thinking that some code uses separate functions for database access here, but not before. We’ll have to correct that shortly.

This endpoint retrieves the role of the current user from the context and checks that it is “admin”. If the role is “admin”, it retrieves the email address from the URL parameter and calls the getUserByEmail function to retrieve the corresponding user from the database, and returns the user as a JSON response.

Finally, we’ll define the endpoint for updating a specific user by email address, which will only be accessible by admin users:

func updateUserByEmailHandler(c echo.Context) error {
    role := c.Get("role").(string)
    if role != "admin" {
        return c.JSON(http.StatusForbidden, map[string]string{"error": "not authorized"})
    }

    email := c.Param("email")

    user := new(User)
    if err := c.Bind(user); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
    }

    if user.Email != email {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "cannot change email"})
    }

    err := updateUser(user)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update user"})
    }

    return c.NoContent(http.StatusOK)
}

This endpoint retrieves the role of the current user from the context and checks that it is “admin”. If the role is “admin”, it retrieves the email address from the URL parameter, binds the request body to a new User struct, and checks that the email address in the request matches the email address in the URL parameter. If the email address matches, it calls the updateUser function to update the user in the database.

That’s it for step 6!

Show me Step 7

Sure thing! In step 7, we’ll define the endpoints for managing user roles, which will only be accessible by admin users.

First, we’ll define the endpoint for retrieving the role of a specific user by email address:

func getUserRoleByEmailHandler(c echo.Context) error {
    role := c.Get("role").(string)
    if role != "admin" {
        return c.JSON(http.StatusForbidden, map[string]string{"error": "not authorized"})
    }

    email := c.Param("email")

    user, err := getUserByEmail(email)
    if err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "user not found"})
    }

    return c.JSON(http.StatusOK, map[string]string{"role": user.Role})
}

This endpoint retrieves the role of the current user from the context and checks that it is “admin”. If the role is “admin”, it retrieves the email address from the URL parameter, calls the getUserByEmail function to retrieve the corresponding user from the database, and returns the user’s role as a JSON response.

Next, we’ll define the endpoint for updating the role of a specific user by email address:

func updateUserRoleByEmailHandler(c echo.Context) error {
    role := c.Get("role").(string)
    if role != "admin" {
        return c.JSON(http.StatusForbidden, map[string]string{"error": "not authorized"})
    }

    email := c.Param("email")

    req := struct {
        Role string `json:"role"`
    }{}

    if err := c.Bind(&req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
    }

    if err := updateUserRoleByEmail(email, req.Role); err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update user role"})
    }

    return c.NoContent(http.StatusOK)
}

This endpoint retrieves the role of the current user from the context and checks that it is “admin”. If the role is “admin”, it retrieves the email address from the URL parameter, binds the request body to a new struct containing a Role field, and calls the updateUserRoleByEmail function to update the user’s role in the database.

That’s it for step 7!

ChatGPT lost track. It seems to have lost track of each of the steps and has given us some additional user handlers. OK, that’s fine, we’ll just be specific about the next step.

Document the REST API using OpenAPI 3

Sure thing! In this step, we’ll document the REST API using OpenAPI 3. OpenAPI is a specification for building APIs, and it allows you to define the endpoints, input parameters, responses, and error messages of your API in a machine-readable format.

To get started, we’ll create a new file called openapi.yaml in the root directory of our project. This file will contain the OpenAPI definition of our API.

Here’s an example OpenAPI definition that documents the endpoints we’ve created so far:

openapi: 3.0.0
info:
  title: Authentication API
  description: A REST API for user authentication, authorization, and profile management
  version: 1.0.0
servers:
  - url: http://localhost:8080
paths:
  /register:
    post:
      summary: Register a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
      responses:
        '200':
          description: OK
        '400':
          description: Invalid request
        '409':
          description: User with email address already exists
        '500':
          description: Internal server error
  /login:
    post:
      summary: Authenticate a user and get a JWT
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
        '400':
          description: Invalid request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
  /profile:
    get:
      summary: Get the profile of the authenticated user
      security:
        - BearerAuth: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  email:
                    type: string
                    format: email
                  role:
                    type: string
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
    put:
      summary: Update the profile of the authenticated user
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
      responses:
        '200':
          description: OK
        '400':
          description: Invalid request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
  /users/{email}:
    get:
      summary: Get the profile of a specific user
      parameters:
        - name: email
          in: path
          required: true
          schema:
            type: string
            format: email
      security:
        - BearerAuth: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  email:
                    type: string
                    format: email
                  role:
                    type: string
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: User not found

Show me the implementation of main() that will use all of this code, and provide a way to Serve the documentation via the swagger-ui from /docs

Sure! Here’s an example implementation of main() that uses all the code we’ve developed so far and serves the documentation via the Swagger UI from /docs.

package main

import (
	"log"
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/swaggo/echo-swagger"
	_ "github.com/swaggo/echo-swagger/example/docs"
)

// @title Authentication API
// @description This is a sample authentication API service.
// @version 1
// @host localhost:1323
// @BasePath /
func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Database connection
	db, err := ConnectDB()
	if err != nil {
		log.Fatalf("failed to connect database: %v", err)
	}
	defer db.Close()

	// Initialize user repository
	userRepo := NewUserRepository(db)

	// Initialize JWT config
	jwtConfig := JWTConfig{
		SecretKey: "my_secret_key",
	}

	// Initialize authentication service
	authService := NewAuthService(userRepo, jwtConfig)

	// Initialize API handlers
	userHandler := NewUserHandler(userRepo)
	authHandler := NewAuthHandler(authService)

	// API routes
	v1 := e.Group("/api/v1")
	v1.POST("/users", userHandler.CreateUser)
	v1.GET("/users/:email", userHandler.GetUser, middleware.JWT([]byte(jwtConfig.SecretKey)))
	v1.PUT("/users/:email", userHandler.UpdateUser, middleware.JWT([]byte(jwtConfig.SecretKey)))
	v1.DELETE("/users/:email", userHandler.DeleteUser, middleware.JWT([]byte(jwtConfig.SecretKey)))
	v1.POST("/login", authHandler.Login)
	v1.GET("/profile", authHandler.GetProfile, middleware.JWT([]byte(jwtConfig.SecretKey)))

	// Serve documentation via Swagger UI
	e.GET("/docs/*", echoSwagger.WrapHandler)

	// Start server
	err = e.Start(":1323")
	if err != nil {
		log.Fatalf("failed to start server: %v", err)
	}
}

This implementation initializes the necessary components like the database connection, user repository, authentication service, and API handlers. It also defines the necessary API routes with their corresponding handlers, and uses the echo-swagger package to serve the documentation via Swagger UI from /docs.

Note that we are using the _ “github.com/swaggo/echo-swagger/example/docs” import to load the Swagger UI assets into our binary. This is necessary to serve the Swagger UI via the /docs route. You’ll need to replace this with your own Swagger UI assets if you’re using a different version of echo-swagger.

Give me the file layout for this application

Sure! Here’s an example file layout for our authentication API service in Go:

- cmd/
  - main.go
- db/
  - migrations/
    - 0001_init.sql
  - db.go
- internal/
  - auth/
    - auth.go
    - handler.go
    - service.go
  - user/
    - user.go
    - handler.go
    - repository.go
  - jwt/
    - jwt.go
- swagger/
  - swagger.json
- go.mod
- go.sum

In this layout, we have a cmd/ directory that contains our main.go file, which initializes our application and starts the server. We also have a db/ directory that contains our database connection and migration scripts. The internal/ directory contains the core logic of our application, with subdirectories for our user, authentication, and JWT components. Finally, we have a swagger/ directory that contains our OpenAPI 3 specification in JSON format.

Note that this is just one possible file layout, and you may need to adjust it to fit the specific needs of your application.

what functions will go in what file?

Here’s an example breakdown of the functions that could go in each file in our suggested file layout:

  • cmd/main.go: initializes the application and starts the server.
  • db/db.go: contains the database connection and helper functions for working with the database.
  • db/migrations/0001_init.sql: contains the database migration script for setting up the initial schema.
  • internal/auth/auth.go: defines the User, Credentials, and Role structs, as well as any constants or variables related to authentication.
  • internal/auth/handler.go: contains the HTTP handlers for our authentication endpoints (e.g., /register, /login).
  • internal/auth/service.go: contains the business logic for our authentication service.
  • internal/user/user.go: defines the User struct and any constants or variables related to user management.
  • internal/user/handler.go: contains the HTTP handlers for our user management endpoints (e.g., /users/:id, /users/me).
  • internal/user/repository.go: contains the functions for interacting with the database to perform CRUD operations on user data.
  • internal/jwt/jwt.go: defines the Token struct and any constants or variables related to JWTs.
  • swagger/swagger.json: contains the OpenAPI 3 specification in JSON format.

Note that this is just one possible breakdown, and you may need to adjust it to fit the specific needs of your application.

Conclusion

What we’re left with is a lot of work.

  1. The main function provided uses no functions that we defined earlier.
  2. The handlers probably “work” but we’ll need to fix them to fit in the project structure.
  3. Not all the endpoints defined in main correspond with functions we created earlier.

There’s just a complete mess of functions that still need to be defined.

What even am I doing at this point?

Well, either I write the code, and then massage it into working code. Or ChatGPT writes the code … and I massage that into working code. Or massage the prompts.

ChatGPT is an interesting tool, and I think there’s value to be extracted here right now - but I think this is early days. I’m really excited to see what it could do if it had the context of an entire repository of code and you could interact with it to generate additional code using the existing code as a basis.

Massaging ChatGPT questions

The problem with trying to massage ChatGPT’s output is that it really doesn’t hold the context of everything that it suggested before well. For example, because of its semi-random nature, it will use different patterns in successful outputs; the main() func defined above doesn’t make sense when you consider we don’t have things organized in packages so asking it to define main() again but using packages for these things gives you a completely different implementation which even changed the endpoints.

At this point, you have to be extremely specific about exactly what you want the output to look like, and this can still save you net time, I would think but it starts getting marginal if you’re spending an hour massaging the output of ChatGPT versus just writing the correct code in the first place.

How can I use ChatGPT right now in my day job as a software engineer?

If you give it specific things like, here’s a rest API, it has these endpoints, give me the swagger doc - it will work really really well. And I don’t know about you but writing a swagger doc by hand is laborious.

For example if I ask it “provide me a summary of the endpoints we have defined in the application so far” it gives me

Public endpoints

Register a new user

  • POST /api/v1/auth/register
  • Request body: { “email”: “user@example.com”, “password”: “password” }
  • Registers a new user with the given email and password

Login

  • POST /api/v1/auth/login
  • Request body: { “email”: “user@example.com”, “password”: “password” }
  • Authenticates a user and returns a JWT token in the Authorization header

Authenticated endpoints

Get user profile

  • GET /api/v1/users/me
  • Returns the profile information for the authenticated user

Update user profile

  • PUT /api/v1/users/me
  • Request body: { “name”: “Jane Doe”, “phone”: “123-456-7890” }
  • Updates the profile information for the authenticated user

Admin endpoints

Get all users

  • GET /api/v1/users
  • Returns a list of all users in the system

Get user by email

  • GET /api/v1/users/:email
  • Returns the profile information for the user with the given email

Update user by email

  • PUT /api/v1/users/:email
  • Request body: { “name”: “John Doe”, “phone”: “555-555-5555”, “role”: “admin” }
  • Updates the profile information and role for the user with the given email. Only accessible by admin users.

You’ll note that it totally just changed everything, because of that lack of context. You CAN fix that, by telling it about its error, and it will fix the error … but remember, ChatGPT is a token generator. It doesn’t really “remember”. It just passes the previous set of generated tokens into the input along with your next question … so if it makes several mistakes your output will get more and more nonsensical.

So, I’ll leave these recommendations with you.

  • Use ChatGPT, but time-box it. When you’re spending more time trying to correct ChatGPT, this is probably the point where you need to stop, and you need to start just working on it on your own/
  • When you do use ChatGPT, try to give it very specific examples. For example, if you want it to generate some database access code for you, give it the schema, and be specific what you want the function signatures to look like. Let it handle the rest.
  • Use it for generating unit tests. It’s pretty good at that. Just make sure you give it a clear description of what it is you expect the function you’re testing to do, and what the tests should cover. Favor writing pure functions, which are easier to test.

Nathan's blog

A blog about programming, motorcycles, dungeons and dragons, amigas, and other things.


By matjam, 2023-02-22