[Zwischencommit]

This commit is contained in:
xoy 2025-03-04 20:18:55 +01:00
parent 0ae6f5c00a
commit cf15c8dddc
22 changed files with 409 additions and 18 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
**/.DS_Store

0
data/database.sql Normal file → Executable file
View file

BIN
data/fisch.db Executable file

Binary file not shown.

203
db.go Executable file
View file

@ -0,0 +1,203 @@
package main
import (
"database/sql"
"os"
_ "github.com/mattn/go-sqlite3"
)
type Connection struct {
DB *sql.DB
Error error
}
func Connect() *Connection {
db, err := sql.Open("sqlite3", "./data/fisch.db")
if err != nil {
return &Connection{nil, err}
}
return &Connection{db, nil}
}
func (conn *Connection) InitDatabase() error {
sql, err := os.ReadFile("./data/database.sql")
if err != nil {
return err
}
_, err = conn.DB.Exec(string(sql))
return err
}
func (conn *Connection) QueryLocations() ([]*Location, error) {
if conn.Error != nil {
return nil, conn.Error
}
rows, err := conn.DB.Query("select id, parent, name, description from locations")
if err != nil {
return nil, err
}
locations := make([]*Location, 0)
for rows.Next() {
location := Location{}
err := rows.Scan(&location.Id, &location.Parent, &location.Name, &location.Description)
if err != nil {
return nil, err
}
locations = append(locations, &location)
}
return locations, nil
}
func (conn *Connection) QueryLocation(id int64) (*Location, error) {
if conn.Error != nil {
return nil, conn.Error
}
row := conn.DB.QueryRow("select parent, name, description from locations where id=?", id)
location := Location{}
location.Id = sql.NullInt64{id, true}
err := row.Scan(&location.Parent, &location.Name, &location.Description)
if err != nil {
return nil, err
}
return &location, nil
}
func (conn *Connection) QueryContainers() ([]*Container, error) {
if conn.Error != nil {
return nil, conn.Error
}
rows, err := conn.DB.Query("select id, name from containers")
if err != nil {
return nil, err
}
containers := make([]*Container, 0)
for rows.Next() {
container := Container{}
err := rows.Scan(&container.Id, &container.Name)
if err != nil {
return nil, err
}
containers = append(containers, &container)
}
return containers, nil
}
func (conn *Connection) QueryContainer(id int64) (*Container, error) {
if conn.Error != nil {
return nil, conn.Error
}
row := conn.DB.QueryRow("select name from containers where id=?", id)
container := Container{}
container.Id = sql.NullInt64{id, true}
err := row.Scan(&container.Name)
if err != nil {
return nil, err
}
return &container, nil
}
func (conn *Connection) QueryParts() ([]*Part, error) {
if conn.Error != nil {
return nil, conn.Error
}
rows, err := conn.DB.Query("select id, name, tags, location, container from parts")
if err != nil {
return nil, err
}
parts := make([]*Part, 0)
for rows.Next() {
part := Part{}
var locationId sql.NullInt64
var containerId sql.NullInt64
err := rows.Scan(&part.Id, &part.Name, &part.Tags, &locationId, &containerId)
if err != nil {
return nil, err
}
var location *Location
if locationId.Valid {
location, err = conn.QueryLocation(locationId.Int64)
if err != nil {
return nil, err
}
}
var container *Container
if containerId.Valid {
container, err = conn.QueryContainer(containerId.Int64)
if err != nil {
return nil, err
}
}
part.Location = *location
part.Container = *container
parts = append(parts, &part)
}
return parts, nil
}
func (conn *Connection) QueryPart(id int64) (*Part, error) {
if conn.Error != nil {
return nil, conn.Error
}
row := conn.DB.QueryRow("select name, tags, location, container from parts where id=?", id)
part := Part{}
part.Id = sql.NullInt64{id, true}
var locationId sql.NullInt64
var containerId sql.NullInt64
err := row.Scan(&part.Name, &part.Tags, &locationId, &containerId)
if err != nil {
return nil, err
}
var location *Location
if locationId.Valid {
location, err = conn.QueryLocation(locationId.Int64)
if err != nil {
return nil, err
}
part.Location = *location
}
var container *Container
if containerId.Valid {
container, err = conn.QueryContainer(containerId.Int64)
if err != nil {
return nil, err
}
part.Container = *container
}
return &part, nil
}

1
go.mod Normal file → Executable file
View file

@ -5,6 +5,7 @@ go 1.23.6
require ( require (
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/template/html/v2 v2.1.3 github.com/gofiber/template/html/v2 v2.1.3
github.com/mattn/go-sqlite3 v1.14.24
) )
require ( require (

2
go.sum Normal file → Executable file
View file

@ -21,6 +21,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=

50
main.go Normal file → Executable file
View file

@ -21,6 +21,8 @@ func main() {
NewNavItem("Verwaltung", "/admin"), NewNavItem("Verwaltung", "/admin"),
} }
conn := Connect()
app.Get("/", func(c *fiber.Ctx) error { app.Get("/", func(c *fiber.Ctx) error {
return c.Render("search", fiber.Map{ return c.Render("search", fiber.Map{
"Title": "Suche", "Title": "Suche",
@ -54,14 +56,56 @@ func main() {
app.Get("/admin", func(c *fiber.Ctx) error { app.Get("/admin", func(c *fiber.Ctx) error {
return c.Render("admin/tables", fiber.Map{ return c.Render("admin/tables", fiber.Map{
"Title": "Verwaltung", "Title": "Verwaltung",
"Stylenames": NewStyleItemList("colors", "main", "admin"), "Stylenames": NewStyleItemList("colors", "main", "tables"),
"NavItems": navItems,
"ActivePage": "/admin",
"Tables": []NavItem{
NewNavItem("Orte", "locations"),
NewNavItem("Behälter", "containers"),
NewNavItem("Teile", "parts"),
},
})
})
app.Get("/admin/locations/overview", func(c *fiber.Ctx) error {
locations, err := conn.QueryLocations()
if err != nil {
return err
}
table := ToTable[*Location](locations, TableColumns{
"ID",
"Übergeordneter Ort",
"Name",
"Beschreibung",
})
return c.Render("admin/overview", fiber.Map{
"Title": "Verwaltung",
"Stylenames": NewStyleItemList("colors", "main", "overview"),
"NavItems": navItems,
"ActivePage": "/admin",
"Table": "locations",
"Columns": table.Collumns,
"Rows": table.Rows,
})
})
app.Get("/admin/containers/overview", func(c *fiber.Ctx) error {
return c.Render("admin/overview", fiber.Map{
"Title": "Verwaltung",
"Stylenames": NewStyleItemList("colors", "main", "overview"),
"NavItems": navItems, "NavItems": navItems,
"ActivePage": "/admin", "ActivePage": "/admin",
}) })
}) })
app.Get("/admin/locations/overview", func(c *fiber.Ctx) error { app.Get("/admin/parts/overview", func(c *fiber.Ctx) error {
return c.Render("admin/overview") return c.Render("admin/overview", fiber.Map{
"Title": "Verwaltung",
"Stylenames": NewStyleItemList("colors", "main", "overview"),
"NavItems": navItems,
"ActivePage": "/admin",
})
}) })
log.Fatal(app.Listen(":3000")) log.Fatal(app.Listen(":3000"))

14
misc.go Executable file
View file

@ -0,0 +1,14 @@
package main
func ToTable[T DatabaseType](rows []T, columns TableColumns) Table {
tableRows := make([]TableRow, len(rows))
for i, dT := range rows {
tableRows[i] = dT.ToTableRow()
}
return Table{
columns,
tableRows,
}
}

View file

0
static/css/colors.css Normal file → Executable file
View file

0
static/css/main.css Normal file → Executable file
View file

38
static/css/overview.css Executable file
View file

@ -0,0 +1,38 @@
main {
display: flex;
justify-content: center;
align-items: center;
padding-top: 2em;
padding-bottom: 2em;
}
table {
border-collapse: collapse;
width: 1200px;
max-width: 100%;
}
table :is(th, td) {
border: 2px solid var(--bg-border);
padding: 1em;
}
table tr td:last-child > div.action-container {
display: flex;
align-items: baseline;
justify-content: space-around;
}
table tr td:last-child form {
display: flex;
flex-direction: row;
align-items: center;
height: max-content;
}
table tr td:last-child form button {
background-color: transparent;
border: none;
cursor: pointer;
color: var(--fg);
}

0
static/css/search.css Normal file → Executable file
View file

15
static/css/tables.css Executable file
View file

@ -0,0 +1,15 @@
main {
display: flex;
justify-content: center;
align-items: start;
gap: 1em;
padding: 1em;
}
main > a {
display: inline-block;
padding: 2em;
background-color: var(--bg-light);
border-radius: 50px;
font-size: 3em;
}

77
types.go Normal file → Executable file
View file

@ -1,5 +1,10 @@
package main package main
import (
"database/sql"
"fmt"
)
// Web // Web
type NavItem struct { type NavItem struct {
@ -17,15 +22,79 @@ func NewStyleItemList(stylenames ...Stylename) []Stylename {
return stylenames return stylenames
} }
type TableColumns []string
type TableRow struct {
Columns TableColumns
Id int
}
type Table struct {
Collumns TableColumns
Rows []TableRow
}
// Database // Database
type Location struct { type Location struct {
id int Id sql.NullInt64
parent int Parent sql.NullInt64
Name string Name sql.NullString
Description string Description sql.NullString
}
func (l *Location) ToTableRow() TableRow {
columns := make(TableColumns, 4)
tr := TableRow{}
if l.Id.Valid {
columns[0] = fmt.Sprintf("%d", l.Id.Int64)
tr.Id = int(l.Id.Int64)
} else {
columns[0] = "NULL"
tr.Id = -1
}
if l.Parent.Valid {
columns[1] = fmt.Sprintf("%d", l.Parent.Int64)
} else {
columns[1] = "NULL"
}
if l.Name.Valid {
columns[2] = l.Name.String
} else {
columns[2] = "NULL"
}
if l.Description.Valid {
columns[3] = l.Description.String
} else {
columns[3] = "NULL"
}
tr.Columns = columns
return tr
} }
func (l *Location) GetParent() *Location { func (l *Location) GetParent() *Location {
return nil return nil
} }
type Container struct {
Id sql.NullInt64
Name sql.NullString
}
type Part struct {
Id sql.NullInt64
Name sql.NullString
Tags sql.NullString
Location Location
Container Container
}
// Interfaces
type DatabaseType interface {
ToTableRow() TableRow
}

0
views/admin/edit.html Normal file → Executable file
View file

20
views/admin/overview.html Normal file → Executable file
View file

@ -5,21 +5,25 @@
{{ range .Columns }} {{ range .Columns }}
<th>{{ . }}</th> <th>{{ . }}</th>
{{ end }} {{ end }}
<th>Aktionen</th>
</tr> </tr>
{{ $Table := .Table }}
{{ range .Rows }} {{ range .Rows }}
<tr> <tr>
{{ range .Columns }} {{ range .Columns }}
<td>{{ . }}</td> <td>{{ . }}</td>
{{ end }} {{ end }}
<td> <td>
<form action="/admin/{{ .Table }}/edit"> <div class="action-container">
<input type="hidden" name="id" value="{{ .Id }}"> <form action="/admin/{{ $Table }}/edit">
<button type="submit"></button> <input type="hidden" name="id" value="{{ .Id }}">
</form> <button type="submit"></button>
<form action="/admin/{{ .Table }}/delete"> </form>
<input type="hidden" name="id" value="{{ .Id }}"> <form action="/admin/{{ $Table }}/delete">
<button type="submit"></button> <input type="hidden" name="id" value="{{ .Id }}">
</form> <button type="submit"></button>
</form>
</div>
</td> </td>
</tr> </tr>
{{ end }} {{ end }}

2
views/admin/tables.html Normal file → Executable file
View file

@ -1,7 +1,7 @@
{{template "partials/base-top" .}} {{template "partials/base-top" .}}
{{ range .Tables }} {{ range .Tables }}
<a href="/admin/{{ .Table }}/overview">{{ .Caption }}</a> <a href="/admin/{{ .Destination }}/overview">{{ .Caption }}</a>
{{ end }} {{ end }}
{{template "partials/base-bottom" .}} {{template "partials/base-bottom" .}}

0
views/alert.html Normal file → Executable file
View file

2
views/partials/base-bottom.html Normal file → Executable file
View file

@ -1,7 +1,7 @@
</main> </main>
<footer> <footer>
<nav> <nav>
<a href="#">Repository</a> <a href="https://git.ctdo.de/xoy/fisch">Repository</a>
</nav> </nav>
</footer> </footer>
</body> </body>

2
views/partials/base-top.html Normal file → Executable file
View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>fisch - {{ .Title }}</title> <title>fisch - {{ .Title }}</title>
{{ range .Stylenames }} {{ range .Stylenames }}
<link rel="stylesheet" href="css/{{ . }}.css"> <link rel="stylesheet" href="/css/{{ . }}.css">
{{ end }} {{ end }}
</head> </head>
<body> <body>

0
views/search.html Normal file → Executable file
View file