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 = `` +const TREE_BODY_TEMPLATE string = `
  • %s
  • ` + +// Table definition +const TABLE_TEMPLATE string = ` + + ` + +// Fields definition +const FIELDS_LIST_OPEN string = `` +const FIELD_TEMPLATE string = ` +
  • + +
  • +` + +// This is not implemented yet +const PRIMARY_KEY string = `` + +// Generate the tree based on the database tables and columns +func TableTree(tree map[string][]string) string { + html := TREE_OPEN + + var body string + for _, table := range getSortedKeys(tree) { + body += fmt.Sprintf(TABLE_TEMPLATE, table) + body += FIELDS_LIST_OPEN + generateFields(tree[table]) + FIELDS_LIST_CLOSE + } + + html += fmt.Sprintf(TREE_BODY_TEMPLATE, body) + return html + TREE_CLOSE +} + +// Using a list of fields, generate the HTML for the fields +func generateFields(fields []string) string { + var html string + for _, field := range fields { + html += fmt.Sprintf(FIELD_TEMPLATE, field) + } + return html +} + +// Return a list of the keys in a map, sorted alphabetically +func getSortedKeys(m map[string][]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + + sort.Strings(keys) + return keys +} diff --git a/web/templates/index.html b/web/templates/index.html index c72707a..78e9b20 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -33,78 +33,22 @@
    -
    +

    database Tables

    +
    -
    -
      -
    • - -
        -
      • - -
      • -
      • - -
      • -
      -
    • -
    • - -
        -
      • - -
      • -
      • - -
      • -
      -
    • -
    +
    +