new architecture, add handlers, begin model code
This commit is contained in:
parent
5cd3eeb765
commit
5877e2556c
|
@ -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) {
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrNoRecord = errors.New("models: no matching record found")
|
|
@ -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
|
||||||
|
}
|
|
@ -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
11
main.go
|
@ -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)
|
|
||||||
}
|
|
|
@ -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}}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{{define "title"}}Home{{end}}
|
||||||
|
|
||||||
|
{{define "main"}}
|
||||||
|
<h2>Latest Websites</h2>
|
||||||
|
<p>There's nothing to see here yet!</p>
|
||||||
|
{{end}}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue