new architecture, add handlers, begin model code
This commit is contained in:
		
							parent
							
								
									5cd3eeb765
								
							
						
					
					
						commit
						5877e2556c
					
				
							
								
								
									
										78
									
								
								cmd/web/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								cmd/web/handlers.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										22
									
								
								cmd/web/helpers.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										63
									
								
								cmd/web/main.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										17
									
								
								cmd/web/routes.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										5
									
								
								internal/models/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								internal/models/errors.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| package models | ||||
| 
 | ||||
| import "errors" | ||||
| 
 | ||||
| var ErrNoRecord = errors.New("models: no matching record found") | ||||
							
								
								
									
										83
									
								
								internal/models/siteentry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								internal/models/siteentry.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										64
									
								
								internal/models/webmaster.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/models/webmaster.go
									
									
									
									
									
										Normal 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
									
									
									
									
									
								
							
							
						
						
									
										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) | ||||
| } | ||||
							
								
								
									
										17
									
								
								ui/html/base.tmpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ui/html/base.tmpl.html
									
									
									
									
									
										Normal 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}} | ||||
							
								
								
									
										6
									
								
								ui/html/pages/home.tmpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ui/html/pages/home.tmpl.html
									
									
									
									
									
										Normal 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}} | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user