diff --git a/internal/database/DeleteAllUsers.sql.go b/internal/database/DeleteAllUsers.sql.go new file mode 100644 index 0000000..19fc642 --- /dev/null +++ b/internal/database/DeleteAllUsers.sql.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: DeleteAllUsers.sql + +package database + +import ( + "context" +) + +const deleteAllUser = `-- name: DeleteAllUser :exec +DELETE FROM users WHERE true +` + +func (q *Queries) DeleteAllUser(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, deleteAllUser) + return err +} diff --git a/internal/httpResponse/HTML.go b/internal/httpResponse/HTML.go new file mode 100644 index 0000000..6e5c679 --- /dev/null +++ b/internal/httpResponse/HTML.go @@ -0,0 +1,15 @@ +package httpResponse + +import ( + "log" + "net/http" +) + +func HTMLHandler(w http.ResponseWriter, status int, text string) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(status) + _, err := w.Write([]byte(text)) + if err != nil { + log.Default().Println(err) + } +} diff --git a/internal/httpResponse/JSON.go b/internal/httpResponse/JSON.go new file mode 100644 index 0000000..75bb720 --- /dev/null +++ b/internal/httpResponse/JSON.go @@ -0,0 +1,15 @@ +package httpResponse + +import ( + "log" + "net/http" +) + +func JSONHandler(w http.ResponseWriter, status int, text string) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(status) + _, err := w.Write([]byte(text)) + if err != nil { + log.Default().Println(err) + } +} diff --git a/internal/httpResponse/PlainText.go b/internal/httpResponse/PlainText.go new file mode 100644 index 0000000..bcbe86e --- /dev/null +++ b/internal/httpResponse/PlainText.go @@ -0,0 +1,15 @@ +package httpResponse + +import ( + "log" + "net/http" +) + +func PlainTextHandler(w http.ResponseWriter, status int, text string) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(status) + _, err := w.Write([]byte(text)) + if err != nil { + log.Default().Println(err) + } +} diff --git a/internal/httpResponse/SomethingWentWrong.go b/internal/httpResponse/SomethingWentWrong.go new file mode 100644 index 0000000..19851b4 --- /dev/null +++ b/internal/httpResponse/SomethingWentWrong.go @@ -0,0 +1,9 @@ +package httpResponse + +import ( + "net/http" +) + +func SomethingWentWrong(w http.ResponseWriter) { + JSONHandler(w, http.StatusInternalServerError, `{"error": "Something went wrong"}`) +} diff --git a/main.go b/main.go index 33bf220..de65ebb 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,14 @@ package main import ( + "context" "database/sql" "encoding/json" "fmt" "github.com/joho/godotenv" _ "github.com/lib/pq" "github.com/rdarius/go-http-server/internal/database" + "github.com/rdarius/go-http-server/internal/httpResponse" "log" "net/http" "os" @@ -17,6 +19,7 @@ import ( type apiConfig struct { fileserverHits atomic.Int32 db database.Queries + platform string } func (cfg *apiConfig) middlewareMetricsInc(next http.Handler) http.Handler { @@ -27,47 +30,42 @@ func (cfg *apiConfig) middlewareMetricsInc(next http.Handler) http.Handler { } func readinessHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte("OK")) + httpResponse.PlainTextHandler(w, http.StatusOK, "OK") } func metricsHandler(cfg *apiConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) hits := cfg.fileserverHits.Load() - w.Write([]byte(fmt.Sprintf("Hits: %d", hits))) + httpResponse.PlainTextHandler(w, http.StatusOK, fmt.Sprintf("Hits: %d", hits)) } } func adminMetricsHandler(cfg *apiConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) hits := cfg.fileserverHits.Load() - w.Write([]byte(fmt.Sprintf(` + httpResponse.HTMLHandler(w, http.StatusOK, fmt.Sprintf(`
Chirpy has been visited %d times!
-`, hits))) +`, hits)) } } func resetMetricsHandler(cfg *apiConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + if cfg.platform != "dev" { + httpResponse.JSONHandler(w, http.StatusForbidden, `{"error": "Forbidden!"}`) + return + } + + err := cfg.db.DeleteAllUser(context.Background()) + if err != nil { + httpResponse.SomethingWentWrong(w) + return + } cfg.fileserverHits.Store(0) - w.WriteHeader(http.StatusOK) - w.Write([]byte("Metrics Reset")) - } -} -func adminResetMetricsHandler(cfg *apiConfig) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - cfg.fileserverHits.Store(0) - w.WriteHeader(http.StatusOK) - w.Write([]byte("Metrics Reset")) + httpResponse.PlainTextHandler(w, http.StatusOK, "Metrics Reset") } } @@ -76,6 +74,35 @@ func fileServerHandler() http.Handler { return http.StripPrefix("/app", fileServer) } +func postUsersHandler(cfg *apiConfig) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + type parameters struct { + Email string `json:"email"` + } + + params := parameters{} + err := json.NewDecoder(r.Body).Decode(¶ms) + if err != nil { + httpResponse.JSONHandler(w, http.StatusBadRequest, `{"error": "Failed to parse request body"}`) + return + } + + usr, err := cfg.db.CreateUser(context.Background(), params.Email) + if err != nil { + httpResponse.JSONHandler(w, http.StatusInternalServerError, fmt.Sprintf(`{"error": "Failed to create user", "message": "%s"}`, err.Error())) + return + } + + jsonData := fmt.Sprintf(`{ + "id": "%s", + "created_at": "%s", + "updated_at": "%s", + "email": "%s" + }`, usr.ID, usr.CreatedAt, usr.UpdatedAt, usr.Email) + httpResponse.JSONHandler(w, http.StatusCreated, jsonData) + } +} + func validateChirpHandler(w http.ResponseWriter, r *http.Request) { type parameters struct { Body string `json:"body"` @@ -87,16 +114,12 @@ func validateChirpHandler(w http.ResponseWriter, r *http.Request) { params := parameters{} err := decoder.Decode(¶ms) if err != nil { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(`{"error": "Something went wrong"}`)) + httpResponse.SomethingWentWrong(w) return } if len(params.Body) > 140 { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error": "Chirp is too long"}`)) + httpResponse.JSONHandler(w, http.StatusBadRequest, `{"error": "Chirp is too long"}`) return } @@ -108,9 +131,7 @@ func validateChirpHandler(w http.ResponseWriter, r *http.Request) { chirp = re.ReplaceAllString(chirp, "****") } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(fmt.Sprintf(`{"cleaned_body": "%s"}`, chirp))) + httpResponse.JSONHandler(w, http.StatusOK, fmt.Sprintf(`{"cleaned_body": "%s"}`, chirp)) return } @@ -119,16 +140,17 @@ func main() { if err != nil { log.Fatal("Error loading .env file") } + apiCfg := &apiConfig{} dbURL := os.Getenv("DB_URL") + apiCfg.platform = os.Getenv("PLATFORM") + db, err := sql.Open("postgres", dbURL) if err != nil { log.Fatal(err) } - apiCfg := &apiConfig{} - dbQueries := database.New(db) apiCfg.db = *dbQueries @@ -138,9 +160,10 @@ func main() { mux.HandleFunc("GET /api/metrics", metricsHandler(apiCfg)) mux.HandleFunc("POST /api/reset", resetMetricsHandler(apiCfg)) mux.HandleFunc("POST /api/validate_chirp", validateChirpHandler) + mux.HandleFunc("POST /api/users", postUsersHandler(apiCfg)) mux.HandleFunc("GET /admin/metrics", adminMetricsHandler(apiCfg)) - mux.HandleFunc("POST /admin/reset", adminResetMetricsHandler(apiCfg)) + mux.HandleFunc("POST /admin/reset", resetMetricsHandler(apiCfg)) mux.Handle("/app/", apiCfg.middlewareMetricsInc(fileServerHandler())) diff --git a/sql/queries/DeleteAllUsers.sql b/sql/queries/DeleteAllUsers.sql new file mode 100644 index 0000000..649c04f --- /dev/null +++ b/sql/queries/DeleteAllUsers.sql @@ -0,0 +1,2 @@ +-- name: DeleteAllUser :exec +DELETE FROM users WHERE true; \ No newline at end of file diff --git a/sql/schema/001_users.sql b/sql/schema/001_users.sql index 09ba992..7235abe 100644 --- a/sql/schema/001_users.sql +++ b/sql/schema/001_users.sql @@ -3,7 +3,7 @@ CREATE TABLE users( id UUID PRIMARY KEY, created_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP, - email TEXT not null + email TEXT not null UNIQUE ); -- +goose Down