func (gm *GMServer) userTagsGet(gmr *GMRequest) (resp interface{}, err error) {
	err = gm.authorizeOperation(gmr.Caller, &authzArgs{OwnerID: gmr.SubjUser.ID})
	if err != nil {
		return nil, errors.Trace(err)
	}

	allowNew := gmr.FormValue(QSArgTagsAllowNew) == "1"

	// By default, use shape "tree"
	shape := QSArgTagsShapeTree

	// Determine pattern: by default, use an empty string
	strpattern := ""
	if t := gmr.FormValue(QSArgTagsPattern); t != "" {
		strpattern = t
	}

	// If querytype is "pattern", change the default shape to "flat"
	if strpattern != "" {
		shape = QSArgTagsShapeFlat
	}

	// If shape was given, use it
	if s := gmr.FormValue(QSArgTagsShape); s != "" {
		if s != QSArgTagsShapeTree && s != QSArgTagsShapeFlat && s != QSArgTagsShapeSingle {
			return nil, errors.Errorf(
				"invalid %s: %q; valid values are: %q, %q",
				QSArgTagsShape, shape, QSArgTagsShapeTree, QSArgTagsShapeFlat,
			)
		}
		shape = s
	}

	if shape != QSArgTagsShapeFlat && strpattern != "" {
		return nil, errors.Errorf("pattern and %s %q cannot be used together", QSArgTagsShape, shape)
	}

	// Get tags tree from either cache or database
	var tagData *storage.TagData
	withSubtags := (shape != QSArgTagsShapeSingle)

	// Get a cached struct for the given user. If it does not exist, create one,
	// and add to the cache.
	cache := userIDToTagsTree.GetCacheForUser(gmr.SubjUser.ID)
	if cache == nil {
		glog.V(3).Infof("No cache for user %d, creating", gmr.SubjUser.ID)
		cache = &cacheTagsTree{
			tagIDToTree: make(map[string]*storage.TagData),
		}
		userIDToTagsTree.SetCacheForUser(gmr.SubjUser.ID, cache)
	}

	// Try to get cached tree data from the cache struct. If cache does not
	// contain what we need, we'll need to reach the database, get tree data
	// from there, and put it to the cache.
	tagPath := pattern.Path(gmr.HttpReq.Context())
	tagData = cache.GetTagData(tagPath, withSubtags)
	if tagData == nil {
		glog.V(3).Infof(
			"No tree data cache for user %d, path=%q, withSubtags=%v, creating",
			gmr.SubjUser.ID, tagPath, withSubtags,
		)
		err = gm.si.Tx(func(tx *sql.Tx) error {
			var parentTagID int
			var err error

			parentTagID, err = gm.getTagIDFromPath(gmr, tx, gmr.SubjUser.ID, false)
			if err != nil {
				return errors.Trace(err)
			}

			tagData, err = gm.si.GetTag(tx, parentTagID, &storage.GetTagOpts{
				GetNames:   true,
				GetSubtags: withSubtags,
			})
			if err != nil {
				return errors.Trace(err)
			}

			return nil
		})
		if err != nil {
			return nil, errors.Trace(err)
		}

		cache.SetTagData(tagPath, withSubtags, tagData)
	} else {
		glog.V(3).Infof(
			"Got tree data cache for user %d, path=%q, withSubtags=%v",
			gmr.SubjUser.ID, tagPath, withSubtags,
		)
	}

	// Convert internal tags tree into the requested shape
	switch shape {

	case QSArgTagsShapeTree, QSArgTagsShapeSingle:
		resp = gm.createUserTagData(tagData)

	case QSArgTagsShapeFlat:
		tagsFlat := gm.createTagDataFlatInternal(tagData, nil, nil)

		if strpattern != "" {
			// Convert a slice to a slice of needed interface (tagmatcher.TagPather)
			tp := make([]tagmatcher.TagPather, len(tagsFlat))
			for i, v := range tagsFlat {
				tp[i] = v
			}

			// Match against the strpattern
			matcher := tagmatcher.NewTagMatcher()
			tp, err = matcher.Filter(tp, strpattern)
			if err != nil {
				return nil, errors.Trace(err)
			}

			// Convert resulting slice back to a slice of tagDataFlatInternal
			tagsFlat = make([]*tagDataFlatInternal, len(tp))
			for i, v := range tp {
				tagsFlat[i] = v.(*tagDataFlatInternal)
			}

			// if there are too many tags, leave only first maxFlatTagsCnt
			//
			// TODO: add a query parameter which should override default limit
			//
			// TODO: probably we need to add a trailing special item which
			// indicates that there are N extra tags, and if user clicks on it,
			// the client would request all tags
			if len(tagsFlat) > maxFlatTagsCnt {
				tagsFlat = tagsFlat[:maxFlatTagsCnt]
			}
		}

		// If new tags are allowed, get a suggestion for the new tag, if any
		var newTag *userTagDataFlat
		if allowNew {
			var err error
			newTag, err = gm.getNewTagSuggestion(gmr, strpattern)
			if err != nil {
				return nil, errors.Trace(err)
			}
		}

		// Convert internal slice to a public slice
		// (and if new tags are allowed, add a suggestion as a second item)
		userTagsFlat := []userTagDataFlat{}
		for i, v := range tagsFlat {
			if allowNew && i == newTagSuggestionIndex && newTag != nil {
				userTagsFlat = append(userTagsFlat, *newTag)
			}
			userTagsFlat = append(userTagsFlat, userTagDataFlat{
				Path:        v.Path(),
				ID:          v.id,
				Description: v.description,
			})
		}

		// if new tags are allowed, but we didn't have a chance to insert a
		// suggestion due to the low number of the existing matching tags,
		// then add a new tag suggestion as the last item
		if allowNew && len(userTagsFlat) <= newTagSuggestionIndex && newTag != nil {
			userTagsFlat = append(userTagsFlat, *newTag)
		}

		resp = userTagsFlat

	default:
		return nil, hh.MakeInternalServerError(errors.Errorf("should never be here"))
	}

	return resp, nil
}
// userTagsGet is a GET /tags and /tags/* handler
userTagsGet is a GET /tags and /tags handler
// userTagsGet is a GET / tags and / tags / * handler
func (gm *GMServer) userTagsGet(gmr *GMRequest) (resp interface{}, err error) {
	err = gm.authorizeOperation(gmr.Caller, &authzArgs{OwnerID: gmr.SubjUser.ID})
	if err != nil {
		return nil, errors.Trace(err)
	}
	allowNew := gmr.FormValue(QSArgTagsAllowNew) == "1"
	shape := QSArgTagsShapeTree
	strpattern := ""
	if t := gmr.FormValue(QSArgTagsPattern); t != "" {
		strpattern = t
	}
	if strpattern != "" {
		shape = QSArgTagsShapeFlat
	}
	if s := gmr.FormValue(QSArgTagsShape); s != "" {
		if s != QSArgTagsShapeTree && s != QSArgTagsShapeFlat && s != QSArgTagsShapeSingle {
			return nil, errors.Errorf(
				"invalid %s: %q; valid values are: %q, %q",
				QSArgTagsShape, shape, QSArgTagsShapeTree, QSArgTagsShapeFlat,
			)
		}
		shape = s
	}
	if shape != QSArgTagsShapeFlat && strpattern != "" {
		return nil, errors.Errorf("pattern and %s %q cannot be used together", QSArgTagsShape, shape)
	}
	var tagData *storage.TagData
	withSubtags := (shape != QSArgTagsShapeSingle)
	cache := userIDToTagsTree.GetCacheForUser(gmr.SubjUser.ID)
	if cache == nil {
		glog.V(3).Infof("No cache for user %d, creating", gmr.SubjUser.ID)
		cache = &cacheTagsTree{
			tagIDToTree: make(map[string]*storage.TagData),
		}
		userIDToTagsTree.SetCacheForUser(gmr.SubjUser.ID, cache)
	}
	tagPath := pattern.Path(gmr.HttpReq.Context())
	tagData = cache.GetTagData(tagPath, withSubtags)
	if tagData == nil {
		glog.V(3).Infof(
			"No tree data cache for user %d, path=%q, withSubtags=%v, creating",
			gmr.SubjUser.ID, tagPath, withSubtags,
		)
		err = gm.si.Tx(func(tx *sql.Tx) error {
			var parentTagID int
			var err error
			parentTagID, err = gm.getTagIDFromPath(gmr, tx, gmr.SubjUser.ID, false)
			if err != nil {
				return errors.Trace(err)
			}
			tagData, err = gm.si.GetTag(tx, parentTagID, &storage.GetTagOpts{
				GetNames:   true,
				GetSubtags: withSubtags,
			})
			if err != nil {
				return errors.Trace(err)
			}
			return nil
		})
		if err != nil {
			return nil, errors.Trace(err)
		}
		cache.SetTagData(tagPath, withSubtags, tagData)
	} else {
		glog.V(3).Infof(
			"Got tree data cache for user %d, path=%q, withSubtags=%v",
			gmr.SubjUser.ID, tagPath, withSubtags,
		)
	}
	switch shape {
	case QSArgTagsShapeTree, QSArgTagsShapeSingle:
		resp = gm.createUserTagData(tagData)
	case QSArgTagsShapeFlat:
		tagsFlat := gm.createTagDataFlatInternal(tagData, nil, nil)
		if strpattern != "" {
			tp := make([]tagmatcher.TagPather, len(tagsFlat))
			for i, v := range tagsFlat {
				tp[i] = v
			}
			matcher := tagmatcher.NewTagMatcher()
			tp, err = matcher.Filter(tp, strpattern)
			if err != nil {
				return nil, errors.Trace(err)
			}
			tagsFlat = make([]*tagDataFlatInternal, len(tp))
			for i, v := range tp {
				tagsFlat[i] = v.(*tagDataFlatInternal)
			}
			 TODO: add a query parameter which should override default limit
			 TODO: probably we need to add a trailing special item which
			 indicates that there are N extra tags, and if user clicks on it,
			 the client would request all tags
			if len(tagsFlat) > maxFlatTagsCnt {
				tagsFlat = tagsFlat[:maxFlatTagsCnt]
			}
		}
		 If new tags are allowed, get a suggestion for the new tag, if any
		var newTag *userTagDataFlat
		if allowNew {
			var err error
			newTag, err = gm.getNewTagSuggestion(gmr, strpattern)
			if err != nil {
				return nil, errors.Trace(err)
			}
		}
		 Convert internal slice to a public slice
		 (and if new tags are allowed, add a suggestion as a second item)
		userTagsFlat := []userTagDataFlat{}
		for i, v := range tagsFlat {
			if allowNew && i == newTagSuggestionIndex && newTag != nil {
				userTagsFlat = append(userTagsFlat, *newTag)
			}
			userTagsFlat = append(userTagsFlat, userTagDataFlat{
				Path:        v.Path(),
				ID:          v.id,
				Description: v.description,
			})
		}
		 if new tags are allowed, but we didn't have a chance to insert a
		 suggestion due to the low number of the existing matching tags,
		 then add a new tag suggestion as the last item
		if allowNew && len(userTagsFlat) <= newTagSuggestionIndex && newTag != nil {
			userTagsFlat = append(userTagsFlat, *newTag)
		}
		resp = userTagsFlat
	default:
		return nil, hh.MakeInternalServerError(errors.Errorf("should never be here"))
	}
	return resp, nil
}
func ( gm * GMServer ) userTagsGet ( gmr * GMRequest ) ( resp interface { } , err error ) { err = gm . authorizeOperation ( gmr . Caller , & authzArgs { OwnerID : gmr . SubjUser . ID } ) if err != nil { return nil , errors . Trace ( err ) } allowNew := gmr . FormValue ( QSArgTagsAllowNew ) == " " shape := QSArgTagsShapeTree strpattern := " " if t := gmr . FormValue ( QSArgTagsPattern ) ; t != " " { strpattern = t } if strpattern != " " { shape = QSArgTagsShapeFlat } if s := gmr . FormValue ( QSArgTagsShape ) ; s != " " { if s != QSArgTagsShapeTree && s != QSArgTagsShapeFlat && s != QSArgTagsShapeSingle { return nil , errors . Errorf ( " " , QSArgTagsShape , shape , QSArgTagsShapeTree , QSArgTagsShapeFlat , ) } shape = s } if shape != QSArgTagsShapeFlat && strpattern != " " { return nil , errors . Errorf ( " " , QSArgTagsShape , shape ) } var tagData * storage . TagData withSubtags := ( shape != QSArgTagsShapeSingle ) cache := userIDToTagsTree . GetCacheForUser ( gmr . SubjUser . ID ) if cache == nil { glog . V ( 3 ) . Infof ( " " , gmr . SubjUser . ID ) cache = & cacheTagsTree { tagIDToTree : make ( map [ string ] * storage . TagData ) , } userIDToTagsTree . SetCacheForUser ( gmr . SubjUser . ID , cache ) } tagPath := pattern . Path ( gmr . HttpReq . Context ( ) ) tagData = cache . GetTagData ( tagPath , withSubtags ) if tagData == nil { glog . V ( 3 ) . Infof ( " " , gmr . SubjUser . ID , tagPath , withSubtags , ) err = gm . si . Tx ( func ( tx * sql . Tx ) error { var parentTagID int var err error parentTagID , err = gm . getTagIDFromPath ( gmr , tx , gmr . SubjUser . ID , false ) if err != nil { return errors . Trace ( err ) } tagData , err = gm . si . GetTag ( tx , parentTagID , & storage . GetTagOpts { GetNames : true , GetSubtags : withSubtags , } ) if err != nil { return errors . Trace ( err ) } return nil } ) if err != nil { return nil , errors . Trace ( err ) } cache . SetTagData ( tagPath , withSubtags , tagData ) } else { glog . V ( 3 ) . Infof ( " " , gmr . SubjUser . ID , tagPath , withSubtags , ) } switch shape { case QSArgTagsShapeTree , QSArgTagsShapeSingle : resp = gm . createUserTagData ( tagData ) case QSArgTagsShapeFlat : tagsFlat := gm . createTagDataFlatInternal ( tagData , nil , nil ) if strpattern != " " { tp := make ( [ ] tagmatcher . TagPather , len ( tagsFlat ) ) for i , v := range tagsFlat { tp [ i ] = v } matcher := tagmatcher . NewTagMatcher ( ) tp , err = matcher . Filter ( tp , strpattern ) if err != nil { return nil , errors . Trace ( err ) } tagsFlat = make ( [ ] * tagDataFlatInternal , len ( tp ) ) for i , v := range tp { tagsFlat [ i ] = v . ( * tagDataFlatInternal ) } if len ( tagsFlat ) > maxFlatTagsCnt { tagsFlat = tagsFlat [ : maxFlatTagsCnt ] } } var newTag * userTagDataFlat if allowNew { var err error newTag , err = gm . getNewTagSuggestion ( gmr , strpattern ) if err != nil { return nil , errors . Trace ( err ) } } userTagsFlat := [ ] userTagDataFlat { } for i , v := range tagsFlat { if allowNew && i == newTagSuggestionIndex && newTag != nil { userTagsFlat = append ( userTagsFlat , * newTag ) } userTagsFlat = append ( userTagsFlat , userTagDataFlat { Path : v . Path ( ) , ID : v . id , Description : v . description , } ) } if allowNew && len ( userTagsFlat ) <= newTagSuggestionIndex && newTag != nil { userTagsFlat = append ( userTagsFlat , * newTag ) } resp = userTagsFlat default : return nil , hh . MakeInternalServerError ( errors . Errorf ( " " ) ) } return resp , nil }
userTagsGet is a GET /tags and /tags handler
userTagsGet is a GET / tags and / tags handler
// By default, use shape "tree" // Determine pattern: by default, use an empty string // If querytype is "pattern", change the default shape to "flat" // If shape was given, use it // Get tags tree from either cache or database // Get a cached struct for the given user. If it does not exist, create one, // and add to the cache. // Try to get cached tree data from the cache struct. If cache does not // contain what we need, we'll need to reach the database, get tree data // from there, and put it to the cache. // Convert internal tags tree into the requested shape // Convert a slice to a slice of needed interface (tagmatcher.TagPather) // Match against the strpattern // Convert resulting slice back to a slice of tagDataFlatInternal // if there are too many tags, leave only first maxFlatTagsCnt // // TODO: add a query parameter which should override default limit // // TODO: probably we need to add a trailing special item which // indicates that there are N extra tags, and if user clicks on it, // the client would request all tags // If new tags are allowed, get a suggestion for the new tag, if any // Convert internal slice to a public slice // (and if new tags are allowed, add a suggestion as a second item) // if new tags are allowed, but we didn't have a chance to insert a // suggestion due to the low number of the existing matching tags, // then add a new tag suggestion as the last item