224 lines
4.5 KiB
Go
224 lines
4.5 KiB
Go
// backend\device_lookups.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type DeviceLookupOption struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type CreateDeviceLookupRequest struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (s *Server) handleDeviceLookups(w http.ResponseWriter, r *http.Request) {
|
|
manufacturers, err := listDeviceLookupOptions(r.Context(), s.db, "device_manufacturers")
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Hersteller konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
locations, err := listDeviceLookupOptions(r.Context(), s.db, "device_locations")
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Standorte konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"manufacturers": manufacturers,
|
|
"locations": locations,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleCreateDeviceManufacturer(w http.ResponseWriter, r *http.Request) {
|
|
s.handleCreateDeviceLookup(w, r, "device_manufacturers", "Hersteller")
|
|
}
|
|
|
|
func (s *Server) handleDeleteDeviceManufacturer(w http.ResponseWriter, r *http.Request) {
|
|
s.handleDeleteDeviceLookup(w, r, "device_manufacturers", "Hersteller")
|
|
}
|
|
|
|
func (s *Server) handleCreateDeviceLocation(w http.ResponseWriter, r *http.Request) {
|
|
s.handleCreateDeviceLookup(w, r, "device_locations", "Standort")
|
|
}
|
|
|
|
func (s *Server) handleCreateDeviceLookup(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
tableName string,
|
|
label string,
|
|
) {
|
|
var input CreateDeviceLookupRequest
|
|
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
|
return
|
|
}
|
|
|
|
name := strings.TrimSpace(input.Name)
|
|
if name == "" {
|
|
writeError(w, http.StatusBadRequest, label+" ist erforderlich")
|
|
return
|
|
}
|
|
|
|
option, err := upsertDeviceLookupOption(r.Context(), s.db, tableName, name)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, label+" konnte nicht gespeichert werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, map[string]any{
|
|
"option": option,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleDeleteDeviceLookup(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
tableName string,
|
|
label string,
|
|
) {
|
|
id := strings.TrimSpace(r.PathValue("id"))
|
|
if id == "" {
|
|
writeError(w, http.StatusBadRequest, label+" ist erforderlich")
|
|
return
|
|
}
|
|
|
|
commandTag, err := s.db.Exec(
|
|
r.Context(),
|
|
fmt.Sprintf(`DELETE FROM %s WHERE id = $1`, tableName),
|
|
id,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, label+" konnte nicht gelöscht werden")
|
|
return
|
|
}
|
|
|
|
if commandTag.RowsAffected() == 0 {
|
|
writeError(w, http.StatusNotFound, label+" wurde nicht gefunden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"deleted": true,
|
|
})
|
|
}
|
|
|
|
func listDeviceLookupOptions(
|
|
ctx context.Context,
|
|
db interface {
|
|
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
|
},
|
|
tableName string,
|
|
) ([]DeviceLookupOption, error) {
|
|
query := fmt.Sprintf(
|
|
`
|
|
SELECT id::TEXT, name
|
|
FROM %s
|
|
ORDER BY lower(name) ASC
|
|
`,
|
|
tableName,
|
|
)
|
|
|
|
rows, err := db.Query(ctx, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
options := []DeviceLookupOption{}
|
|
|
|
for rows.Next() {
|
|
var option DeviceLookupOption
|
|
|
|
if err := rows.Scan(&option.ID, &option.Name); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
options = append(options, option)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
func ensureDeviceLookupValues(
|
|
ctx context.Context,
|
|
tx pgx.Tx,
|
|
manufacturer string,
|
|
location string,
|
|
) error {
|
|
if err := ensureOptionalDeviceLookupValue(ctx, tx, "device_manufacturers", manufacturer); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ensureOptionalDeviceLookupValue(ctx, tx, "device_locations", location); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ensureOptionalDeviceLookupValue(
|
|
ctx context.Context,
|
|
db deviceTransaction,
|
|
tableName string,
|
|
name string,
|
|
) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return nil
|
|
}
|
|
|
|
_, err := upsertDeviceLookupOption(ctx, db, tableName, name)
|
|
return err
|
|
}
|
|
|
|
func upsertDeviceLookupOption(
|
|
ctx context.Context,
|
|
db deviceTransaction,
|
|
tableName string,
|
|
name string,
|
|
) (DeviceLookupOption, error) {
|
|
name = strings.TrimSpace(name)
|
|
normalizedName := strings.ToLower(name)
|
|
|
|
query := fmt.Sprintf(
|
|
`
|
|
INSERT INTO %s (
|
|
name,
|
|
normalized_name
|
|
)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (normalized_name)
|
|
DO UPDATE SET
|
|
updated_at = now()
|
|
RETURNING id::TEXT, name
|
|
`,
|
|
tableName,
|
|
)
|
|
|
|
var option DeviceLookupOption
|
|
|
|
err := db.QueryRow(
|
|
ctx,
|
|
query,
|
|
name,
|
|
normalizedName,
|
|
).Scan(&option.ID, &option.Name)
|
|
|
|
return option, err
|
|
}
|