added interactive setup of config.json
This commit is contained in:
73
AGENTS.md
Normal file
73
AGENTS.md
Normal 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.
|
||||
131
dbPrompt.go
131
dbPrompt.go
@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user