From c3610ac1e477d12dbb521560182e1c3912764214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Sun, 17 Nov 2024 00:54:15 +0100 Subject: [PATCH] HUGE progress --- .gitignore | 4 +- cmd/secret-santa/main.go | 31 +++++++++++ internal/app/app.go | 32 +++++++++++ internal/app/routes.go | 14 +++++ internal/config/config.go | 29 ++++++++++ internal/handlers/user_handler.go | 93 +++++++++++++++++++++++++++++++ internal/handlers/utils.go | 38 +++++++++++++ internal/queries/user.sql.go | 23 +++++++- main.go | 51 ----------------- sql/queries/user.sql | 8 ++- 10 files changed, 265 insertions(+), 58 deletions(-) create mode 100644 cmd/secret-santa/main.go create mode 100644 internal/app/app.go create mode 100644 internal/app/routes.go create mode 100644 internal/config/config.go create mode 100644 internal/handlers/user_handler.go create mode 100644 internal/handlers/utils.go delete mode 100644 main.go diff --git a/.gitignore b/.gitignore index f344e37..4cdf929 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -secret-santa -*.db \ No newline at end of file +*.db +.idea/ diff --git a/cmd/secret-santa/main.go b/cmd/secret-santa/main.go new file mode 100644 index 0000000..38bd143 --- /dev/null +++ b/cmd/secret-santa/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "database/sql" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/app" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/config" + "log" + "net/http" + + _ "github.com/mattn/go-sqlite3" +) + +func main() { + dbConnection, err := sql.Open("sqlite3", "./test.db") + if err != nil { + log.Fatal(err) + } + log.Println("Database sucessfully connected") + + appConfig := config.NewAppConfig(nil, nil) + application := app.NewApp(dbConnection, &appConfig) + + log.Printf("Listening on http://%s", appConfig.GenerateIP()) + + log.Fatal( + http.ListenAndServe( + appConfig.GenerateIP(), + application.Router(), + ), + ) +} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..6930dc2 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,32 @@ +package app + +import ( + "database/sql" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/config" + "net/http" +) + +type App struct { + Config config.AppConfig + DB *sql.DB +} + +func NewApp(db *sql.DB, appConfig *config.AppConfig) *App { + defaultConfig := config.NewAppConfig(nil, nil) + if appConfig == nil { + appConfig = &defaultConfig + } + + return &App{ + Config: *appConfig, + DB: db, + } +} + +func (a *App) Router() http.Handler { + router := http.NewServeMux() + + AddRoutes(router, a.DB) + + return router +} diff --git a/internal/app/routes.go b/internal/app/routes.go new file mode 100644 index 0000000..afa79c9 --- /dev/null +++ b/internal/app/routes.go @@ -0,0 +1,14 @@ +package app + +import ( + "database/sql" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/handlers" + "net/http" +) + +func AddRoutes(router *http.ServeMux, db *sql.DB) { + router.HandleFunc("GET /users/{id}", handlers.HandleGetUsersByID(db)) + router.HandleFunc("GET /users/", handlers.HandleGetUsers(db)) + router.HandleFunc("POST /users/", handlers.HandleCreateUser(db)) + router.HandleFunc("DELETE /users/{id}", handlers.HandleDeleteUser(db)) +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..d0a5efb --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,29 @@ +package config + +import "fmt" + +type AppConfig struct { + ListenIP string `json:"listen_ip"` + ListenPort int `json:"listen_port"` +} + +func (c *AppConfig) GenerateIP() string { + return fmt.Sprintf("%s:%d", c.ListenIP, c.ListenPort) +} + +func NewAppConfig(port *int, ip *string) AppConfig { + defaultPort := 8080 + defaultIp := "127.0.0.1" + + if port == nil { + port = &defaultPort + } + if ip == nil { + ip = &defaultIp + } + + return AppConfig{ + ListenIP: *ip, + ListenPort: *port, + } +} diff --git a/internal/handlers/user_handler.go b/internal/handlers/user_handler.go new file mode 100644 index 0000000..a5bff03 --- /dev/null +++ b/internal/handlers/user_handler.go @@ -0,0 +1,93 @@ +package handlers + +import ( + "context" + "database/sql" + "errors" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/queries" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/types" + "net/http" +) + +func HandleGetUsers(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() + query := queries.New(db) + + users, err := query.GetUsers(ctx) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + + if len(users) == 0 { + users = []queries.User{} + } + + writeJSONResponse(w, 200, types.JsonResponse[[]queries.User]{Data: users}) + } +} +func HandleGetUsersByID(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() + query := queries.New(db) + + id, err := parseIDFromURL(r) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + + user, err := query.GetUserId(ctx, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + writeErrorResponse(w, 404, "User not found") + return + } + writeErrorResponse(w, 500, err.Error()) + return + } + writeJSONResponse(w, 200, types.JsonResponse[queries.User]{Data: user}) + } +} + +func HandleCreateUser(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() + query := queries.New(db) + + var createUser queries.CreateUserParams + err := readJSONRequest(r, &createUser) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + + user, err := query.CreateUser(ctx, createUser) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + writeJSONResponse(w, 201, types.JsonResponse[queries.User]{Data: user}) + } +} + +func HandleDeleteUser(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() + query := queries.New(db) + + id, err := parseIDFromURL(r) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + + err = query.DeleteUser(ctx, id) + if err != nil { + writeErrorResponse(w, 500, err.Error()) + return + } + writeJSONResponse(w, 204, nil) + } +} diff --git a/internal/handlers/utils.go b/internal/handlers/utils.go new file mode 100644 index 0000000..ff2c026 --- /dev/null +++ b/internal/handlers/utils.go @@ -0,0 +1,38 @@ +package handlers + +import ( + "encoding/json" + "git.katuwoss.dev/JustScreaMy/secret-santa/internal/types" + "log" + "net/http" + "strconv" +) + +func writeJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + err := json.NewEncoder(w).Encode(data) + if err != nil { + log.Fatalf("Failed to write response. Error: %s\n", err) + } +} + +func writeErrorResponse(w http.ResponseWriter, statusCode int, message string) { + writeJSONResponse(w, statusCode, types.ErrorResponse{Message: message}) +} + +func readJSONRequest(r *http.Request, data interface{}) error { + err := json.NewDecoder(r.Body).Decode(data) + if err != nil { + return err + } + return nil +} + +func parseIDFromURL(r *http.Request) (int64, error) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + return 0, err + } + return id, nil +} diff --git a/internal/queries/user.sql.go b/internal/queries/user.sql.go index d9f6305..c6bb906 100644 --- a/internal/queries/user.sql.go +++ b/internal/queries/user.sql.go @@ -9,8 +9,9 @@ import ( "context" ) -const createUser = `-- name: CreateUser :exec +const createUser = `-- name: CreateUser :one INSERT INTO users (id, email, first_name, last_name) VALUES (NULL, ?, ?, ?) +RETURNING id, first_name, last_name, email ` type CreateUserParams struct { @@ -19,8 +20,24 @@ type CreateUserParams struct { LastName string `json:"last_name"` } -func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) error { - _, err := q.db.ExecContext(ctx, createUser, arg.Email, arg.FirstName, arg.LastName) +func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) { + row := q.db.QueryRowContext(ctx, createUser, arg.Email, arg.FirstName, arg.LastName) + var i User + err := row.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + ) + return i, err +} + +const deleteUser = `-- name: DeleteUser :exec +DELETE FROM users WHERE id = ? +` + +func (q *Queries) DeleteUser(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteUser, id) return err } diff --git a/main.go b/main.go deleted file mode 100644 index 2761722..0000000 --- a/main.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "encoding/json" - "log" - "net/http" - - "git.katuwoss.dev/JustScreaMy/secret-santa/internal/queries" - "git.katuwoss.dev/JustScreaMy/secret-santa/internal/types" - _ "github.com/mattn/go-sqlite3" -) - -var dbConnection *sql.DB - -func init() { - var err error - dbConnection, err = sql.Open("sqlite3", "./test.db") - if err != nil { - log.Fatal(err) - } - log.Println("Database sucessfully connected") -} - -func main() { - ctx := context.Background() - - log.Println("Created database connection") - - router := http.NewServeMux() - - router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - query := queries.New(dbConnection) - - w.Header().Set("Content-Type", "application/json") - users, err := query.GetUsers(ctx) - if err != nil { - w.WriteHeader(404) - log.Printf("Failed to get all users: %s\n", err.Error()) - json.NewEncoder(w).Encode(types.ErrorResponse{Message: err.Error()}) - return - } - if len(users) == 0 { - users = make([]queries.User, 0) - } - json.NewEncoder(w).Encode(types.JsonResponse[[]queries.User]{Data: users}) - }) - - log.Fatal(http.ListenAndServe(":8080", router)) -} diff --git a/sql/queries/user.sql b/sql/queries/user.sql index 3f66441..cbaf681 100644 --- a/sql/queries/user.sql +++ b/sql/queries/user.sql @@ -5,5 +5,9 @@ WHERE id = ? LIMIT 1; -- name: GetUsers :many SELECT * FROM users; --- name: CreateUser :exec -INSERT INTO users (id, email, first_name, last_name) VALUES (NULL, ?, ?, ?); \ No newline at end of file +-- name: CreateUser :one +INSERT INTO users (id, email, first_name, last_name) VALUES (NULL, ?, ?, ?) +RETURNING *; + +-- name: DeleteUser :exec +DELETE FROM users WHERE id = ?; \ No newline at end of file