lets-go:4-x
This commit is contained in:
parent
fd34f7ba42
commit
569f2fc0a9
@ -1,10 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
//"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"snippetbox.chaosfem.tw/internal/models"
|
||||
)
|
||||
|
||||
// home ...
|
||||
@ -14,22 +17,32 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
files := []string{
|
||||
"./ui/html/base.tmpl",
|
||||
"./ui/html/partials/nav.tmpl",
|
||||
"./ui/html/pages/home.tmpl",
|
||||
}
|
||||
|
||||
ts, err := template.ParseFiles(files...)
|
||||
snippets, err := app.snippets.Latest()
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", nil)
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
for _, snippet := range snippets {
|
||||
fmt.Fprintf(w, "%+v", snippet)
|
||||
}
|
||||
|
||||
// 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 ...
|
||||
@ -40,7 +53,17 @@ func (app *application) snippetView(w http.ResponseWriter, r *http.Request) {
|
||||
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 ...
|
||||
@ -52,5 +75,15 @@ func (app *application) snippetCreate(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
}
|
||||
|
@ -1,32 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"os"
|
||||
|
||||
"snippetbox.chaosfem.tw/internal/models"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// for application wide dependencies
|
||||
type application struct {
|
||||
logger *slog.Logger
|
||||
snippets *models.SnippetModel
|
||||
}
|
||||
|
||||
// main it's the snippetbox webapp
|
||||
func main() {
|
||||
// configuration
|
||||
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")
|
||||
loglevel := flag.String("loglevel", "INFO", "Log level: DEBUG, INFO, WARN, or ERROR")
|
||||
flag.Parse()
|
||||
|
||||
// setup the application
|
||||
app := &application{
|
||||
logger: loggerBuilder(logfmt, loglevel),
|
||||
}
|
||||
logger := loggerBuilder(logfmt, loglevel)
|
||||
|
||||
app.logger.Info("starting server", slog.String("addr", *addr))
|
||||
err := http.ListenAndServe(*addr, app.routes())
|
||||
app.logger.Error(err.Error())
|
||||
db, err := openDB(*dsn, logger)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer db.Close()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ CREATE TABLE snippets (
|
||||
expires DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE USER 'web'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON snippetbox.* TO 'web'@'localhost' IDENTIFIED BY 'dbpass';
|
||||
CREATE USER 'web';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON snippetbox.* TO 'web' IDENTIFIED BY 'dbpass';
|
||||
|
||||
CREATE INDEX idx_snippets_created ON snippets(created);
|
||||
|
7
snippetbox/internal/models/errors.go
Normal file
7
snippetbox/internal/models/errors.go
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrNoRecord = errors.New("models: no matching record found")
|
86
snippetbox/internal/models/snippets.go
Normal file
86
snippetbox/internal/models/snippets.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user