github.com/bitcubate/cryptojournal@v1.2.5-0.20171102134152-f578b3d788ab/src/stories/story.go (about) 1 // Package stories represents the story resource 2 package stories 3 4 import ( 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/fragmenta/model/file" 10 11 "github.com/bitcubate/cryptojournal/src/lib/resource" 12 "github.com/bitcubate/cryptojournal/src/lib/status" 13 ) 14 15 // Story handles saving and retreiving stories from the database 16 type Story struct { 17 // resource.Base defines behaviour and fields shared between all resources 18 resource.Base 19 20 // status.ResourceStatus defines a status field and associated behaviour 21 status.ResourceStatus 22 23 CommentCount int64 24 Name string 25 Summary string 26 URL string 27 Points int64 28 Rank int64 29 UserID int64 30 31 // UserName denormalises the user name - use join instead? 32 UserName string 33 } 34 35 // NegativePoints returns a negative point score or 0 if points is above 0 36 func (s *Story) NegativePoints() int64 { 37 if s.Points > 0 { 38 return 0 39 } 40 return -s.Points 41 } 42 43 // Domain returns the domain of the story URL 44 func (s *Story) Domain() string { 45 parts := strings.Split(s.URL, "/") 46 if len(parts) > 2 { 47 return strings.Replace(parts[2], "www.", "", 1) 48 } 49 50 if len(s.URL) > 0 { 51 return s.URL 52 } 53 54 return "CJ" 55 } 56 57 // ShowAsk returns true if this is a Show: or Ask: story 58 func (s *Story) ShowAsk() bool { 59 return strings.HasPrefix(s.Name, "Show:") || strings.HasPrefix(s.Name, "Ask:") 60 } 61 62 // DestinationURL returns the URL of the story 63 // if no url is set, it uses the CanonicalURL 64 func (s *Story) DestinationURL() string { 65 // If downvoted, don't publish urls 66 if s.Points < 0 { 67 return "" 68 } 69 // If we have an external url, return it 70 if s.URL != "" { 71 return s.URL 72 } 73 // If we have an empty url, return the story url instead 74 return s.CanonicalURL() 75 } 76 77 // PrimaryURL returns the URL to use for this story in lists 78 // Videos and Show Ask stories link to the story 79 // for other links for now it is the destination 80 func (s *Story) PrimaryURL() string { 81 // If video or show or empty, return story url 82 if s.YouTube() || s.ShowAsk() || s.URL == "" { 83 return s.CanonicalURL() 84 } 85 86 return s.DestinationURL() 87 } 88 89 // CanonicalURL is the canonical URL of the story on this site 90 // including a slug for seo 91 func (s *Story) CanonicalURL() string { 92 return fmt.Sprintf("/stories/%d-%s", s.ID, file.SanitizeName(s.Name)) 93 } 94 95 // Code returns true if this is a link to a git repository 96 // At present we only check for github urls, we should at least check for bitbucket 97 func (s *Story) Code() bool { 98 if strings.Contains(s.URL, "https://github.com") { 99 if strings.Contains(s.URL, "/commit/") || strings.Contains(s.URL, "/releases/") || strings.HasSuffix(s.URL, ".md") { 100 return false 101 } 102 return true 103 } 104 return false 105 } 106 107 // GodocURL returns the godoc.org URL for this story, or empty string if none 108 func (s *Story) GodocURL() string { 109 if s.Code() { 110 return strings.Replace(s.URL, "https://github.com", "https://godoc.org/github.com", 1) 111 } 112 return "" 113 } 114 115 // VetURL returns a URL for goreportcard.com, for code repos 116 func (s *Story) VetURL() string { 117 if s.Code() { 118 return strings.Replace(s.URL, "https://github.com/", "http://goreportcard.com/report/", 1) 119 } 120 return "" 121 } 122 123 // YouTube returns true if this is a youtube video 124 func (s *Story) YouTube() bool { 125 return strings.Contains(s.URL, "youtube.com/watch?v=") 126 } 127 128 // YouTubeURL returns the youtube URL 129 func (s *Story) YouTubeURL() string { 130 url := strings.Replace(s.URL, "https://s.youtube.com", "https://www.youtube.com", 1) 131 // https://www.youtube.com/watch?v=sZx3oZt7LVg -> 132 // https://www.youtube.com/embed/sZx3oZt7LVg 133 url = strings.Replace(url, "watch?v=", "embed/", 1) 134 return url 135 } 136 137 // CommentCountDisplay returns the comment count or ellipsis if count is 0 138 func (s *Story) CommentCountDisplay() string { 139 if s.CommentCount > 0 { 140 return fmt.Sprintf("%d", s.CommentCount) 141 } 142 return "…" 143 } 144 145 // NameDisplay returns a title string without hashtags (assumed to be at the end), 146 // by truncating the title at the first # 147 func (s *Story) NameDisplay() string { 148 if strings.Contains(s.Name, "#") { 149 return s.Name[0:strings.Index(s.Name, "#")] 150 } 151 return s.Name 152 } 153 154 // Tags are defined as words beginning with # in the title 155 // TODO: for speed and clarity we could extract at submit time instead and store in db 156 func (s *Story) Tags() []string { 157 var tags []string 158 if strings.Contains(s.Name, "#") { 159 // split on " #" 160 parts := strings.Split(s.Name, " #") 161 tags = parts[1:] 162 } 163 return tags 164 } 165 166 // Editable returns true if this story is editable. 167 // Stories are editable if less than 1 hours old 168 func (s *Story) Editable() bool { 169 return time.Now().Sub(s.CreatedAt) < time.Hour*1 170 } 171 172 // OwnedBy returns true if this user id owns this story. 173 func (s *Story) OwnedBy(uid int64) bool { 174 return uid == s.UserID 175 }