added interactive setup of config.json

This commit is contained in:
2026-02-23 19:19:39 +01:00
parent b692a58ee2
commit 74ef68daad
2 changed files with 194 additions and 10 deletions

73
AGENTS.md Normal file
View File

@ -0,0 +1,73 @@
# AGENTS.md
Tento subor je prakticky navod pre AI agentov a contributorov v projekte `dbPrompt`.
## 1. Ciel projektu
`dbPrompt` je jednoducha web aplikacia na spustanie SQL dotazov nad MySQL s historiou vykonanych dopytov.
## 2. Struktura projektu
- `dbPrompt.go`: backend server (Go, `net/http`), API endpointy aj praca s historiou.
- `index.html`: cely frontend (HTML + CSS + JavaScript v jednom subore).
- `static/`: staticke assety (napr. logo).
- `history/`: JSON subory s historiou dopytov.
- `config.json`: DB konfiguracia pre lokalny beh.
- `build.bat`: build pre Windows/Linux binarky do `dist/`.
## 3. Lokalny beh a overenie
1. Skontroluj `config.json` (lokalne MySQL udaje).
2. Spust aplikaciu:
- `go run dbPrompt.go`
3. Otvor:
- `http://localhost:8080`
4. Pred odovzdanim zmien spusti:
- `go test ./...`
Poznamka: V projekte aktualne nie su unit testy, ale `go test ./...` overi aspon build.
## 4. API kontrakt (nezlomit bez poziadavky)
- `GET /history/`: vracia historiu ako JSON pole, zoradene od najstarsich.
- `POST /query`: prijme `{ id, query }`, vykona SQL, vrati `QueryResult`.
- `DELETE /history/{id}`: zmaze jeden zaznam historie.
`QueryResult` klucove polia:
- `id`, `query`, `timestamp`
- `duration` (ms)
- `error` alebo vysledok (`columns`, `rows`) alebo `rowsAffected`
Ak menis schema odpovede, zosulad backend aj frontend naraz.
## 5. Pravidla pre zmeny
- Zachovaj jednoduchu architekturu (single-file backend + single-file frontend), pokial nie je explicitna poziadavka na refactor.
- Preferuj male, cielene zmeny bez "cleanupu navyse".
- Pri zmenach endpointov vzdy uprav aj frontend render a nacitanie historie.
- Pri praci s cestami zachovaj sanitizaciu ID (`filepath.Base`) kvoli directory traversal.
- Nezavadzaj nove zavislosti bez dovodu.
## 6. Bezpecnost a data
- Nikdy necommituj realne prihlasovacie udaje do DB.
- `config.json` je lokalny runtime subor; citlive hodnoty maskuj v ukazkach.
- SQL sa vykonava priamo zo vstupu uzivatela. Ak by si pridaval funkcionalitu, zvaz oddelenie read-only rezimu alebo aspon jasne varovanie v UI.
## 7. Frontend poznamky
- UI je event-delegated cez `mainContainer.addEventListener('click', ...)`.
- Historicke bloky maju tlacidlo `Delete`; "new-query-block" ho nema, kym query neuspesne/neuspesne neprebehne.
- Collapsible tabulky pouzivaju `.collapsible-header` a `.collapsible-content`.
Pri zmenach vizualu zachovaj funkcnost:
- run query
- delete history item
- load history po starte
- collapse/expand individualne aj globalne
## 8. Definition of done pre kazdu upravu
1. Zmena funguje funkcne v UI/API.
2. `go test ./...` prejde.
3. Nemas neplanovane zmeny mimo scope zadania.

View File

@ -1,6 +1,7 @@
package main
import (
"bufio"
"database/sql"
"embed"
"encoding/json"
@ -52,17 +53,9 @@ var db *sql.DB
var historyDir = "./history"
func main() {
// Load configuration
file, err := os.Open("config.json")
config, err := loadOrCreateConfig("config.json")
if err != nil {
log.Fatalf("Failed to open config file: %v", err)
}
defer file.Close()
var config Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
log.Fatalf("Failed to decode config: %v", err)
log.Fatalf("Failed to load configuration: %v", err)
}
// Connect to the database
@ -99,6 +92,124 @@ func main() {
}
}
func loadOrCreateConfig(path string) (Config, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("Configuration file %q was not found.\n", path)
fmt.Println("Please provide MySQL connection details to create it.")
cfg, promptErr := promptConfigFromConsole()
if promptErr != nil {
return Config{}, promptErr
}
data, marshalErr := json.MarshalIndent(cfg, "", " ")
if marshalErr != nil {
return Config{}, fmt.Errorf("failed to serialize config: %w", marshalErr)
}
if writeErr := os.WriteFile(path, append(data, '\n'), 0600); writeErr != nil {
return Config{}, fmt.Errorf("failed to create %s: %w", path, writeErr)
}
fmt.Printf("Created %s successfully.\n", path)
return cfg, nil
} else if err != nil {
return Config{}, fmt.Errorf("failed to check config file: %w", err)
}
file, err := os.Open(path)
if err != nil {
return Config{}, fmt.Errorf("failed to open config file: %w", err)
}
defer file.Close()
var cfg Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&cfg); err != nil {
return Config{}, fmt.Errorf("failed to decode config: %w", err)
}
return cfg, nil
}
func promptConfigFromConsole() (Config, error) {
reader := bufio.NewReader(os.Stdin)
hostname, err := promptRequired(reader, "hostname")
if err != nil {
return Config{}, err
}
port, err := promptPort(reader, 3306)
if err != nil {
return Config{}, err
}
username, err := promptRequired(reader, "username")
if err != nil {
return Config{}, err
}
password, err := promptRequired(reader, "password")
if err != nil {
return Config{}, err
}
database, err := promptRequired(reader, "database")
if err != nil {
return Config{}, err
}
return Config{
Username: username,
Password: password,
Hostname: hostname,
Port: port,
Database: database,
}, nil
}
func promptRequired(reader *bufio.Reader, field string) (string, error) {
for {
fmt.Printf("%s: ", field)
value, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("failed to read %s: %w", field, err)
}
value = strings.TrimSpace(value)
if value == "" {
fmt.Printf("%s is required.\n", field)
continue
}
return value, nil
}
}
func promptPort(reader *bufio.Reader, defaultPort int) (int, error) {
for {
fmt.Printf("port [%d]: ", defaultPort)
value, err := reader.ReadString('\n')
if err != nil {
return 0, fmt.Errorf("failed to read port: %w", err)
}
value = strings.TrimSpace(value)
if value == "" {
return defaultPort, nil
}
port, atoiErr := strconv.Atoi(value)
if atoiErr != nil || port <= 0 || port > 65535 {
fmt.Println("Port must be a number between 1 and 65535.")
continue
}
return port, nil
}
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
}