new architecture, add handlers, begin model code

This commit is contained in:
yequari 2023-07-11 00:38:05 -07:00
parent 5cd3eeb765
commit 5877e2556c
11 changed files with 355 additions and 122 deletions

78
cmd/web/handlers.go Normal file
View File

@ -0,0 +1,78 @@
package main
import (
"fmt"
"errors"
"net/http"
"text/template"
"git.32bit.cafe/yequari/webring/internal/models"
)
func (app *application) home(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
files := []string{
"./ui/html/base.tmpl.html",
"./ui/html/pages/home.tmpl.html",
}
ts, err := template.ParseFiles(files...)
if err != nil {
app.serverError(w, err)
return
}
err = ts.ExecuteTemplate(w, "base", nil)
if err != nil {
app.serverError(w, err)
}
}
func (app *application) webmasterView(w http.ResponseWriter, r *http.Request) {
id := models.WebmasterId(r.URL.Query().Get("id"))
webmaster, err := app.webmasters.Get(id)
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
app.notFound(w)
} else {
app.serverError(w, err)
}
return
}
fmt.Fprintf(w, "%+v", webmaster)
}
func (app *application) webmasterCreate(w http.ResponseWriter, r *http.Request) {
}
func (app *application) siteEntryView(w http.ResponseWriter, r *http.Request) {
id := models.SiteId(r.URL.Query().Get("id"))
siteEntry, err := app.siteEntries.Get(id)
if err != nil {
if errors.Is(err, models.ErrNoRecord) {
app.notFound(w)
} else {
app.serverError(w, err)
}
return
}
fmt.Fprintf(w, "%+v", siteEntry)
}
func (app *application) siteEntryCreate(w http.ResponseWriter, r *http.Request) {
}
func (app *application) nextSiteEntry(w http.ResponseWriter, r *http.Request) {
}
func (app *application) prevSiteEntry(w http.ResponseWriter, r *http.Request) {
}

22
cmd/web/helpers.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"fmt"
"net/http"
"runtime/debug"
)
func (app *application) serverError(w http.ResponseWriter, err error) {
trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
app.errorLog.Print(trace)
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) notFound(w http.ResponseWriter) {
app.clientError(w, http.StatusNotFound)
}

63
cmd/web/main.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"flag"
"log"
"net/http"
"os"
"database/sql"
"git.32bit.cafe/yequari/webring/internal/models"
_ "modernc.org/sqlite"
)
type application struct {
errorLog *log.Logger
infoLog *log.Logger
siteEntries *models.SiteEntryModel
webmasters *models.WebmasterModel
}
func main() {
addr := flag.String("addr", ":8000", "HTTP network address")
dsn := flag.String("dsn", "webring.db", "data source name")
flag.Parse()
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
db, err := openDB(*dsn)
if err != nil {
errorLog.Fatal(err)
}
defer db.Close()
app := &application{
errorLog: errorLog,
infoLog: infoLog,
siteEntries: &models.SiteEntryModel{DB: db},
webmasters: &models.WebmasterModel{DB: db},
}
srv := &http.Server{
Addr: *addr,
ErrorLog: errorLog,
Handler: app.routes(),
}
infoLog.Printf("Starting server on %s", *addr)
err = srv.ListenAndServe()
errorLog.Fatal(err)
}
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
}

17
cmd/web/routes.go Normal file
View File

@ -0,0 +1,17 @@
package main
import "net/http"
func (app *application) routes() *http.ServeMux {
mux := http.NewServeMux()
fileServer := http.FileServer(http.Dir("./ui/static"))
mux.Handle("/static/", http.StripPrefix("/static", fileServer))
// TODO: add more handlers
mux.HandleFunc("/", app.home)
mux.HandleFunc("/webmasters/view", app.webmasterView)
mux.HandleFunc("/sites/view", app.siteEntryView)
return mux
}

View File

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

View File

@ -0,0 +1,83 @@
package models
import (
"database/sql"
"time"
"errors"
"github.com/google/uuid"
)
type SiteId string
type SiteEntry struct {
Id SiteId
Name string
Webmaster WebmasterId
Url string
DateAdded time.Time
Next SiteId
Prev SiteId
}
type SiteEntryModel struct {
DB *sql.DB
}
// Commit a SiteEntry to the database
func (m *SiteEntryModel) Insert(name string, url string, webmaster *Webmaster) (SiteId, error) {
stmt := `INSERT INTO siteentries (id, name, url, webmaster, dateAdded)
VALUES(?,?,?,?,datetime("now"))`
id := uuid.NewString()
_, err := m.DB.Exec(stmt, id, name, url, webmaster.Id)
if err != nil {
return "", nil
}
return SiteId(id), nil
}
// Retrieve a SiteEntry from the database by id
func (m *SiteEntryModel) Get(id SiteId) (*SiteEntry, error) {
stmt := `SELECT id, name, url, webmaster, dateAdded FROM siteentries
WHERE id = ?`
row := m.DB.QueryRow(stmt, id)
s := &SiteEntry{}
var timeStr string
err := row.Scan(&s.Id, &s.Name, &s.Url, &s.Webmaster, &timeStr)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNoRecord
} else {
return nil, err
}
}
// time is stored as a string in sqlite, so we need to parse it
// probably should be something taken care of in the driver
dateAdded, err := time.Parse(time.DateTime, timeStr)
if err != nil {
return nil, err
}
s.DateAdded = dateAdded
return s, nil
}
// Retrieve a SiteEntry from the database by url
func (m *SiteEntryModel) GetByUrl(url string) (*SiteEntry, error) {
return nil, nil
}
// Update existing SiteEntry with the values of passed entry
func (m *SiteEntryModel) Update(entry *SiteEntry) error {
return nil
}
// Delete SiteEntry from database
func (m *SiteEntryModel) Delete(entry *SiteEntry) error {
return nil
}

View File

@ -0,0 +1,64 @@
package models
import (
"database/sql"
"errors"
"github.com/google/uuid"
)
type WebmasterId string
type Webmaster struct {
Id WebmasterId
Name string
Email string
}
type WebmasterModel struct {
DB *sql.DB
}
// Commit a SiteWebmaster to the database
func (m *WebmasterModel) Insert(name string, email string) (WebmasterId, error) {
stmt := `INSERT INTO webmasters (id, name, email)
VALUES(?, ?, ?)`
id := uuid.NewString()
_, err := m.DB.Exec(stmt, id, name, email)
if err != nil {
return "", err
}
return WebmasterId(id), nil
}
// Retrieve a SiteWebmasterModel from the database by id
func (m *WebmasterModel) Get(id WebmasterId) (*Webmaster, error) {
stmt := `SELECT id, name, email FROM webmasters
WHERE id = ?`
result := m.DB.QueryRow(stmt, id)
w := &Webmaster{}
err := result.Scan(&w.Id, &w.Name, &w.Email)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNoRecord
} else {
return nil, err
}
}
return w, nil
}
// Update a SiteWebmasterModel in the database with the values of the passed webmaster
func (m *WebmasterModel) Update(webmaster *Webmaster) error {
return nil
}
// Delete a SiteWebmasterModel from the database
func (m *WebmasterModel) Delete(webmaster *Webmaster) error {
return nil
}

11
main.go
View File

@ -1,11 +0,0 @@
package main
import (
"fmt"
"git.32bit.cafe/yequari/webring/webring"
)
func main() {
site := webring.NewSiteEntry("example", "me", "example.com")
fmt.Println(site)
}

17
ui/html/base.tmpl.html Normal file
View File

@ -0,0 +1,17 @@
{{define "base"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{template "title" .}} - 32-Bit Cafe</title>
</head>
<body>
<header>
<h1>Webring</h1>
</header>
<main>
{{template "main" .}}
</main>
</body>
</html>
{{end}}

View File

@ -0,0 +1,6 @@
{{define "title"}}Home{{end}}
{{define "main"}}
<h2>Latest Websites</h2>
<p>There's nothing to see here yet!</p>
{{end}}

View File

@ -1,111 +0,0 @@
package webring
import (
"database/sql"
"time"
"github.com/google/uuid"
)
type SiteId string
type SiteEntry struct {
Id SiteId
Name string
Webmaster *SiteWebmaster
Url string
DateAdded time.Time
Next SiteId
Prev SiteId
}
func NewSiteEntry(siteName, webmasterEmail, siteUrl string) SiteEntry {
// next site is the first one in the ring
// previous site is the last one inserted
// retrieve webmaster from database or create if it doesn't exist
return SiteEntry{
Id: SiteId(uuid.NewString()),
Name: siteName,
Url: siteUrl,
DateAdded: time.Now(),
}
}
type WebmasterId string
type SiteWebmaster struct {
Id WebmasterId
Name string
Email string
}
func NewSiteWebmaster(name, email string) SiteWebmaster {
return SiteWebmaster{ Id: WebmasterId(uuid.NewString()),
Name: name,
Email: email,
}
}
type Webring struct {
Db *sql.DB
Length int
first SiteId
last SiteId
}
// Retrieve the first website added to the webring
func (webring *Webring) retrieveFirstSite() (*SiteEntry, error) {
return nil, nil
}
// Retrieve the latest website added to the webring
func (webring *Webring) retrieveLastSite() (*SiteEntry, error) {
return nil, nil
}
// Commit a SiteEntry to the database
func (webring *Webring) CreateSiteEntry(entry *SiteEntry) error {
return nil
}
// Retrieve a SiteEntry from the database by id
func (webring *Webring) RetrieveSiteEntry(id SiteId) (*SiteEntry, error) {
return nil, nil
}
// Retrieve a SiteEntry from the database by url
func (webring *Webring) RetriveSiteEntryByUrl(url string) (*SiteEntry, error) {
return nil, nil
}
// Update existing SiteEntry with the values of passed entry
func (webring *Webring) UpdateSiteEntry(entry *SiteEntry) error {
return nil
}
// Delete SiteEntry from database
func (webring *Webring) DeleteSiteEntry(entry *SiteEntry) error {
return nil
}
// Commit a SiteWebmaster to the database
func (webring *Webring) CreateSiteWebmaster(webmaster *SiteWebmaster) error {
return nil
}
// Retrieve a SiteWebmaster from the database by id
func (webring *Webring) RetrieveSiteWebmaster(id WebmasterId) (*SiteWebmaster, error) {
return nil, nil
}
// Update a SiteWebmaster in the database with the values of the passed webmaster
func (webring *Webring) UpdateSiteWebmaster(webmaster *SiteWebmaster) error {
return nil
}
// Delete a SiteWebmaster from the database
func (webring *Webring) DeleteSiteWebmaster(webmaster *SiteWebmaster) error {
return nil
}