func GetBookmarks(c *gin.Context) {
	bookmarks, err := bookmarks.ReadAll(bookmarks.Path(command.Opts.BookmarksDir))
	serveResult(c, bookmarks, err)
}
GetBookmarks renders the list of available bookmarks
package api
import (
	"encoding/base64"
	"fmt"
	"net/http"
	neturl "net/url"
	"regexp"
	"strings"
	"time"
	"github.com/gin-gonic/gin"
	"github.com/tuvistavie/securerandom"
	"github.com/sosedoff/pgweb/pkg/bookmarks"
	"github.com/sosedoff/pgweb/pkg/client"
	"github.com/sosedoff/pgweb/pkg/command"
	"github.com/sosedoff/pgweb/pkg/connection"
	"github.com/sosedoff/pgweb/pkg/shared"
	"github.com/sosedoff/pgweb/static"
)
var (
	DbClient *client.Client
	DbSessions = map[string]*client.Client{}
)
func DB(c *gin.Context) *client.Client {
	if command.Opts.Sessions {
		return DbSessions[getSessionId(c.Request)]
	}
	return DbClient
}
func setClient(c *gin.Context, newClient *client.Client) error {
	currentClient := DB(c)
	if currentClient != nil {
		currentClient.Close()
	}
	if !command.Opts.Sessions {
		DbClient = newClient
		return nil
	}
	sid := getSessionId(c.Request)
	if sid == "" {
		return errSessionRequired
	}
	DbSessions[sid] = newClient
	return nil
}
func GetHome(prefix string) http.Handler {
	if prefix != "" {
		prefix = "/" + prefix
	}
	return http.StripPrefix(prefix, http.FileServer(http.FS(static.Static)))
}
func GetAssets(prefix string) http.Handler {
	if prefix != "" {
		prefix = "/" + prefix + "static/"
	} else {
		prefix = "/static/"
	}
	return http.StripPrefix(prefix, http.FileServer(http.FS(static.Static)))
}
func GetSessions(c *gin.Context) {
	if command.Opts.Debug {
		successResponse(c, DbSessions)
		return
	}
	successResponse(c, gin.H{"sessions": len(DbSessions)})
}
func ConnectWithBackend(c *gin.Context) {
	backend := Backend{
		Endpoint:    command.Opts.ConnectBackend,
		Token:       command.Opts.ConnectToken,
		PassHeaders: command.Opts.ConnectHeaders,
	}
	cred, err := backend.FetchCredential(c.Param("resource"), c)
	if err != nil {
		badRequest(c, err)
		return
	}
	sid, err := securerandom.Uuid()
	if err != nil {
		badRequest(c, err)
		return
	}
	c.Request.Header.Add("x-session-id", sid)
	cl, err := client.NewFromUrl(cred.DatabaseURL, nil)
	if err != nil {
		badRequest(c, err)
		return
	}
	cl.External = true
	_, err = cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}
	redirectURI := fmt.Sprintf("/%s?session=%s", command.Opts.Prefix, sid)
	c.Redirect(301, redirectURI)
}
func Connect(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}
	var sshInfo *shared.SSHInfo
	url := c.Request.FormValue("url")
	if url == "" {
		badRequest(c, errURLRequired)
		return
	}
	opts := command.Options{URL: url}
	url, err := connection.FormatURL(opts)
	if err != nil {
		badRequest(c, err)
		return
	}
	if c.Request.FormValue("ssh") != "" {
		sshInfo = parseSshInfo(c)
	}
	cl, err := client.NewFromUrl(url, sshInfo)
	if err != nil {
		badRequest(c, err)
		return
	}
	err = cl.Test()
	if err != nil {
		badRequest(c, err)
		return
	}
	info, err := cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}
	successResponse(c, info.Format()[0])
}
func SwitchDb(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}
	name := c.Request.URL.Query().Get("db")
	if name == "" {
		name = c.Request.FormValue("db")
	}
	if name == "" {
		badRequest(c, errDatabaseNameRequired)
		return
	}
	conn := DB(c)
	if conn == nil {
		badRequest(c, errNotConnected)
		return
	}
	if conn.External {
		badRequest(c, errSessionLocked)
		return
	}
	currentURL, err := neturl.Parse(conn.ConnectionString)
	if err != nil {
		badRequest(c, errInvalidConnString)
		return
	}
	currentURL.Path = name
	cl, err := client.NewFromUrl(currentURL.String(), nil)
	if err != nil {
		badRequest(c, err)
		return
	}
	err = cl.Test()
	if err != nil {
		badRequest(c, err)
		return
	}
	info, err := cl.Info()
	if err == nil {
		err = setClient(c, cl)
	}
	if err != nil {
		cl.Close()
		badRequest(c, err)
		return
	}
	conn.Close()
	successResponse(c, info.Format()[0])
}
func Disconnect(c *gin.Context) {
	if command.Opts.LockSession {
		badRequest(c, errSessionLocked)
		return
	}
	conn := DB(c)
	if conn == nil {
		badRequest(c, errNotConnected)
		return
	}
	err := conn.Close()
	if err != nil {
		badRequest(c, err)
		return
	}
	successResponse(c, gin.H{"success": true})
}
func RunQuery(c *gin.Context) {
	query := cleanQuery(c.Request.FormValue("query"))
	if query == "" {
		badRequest(c, errQueryRequired)
		return
	}
	HandleQuery(query, c)
}
func ExplainQuery(c *gin.Context) {
	query := cleanQuery(c.Request.FormValue("query"))
	if query == "" {
		badRequest(c, errQueryRequired)
		return
	}
	HandleQuery(fmt.Sprintf("EXPLAIN ANALYZE %s", query), c)
}
func GetDatabases(c *gin.Context) {
	if command.Opts.LockSession {
		serveResult(c, []string{}, nil)
		return
	}
	conn := DB(c)
	if conn.External {
		errorResponse(c, 403, errNotPermitted)
		return
	}
	names, err := DB(c).Databases()
	serveResult(c, names, err)
}
func GetObjects(c *gin.Context) {
	result, err := DB(c).Objects()
	if err != nil {
		badRequest(c, err)
		return
	}
	successResponse(c, client.ObjectsFromResult(result))
}
func GetSchemas(c *gin.Context) {
	res, err := DB(c).Schemas()
	serveResult(c, res, err)
}
func GetTable(c *gin.Context) {
	var res *client.Result
	var err error
	if c.Request.FormValue("type") == "materialized_view" {
		res, err = DB(c).MaterializedView(c.Params.ByName("table"))
	} else {
		res, err = DB(c).Table(c.Params.ByName("table"))
	}
	serveResult(c, res, err)
}
func GetTableRows(c *gin.Context) {
	offset, err := parseIntFormValue(c, "offset", 0)
	if err != nil {
		badRequest(c, err)
		return
	}
	limit, err := parseIntFormValue(c, "limit", 100)
	if err != nil {
		badRequest(c, err)
		return
	}
	opts := client.RowsOptions{
		Limit:      limit,
		Offset:     offset,
		SortColumn: c.Request.FormValue("sort_column"),
		SortOrder:  c.Request.FormValue("sort_order"),
		Where:      c.Request.FormValue("where"),
	}
	res, err := DB(c).TableRows(c.Params.ByName("table"), opts)
	if err != nil {
		badRequest(c, err)
		return
	}
	countRes, err := DB(c).TableRowsCount(c.Params.ByName("table"), opts)
	if err != nil {
		badRequest(c, err)
		return
	}
	numFetch := int64(opts.Limit)
	numOffset := int64(opts.Offset)
	numRows := countRes.Rows[0][0].(int64)
	numPages := numRows / numFetch
	if numPages*numFetch < numRows {
		numPages++
	}
	res.Pagination = &client.Pagination{
		Rows:    numRows,
		Page:    (numOffset / numFetch) + 1,
		Pages:   numPages,
		PerPage: numFetch,
	}
	serveResult(c, res, err)
}
func GetTableInfo(c *gin.Context) {
	res, err := DB(c).TableInfo(c.Params.ByName("table"))
	if err == nil {
		successResponse(c, res.Format()[0])
	} else {
		badRequest(c, err)
	}
}
func GetHistory(c *gin.Context) {
	successResponse(c, DB(c).History)
}
func GetConnectionInfo(c *gin.Context) {
	res, err := DB(c).Info()
	if err != nil {
		badRequest(c, err)
		return
	}
	info := res.Format()[0]
	info["session_lock"] = command.Opts.LockSession
	successResponse(c, info)
}
func GetActivity(c *gin.Context) {
	res, err := DB(c).Activity()
	serveResult(c, res, err)
}
func GetTableIndexes(c *gin.Context) {
	res, err := DB(c).TableIndexes(c.Params.ByName("table"))
	serveResult(c, res, err)
}
func GetTableConstraints(c *gin.Context) {
	res, err := DB(c).TableConstraints(c.Params.ByName("table"))
	serveResult(c, res, err)
}
func HandleQuery(query string, c *gin.Context) {
	rawQuery, err := base64.StdEncoding.DecodeString(desanitize64(query))
	if err == nil {
		query = string(rawQuery)
	}
	result, err := DB(c).Query(query)
	if err != nil {
		badRequest(c, err)
		return
	}
	format := getQueryParam(c, "format")
	filename := getQueryParam(c, "filename")
	if filename == "" {
		filename = fmt.Sprintf("pgweb-%v.%v", time.Now().Unix(), format)
	}
	if format != "" {
		c.Writer.Header().Set("Content-disposition", "attachment;filename="+filename)
	}
	switch format {
	case "csv":
		c.Data(200, "text/csv", result.CSV())
	case "json":
		c.Data(200, "application/json", result.JSON())
	case "xml":
		c.XML(200, result)
	default:
		c.JSON(200, result)
	}
}