crud example komplett

This commit is contained in:
thomashamburg 2025-11-09 17:28:42 +01:00
parent 3dd83793af
commit 96d8e0da3c
11 changed files with 323 additions and 39 deletions

BIN
crud/.crud Executable file

Binary file not shown.

View File

@ -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.

View File

@ -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>

View File

@ -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)

View File

@ -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))
} }

View File

@ -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}}
{{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}} {{end}}

View File

@ -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
View 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}}

View File

@ -1,25 +1,28 @@
<!DOCTYPE html > <!DOCTYPE html>
<html lang="en"> <html lang="en">
<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>
</html> </html>

44
crud/sample.go Normal file
View 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
}
},
)
}