crud example komplett
This commit is contained in:
parent
3dd83793af
commit
96d8e0da3c
BIN
crud/.crud
Executable file
BIN
crud/.crud
Executable file
Binary file not shown.
@ -3,37 +3,33 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crud/sqlite"
|
"crud/sqlite"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseQuery = `SELECT u.id, u.name, u.username, u.email, u.phone, u.website, a.street, a.suite, a.zipcode, a.city, c.name as company, c.catch_phrase, c.bs
|
||||||
|
FROM user u
|
||||||
|
JOIN company c ON u.company_id = c.id
|
||||||
|
JOIN address a ON u.address_id = a.id
|
||||||
|
`
|
||||||
|
|
||||||
func Board(ctx context.Context, db *sqlite.Database, templ *template.Template) http.Handler {
|
func Board(ctx context.Context, db *sqlite.Database, templ *template.Template) http.Handler {
|
||||||
// Implementation of Board handler
|
|
||||||
return http.HandlerFunc(
|
return http.HandlerFunc(
|
||||||
|
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
query := `SELECT u.id, u.name, u.username, u.email, u.phone, u.website, a.street, a.suite, a.zipcode, a.city, c.name as company, c.catch_phrase, c.bs
|
|
||||||
FROM user u
|
|
||||||
JOIN company c ON u.company_id = c.id
|
|
||||||
JOIN address a ON u.address_id = a.id;
|
|
||||||
`
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
// w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
records, err := db.ReadRecords(ctx, query)
|
records, err := sqlite.NoRowsOk(db.ReadRecords(ctx, baseQuery+";"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a data structure to pass to template
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"records": records,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute template with proper error handling
|
// Execute template with proper error handling
|
||||||
if err := templ.ExecuteTemplate(w, "board", data); err != nil {
|
if err := templ.ExecuteTemplate(w, "board", sqlite.Record{"records": records}); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -47,13 +43,15 @@ func DeleteRecord(ctx context.Context, db *sqlite.Database, templ *template.Temp
|
|||||||
|
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id := r.URL.Query().Get("id")
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||||
if id == "" {
|
if err != nil {
|
||||||
http.Error(w, "Missing id parameter", http.StatusBadRequest)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
w.Write([]byte(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DeleteRecord(ctx, "users", "id", id); err != nil {
|
// fmt.Println("DeleteRecord handler called ", id)
|
||||||
|
|
||||||
|
if err := db.DeleteRecord(ctx, "user", "id", id); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -63,3 +61,65 @@ func DeleteRecord(ctx context.Context, db *sqlite.Database, templ *template.Temp
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EditRecord(ctx context.Context, db *sqlite.Database, templ *template.Template) http.Handler {
|
||||||
|
// Implementation of EditRecord handler
|
||||||
|
return http.HandlerFunc(
|
||||||
|
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// fmt.Println("EditRecord handler called")
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
records, err := db.ReadRecords(ctx, baseQuery+" WHERE u.id = ?;", id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := records[0]
|
||||||
|
|
||||||
|
// fmt.Printf("Record to edit: %+v\n", rec)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
||||||
|
// Execute template with proper error handling
|
||||||
|
if err := templ.ExecuteTemplate(w, "edit-user", rec); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PatchRecord(ctx context.Context, db *sqlite.Database, templ *template.Template) http.Handler {
|
||||||
|
// Implementation of PatchRecord handler
|
||||||
|
return http.HandlerFunc(
|
||||||
|
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
fmt.Println("PatchRecord handler called")
|
||||||
|
|
||||||
|
// id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||||
|
// if err != nil {
|
||||||
|
// w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
// w.Write([]byte(err.Error()))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := db.DeleteRecord(ctx, "user", "id", id); err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Redirect or respond with success
|
||||||
|
http.Redirect(w, r, "/board", http.StatusSeeOther)
|
||||||
|
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -10,8 +10,7 @@
|
|||||||
<script type="module" src="datastar.js"></script>
|
<script type="module" src="datastar.js"></script>
|
||||||
<style>
|
<style>
|
||||||
#board {
|
#board {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
/* 3 columns */
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
logFileName := "./crud.log"
|
logFileName := "./.crud.log"
|
||||||
logFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
logFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error opening file: %v", err)
|
log.Printf("error opening file: %v", err)
|
||||||
|
|||||||
@ -22,9 +22,7 @@ func addRoutes(
|
|||||||
) {
|
) {
|
||||||
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
||||||
mux.Handle("GET /board", api.Board(ctx, database, templ))
|
mux.Handle("GET /board", api.Board(ctx, database, templ))
|
||||||
mux.Handle("GET /delete-record/{id}", api.DeleteRecord(ctx, database, templ))
|
mux.Handle("GET /edit-record/{id}", api.EditRecord(ctx, database, templ))
|
||||||
// mux.Handle("GET /count", api.ProductCount(database))
|
mux.Handle("PATCH /patch-record/{id}", api.PatchRecord(ctx, database, templ))
|
||||||
// mux.Handle("GET /nutriments/{page}", api.DataNutriments(database, templ))
|
mux.Handle("DELETE /delete-record/{id}", api.DeleteRecord(ctx, database, templ))
|
||||||
// mux.Handle("GET /products/{page}", api.DataProducts(database, templ))
|
|
||||||
// mux.Handle("GET /brandowner/{page}", api.DataBrandOwner(database, templ))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{{define "board"}}
|
{{define "board"}}
|
||||||
<div id="board" class="grid">
|
<div id="board" class="grid">
|
||||||
{{range .records}}
|
{{range .records}}
|
||||||
<article>
|
<article id="user-{{.id}}">
|
||||||
<header>
|
<header>
|
||||||
<h3>{{.name}}</h3>
|
<h3>{{.name}}</h3>
|
||||||
<p>@{{.username}}</p>
|
<p>{{.username}}</p>
|
||||||
</header>
|
</header>
|
||||||
<form inert>
|
<form inert>
|
||||||
<label>
|
<label>
|
||||||
@ -54,8 +54,66 @@
|
|||||||
</form>
|
</form>
|
||||||
<footer class="grid">
|
<footer class="grid">
|
||||||
<button class="primary" data-on:click="@get('edit-record/{{.id}}')">Edit</button>
|
<button class="primary" data-on:click="@get('edit-record/{{.id}}')">Edit</button>
|
||||||
<button class="secondary" data-on:click="@get('delete-record/{{.id}}')">Delete</button>
|
<button class="secondary" data-on:click="@delete('delete-record/{{.id}}')">Delete</button>
|
||||||
</article>
|
</article>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{define "edit-user"}}
|
||||||
|
<article id="user-{{.id}}">
|
||||||
|
<header>
|
||||||
|
<h3>{{.name}}</h3>
|
||||||
|
<p>{{.username}}</p>
|
||||||
|
</header>
|
||||||
|
<form>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" value="{{.email}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Phone
|
||||||
|
<input type="tel" value="{{.phone}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Website
|
||||||
|
<input type="url" value="{{.website}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Company
|
||||||
|
<input type="text" value="{{.company}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
Street
|
||||||
|
<input type="text" value="{{.street}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Suite
|
||||||
|
<input type="text" value="{{.suite}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
City
|
||||||
|
<input type="text" value="{{.city}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Zipcode
|
||||||
|
<input type="text" value="{{.zipcode}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<footer class="grid">
|
||||||
|
<button class="primary" data-on:click="@patch('patch-record/{{.id}}')">Save</button>
|
||||||
|
<button class="secondary" data-on:click="@get('board')">Cancel</button>
|
||||||
|
</article>
|
||||||
|
{{end}}
|
||||||
@ -3,3 +3,6 @@ cp .user.db user.db
|
|||||||
ls -l
|
ls -l
|
||||||
echo "The CRUD Example"
|
echo "The CRUD Example"
|
||||||
---PAUSE---
|
---PAUSE---
|
||||||
|
micro index.html board-content.html sample.go
|
||||||
|
xdg-open http://localhost:8080
|
||||||
|
./.crud
|
||||||
|
|||||||
119
crud/board-content.html
Normal file
119
crud/board-content.html
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
{{define "board"}}
|
||||||
|
<div id="board" class="grid">
|
||||||
|
{{range .records}}
|
||||||
|
<article id="user-{{.id}}">
|
||||||
|
<header>
|
||||||
|
<h3>{{.name}}</h3>
|
||||||
|
<p>{{.username}}</p>
|
||||||
|
</header>
|
||||||
|
<form inert>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" value="{{.email}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Phone
|
||||||
|
<input type="tel" value="{{.phone}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Website
|
||||||
|
<input type="url" value="{{.website}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Company
|
||||||
|
<input type="text" value="{{.company}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
Street
|
||||||
|
<input type="text" value="{{.street}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Suite
|
||||||
|
<input type="text" value="{{.suite}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
City
|
||||||
|
<input type="text" value="{{.city}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Zipcode
|
||||||
|
<input type="text" value="{{.zipcode}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<footer class="grid">
|
||||||
|
<button class="primary" data-on:click="@get('edit-record/{{.id}}')">Edit</button>
|
||||||
|
<button class="secondary" data-on:click="@delete('delete-record/{{.id}}')">Delete</button>
|
||||||
|
</article>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "edit-user"}}
|
||||||
|
<article id="user-{{.id}}">
|
||||||
|
<header>
|
||||||
|
<h3>{{.name}}</h3>
|
||||||
|
<p>{{.username}}</p>
|
||||||
|
</header>
|
||||||
|
<form>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" value="{{.email}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Phone
|
||||||
|
<input type="tel" value="{{.phone}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Website
|
||||||
|
<input type="url" value="{{.website}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Company
|
||||||
|
<input type="text" value="{{.company}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
Street
|
||||||
|
<input type="text" value="{{.street}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Suite
|
||||||
|
<input type="text" value="{{.suite}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset role="group">
|
||||||
|
<label>
|
||||||
|
City
|
||||||
|
<input type="text" value="{{.city}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Zipcode
|
||||||
|
<input type="text" value="{{.zipcode}}" />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<footer class="grid">
|
||||||
|
<button class="primary" data-on:click="@patch('patch-record/{{.id}}')">Save</button>
|
||||||
|
<button class="secondary" data-on:click="@get('board')">Cancel</button>
|
||||||
|
</article>
|
||||||
|
{{end}}
|
||||||
@ -4,20 +4,23 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Hypermedia</title>
|
<title>CRUD Example</title>
|
||||||
<link rel="stylesheet" href="pico.blue.css">
|
<link rel="stylesheet" href="pico.blue.css">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<script type="module" src="datastar.js"></script>
|
<script type="module" src="datastar.js"></script>
|
||||||
|
<style>
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="container" data-theme="light">
|
<body class="container-fluid" data-theme="light">
|
||||||
|
|
||||||
<h1 style="margin-top: 1em; text-align:center">Hypermedia as the Engine of Application State</h1>
|
<h1 style="margin-top: 1em; text-align:center">The CRUD Example</h1>
|
||||||
|
|
||||||
<main class="grid" data-theme="dark">
|
<main data-theme="dark">
|
||||||
<article id="record1" data-init="@get('display-record1.html')"></article>
|
<div id="board" data-init="@get('board')"></div>
|
||||||
<article id="record2" data-init="@get('display-record2.html')"></article>
|
|
||||||
<article id="record3" data-init="@get('display-record3.html')"></article>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
44
crud/sample.go
Normal file
44
crud/sample.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// addRoutes combines the URL endpoints with the applications's services
|
||||||
|
// and dependencies and required middleware
|
||||||
|
func addRoutes(
|
||||||
|
ctx context.Context,
|
||||||
|
mux *http.ServeMux,
|
||||||
|
database *sqlite.Database,
|
||||||
|
static fs.FS,
|
||||||
|
templ *template.Template,
|
||||||
|
) {
|
||||||
|
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
||||||
|
mux.Handle("GET /board", api.Board(ctx, database, templ))
|
||||||
|
mux.Handle("GET /edit-record/{id}", api.EditRecord(ctx, database, templ))
|
||||||
|
mux.Handle("PATCH /patch-record/{id}", api.PatchRecord(ctx, database, templ))
|
||||||
|
mux.Handle("DELETE /delete-record/{id}", api.DeleteRecord(ctx, database, templ))
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = `SELECT u.id, u.name, u.username, u.email, u.phone, u.website, a.street, a.suite, a.zipcode, a.city, c.name as company, c.catch_phrase, c.bs
|
||||||
|
FROM user u
|
||||||
|
JOIN company c ON u.company_id = c.id
|
||||||
|
JOIN address a ON u.address_id = a.id;
|
||||||
|
`
|
||||||
|
|
||||||
|
// load user data and fill template
|
||||||
|
func Board(ctx context.Context, db *sqlite.Database, templ *template.Template) http.Handler {
|
||||||
|
return http.HandlerFunc(
|
||||||
|
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
||||||
|
records, err := sqlite.NoRowsOk(db.ReadRecords(ctx, query))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute template with proper error handling
|
||||||
|
if err := templ.ExecuteTemplate(w, "board", sqlite.Record{"records": records}); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user