summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorMatthew Lemon <y@yulqen.org>2024-04-18 11:06:32 +0100
committerMatthew Lemon <y@yulqen.org>2024-04-18 11:06:32 +0100
commit20560419614e22fbb58567cdb0b88b54caf679f4 (patch)
tree163df44f07907d865b363db3bc198287ebd3d25f /cmd
parent435742cede199e3c85b5e2eb5a42ccbee4906a05 (diff)
Adds code from ded-go-core - no database
D2S test app code removed (nginx error). Adds all go code from `ded-go-core` using the basic Gov.UK UI for a test page for DED.
Diffstat (limited to 'cmd')
-rw-r--r--cmd/web/handlers.go144
-rw-r--r--cmd/web/helpers.go52
-rw-r--r--cmd/web/main.go82
-rw-r--r--cmd/web/routes.go18
-rw-r--r--cmd/web/templates.go48
5 files changed, 344 insertions, 0 deletions
diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go
new file mode 100644
index 0000000..0827ea1
--- /dev/null
+++ b/cmd/web/handlers.go
@@ -0,0 +1,144 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/defencedigital/ded-web/internal/models"
+)
+
+func (app *application) listPersons(w http.ResponseWriter, r *http.Request) {
+ ps, err := app.persons.ListAll()
+ if err != nil {
+ app.serverError(w, r, err)
+ return
+ }
+
+ app.render(w, r, http.StatusOK, "persons_list.tmpl.html", templateData{
+ Persons: ps,
+ })
+}
+
+func (app *application) listOperations(w http.ResponseWriter, r *http.Request) {
+
+ ops, err := app.operations.ListAll()
+ if err != nil {
+ app.serverError(w, r, err)
+ return
+ }
+
+ var newOps []models.Operation
+
+ for _, op := range ops {
+ esses, err := app.engagementStrategies.GetForOperation(op.ID)
+ // TODO: Check what kind of error this is, don't just continue
+ if err != nil {
+ continue
+ }
+ if len(esses) > 0 {
+ op.EngagementStrategies = esses
+ newOps = append(newOps, op)
+ } else {
+ newOps = append(newOps, op)
+ }
+ }
+
+ app.render(w, r, http.StatusOK, "operations_list.tmpl.html", templateData{
+ Operations: newOps,
+ })
+}
+
+func (app *application) listOrganisations(w http.ResponseWriter, r *http.Request) {
+
+ latest, err := app.organisations.Latest()
+ if err != nil {
+ app.serverError(w, r, err)
+ return
+ }
+
+ // We are putting latest in the Organisations field of templateData and leaving
+ // templateData.Operations as the nil value because we don't need Operations in this
+ // page, of course.
+ app.render(w, r, http.StatusOK, "organisations_list.tmpl.html", templateData{
+ Organisations: latest,
+ })
+}
+
+func (app *application) home(w http.ResponseWriter, r *http.Request) {
+ // Check if the current request URL path exactly matches "/". If it doesn't, use
+ // the http.NotFound() function to send a 404 response to the client.
+ // Importantly, we then return from the handler. If we don't return the handler
+ // would keep executing.
+ if r.URL.Path != "/" {
+ app.notFound(w)
+ return
+ }
+
+ // organisations, err := app.organisations.Latest()
+ // if err != nil {
+ // app.serverError(w, r, err)
+ // return
+ // }
+
+ // for _, organisation := range organisations {
+ // fmt.Fprintf(w, "%+v\n", organisation)
+ // }
+
+ // files := []string{
+ // "./ui/html/base.tmpl.html",
+ // "./ui/html/pages/home.tmpl.html",
+ // "./ui/html/partials/nav.tmpl.html",
+ // }
+
+ // ts, err := template.ParseFiles(files...)
+ // if err != nil {
+ // app.serverError(w, r, err) // Use the serverError() helper
+ // return
+ // }
+
+ // err = ts.ExecuteTemplate(w, "base", nil)
+ // if err != nil {
+ // app.serverError(w, r, err) // Use the serverError() helper
+ // }
+ app.render(w, r, http.StatusOK, "home.tmpl.html", templateData{})
+}
+
+func (app *application) organisationView(w http.ResponseWriter, r *http.Request) {
+ id, err := strconv.Atoi(r.URL.Query().Get("id"))
+ if err != nil || id < 1 {
+ app.notFound(w)
+ return
+ }
+
+ o, err := app.organisations.Get(id)
+ if err != nil {
+ if errors.Is(err, models.ErrNoRecord) {
+ app.notFound(w)
+ } else {
+ app.serverError(w, r, err)
+ }
+ return
+ }
+ fmt.Fprintf(w, "%+v", o)
+}
+
+func (app *application) organisationCreate(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ w.Header().Set("Allow", http.MethodPost)
+ app.clientError(w, http.StatusMethodNotAllowed)
+ return
+ }
+
+ // Dummy data
+ name := "Test Organisation"
+
+ id, err := app.organisations.Insert(name)
+ if err != nil {
+ app.serverError(w, r, err)
+ return
+ }
+
+ http.Redirect(w, r, fmt.Sprintf("/organisation/view?id=%d", id), http.StatusSeeOther)
+}
diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go
new file mode 100644
index 0000000..fc9b8f8
--- /dev/null
+++ b/cmd/web/helpers.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "runtime/debug"
+)
+
+func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) {
+ var (
+ method = r.Method
+ uri = r.URL.RequestURI()
+ trace = string(debug.Stack())
+ )
+
+ app.logger.Error(err.Error(), "method", method, "uri", uri, "trace", trace)
+ log.Printf("Crash! %s %s %s", method, uri, trace)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+}
+
+// The clientError helper sends a specific status code and corresponding description
+// to the user. We'll use this later in the book to send responses like 400 "Bad
+// Request" when there's a problem with the request that the user sent.
+func (app *application) clientError(w http.ResponseWriter, status int) {
+ http.Error(w, http.StatusText(status), status)
+}
+
+// notFound helper is a convenience wrapper around clientError which sends a 404 Not Found response
+func (app *application) notFound(w http.ResponseWriter) {
+ app.clientError(w, http.StatusNotFound)
+}
+
+func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
+ // retrieve the appropriate template set from the cache based on the page name.
+ // If no entry exists in the cache with the provided name, create a new error
+ // and call serverError() helper that we made earlier and return.
+ ts, ok := app.templateCache[page]
+ if !ok {
+ err := fmt.Errorf("the template %s does not exist", page)
+ app.serverError(w, r, err)
+ return
+ }
+
+ // Write out the provided HTTP status code('200 OK', '400 Bad Request', etc).
+ w.WriteHeader(status)
+
+ err := ts.ExecuteTemplate(w, "base", data)
+ if err != nil {
+ app.serverError(w, r, err)
+ }
+}
diff --git a/cmd/web/main.go b/cmd/web/main.go
new file mode 100644
index 0000000..5f4bfdf
--- /dev/null
+++ b/cmd/web/main.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+ "database/sql"
+ "flag"
+ "html/template"
+ "log/slog"
+ "net/http"
+ "os"
+
+ // _ "github.com/go-sql-driver/mysql"
+ "github.com/defencedigital/ded-web/internal/models"
+ // _ "github.com/lib/pq"
+)
+
+type application struct {
+ logger *slog.Logger
+ operations *models.OperationModel
+ organisations *models.OrganisationModel
+ persons *models.PersonModel
+ engagementStrategies *models.EngagementStrategyModel
+ templateCache map[string]*template.Template
+}
+
+func main() {
+ addr := flag.String("addr", ":4000", "HTTP network port")
+ // This uses "ded-db" in the connection string; this should be the name of the container running postgres.
+ // If not running this app inside a container, use "localhost".
+ //dsn := flag.String("dsn", "postgresql://postgres:secret@ded-db?sslmode=disable", "PostgreSQL data source name")
+ flag.Parse()
+
+ logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
+
+ // Database connection
+ //db, err := openDB(*dsn)
+ //if err != nil {
+ // logger.Error(err.Error())
+ // os.Exit(1)
+ //}
+ //
+ //defer db.Close()
+
+ // initialise the new template cache...
+ templateCache, err := newTemplateCache()
+ if err != nil {
+ logger.Error(err.Error())
+ os.Exit(1)
+ }
+
+ app := &application{
+ logger: logger,
+ //operations: &models.OperationModel{DB: db},
+ //organisations: &models.OrganisationModel{DB: db},
+ //persons: &models.PersonModel{DB: db},
+ //engagementStrategies: &models.EngagementStrategyModel{DB: db},
+ templateCache: templateCache,
+ }
+
+ // mux := http.NewServeMux()
+ // mux.HandleFunc("/", home)
+ // log.Print("starting server on :4000")
+ logger.Info("starting server", "addr", *addr)
+ err = http.ListenAndServe(*addr, app.routes())
+ logger.Error(err.Error())
+ os.Exit(1)
+}
+
+// openDB wraps sql.Open() and returns a sql.DB connection pool
+// for a given DSN.
+func openDB(dsn string) (*sql.DB, error) {
+ db, err := sql.Open("postgres", dsn)
+ if err != nil {
+ return nil, err
+ }
+
+ err = db.Ping()
+ if err != nil {
+ db.Close()
+ return nil, err
+ }
+ return db, nil
+}
diff --git a/cmd/web/routes.go b/cmd/web/routes.go
new file mode 100644
index 0000000..58a351a
--- /dev/null
+++ b/cmd/web/routes.go
@@ -0,0 +1,18 @@
+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))
+
+ mux.HandleFunc("/", app.home)
+ mux.HandleFunc("/organisation/list", app.listOrganisations)
+ mux.HandleFunc("/organisation/view", app.organisationView)
+ mux.HandleFunc("/organisation/create", app.organisationCreate)
+ mux.HandleFunc("/operation/list", app.listOperations)
+ mux.HandleFunc("/person/list", app.listPersons)
+ return mux
+}
diff --git a/cmd/web/templates.go b/cmd/web/templates.go
new file mode 100644
index 0000000..845fe25
--- /dev/null
+++ b/cmd/web/templates.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "html/template"
+ "path/filepath"
+
+ "github.com/defencedigital/ded-web/internal/models"
+)
+
+// TODO: At the moment we are using a struct will have fields for all the data
+// we will need. Not sure whether this is good or bad at the moment.
+type templateData struct {
+ Operations []models.Operation
+ Organisations []models.Organisation
+ Persons []models.Person
+}
+
+func newTemplateCache() (map[string]*template.Template, error) {
+ cache := map[string]*template.Template{}
+
+ // get a slice of filepaths match the pattern for our page templates
+ // e.g. [ui/html/pages/home.tmpl.html ui/html/pages/view.tmpl.html]
+ pages, err := filepath.Glob("./ui/html/pages/*.html")
+ if err != nil {
+ return nil, err
+ }
+
+ // loop through the page filepaths one by one
+ for _, page := range pages {
+ // extract "home.tmp.html" from the full filepath.
+ name := filepath.Base(page)
+
+ files := []string{
+ "./ui/html/base.tmpl.html",
+ "./ui/html/partials/nav.tmpl.html",
+ page,
+ }
+
+ ts, err := template.ParseFiles(files...)
+ if err != nil {
+ return nil, err
+ }
+
+ cache[name] = ts
+ }
+
+ return cache, nil
+}