From 276a6be7b9c16f5e541ea887baf3d6d31d403afc Mon Sep 17 00:00:00 2001 From: Azpect3120 <104033825+Azpect3120@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:08:40 -0700 Subject: [PATCH] FEAT: Table tree is now displayed! Has no functionality yet, but that's next. And not sure how it works when switching connections. --- internal/database/connect.go | 5 +- internal/database/tree.go | 107 +++++++++++++++++++++++++++++++++++ internal/http/router.go | 4 ++ internal/templates/tree.go | 76 +++++++++++++++++++++++++ web/templates/index.html | 84 +++++---------------------- 5 files changed, 203 insertions(+), 73 deletions(-) create mode 100644 internal/database/tree.go create mode 100644 internal/templates/tree.go diff --git a/internal/database/connect.go b/internal/database/connect.go index f08c1c7..4a325a1 100644 --- a/internal/database/connect.go +++ b/internal/database/connect.go @@ -9,11 +9,10 @@ import ( "github.com/gin-gonic/gin" ) +// Change the current connection in the session func ChangeConnection(c *gin.Context) { conn := c.PostForm("connected-database") - // Do something to change the connection - session := sessions.Default(c) conn_bytes, ok := session.Get("connections").([]byte) if !ok { @@ -39,5 +38,5 @@ func ChangeConnection(c *gin.Context) { session.Set("current", name) session.Save() - c.String(200, templates.ConnectionsList(connections, name)) + c.String(200, templates.ConnectionsList(connections, name)+TableTree(c)) } diff --git a/internal/database/tree.go b/internal/database/tree.go new file mode 100644 index 0000000..2f8a1e3 --- /dev/null +++ b/internal/database/tree.go @@ -0,0 +1,107 @@ +package database + +import ( + "database/sql" + "encoding/json" + "fmt" + + "github.com/Azpect3120/Web-Database-Viewer/internal/templates" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +func TableTree(c *gin.Context) string { + session := sessions.Default(c) + connections_bytes, ok := session.Get("connections").([]byte) + current, ok := session.Get("current").(string) + if !ok { + fmt.Println("No connections found") + return "" + } + + var connections map[string]string + if err := json.Unmarshal(connections_bytes, &connections); err != nil { + fmt.Println(err) + return "" + } + + url := connections[current] + + tree, err := generateTree(url) + if err != nil { + fmt.Println(err) + return "" + } + + fmt.Printf("%+v\n", tree) + + return templates.TableTree(tree) +} + +// Generate the tree of the database tables +func generateTree(url string) (map[string][]string, error) { + conn, err := sql.Open("postgres", url) + if err != nil { + return map[string][]string{}, err + } + defer conn.Close() + + tree, err := tableList(conn) + if err != nil { + return map[string][]string{}, err + } + + if err := fillColumns(conn, tree); err != nil { + return map[string][]string{}, err + } + + return tree, nil +} + +// Return a map with the keys being the table names and the values +// being blank which can be later used to store the columns. +func tableList(conn *sql.DB) (map[string][]string, error) { + rows, err := conn.Query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';") + if err != nil { + return map[string][]string{}, err + } + defer rows.Close() + + tree := make(map[string][]string) + for rows.Next() { + var table string + if err := rows.Scan(&table); err != nil { + return map[string][]string{}, err + } + tree[table] = []string{} + } + + return tree, nil +} + +// Fill the columns of the tables in the tree using the keys found +// in the tableList function. +// +// For now, the only data stored is the +// column name, but in the future this could be expanded to store +// datatype, constraints, primary keys, relationship, etc. +func fillColumns(conn *sql.DB, tree map[string][]string) error { + for table := range tree { + rows, err := conn.Query(fmt.Sprintf("SELECT column_name FROM information_schema.columns WHERE table_name = '%s';", table)) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var column string + if err := rows.Scan(&column); err != nil { + return err + } + tree[table] = append(tree[table], column) + } + } + + return nil +} diff --git a/internal/http/router.go b/internal/http/router.go index 0052f41..ec0b490 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -68,4 +68,8 @@ func populate(web, api *gin.RouterGroup) { c.String(200, html) }) api.POST("/connections/connect", database.ChangeConnection) + + web.GET("/connections/tree", func(c *gin.Context) { + c.String(200, database.TableTree(c)) + }) } diff --git a/internal/templates/tree.go b/internal/templates/tree.go new file mode 100644 index 0000000..865f145 --- /dev/null +++ b/internal/templates/tree.go @@ -0,0 +1,76 @@ +package templates + +import ( + "fmt" + "sort" +) + +// Tree definition +const TREE_OPEN string = `