~hokiegeek/ytpulse

2c784acbd13343cb86a523d3993cbce1f8cc1ef0 — HokieGeek 3 months ago 32f2723
refactored so that can have subtypes of collections for pulse
6 files changed, 412 insertions(+), 125 deletions(-)

M go.mod
M go.sum
M program.go
M pulse.go
M serve.go
M session.go
M go.mod => go.mod +1 -2
@@ 19,10 19,9 @@ require (
	golang.org/x/mod v0.4.2 // indirect
	golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
	golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84
	golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d // indirect
	google.golang.org/api v0.42.0 // indirect
	google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
)

// replace git.sr.ht/~hokiegeek/ytlibrarian => ../ytlibrarian
replace git.sr.ht/~hokiegeek/ytlibrarian => ../ytlibrarian

M go.sum => go.sum +2 -0
@@ 398,6 398,8 @@ golang.org/x/sys v0.0.0-20210314195730-07df6a141424/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

M program.go => program.go +8 -0
@@ 51,6 51,12 @@ func ytlCollectionAsProgram(ytlc ytlibrarian.Collection) (Program, error) {
	return p, err
}

/*
type programCollection struct {
	pulseCollection
}
*/

// ProgramDays collects the items within a Program
type ProgramDays struct {
	ProgramID string            `json:"program_id"`


@@ 177,6 183,7 @@ func UpdateProgram(librarian *ytlibrarian.Librarian, program Program, user User)
}

// AddProgram retrieves program details from a Tuber and adds a Program Thing to the given Thinger
/*
func AddProgram(librarian *ytlibrarian.Librarian, authToken string, user User, id string) (program Program, err error) {
	tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
	if err != nil {


@@ 194,6 201,7 @@ func AddProgram(librarian *ytlibrarian.Librarian, authToken string, user User, i

	return sesh, nil
}
*/

// GetProgramByID retrieves the program which matches the given ID
func GetProgramByID(librarian *ytlibrarian.Librarian, user User, id uuid.UUID) (Program, error) {

M pulse.go => pulse.go +245 -0
@@ 1,5 1,21 @@
package ytpulse

import (
	"encoding/json"
	"fmt"
	"log"

	"git.sr.ht/~hokiegeek/ytlibrarian"
	"github.com/google/uuid"
	"golang.org/x/oauth2"
)

const (
	pulseCollectionSession    = "session"
	pulseCollectionProgram    = "program"
	pulseCollectionProgramDay = "programDay"
)

type userData struct {
	ActivityType string   `json:"activityType,omitempty"`
	Tags         []string `json:"tags,omitempty"`


@@ 8,3 24,232 @@ type userData struct {
	SkillLevel   int      `json:"skillLevel,omitempty"`
	Comments     string   `json:"comments,omitempty"`
}

type pulseCollection struct {
	ytlibrarian.Collection
	Subtype  string   `json:"subtype,omitempty"`
	UserData userData `json:"userData,omitempty"`
}

func (p pulseCollection) asYtlCollection() (ytlibrarian.Collection, error) {
	var ytl ytlibrarian.Collection
	ytl.ID = p.ID
	ytl.ReferenceID = p.ReferenceID
	ytl.Description = p.Description
	ytl.Title = p.Title
	ytl.ThumbnailURLs = p.ThumbnailURLs
	ytl.DurationSecs = p.DurationSecs
	ytl.PrivacyStatus = p.PrivacyStatus
	ytl.Source = p.Source
	ytl.URL = p.URL

	// deets, err := json.Marshal(&p.UserData)
	deets, err := json.Marshal(&p)
	ytl.Data = string(deets)
	return ytl, err
}

func ytlCollectionAsPulseCollection(ytlc ytlibrarian.Collection) (pulseCollection, error) {
	var p pulseCollection
	log.Println("afp: >>>>> ytlCollectionAsPulseCollection: ytlc", ytlc)
	log.Println("afp: >>>>> ytlCollectionAsPulseCollection: ytlc.Data", ytlc.Data)

	var err error
	if ytlc.Data != "" {
		err = json.Unmarshal([]byte(ytlc.Data), &p)
	}

	log.Println("afp: >>>>> ytlCollectionAsPulseCollection: p.Subtype", p.Subtype)

	p.ID = ytlc.ID
	p.ReferenceID = ytlc.ReferenceID
	p.Description = ytlc.Description
	p.Title = ytlc.Title
	p.ThumbnailURLs = ytlc.ThumbnailURLs
	p.DurationSecs = ytlc.DurationSecs
	p.PrivacyStatus = ytlc.PrivacyStatus
	p.Source = ytlc.Source
	p.URL = ytlc.URL

	return p, err
}

func createPulseCollection(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) (pulseCollection, error) {
	log.Println("afp: createPC: pulseColl ", pulseColl)
	pcAsColl, err := pulseColl.asYtlCollection()
	if err != nil {
		return pulseCollection{}, fmt.Errorf("could not convert pulse collection to Collection: %v", err)
	}
	log.Println("afp: createPC: pcAsColl ", pcAsColl)
	log.Println("afp: createPC: pcAsColl.Data ", pcAsColl.Data)

	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.CreateCollection(librarian.Thinger, pcAsColl, ytluser)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("librarian did not add pulse collection: %v", err)
	}
	log.Println("afp: createPC: ytlc ", ytlcollection)
	pc, err := ytlCollectionAsPulseCollection(ytlcollection)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("error processing Collection thing: %v", err)
	}
	log.Println("afp: createPC(done): pc ", pc)

	return pc, nil
}

func updatePulseCollection(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) (pulseCollection, error) {
	ytluser := user.asYtlUser()
	pcAsColl, err := pulseColl.asYtlCollection()
	if err != nil {
		return pulseColl, err
	}
	ytlcollection, err := ytlibrarian.UpdateCollection(librarian.Thinger, pcAsColl, ytluser)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("librarian did not update pulse collection: %v", err)
	}

	pc, err := ytlCollectionAsPulseCollection(ytlcollection)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("error processing Collection thing: %v", err)
	}

	return pc, nil
}

func addPulseCollection(librarian *ytlibrarian.Librarian, authToken string, user User, id string) (pc pulseCollection, err error) {
	tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
	if err != nil {
		return pulseCollection{}, fmt.Errorf("error instantiating Tuber: %v", err)
	}
	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.AddCollection(librarian.Thinger, tuber, ytluser, id)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("librarian did not add pulse collection: %v", err)
	}
	pc, err = ytlCollectionAsPulseCollection(ytlcollection)
	if err != nil {
		return pulseCollection{}, fmt.Errorf("error processing Collection thing: %v", err)
	}

	return pc, nil
}

func getPulseCollectionByID(librarian *ytlibrarian.Librarian, user User, id uuid.UUID) (pulseCollection, error) {
	ytluser := user.asYtlUser()
	ytlcollect, err := ytlibrarian.GetCollectionByID(librarian.Thinger, ytluser, id)
	if err != nil {
		return pulseCollection{}, err //fmt.Errorf("librarian failure retrieving Collection: %v", err)
	}
	coll, err := ytlCollectionAsPulseCollection(ytlcollect)
	if err != nil {
		return pulseCollection{}, err //fmt.Errorf("error processing librarian Collection: %v", err)
	}
	return coll, nil
}

func getPulseCollectionByReferenceID(librarian *ytlibrarian.Librarian, user User, refID string) (pulseCollection, error) {
	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.GetCollectionByReferenceID(librarian.Thinger, ytluser, refID)
	if err != nil {
		return pulseCollection{}, err
	}
	coll, err := ytlCollectionAsPulseCollection(ytlcollection)
	if err != nil {
		return pulseCollection{}, err // TODO
	}
	return coll, nil
}

func getPulseCollectionsByUser(librarian *ytlibrarian.Librarian, user User) ([]pulseCollection, error) {
	ytluser := user.asYtlUser()
	ytlcollections, err := ytlibrarian.GetCollectionsByUser(librarian.Thinger, ytluser)
	if err != nil {
		return nil, err
	}

	var bulkErr ytlibrarian.BulkItemError
	collections := make([]pulseCollection, len(ytlcollections))
	for i, ytc := range ytlcollections {
		collections[i], err = ytlCollectionAsPulseCollection(ytc)
		if err != nil {
			bulkErr.IDs = append(bulkErr.IDs, ytc.ID.String())
			bulkErr.Errors = append(bulkErr.Errors, err)
			continue
		}
	}
	if len(bulkErr.Errors) > 0 {
		return nil, &bulkErr
	}

	return collections, nil
}

func linkPulseCollectionToUser(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User, details string) error {
	ytlcollection, err := pulseColl.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.LinkCollectionToUser(librarian.Thinger, ytlcollection, ytluser, details)
}

func unlinkPulseCollectionAndUser(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) error {
	ytlcollection, err := pulseColl.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.UnlinkCollectionAndUser(librarian.Thinger, ytlcollection, ytluser)
}

func removePulseCollectionByID(librarian *ytlibrarian.Librarian, id uuid.UUID) error {
	return ytlibrarian.RemoveCollectionByID(librarian.Thinger, id)
}

func updatePulseCollectionCustomData(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) error {
	ytlcollection, err := pulseColl.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.UpdateCollectionCustomData(librarian.Thinger, ytlcollection, ytluser, ytlcollection.Data)
}

func getUserPulseCollectionCustomData(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) (string, error) {
	ytlcollection, err := pulseColl.asYtlCollection()
	if err != nil {
		return "", err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.GetUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
}

func removeUserPulseCollectionCustomData(librarian *ytlibrarian.Librarian, pulseColl pulseCollection, user User) error {
	ytlcollection, err := pulseColl.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.RemoveUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
}

func publishPulseCollection(librarian *ytlibrarian.Librarian, user User, authToken string, id uuid.UUID) (pulseCollection, error) {
	tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
	if err != nil {
		return pulseCollection{}, fmt.Errorf("error instantiating Tuber: %v", err)
	}

	ytluser := user.asYtlUser()
	collection, err := ytlibrarian.PublishCollection(librarian.Thinger, tuber, ytluser, id)
	if err != nil {
		return pulseCollection{}, err
	}

	pc, err := ytlCollectionAsPulseCollection(collection)
	if err != nil {
		return pulseCollection{}, err
	}

	return pc, nil
}

M serve.go => serve.go +31 -16
@@ 14,6 14,7 @@ import (
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"

	"git.sr.ht/~hokiegeek/ytlibrarian"
)


@@ 121,6 122,18 @@ func ListenAndServe(port string) error {
	return router.Run(port)
}

func tokenFromGoogleAuthCode(clientID, secret, code string) (*oauth2.Token, error) {
	conf := &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: secret,
		RedirectURL:  "postmessage",
		Endpoint:     google.Endpoint,
	}

	// exchange auth_code to token including refresh_token
	return conf.Exchange(oauth2.NoContext, code)
}

func isAPIKeyValid(receivedKey string) bool {
	apiKey := os.Getenv("YTLIBRARIAN_API_KEY")
	return apiKey == receivedKey


@@ 239,7 252,7 @@ func authorizeUser(c *gin.Context) {
		clientID     = os.Getenv("OAUTH_CLIENT_ID")
		clientSecret = os.Getenv("OAUTH_CLIENT_SECRET")
	)
	token, err := ytlibrarian.TokenFromGoogleAuthCode(clientID, clientSecret, oauthCode)
	token, err := tokenFromGoogleAuthCode(clientID, clientSecret, oauthCode)
	if err != nil {
		if oauthCode == "" {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})


@@ 1417,21 1430,23 @@ func getOrAddRequestedProgram(c *gin.Context, librarian *ytlibrarian.Librarian, 
	if err != nil { // if we had an error
		var nterr *ytlibrarian.ThingNotFoundError
		if errors.As(err, &nterr) { // ... and it's because the video thing wasn't found
			if c.DefaultQuery("refid", "false") == "true" { // ... and the lookup was by the external ID
				// ... then go ahead and download it
				program, err = AddProgram(librarian, oauthtoken, user, c.Param("id"))
				if err != nil {
					log.Printf("warning: could not add new Program to thinger: %v\n", err)
					c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
					return
				}

				// ... but don't tell them because they need to explicitly link the user to this resource
				// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "user not authorized to retrieve video"})
			} else {
				c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
				return
			}
			/*
				if c.DefaultQuery("refid", "false") == "true" { // ... and the lookup was by the external ID
					// ... then go ahead and download it
					program, err = AddProgram(librarian, oauthtoken, user, c.Param("id"))
					if err != nil {
						log.Printf("warning: could not add new Program to thinger: %v\n", err)
						c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
						return
					}

					// ... but don't tell them because they need to explicitly link the user to this resource
					// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "user not authorized to retrieve video"})
				} else {
			*/
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
			return
			// }
		} else {
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return

M session.go => session.go +125 -107
@@ 3,6 3,7 @@ package ytpulse
import (
	"encoding/json"
	"fmt"
	"log"

	"git.sr.ht/~hokiegeek/ytlibrarian"
	"github.com/google/uuid"


@@ 15,6 16,24 @@ type Session struct {
	UserData userData `json:"userData,omitempty"`
}

func (s Session) asPulseCollection() pulseCollection {
	var pc pulseCollection
	pc.ID = s.ID
	pc.ReferenceID = s.ReferenceID
	pc.Description = s.Description
	pc.Title = s.Title
	pc.ThumbnailURLs = s.ThumbnailURLs
	pc.DurationSecs = s.DurationSecs
	pc.PrivacyStatus = s.PrivacyStatus
	pc.Source = s.Source
	pc.URL = s.URL

	pc.Subtype = pulseCollectionSession
	pc.UserData = s.UserData

	return pc
}

func (s Session) asYtlCollection() (ytlibrarian.Collection, error) {
	var ytl ytlibrarian.Collection
	ytl.ID = s.ID


@@ 51,6 70,30 @@ func ytlCollectionAsSession(ytlc ytlibrarian.Collection) (Session, error) {
	return s, err
}

func (p pulseCollection) asSession() Session {
	var s Session
	s.ID = p.ID
	s.ReferenceID = p.ReferenceID
	s.Description = p.Description
	s.Title = p.Title
	s.ThumbnailURLs = p.ThumbnailURLs
	s.DurationSecs = p.DurationSecs
	s.PrivacyStatus = p.PrivacyStatus
	s.Source = p.Source
	s.URL = p.URL
	s.UserData = p.UserData

	return s

	/*
		var err error
		if p.Data != "" {
			err = json.Unmarshal([]byte(p.UserData), &s.UserData)
		}
		return s, err
	*/
}

// SessionItems collects the items within a collection
type SessionItems struct {
	SessionID string        `json:"sessionId"`


@@ 74,107 117,68 @@ type SessionItem struct {

// CreateSession creates a new session in the thinger that is not associated with an external thing
func CreateSession(librarian *ytlibrarian.Librarian, session Session, user User) (Session, error) {
	seshCollection, err := session.asYtlCollection()
	if err != nil {
		return Session{}, fmt.Errorf("could not convert Session to Collection: %v", err)
	}

	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.CreateCollection(librarian.Thinger, seshCollection, ytluser)
	pc, err := createPulseCollection(librarian, session.asPulseCollection(), user)
	if err != nil {
		return Session{}, fmt.Errorf("librarian did not add Session: %v", err)
	}
	sesh, err := ytlCollectionAsSession(ytlcollection)
	if err != nil {
		return Session{}, fmt.Errorf("error processing Collection thing: %v", err)
		return Session{}, err
	}

	return sesh, nil
	return pc.asSession(), nil
}

// UpdateSession updates an existing collection in the thinger
func UpdateSession(librarian *ytlibrarian.Librarian, session Session, user User) (Session, error) {
	ytluser := user.asYtlUser()
	seshcollect, err := session.asYtlCollection()
	if err != nil {
		return session, err
	}
	ytlcollection, err := ytlibrarian.UpdateCollection(librarian.Thinger, seshcollect, ytluser)
	if err != nil {
		return Session{}, fmt.Errorf("librarian did not update Session: %v", err)
	}

	sesh, err := ytlCollectionAsSession(ytlcollection)
	pc, err := updatePulseCollection(librarian, session.asPulseCollection(), user)
	if err != nil {
		return Session{}, fmt.Errorf("error processing Collection thing: %v", err)
		return Session{}, err
	}

	return sesh, nil
	return pc.asSession(), nil
}

// AddSession retrieves session details from a Tuber and adds a Session Thing to the given Thinger
func AddSession(librarian *ytlibrarian.Librarian, authToken string, user User, id string) (session Session, err error) {
	tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
	if err != nil {
		return Session{}, fmt.Errorf("error instantiating Tuber: %v", err)
	}
	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.AddCollection(librarian.Thinger, tuber, ytluser, id)
	if err != nil {
		return Session{}, fmt.Errorf("librarian did not add Session: %v", err)
	}
	sesh, err := ytlCollectionAsSession(ytlcollection)
	pc, err := addPulseCollection(librarian, authToken, user, id)
	if err != nil {
		return Session{}, fmt.Errorf("error processing Collection thing: %v", err)
		return Session{}, err
	}

	return sesh, nil
	return pc.asSession(), nil
}

// GetSessionByID retrieves the session which matches the given ID
func GetSessionByID(librarian *ytlibrarian.Librarian, user User, id uuid.UUID) (Session, error) {
	ytluser := user.asYtlUser()
	ytlcollect, err := ytlibrarian.GetCollectionByID(librarian.Thinger, ytluser, id)
	if err != nil {
		return Session{}, err //fmt.Errorf("librarian failure retrieving Collection: %v", err)
	}
	sesh, err := ytlCollectionAsSession(ytlcollect)
	pc, err := getPulseCollectionByID(librarian, user, id)
	if err != nil {
		return Session{}, err //fmt.Errorf("error processing librarian Collection: %v", err)
		return Session{}, err
	}
	return sesh, nil
	return pc.asSession(), nil
}

// GetSessionByReferenceID retrieves the user thing by the given external ID
func GetSessionByReferenceID(librarian *ytlibrarian.Librarian, user User, refID string) (Session, error) {
	ytluser := user.asYtlUser()
	ytlcollection, err := ytlibrarian.GetCollectionByReferenceID(librarian.Thinger, ytluser, refID)
	pc, err := getPulseCollectionByReferenceID(librarian, user, refID)
	if err != nil {
		return Session{}, err
	}
	sesh, err := ytlCollectionAsSession(ytlcollection)
	if err != nil {
		return Session{}, err // TODO
	}
	return sesh, nil
	return pc.asSession(), nil
}

// GetSessionsByUser retrieves the sessions which belong to the given user
func GetSessionsByUser(librarian *ytlibrarian.Librarian, user User) ([]Session, error) {
	ytluser := user.asYtlUser()
	ytlcollections, err := ytlibrarian.GetCollectionsByUser(librarian.Thinger, ytluser)
	pcs, err := getPulseCollectionsByUser(librarian, user)
	if err != nil {
		return nil, err
	}

	var bulkErr ytlibrarian.BulkItemError
	sessions := make([]Session, len(ytlcollections))
	for i, ytc := range ytlcollections {
		sessions[i], err = ytlCollectionAsSession(ytc)
		if err != nil {
			bulkErr.IDs = append(bulkErr.IDs, ytc.ID.String())
			bulkErr.Errors = append(bulkErr.Errors, err)
			continue
	sessions := make([]Session, 0)
	for _, pc := range pcs {
		log.Println("afp: got pc:", pc)
		log.Println("afp: got pc subtype:", pc.Subtype)
		if pc.Subtype == pulseCollectionSession {
			sessions = append(sessions, pc.asSession())
			if err != nil {
				bulkErr.IDs = append(bulkErr.IDs, pc.ID.String())
				bulkErr.Errors = append(bulkErr.Errors, err)
				continue
			}
		}
	}
	if len(bulkErr.Errors) > 0 {


@@ 186,22 190,12 @@ func GetSessionsByUser(librarian *ytlibrarian.Librarian, user User) ([]Session, 

// LinkSessionToUser adds a relationship between a user and a given session as well as the items
func LinkSessionToUser(librarian *ytlibrarian.Librarian, session Session, user User, details string) error {
	ytlcollection, err := session.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.LinkCollectionToUser(librarian.Thinger, ytlcollection, ytluser, details)
	return linkPulseCollectionToUser(librarian, session.asPulseCollection(), user, details)
}

// UnlinkSessionAndUser removes relationship between a session and a user
func UnlinkSessionAndUser(librarian *ytlibrarian.Librarian, session Session, user User) error {
	ytlcollection, err := session.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.UnlinkCollectionAndUser(librarian.Thinger, ytlcollection, ytluser)
	return unlinkPulseCollectionAndUser(librarian, session.asPulseCollection(), user)
}

// RemoveSessionByID removes the identified session from the Thinger


@@ 211,32 205,48 @@ func RemoveSessionByID(librarian *ytlibrarian.Librarian, id uuid.UUID) error {

// UpdateSessionCustomData links the given data to a Session on behalf of a User
func UpdateSessionCustomData(librarian *ytlibrarian.Librarian, session Session, user User) error {
	ytlcollection, err := session.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.UpdateCollectionCustomData(librarian.Thinger, ytlcollection, ytluser, ytlcollection.Data)
	return updatePulseCollectionCustomData(librarian, session.asPulseCollection(), user)
	/*
		pc, err := getPulseCollectionByReferenceID(librarian, user, refID)
		if err != nil {
			return Session{}, err
		}
		return pc.asSession(), nil
	*/
	/*
		ytlcollection, err := session.asYtlCollection()
		if err != nil {
			return err
		}
		ytluser := user.asYtlUser()
		return ytlibrarian.UpdateCollectionCustomData(librarian.Thinger, ytlcollection, ytluser, ytlcollection.Data)
	*/
}

// GetUserSessionCustomData retrieves the given data to a Session on behalf of a User
func GetUserSessionCustomData(librarian *ytlibrarian.Librarian, session Session, user User) (string, error) {
	ytlcollection, err := session.asYtlCollection()
	if err != nil {
		return "", err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.GetUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
	return getUserPulseCollectionCustomData(librarian, session.asPulseCollection(), user)
	/*
		ytlcollection, err := session.asYtlCollection()
		if err != nil {
			return "", err
		}
		ytluser := user.asYtlUser()
		return ytlibrarian.GetUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
	*/
}

// RemoveUserSessionCustomData does what it says on the tin
func RemoveUserSessionCustomData(librarian *ytlibrarian.Librarian, session Session, user User) error {
	ytlcollection, err := session.asYtlCollection()
	if err != nil {
		return err
	}
	ytluser := user.asYtlUser()
	return ytlibrarian.RemoveUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
	return removeUserPulseCollectionCustomData(librarian, session.asPulseCollection(), user)
	/*
		ytlcollection, err := session.asYtlCollection()
		if err != nil {
			return err
		}
		ytluser := user.asYtlUser()
		return ytlibrarian.RemoveUserCollectionCustomData(librarian.Thinger, ytlcollection, ytluser)
	*/
}

// GetSessionItems retrieves the session which matches the given ID


@@ 305,23 315,31 @@ func AppendSessionItem(librarian *ytlibrarian.Librarian, user User, sessionID, i

// PublishSession inserts or updates the identified session to the given tuber service
func PublishSession(librarian *ytlibrarian.Librarian, user User, authToken string, id uuid.UUID) (Session, error) {
	tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
	if err != nil {
		return Session{}, fmt.Errorf("error instantiating Tuber: %v", err)
	}

	ytluser := user.asYtlUser()
	collection, err := ytlibrarian.PublishCollection(librarian.Thinger, tuber, ytluser, id)
	pc, err := publishPulseCollection(librarian, user, authToken, id)
	if err != nil {
		return Session{}, err
	}

	session, err := ytlCollectionAsSession(collection)
	if err != nil {
		return Session{}, err
	}
	return pc.asSession(), nil
	/*
		tuber, err := librarian.CreateTuber(&oauth2.Token{RefreshToken: authToken})
		if err != nil {
			return Session{}, fmt.Errorf("error instantiating Tuber: %v", err)
		}

		ytluser := user.asYtlUser()
		collection, err := ytlibrarian.PublishCollection(librarian.Thinger, tuber, ytluser, id)
		if err != nil {
			return Session{}, err
		}

		session, err := ytlCollectionAsSession(collection)
		if err != nil {
			return Session{}, err
		}

	return session, nil
		return session, nil
	*/
}

// UnpublishSession inserts or updates the identified session to the given tuber service