initial commit
This commit is contained in:
parent
80cb26c286
commit
4fc9bb5c1f
|
@ -21,3 +21,5 @@
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
# sqlite3 databases
|
||||||
|
*.db
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
app.render(w, r, http.StatusOK, "home.tmpl.html", templateData{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func postUserRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsersList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func postGuestbooksCreate(w http.ResponseWriter, r* http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGuestbooksList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGuestbook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
var (
|
||||||
|
method = r.Method
|
||||||
|
uri = r.URL.RequestURI()
|
||||||
|
)
|
||||||
|
|
||||||
|
app.logger.Error(err.Error(), "method", method, "uri", uri)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) clientError(w http.ResponseWriter, status int) {
|
||||||
|
http.Error(w, http.StatusText(status), status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
|
||||||
|
ts, ok := app.templateCache[page]
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("the template %s does not exist", page)
|
||||||
|
app.serverError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(status)
|
||||||
|
err := ts.ExecuteTemplate(w, "base", data)
|
||||||
|
if err != nil {
|
||||||
|
app.serverError(w, r, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type application struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
templateCache map[string]*template.Template
|
||||||
|
guestbooks *models.GuestbookModel
|
||||||
|
users *models.UserModel
|
||||||
|
guestbookComments *models.GuestbookCommentModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
addr := flag.String("addr", ":3000", "HTTP network address")
|
||||||
|
dsn := flag.String("dsn", "guestbook.db", "data source name")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||||
|
|
||||||
|
db, err := openDB(*dsn)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
templateCache, err := newTemplateCache()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &application{
|
||||||
|
templateCache: templateCache,
|
||||||
|
logger: logger,
|
||||||
|
guestbooks: &models.GuestbookModel{DB: db},
|
||||||
|
users: &models.UserModel{DB: db},
|
||||||
|
guestbookComments: &models.GuestbookCommentModel{DB: db},
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Starting server on %s", slog.Any("addr", ":4000"))
|
||||||
|
|
||||||
|
err = http.ListenAndServe(*addr, app.routes());
|
||||||
|
logger.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openDB(dsn string) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("sqlite", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = db.Ping(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) routes() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", app.home);
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type templateData struct {
|
||||||
|
CurrentYear int
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanDate(t time.Time) string {
|
||||||
|
return t.Format("02 Jan 2006 at 15:04")
|
||||||
|
}
|
||||||
|
|
||||||
|
var functions = template.FuncMap {
|
||||||
|
"humanDate": humanDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplateCache() (map[string]*template.Template, error) {
|
||||||
|
cache := map[string]*template.Template{}
|
||||||
|
pages, err := filepath.Glob("./ui/html/pages/*.tmpl.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, page := range pages {
|
||||||
|
name := filepath.Base(page)
|
||||||
|
ts, err := template.New(name).Funcs(functions).ParseFiles("./ui/html/base.tmpl.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts, err = ts.ParseGlob("./ui/html/partials/*.tmpl.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts, err = ts.ParseFiles(page)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cache[name] = ts
|
||||||
|
}
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) newTemplateData(r *http.Request) *templateData {
|
||||||
|
return &templateData {
|
||||||
|
CurrentYear: time.Now().Year(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
CREATE TABLE users (
|
||||||
|
Id blob(16) primary key,
|
||||||
|
Username varchar(32) NOT NULL,
|
||||||
|
Email varchar(256) NOT NULL,
|
||||||
|
IsDeleted boolean NOT NULL DEFAULT FALSE
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE guestbooks (
|
||||||
|
Id blob(16) primary key,
|
||||||
|
SiteUrl varchar(512) NOT NULL,
|
||||||
|
UserId blob(16) NOT NULL,
|
||||||
|
IsDeleted boolean NOT NULL DEFAULT FALSE,
|
||||||
|
IsActive boolean NOT NULL DEFAULT TRUE,
|
||||||
|
FOREIGN KEY (UserId) REFERENCES users(Id)
|
||||||
|
ON DELETE RESTRICT
|
||||||
|
ON UPDATE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE guestbook_comments (
|
||||||
|
Id blob(16) primary key,
|
||||||
|
GuestbookId blob(16) NOT NULL,
|
||||||
|
ParentId blob(16),
|
||||||
|
AuthorName varchar(256) NOT NULL,
|
||||||
|
AuthorEmail varchar(256) NOT NULL,
|
||||||
|
AuthorSite varchar(256),
|
||||||
|
CommentText text NOT NULL,
|
||||||
|
PageUrl varchar(256),
|
||||||
|
IsPublished boolean NOT NULL DEFAULT TRUE,
|
||||||
|
IsDeleted boolean NOT NULL DEFAULT FALSE,
|
||||||
|
FOREIGN KEY (GuestbookId)
|
||||||
|
REFERENCES guestbooks(Id)
|
||||||
|
ON DELETE RESTRICT
|
||||||
|
ON UPDATE RESTRICT,
|
||||||
|
FOREIGN KEY (ParentId)
|
||||||
|
REFERENCES guestbook_comments(Id)
|
||||||
|
ON DELETE RESTRICT
|
||||||
|
ON UPDATE RESTRICT
|
||||||
|
);
|
|
@ -0,0 +1,20 @@
|
||||||
|
module git.32bit.cafe/32bitcafe/guestbook
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||||
|
modernc.org/libc v1.55.3 // indirect
|
||||||
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
|
modernc.org/memory v1.8.0 // indirect
|
||||||
|
modernc.org/sqlite v1.33.1 // indirect
|
||||||
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
|
modernc.org/token v1.1.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,29 @@
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||||
|
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||||
|
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||||
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
|
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||||
|
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||||
|
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||||
|
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||||
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
@ -0,0 +1,47 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Guestbook struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
SiteUrl string
|
||||||
|
UserId uuid.UUID
|
||||||
|
IsDeleted bool
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuestbookModel struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookModel) Insert(siteUrl string, userId uuid.UUID) (uuid.UUID, error) {
|
||||||
|
id := uuid.New()
|
||||||
|
stmt := `INSERT INTO guestbooks (Id, SiteUrl, UserId, IsDeleted, IsActive)
|
||||||
|
VALUES(?, ?, FALSE, TRUE)`
|
||||||
|
_, err := m.DB.Exec(stmt, id, siteUrl, userId)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookModel) Get(id uuid.UUID) (Guestbook, error) {
|
||||||
|
stmt := `SELECT Id, SiteUrl, UserId, IsDeleted, IsActive FROM guestbooks
|
||||||
|
WHERE id = ?`
|
||||||
|
row := m.DB.QueryRow(stmt, id)
|
||||||
|
var g Guestbook
|
||||||
|
err := row.Scan(&g.ID, &g.SiteUrl, &g.UserId, &g.IsDeleted, &g.IsActive)
|
||||||
|
if err != nil {
|
||||||
|
return Guestbook{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookModel) GetAll(userId uuid.UUID) ([]Guestbook, error) {
|
||||||
|
return []Guestbook{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GuestbookComment struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
GuestbookId uuid.UUID
|
||||||
|
ParentId uuid.UUID
|
||||||
|
AuthorName string
|
||||||
|
AuthorEmail string
|
||||||
|
AuthorSite string
|
||||||
|
CommentText string
|
||||||
|
PageUrl string
|
||||||
|
IsPublished bool
|
||||||
|
IsDeleted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuestbookCommentModel struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookCommentModel) Insert(guestbookId, parentId uuid.UUID, authorName,
|
||||||
|
authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (uuid.UUID, error) {
|
||||||
|
id := uuid.New()
|
||||||
|
stmt := `INSERT INTO guestbook_comments (Id, GuestbookId, ParentId, AuthorName,
|
||||||
|
AuthorEmail, AuthorSite, CommentText, PageUrl, IsPublished, IsDeleted)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE)`
|
||||||
|
_, err := m.DB.Exec(stmt, id, guestbookId, parentId, authorName, authorEmail,
|
||||||
|
authorSite, commentText, pageUrl, isPublished)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookCommentModel) Get(id uuid.UUID) (GuestbookComment, error) {
|
||||||
|
stmt := `SELECT Id, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
|
||||||
|
CommentText, PageUrl, IsPublished, IsDeleted FROM guestbook_comments WHERE id = ?`
|
||||||
|
row := m.DB.QueryRow(stmt, id)
|
||||||
|
var c GuestbookComment
|
||||||
|
err := row.Scan(&c.ID, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.IsPublished, &c.IsDeleted)
|
||||||
|
if err != nil {
|
||||||
|
return GuestbookComment{}, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GuestbookCommentModel) GetAll(guestbookId *uuid.UUID) {
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
IsDeleted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserModel struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserModel) Insert(username string, email string) (uuid.UUID, error) {
|
||||||
|
id := uuid.New()
|
||||||
|
stmt := `INSERT INTO users (Id, Username, Email, IsDeleted)
|
||||||
|
VALUES (?, ?, ?, FALSE)`
|
||||||
|
_, err := m.DB.Exec(stmt, id, username, email)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserModel) Get(id uuid.UUID) (User, error) {
|
||||||
|
stmt := `SELECT Id, Username, Email, IsDeleted FROM users WHERE id = ?`
|
||||||
|
row := m.DB.QueryRow(stmt, id)
|
||||||
|
var u User
|
||||||
|
err := row.Scan(&u.ID, &u.Username, &u.Email, &u.IsDeleted)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{{ define "base" }}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{{ template "title" }} - Guestbook</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><a href="/">Guestbook</a></h1>
|
||||||
|
</header>
|
||||||
|
{{ template "nav" . }}
|
||||||
|
<main>
|
||||||
|
{{ template "main" . }}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>A 32-bit Cafe Project</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{ define "title" }}Home{{ end }}
|
||||||
|
{{ define "main" }}
|
||||||
|
<h2>Latest Guestbooks</h2>
|
||||||
|
<p>There's nothing here yet</p>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{ define "nav" }}
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</nav>
|
||||||
|
{{ end }}
|
Loading…
Reference in New Issue