lets-go:4-x

This commit is contained in:
tamsin johnson 2024-01-25 12:41:08 -08:00
parent fd34f7ba42
commit 569f2fc0a9
5 changed files with 183 additions and 21 deletions

View File

@ -1,10 +1,13 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"html/template" //"html/template"
"net/http" "net/http"
"strconv" "strconv"
"snippetbox.chaosfem.tw/internal/models"
) )
// home ... // home ...
@ -14,22 +17,32 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
return return
} }
files := []string{ snippets, err := app.snippets.Latest()
"./ui/html/base.tmpl",
"./ui/html/partials/nav.tmpl",
"./ui/html/pages/home.tmpl",
}
ts, err := template.ParseFiles(files...)
if err != nil { if err != nil {
app.serverError(w, r, err) app.serverError(w, r, err)
return return
} }
err = ts.ExecuteTemplate(w, "base", nil) for _, snippet := range snippets {
if err != nil { fmt.Fprintf(w, "%+v", snippet)
app.serverError(w, r, err)
} }
// files := []string{
// "./ui/html/base.tmpl",
// "./ui/html/partials/nav.tmpl",
// "./ui/html/pages/home.tmpl",
// }
// ts, err := template.ParseFiles(files...)
// if err != nil {
// app.serverError(w, r, err)
// return
// }
// err = ts.ExecuteTemplate(w, "base", nil)
// if err != nil {
// app.serverError(w, r, err)
// }
} }
// snippetView ... // snippetView ...
@ -40,7 +53,17 @@ func (app *application) snippetView(w http.ResponseWriter, r *http.Request) {
return return
} }
fmt.Fprintf(w, "It's snippet id: %d", id) snippet, err := app.snippets.Get(id)
if err != nil || id < 1 {
if errors.Is(err, models.ErrNoRecord) {
app.notFound(w)
} else {
app.serverError(w, r, err)
}
return
}
fmt.Fprintf(w, "%+v", snippet)
} }
// snippetCreate ... // snippetCreate ...
@ -52,5 +75,15 @@ func (app *application) snippetCreate(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Write([]byte("Creating a snippet!")) title := "0 snail"
content := "0 snail\nClimb Mount Fuji,\nBut slowly, slowly!\n\n - Kobayashi Issa"
expires := 7
id, err := app.snippets.Insert(title, content, expires)
if err != nil {
app.serverError(w, r, err)
return
}
http.Redirect(w, r, fmt.Sprintf("/snippet/view?id=%d", id), http.StatusSeeOther)
} }

View File

@ -1,32 +1,68 @@
package main package main
import ( import (
"database/sql"
"flag" "flag"
"log/slog" "log/slog"
"net/http" "net/http"
"regexp"
"os" "os"
"snippetbox.chaosfem.tw/internal/models"
_ "github.com/go-sql-driver/mysql"
) )
// for application wide dependencies // for application wide dependencies
type application struct { type application struct {
logger *slog.Logger logger *slog.Logger
snippets *models.SnippetModel
} }
// main it's the snippetbox webapp // main it's the snippetbox webapp
func main() { func main() {
// configuration // configuration
addr := flag.String("addr", ":4000", "HTTP network address") addr := flag.String("addr", ":4000", "HTTP network address")
dsn := flag.String("dsn", "web:dbpass@/snippetbox?parseTime=true", "DB data source name")
logfmt := flag.String("logfmt", "text", "Log output format") logfmt := flag.String("logfmt", "text", "Log output format")
loglevel := flag.String("loglevel", "INFO", "Log level: DEBUG, INFO, WARN, or ERROR") loglevel := flag.String("loglevel", "INFO", "Log level: DEBUG, INFO, WARN, or ERROR")
flag.Parse() flag.Parse()
// setup the application logger := loggerBuilder(logfmt, loglevel)
app := &application{
logger: loggerBuilder(logfmt, loglevel), db, err := openDB(*dsn, logger)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
} }
app.logger.Info("starting server", slog.String("addr", *addr)) defer db.Close()
err := http.ListenAndServe(*addr, app.routes())
app.logger.Error(err.Error()) // setup the application
app := &application{
logger: logger,
snippets: &models.SnippetModel{DB: db},
}
logger.Info("starting server", slog.String("addr", *addr))
err = http.ListenAndServe(*addr, app.routes())
logger.Error(err.Error())
os.Exit(1) os.Exit(1)
} }
func openDB(dsn string, logger *slog.Logger) (*sql.DB, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
db.Close()
return nil, err
}
passRegexp := regexp.MustCompile(":\\S+@")
logger.Info("Opened DB connection pool", slog.String("adapter", "mysql"), slog.String("dsn", passRegexp.ReplaceAllString(dsn, ":<REDACTED>@")))
return db, nil
}

View File

@ -8,7 +8,7 @@ CREATE TABLE snippets (
expires DATETIME NOT NULL expires DATETIME NOT NULL
); );
CREATE USER 'web'@'localhost'; CREATE USER 'web';
GRANT SELECT, INSERT, UPDATE, DELETE ON snippetbox.* TO 'web'@'localhost' IDENTIFIED BY 'dbpass'; GRANT SELECT, INSERT, UPDATE, DELETE ON snippetbox.* TO 'web' IDENTIFIED BY 'dbpass';
CREATE INDEX idx_snippets_created ON snippets(created); CREATE INDEX idx_snippets_created ON snippets(created);

View File

@ -0,0 +1,7 @@
package models
import (
"errors"
)
var ErrNoRecord = errors.New("models: no matching record found")

View File

@ -0,0 +1,86 @@
package models
import (
"database/sql"
"errors"
"time"
)
type Snippet struct {
ID int
Title string
Content string
Created time.Time
Expires time.Time
}
type SnippetModel struct {
DB *sql.DB
}
// Insert ...
func (m *SnippetModel) Insert(title string, content string, expires int) (int, error) {
stmt := `INSERT INTO snippets (title, content, created, expires)
VALUES(?, ?, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? DAY))`
result, err := m.DB.Exec(stmt, title, content, expires)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
// Get ...
func (m *SnippetModel) Get(id int) (Snippet, error) {
stmt := `SELECT id, title, content, created, expires FROM snippets
WHERE expires > UTC_TIMESTAMP() and id = ?`
var s Snippet
err := m.DB.QueryRow(stmt, id).Scan(&s.ID, &s.Title, &s.Content, &s.Created, &s.Expires)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return Snippet{}, ErrNoRecord
} else {
return Snippet{}, err
}
}
return s, nil
}
// Latest ...
func (m *SnippetModel) Latest() ([]Snippet, error) {
stmt := `SELECT id, title, content, created, expires FROM snippets
WHERE expires > UTC_TIMESTAMP() ORDER BY id DESC LIMIT 10`
rows, err := m.DB.Query(stmt)
if err != nil {
return nil, err
}
defer rows.Close()
var snippets []Snippet
for rows.Next() {
var s Snippet
err = rows.Scan(&s.ID, &s.Title, &s.Content, &s.Created, &s.Expires)
if err != nil {
return nil, err
}
snippets = append(snippets, s)
}
if err = rows.Err(); err != nil {
return nil, err
}
return snippets, nil
}