[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 (
github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/template/html/v2 v2.1.3
github.com/mattn/go-sqlite3 v1.14.24
)
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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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"),
}
conn := Connect()
app.Get("/", func(c *fiber.Ctx) error {
return c.Render("search", fiber.Map{
"Title": "Suche",
@ -54,14 +56,56 @@ func main() {
app.Get("/admin", func(c *fiber.Ctx) error {
return c.Render("admin/tables", fiber.Map{
"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,
"ActivePage": "/admin",
})
})
app.Get("/admin/locations/overview", func(c *fiber.Ctx) error {
return c.Render("admin/overview")
app.Get("/admin/parts/overview", func(c *fiber.Ctx) error {
return c.Render("admin/overview", fiber.Map{
"Title": "Verwaltung",
"Stylenames": NewStyleItemList("colors", "main", "overview"),
"NavItems": navItems,
"ActivePage": "/admin",
})
})
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
import (
"database/sql"
"fmt"
)
// Web
type NavItem struct {
@ -17,15 +22,79 @@ func NewStyleItemList(stylenames ...Stylename) []Stylename {
return stylenames
}
type TableColumns []string
type TableRow struct {
Columns TableColumns
Id int
}
type Table struct {
Collumns TableColumns
Rows []TableRow
}
// Database
type Location struct {
id int
parent int
Name string
Description string
Id sql.NullInt64
Parent sql.NullInt64
Name sql.NullString
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 {
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 }}
<th>{{ . }}</th>
{{ end }}
<th>Aktionen</th>
</tr>
{{ $Table := .Table }}
{{ range .Rows }}
<tr>
{{ range .Columns }}
<td>{{ . }}</td>
{{ end }}
<td>
<form action="/admin/{{ .Table }}/edit">
<input type="hidden" name="id" value="{{ .Id }}">
<button type="submit"></button>
</form>
<form action="/admin/{{ .Table }}/delete">
<input type="hidden" name="id" value="{{ .Id }}">
<button type="submit"></button>
</form>
<div class="action-container">
<form action="/admin/{{ $Table }}/edit">
<input type="hidden" name="id" value="{{ .Id }}">
<button type="submit"></button>
</form>
<form action="/admin/{{ $Table }}/delete">
<input type="hidden" name="id" value="{{ .Id }}">
<button type="submit"></button>
</form>
</div>
</td>
</tr>
{{ end }}

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

@ -1,7 +1,7 @@
{{template "partials/base-top" .}}
{{ range .Tables }}
<a href="/admin/{{ .Table }}/overview">{{ .Caption }}</a>
<a href="/admin/{{ .Destination }}/overview">{{ .Caption }}</a>
{{ end }}
{{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>
<footer>
<nav>
<a href="#">Repository</a>
<a href="https://git.ctdo.de/xoy/fisch">Repository</a>
</nav>
</footer>
</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">
<title>fisch - {{ .Title }}</title>
{{ range .Stylenames }}
<link rel="stylesheet" href="css/{{ . }}.css">
<link rel="stylesheet" href="/css/{{ . }}.css">
{{ end }}
</head>
<body>

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