From 0a978da3ac497b9faa3dc842af0d49ef9f861842 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 19 Jan 2025 14:32:25 -0500 Subject: [PATCH] Initial insert in handler --- cmd/ratchetd/main.go | 2 +- internal/model/snippets.go | 66 ++++++++++++++++++++++++++++++++++ internal/server/base_server.go | 26 +++++++++++--- 3 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 internal/model/snippets.go diff --git a/cmd/ratchetd/main.go b/cmd/ratchetd/main.go index 94a50e0..d9ccf1b 100644 --- a/cmd/ratchetd/main.go +++ b/cmd/ratchetd/main.go @@ -45,7 +45,7 @@ func main() { // Propagate build information to root package to share globally // ratchet.Version = strings.TrimPrefix(version, "") // ratchet.Commit = commit - server := server.NewRatchetServer(logger) + server := server.NewRatchetServer(logger, db) // START SERVING REQUESTS slog.Debug("Herp dirp!") diff --git a/internal/model/snippets.go b/internal/model/snippets.go new file mode 100644 index 0000000..7c146d9 --- /dev/null +++ b/internal/model/snippets.go @@ -0,0 +1,66 @@ +package model + +import ( + "database/sql" + "fmt" + "log/slog" + "time" +) + +type Snippet struct { + ID int + Title string + Content string + CreatedAt time.Time + UpdatedAt time.Time + ExpiresAt time.Time +} + +type SnippetService struct { + DB *sql.DB +} + +// Insert inserts a new SnippetModel into the database +func (s *SnippetService) Insert(title, content string, expiresAt int) (int, error) { + slog.Debug(fmt.Sprintf("Inserting new snippet. Title: %s", title)) + stmt, err := s.DB.Prepare("INSERT INTO snippets (title, content, expires_at) VALUES ($1, $2, DATETIME(CURRENT_TIMESTAMP, '+' || $3 || ' DAY'))") + if err != nil { + slog.Debug("The prepared statement has an error") + return 0, err + } + defer stmt.Close() + + // stmt.Exec returns a sql.Result. That also has access to the statement metadata + // use _ if you don't care about the result and only want to check the err. + + // Exec will NOT reserve a connection. unlike db.Query which returns a sql.Rows that + // will hold on to a connection until .Close() is called. + res, err := stmt.Exec(title, content, expiresAt) + if err != nil { + slog.Debug("SQL DDL returned an error.") + return 0, err + } + + // Use the LastInsertId() method on the result to get the ID of our + // newly inserted record in the snippets table. + lastId, err := res.LastInsertId() + if err != nil { + slog.Debug("An error occured when retrieving insert result id.") + return 0, err + } + + // The ID returned has the type int64, so we convert it to an int type + // before returning. + slog.Debug(fmt.Sprintf("Inserted new snippet. Snippet pk: %d", int(lastId))) + return int(lastId), nil +} + +// Get retrieves a specific Snippet by ID +func (s *SnippetService) Get(id int) (Snippet, error) { + return Snippet{}, nil +} + +// Latest retrieves up to latest 10 Snippets from the database. +func (s *SnippetService) Lastest() (Snippet, error) { + return Snippet{}, nil +} diff --git a/internal/server/base_server.go b/internal/server/base_server.go index c9219b8..db19959 100644 --- a/internal/server/base_server.go +++ b/internal/server/base_server.go @@ -1,6 +1,7 @@ package server import ( + "database/sql" "fmt" "html/template" "log/slog" @@ -8,6 +9,7 @@ import ( "strconv" "git.runcible.io/learning/ratchet/internal/domain/user" + "git.runcible.io/learning/ratchet/internal/model" ) type RatchetServer struct { @@ -15,12 +17,14 @@ type RatchetServer struct { logger *slog.Logger //Services used by HTTP routes - UserService user.UserService + snippetService *model.SnippetService + UserService user.UserService } -func NewRatchetServer(logger *slog.Logger) *RatchetServer { +func NewRatchetServer(logger *slog.Logger, db *sql.DB) *RatchetServer { rs := new(RatchetServer) rs.logger = logger + rs.snippetService = &model.SnippetService{DB: db} // TODO implement middleware that disables directory listings fileServer := http.FileServer(http.Dir("./ui/static/")) router := http.NewServeMux() @@ -101,15 +105,27 @@ func (rs *RatchetServer) snippetView(w http.ResponseWriter, r *http.Request) { // snippetCreate handles display of the form used to create snippets func (rs *RatchetServer) snippetCreate(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Displaying snippetCreate form...")) + w.Write([]byte("Create snippet form...")) } // snippetCreate handles display of the form used to create snippets +// +// curl -iL -d "" http://localhost:5000/snippet/create func (rs *RatchetServer) snippetCreatePost(w http.ResponseWriter, r *http.Request) { // example of a custom header. Must be done before calling WriteHeader // or they will fail to take effect. w.Header().Add("Server", "Dirp") - w.WriteHeader(http.StatusCreated) + // Create some variables holding dummy data. We'll remove these later on + // during the build. + title := "O snail" + content := "O snail\nClimb Mount Fuji,\nBut slowly, slowly!\n\n– Kobayashi Issa" + expires := 7 - w.Write([]byte("Created snippet...")) + id, err := rs.snippetService.Insert(title, content, expires) + + if err != nil { + rs.serverError(w, r, err) + } + + http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther) }