[Zwischencommit]
This commit is contained in:
parent
0ae6f5c00a
commit
cf15c8dddc
22 changed files with 409 additions and 18 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
**/.DS_Store
|
0
data/database.sql
Normal file → Executable file
0
data/database.sql
Normal file → Executable file
BIN
data/fisch.db
Executable file
BIN
data/fisch.db
Executable file
Binary file not shown.
203
db.go
Executable file
203
db.go
Executable 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
1
go.mod
Normal file → Executable 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
2
go.sum
Normal file → Executable 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
50
main.go
Normal file → Executable 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
14
misc.go
Executable 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,
|
||||
}
|
||||
}
|
0
static/css/colors.css
Normal file → Executable file
0
static/css/colors.css
Normal file → Executable file
0
static/css/main.css
Normal file → Executable file
0
static/css/main.css
Normal file → Executable file
38
static/css/overview.css
Executable file
38
static/css/overview.css
Executable 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
0
static/css/search.css
Normal file → Executable file
15
static/css/tables.css
Executable file
15
static/css/tables.css
Executable 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
77
types.go
Normal file → Executable 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
0
views/admin/edit.html
Normal file → Executable file
20
views/admin/overview.html
Normal file → Executable file
20
views/admin/overview.html
Normal file → Executable 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
2
views/admin/tables.html
Normal file → Executable 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
0
views/alert.html
Normal file → Executable file
2
views/partials/base-bottom.html
Normal file → Executable file
2
views/partials/base-bottom.html
Normal file → Executable 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
2
views/partials/base-top.html
Normal file → Executable 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
0
views/search.html
Normal file → Executable file
Loading…
Add table
Reference in a new issue