Bnei-Baruch/mdb

View on GitHub
api/hierarchy.go

Summary

Maintainability
C
1 day
Test Coverage
package api

import (
    "database/sql"
    "fmt"
    "math"
    "net/http"

    "github.com/lib/pq"
    "github.com/volatiletech/sqlboiler/v4/queries"
    "gopkg.in/gin-gonic/gin.v1"

    "github.com/Bnei-Baruch/mdb/common"
)

// SOURCE_HIERARCHY_SQL args:
// 0,1,3,4 language
// 2 roots clause, "parent_id is NULL", "id = 8392", etc...
// 5 depth (int)
const SOURCE_HIERARCHY_SQL = `
WITH RECURSIVE rec_sources AS (
  SELECT
    s.id, s.uid, s.pattern, s.parent_id, s.position, s.type_id,
    coalesce((SELECT name FROM source_i18n WHERE source_id = s.id AND language = '%s'),
             (SELECT name FROM source_i18n WHERE source_id = s.id AND language = 'he')) "name",
    coalesce((SELECT description FROM source_i18n WHERE source_id = s.id AND language = '%s'),
             (SELECT description FROM source_i18n WHERE source_id = s.id AND language = 'he')) "description",
    1 "depth"
  FROM sources s
  WHERE s.%s
  UNION
  SELECT
    s.id, s.uid, s.pattern, s.parent_id, s.position, s.type_id,
    coalesce((SELECT name FROM source_i18n WHERE source_id = s.id AND language = '%s'),
             (SELECT name FROM source_i18n WHERE source_id = s.id AND language = 'he')) "name",
    coalesce((SELECT description FROM source_i18n WHERE source_id = s.id AND language = '%s'),
             (SELECT description FROM source_i18n WHERE source_id = s.id AND language = 'he')) "description",
    depth + 1
  FROM sources s INNER JOIN rec_sources rs ON s.parent_id = rs.id
  WHERE rs.depth < %d
)
SELECT * FROM rec_sources
ORDER by depth, parent_id, position, name;
`

const AUTHORS_SOURCES_SQL = `
SELECT
  a.code,
  coalesce((SELECT name FROM author_i18n WHERE author_id = a.id AND language = '%s'),
           (SELECT name FROM author_i18n WHERE author_id = a.id AND language = 'he')) "name",
  coalesce((SELECT full_name FROM author_i18n WHERE author_id = a.id AND language = '%s'),
           (SELECT full_name FROM author_i18n WHERE author_id = a.id AND language = 'he')) "full_name",
  (SELECT array_agg(source_id) FROM authors_sources WHERE author_id = a.id GROUP BY author_id) "sources"
FROM authors a;
`

// TAG_HIERARCHY_SQL args:
// 0,2 language
// 1 roots clause, "parent_id is NULL", "id = 8392", etc...
// 3 depth (int)
const TAG_HIERARCHY_SQL = `
WITH RECURSIVE rec_tags AS (
  SELECT
    t.id, t.uid, t.pattern, t.parent_id,
    coalesce((SELECT label FROM tag_i18n WHERE tag_id = t.id AND language = '%s'),
             (SELECT label FROM tag_i18n WHERE tag_id = t.id AND language = 'he')) "label",
    1 "depth"
  FROM tags t
  WHERE t.%s
  UNION
  SELECT
    t.id, t.uid, t.pattern, t.parent_id,
    coalesce((SELECT label FROM tag_i18n WHERE tag_id = t.id AND language = '%s'),
             (SELECT label FROM tag_i18n WHERE tag_id = t.id AND language = 'he')) "label",
    depth + 1
  FROM tags t INNER JOIN rec_tags rt ON t.parent_id = rt.id
  WHERE rt.depth < %d
)
SELECT * FROM rec_tags
ORDER BY depth, parent_id, label;
`

func SourcesHierarchyHandler(c *gin.Context) {
    var r SourcesHierarchyRequest
    if c.Bind(&r) != nil {
        return
    }

    var l string
    if r.Language == "" {
        l = common.LANG_HEBREW
    } else {
        l = r.Language
    }

    var depth int
    if r.Depth == 0 {
        depth = math.MaxInt8
    } else {
        depth = r.Depth
    }

    var rootClause string
    if r.RootUID == "" {
        rootClause = "parent_id IS NULL"
    } else {
        rootClause = fmt.Sprintf("uid = '%s'", r.RootUID)
    }

    // Execute query
    mdb := c.MustGet("MDB").(*sql.DB)
    rsql := fmt.Sprintf(SOURCE_HIERARCHY_SQL, l, l, rootClause, l, l, depth)
    rows, err := queries.Raw(rsql).Query(mdb)
    if err != nil {
        NewInternalError(err).Abort(c)
        return
    }
    defer rows.Close()

    // Iterate rows, build tree
    sources := make(map[int64]*SourceH)
    roots := make([]*SourceH, 0)
    for rows.Next() {
        // Scan source
        s := new(SourceH)
        var typeID, d int64
        err := rows.Scan(&s.ID, &s.UID, &s.Pattern, &s.ParentID, &s.Position, &typeID, &s.Name, &s.Description, &d)
        if err != nil {
            NewInternalError(err).Abort(c)
            return
        }
        s.Type = common.SOURCE_TYPE_REGISTRY.ByID[typeID].Name

        // Attach source to tree
        sources[s.ID] = s
        if s.ParentID.Valid {
            p, ok := sources[s.ParentID.Int64]
            if ok {
                if p.Children == nil {
                    p.Children = make([]*SourceH, 0)
                }
                p.Children = append(p.Children, s)
            } else {
                roots = append(roots, s)
            }
        } else {
            roots = append(roots, s)
        }
    }
    err = rows.Err()
    if err != nil {
        NewInternalError(err).Abort(c)
        return
    }

    if r.RootUID == "" {
        rsql = fmt.Sprintf(AUTHORS_SOURCES_SQL, l, l)
        rows, err := queries.Raw(rsql).Query(mdb)
        if err != nil {
            NewInternalError(err).Abort(c)
            return
        }
        defer rows.Close()

        authors := make([]*AuthorH, 0)
        for rows.Next() {
            a := new(AuthorH)
            var sids pq.Int64Array
            err := rows.Scan(&a.Code, &a.Name, &a.FullName, &sids)
            if err != nil {
                NewInternalError(err).Abort(c)
                return
            }

            // Associate sources
            a.Children = make([]*SourceH, len(sids))
            for i, x := range sids {
                a.Children[i] = sources[x]
            }
            authors = append(authors, a)
        }
        err = rows.Err()
        if err == nil {
            c.JSON(http.StatusOK, authors)
        } else {
            NewInternalError(err).Abort(c)
            return
        }
    } else {
        c.JSON(http.StatusOK, roots)
    }
}

func TagsHierarchyHandler(c *gin.Context) {
    var r TagsHierarchyRequest
    if c.Bind(&r) != nil {
        return
    }

    var l string
    if r.Language == "" {
        l = common.LANG_HEBREW
    } else {
        l = r.Language
    }

    var depth int
    if r.Depth == 0 {
        depth = math.MaxInt8
    } else {
        depth = r.Depth
    }

    var rootClause string
    if r.RootUID == "" {
        rootClause = "parent_id IS NULL"
    } else {
        rootClause = fmt.Sprintf("uid = '%s'", r.RootUID)
    }

    // Execute query
    mdb := c.MustGet("MDB").(*sql.DB)
    rsql := fmt.Sprintf(TAG_HIERARCHY_SQL, l, rootClause, l, depth)
    rows, err := queries.Raw(rsql).Query(mdb)
    if err != nil {
        NewInternalError(err).Abort(c)
        return
    }
    defer rows.Close()

    // Iterate rows, build tree
    tags := make(map[int64]*TagH)
    roots := make([]*TagH, 0)
    for rows.Next() {
        // Scan tag
        t := new(TagH)
        var d int64
        err := rows.Scan(&t.ID, &t.UID, &t.Pattern, &t.ParentID, &t.Label, &d)
        if err != nil {
            NewInternalError(err).Abort(c)
            return
        }

        // Attach tag to tree
        tags[t.ID] = t
        if t.ParentID.Valid {
            p, ok := tags[t.ParentID.Int64]
            if ok {
                if p.Children == nil {
                    p.Children = make([]*TagH, 0)
                }
                p.Children = append(p.Children, t)
            } else {
                roots = append(roots, t)
            }
        } else {
            roots = append(roots, t)
        }
    }
    err = rows.Err()
    if err != nil {
        NewInternalError(err).Abort(c)
        return
    }

    c.JSON(http.StatusOK, roots)
}